diff --git a/Cargo.lock b/Cargo.lock index c750742d81..073e07b812 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2338,6 +2338,14 @@ dependencies = [ "casper-types", ] +[[package]] +name = "gh-4771-regression" +version = "0.1.0" +dependencies = [ + "casper-contract", + "casper-types", +] + [[package]] name = "gimli" version = "0.26.2" diff --git a/Makefile b/Makefile index 5d18f51730..8b1db3c0bd 100644 --- a/Makefile +++ b/Makefile @@ -152,7 +152,7 @@ lint-smart-contracts: .PHONY: audit-rs audit-rs: - $(CARGO) audit --ignore RUSTSEC-2024-0344 + $(CARGO) audit --ignore RUSTSEC-2024-0332 --ignore RUSTSEC-2024-0344 .PHONY: audit-as audit-as: diff --git a/binary_port/src/node_status.rs b/binary_port/src/node_status.rs index 9a67ce2cae..3666412df7 100644 --- a/binary_port/src/node_status.rs +++ b/binary_port/src/node_status.rs @@ -1,7 +1,7 @@ use casper_types::{ bytesrepr::{self, FromBytes, ToBytes}, - AvailableBlockRange, BlockHash, BlockSynchronizerStatus, Digest, NextUpgrade, Peers, PublicKey, - TimeDiff, Timestamp, + AvailableBlockRange, BlockHash, BlockSynchronizerStatus, Digest, NextUpgrade, Peers, + ProtocolVersion, PublicKey, TimeDiff, Timestamp, }; #[cfg(test)] @@ -15,6 +15,8 @@ use crate::{minimal_block_info::MinimalBlockInfo, type_wrappers::ReactorStateNam /// Status information about the node. #[derive(Debug, PartialEq, Serialize)] pub struct NodeStatus { + /// The current protocol version. + pub protocol_version: ProtocolVersion, /// The node ID and network address of each connected peer. pub peers: Peers, /// The compiled node version. @@ -49,6 +51,7 @@ impl NodeStatus { #[cfg(test)] pub(crate) fn random(rng: &mut TestRng) -> Self { Self { + protocol_version: ProtocolVersion::from_parts(rng.gen(), rng.gen(), rng.gen()), peers: Peers::random(rng), build_version: rng.random_string(5..10), chainspec_name: rng.random_string(5..10), @@ -71,7 +74,8 @@ impl NodeStatus { impl FromBytes for NodeStatus { fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (peers, remainder) = FromBytes::from_bytes(bytes)?; + let (protocol_version, remainder) = ProtocolVersion::from_bytes(bytes)?; + let (peers, remainder) = Peers::from_bytes(remainder)?; let (build_version, remainder) = String::from_bytes(remainder)?; let (chainspec_name, remainder) = String::from_bytes(remainder)?; let (starting_state_root_hash, remainder) = Digest::from_bytes(remainder)?; @@ -87,6 +91,7 @@ impl FromBytes for NodeStatus { let (latest_switch_block_hash, remainder) = Option::::from_bytes(remainder)?; Ok(( NodeStatus { + protocol_version, peers, build_version, chainspec_name, @@ -116,6 +121,7 @@ impl ToBytes for NodeStatus { fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { let NodeStatus { + protocol_version, peers, build_version, chainspec_name, @@ -131,6 +137,7 @@ impl ToBytes for NodeStatus { block_sync, latest_switch_block_hash, } = self; + protocol_version.write_bytes(writer)?; peers.write_bytes(writer)?; build_version.write_bytes(writer)?; chainspec_name.write_bytes(writer)?; @@ -148,7 +155,8 @@ impl ToBytes for NodeStatus { } fn serialized_length(&self) -> usize { - self.peers.serialized_length() + self.protocol_version.serialized_length() + + self.peers.serialized_length() + self.build_version.serialized_length() + self.chainspec_name.serialized_length() + self.starting_state_root_hash.serialized_length() diff --git a/binary_port/src/type_wrappers.rs b/binary_port/src/type_wrappers.rs index 10e687f573..26fddbd99a 100644 --- a/binary_port/src/type_wrappers.rs +++ b/binary_port/src/type_wrappers.rs @@ -6,6 +6,7 @@ use datasize::DataSize; use casper_types::{ bytesrepr::{self, Bytes, FromBytes, ToBytes}, + system::auction::DelegationRate, EraId, ExecutionInfo, Key, PublicKey, TimeDiff, Timestamp, Transaction, ValidatorChange, U512, }; use serde::Serialize; @@ -179,12 +180,17 @@ impl GetTrieFullResult { pub struct RewardResponse { amount: U512, era_id: EraId, + delegation_rate: DelegationRate, } impl RewardResponse { /// Constructs new reward response. - pub fn new(amount: U512, era_id: EraId) -> Self { - Self { amount, era_id } + pub fn new(amount: U512, era_id: EraId, delegation_rate: DelegationRate) -> Self { + Self { + amount, + era_id, + delegation_rate, + } } /// Returns the amount of the reward. @@ -196,6 +202,11 @@ impl RewardResponse { pub fn era_id(&self) -> EraId { self.era_id } + + /// Returns the delegation rate of the validator. + pub fn delegation_rate(&self) -> DelegationRate { + self.delegation_rate + } } impl ToBytes for RewardResponse { @@ -206,12 +217,15 @@ impl ToBytes for RewardResponse { } fn serialized_length(&self) -> usize { - self.amount.serialized_length() + self.era_id.serialized_length() + self.amount.serialized_length() + + self.era_id.serialized_length() + + self.delegation_rate.serialized_length() } fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { self.amount.write_bytes(writer)?; - self.era_id.write_bytes(writer) + self.era_id.write_bytes(writer)?; + self.delegation_rate.write_bytes(writer) } } @@ -219,7 +233,11 @@ impl FromBytes for RewardResponse { fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { let (amount, remainder) = FromBytes::from_bytes(bytes)?; let (era_id, remainder) = FromBytes::from_bytes(remainder)?; - Ok((RewardResponse::new(amount, era_id), remainder)) + let (delegation_rate, remainder) = FromBytes::from_bytes(remainder)?; + Ok(( + RewardResponse::new(amount, era_id, delegation_rate), + remainder, + )) } } @@ -436,6 +454,7 @@ mod tests { bytesrepr::test_serialization_roundtrip(&RewardResponse::new( rng.gen(), EraId::random(rng), + rng.gen(), )); } diff --git a/ci/nightly-test.sh b/ci/nightly-test.sh index 92861ac3ed..dfe3b3f1f4 100755 --- a/ci/nightly-test.sh +++ b/ci/nightly-test.sh @@ -130,6 +130,7 @@ run_test_and_count 'start_run_teardown "swap_validator_set.sh"' run_test_and_count 'start_run_teardown "sync_upgrade_test.sh node=6 era=5 timeout=500"' run_test_and_count 'start_run_teardown "validators_disconnect.sh"' run_test_and_count 'start_run_teardown "event_stream.sh"' +run_test_and_count 'start_run_teardown "regression_4771.sh"' # Without start_run_teardown - these ones perform their own assets setup, network start and teardown run_test_and_count 'source "$SCENARIOS_DIR/upgrade_after_emergency_upgrade_test_pre_1.5.sh"' run_test_and_count 'source "$SCENARIOS_DIR/regression_3976.sh"' diff --git a/execution_engine_testing/tests/src/test/system_contracts/auction/bids.rs b/execution_engine_testing/tests/src/test/system_contracts/auction/bids.rs index a8c25a30ad..d9d1fb536a 100644 --- a/execution_engine_testing/tests/src/test/system_contracts/auction/bids.rs +++ b/execution_engine_testing/tests/src/test/system_contracts/auction/bids.rs @@ -5536,9 +5536,10 @@ fn credits_are_considered_when_determining_validators() { // Add a credit for node 1 artificially (assume it has proposed a block with a transaction and // received credit). + let credit_amount = U512::from(2001); let add_credit = HandleFeeMode::credit( Box::new(ACCOUNT_1_PK.clone()), - U512::from(2001), + credit_amount, INITIAL_ERA_ID, ); builder.handle_fee( @@ -5566,8 +5567,9 @@ fn credits_are_considered_when_determining_validators() { Some(&U512::from(ACCOUNT_2_BOND)) ); assert!(!new_validator_weights.contains_key(&BID_ACCOUNT_1_PK)); + let expected_amount = credit_amount.saturating_add(U512::from(ACCOUNT_1_BOND)); assert_eq!( new_validator_weights.get(&ACCOUNT_1_PK), - Some(&U512::from(ACCOUNT_1_BOND)) + Some(&expected_amount) ); } diff --git a/node/src/components/binary_port.rs b/node/src/components/binary_port.rs index 1ddfef39ee..b5860a5d89 100644 --- a/node/src/components/binary_port.rs +++ b/node/src/components/binary_port.rs @@ -849,6 +849,7 @@ where }; let status = NodeStatus { + protocol_version, peers: Peers::from(peers), build_version: crate::VERSION_STRING.clone(), chainspec_name: network_name.into(), @@ -939,22 +940,31 @@ where let Some(validator_rewards) = block_rewards.get(&validator) else { return BinaryResponse::new_empty(protocol_version); }; - match auction::reward( + + let seigniorage_recipient = snapshot + .get(&header.era_id()) + .and_then(|era| era.get(&validator)); + let reward = auction::reward( &validator, delegator.as_deref(), header.era_id(), validator_rewards, &snapshot, - ) { - Ok(Some(reward)) => { - let response = RewardResponse::new(reward, header.era_id()); + ); + match (reward, seigniorage_recipient) { + (Ok(Some(reward)), Some(seigniorage_recipient)) => { + let response = RewardResponse::new( + reward, + header.era_id(), + *seigniorage_recipient.delegation_rate(), + ); BinaryResponse::from_value(response, protocol_version) } - Ok(None) => BinaryResponse::new_empty(protocol_version), - Err(error) => { + (Err(error), _) => { warn!(%error, "failed when calculating rewards"); BinaryResponse::new_error(ErrorCode::InternalError, protocol_version) } + _ => BinaryResponse::new_empty(protocol_version), } } } diff --git a/node/src/components/contract_runtime.rs b/node/src/components/contract_runtime.rs index ddeafb3a26..5a91284ad5 100644 --- a/node/src/components/contract_runtime.rs +++ b/node/src/components/contract_runtime.rs @@ -29,9 +29,9 @@ use tracing::{debug, error, info, trace}; use casper_execution_engine::engine_state::{EngineConfigBuilder, ExecutionEngineV1}; use casper_storage::{ data_access_layer::{ - AddressableEntityRequest, BlockStore, DataAccessLayer, EntryPointsRequest, - ExecutionResultsChecksumRequest, FlushRequest, FlushResult, GenesisRequest, GenesisResult, - TrieRequest, + AddressableEntityRequest, AddressableEntityResult, BlockStore, DataAccessLayer, + EntryPointsRequest, ExecutionResultsChecksumRequest, FlushRequest, FlushResult, + GenesisRequest, GenesisResult, TrieRequest, }, global_state::{ state::{lmdb::LmdbGlobalState, CommitProvider, StateProvider}, @@ -42,7 +42,8 @@ use casper_storage::{ tracking_copy::TrackingCopyError, }; use casper_types::{ - ActivationPoint, Chainspec, ChainspecRawBytes, ChainspecRegistry, EraId, PublicKey, + account::AccountHash, ActivationPoint, Chainspec, ChainspecRawBytes, ChainspecRegistry, + EntityAddr, EraId, Key, PublicKey, }; use crate::{ @@ -386,7 +387,7 @@ impl ContractRuntime { } ContractRuntimeRequest::GetAddressableEntity { state_root_hash, - key, + entity_addr, responder, } => { trace!(?state_root_hash, "get addressable entity"); @@ -394,8 +395,29 @@ impl ContractRuntime { let data_access_layer = Arc::clone(&self.data_access_layer); async move { let start = Instant::now(); - let request = AddressableEntityRequest::new(state_root_hash, key); + let entity_key = match entity_addr { + EntityAddr::SmartContract(_) | EntityAddr::System(_) => Key::AddressableEntity(entity_addr), + EntityAddr::Account(account) => Key::Account(AccountHash::new(account)), + }; + let request = AddressableEntityRequest::new(state_root_hash, entity_key); let result = data_access_layer.addressable_entity(request); + let result = match &result { + AddressableEntityResult::ValueNotFound(msg) => { + if entity_addr.is_contract() { + trace!(%msg, "can not read addressable entity by Key::AddressableEntity or Key::Account, will try by Key::Hash"); + let entity_key = Key::Hash(entity_addr.value()); + let request = AddressableEntityRequest::new(state_root_hash, entity_key); + data_access_layer.addressable_entity(request) + } + else { + result + } + }, + AddressableEntityResult::RootNotFound | + AddressableEntityResult::Success { .. } | + AddressableEntityResult::Failure(_) => result, + }; + metrics .addressable_entity .observe(start.elapsed().as_secs_f64()); diff --git a/node/src/components/transaction_acceptor.rs b/node/src/components/transaction_acceptor.rs index 44641ac41d..2991f4df89 100644 --- a/node/src/components/transaction_acceptor.rs +++ b/node/src/components/transaction_acceptor.rs @@ -13,12 +13,12 @@ use tracing::{debug, error, trace}; use casper_execution_engine::engine_state::MAX_PAYMENT; use casper_storage::data_access_layer::{balance::BalanceHandling, BalanceRequest, ProofHandling}; use casper_types::{ - account::AccountHash, addressable_entity::AddressableEntity, contracts::ContractHash, - system::auction::ARG_AMOUNT, AddressableEntityHash, AddressableEntityIdentifier, BlockHeader, - Chainspec, EntityAddr, EntityVersion, EntityVersionKey, EntryPoint, EntryPointAddr, - ExecutableDeployItem, ExecutableDeployItemIdentifier, InitiatorAddr, Key, Package, PackageAddr, - PackageHash, PackageIdentifier, Transaction, TransactionEntryPoint, - TransactionInvocationTarget, TransactionTarget, DEFAULT_ENTRY_POINT_NAME, U512, + account::AccountHash, addressable_entity::AddressableEntity, system::auction::ARG_AMOUNT, + AddressableEntityHash, AddressableEntityIdentifier, BlockHeader, Chainspec, EntityAddr, + EntityVersion, EntityVersionKey, EntryPoint, EntryPointAddr, ExecutableDeployItem, + ExecutableDeployItemIdentifier, InitiatorAddr, Key, Package, PackageAddr, PackageHash, + PackageIdentifier, Transaction, TransactionEntryPoint, TransactionInvocationTarget, + TransactionTarget, DEFAULT_ENTRY_POINT_NAME, U512, }; use crate::{ @@ -177,12 +177,13 @@ impl TransactionAcceptor { }; if event_metadata.source.is_client() { - let account_key = match event_metadata.transaction.initiator_addr() { - InitiatorAddr::PublicKey(public_key) => Key::from(public_key.to_account_hash()), - InitiatorAddr::AccountHash(account_hash) => Key::from(account_hash), + let account_hash = match event_metadata.transaction.initiator_addr() { + InitiatorAddr::PublicKey(public_key) => public_key.to_account_hash(), + InitiatorAddr::AccountHash(account_hash) => account_hash, }; + let entity_addr = EntityAddr::Account(account_hash.value()); effect_builder - .get_addressable_entity(*block_header.state_root_hash(), account_key) + .get_addressable_entity(*block_header.state_root_hash(), entity_addr) .event(move |result| Event::GetAddressableEntityResult { event_metadata, maybe_entity: result.into_option(), @@ -310,9 +311,9 @@ impl TransactionAcceptor { ExecutableDeployItemIdentifier::AddressableEntity( AddressableEntityIdentifier::Hash(contract_hash), ) => { - let query_key = Key::from(ContractHash::new(contract_hash.value())); + let entity_addr = EntityAddr::SmartContract(contract_hash.value()); effect_builder - .get_addressable_entity(*block_header.state_root_hash(), query_key) + .get_addressable_entity(*block_header.state_root_hash(), entity_addr) .event(move |result| Event::GetContractResult { event_metadata, block_header, @@ -323,16 +324,13 @@ impl TransactionAcceptor { } ExecutableDeployItemIdentifier::AddressableEntity( AddressableEntityIdentifier::Addr(entity_addr), - ) => { - let query_key = Key::AddressableEntity(entity_addr); - effect_builder - .get_addressable_entity(*block_header.state_root_hash(), query_key) - .event(move |result| Event::GetAddressableEntityResult { - event_metadata, - block_header, - maybe_entity: result.into_option(), - }) - } + ) => effect_builder + .get_addressable_entity(*block_header.state_root_hash(), entity_addr) + .event(move |result| Event::GetAddressableEntityResult { + event_metadata, + block_header, + maybe_entity: result.into_option(), + }), ExecutableDeployItemIdentifier::Package( ref contract_package_identifier @ PackageIdentifier::Hash { package_hash, .. }, ) => { @@ -429,9 +427,9 @@ impl TransactionAcceptor { ExecutableDeployItemIdentifier::AddressableEntity( AddressableEntityIdentifier::Hash(entity_hash), ) => { - let key = Key::from(ContractHash::new(entity_hash.value())); + let entity_addr = EntityAddr::SmartContract(entity_hash.value()); effect_builder - .get_addressable_entity(*block_header.state_root_hash(), key) + .get_addressable_entity(*block_header.state_root_hash(), entity_addr) .event(move |result| Event::GetContractResult { event_metadata, block_header, @@ -442,16 +440,13 @@ impl TransactionAcceptor { } ExecutableDeployItemIdentifier::AddressableEntity( AddressableEntityIdentifier::Addr(entity_addr), - ) => { - let key = Key::AddressableEntity(entity_addr); - effect_builder - .get_addressable_entity(*block_header.state_root_hash(), key) - .event(move |result| Event::GetAddressableEntityResult { - event_metadata, - block_header, - maybe_entity: result.into_option(), - }) - } + ) => effect_builder + .get_addressable_entity(*block_header.state_root_hash(), entity_addr) + .event(move |result| Event::GetAddressableEntityResult { + event_metadata, + block_header, + maybe_entity: result.into_option(), + }), ExecutableDeployItemIdentifier::Package( ref package_identifier @ PackageIdentifier::Hash { package_hash, .. }, ) => { @@ -518,9 +513,8 @@ impl TransactionAcceptor { NextStep::GetContract(entity_addr) => { // Use `Key::Hash` variant so that we try to retrieve the entity as either an // AddressableEntity, or fall back to retrieving an un-migrated Contract. - let key = Key::Hash(entity_addr.value()); effect_builder - .get_addressable_entity(*block_header.state_root_hash(), key) + .get_addressable_entity(*block_header.state_root_hash(), entity_addr) .event(move |result| Event::GetContractResult { event_metadata, block_header, @@ -715,9 +709,9 @@ impl TransactionAcceptor { match package.lookup_entity_hash(entity_version_key) { Some(&contract_hash) => { - let key = Key::from(ContractHash::new(contract_hash.value())); + let entity_addr = EntityAddr::SmartContract(contract_hash.value()); effect_builder - .get_addressable_entity(*block_header.state_root_hash(), key) + .get_addressable_entity(*block_header.state_root_hash(), entity_addr) .event(move |result| Event::GetContractResult { event_metadata, block_header, diff --git a/node/src/components/transaction_acceptor/tests.rs b/node/src/components/transaction_acceptor/tests.rs index 74c21cda4f..0d2081f16a 100644 --- a/node/src/components/transaction_acceptor/tests.rs +++ b/node/src/components/transaction_acceptor/tests.rs @@ -841,7 +841,7 @@ impl reactor::Reactor for Reactor { } ContractRuntimeRequest::GetAddressableEntity { state_root_hash: _, - key, + entity_addr, responder, } => { let result = if matches!( @@ -852,12 +852,13 @@ impl reactor::Reactor for Reactor { TestScenario::FromPeerMissingAccount(_) ) { AddressableEntityResult::ValueNotFound("missing account".to_string()) - } else if let Key::Account(account_hash) = key { - let account = create_account(account_hash, self.test_scenario); + } else if let EntityAddr::Account(account_hash) = entity_addr { + let account = + create_account(AccountHash::new(account_hash), self.test_scenario); AddressableEntityResult::Success { entity: AddressableEntity::from(account), } - } else if let Key::Hash(..) = key { + } else if let EntityAddr::SmartContract(..) = entity_addr { match self.test_scenario { TestScenario::FromPeerCustomPaymentContract( ContractScenario::MissingContractAtHash, @@ -894,10 +895,12 @@ impl reactor::Reactor for Reactor { entity: AddressableEntity::from(contract), } } - _ => panic!("unexpected GetAddressableEntity: {:?}", key), + _ => panic!("unexpected GetAddressableEntity: {:?}", entity_addr), } } else { - panic!("should GetAddressableEntity using Key's Account or Hash variant"); + panic!( + "should GetAddressableEntity using Account or SmartContract variant" + ); }; responder.respond(result).ignore() } diff --git a/node/src/effect.rs b/node/src/effect.rs index 330c7d82a2..55e801c460 100644 --- a/node/src/effect.rs +++ b/node/src/effect.rs @@ -132,9 +132,9 @@ use casper_storage::{ use casper_types::{ execution::{Effects as ExecutionEffects, ExecutionResult}, Approval, AvailableBlockRange, Block, BlockHash, BlockHeader, BlockSignatures, - BlockSynchronizerStatus, BlockV2, ChainspecRawBytes, DeployHash, Digest, EraId, ExecutionInfo, - FinalitySignature, FinalitySignatureId, FinalitySignatureV2, Key, NextUpgrade, Package, - ProtocolUpgradeConfig, ProtocolVersion, PublicKey, TimeDiff, Timestamp, Transaction, + BlockSynchronizerStatus, BlockV2, ChainspecRawBytes, DeployHash, Digest, EntityAddr, EraId, + ExecutionInfo, FinalitySignature, FinalitySignatureId, FinalitySignatureV2, Key, NextUpgrade, + Package, ProtocolUpgradeConfig, ProtocolVersion, PublicKey, TimeDiff, Timestamp, Transaction, TransactionHash, TransactionHeader, TransactionId, Transfer, U512, }; @@ -2050,11 +2050,12 @@ impl EffectBuilder { .await } - /// Retrieves an `AddressableEntity` from under the given key in global state if present. + /// Retrieves an `AddressableEntity` from under the given entity address (or key, if the former + /// is not found) in global state. pub(crate) async fn get_addressable_entity( self, state_root_hash: Digest, - key: Key, + entity_addr: EntityAddr, ) -> AddressableEntityResult where REv: From, @@ -2062,7 +2063,7 @@ impl EffectBuilder { self.make_request( |responder| ContractRuntimeRequest::GetAddressableEntity { state_root_hash, - key, + entity_addr, responder, }, QueueKind::ContractRuntime, diff --git a/node/src/effect/requests.rs b/node/src/effect/requests.rs index f65f1ab407..c5f89403b2 100644 --- a/node/src/effect/requests.rs +++ b/node/src/effect/requests.rs @@ -33,9 +33,9 @@ use casper_storage::{ use casper_types::{ execution::ExecutionResult, Approval, AvailableBlockRange, Block, BlockHash, BlockHeader, BlockSignatures, BlockSynchronizerStatus, BlockV2, ChainspecRawBytes, DeployHash, Digest, - DisplayIter, EraId, ExecutionInfo, FinalitySignature, FinalitySignatureId, Key, NextUpgrade, - ProtocolUpgradeConfig, ProtocolVersion, PublicKey, TimeDiff, Timestamp, Transaction, - TransactionHash, TransactionHeader, TransactionId, Transfer, + DisplayIter, EntityAddr, EraId, ExecutionInfo, FinalitySignature, FinalitySignatureId, Key, + NextUpgrade, ProtocolUpgradeConfig, ProtocolVersion, PublicKey, TimeDiff, Timestamp, + Transaction, TransactionHash, TransactionHeader, TransactionId, Transfer, }; use super::{AutoClosingResponder, GossipTarget, Responder}; @@ -822,13 +822,13 @@ pub(crate) enum ContractRuntimeRequest { state_root_hash: Digest, responder: Responder, }, - /// Returns an `AddressableEntity` if found under the given key. If a legacy `Account` + /// Returns an `AddressableEntity` if found under the given entity_addr. If a legacy `Account` /// or contract exists under the given key, it will be migrated to an `AddressableEntity` /// and returned. However, global state is not altered and the migrated record does not /// actually exist. GetAddressableEntity { state_root_hash: Digest, - key: Key, + entity_addr: EntityAddr, responder: Responder, }, /// Returns a singular entry point based under the given state root hash and entry @@ -925,13 +925,13 @@ impl Display for ContractRuntimeRequest { ), ContractRuntimeRequest::GetAddressableEntity { state_root_hash, - key, + entity_addr, .. } => { write!( formatter, "get addressable_entity {} under {}", - key, state_root_hash + entity_addr, state_root_hash ) } ContractRuntimeRequest::GetTrie { request, .. } => { diff --git a/node/src/reactor/main_reactor/tests/binary_port.rs b/node/src/reactor/main_reactor/tests/binary_port.rs index f8ff03c466..82b15e10c1 100644 --- a/node/src/reactor/main_reactor/tests/binary_port.rs +++ b/node/src/reactor/main_reactor/tests/binary_port.rs @@ -50,6 +50,7 @@ const MESSAGE_SIZE: u32 = 1024 * 1024 * 10; struct TestData { rng: TestRng, + protocol_version: ProtocolVersion, chainspec_raw_bytes: ChainspecRawBytes, highest_block: Block, secret_signing_key: Arc, @@ -149,6 +150,7 @@ async fn setup() -> ( .bind_address() .expect("should be bound"); + let protocol_version = first_node.main_reactor().chainspec.protocol_version(); // We let the entire network run in the background, until our request completes. let finish_cranking = fixture.run_until_stopped(rng.create_child()); @@ -164,6 +166,7 @@ async fn setup() -> ( finish_cranking, TestData { rng, + protocol_version, chainspec_raw_bytes, highest_block, secret_signing_key, @@ -298,6 +301,7 @@ async fn binary_port_component_handles_all_requests() { finish_cranking, TestData { mut rng, + protocol_version, chainspec_raw_bytes: network_chainspec_raw_bytes, highest_block, secret_signing_key, @@ -325,7 +329,7 @@ async fn binary_port_component_handles_all_requests() { consensus_status(), chainspec_raw_bytes(network_chainspec_raw_bytes), latest_switch_block_header(), - node_status(), + node_status(protocol_version), get_block_header(highest_block.clone_header()), get_block_transfers(highest_block.clone_header()), get_era_summary(state_root_hash), @@ -365,9 +369,7 @@ async fn binary_port_component_handles_all_requests() { None, ), get_reward( - Some(EraIdentifier::Block(BlockIdentifier::Hash( - *highest_block.hash(), - ))), + Some(EraIdentifier::Block(BlockIdentifier::Height(1))), era_one_validator, None, ), @@ -656,7 +658,7 @@ fn latest_switch_block_header() -> TestCase { } } -fn node_status() -> TestCase { +fn node_status(expected_version: ProtocolVersion) -> TestCase { TestCase { name: "node_status", request: BinaryRequest::Get(GetRequest::Information { @@ -668,7 +670,8 @@ fn node_status() -> TestCase { response, Some(PayloadType::NodeStatus), |node_status| { - !node_status.peers.into_inner().is_empty() + node_status.protocol_version == expected_version + && !node_status.peers.into_inner().is_empty() && node_status.chainspec_name == "casper-example" && node_status.last_added_block_info.is_some() && node_status.our_public_signing_key.is_some() @@ -932,7 +935,8 @@ fn get_reward( }), asserter: Box::new(move |response| { assert_response::(response, Some(PayloadType::Reward), |reward| { - reward.amount() > U512::zero() + // test fixture sets delegation rate to 0 + reward.amount() > U512::zero() && reward.delegation_rate() == 0 }) }), } diff --git a/node/src/reactor/main_reactor/tests/transactions.rs b/node/src/reactor/main_reactor/tests/transactions.rs index 57a2711998..a625f85308 100644 --- a/node/src/reactor/main_reactor/tests/transactions.rs +++ b/node/src/reactor/main_reactor/tests/transactions.rs @@ -311,7 +311,7 @@ fn get_entity_by_account_hash( .data_access_layer() .addressable_entity(AddressableEntityRequest::new( state_root_hash, - Key::Account(account_hash), + Key::AddressableEntity(EntityAddr::Account(account_hash.value())), )) .into_option() .unwrap_or_else(|| { diff --git a/smart_contracts/contracts/test/gh-4771-regression/Cargo.toml b/smart_contracts/contracts/test/gh-4771-regression/Cargo.toml new file mode 100644 index 0000000000..1d2016660b --- /dev/null +++ b/smart_contracts/contracts/test/gh-4771-regression/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "gh-4771-regression" +version = "0.1.0" +authors = ["RafaƂ Chabowski "] +edition = "2021" + +[[bin]] +name = "gh_4771_regression" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +casper-contract = { path = "../../../contract" } +casper-types = { path = "../../../../types" } diff --git a/smart_contracts/contracts/test/gh-4771-regression/src/main.rs b/smart_contracts/contracts/test/gh-4771-regression/src/main.rs new file mode 100644 index 0000000000..f8af7bb361 --- /dev/null +++ b/smart_contracts/contracts/test/gh-4771-regression/src/main.rs @@ -0,0 +1,45 @@ +#![no_main] +#![no_std] + +extern crate alloc; + +use alloc::string::ToString; +use casper_contract::contract_api::{runtime, storage}; +use casper_types::{ + addressable_entity::Parameters, CLType, EntryPoint, EntryPointAccess, EntryPointPayment, + EntryPointType, EntryPoints, Key, +}; + +const METHOD_TEST_ENTRY_POINT: &str = "test_entry_point"; +const NEW_KEY_NAME: &str = "Hello"; +const NEW_KEY_VALUE: &str = "World"; +const CONTRACT_PACKAGE_KEY: &str = "contract_package"; +const CONTRACT_HASH_KEY: &str = "contract_hash"; + +#[no_mangle] +fn test_entry_point() { + let value = storage::new_uref(NEW_KEY_VALUE); + runtime::put_key(NEW_KEY_NAME, value.into()); +} + +#[no_mangle] +fn call() { + let mut entry_points = EntryPoints::new(); + entry_points.add_entry_point(EntryPoint::new( + METHOD_TEST_ENTRY_POINT, + Parameters::new(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Called, + EntryPointPayment::Caller, + )); + + let (contract_hash, _version) = storage::new_contract( + entry_points, + None, + Some(CONTRACT_PACKAGE_KEY.to_string()), + None, + None, + ); + runtime::put_key(CONTRACT_HASH_KEY, Key::contract_entity_key(contract_hash)); +} diff --git a/storage/src/system/auction.rs b/storage/src/system/auction.rs index b194f6aa23..65032328bf 100644 --- a/storage/src/system/auction.rs +++ b/storage/src/system/auction.rs @@ -11,7 +11,7 @@ use tracing::{debug, error, warn}; use crate::system::auction::detail::{ process_with_vesting_schedule, read_delegator_bid, read_delegator_bids, read_validator_bid, - seigniorage_recipient, + seigniorage_recipients, }; use casper_types::{ account::AccountHash, @@ -573,7 +573,6 @@ pub trait Auction: // Compute next auction winners let winners: ValidatorWeights = { let locked_validators = validator_bids_detail.validator_weights( - self, era_id, era_end_timestamp_millis, vesting_schedule_period_millis, @@ -585,7 +584,6 @@ pub trait Auction: let remaining_auction_slots = validator_slots.saturating_sub(locked_validators.len()); if remaining_auction_slots > 0 { let unlocked_validators = validator_bids_detail.validator_weights( - self, era_id, era_end_timestamp_millis, vesting_schedule_period_millis, @@ -613,10 +611,11 @@ pub trait Auction: } }; - let (validator_bids, validator_credits) = validator_bids_detail.destructure(); + let (validator_bids, validator_credits, delegator_bids) = + validator_bids_detail.destructure(); // call prune BEFORE incrementing the era - detail::prune_validator_credits(self, era_id, validator_credits); + detail::prune_validator_credits(self, era_id, &validator_credits); // Increment era era_id = era_id.checked_add(1).ok_or(Error::ArithmeticOverflow)?; @@ -628,16 +627,7 @@ pub trait Auction: // Update seigniorage recipients for current era { let mut snapshot = detail::get_seigniorage_recipients_snapshot(self)?; - let mut recipients = SeigniorageRecipients::new(); - - for era_validator in winners.keys() { - let seigniorage_recipient = match validator_bids.get(era_validator) { - Some(validator_bid) => seigniorage_recipient(self, validator_bid)?, - None => return Err(Error::BidNotFound.into()), - }; - recipients.insert(era_validator.clone(), seigniorage_recipient); - } - + let recipients = seigniorage_recipients(&winners, &validator_bids, &delegator_bids)?; let previous_recipients = snapshot.insert(delayed_era, recipients); assert!(previous_recipients.is_none()); @@ -942,17 +932,18 @@ fn rewards_per_validator( let Some(recipient) = seigniorage_recipients_snapshot .get(&rewarded_era) .ok_or(Error::MissingSeigniorageRecipients)? - .get(validator).cloned() - else { - // We couldn't find the validator. If the reward amount is zero, we don't care - - // the validator wasn't supposed to be rewarded in this era, anyway. Otherwise, - // return an error. - if reward_amount.is_zero() { - continue; - } else { - return Err(Error::ValidatorNotFound); - } - }; + .get(validator) + .cloned() + else { + // We couldn't find the validator. If the reward amount is zero, we don't care - + // the validator wasn't supposed to be rewarded in this era, anyway. Otherwise, + // return an error. + if reward_amount.is_zero() { + continue; + } else { + return Err(Error::ValidatorNotFound); + } + }; let total_stake = recipient.total_stake().ok_or(Error::ArithmeticOverflow)?; diff --git a/storage/src/system/auction/detail.rs b/storage/src/system/auction/detail.rs index 25d45a129a..597a55fc15 100644 --- a/storage/src/system/auction/detail.rs +++ b/storage/src/system/auction/detail.rs @@ -10,11 +10,11 @@ use casper_types::{ account::AccountHash, bytesrepr::{FromBytes, ToBytes}, system::auction::{ - BidAddr, BidKind, Delegator, Error, SeigniorageAllocation, SeigniorageRecipient, - SeigniorageRecipientsSnapshot, UnbondingPurse, UnbondingPurses, ValidatorBid, - ValidatorBids, ValidatorCredit, ValidatorCredits, AUCTION_DELAY_KEY, - ERA_END_TIMESTAMP_MILLIS_KEY, ERA_ID_KEY, SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY, - UNBONDING_DELAY_KEY, VALIDATOR_SLOTS_KEY, + BidAddr, BidKind, Delegator, DelegatorBids, Error, SeigniorageAllocation, + SeigniorageRecipient, SeigniorageRecipients, SeigniorageRecipientsSnapshot, UnbondingPurse, + UnbondingPurses, ValidatorBid, ValidatorBids, ValidatorCredit, ValidatorCredits, + AUCTION_DELAY_KEY, ERA_END_TIMESTAMP_MILLIS_KEY, ERA_ID_KEY, + SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY, UNBONDING_DELAY_KEY, VALIDATOR_SLOTS_KEY, }, ApiError, CLTyped, EraId, Key, KeyTag, PublicKey, URef, U512, }; @@ -59,6 +59,7 @@ where pub struct ValidatorBidsDetail { validator_bids: ValidatorBids, validator_credits: ValidatorCredits, + delegator_bids: DelegatorBids, } impl ValidatorBidsDetail { @@ -66,6 +67,7 @@ impl ValidatorBidsDetail { ValidatorBidsDetail { validator_bids: BTreeMap::new(), validator_credits: BTreeMap::new(), + delegator_bids: BTreeMap::new(), } } @@ -74,7 +76,9 @@ impl ValidatorBidsDetail { &mut self, validator: PublicKey, validator_bid: Box, + delegators: Vec>, ) -> Option> { + self.delegator_bids.insert(validator.clone(), delegators); self.validator_bids.insert(validator, validator_bid) } @@ -110,19 +114,15 @@ impl ValidatorBidsDetail { /// Get validator weights. #[allow(clippy::too_many_arguments)] - pub fn validator_weights

( + pub fn validator_weights( &mut self, - provider: &mut P, era_ending: EraId, era_end_timestamp_millis: u64, vesting_schedule_period_millis: u64, locked: bool, include_credits: bool, cap: Ratio, - ) -> Result - where - P: RuntimeProvider + ?Sized + StorageProvider, - { + ) -> Result { let mut ret = BTreeMap::new(); for (validator_public_key, bid) in self.validator_bids.iter().filter(|(_, v)| { @@ -133,7 +133,13 @@ impl ValidatorBidsDetail { ) && !v.inactive() }) { - let staked_amount = total_staked_amount(provider, bid)?; + let mut staked_amount = bid.staked_amount(); + if let Some(delegators) = self.delegator_bids.get(validator_public_key) { + staked_amount = staked_amount + .checked_add(delegators.iter().map(|d| d.staked_amount()).sum()) + .ok_or(Error::InvalidAmount)?; + } + let credit_amount = self.credit_amount( validator_public_key, era_ending, @@ -182,8 +188,12 @@ impl ValidatorBidsDetail { } /// Consume self into in underlying collections. - pub fn destructure(self) -> (ValidatorBids, ValidatorCredits) { - (self.validator_bids, self.validator_credits) + pub fn destructure(self) -> (ValidatorBids, ValidatorCredits, DelegatorBids) { + ( + self.validator_bids, + self.validator_credits, + self.delegator_bids, + ) } } @@ -193,13 +203,13 @@ impl ValidatorBidsDetail { pub fn prune_validator_credits

( provider: &mut P, era_ending: EraId, - validator_credits: ValidatorCredits, + validator_credits: &ValidatorCredits, ) where P: StorageProvider + RuntimeProvider + ?Sized, { for (validator_public_key, inner) in validator_credits { if inner.contains_key(&era_ending) { - provider.prune_bid(BidAddr::new_credit(&validator_public_key, era_ending)) + provider.prune_bid(BidAddr::new_credit(validator_public_key, era_ending)) } } } @@ -215,7 +225,9 @@ where for key in bids_keys { match provider.read_bid(&key)? { Some(BidKind::Validator(validator_bid)) => { - ret.insert_bid(validator_bid.validator_public_key().clone(), validator_bid); + let validator_public_key = validator_bid.validator_public_key(); + let delegator_bids = delegators(provider, validator_public_key)?; + ret.insert_bid(validator_public_key.clone(), validator_bid, delegator_bids); } Some(BidKind::Credit(credit)) => { ret.insert_credit(credit.validator_public_key().clone(), era_id, credit); @@ -896,28 +908,44 @@ where } } -pub fn seigniorage_recipient

( - provider: &mut P, - validator_bid: &ValidatorBid, -) -> Result -where - P: RuntimeProvider + ?Sized + StorageProvider, -{ - let mut delegator_stake: BTreeMap = BTreeMap::new(); - for delegator_bid in read_delegator_bids(provider, validator_bid.validator_public_key())? { - if delegator_bid.staked_amount().is_zero() { - continue; +pub fn seigniorage_recipients( + validator_weights: &ValidatorWeights, + validator_bids: &ValidatorBids, + delegator_bids: &DelegatorBids, +) -> Result { + let mut recipients = SeigniorageRecipients::new(); + for (validator_public_key, validator_total_weight) in validator_weights { + // check if validator bid exists before processing. + let validator_bid = validator_bids + .get(validator_public_key) + .ok_or(Error::ValidatorNotFound)?; + // calculate delegator portion(s), if any + let mut delegators_weight = U512::zero(); + let mut delegators_stake: BTreeMap = BTreeMap::new(); + if let Some(delegators) = delegator_bids.get(validator_public_key) { + for delegator_bid in delegators { + if delegator_bid.staked_amount().is_zero() { + continue; + } + let delegator_staked_amount = delegator_bid.staked_amount(); + delegators_weight = delegators_weight.saturating_add(delegator_staked_amount); + delegators_stake.insert( + delegator_bid.delegator_public_key().clone(), + delegator_staked_amount, + ); + } } - delegator_stake.insert( - delegator_bid.delegator_public_key().clone(), - delegator_bid.staked_amount(), + + // determine validator's personal stake (total weight - sum of delegators weight) + let validator_stake = validator_total_weight.saturating_sub(delegators_weight); + let seigniorage_recipient = SeigniorageRecipient::new( + validator_stake, + *validator_bid.delegation_rate(), + delegators_stake, ); + recipients.insert(validator_public_key.clone(), seigniorage_recipient); } - Ok(SeigniorageRecipient::new( - validator_bid.staked_amount(), - *validator_bid.delegation_rate(), - delegator_stake, - )) + Ok(recipients) } /// Returns the era validators from a snapshot. @@ -990,26 +1018,25 @@ where } } -/// Returns the total staked amount of validator + all delegators -pub fn total_staked_amount

(provider: &mut P, validator_bid: &ValidatorBid) -> Result +pub fn delegators

( + provider: &mut P, + validator_public_key: &PublicKey, +) -> Result>, Error> where P: RuntimeProvider + ?Sized + StorageProvider, { - let bid_addr = BidAddr::from(validator_bid.validator_public_key().clone()); + let mut ret = vec![]; + let bid_addr = BidAddr::from(validator_public_key.clone()); let delegator_bid_keys = provider.get_keys_by_prefix( &bid_addr .delegators_prefix() .map_err(|_| Error::Serialization)?, )?; - let mut sum = U512::zero(); - for delegator_bid_key in delegator_bid_keys { let delegator = read_delegator_bid(provider, &delegator_bid_key)?; - let staked_amount = delegator.staked_amount(); - sum += staked_amount; + ret.push(delegator); } - sum.checked_add(validator_bid.staked_amount()) - .ok_or(Error::InvalidAmount) + Ok(ret) } diff --git a/types/src/addressable_entity.rs b/types/src/addressable_entity.rs index 701dd866f1..2fcafa1782 100644 --- a/types/src/addressable_entity.rs +++ b/types/src/addressable_entity.rs @@ -866,6 +866,16 @@ impl EntityAddr { || self.value() == PublicKey::System.to_account_hash().value() } + /// Is this a contract entity address? + pub fn is_contract(&self) -> bool { + self.tag() == EntityKindTag::SmartContract + } + + /// Is this an account entity address? + pub fn is_account(&self) -> bool { + self.tag() == EntityKindTag::Account + } + /// Returns the 32 bytes of the [`EntityAddr`]. pub fn value(&self) -> HashAddr { match self { diff --git a/types/src/system/auction.rs b/types/src/system/auction.rs index a299967d0d..54b55afa31 100644 --- a/types/src/system/auction.rs +++ b/types/src/system/auction.rs @@ -49,6 +49,9 @@ pub type DelegationRate = u8; /// Validators mapped to their bids. pub type ValidatorBids = BTreeMap>; +/// Delegator bids mapped to their validator. +pub type DelegatorBids = BTreeMap>>; + /// Validators mapped to their credits by era. pub type ValidatorCredits = BTreeMap>>; diff --git a/utils/global-state-update-gen/src/generic.rs b/utils/global-state-update-gen/src/generic.rs index 62118ebfb0..56d344c90e 100644 --- a/utils/global-state-update-gen/src/generic.rs +++ b/utils/global-state-update-gen/src/generic.rs @@ -308,9 +308,15 @@ pub fn add_and_remove_bids( public_key.clone(), *bid.bonding_purse(), ))), - BidKind::Validator(validator_bid) => BidKind::Validator(Box::new( - ValidatorBid::empty(public_key.clone(), *validator_bid.bonding_purse()), - )), + BidKind::Validator(validator_bid) => { + let mut new_bid = + ValidatorBid::empty(public_key.clone(), *validator_bid.bonding_purse()); + new_bid.set_delegation_amount_boundaries( + validator_bid.minimum_delegation_amount(), + validator_bid.maximum_delegation_amount(), + ); + BidKind::Validator(Box::new(new_bid)) + } BidKind::Delegator(delegator_bid) => { BidKind::Delegator(Box::new(Delegator::empty( public_key.clone(), @@ -425,6 +431,8 @@ fn create_or_update_bid( *bid.delegation_rate(), delegator_stake, ), + 0, + u64::MAX, ) } BidKind::Validator(validator_bid) => { @@ -444,13 +452,17 @@ fn create_or_update_bid( *validator_bid.delegation_rate(), delegator_stake, ), + validator_bid.minimum_delegation_amount(), + validator_bid.maximum_delegation_amount(), ) } _ => unreachable!(), }); // existing bid - if let Some((bonding_purse, existing_recipient)) = maybe_existing_recipient { + if let Some((bonding_purse, existing_recipient, min_delegation_amount, max_delegation_amount)) = + maybe_existing_recipient + { if existing_recipient == *updated_recipient { return; // noop } @@ -524,8 +536,8 @@ fn create_or_update_bid( *bonding_purse, *updated_recipient.stake(), *updated_recipient.delegation_rate(), - 0, - u64::MAX, + min_delegation_amount, + max_delegation_amount, ); state.set_bid(