unc_primitives/chunk_validation.rs
1use std::collections::HashMap;
2
3use crate::challenge::PartialState;
4use crate::sharding::{ChunkHash, ReceiptProof, ShardChunkHeader};
5use crate::transaction::SignedTransaction;
6use borsh::{BorshDeserialize, BorshSerialize};
7use unc_crypto::Signature;
8use unc_primitives_core::hash::CryptoHash;
9use unc_primitives_core::types::AccountId;
10
11/// The state witness for a chunk; proves the state transition that the
12/// chunk attests to.
13#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
14pub struct ChunkStateWitness {
15 /// The chunk header that this witness is for. While this is not needed
16 /// to apply the state transition, it is needed for a chunk validator to
17 /// produce a chunk endorsement while knowing what they are endorsing.
18 pub chunk_header: ShardChunkHeader,
19 /// The base state and post-state-root of the main transition where we
20 /// apply transactions and receipts. Corresponds to the state transition
21 /// that takes us from the pre-state-root of the last new chunk of this
22 /// shard to the post-state-root of that same chunk.
23 pub main_state_transition: ChunkStateTransition,
24 /// For the main state transition, we apply transactions and receipts.
25 /// Exactly which of them must be applied is a deterministic property
26 /// based on the blockchain history this chunk is based on.
27 ///
28 /// The set of receipts is exactly
29 /// Filter(R, |receipt| receipt.target_shard = S), where
30 /// - R is the set of outgoing receipts included in the set of chunks C
31 /// (defined below),
32 /// - S is the shard of this chunk.
33 ///
34 /// The set of chunks C, from which the receipts are sourced, is defined as
35 /// all new chunks included in the set of blocks B.
36 ///
37 /// The set of blocks B is defined as the contiguous subsequence of blocks
38 /// B1 (EXCLUSIVE) to B2 (inclusive) in this chunk's chain (i.e. the linear
39 /// chain that this chunk's parent block is on), where B1 is the block that
40 /// contains the last new chunk of shard S before this chunk, and B1 is the
41 /// block that contains the last new chunk of shard S before B2.
42 ///
43 /// Furthermore, the set of transactions to apply is exactly the
44 /// transactions included in the chunk of shard S at B2.
45 ///
46 /// For the purpose of this text, a "new chunk" is defined as a chunk that
47 /// is proposed by a chunk producer, not one that was copied from the
48 /// previous block (commonly called a "missing chunk").
49 ///
50 /// This field, `source_receipt_proofs`, is a (non-strict) superset of the
51 /// receipts that must be applied, along with information that allows these
52 /// receipts to be verifiable against the blockchain history.
53 pub source_receipt_proofs: HashMap<ChunkHash, ReceiptProof>,
54 /// An overall hash of the list of receipts that should be applied. This is
55 /// redundant information but is useful for diagnosing why a witness might
56 /// fail. This is the hash of the borsh encoding of the Vec<Receipt> in the
57 /// order that they should be applied.
58 pub exact_receipts_hash: CryptoHash,
59 /// The transactions to apply. These must be in the correct order in which
60 /// they are to be applied.
61 pub transactions: Vec<SignedTransaction>,
62 /// For each missing chunk after the last new chunk of the shard, we need
63 /// to carry out an implicit state transition. Mostly, this is for
64 /// distributing validator rewards. This list contains one for each such
65 /// chunk, in forward chronological order.
66 ///
67 /// After these are applied as well, we should arrive at the pre-state-root
68 /// of the chunk that this witness is for.
69 pub implicit_transitions: Vec<ChunkStateTransition>,
70 /// Finally, we need to be able to verify that the new transitions proposed
71 /// by the chunk (that this witness is for) are valid. For that, we need
72 /// the transactions as well as another partial storage (based on the
73 /// pre-state-root of this chunk) in order to verify that the sender
74 /// accounts have appropriate balances, access keys, nonces, etc.
75 pub new_transactions: Vec<SignedTransaction>,
76 pub new_transactions_validation_state: PartialState,
77}
78
79/// Represents the base state and the expected post-state-root of a chunk's state
80/// transition. The actual state transition itself is not included here.
81#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
82pub struct ChunkStateTransition {
83 /// The block that contains the chunk; this identifies which part of the
84 /// state transition we're talking about.
85 pub block_hash: CryptoHash,
86 /// The partial state before the state transition. This includes whatever
87 /// initial state that is necessary to compute the state transition for this
88 /// chunk.
89 pub base_state: PartialState,
90 /// The expected final state root after applying the state transition.
91 /// This is redundant information, because the post state root can be
92 /// derived by applying the state transition onto the base state, but
93 /// this makes it easier to debug why a state witness may fail to validate.
94 pub post_state_root: CryptoHash,
95}
96
97/// The endorsement of a chunk by a chunk validator. By providing this, a
98/// chunk validator has verified that the chunk state witness is correct.
99#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
100pub struct ChunkEndorsement {
101 pub inner: ChunkEndorsementInner,
102 pub account_id: AccountId,
103 pub signature: Signature,
104}
105
106/// This is the part of the chunk endorsement that is actually being signed.
107#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
108pub struct ChunkEndorsementInner {
109 pub chunk_hash: ChunkHash,
110 /// An arbitrary static string to make sure that this struct cannot be
111 /// serialized to look identical to another serialized struct. For chunk
112 /// production we are signing a chunk hash, so we need to make sure that
113 /// this signature means something different.
114 ///
115 signature_differentiator: String,
116}
117
118impl ChunkEndorsementInner {
119 pub fn new(chunk_hash: ChunkHash) -> Self {
120 Self { chunk_hash, signature_differentiator: "ChunkEndorsement".to_owned() }
121 }
122}
123
124/// Stored on disk for each chunk, including missing chunks, in order to
125/// produce a chunk state witness when needed.
126#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
127pub struct StoredChunkStateTransitionData {
128 /// The partial state that is needed to apply the state transition,
129 /// whether it is a new chunk state transition or a implicit missing chunk
130 /// state transition.
131 pub base_state: PartialState,
132 /// If this is a new chunk state transition, the hash of the receipts that
133 /// were used to apply the state transition. This is redundant information,
134 /// but is used to validate against `StateChunkWitness::exact_receipts_hash`
135 /// to ease debugging of why a state witness may be incorrect.
136 pub receipts_hash: CryptoHash,
137}