use casper_execution_engine::core::engine_state::executable_deploy_item::ExecutableDeployItem;
use casper_types::{
bytesrepr::{Bytes, ToBytes},
runtime_args, RuntimeArgs, SecretKey,
};
use crate::{
crypto::AsymmetricKeyExt,
testing::TestRng,
types::{BlockLike, Deploy, DeployHash, TimeDiff},
};
use super::*;
use casper_types::standard_payment::ARG_AMOUNT;
fn default_gas_payment() -> Gas {
Gas::from(1u32)
}
fn generate_transfer(
rng: &mut TestRng,
timestamp: Timestamp,
ttl: TimeDiff,
dependencies: Vec<DeployHash>,
payment_amount: Gas,
) -> Deploy {
let gas_price = 0;
let secret_key = SecretKey::random(rng);
let chain_name = "chain".to_string();
let args = runtime_args! {
ARG_AMOUNT => payment_amount.value()
}
.to_bytes()
.expect("should serialize");
let payment = ExecutableDeployItem::ModuleBytes {
module_bytes: Bytes::new(),
args: args.into(),
};
let session = ExecutableDeployItem::Transfer { args: Bytes::new() };
Deploy::new(
timestamp,
ttl,
gas_price,
dependencies,
chain_name,
payment,
session,
&secret_key,
rng,
)
}
fn generate_deploy(
rng: &mut TestRng,
timestamp: Timestamp,
ttl: TimeDiff,
dependencies: Vec<DeployHash>,
payment_amount: Gas,
) -> Deploy {
let gas_price = 0;
let secret_key = SecretKey::random(rng);
let chain_name = "chain".to_string();
let args = runtime_args! {
ARG_AMOUNT => payment_amount.value()
}
.to_bytes()
.expect("should serialize");
let payment = ExecutableDeployItem::ModuleBytes {
module_bytes: Bytes::new(),
args: args.into(),
};
let session = ExecutableDeployItem::ModuleBytes {
module_bytes: Bytes::new(),
args: Bytes::new(),
};
Deploy::new(
timestamp,
ttl,
gas_price,
dependencies,
chain_name,
payment,
session,
&secret_key,
rng,
)
}
fn create_test_proposer() -> BlockProposerReady {
BlockProposerReady {
sets: Default::default(),
deploy_config: Default::default(),
state_key: b"block-proposer-test".to_vec(),
request_queue: Default::default(),
unhandled_finalized: Default::default(),
}
}
impl From<StorageRequest> for Event {
fn from(_: StorageRequest) -> Self {
unreachable!("no storage requests in block proposer unit tests")
}
}
impl From<StateStoreRequest> for Event {
fn from(_: StateStoreRequest) -> Self {
unreachable!("no state store requests in block proposer unit tests")
}
}
#[test]
fn should_add_and_take_deploys() {
let creation_time = Timestamp::from(100);
let ttl = TimeDiff::from(Duration::from_millis(100));
let block_time1 = Timestamp::from(80);
let block_time2 = Timestamp::from(120);
let block_time3 = Timestamp::from(220);
let no_deploys = HashSet::new();
let mut proposer = create_test_proposer();
let mut rng = crate::new_rng();
let deploy1 = generate_deploy(&mut rng, creation_time, ttl, vec![], default_gas_payment());
let deploy2 = generate_deploy(&mut rng, creation_time, ttl, vec![], default_gas_payment());
let deploy3 = generate_deploy(&mut rng, creation_time, ttl, vec![], default_gas_payment());
let deploy4 = generate_deploy(&mut rng, creation_time, ttl, vec![], default_gas_payment());
assert!(proposer
.propose_proto_block(
DeployConfig::default(),
block_time2,
no_deploys.clone(),
true
)
.deploys()
.is_empty());
proposer.add_deploy_or_transfer(block_time2, *deploy1.id(), deploy1.deploy_type().unwrap());
proposer.add_deploy_or_transfer(block_time2, *deploy2.id(), deploy2.deploy_type().unwrap());
assert!(proposer
.propose_proto_block(
DeployConfig::default(),
block_time1,
no_deploys.clone(),
true
)
.deploys()
.is_empty());
assert!(proposer
.propose_proto_block(
DeployConfig::default(),
block_time3,
no_deploys.clone(),
true
)
.deploys()
.is_empty());
let block = proposer.propose_proto_block(
DeployConfig::default(),
block_time2,
no_deploys.clone(),
true,
);
let deploys = block.deploys();
assert_eq!(deploys.len(), 2);
assert!(deploys.contains(&deploy1.id()));
assert!(deploys.contains(&deploy2.id()));
let block = proposer.propose_proto_block(
DeployConfig::default(),
block_time2,
no_deploys.clone(),
true,
);
let deploys = block
.deploys()
.iter()
.map(|hash| **hash)
.collect::<HashSet<_>>();
assert_eq!(deploys.len(), 2);
assert!(proposer
.propose_proto_block(DeployConfig::default(), block_time2, deploys.clone(), true)
.deploys()
.is_empty());
proposer.finalized_deploys(deploys.iter().copied());
proposer.add_deploy_or_transfer(block_time2, *deploy3.id(), deploy3.deploy_type().unwrap());
proposer.add_deploy_or_transfer(block_time2, *deploy4.id(), deploy4.deploy_type().unwrap());
let block =
proposer.propose_proto_block(DeployConfig::default(), block_time2, no_deploys, true);
let deploys = block.deploys();
assert_eq!(deploys.len(), 2);
assert!(deploys.contains(&deploy3.id()));
assert!(deploys.contains(&deploy4.id()));
}
#[test]
fn should_successfully_prune() {
let expired_time = Timestamp::from(201);
let creation_time = Timestamp::from(100);
let test_time = Timestamp::from(120);
let ttl = TimeDiff::from(Duration::from_millis(100));
let mut rng = crate::new_rng();
let deploy1 = generate_deploy(&mut rng, creation_time, ttl, vec![], default_gas_payment());
let deploy2 = generate_deploy(&mut rng, creation_time, ttl, vec![], default_gas_payment());
let deploy3 = generate_deploy(&mut rng, creation_time, ttl, vec![], default_gas_payment());
let deploy4 = generate_deploy(
&mut rng,
creation_time + Duration::from_secs(20).into(),
ttl,
vec![],
default_gas_payment(),
);
let mut proposer = create_test_proposer();
proposer.add_deploy_or_transfer(creation_time, *deploy1.id(), deploy1.deploy_type().unwrap());
proposer.add_deploy_or_transfer(creation_time, *deploy2.id(), deploy2.deploy_type().unwrap());
proposer.add_deploy_or_transfer(creation_time, *deploy3.id(), deploy3.deploy_type().unwrap());
proposer.add_deploy_or_transfer(creation_time, *deploy4.id(), deploy4.deploy_type().unwrap());
proposer.finalized_deploys(vec![*deploy1.id()]);
assert_eq!(proposer.sets.pending.len(), 3);
assert!(proposer.sets.finalized_deploys.contains_key(deploy1.id()));
let pruned = proposer.prune(test_time);
assert_eq!(pruned, 0);
assert_eq!(proposer.sets.pending.len(), 3);
assert_eq!(proposer.sets.finalized_deploys.len(), 1);
assert!(proposer.sets.finalized_deploys.contains_key(&deploy1.id()));
let pruned = proposer.prune(expired_time);
assert_eq!(pruned, 3);
assert_eq!(proposer.sets.pending.len(), 1); assert_eq!(proposer.sets.finalized_deploys.len(), 0);
}
#[test]
fn should_keep_track_of_unhandled_deploys() {
let creation_time = Timestamp::from(100);
let test_time = Timestamp::from(120);
let ttl = TimeDiff::from(Duration::from_millis(100));
let mut rng = crate::new_rng();
let deploy1 = generate_deploy(&mut rng, creation_time, ttl, vec![], default_gas_payment());
let deploy2 = generate_deploy(&mut rng, creation_time, ttl, vec![], default_gas_payment());
let mut proposer = create_test_proposer();
proposer.add_deploy_or_transfer(creation_time, *deploy1.id(), deploy1.deploy_type().unwrap());
proposer.finalized_deploys(vec![*deploy1.id(), *deploy2.id()]);
assert!(
proposer.contains_finalized(deploy1.id()),
"should contain deploy1"
);
assert!(
proposer.contains_finalized(deploy2.id()),
"deploy2's hash should be considered seen"
);
assert!(
!proposer.sets.finalized_deploys.contains_key(deploy2.id()),
"should not yet contain deploy2"
);
let past_deploys = HashSet::new();
assert!(
proposer.is_deploy_valid(
deploy2.header(),
test_time,
&proposer.deploy_config,
&past_deploys
),
"deploy2 should -not- be valid"
);
proposer.add_deploy_or_transfer(creation_time, *deploy2.id(), deploy2.deploy_type().unwrap());
assert!(
proposer.sets.finalized_deploys.contains_key(deploy2.id()),
"deploy2 should now be in finalized_deploys"
);
assert!(
!proposer.unhandled_finalized.contains(deploy2.id()),
"deploy2 should not be in unhandled_finalized"
);
}
#[test]
fn should_respect_limits_for_wasmless_transfers() {
test_proposer_with(TestArgs {
transfer_count: 30,
max_transfer_count: 20,
proposed_count: 20,
remaining_pending_count: 10,
..Default::default()
});
}
#[test]
fn should_respect_limits_for_wasm_deploys() {
test_proposer_with(TestArgs {
deploy_count: 30,
max_deploy_count: 20,
proposed_count: 20,
remaining_pending_count: 10,
..Default::default()
});
}
#[test]
fn should_respect_limits_for_wasm_deploys_and_transfers_together() {
test_proposer_with(TestArgs {
transfer_count: 30,
max_transfer_count: 20,
deploy_count: 30,
max_deploy_count: 20,
proposed_count: 40,
remaining_pending_count: 20,
..Default::default()
});
}
#[test]
fn should_respect_limits_for_gas_cost() {
test_proposer_with(TestArgs {
transfer_count: 15,
max_transfer_count: 20,
deploy_count: 30,
max_deploy_count: 20,
payment_amount: default_gas_payment(),
block_gas_limit: 10,
proposed_count: 25,
remaining_pending_count: 20,
..Default::default()
});
}
#[test]
fn should_respect_block_gas_limit_for_transfers() {
test_proposer_with(TestArgs {
transfer_count: 15,
payment_amount: default_gas_payment(),
block_gas_limit: 1,
max_transfer_count: 15,
proposed_count: 15,
remaining_pending_count: 0,
..Default::default()
});
}
#[test]
fn should_respect_block_gas_limit_for_deploys() {
test_proposer_with(TestArgs {
deploy_count: 15,
payment_amount: default_gas_payment(),
block_gas_limit: 5,
max_deploy_count: 15,
proposed_count: 5,
remaining_pending_count: 10,
..Default::default()
});
}
#[test]
fn should_propose_deploy_if_block_size_limit_met() {
test_proposer_with(TestArgs {
transfer_count: 1,
deploy_count: 1,
payment_amount: default_gas_payment(),
block_gas_limit: 10,
max_transfer_count: 2,
max_deploy_count: 2,
proposed_count: 2,
remaining_pending_count: 0,
max_block_size: Some(2 * DEPLOY_APPROX_MIN_SIZE),
});
}
#[test]
fn should_not_propose_deploy_if_block_size_limit_within_threshold() {
test_proposer_with(TestArgs {
transfer_count: 2,
deploy_count: 2,
payment_amount: default_gas_payment(),
block_gas_limit: 10,
max_transfer_count: 3,
max_deploy_count: 3,
proposed_count: 2,
remaining_pending_count: 2,
max_block_size: Some(2 * DEPLOY_APPROX_MIN_SIZE),
});
}
#[test]
fn should_not_propose_deploy_if_block_size_limit_passed() {
test_proposer_with(TestArgs {
deploy_count: 0,
transfer_count: 1,
payment_amount: default_gas_payment(),
block_gas_limit: 10,
max_transfer_count: 5,
proposed_count: 0,
remaining_pending_count: 1,
max_block_size: Some(100usize),
..Default::default()
});
}
#[derive(Default)]
struct TestArgs {
deploy_count: u32,
max_deploy_count: u32,
transfer_count: u32,
max_transfer_count: u32,
payment_amount: Gas,
block_gas_limit: u64,
remaining_pending_count: usize,
proposed_count: usize,
max_block_size: Option<usize>,
}
fn test_proposer_with(
TestArgs {
deploy_count,
max_deploy_count,
transfer_count,
max_transfer_count,
payment_amount,
block_gas_limit,
remaining_pending_count,
proposed_count,
max_block_size,
}: TestArgs,
) -> BlockProposerReady {
let creation_time = Timestamp::from(100);
let test_time = Timestamp::from(120);
let ttl = TimeDiff::from(Duration::from_millis(100));
let past_deploys = HashSet::new();
let mut rng = crate::new_rng();
let mut proposer = create_test_proposer();
let mut config = proposer.deploy_config;
config.block_max_deploy_count = max_deploy_count;
config.block_max_transfer_count = max_transfer_count;
config.block_gas_limit = block_gas_limit;
if let Some(max_block_size) = max_block_size {
config.max_block_size = max_block_size as u32;
}
for _ in 0..deploy_count {
let deploy = generate_deploy(&mut rng, creation_time, ttl, vec![], payment_amount);
println!("generated deploy with size {}", deploy.serialized_length());
proposer.add_deploy_or_transfer(creation_time, *deploy.id(), deploy.deploy_type().unwrap());
}
for _ in 0..transfer_count {
let transfer = generate_transfer(&mut rng, creation_time, ttl, vec![], payment_amount);
println!(
"generated transfer with size {}",
transfer.serialized_length()
);
proposer.add_deploy_or_transfer(
creation_time,
*transfer.id(),
transfer.deploy_type().unwrap(),
);
}
let block = proposer.propose_proto_block(config, test_time, past_deploys, true);
let all_deploys = BlockLike::deploys(&block);
proposer.finalized_deploys(all_deploys.iter().map(|hash| **hash));
assert_eq!(
all_deploys.len(),
proposed_count,
"should have a proposed_count of {}, but got {}",
proposed_count,
all_deploys.len()
);
assert_eq!(
proposer.sets.pending.len(),
remaining_pending_count,
"should have a remaining_pending_count of {}, but got {}",
remaining_pending_count,
proposer.sets.pending.len()
);
proposer
}
#[test]
fn should_return_deploy_dependencies() {
let creation_time = Timestamp::from(100);
let ttl = TimeDiff::from(Duration::from_millis(100));
let block_time = Timestamp::from(120);
let mut rng = crate::new_rng();
let deploy1 = generate_deploy(&mut rng, creation_time, ttl, vec![], default_gas_payment());
let deploy2 = generate_deploy(
&mut rng,
creation_time,
ttl,
vec![*deploy1.id()],
default_gas_payment(),
);
let no_deploys = HashSet::new();
let mut proposer = create_test_proposer();
proposer.add_deploy_or_transfer(creation_time, *deploy2.id(), deploy2.deploy_type().unwrap());
assert!(proposer
.propose_proto_block(
DeployConfig::default(),
block_time,
no_deploys.clone(),
true
)
.deploys()
.is_empty());
proposer.add_deploy_or_transfer(creation_time, *deploy1.id(), deploy1.deploy_type().unwrap());
let block = proposer.propose_proto_block(
DeployConfig::default(),
block_time,
no_deploys.clone(),
true,
);
let deploys = block
.deploys()
.iter()
.map(|hash| **hash)
.collect::<Vec<_>>();
assert_eq!(deploys.len(), 1);
assert!(deploys.contains(deploy1.id()));
proposer.finalized_deploys(deploys.iter().copied());
let block = proposer.propose_proto_block(DeployConfig::default(), block_time, no_deploys, true);
let deploys2 = block.wasm_deploys();
assert_eq!(deploys2.len(), 1);
assert!(deploys2.contains(deploy2.id()));
}