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
use crate::epoch_block_info::BlockInfo;
use crate::epoch_info::EpochInfo;
use crate::merkle::PartialMerkleTree;
use crate::types::validator_stake::ValidatorStake;
use crate::utils::compression::CompressedData;
use crate::version::BLOCK_HEADER_V3_PROTOCOL_VERSION;
use crate::{block_header::BlockHeader, merkle::MerklePathItem};
use borsh::{BorshDeserialize, BorshSerialize};
use bytesize::ByteSize;
use near_crypto::Signature;
use near_primitives_core::types::ProtocolVersion;
use near_schema_checker_lib::ProtocolSchema;
use std::fmt::Debug;
use std::sync::Arc;
/// Versioned enum for EpochSyncProof. Because this structure is sent over the network and also
/// persisted on disk, we want it to be deserializable even if the structure changes in the future.
/// There's no guarantee that there's any compatibility (most likely not), but being able to
/// deserialize it will allow us to modify the code in the future to properly perform any upgrades.
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
#[borsh(use_discriminant = true)]
#[repr(u8)]
pub enum EpochSyncProof {
V1(EpochSyncProofV1) = 0,
}
impl EpochSyncProof {
/// Right now this would never fail, but in the future this API can be changed.
pub fn into_v1(self) -> EpochSyncProofV1 {
match self {
EpochSyncProof::V1(v1) => v1,
}
}
/// Right now this would never fail, but in the future this API can be changed.
pub fn as_v1(&self) -> &EpochSyncProofV1 {
match self {
EpochSyncProof::V1(v1) => v1,
}
}
}
/// Proof that the blockchain history had progressed from the genesis to the
/// current epoch indicated in the proof.
///
/// A side note to better understand the fields in this proof: the last three blocks of any
/// epoch are guaranteed to have consecutive heights:
/// - H: The last final block of the epoch
/// - H + 1: The second last block of the epoch
/// - H + 2: The last block of the epoch
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
pub struct EpochSyncProofV1 {
/// All the relevant epochs, starting from the second epoch after genesis (i.e. genesis is
/// epoch EpochId::default, and then the next epoch after genesis is fully determined by
/// the genesis; after that would be the first epoch included here), to and including the
/// current epoch, in that order.
///
/// The first entry in this list is proven against the genesis. Then, each entry is proven
/// against the previous entry, thereby validating the entire list by induction.
pub all_epochs: Vec<EpochSyncProofEpochData>,
/// Some extra data for the last epoch before the current epoch.
pub last_epoch: EpochSyncProofLastEpochData,
/// Extra information to initialize the current epoch we're syncing to.
pub current_epoch: EpochSyncProofCurrentEpochData,
}
const MAX_UNCOMPRESSED_EPOCH_SYNC_PROOF_SIZE: u64 = ByteSize::mib(500).0;
const EPOCH_SYNC_COMPRESSION_LEVEL: i32 = 3;
#[derive(
Clone,
PartialEq,
Eq,
BorshSerialize,
BorshDeserialize,
derive_more::From,
derive_more::AsRef,
ProtocolSchema,
)]
pub struct CompressedEpochSyncProof(Box<[u8]>);
impl
CompressedData<
EpochSyncProof,
MAX_UNCOMPRESSED_EPOCH_SYNC_PROOF_SIZE,
EPOCH_SYNC_COMPRESSION_LEVEL,
> for CompressedEpochSyncProof
{
}
impl Debug for CompressedEpochSyncProof {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CompressedEpochSyncProof")
.field("len", &self.0.len())
.field("proof", &self.decode())
.finish()
}
}
/// For epoch sync we need to keep track of when the block producer hash format changed.
/// This is required for the correct calculation of the proof. See [`use_versioned_bp_hash_format`]
pub fn should_use_versioned_bp_hash_format(protocol_version: ProtocolVersion) -> bool {
protocol_version >= BLOCK_HEADER_V3_PROTOCOL_VERSION
}
/// Data needed for each epoch covered in the epoch sync proof.
///
/// FIXME(Clone): cloning them `Vec`s here should be pretty darn expensive, no?
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
pub struct EpochSyncProofEpochData {
/// The block producers and their stake, for this epoch. This is verified
/// against the `next_bp_hash` of the `last_final_block_header` of the epoch before this.
pub block_producers: Vec<ValidatorStake>,
/// Whether the block producers are encoded in the versioned format for computing the bp_hash.
/// This is verified together with `block_producers` against `next_bp_hash` of the
/// `last_final_block_header` of the epoch before this. This field does not need to be trusted,
/// because given any valid bp hash, there is only one possible value of this boolean that
/// could pass verification, because the two encodings do not collide.
///
/// Specifically, the reason that the two encodings do not collide is:
/// - The old version is the borsh encoding of a vector of `ValidatorStakeV1`, meaning the
/// first few bytes are:
/// | vec length (4 bytes) | account id len (4 bytes) | account id (variable length) | ...
/// - The old version is the borsh encoding of a vector of `ValidatorStake` which is an enum,
/// so the first few bytes are:
/// | vec length (4 bytes) | enum tag (1 byte) | ...
/// - Right now, the enum tag is always 0 because there's only ValidatorStakeV2, so
/// - The only way for these two to collide is if the account id length has a lowest byte of
/// zero, which is impossible because a valid AccountId has length between 2 and 64.
/// - In the future, the enum tag can be larger. However, assuming that the first element of
/// ValidatorStakeVx is always AccountId, then the next 4 bytes after enum tag is the length
/// of the account id, but to have a collision the first 3 bytes of that must be zeros, which
/// is again impossible.
pub use_versioned_bp_hash_format: bool,
/// The last final block header of the epoch (i.e. third last block of the epoch).
/// This is verified against `this_epoch_endorsements_for_last_final_block`.
pub last_final_block_header: Arc<BlockHeader>,
/// Endorsements for the last final block, which comes from the second last block of the epoch.
/// Since it has a consecutive height from the final block, the approvals included in it are
/// guaranteed to be endorsements which directly endorse the final block.
///
/// Note an important subtlety: This is *not* the complete set of approvals included in the
/// second last block. This is a subset of them that correspond to only this epoch's block
/// producers, as the next epoch's block producers are also required to sign this block. We
/// do not include the next epoch's block producers' endorsements here, as we ultimately
/// would not have a reliable way to verify the next epoch's block producers (it would result in
/// circular reasoning since the next epoch's block producers are verified against this epoch's
/// final block), so even if we included them we would not be able to use them meaningfully.
pub this_epoch_endorsements_for_last_final_block: Vec<Option<Box<Signature>>>,
}
/// Data needed to initialize the epoch sync boundary.
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
pub struct EpochSyncProofLastEpochData {
/// The following six fields are used to derive the epoch_sync_data_hash included in the
/// first block of the epoch right after (assuming it is a BlockHeaderV3 or newer). This
/// is used to verify all the data we need around the epoch sync boundary, against
/// `first_block_header_in_epoch` in `current_epoch`.
pub epoch_info: EpochInfo,
pub next_epoch_info: EpochInfo,
pub next_next_epoch_info: EpochInfo,
pub first_block_in_epoch: BlockInfo,
pub last_block_in_epoch: BlockInfo,
pub second_last_block_in_epoch: BlockInfo,
}
/// Data needed to initialize the current epoch we're syncing to.
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
pub struct EpochSyncProofCurrentEpochData {
/// The first block header that begins the epoch. It is proven using a merkle proof
/// against `last_final_block_header` in the last entry of `all_epochs`. Note that we cannot
/// use signatures to prove this like for the final block, because the first block header may
/// not have a consecutive height afterwards.
pub first_block_header_in_epoch: Arc<BlockHeader>,
/// The last two block headers are also needed for various purposes after epoch sync.
/// They are proven against the `first_block_header_in_epoch`.
pub last_block_header_in_prev_epoch: Arc<BlockHeader>,
pub second_last_block_header_in_prev_epoch: Arc<BlockHeader>,
/// Used to prove `first_block_header_in_epoch` against the `last_final_block_header` of
/// the last entry of `all_epochs`.
pub merkle_proof_for_first_block: Vec<MerklePathItem>,
/// Partial merkle tree for the first block in this epoch. It is needed to construct future
/// partial merkle trees for any blocks that follow.
/// This is proven against the merkle root and block ordinal in `first_block_header_in_epoch`
/// (as there is only one unique correct partial merkle tree for a specific root and a specific
/// block ordinal).
pub partial_merkle_tree_for_first_block: PartialMerkleTree,
}