-
Notifications
You must be signed in to change notification settings - Fork 619
/
receipt.rs
576 lines (527 loc) · 19.7 KB
/
receipt.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
use crate::hash::CryptoHash;
use crate::serialize::dec_format;
use crate::transaction::{Action, TransferAction};
use crate::types::{AccountId, Balance, BlockHeight, ShardId};
use borsh::{BorshDeserialize, BorshSerialize};
use near_crypto::{KeyType, PublicKey};
use near_fmt::AbbrBytes;
use near_schema_checker_lib::ProtocolSchema;
use serde_with::base64::Base64;
use serde_with::serde_as;
use std::borrow::Borrow;
use std::collections::{BTreeMap, HashMap};
use std::fmt;
use std::io::{self, Read};
use std::io::{Error, ErrorKind};
/// The outgoing (egress) data which will be transformed
/// to a `DataReceipt` to be sent to a `receipt.receiver`
#[derive(
BorshSerialize,
BorshDeserialize,
Hash,
Clone,
Debug,
PartialEq,
Eq,
serde::Serialize,
serde::Deserialize,
ProtocolSchema,
)]
pub struct DataReceiver {
pub data_id: CryptoHash,
pub receiver_id: AccountId,
}
/// Receipts are used for a cross-shard communication.
/// Receipts could be 2 types (determined by a `ReceiptEnum`): `ReceiptEnum::Action` of `ReceiptEnum::Data`.
#[derive(
BorshSerialize,
BorshDeserialize,
Debug,
PartialEq,
Eq,
Clone,
serde::Serialize,
serde::Deserialize,
ProtocolSchema,
)]
pub struct ReceiptV0 {
/// An issuer account_id of a particular receipt.
/// `predecessor_id` could be either `Transaction` `signer_id` or intermediate contract's `account_id`.
pub predecessor_id: AccountId,
/// `receiver_id` is a receipt destination.
pub receiver_id: AccountId,
/// An unique id for the receipt
pub receipt_id: CryptoHash,
/// A receipt type
pub receipt: ReceiptEnum,
}
#[derive(
BorshSerialize,
BorshDeserialize,
Debug,
PartialEq,
Eq,
Clone,
serde::Serialize,
serde::Deserialize,
ProtocolSchema,
)]
pub struct ReceiptV1 {
/// An issuer account_id of a particular receipt.
/// `predecessor_id` could be either `Transaction` `signer_id` or intermediate contract's `account_id`.
pub predecessor_id: AccountId,
/// `receiver_id` is a receipt destination.
pub receiver_id: AccountId,
/// An unique id for the receipt
pub receipt_id: CryptoHash,
/// A receipt type
pub receipt: ReceiptEnum,
/// Priority of a receipt
pub priority: u64,
}
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize, ProtocolSchema)]
#[serde(untagged)]
pub enum Receipt {
V0(ReceiptV0),
V1(ReceiptV1),
}
impl BorshSerialize for Receipt {
fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
match self {
Receipt::V0(receipt) => receipt.serialize(writer),
Receipt::V1(receipt) => {
BorshSerialize::serialize(&1_u8, writer)?;
receipt.serialize(writer)
}
}
}
}
impl BorshDeserialize for Receipt {
/// Deserialize based on the first and second bytes of the stream. For V0, we do backward compatible deserialization by deserializing
/// the entire stream into V0. For V1, we consume the first byte and then deserialize the rest.
fn deserialize_reader<R: Read>(reader: &mut R) -> std::io::Result<Self> {
let u1 = u8::deserialize_reader(reader)?;
let u2 = u8::deserialize_reader(reader)?;
let u3 = u8::deserialize_reader(reader)?;
let u4 = u8::deserialize_reader(reader)?;
// This is a ridiculous hackery: because the first field in `ReceiptV0` is an `AccountId`
// and an account id is at most 64 bytes, for all valid `ReceiptV0` the second byte must be 0
// because of the littel endian encoding of the length of the account id.
// On the other hand, for `ReceiptV0`, since the first byte is 1 and an account id must have nonzero
// length, so the second byte must not be zero. Therefore, we can distinguish between the two versions
// by looking at the second byte.
let read_predecessor_id = |buf: [u8; 4], reader: &mut R| -> std::io::Result<AccountId> {
let str_len = u32::from_le_bytes(buf);
let mut str_vec = Vec::with_capacity(str_len as usize);
for _ in 0..str_len {
str_vec.push(u8::deserialize_reader(reader)?);
}
AccountId::try_from(String::from_utf8(str_vec).map_err(|_| {
Error::new(ErrorKind::InvalidData, "Failed to parse AccountId from bytes")
})?)
.map_err(|e| Error::new(ErrorKind::InvalidData, e.to_string()))
};
if u2 == 0 {
let signer_id = read_predecessor_id([u1, u2, u3, u4], reader)?;
let receiver_id = AccountId::deserialize_reader(reader)?;
let receipt_id = CryptoHash::deserialize_reader(reader)?;
let receipt = ReceiptEnum::deserialize_reader(reader)?;
Ok(Receipt::V0(ReceiptV0 {
predecessor_id: signer_id,
receiver_id,
receipt_id,
receipt,
}))
} else {
let u5 = u8::deserialize_reader(reader)?;
let signer_id = read_predecessor_id([u2, u3, u4, u5], reader)?;
let receiver_id = AccountId::deserialize_reader(reader)?;
let receipt_id = CryptoHash::deserialize_reader(reader)?;
let receipt = ReceiptEnum::deserialize_reader(reader)?;
let priority = u64::deserialize_reader(reader)?;
Ok(Receipt::V1(ReceiptV1 {
predecessor_id: signer_id,
receiver_id,
receipt_id,
receipt,
priority,
}))
}
}
}
pub enum ReceiptPriority {
/// Used in ReceiptV1
Priority(u64),
/// Used in ReceiptV0
NoPriority,
}
impl ReceiptPriority {
pub fn value(&self) -> u64 {
match self {
ReceiptPriority::Priority(value) => *value,
ReceiptPriority::NoPriority => 0,
}
}
}
impl Borrow<CryptoHash> for Receipt {
fn borrow(&self) -> &CryptoHash {
match self {
Receipt::V0(receipt) => &receipt.receipt_id,
Receipt::V1(receipt) => &receipt.receipt_id,
}
}
}
impl Receipt {
pub fn receiver_id(&self) -> &AccountId {
match self {
Receipt::V0(receipt) => &receipt.receiver_id,
Receipt::V1(receipt) => &receipt.receiver_id,
}
}
pub fn set_receiver_id(&mut self, receiver_id: AccountId) {
match self {
Receipt::V0(receipt) => receipt.receiver_id = receiver_id,
Receipt::V1(receipt) => receipt.receiver_id = receiver_id,
}
}
pub fn predecessor_id(&self) -> &AccountId {
match self {
Receipt::V0(receipt) => &receipt.predecessor_id,
Receipt::V1(receipt) => &receipt.predecessor_id,
}
}
pub fn set_predecessor_id(&mut self, predecessor_id: AccountId) {
match self {
Receipt::V0(receipt) => receipt.predecessor_id = predecessor_id,
Receipt::V1(receipt) => receipt.predecessor_id = predecessor_id,
}
}
pub fn receipt(&self) -> &ReceiptEnum {
match self {
Receipt::V0(receipt) => &receipt.receipt,
Receipt::V1(receipt) => &receipt.receipt,
}
}
pub fn receipt_mut(&mut self) -> &mut ReceiptEnum {
match self {
Receipt::V0(receipt) => &mut receipt.receipt,
Receipt::V1(receipt) => &mut receipt.receipt,
}
}
pub fn take_receipt(self) -> ReceiptEnum {
match self {
Receipt::V0(receipt) => receipt.receipt,
Receipt::V1(receipt) => receipt.receipt,
}
}
pub fn receipt_id(&self) -> &CryptoHash {
match self {
Receipt::V0(receipt) => &receipt.receipt_id,
Receipt::V1(receipt) => &receipt.receipt_id,
}
}
pub fn set_receipt_id(&mut self, receipt_id: CryptoHash) {
match self {
Receipt::V0(receipt) => receipt.receipt_id = receipt_id,
Receipt::V1(receipt) => receipt.receipt_id = receipt_id,
}
}
pub fn priority(&self) -> ReceiptPriority {
match self {
Receipt::V0(_) => ReceiptPriority::NoPriority,
Receipt::V1(receipt) => ReceiptPriority::Priority(receipt.priority),
}
}
/// It's not a content hash, but receipt_id is unique.
pub fn get_hash(&self) -> CryptoHash {
*self.receipt_id()
}
/// Generates a receipt with a transfer from system for a given balance without a receipt_id.
/// This should be used for token refunds instead of gas refunds. It inherits priority from the parent receipt.
/// It doesn't refund the allowance of the access key. For gas refunds use `new_gas_refund`.
pub fn new_balance_refund(
receiver_id: &AccountId,
refund: Balance,
priority: ReceiptPriority,
) -> Self {
match priority {
ReceiptPriority::Priority(priority) => Receipt::V1(ReceiptV1 {
predecessor_id: "system".parse().unwrap(),
receiver_id: receiver_id.clone(),
receipt_id: CryptoHash::default(),
receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: "system".parse().unwrap(),
signer_public_key: PublicKey::empty(KeyType::ED25519),
gas_price: 0,
output_data_receivers: vec![],
input_data_ids: vec![],
actions: vec![Action::Transfer(TransferAction { deposit: refund })],
}),
priority,
}),
ReceiptPriority::NoPriority => Receipt::V0(ReceiptV0 {
predecessor_id: "system".parse().unwrap(),
receiver_id: receiver_id.clone(),
receipt_id: CryptoHash::default(),
receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: "system".parse().unwrap(),
signer_public_key: PublicKey::empty(KeyType::ED25519),
gas_price: 0,
output_data_receivers: vec![],
input_data_ids: vec![],
actions: vec![Action::Transfer(TransferAction { deposit: refund })],
}),
}),
}
}
/// Generates a receipt with a transfer action from system for a given balance without a
/// receipt_id. It contains `signer_id` and `signer_public_key` to indicate this is a gas
/// refund. The execution of this receipt will try to refund the allowance of the
/// access key with the given public key.
/// Gas refund does not inherit priority from its parent receipt and has no priority associated with it
/// NOTE: The access key may be replaced by the owner, so the execution can't rely that the
/// access key is the same and it should use best effort for the refund.
pub fn new_gas_refund(
receiver_id: &AccountId,
refund: Balance,
signer_public_key: PublicKey,
priority: ReceiptPriority,
) -> Self {
match priority {
ReceiptPriority::Priority(priority) => Receipt::V1(ReceiptV1 {
predecessor_id: "system".parse().unwrap(),
receiver_id: receiver_id.clone(),
receipt_id: CryptoHash::default(),
receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: receiver_id.clone(),
signer_public_key,
gas_price: 0,
output_data_receivers: vec![],
input_data_ids: vec![],
actions: vec![Action::Transfer(TransferAction { deposit: refund })],
}),
priority,
}),
ReceiptPriority::NoPriority => Receipt::V0(ReceiptV0 {
predecessor_id: "system".parse().unwrap(),
receiver_id: receiver_id.clone(),
receipt_id: CryptoHash::default(),
receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: receiver_id.clone(),
signer_public_key,
gas_price: 0,
output_data_receivers: vec![],
input_data_ids: vec![],
actions: vec![Action::Transfer(TransferAction { deposit: refund })],
}),
}),
}
}
}
/// Receipt could be either ActionReceipt or DataReceipt
#[derive(
BorshSerialize,
BorshDeserialize,
Clone,
Debug,
PartialEq,
Eq,
serde::Serialize,
serde::Deserialize,
ProtocolSchema,
)]
pub enum ReceiptEnum {
Action(ActionReceipt),
Data(DataReceipt),
PromiseYield(ActionReceipt),
PromiseResume(DataReceipt),
}
/// ActionReceipt is derived from an Action from `Transaction or from Receipt`
#[derive(
BorshSerialize,
BorshDeserialize,
Debug,
PartialEq,
Eq,
Clone,
serde::Serialize,
serde::Deserialize,
ProtocolSchema,
)]
pub struct ActionReceipt {
/// A signer of the original transaction
pub signer_id: AccountId,
/// An access key which was used to sign the original transaction
pub signer_public_key: PublicKey,
/// A gas_price which has been used to buy gas in the original transaction
#[serde(with = "dec_format")]
pub gas_price: Balance,
/// If present, where to route the output data
pub output_data_receivers: Vec<DataReceiver>,
/// A list of the input data dependencies for this Receipt to process.
/// If all `input_data_ids` for this receipt are delivered to the account
/// that means we have all the `ReceivedData` input which will be than converted to a
/// `PromiseResult::Successful(value)` or `PromiseResult::Failed`
/// depending on `ReceivedData` is `Some(_)` or `None`
pub input_data_ids: Vec<CryptoHash>,
/// A list of actions to process when all input_data_ids are filled
pub actions: Vec<Action>,
}
/// An incoming (ingress) `DataReceipt` which is going to a Receipt's `receiver` input_data_ids
/// Which will be converted to `PromiseResult::Successful(value)` or `PromiseResult::Failed`
#[serde_as]
#[derive(
BorshSerialize,
BorshDeserialize,
Hash,
PartialEq,
Eq,
Clone,
serde::Serialize,
serde::Deserialize,
ProtocolSchema,
)]
pub struct DataReceipt {
pub data_id: CryptoHash,
#[serde_as(as = "Option<Base64>")]
pub data: Option<Vec<u8>>,
}
impl fmt::Debug for DataReceipt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DataReceipt")
.field("data_id", &self.data_id)
.field("data", &format_args!("{}", AbbrBytes(self.data.as_deref())))
.finish()
}
}
/// A temporary data which is created by processing of DataReceipt
/// stored in a state trie with a key = `account_id` + `data_id` until
/// `input_data_ids` of all incoming Receipts are satisfied
/// None means data retrieval was failed
#[derive(BorshSerialize, BorshDeserialize, Hash, PartialEq, Eq, Clone, ProtocolSchema)]
pub struct ReceivedData {
pub data: Option<Vec<u8>>,
}
impl fmt::Debug for ReceivedData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ReceivedData")
.field("data", &format_args!("{}", AbbrBytes(self.data.as_deref())))
.finish()
}
}
/// Stores indices for a persistent queue for delayed receipts that didn't fit into a block.
#[derive(Default, BorshSerialize, BorshDeserialize, Clone, PartialEq, Debug, ProtocolSchema)]
pub struct DelayedReceiptIndices {
// First inclusive index in the queue.
pub first_index: u64,
// Exclusive end index of the queue
pub next_available_index: u64,
}
impl DelayedReceiptIndices {
pub fn len(&self) -> u64 {
self.next_available_index - self.first_index
}
}
/// Stores indices for a persistent queue for PromiseYield timeouts.
#[derive(Default, BorshSerialize, BorshDeserialize, Clone, PartialEq, Debug, ProtocolSchema)]
pub struct PromiseYieldIndices {
// First inclusive index in the queue.
pub first_index: u64,
// Exclusive end index of the queue
pub next_available_index: u64,
}
impl PromiseYieldIndices {
pub fn len(&self) -> u64 {
self.next_available_index - self.first_index
}
}
/// Entries in the queue of PromiseYield timeouts.
#[derive(BorshSerialize, BorshDeserialize, Clone, PartialEq, Debug, ProtocolSchema)]
pub struct PromiseYieldTimeout {
/// The account on which the yielded promise was created
pub account_id: AccountId,
/// The `data_id` used to identify the awaited input data
pub data_id: CryptoHash,
/// The block height before which the data must be submitted
pub expires_at: BlockHeight,
}
/// Stores indices for a persistent queue in the state trie.
#[derive(Default, BorshSerialize, BorshDeserialize, Clone, PartialEq, Debug, ProtocolSchema)]
pub struct TrieQueueIndices {
// First inclusive index in the queue.
pub first_index: u64,
// Exclusive end index of the queue
pub next_available_index: u64,
}
impl TrieQueueIndices {
pub fn len(&self) -> u64 {
self.next_available_index - self.first_index
}
pub fn is_default(&self) -> bool {
self.next_available_index == 0
}
}
impl From<DelayedReceiptIndices> for TrieQueueIndices {
fn from(other: DelayedReceiptIndices) -> Self {
Self { first_index: other.first_index, next_available_index: other.next_available_index }
}
}
impl From<TrieQueueIndices> for DelayedReceiptIndices {
fn from(other: TrieQueueIndices) -> Self {
Self { first_index: other.first_index, next_available_index: other.next_available_index }
}
}
/// Stores indices for a persistent queue for buffered receipts that couldn't be
/// forwarded.
///
/// This is the singleton value stored in the `BUFFERED_RECEIPT_INDICES` trie
/// column.
#[derive(Default, BorshSerialize, BorshDeserialize, Clone, PartialEq, Debug, ProtocolSchema)]
pub struct BufferedReceiptIndices {
pub shard_buffers: BTreeMap<ShardId, TrieQueueIndices>,
}
/// Map of shard to list of receipts to send to it.
pub type ReceiptResult = HashMap<ShardId, Vec<Receipt>>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_receipt_v0_serialization() {
let receipt_v0 = Receipt::V0(ReceiptV0 {
predecessor_id: "predecessor_id".parse().unwrap(),
receiver_id: "receiver_id".parse().unwrap(),
receipt_id: CryptoHash::default(),
receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: "signer_id".parse().unwrap(),
signer_public_key: PublicKey::empty(KeyType::ED25519),
gas_price: 0,
output_data_receivers: vec![],
input_data_ids: vec![],
actions: vec![Action::Transfer(TransferAction { deposit: 0 })],
}),
});
let serialized_receipt = borsh::to_vec(&receipt_v0).unwrap();
let receipt2 = Receipt::try_from_slice(&serialized_receipt).unwrap();
assert_eq!(receipt_v0, receipt2);
}
#[test]
fn test_receipt_v1_serialization() {
let receipt_v1 = Receipt::V1(ReceiptV1 {
predecessor_id: "predecessor_id".parse().unwrap(),
receiver_id: "receiver_id".parse().unwrap(),
receipt_id: CryptoHash::default(),
receipt: ReceiptEnum::Action(ActionReceipt {
signer_id: "signer_id".parse().unwrap(),
signer_public_key: PublicKey::empty(KeyType::ED25519),
gas_price: 0,
output_data_receivers: vec![],
input_data_ids: vec![],
actions: vec![Action::Transfer(TransferAction { deposit: 0 })],
}),
priority: 1,
});
let serialized_receipt = borsh::to_vec(&receipt_v1).unwrap();
let receipt2 = Receipt::try_from_slice(&serialized_receipt).unwrap();
assert_eq!(receipt_v1, receipt2);
}
}