use linera_base::data_types::{Blob, BlockHeight, Bytecode, Timestamp};
#[cfg(with_testing)]
use linera_base::vm::VmRuntime;
use linera_views::context::MemoryContext;
use super::*;
use crate::{
test_utils::dummy_chain_description, ExecutionStateView, TestExecutionRuntimeContext,
FLAG_HISTORICAL_HASH, FLAG_HISTORICAL_HASH_SHADOW, FLAG_ZERO_HASH,
};
async fn new_view_and_context() -> (
ExecutionStateView<MemoryContext<TestExecutionRuntimeContext>>,
OperationContext,
) {
let description = dummy_chain_description(5);
let context = OperationContext {
chain_id: ChainId::from(&description),
authenticated_signer: None,
height: BlockHeight::from(7),
round: Some(0),
timestamp: Default::default(),
};
let state = SystemExecutionState {
description: Some(description),
epoch: Epoch(1),
admin_chain_id: Some(dummy_chain_description(0).id()),
committees: BTreeMap::new(),
..SystemExecutionState::default()
};
let view = state.into_view().await;
(view, context)
}
fn expected_application_id(
context: &OperationContext,
module_id: &ModuleId,
parameters: Vec<u8>,
required_application_ids: Vec<ApplicationId>,
application_index: u32,
) -> ApplicationId {
let description = ApplicationDescription {
module_id: *module_id,
creator_chain_id: context.chain_id,
block_height: context.height,
application_index,
parameters,
required_application_ids,
};
From::from(&description)
}
#[tokio::test]
async fn application_message_index() -> anyhow::Result<()> {
let (mut view, context) = new_view_and_context().await;
let contract = Bytecode::new(b"contract".into());
let service = Bytecode::new(b"service".into());
let contract_blob = Blob::new_contract_bytecode(contract.compress());
let service_blob = Blob::new_service_bytecode(service.compress());
let vm_runtime = VmRuntime::Wasm;
let module_id = ModuleId::new(contract_blob.id().hash, service_blob.id().hash, vm_runtime);
let operation = SystemOperation::CreateApplication {
module_id,
parameters: vec![],
instantiation_argument: vec![],
required_application_ids: vec![],
};
let mut txn_tracker = TransactionTracker::default();
view.context()
.extra()
.add_blobs([contract_blob, service_blob])
.await?;
let mut controller = ResourceController::default();
let new_application = view
.system
.execute_operation(context, operation, &mut txn_tracker, &mut controller)
.await?;
let id = expected_application_id(&context, &module_id, vec![], vec![], 0);
assert_eq!(new_application, Some((id, vec![])));
Ok(())
}
#[tokio::test]
async fn open_chain_message_index() {
let (mut view, context) = new_view_and_context().await;
let owner = linera_base::crypto::AccountPublicKey::test_key(0).into();
let ownership = ChainOwnership::single(owner);
let config = OpenChainConfig {
ownership,
balance: Amount::ZERO,
application_permissions: Default::default(),
};
let mut txn_tracker = TransactionTracker::default();
let operation = SystemOperation::OpenChain(config.clone());
let mut controller = ResourceController::default();
let new_application = view
.system
.execute_operation(context, operation, &mut txn_tracker, &mut controller)
.await
.unwrap();
assert_eq!(new_application, None);
assert_eq!(
txn_tracker.into_outcome().unwrap().blobs[0].id().blob_type,
BlobType::ChainDescription,
);
}
#[tokio::test]
async fn empty_accounts_are_removed() -> anyhow::Result<()> {
let owner = AccountOwner::from(CryptoHash::test_hash("account owner"));
let amount = Amount::from_tokens(99);
let mut view = SystemExecutionState {
description: Some(dummy_chain_description(0)),
balances: BTreeMap::from([(owner, amount)]),
..SystemExecutionState::default()
}
.into_view()
.await;
view.system.debit(&owner, amount).await?;
assert!(view.system.balances.indices().await?.is_empty());
Ok(())
}
#[tokio::test]
async fn hashing_test() -> anyhow::Result<()> {
let (mut view, _) = new_view_and_context().await;
let zero_hash = CryptoHash::from([0u8; 32]);
assert_ne!(view.crypto_hash_mut().await?, zero_hash);
let mut committees = BTreeMap::new();
committees.insert(Epoch(0), Committee::default());
view.system.committees.set(committees.clone());
view.system.epoch.set(Epoch(0));
assert_ne!(view.crypto_hash_mut().await?, zero_hash);
let mut committee = Committee::default();
committee
.policy_mut()
.http_request_allow_list
.insert(FLAG_ZERO_HASH.to_owned());
committees.insert(Epoch(1), committee);
view.system.committees.set(committees);
view.system.epoch.set(Epoch(1));
assert_eq!(view.crypto_hash_mut().await?, zero_hash);
Ok(())
}
fn install_committee_with_flag(
view: &mut ExecutionStateView<MemoryContext<TestExecutionRuntimeContext>>,
flag: &str,
) {
let mut committee = Committee::default();
committee
.policy_mut()
.http_request_allow_list
.insert(flag.to_owned());
let mut committees = BTreeMap::new();
committees.insert(Epoch(1), committee);
view.system.committees.set(committees);
view.system.epoch.set(Epoch(1));
}
#[tokio::test]
async fn historical_hashing_enforce_seeds_then_chains() -> anyhow::Result<()> {
let zero_hash = CryptoHash::from([0u8; 32]);
let (mut view, _) = new_view_and_context().await;
install_committee_with_flag(&mut view, FLAG_HISTORICAL_HASH);
assert!(view.historical_hash.get().is_none());
let seed = view.crypto_hash_mut().await?;
assert_ne!(seed, zero_hash);
let seed_raw = *view.historical_hash.get();
assert!(seed_raw.is_some());
view.system.timestamp.set(Timestamp::from(42));
let chained = view.crypto_hash_mut().await?;
assert_ne!(chained, zero_hash);
assert_ne!(chained, seed);
assert_ne!(*view.historical_hash.get(), seed_raw);
Ok(())
}
#[tokio::test]
async fn historical_hashing_shadow_reports_zero_but_advances() -> anyhow::Result<()> {
let zero_hash = CryptoHash::from([0u8; 32]);
let (mut view, _) = new_view_and_context().await;
install_committee_with_flag(&mut view, FLAG_HISTORICAL_HASH_SHADOW);
let reported = view.crypto_hash_mut().await?;
assert_eq!(reported, zero_hash);
let seed_raw = *view.historical_hash.get();
assert!(seed_raw.is_some());
view.system.timestamp.set(Timestamp::from(42));
assert_eq!(view.crypto_hash_mut().await?, zero_hash);
assert_ne!(*view.historical_hash.get(), seed_raw);
Ok(())
}
#[tokio::test]
async fn legacy_hash_matches_pre_change_value() -> anyhow::Result<()> {
let (mut view, _) = new_view_and_context().await;
let recorded = CryptoHash::from([
146, 2, 171, 91, 66, 52, 95, 140, 217, 91, 65, 171, 135, 96, 184, 168, 137, 188, 74, 230,
101, 129, 37, 163, 165, 5, 233, 111, 203, 213, 151, 165,
]);
assert_eq!(view.crypto_hash_mut().await?, recorded);
Ok(())
}
#[tokio::test]
async fn historical_hashing_resets_when_deactivated() -> anyhow::Result<()> {
let zero_hash = CryptoHash::from([0u8; 32]);
let (mut view, _) = new_view_and_context().await;
install_committee_with_flag(&mut view, FLAG_HISTORICAL_HASH);
assert_ne!(view.crypto_hash_mut().await?, zero_hash);
assert!(view.historical_hash.get().is_some());
install_committee_with_flag(&mut view, FLAG_ZERO_HASH);
assert_eq!(view.crypto_hash_mut().await?, zero_hash);
assert!(view.historical_hash.get().is_none());
install_committee_with_flag(&mut view, FLAG_HISTORICAL_HASH);
assert_ne!(view.crypto_hash_mut().await?, zero_hash);
assert!(view.historical_hash.get().is_some());
Ok(())
}
#[tokio::test]
async fn historical_hashing_is_deterministic() -> anyhow::Result<()> {
async fn run(timestamp: u64) -> anyhow::Result<(CryptoHash, CryptoHash)> {
let (mut view, _) = new_view_and_context().await;
install_committee_with_flag(&mut view, FLAG_HISTORICAL_HASH);
let seed = view.crypto_hash_mut().await?;
view.system.timestamp.set(Timestamp::from(timestamp));
let chained = view.crypto_hash_mut().await?;
Ok((seed, chained))
}
let (seed_a, chained_a) = Box::pin(run(42)).await?;
let (seed_b, chained_b) = Box::pin(run(42)).await?;
assert_eq!(seed_a, seed_b);
assert_eq!(chained_a, chained_b);
let (seed_c, chained_c) = Box::pin(run(99)).await?;
assert_eq!(seed_a, seed_c); assert_ne!(chained_a, chained_c);
Ok(())
}
#[tokio::test]
async fn historical_hashing_persists_and_chains_across_reload() -> anyhow::Result<()> {
use linera_views::{
batch::Batch, context::Context as _, store::WritableKeyValueStore as _, views::View as _,
};
async fn save(view: &mut ExecutionStateView<MemoryContext<TestExecutionRuntimeContext>>) {
let mut batch = Batch::new();
view.pre_save(&mut batch).unwrap();
view.context().store().write_batch(batch).await.unwrap();
view.post_save();
}
let zero_hash = CryptoHash::from([0u8; 32]);
let (mut view, _) = new_view_and_context().await;
let context = view.context().clone();
save(&mut view).await;
let mut view = ExecutionStateView::load(context.clone()).await?;
assert!(view.historical_hash.get().is_none());
install_committee_with_flag(&mut view, FLAG_HISTORICAL_HASH);
let seed = view.crypto_hash_mut().await?;
assert_ne!(seed, zero_hash);
let seed_raw = *view.historical_hash.get();
assert!(seed_raw.is_some());
save(&mut view).await;
let mut view = ExecutionStateView::load(context.clone()).await?;
assert_eq!(*view.historical_hash.get(), seed_raw);
install_committee_with_flag(&mut view, FLAG_HISTORICAL_HASH);
view.system.timestamp.set(Timestamp::from(42));
let chained = view.crypto_hash_mut().await?;
assert_ne!(chained, seed);
assert_ne!(chained, zero_hash);
let chained_raw = *view.historical_hash.get();
save(&mut view).await;
let view = ExecutionStateView::load(context.clone()).await?;
assert_eq!(*view.historical_hash.get(), chained_raw);
Ok(())
}