near_primitives/stateless_validation/
contract_distribution.rs

1use std::collections::HashSet;
2use std::sync::Arc;
3
4use borsh::{BorshDeserialize, BorshSerialize};
5use bytesize::ByteSize;
6use near_crypto::{PublicKey, Signature};
7use near_primitives_core::code::ContractCode;
8use near_primitives_core::hash::{CryptoHash, hash};
9use near_primitives_core::types::{AccountId, ShardId};
10use near_schema_checker_lib::ProtocolSchema;
11
12use super::ChunkProductionKey;
13#[cfg(feature = "solomon")]
14use crate::reed_solomon::{ReedSolomonEncoderDeserialize, ReedSolomonEncoderSerialize};
15use crate::types::SignatureDifferentiator;
16use crate::{utils::compression::CompressedData, validator_signer::ValidatorSigner};
17
18// Data structures for chunk producers to send accessed contracts to chunk validators.
19
20/// Contains contracts (as code-hashes) accessed during the application of a chunk.
21/// This is used by the chunk producer to let the chunk validators know about which contracts
22/// are needed for validating a witness, so that the chunk validators can request missing code.
23#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
24#[borsh(use_discriminant = true)]
25#[repr(u8)]
26pub enum ChunkContractAccesses {
27    V1(ChunkContractAccessesV1) = 0,
28}
29
30/// Contains information necessary to identify StateTransitionData in the storage.
31#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
32pub struct MainTransitionKey {
33    pub block_hash: CryptoHash,
34    pub shard_id: ShardId,
35}
36
37impl ChunkContractAccesses {
38    pub fn new(
39        next_chunk: ChunkProductionKey,
40        contracts: HashSet<CodeHash>,
41        main_transition: MainTransitionKey,
42        signer: &ValidatorSigner,
43    ) -> Self {
44        Self::V1(ChunkContractAccessesV1::new(next_chunk, contracts, main_transition, signer))
45    }
46
47    pub fn contracts(&self) -> &[CodeHash] {
48        match self {
49            Self::V1(accesses) => &accesses.inner.contracts,
50        }
51    }
52
53    pub fn chunk_production_key(&self) -> &ChunkProductionKey {
54        match self {
55            Self::V1(accesses) => &accesses.inner.next_chunk,
56        }
57    }
58
59    pub fn main_transition(&self) -> &MainTransitionKey {
60        match self {
61            Self::V1(accesses) => &accesses.inner.main_transition,
62        }
63    }
64
65    pub fn verify_signature(&self, public_key: &PublicKey) -> bool {
66        match self {
67            Self::V1(accesses) => accesses.verify_signature(public_key),
68        }
69    }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
73pub struct ChunkContractAccessesV1 {
74    inner: ChunkContractAccessesInner,
75    /// Signature of the inner, signed by the chunk producer of the next chunk.
76    signature: Signature,
77}
78
79impl ChunkContractAccessesV1 {
80    fn new(
81        next_chunk: ChunkProductionKey,
82        contracts: HashSet<CodeHash>,
83        main_transition: MainTransitionKey,
84        signer: &ValidatorSigner,
85    ) -> Self {
86        let inner = ChunkContractAccessesInner::new(next_chunk, contracts, main_transition);
87        let signature = signer.sign_bytes(&borsh::to_vec(&inner).unwrap());
88        Self { inner, signature }
89    }
90
91    fn verify_signature(&self, public_key: &PublicKey) -> bool {
92        self.signature.verify(&borsh::to_vec(&self.inner).unwrap(), public_key)
93    }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
97pub struct ChunkContractAccessesInner {
98    /// Production metadata of the chunk created after the chunk the accesses belong to.
99    /// We associate this message with the next-chunk info because this message is generated
100    /// and distributed while generating the state-witness of the next chunk
101    /// (by the chunk producer of the next chunk).
102    next_chunk: ChunkProductionKey,
103    /// List of code-hashes for the contracts accessed.
104    contracts: Vec<CodeHash>,
105    /// Corresponds to the StateTransitionData where the contracts were accessed.
106    main_transition: MainTransitionKey,
107    signature_differentiator: SignatureDifferentiator,
108}
109
110impl ChunkContractAccessesInner {
111    fn new(
112        next_chunk: ChunkProductionKey,
113        contracts: HashSet<CodeHash>,
114        main_transition: MainTransitionKey,
115    ) -> Self {
116        Self {
117            next_chunk,
118            contracts: contracts.into_iter().collect(),
119            main_transition,
120            signature_differentiator: "ChunkContractAccessesInner".to_owned(),
121        }
122    }
123}
124
125// Data structures for chunk validators to request contract code from chunk producers.
126
127/// Message to request missing code for a set of contracts.
128/// The contracts are identified by the hash of their code.
129#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
130#[borsh(use_discriminant = true)]
131#[repr(u8)]
132pub enum ContractCodeRequest {
133    V1(ContractCodeRequestV1) = 0,
134}
135
136impl ContractCodeRequest {
137    pub fn new(
138        next_chunk: ChunkProductionKey,
139        contracts: HashSet<CodeHash>,
140        main_transition: MainTransitionKey,
141        signer: &ValidatorSigner,
142    ) -> Self {
143        Self::V1(ContractCodeRequestV1::new(next_chunk, contracts, main_transition, signer))
144    }
145
146    pub fn requester(&self) -> &AccountId {
147        match self {
148            Self::V1(request) => &request.inner.requester,
149        }
150    }
151
152    pub fn contracts(&self) -> &[CodeHash] {
153        match self {
154            Self::V1(request) => &request.inner.contracts,
155        }
156    }
157
158    pub fn chunk_production_key(&self) -> &ChunkProductionKey {
159        match self {
160            Self::V1(request) => &request.inner.next_chunk,
161        }
162    }
163
164    pub fn main_transition(&self) -> &MainTransitionKey {
165        match self {
166            Self::V1(request) => &request.inner.main_transition,
167        }
168    }
169
170    pub fn verify_signature(&self, public_key: &PublicKey) -> bool {
171        match self {
172            Self::V1(v1) => v1.verify_signature(public_key),
173        }
174    }
175}
176
177#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
178pub struct ContractCodeRequestV1 {
179    inner: ContractCodeRequestInner,
180    /// Signature of the inner.
181    signature: Signature,
182}
183
184impl ContractCodeRequestV1 {
185    fn new(
186        next_chunk: ChunkProductionKey,
187        contracts: HashSet<CodeHash>,
188        main_transition: MainTransitionKey,
189        signer: &ValidatorSigner,
190    ) -> Self {
191        let inner = ContractCodeRequestInner::new(
192            signer.validator_id().clone(),
193            next_chunk,
194            contracts,
195            main_transition,
196        );
197        let signature = signer.sign_bytes(&borsh::to_vec(&inner).unwrap());
198        Self { inner, signature }
199    }
200
201    pub fn verify_signature(&self, public_key: &PublicKey) -> bool {
202        self.signature.verify(&borsh::to_vec(&self.inner).unwrap(), public_key)
203    }
204}
205
206#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
207pub struct ContractCodeRequestInner {
208    /// Account of the node requesting the contracts. Used for signature verification and
209    /// to identify the node to send the response to.
210    requester: AccountId,
211    /// Production metadata of the chunk created after the chunk the accesses belong to.
212    /// We associate this message with the next-chunk info because this message is generated
213    /// and distributed while generating the state-witness of the next chunk
214    /// (by the chunk producer of the next chunk).
215    next_chunk: ChunkProductionKey,
216    /// List of code-hashes for the contracts accessed.
217    contracts: Vec<CodeHash>,
218    main_transition: MainTransitionKey,
219    signature_differentiator: SignatureDifferentiator,
220}
221
222impl ContractCodeRequestInner {
223    fn new(
224        requester: AccountId,
225        next_chunk: ChunkProductionKey,
226        contracts: HashSet<CodeHash>,
227        main_transition: MainTransitionKey,
228    ) -> Self {
229        Self {
230            requester,
231            next_chunk,
232            contracts: contracts.into_iter().collect(),
233            main_transition,
234            signature_differentiator: "ContractCodeRequestInner".to_owned(),
235        }
236    }
237}
238
239// Data structures for chunk producers to send contract code to chunk validators as response to ContractCodeRequest.
240
241#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
242#[borsh(use_discriminant = true)]
243#[repr(u8)]
244pub enum ContractCodeResponse {
245    V1(ContractCodeResponseV1) = 0,
246}
247
248impl ContractCodeResponse {
249    pub fn encode(
250        next_chunk: ChunkProductionKey,
251        contracts: &Vec<CodeBytes>,
252    ) -> std::io::Result<Self> {
253        ContractCodeResponseV1::encode(next_chunk, contracts).map(|v1| Self::V1(v1))
254    }
255
256    pub fn chunk_production_key(&self) -> &ChunkProductionKey {
257        match self {
258            Self::V1(v1) => &v1.next_chunk,
259        }
260    }
261
262    pub fn decompress_contracts(&self) -> std::io::Result<Vec<CodeBytes>> {
263        let compressed_contracts = match self {
264            Self::V1(v1) => &v1.compressed_contracts,
265        };
266        compressed_contracts.decode().map(|(data, _size)| data)
267    }
268}
269
270#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
271pub struct ContractCodeResponseV1 {
272    // The same as `next_chunk` in `ContractCodeRequest`
273    next_chunk: ChunkProductionKey,
274    /// Code for the contracts.
275    compressed_contracts: CompressedContractCode,
276}
277
278impl ContractCodeResponseV1 {
279    pub fn encode(
280        next_chunk: ChunkProductionKey,
281        contracts: &Vec<CodeBytes>,
282    ) -> std::io::Result<Self> {
283        let (compressed_contracts, _size) = CompressedContractCode::encode(&contracts)?;
284        Ok(Self { next_chunk, compressed_contracts })
285    }
286}
287
288/// Represents max allowed size of the raw (not compressed) contract code response,
289/// corresponds to the size of borsh-serialized ContractCodeResponse.
290const MAX_UNCOMPRESSED_CONTRACT_CODE_RESPONSE_SIZE: u64 =
291    ByteSize::mib(if cfg!(feature = "test_features") { 512 } else { 64 }).0;
292const CONTRACT_CODE_RESPONSE_COMPRESSION_LEVEL: i32 = 3;
293
294/// This is the compressed version of a list of borsh-serialized contract code.
295#[derive(
296    Debug,
297    Clone,
298    PartialEq,
299    Eq,
300    BorshSerialize,
301    BorshDeserialize,
302    ProtocolSchema,
303    derive_more::From,
304    derive_more::AsRef,
305)]
306struct CompressedContractCode(Box<[u8]>);
307
308impl
309    CompressedData<
310        Vec<CodeBytes>,
311        MAX_UNCOMPRESSED_CONTRACT_CODE_RESPONSE_SIZE,
312        CONTRACT_CODE_RESPONSE_COMPRESSION_LEVEL,
313    > for CompressedContractCode
314{
315}
316
317/// Hash of some (uncompiled) contract code.
318#[derive(
319    Debug,
320    Clone,
321    Hash,
322    PartialEq,
323    Eq,
324    Ord,
325    PartialOrd,
326    BorshSerialize,
327    BorshDeserialize,
328    serde::Serialize,
329    serde::Deserialize,
330    ProtocolSchema,
331)]
332pub struct CodeHash(pub CryptoHash);
333
334impl From<CryptoHash> for CodeHash {
335    fn from(crypto_hash: CryptoHash) -> Self {
336        Self(crypto_hash)
337    }
338}
339
340impl Into<CryptoHash> for CodeHash {
341    fn into(self) -> CryptoHash {
342        self.0
343    }
344}
345
346/// Raw bytes of the (uncompiled) contract code.
347#[derive(
348    Debug,
349    Clone,
350    PartialEq,
351    Eq,
352    BorshSerialize,
353    BorshDeserialize,
354    serde::Serialize,
355    serde::Deserialize,
356    ProtocolSchema,
357)]
358pub struct CodeBytes(pub Arc<[u8]>);
359
360impl CodeBytes {
361    pub fn hash(&self) -> CodeHash {
362        hash(self.0.as_ref()).into()
363    }
364}
365
366impl From<ContractCode> for CodeBytes {
367    fn from(code: ContractCode) -> Self {
368        Self(code.take_code().into())
369    }
370}
371
372impl Into<ContractCode> for CodeBytes {
373    fn into(self) -> ContractCode {
374        ContractCode::new(self.0.to_vec(), None)
375    }
376}
377
378/// Contains the accesses and changes (eg. deployments) to the contracts while applying a chunk.
379#[derive(Debug, Default, Clone)]
380pub struct ContractUpdates {
381    /// Code-hashes of the contracts accessed (called) while applying the chunk.
382    pub contract_accesses: HashSet<CodeHash>,
383    /// Contracts deployed while applying the chunk.
384    pub contract_deploys: Vec<ContractCode>,
385}
386
387impl ContractUpdates {
388    /// Returns the code-hashes of the contracts deployed.
389    pub fn contract_deploy_hashes(&self) -> HashSet<CodeHash> {
390        self.contract_deploys.iter().map(|contract| (*contract.hash()).into()).collect()
391    }
392}
393
394// Data structures for chunk producers to send deployed contracts to chunk validators.
395
396#[derive(Clone, BorshSerialize, BorshDeserialize, ProtocolSchema)]
397pub struct ChunkContractDeploys {
398    compressed_contracts: CompressedContractCode,
399}
400
401impl ChunkContractDeploys {
402    pub fn compress_contracts(contracts: &Vec<CodeBytes>) -> std::io::Result<Self> {
403        CompressedContractCode::encode(contracts)
404            .map(|(compressed_contracts, _size)| Self { compressed_contracts })
405    }
406
407    pub fn decompress_contracts(&self) -> std::io::Result<Vec<CodeBytes>> {
408        self.compressed_contracts.decode().map(|(data, _size)| data)
409    }
410}
411
412#[cfg(feature = "solomon")]
413impl ReedSolomonEncoderSerialize for ChunkContractDeploys {}
414#[cfg(feature = "solomon")]
415impl ReedSolomonEncoderDeserialize for ChunkContractDeploys {}
416
417#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
418#[borsh(use_discriminant = true)]
419#[repr(u8)]
420pub enum PartialEncodedContractDeploys {
421    V1(PartialEncodedContractDeploysV1) = 0,
422}
423
424impl PartialEncodedContractDeploys {
425    pub fn new(
426        key: ChunkProductionKey,
427        part: PartialEncodedContractDeploysPart,
428        signer: &ValidatorSigner,
429    ) -> Self {
430        Self::V1(PartialEncodedContractDeploysV1::new(key, part, signer))
431    }
432
433    pub fn chunk_production_key(&self) -> &ChunkProductionKey {
434        match &self {
435            Self::V1(v1) => &v1.inner.next_chunk,
436        }
437    }
438
439    pub fn part(&self) -> &PartialEncodedContractDeploysPart {
440        match &self {
441            Self::V1(v1) => &v1.inner.part,
442        }
443    }
444
445    pub fn verify_signature(&self, public_key: &PublicKey) -> bool {
446        match self {
447            Self::V1(accesses) => accesses.verify_signature(public_key),
448        }
449    }
450}
451
452impl Into<(ChunkProductionKey, PartialEncodedContractDeploysPart)>
453    for PartialEncodedContractDeploys
454{
455    fn into(self) -> (ChunkProductionKey, PartialEncodedContractDeploysPart) {
456        match self {
457            Self::V1(PartialEncodedContractDeploysV1 { inner, .. }) => {
458                (inner.next_chunk, inner.part)
459            }
460        }
461    }
462}
463
464#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
465pub struct PartialEncodedContractDeploysV1 {
466    inner: PartialEncodedContractDeploysInner,
467    signature: Signature,
468}
469
470impl PartialEncodedContractDeploysV1 {
471    pub fn new(
472        key: ChunkProductionKey,
473        part: PartialEncodedContractDeploysPart,
474        signer: &ValidatorSigner,
475    ) -> Self {
476        let inner = PartialEncodedContractDeploysInner::new(key, part);
477        let signature = signer.sign_bytes(&borsh::to_vec(&inner).unwrap());
478        Self { inner, signature }
479    }
480
481    pub fn verify_signature(&self, public_key: &PublicKey) -> bool {
482        self.signature.verify(&borsh::to_vec(&self.inner).unwrap(), public_key)
483    }
484}
485
486#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
487pub struct PartialEncodedContractDeploysPart {
488    pub part_ord: usize,
489    pub data: Box<[u8]>,
490    pub encoded_length: usize,
491}
492
493impl std::fmt::Debug for PartialEncodedContractDeploysPart {
494    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
495        f.debug_struct("PartialEncodedContractDeploysPart")
496            .field("part_ord", &self.part_ord)
497            .field("data_size", &self.data.len())
498            .field("encoded_length", &self.encoded_length)
499            .finish()
500    }
501}
502
503#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, ProtocolSchema)]
504pub struct PartialEncodedContractDeploysInner {
505    next_chunk: ChunkProductionKey,
506    part: PartialEncodedContractDeploysPart,
507    signature_differentiator: SignatureDifferentiator,
508}
509
510impl PartialEncodedContractDeploysInner {
511    fn new(next_chunk: ChunkProductionKey, part: PartialEncodedContractDeploysPart) -> Self {
512        Self {
513            next_chunk,
514            part,
515            signature_differentiator: "PartialEncodedContractDeploysInner".to_owned(),
516        }
517    }
518}