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