Skip to content

Commit

Permalink
Merge pull request #30 from alsrdn/validator-credit-tests
Browse files Browse the repository at this point in the history
tests: add tests for validator credit
  • Loading branch information
EdHastingsCasperAssociation authored May 15, 2024
2 parents 6bc7811 + aeb359e commit 7919862
Show file tree
Hide file tree
Showing 3 changed files with 269 additions and 11 deletions.
38 changes: 32 additions & 6 deletions execution_engine_testing/test_support/src/wasm_test_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ use casper_storage::{
balance::BalanceHandling, AuctionMethod, BalanceIdentifier, BalanceRequest, BalanceResult,
BiddingRequest, BiddingResult, BidsRequest, BlockRewardsRequest, BlockRewardsResult,
BlockStore, DataAccessLayer, EraValidatorsRequest, EraValidatorsResult, FeeRequest,
FeeResult, FlushRequest, FlushResult, GenesisRequest, GenesisResult, ProofHandling,
ProtocolUpgradeRequest, ProtocolUpgradeResult, PruneRequest, PruneResult, QueryRequest,
QueryResult, RoundSeigniorageRateRequest, RoundSeigniorageRateResult, StepRequest,
StepResult, SystemEntityRegistryPayload, SystemEntityRegistryRequest,
SystemEntityRegistryResult, SystemEntityRegistrySelector, TotalSupplyRequest,
TotalSupplyResult, TransferRequest, TrieRequest,
FeeResult, FlushRequest, FlushResult, GenesisRequest, GenesisResult, HandleFeeMode,
HandleFeeRequest, HandleFeeResult, ProofHandling, ProtocolUpgradeRequest,
ProtocolUpgradeResult, PruneRequest, PruneResult, QueryRequest, QueryResult,
RoundSeigniorageRateRequest, RoundSeigniorageRateResult, StepRequest, StepResult,
SystemEntityRegistryPayload, SystemEntityRegistryRequest, SystemEntityRegistryResult,
SystemEntityRegistrySelector, TotalSupplyRequest, TotalSupplyResult, TransferRequest,
TrieRequest,
},
global_state::{
state::{
Expand Down Expand Up @@ -1032,6 +1033,31 @@ where
distribute_block_rewards_result
}

/// Finalizes payment for a transaction
pub fn handle_fee(
&mut self,
pre_state_hash: Option<Digest>,
protocol_version: ProtocolVersion,
transaction_hash: TransactionHash,
handle_fee_mode: HandleFeeMode,
) -> HandleFeeResult {
let pre_state_hash = pre_state_hash.or(self.post_state_hash).unwrap();
let native_runtime_config = self.native_runtime_config();
let handle_fee_request = HandleFeeRequest::new(
native_runtime_config,
pre_state_hash,
protocol_version,
transaction_hash,
handle_fee_mode,
);
let handle_fee_result = self.data_access_layer.handle_fee(handle_fee_request);
if let HandleFeeResult::Success { effects, .. } = &handle_fee_result {
self.commit_transforms(pre_state_hash, effects.clone());
}

handle_fee_result
}

/// Expects a successful run
#[track_caller]
pub fn expect_success(&mut self) -> &mut Self {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ use tempfile::TempDir;

use casper_engine_test_support::{
utils, ChainspecConfig, ExecuteRequestBuilder, LmdbWasmTestBuilder, StepRequestBuilder,
DEFAULT_ACCOUNTS, DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE,
DEFAULT_ACCOUNTS, DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE, DEFAULT_AUCTION_DELAY,
DEFAULT_CHAINSPEC_REGISTRY, DEFAULT_EXEC_CONFIG, DEFAULT_GENESIS_CONFIG_HASH,
DEFAULT_GENESIS_TIMESTAMP_MILLIS, DEFAULT_LOCKED_FUNDS_PERIOD_MILLIS, DEFAULT_PROTOCOL_VERSION,
DEFAULT_UNBONDING_DELAY, LOCAL_GENESIS_REQUEST, MINIMUM_ACCOUNT_CREATION_BALANCE, SYSTEM_ADDR,
TIMESTAMP_MILLIS_INCREMENT,
DEFAULT_ROUND_SEIGNIORAGE_RATE, DEFAULT_UNBONDING_DELAY, LOCAL_GENESIS_REQUEST,
MINIMUM_ACCOUNT_CREATION_BALANCE, SYSTEM_ADDR, TIMESTAMP_MILLIS_INCREMENT,
};
use casper_execution_engine::{
engine_state::{self, engine_config::DEFAULT_MINIMUM_DELEGATION_AMOUNT, Error},
execution::ExecError,
};
use casper_storage::data_access_layer::GenesisRequest;
use casper_storage::data_access_layer::{GenesisRequest, HandleFeeMode};

use casper_types::{
self,
Expand All @@ -38,7 +38,7 @@ use casper_types::{
},
},
EntityAddr, EraId, GenesisAccount, GenesisConfigBuilder, GenesisValidator, Key, Motes,
ProtocolVersion, PublicKey, SecretKey, U256, U512,
ProtocolVersion, PublicKey, SecretKey, TransactionHash, U256, U512,
};

const ARG_TARGET: &str = "target";
Expand Down Expand Up @@ -5003,3 +5003,132 @@ fn should_handle_excessively_long_bridge_record_chains() {
)
);
}

#[ignore]
#[test]
fn credits_are_considered_when_determining_validators() {
// In this test we have 2 genesis nodes that are validators: Node 1 and Node 2; 1 has less stake
// than 2. We have only 2 validator slots so later we'll bid in another node with a stake
// slightly higher than the one of node 1.
// Under normal circumstances, since node 3 put in a higher bid, it should win the slot and kick
// out node 1. But since we add some credits for node 1 (because it was a validator and
// proposed blocks) it should maintain its slot.
let accounts = {
let mut tmp: Vec<GenesisAccount> = DEFAULT_ACCOUNTS.clone();
let account_1 = GenesisAccount::account(
ACCOUNT_1_PK.clone(),
Motes::new(ACCOUNT_1_BALANCE),
Some(GenesisValidator::new(
Motes::new(ACCOUNT_1_BOND),
DelegationRate::zero(),
)),
);
let account_2 = GenesisAccount::account(
ACCOUNT_2_PK.clone(),
Motes::new(ACCOUNT_2_BALANCE),
Some(GenesisValidator::new(
Motes::new(ACCOUNT_2_BOND),
DelegationRate::zero(),
)),
);
let account_3 = GenesisAccount::account(
BID_ACCOUNT_1_PK.clone(),
Motes::new(BID_ACCOUNT_1_BALANCE),
None,
);
tmp.push(account_1);
tmp.push(account_2);
tmp.push(account_3);
tmp
};

let mut builder = LmdbWasmTestBuilder::default();
let config = GenesisConfigBuilder::default()
.with_accounts(accounts)
.with_validator_slots(2) // set up only 2 validators
.with_auction_delay(DEFAULT_AUCTION_DELAY)
.with_locked_funds_period_millis(DEFAULT_LOCKED_FUNDS_PERIOD_MILLIS)
.with_round_seigniorage_rate(DEFAULT_ROUND_SEIGNIORAGE_RATE)
.with_unbonding_delay(DEFAULT_UNBONDING_DELAY)
.with_genesis_timestamp_millis(DEFAULT_GENESIS_TIMESTAMP_MILLIS)
.build();
let run_genesis_request = GenesisRequest::new(
DEFAULT_GENESIS_CONFIG_HASH,
DEFAULT_PROTOCOL_VERSION,
config,
DEFAULT_CHAINSPEC_REGISTRY.clone(),
);
builder.run_genesis(run_genesis_request);

let genesis_validator_weights = builder
.get_validator_weights(INITIAL_ERA_ID)
.expect("should have genesis validators for initial era");
let auction_delay = builder.get_auction_delay();

// new_era is the first era in the future where new era validator weights will be calculated
let new_era = INITIAL_ERA_ID + auction_delay + 1;
assert!(builder.get_validator_weights(new_era).is_none());
assert_eq!(
builder.get_validator_weights(new_era - 1).unwrap(),
builder.get_validator_weights(INITIAL_ERA_ID).unwrap()
);
// in the genesis era both node 1 and 2 are validators.
assert_eq!(
genesis_validator_weights
.keys()
.cloned()
.collect::<BTreeSet<_>>(),
BTreeSet::from_iter(vec![ACCOUNT_1_PK.clone(), ACCOUNT_2_PK.clone()])
);

// bid in the 3rd node with an amount just a bit more than node 1.
let exec_request_1 = ExecuteRequestBuilder::standard(
*BID_ACCOUNT_1_ADDR,
CONTRACT_ADD_BID,
runtime_args! {
ARG_PUBLIC_KEY => BID_ACCOUNT_1_PK.clone(),
ARG_AMOUNT => U512::from(ACCOUNT_1_BOND + 1),
ARG_DELEGATION_RATE => ADD_BID_DELEGATION_RATE_1,
},
)
.build();

builder.exec(exec_request_1).expect_success().commit();

// Add a credit for node 1 artificially (assume it has proposed a block with a transaction and
// received credit).
let add_credit = HandleFeeMode::credit(
Box::new(ACCOUNT_1_PK.clone()),
U512::from(2001),
INITIAL_ERA_ID,
);
builder.handle_fee(
None,
DEFAULT_PROTOCOL_VERSION,
TransactionHash::from_raw([1; 32]),
add_credit,
);

// run auction and compute validators for new era
builder.run_auction(
DEFAULT_GENESIS_TIMESTAMP_MILLIS + DEFAULT_LOCKED_FUNDS_PERIOD_MILLIS,
Vec::new(),
);

let new_validator_weights: ValidatorWeights = builder
.get_validator_weights(new_era)
.expect("should have first era validator weights");

// We have only 2 slots. Node 2 should be in the set because it's the highest bidder. Node 1
// should keep its validator slot even though it's bid is now lower than node 3. This should
// have happened because there was a credit for node 1 added.
assert_eq!(
new_validator_weights.get(&ACCOUNT_2_PK),
Some(&U512::from(ACCOUNT_2_BOND))
);
assert!(!new_validator_weights.contains_key(&BID_ACCOUNT_1_PK));
assert_eq!(
new_validator_weights.get(&ACCOUNT_1_PK),
Some(&U512::from(ACCOUNT_1_BOND))
);
}
103 changes: 103 additions & 0 deletions node/src/reactor/main_reactor/tests/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,31 @@ fn get_balance(
))
}

fn get_bids(fixture: &mut TestFixture, block_height: Option<u64>) -> Option<Vec<BidKind>> {
let (_node_id, runner) = fixture.network.nodes().iter().next().unwrap();
let block_height = block_height.unwrap_or(
runner
.main_reactor()
.storage()
.highest_complete_block_height()
.expect("missing highest completed block"),
);
let block_header = runner
.main_reactor()
.storage()
.read_block_header_by_height(block_height, true)
.expect("failure to read block header")
.unwrap();
let state_hash = *block_header.state_root_hash();

runner
.main_reactor()
.contract_runtime()
.data_access_layer()
.bids(BidsRequest::new(state_hash))
.into_option()
}

fn get_payment_purse_balance(
fixture: &mut TestFixture,
block_height: Option<u64>,
Expand Down Expand Up @@ -2590,3 +2615,81 @@ async fn sufficient_balance_is_available_after_amortization() {
transfer_cost + half_transfer_cost, // two `min_transfer_amount` should have gone to Bob.
);
}

#[tokio::test]
async fn validator_credit_is_written_and_cleared_after_auction() {
let config = SingleTransactionTestCase::default_test_config()
.with_pricing_handling(PricingHandling::Fixed)
.with_refund_handling(RefundHandling::NoRefund)
.with_fee_handling(FeeHandling::NoFee)
.with_gas_hold_balance_handling(HoldBalanceHandling::Accrued);

let mut test = SingleTransactionTestCase::new(
ALICE_SECRET_KEY.clone(),
BOB_SECRET_KEY.clone(),
CHARLIE_SECRET_KEY.clone(),
Some(config),
)
.await;

let transfer_cost: U512 =
U512::from(test.chainspec().system_costs_config.mint_costs().transfer) * MIN_GAS_PRICE;
let min_transfer_amount = U512::from(
test.chainspec()
.transaction_config
.native_transfer_minimum_motes,
);
let half_transfer_cost =
(Ratio::new(U512::from(1), U512::from(2)) * transfer_cost).to_integer();

// Fund Charlie with some token.
let transfer_amount = min_transfer_amount * 2 + transfer_cost + half_transfer_cost;
let txn = transfer_txn(
BOB_SECRET_KEY.clone(),
&CHARLIE_PUBLIC_KEY,
PricingMode::Fixed {
gas_price_tolerance: MIN_GAS_PRICE,
},
transfer_amount,
);

test.fixture
.run_until_consensus_in_era(ERA_ONE, ONE_MIN)
.await;
let (_txn_hash, block_height, exec_result) = test.send_transaction(txn).await;
assert!(exec_result_is_success(&exec_result));

let charlie_balance = test.get_balances(Some(block_height)).2.unwrap();
assert_eq!(
charlie_balance.available.clone(),
charlie_balance.total.clone()
);
assert_eq!(charlie_balance.available.clone(), transfer_amount);

let bids =
get_bids(&mut test.fixture, Some(block_height)).expect("Expected to get some bid records.");

let _ = bids
.into_iter()
.find(|bid_kind| match bid_kind {
BidKind::Credit(credit) => {
credit.amount() == transfer_cost
&& credit.validator_public_key() == &*ALICE_PUBLIC_KEY // Alice is the proposer.
}
_ => false,
})
.expect("Expected to find the credit for the consumed transfer cost in the bid records.");

test.fixture
.run_until_consensus_in_era(
ERA_ONE.saturating_add(test.chainspec().core_config.auction_delay),
ONE_MIN,
)
.await;

// Check that the credits were cleared after the auction.
let bids = get_bids(&mut test.fixture, None).expect("Expected to get some bid records.");
assert!(!bids
.into_iter()
.any(|bid| matches!(bid, BidKind::Credit(_))));
}

0 comments on commit 7919862

Please sign in to comment.