linera_sdk/test/block.rs
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 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
// Copyright (c) Zefchain Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
//! A builder of [`Block`]s which are then signed to become [`Certificate`]s.
//!
//! Helps with the construction of blocks, adding operations and
use linera_base::{
abi::ContractAbi,
data_types::{Amount, ApplicationPermissions, Blob, Epoch, Round, Timestamp},
identifiers::{AccountOwner, ApplicationId, ChainId},
ownership::TimeoutConfig,
};
use linera_chain::{
data_types::{
IncomingBundle, LiteValue, LiteVote, Medium, MessageAction, Origin, ProposedBlock,
SignatureAggregator,
},
types::{ConfirmedBlock, ConfirmedBlockCertificate},
};
use linera_core::worker::WorkerError;
use linera_execution::{
system::{Recipient, SystemOperation},
Operation,
};
use super::TestValidator;
/// A helper type to build a block proposal using the builder pattern, and then signing them into
/// [`ConfirmedBlockCertificate`]s using a [`TestValidator`].
pub struct BlockBuilder {
block: ProposedBlock,
validator: TestValidator,
}
impl BlockBuilder {
/// Creates a new [`BlockBuilder`], initializing the block so that it belongs to a microchain.
///
/// Initializes the block so that it belongs to the microchain identified by `chain_id` and
/// owned by `owner`. It becomes the block after the specified `previous_block`, or the genesis
/// block if [`None`] is specified.
///
/// # Notes
///
/// This is an internal method, because the [`BlockBuilder`] instance should be built by an
/// [`ActiveChain`]. External users should only be able to add operations and messages to the
/// block.
pub(crate) fn new(
chain_id: ChainId,
owner: AccountOwner,
epoch: Epoch,
previous_block: Option<&ConfirmedBlockCertificate>,
validator: TestValidator,
) -> Self {
let previous_block_hash = previous_block.map(|certificate| certificate.hash());
let height = previous_block
.map(|certificate| {
certificate
.inner()
.height()
.try_add_one()
.expect("Block height limit reached")
})
.unwrap_or_default();
BlockBuilder {
block: ProposedBlock {
epoch,
chain_id,
incoming_bundles: vec![],
operations: vec![],
previous_block_hash,
height,
authenticated_signer: Some(owner),
timestamp: Timestamp::from(0),
},
validator,
}
}
/// Configures the timestamp of this block.
pub fn with_timestamp(&mut self, timestamp: Timestamp) -> &mut Self {
self.block.timestamp = timestamp;
self
}
/// Adds a native token transfer to this block.
pub fn with_native_token_transfer(
&mut self,
sender: AccountOwner,
recipient: Recipient,
amount: Amount,
) -> &mut Self {
self.with_system_operation(SystemOperation::Transfer {
owner: sender,
recipient,
amount,
})
}
/// Adds a [`SystemOperation`] to this block.
pub(crate) fn with_system_operation(&mut self, operation: SystemOperation) -> &mut Self {
self.block.operations.push(operation.into());
self
}
/// Adds an operation to change this chain's ownership.
pub fn with_owner_change(
&mut self,
super_owners: Vec<AccountOwner>,
owners: Vec<(AccountOwner, u64)>,
multi_leader_rounds: u32,
open_multi_leader_rounds: bool,
timeout_config: TimeoutConfig,
) -> &mut Self {
self.with_system_operation(SystemOperation::ChangeOwnership {
super_owners,
owners,
multi_leader_rounds,
open_multi_leader_rounds,
timeout_config,
})
}
/// Adds an application permissions change to this block.
pub fn with_change_application_permissions(
&mut self,
permissions: ApplicationPermissions,
) -> &mut Self {
self.with_system_operation(SystemOperation::ChangeApplicationPermissions(permissions))
}
/// Adds a user `operation` to this block.
///
/// The operation is serialized using [`bcs`] and added to the block, marked to be executed by
/// `application`.
pub fn with_operation<Abi>(
&mut self,
application_id: ApplicationId<Abi>,
operation: Abi::Operation,
) -> &mut Self
where
Abi: ContractAbi,
{
let operation = Abi::serialize_operation(&operation)
.expect("Failed to serialize `Operation` in BlockBuilder");
self.with_raw_operation(application_id.forget_abi(), operation)
}
/// Adds an already serialized user `operation` to this block.
pub fn with_raw_operation(
&mut self,
application_id: ApplicationId,
operation: impl Into<Vec<u8>>,
) -> &mut Self {
self.block.operations.push(Operation::User {
application_id,
bytes: operation.into(),
});
self
}
/// Receives incoming message bundles by specifying them directly.
///
/// This is an internal method that bypasses the check to see if the messages are already
/// present in the inboxes of the microchain that owns this block.
pub(crate) fn with_incoming_bundles(
&mut self,
bundles: impl IntoIterator<Item = IncomingBundle>,
) -> &mut Self {
self.block.incoming_bundles.extend(bundles);
self
}
/// Receives all direct messages that were sent to this chain by the given certificate.
pub fn with_messages_from(&mut self, certificate: &ConfirmedBlockCertificate) -> &mut Self {
self.with_messages_from_by_medium(certificate, &Medium::Direct, MessageAction::Accept)
}
/// Receives all messages that were sent to this chain by the given certificate.
pub fn with_messages_from_by_medium(
&mut self,
certificate: &ConfirmedBlockCertificate,
medium: &Medium,
action: MessageAction,
) -> &mut Self {
let origin = Origin {
sender: certificate.inner().chain_id(),
medium: medium.clone(),
};
let bundles = certificate
.message_bundles_for(medium, self.block.chain_id)
.map(|(_epoch, bundle)| IncomingBundle {
origin: origin.clone(),
bundle,
action,
});
self.with_incoming_bundles(bundles)
}
/// Tries to sign the prepared block with the [`TestValidator`]'s keys and return the
/// resulting [`Certificate`]. Returns an error if block execution fails.
pub(crate) async fn try_sign(
self,
blobs: &[Blob],
) -> Result<ConfirmedBlockCertificate, WorkerError> {
let published_blobs = self
.block
.published_blob_ids()
.into_iter()
.map(|blob_id| {
blobs
.iter()
.find(|blob| blob.id() == blob_id)
.expect("missing published blob")
.clone()
})
.collect();
let (block, _) = self
.validator
.worker()
.stage_block_execution(self.block, None, published_blobs)
.await?;
let value = ConfirmedBlock::new(block);
let vote = LiteVote::new(
LiteValue::new(&value),
Round::Fast,
self.validator.key_pair(),
);
let committee = self.validator.committee().await;
let mut builder = SignatureAggregator::new(value, Round::Fast, &committee);
let certificate = builder
.append(vote.public_key, vote.signature)
.expect("Failed to sign block")
.expect("Committee has more than one test validator");
Ok(certificate)
}
}