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}