linera_chain/test/
mod.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Test utilities
5
6mod http_server;
7
8use linera_base::{
9    crypto::{AccountPublicKey, Signer},
10    data_types::{Amount, BlockHeight, Epoch, Round, Timestamp},
11    identifiers::{AccountOwner, ChainId},
12};
13use linera_execution::{
14    committee::{Committee, ValidatorState},
15    system::Recipient,
16    Message, MessageKind, Operation, ResourceControlPolicy, SystemOperation,
17};
18
19pub use self::http_server::HttpServer;
20use crate::{
21    block::ConfirmedBlock,
22    data_types::{
23        BlockProposal, IncomingBundle, PostedMessage, ProposedBlock, SignatureAggregator, Vote,
24    },
25    types::{CertificateValue, GenericCertificate},
26};
27
28/// Creates a new child of the given block, with the same timestamp.
29pub fn make_child_block(parent: &ConfirmedBlock) -> ProposedBlock {
30    let parent_header = &parent.block().header;
31    ProposedBlock {
32        epoch: parent_header.epoch,
33        chain_id: parent_header.chain_id,
34        incoming_bundles: vec![],
35        operations: vec![],
36        previous_block_hash: Some(parent.hash()),
37        height: parent_header.height.try_add_one().unwrap(),
38        authenticated_signer: parent_header.authenticated_signer,
39        timestamp: parent_header.timestamp,
40    }
41}
42
43/// Creates a block at height 0 for a new chain.
44pub fn make_first_block(chain_id: ChainId) -> ProposedBlock {
45    ProposedBlock {
46        epoch: Epoch::ZERO,
47        chain_id,
48        incoming_bundles: vec![],
49        operations: vec![],
50        previous_block_hash: None,
51        height: BlockHeight::ZERO,
52        authenticated_signer: None,
53        timestamp: Timestamp::default(),
54    }
55}
56
57/// A helper trait to simplify constructing blocks for tests.
58#[allow(async_fn_in_trait)]
59pub trait BlockTestExt: Sized {
60    /// Returns the block with the given authenticated signer.
61    fn with_authenticated_signer(self, authenticated_signer: Option<AccountOwner>) -> Self;
62
63    /// Returns the block with the given operation appended at the end.
64    fn with_operation(self, operation: impl Into<Operation>) -> Self;
65
66    /// Returns the block with a transfer operation appended at the end.
67    fn with_transfer(self, owner: AccountOwner, recipient: Recipient, amount: Amount) -> Self;
68
69    /// Returns the block with a simple transfer operation appended at the end.
70    fn with_simple_transfer(self, chain_id: ChainId, amount: Amount) -> Self;
71
72    /// Returns the block with the given message appended at the end.
73    fn with_incoming_bundle(self, incoming_bundle: IncomingBundle) -> Self;
74
75    /// Returns the block with the specified timestamp.
76    fn with_timestamp(self, timestamp: impl Into<Timestamp>) -> Self;
77
78    /// Returns the block with the specified epoch.
79    fn with_epoch(self, epoch: impl Into<Epoch>) -> Self;
80
81    /// Returns the block with the burn operation appended at the end.
82    fn with_burn(self, amount: Amount) -> Self;
83
84    /// Returns a block proposal in the first round in a default ownership configuration
85    /// (`Round::MultiLeader(0)`) without any hashed certificate values or validated block.
86    async fn into_first_proposal<S: Signer + ?Sized>(
87        self,
88        owner: AccountOwner,
89        signer: &S,
90    ) -> Result<BlockProposal, S::Error> {
91        self.into_proposal_with_round(owner, signer, Round::MultiLeader(0))
92            .await
93    }
94
95    /// Returns a block proposal without any hashed certificate values or validated block.
96    async fn into_proposal_with_round<S: Signer + ?Sized>(
97        self,
98        owner: AccountOwner,
99        signer: &S,
100        round: Round,
101    ) -> Result<BlockProposal, S::Error>;
102}
103
104impl BlockTestExt for ProposedBlock {
105    fn with_authenticated_signer(mut self, authenticated_signer: Option<AccountOwner>) -> Self {
106        self.authenticated_signer = authenticated_signer;
107        self
108    }
109
110    fn with_operation(mut self, operation: impl Into<Operation>) -> Self {
111        self.operations.push(operation.into());
112        self
113    }
114
115    fn with_transfer(self, owner: AccountOwner, recipient: Recipient, amount: Amount) -> Self {
116        self.with_operation(SystemOperation::Transfer {
117            owner,
118            recipient,
119            amount,
120        })
121    }
122
123    fn with_simple_transfer(self, chain_id: ChainId, amount: Amount) -> Self {
124        self.with_transfer(AccountOwner::CHAIN, Recipient::chain(chain_id), amount)
125    }
126
127    fn with_burn(self, amount: Amount) -> Self {
128        self.with_operation(SystemOperation::Transfer {
129            owner: AccountOwner::CHAIN,
130            recipient: Recipient::Burn,
131            amount,
132        })
133    }
134
135    fn with_incoming_bundle(mut self, incoming_bundle: IncomingBundle) -> Self {
136        self.incoming_bundles.push(incoming_bundle);
137        self
138    }
139
140    fn with_timestamp(mut self, timestamp: impl Into<Timestamp>) -> Self {
141        self.timestamp = timestamp.into();
142        self
143    }
144
145    fn with_epoch(mut self, epoch: impl Into<Epoch>) -> Self {
146        self.epoch = epoch.into();
147        self
148    }
149
150    async fn into_proposal_with_round<S: Signer + ?Sized>(
151        self,
152        owner: AccountOwner,
153        signer: &S,
154        round: Round,
155    ) -> Result<BlockProposal, S::Error> {
156        BlockProposal::new_initial(owner, round, self, signer).await
157    }
158}
159
160pub trait VoteTestExt<T: CertificateValue>: Sized {
161    /// Returns a certificate for a committee consisting only of this validator.
162    fn into_certificate(self) -> GenericCertificate<T>;
163}
164
165impl<T: CertificateValue> VoteTestExt<T> for Vote<T> {
166    fn into_certificate(self) -> GenericCertificate<T> {
167        let state = ValidatorState {
168            network_address: "".to_string(),
169            votes: 100,
170            account_public_key: AccountPublicKey::test_key(1),
171        };
172        let committee = Committee::new(
173            vec![(self.public_key, state)].into_iter().collect(),
174            ResourceControlPolicy::only_fuel(),
175        );
176        SignatureAggregator::new(self.value, self.round, &committee)
177            .append(self.public_key, self.signature)
178            .unwrap()
179            .unwrap()
180    }
181}
182
183/// Helper trait to simplify constructing messages for tests.
184pub trait MessageTestExt: Sized {
185    fn to_posted(self, index: u32, kind: MessageKind) -> PostedMessage;
186}
187
188impl<T: Into<Message>> MessageTestExt for T {
189    fn to_posted(self, index: u32, kind: MessageKind) -> PostedMessage {
190        PostedMessage {
191            authenticated_signer: None,
192            grant: Amount::ZERO,
193            refund_grant_to: None,
194            kind,
195            index,
196            message: self.into(),
197        }
198    }
199}