diff --git a/rpc_sidecar/src/http_server.rs b/rpc_sidecar/src/http_server.rs index 2362f204..7b3298ea 100644 --- a/rpc_sidecar/src/http_server.rs +++ b/rpc_sidecar/src/http_server.rs @@ -23,6 +23,7 @@ use super::rpcs::{ GetAccountInfo, GetAuctionInfo, GetBalance, GetDictionaryItem, GetItem, GetTrie, QueryBalance, QueryGlobalState, }, + state_get_auction_info_v2::GetAuctionInfo as GetAuctionInfoV2, RpcWithOptionalParams, RpcWithParams, RpcWithoutParams, }; @@ -59,6 +60,7 @@ pub async fn run( GetEraInfoBySwitchBlock::register_as_handler(node.clone(), &mut handlers); GetEraSummary::register_as_handler(node.clone(), &mut handlers); GetAuctionInfo::register_as_handler(node.clone(), &mut handlers); + GetAuctionInfoV2::register_as_handler(node.clone(), &mut handlers); GetTrie::register_as_handler(node.clone(), &mut handlers); GetValidatorChanges::register_as_handler(node.clone(), &mut handlers); RpcDiscover::register_as_handler(node.clone(), &mut handlers); diff --git a/rpc_sidecar/src/rpcs.rs b/rpc_sidecar/src/rpcs.rs index 30d0885b..c3f61716 100644 --- a/rpc_sidecar/src/rpcs.rs +++ b/rpc_sidecar/src/rpcs.rs @@ -12,6 +12,9 @@ pub mod info; pub mod speculative_exec; pub mod speculative_open_rpc_schema; pub mod state; +pub(crate) mod state_get_auction_info_v2; +#[cfg(test)] +pub(crate) mod test_utils; mod types; use std::{fmt, str, sync::Arc, time::Duration}; diff --git a/rpc_sidecar/src/rpcs/state.rs b/rpc_sidecar/src/rpcs/state.rs index 9641972d..66c9e641 100644 --- a/rpc_sidecar/src/rpcs/state.rs +++ b/rpc_sidecar/src/rpcs/state.rs @@ -2,6 +2,7 @@ mod auction_state; +pub(crate) use auction_state::{JsonEraValidators, JsonValidatorWeights, ERA_VALIDATORS}; use std::{collections::BTreeMap, str, sync::Arc}; use crate::node_client::{EntityResponse, PackageResponse}; @@ -333,7 +334,7 @@ impl RpcWithParams for GetBalance { /// Params for "state_get_auction_info" RPC request. #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(deny_unknown_fields)] -pub struct GetAuctionInfoParams { +pub(crate) struct GetAuctionInfoParams { /// The block identifier. pub block_identifier: BlockIdentifier, } @@ -376,27 +377,12 @@ impl RpcWithOptionalParams for GetAuctionInfo { ) -> Result { let block_identifier = maybe_params.map(|params| params.block_identifier); let block_header = common::get_block_header(&*node_client, block_identifier).await?; - let state_identifier = block_identifier.map(GlobalStateIdentifier::from); - let legacy_bid_stored_values = node_client - .query_global_state_by_tag(state_identifier, KeyTag::Bid) - .await - .map_err(|err| Error::NodeRequest("auction bids", err))? - .into_iter() - .map(|value| { - Ok(BidKind::Unified( - value.into_bid().ok_or(Error::InvalidAuctionState)?.into(), - )) - }); - let bid_stored_values = node_client - .query_global_state_by_tag(state_identifier, KeyTag::BidAddr) - .await - .map_err(|err| Error::NodeRequest("auction bids", err))? - .into_iter() - .map(|value| value.into_bid_kind().ok_or(Error::InvalidAuctionState)); - let bids = legacy_bid_stored_values - .chain(bid_stored_values) - .collect::, Error>>()?; + let state_identifier = + state_identifier.unwrap_or(GlobalStateIdentifier::BlockHeight(block_header.height())); + + let is_not_condor = block_header.protocol_version().value().major == 1; + let bids = fetch_bid_kinds(node_client.clone(), state_identifier, is_not_condor).await?; // always retrieve the latest system contract registry, old versions of the node // did not write it to the global state @@ -412,15 +398,15 @@ impl RpcWithOptionalParams for GetAuctionInfo { .into_t() .map_err(|_| Error::InvalidAuctionState)?; let &auction_hash = registry.get(AUCTION).ok_or(Error::InvalidAuctionState)?; - let maybe_version = get_seniorage_recipients_version( + let maybe_version = get_seigniorage_recipients_version( Arc::clone(&node_client), - state_identifier, + Some(state_identifier), auction_hash, ) .await?; let (snapshot_value, _) = if let Some(result) = node_client .query_global_state( - state_identifier, + Some(state_identifier), Key::addressable_entity_key(EntityKindTag::System, auction_hash), vec![SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY.to_owned()], ) @@ -431,7 +417,7 @@ impl RpcWithOptionalParams for GetAuctionInfo { } else { node_client .query_global_state( - state_identifier, + Some(state_identifier), Key::Hash(auction_hash.value()), vec![SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY.to_owned()], ) @@ -459,7 +445,7 @@ impl RpcWithOptionalParams for GetAuctionInfo { } } -async fn get_seniorage_recipients_version( +pub(crate) async fn get_seigniorage_recipients_version( node_client: Arc, state_identifier: Option, auction_hash: AddressableEntityHash, @@ -479,7 +465,35 @@ async fn get_seniorage_recipients_version( } } -async fn fetch_seigniorage_recipients_snapshot_version_key( +pub(crate) async fn fetch_bid_kinds( + node_client: Arc, + state_identifier: GlobalStateIdentifier, + is_not_condor: bool, +) -> Result, RpcError> { + let key_tag = if is_not_condor { + KeyTag::Bid + } else { + KeyTag::BidAddr + }; + let stored_values = node_client + .query_global_state_by_tag(Some(state_identifier), key_tag) + .await + .map_err(|err| Error::NodeRequest("auction bids", err))? + .into_iter(); + let res: Result, Error> = if is_not_condor { + stored_values + .map(|v| v.into_bid().ok_or(Error::InvalidAuctionState)) + .map(|bid_res| bid_res.map(|bid| BidKind::Unified(bid.into()))) + .collect() + } else { + stored_values + .map(|value| value.into_bid_kind().ok_or(Error::InvalidAuctionState)) + .collect() + }; + res.map_err(|e| e.into()) +} + +pub(crate) async fn fetch_seigniorage_recipients_snapshot_version_key( node_client: Arc, base_key: Key, state_identifier: Option, @@ -491,11 +505,13 @@ async fn fetch_seigniorage_recipients_snapshot_version_key( vec![SEIGNIORAGE_RECIPIENTS_SNAPSHOT_VERSION_KEY.to_owned()], ) .await - .map(|result| result.and_then(unwrap_seniorage_recipients_result)) + .map(|result| result.and_then(unwrap_seigniorage_recipients_result)) .map_err(|err| Error::NodeRequest("auction snapshot", err)) } -fn unwrap_seniorage_recipients_result(query_result: GlobalStateQueryResult) -> Option { +pub(crate) fn unwrap_seigniorage_recipients_result( + query_result: GlobalStateQueryResult, +) -> Option { let (version_value, _) = query_result.into_inner(); let maybe_cl_value = version_value.into_cl_value(); match maybe_cl_value { @@ -1352,16 +1368,16 @@ impl RpcWithParams for GetTrie { } } -fn era_validators_from_snapshot( +pub(crate) fn era_validators_from_snapshot( snapshot: CLValue, maybe_version: Option, ) -> Result { if maybe_version.is_some() { //handle as condor //TODO add some context to the error - let seniorage: BTreeMap = + let seigniorage: BTreeMap = snapshot.into_t().map_err(|_| Error::InvalidAuctionState)?; - Ok(seniorage + Ok(seigniorage .into_iter() .map(|(era_id, recipients)| { let validator_weights = recipients @@ -1376,9 +1392,9 @@ fn era_validators_from_snapshot( } else { //handle as pre-condor //TODO add some context to the error - let seniorage: BTreeMap = + let seigniorage: BTreeMap = snapshot.into_t().map_err(|_| Error::InvalidAuctionState)?; - Ok(seniorage + Ok(seigniorage .into_iter() .map(|(era_id, recipients)| { let validator_weights = recipients @@ -1618,8 +1634,8 @@ mod tests { } ) => { - let response = match req.clone().destructure() { - (None, GlobalStateEntityQualifier::Item { base_key: _, path }) + let response: BinaryResponse = match req.clone().destructure() { + (_, GlobalStateEntityQualifier::Item { base_key: _, path }) if path == vec!["seigniorage_recipients_snapshot_version"] => { let result = GlobalStateQueryResult::new( @@ -1672,7 +1688,7 @@ mod tests { *block.state_root_hash(), block.height(), Default::default(), - vec![bid, BidKind::Unified(legacy_bid.into())] + vec![BidKind::Unified(legacy_bid.into())] ), } ); @@ -1803,7 +1819,7 @@ mod tests { ) => { match req.clone().destructure() { - (None, GlobalStateEntityQualifier::Item { base_key: _, path }) + (_, GlobalStateEntityQualifier::Item { base_key: _, path }) if path == vec!["seigniorage_recipients_snapshot_version"] => { Ok(BinaryResponseAndRequest::new( @@ -1858,7 +1874,7 @@ mod tests { *block.state_root_hash(), block.height(), Default::default(), - vec![bid, BidKind::Unified(legacy_bid.into())] + vec![BidKind::Unified(legacy_bid.into())] ), } ); diff --git a/rpc_sidecar/src/rpcs/state/auction_state.rs b/rpc_sidecar/src/rpcs/state/auction_state.rs index 21d329c4..f54b61da 100644 --- a/rpc_sidecar/src/rpcs/state/auction_state.rs +++ b/rpc_sidecar/src/rpcs/state/auction_state.rs @@ -15,7 +15,7 @@ use casper_types::{ use crate::rpcs::docs::DocExample; -static ERA_VALIDATORS: Lazy = Lazy::new(|| { +pub(crate) static ERA_VALIDATORS: Lazy = Lazy::new(|| { use casper_types::SecretKey; let secret_key_1 = SecretKey::ed25519_from_bytes([42; SecretKey::ED25519_LENGTH]).unwrap(); @@ -75,6 +75,12 @@ pub struct JsonValidatorWeights { weight: U512, } +impl JsonValidatorWeights { + pub fn new(public_key: PublicKey, weight: U512) -> Self { + Self { public_key, weight } + } +} + /// The validators for the given era. #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, JsonSchema)] #[serde(deny_unknown_fields)] @@ -83,10 +89,20 @@ pub struct JsonEraValidators { validator_weights: Vec, } +impl JsonEraValidators { + pub fn new(era_id: EraId, validator_weights: Vec) -> Self { + Self { + era_id, + validator_weights, + } + } +} + +/* We should use the AuctionState struct from casper-types once it's ctor is updated */ /// Data structure summarizing auction contract data. #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, JsonSchema)] #[serde(deny_unknown_fields)] -pub struct AuctionState { +pub(crate) struct AuctionState { /// Global state hash. pub state_root_hash: Digest, /// Block height. diff --git a/rpc_sidecar/src/rpcs/state_get_auction_info_v2.rs b/rpc_sidecar/src/rpcs/state_get_auction_info_v2.rs new file mode 100644 index 00000000..987f405f --- /dev/null +++ b/rpc_sidecar/src/rpcs/state_get_auction_info_v2.rs @@ -0,0 +1,508 @@ +//! RPCs of state_get_auction_info_v2. + +use std::{collections::BTreeMap, str, sync::Arc}; + +use crate::rpcs::state::ERA_VALIDATORS; +use async_trait::async_trait; +use casper_types::system::auction::ValidatorBid; +use once_cell::sync::Lazy; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use super::common; +use super::state::{ + era_validators_from_snapshot, fetch_bid_kinds, get_seigniorage_recipients_version, + GetAuctionInfoParams, JsonEraValidators, JsonValidatorWeights, +}; +use super::{ + docs::{DocExample, DOCS_EXAMPLE_API_VERSION}, + ApiVersion, Error, NodeClient, RpcError, RpcWithOptionalParams, CURRENT_API_VERSION, +}; +use casper_types::{ + addressable_entity::EntityKindTag, + system::{ + auction::{BidKind, DelegatorBid, EraValidators, SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY}, + AUCTION, + }, + AddressableEntityHash, Digest, GlobalStateIdentifier, Key, PublicKey, U512, +}; + +static GET_AUCTION_INFO_RESULT: Lazy = Lazy::new(|| GetAuctionInfoResult { + api_version: DOCS_EXAMPLE_API_VERSION, + auction_state: AuctionState::doc_example().clone(), +}); +static AUCTION_INFO: Lazy = Lazy::new(|| { + use casper_types::{system::auction::DelegationRate, AccessRights, SecretKey, URef}; + use num_traits::Zero; + + let state_root_hash = Digest::from([11; Digest::LENGTH]); + let validator_secret_key = + SecretKey::ed25519_from_bytes([42; SecretKey::ED25519_LENGTH]).unwrap(); + let validator_public_key = PublicKey::from(&validator_secret_key); + + let mut bids = vec![]; + let validator_bid = ValidatorBid::unlocked( + validator_public_key.clone(), + URef::new([250; 32], AccessRights::READ_ADD_WRITE), + U512::from(20), + DelegationRate::zero(), + 0, + u64::MAX, + 0, + ); + bids.push(BidKind::Validator(Box::new(validator_bid))); + + let delegator_secret_key = + SecretKey::ed25519_from_bytes([43; SecretKey::ED25519_LENGTH]).unwrap(); + let delegator_public_key = PublicKey::from(&delegator_secret_key); + let delegator_bid = DelegatorBid::unlocked( + delegator_public_key.into(), + U512::from(10), + URef::new([251; 32], AccessRights::READ_ADD_WRITE), + validator_public_key, + ); + bids.push(BidKind::Delegator(Box::new(delegator_bid))); + + let height: u64 = 10; + let era_validators = ERA_VALIDATORS.clone(); + AuctionState::new(state_root_hash, height, era_validators, bids) +}); + +/// Result for "state_get_auction_info" RPC response. +#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct GetAuctionInfoResult { + /// The RPC API version. + #[schemars(with = "String")] + pub api_version: ApiVersion, + /// The auction state. + pub auction_state: AuctionState, +} + +impl DocExample for GetAuctionInfoResult { + fn doc_example() -> &'static Self { + &GET_AUCTION_INFO_RESULT + } +} + +/// "state_get_auction_info_v2" RPC. +pub struct GetAuctionInfo {} + +#[async_trait] +impl RpcWithOptionalParams for GetAuctionInfo { + const METHOD: &'static str = "state_get_auction_info_v2"; + type OptionalRequestParams = GetAuctionInfoParams; + type ResponseResult = GetAuctionInfoResult; + + async fn do_handle_request( + node_client: Arc, + maybe_params: Option, + ) -> Result { + let block_identifier = maybe_params.map(|params| params.block_identifier); + let block_header = common::get_block_header(&*node_client, block_identifier).await?; + + let state_identifier = block_identifier.map(GlobalStateIdentifier::from); + let state_identifier = + state_identifier.unwrap_or(GlobalStateIdentifier::BlockHeight(block_header.height())); + + let is_not_condor = block_header.protocol_version().value().major == 1; + let bids = fetch_bid_kinds(node_client.clone(), state_identifier, is_not_condor).await?; + + // always retrieve the latest system contract registry, old versions of the node + // did not write it to the global state + let (registry_value, _) = node_client + .query_global_state(Some(state_identifier), Key::SystemEntityRegistry, vec![]) + .await + .map_err(|err| Error::NodeRequest("system contract registry", err))? + .ok_or(Error::GlobalStateEntryNotFound)? + .into_inner(); + let registry: BTreeMap = registry_value + .into_cl_value() + .ok_or(Error::InvalidAuctionState)? + .into_t() + .map_err(|_| Error::InvalidAuctionState)?; + let &auction_hash = registry.get(AUCTION).ok_or(Error::InvalidAuctionState)?; + let maybe_version = get_seigniorage_recipients_version( + Arc::clone(&node_client), + Some(state_identifier), + auction_hash, + ) + .await?; + let (snapshot_value, _) = if let Some(result) = node_client + .query_global_state( + Some(state_identifier), + Key::addressable_entity_key(EntityKindTag::System, auction_hash), + vec![SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY.to_owned()], + ) + .await + .map_err(|err| Error::NodeRequest("auction snapshot", err))? + { + result.into_inner() + } else { + node_client + .query_global_state( + Some(state_identifier), + Key::Hash(auction_hash.value()), + vec![SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY.to_owned()], + ) + .await + .map_err(|err| Error::NodeRequest("auction snapshot", err))? + .ok_or(Error::GlobalStateEntryNotFound)? + .into_inner() + }; + let snapshot = snapshot_value + .into_cl_value() + .ok_or(Error::InvalidAuctionState)?; + + let validators = era_validators_from_snapshot(snapshot, maybe_version)?; + let auction_state = AuctionState::new( + *block_header.state_root_hash(), + block_header.height(), + validators, + bids, + ); + + Ok(Self::ResponseResult { + api_version: CURRENT_API_VERSION, + auction_state, + }) + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct BidKindWrapper { + public_key: PublicKey, + bid: BidKind, +} + +impl BidKindWrapper { + /// ctor + pub fn new(public_key: PublicKey, bid: BidKind) -> Self { + Self { public_key, bid } + } +} + +/// Data structure summarizing auction contract data. +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, JsonSchema)] +#[serde(deny_unknown_fields)] +pub(crate) struct AuctionState { + /// Global state hash. + state_root_hash: Digest, + /// Block height. + block_height: u64, + /// Era validators. + era_validators: Vec, + /// All bids. + bids: Vec, +} + +impl AuctionState { + /// Ctor + pub fn new( + state_root_hash: Digest, + block_height: u64, + era_validators: EraValidators, + bids: Vec, + ) -> Self { + let mut json_era_validators: Vec = Vec::new(); + for (era_id, validator_weights) in era_validators.iter() { + let mut json_validator_weights: Vec = Vec::new(); + for (public_key, weight) in validator_weights.iter() { + json_validator_weights.push(JsonValidatorWeights::new(public_key.clone(), *weight)); + } + json_era_validators.push(JsonEraValidators::new(*era_id, json_validator_weights)); + } + + let bids = bids + .into_iter() + .map(|bid_kind| BidKindWrapper::new(bid_kind.validator_public_key(), bid_kind)) + .collect(); + + AuctionState { + state_root_hash, + block_height, + era_validators: json_era_validators, + bids, + } + } + + // This method is not intended to be used by third party crates. + #[doc(hidden)] + pub fn example() -> &'static Self { + &AUCTION_INFO + } +} + +impl DocExample for AuctionState { + fn doc_example() -> &'static Self { + AuctionState::example() + } +} + +#[cfg(test)] +mod tests { + use crate::{ + rpcs::{ + state_get_auction_info_v2::{AuctionState, GetAuctionInfo, GetAuctionInfoResult}, + test_utils::BinaryPortMock, + RpcWithOptionalParams, CURRENT_API_VERSION, + }, + SUPPORTED_PROTOCOL_VERSION, + }; + use casper_types::{ + system::{ + auction::{ + Bid, BidKind, DelegatorKind, SeigniorageRecipientV1, SeigniorageRecipientV2, + SeigniorageRecipientsV1, SeigniorageRecipientsV2, ValidatorBid, + }, + AUCTION, + }, + testing::TestRng, + AddressableEntityHash, CLValue, EraId, PublicKey, StoredValue, TestBlockV1Builder, U512, + }; + use rand::Rng; + use std::{collections::BTreeMap, sync::Arc}; + + #[tokio::test] + async fn should_read_pre_condor_auction_info_with_addressable_entity_off() { + let rng = &mut TestRng::new(); + let mut binary_port_mock = BinaryPortMock::new(); + let auction_hash: AddressableEntityHash = AddressableEntityHash::new(rng.gen()); + let block_header = TestBlockV1Builder::new() + .build_versioned(rng) + .clone_header(); + let registry = BTreeMap::from([(AUCTION.to_string(), auction_hash)]); + let public_key_1 = PublicKey::random(rng); + let public_key_2 = PublicKey::random(rng); + let recipient_v1 = SeigniorageRecipientV1::new( + U512::from(125), + 50, + BTreeMap::from([(public_key_2, U512::from(500))]), + ); + let recipients_1: BTreeMap = + BTreeMap::from([(public_key_1.clone(), recipient_v1)]); + let v1_recipients: BTreeMap = + BTreeMap::from([(EraId::new(100), recipients_1)]); + let stored_value = StoredValue::CLValue(CLValue::from_t(v1_recipients.clone()).unwrap()); + let bid_1 = Bid::empty(PublicKey::random(rng), rng.gen()); + let bids = vec![bid_1]; + let state_identifier = Some(casper_types::GlobalStateIdentifier::BlockHeight( + block_header.height(), + )); + binary_port_mock + .add_block_header_req_res(block_header.clone()) + .await; + binary_port_mock + .add_bids_fetch_res(bids.clone(), state_identifier) + .await; + binary_port_mock + .add_system_registry(state_identifier, registry) + .await; + binary_port_mock + .add_seigniorage_recipients_version_addressable_entity( + None, + state_identifier, + auction_hash, + ) + .await; + binary_port_mock + .add_seigniorage_recipients_version_key_hash(None, state_identifier, auction_hash) + .await; + binary_port_mock + .add_seigniorage_snapshot_under_addressable_entity(state_identifier, auction_hash, None) + .await; + binary_port_mock + .add_seigniorage_snapshot_under_key_hash( + state_identifier, + auction_hash, + Some(stored_value), + ) + .await; + let resp = GetAuctionInfo::do_handle_request(Arc::new(binary_port_mock), None) + .await + .expect("should handle request"); + let bids = bids + .into_iter() + .map(|b| BidKind::Unified(Box::new(b))) + .collect(); + + let expected_validators = BTreeMap::from([( + EraId::new(100), + BTreeMap::from([(public_key_1, U512::from(625))]), + )]); + + assert_eq!( + resp, + GetAuctionInfoResult { + api_version: CURRENT_API_VERSION, + auction_state: AuctionState::new( + *block_header.state_root_hash(), + block_header.height(), + expected_validators, + bids + ), + } + ); + } + + #[tokio::test] + async fn should_read_condor_auction_info_with_addressable_entity_off() { + let rng = &mut TestRng::new(); + let mut binary_port_mock = BinaryPortMock::new(); + let auction_hash: AddressableEntityHash = AddressableEntityHash::new(rng.gen()); + let block_header = TestBlockV1Builder::new() + .protocol_version(SUPPORTED_PROTOCOL_VERSION.clone()) + .build_versioned(rng) + .clone_header(); + let registry = BTreeMap::from([(AUCTION.to_string(), auction_hash)]); + let public_key_1 = PublicKey::random(rng); + let public_key_2 = PublicKey::random(rng); + let public_key_3 = PublicKey::random(rng); + let delegator_kind_1 = DelegatorKind::PublicKey(public_key_2); + let delegator_kind_2 = DelegatorKind::PublicKey(public_key_3); + let recipient_v2 = SeigniorageRecipientV2::new( + U512::from(125), + 50, + BTreeMap::from([(delegator_kind_1, U512::from(500))]), + BTreeMap::from([(delegator_kind_2, 75)]), + ); + let recipients_1: BTreeMap = + BTreeMap::from([(public_key_1.clone(), recipient_v2)]); + let v2_recipients: BTreeMap = + BTreeMap::from([(EraId::new(100), recipients_1)]); + let stored_value = StoredValue::CLValue(CLValue::from_t(v2_recipients.clone()).unwrap()); + let state_identifier = Some(casper_types::GlobalStateIdentifier::BlockHeight( + block_header.height(), + )); + let validator_bid = ValidatorBid::empty(PublicKey::random(rng), rng.gen()); + let bid_kind_1 = BidKind::Validator(Box::new(validator_bid)); + let bid_kinds = vec![bid_kind_1]; + binary_port_mock + .add_block_header_req_res(block_header.clone()) + .await; + binary_port_mock + .add_bid_kinds_fetch_res(bid_kinds.clone(), state_identifier) + .await; + binary_port_mock + .add_system_registry(state_identifier, registry) + .await; + binary_port_mock + .add_seigniorage_recipients_version_addressable_entity( + None, + state_identifier, + auction_hash, + ) + .await; + binary_port_mock + .add_seigniorage_recipients_version_key_hash(Some(2), state_identifier, auction_hash) + .await; + binary_port_mock + .add_seigniorage_snapshot_under_addressable_entity(state_identifier, auction_hash, None) + .await; + binary_port_mock + .add_seigniorage_snapshot_under_key_hash( + state_identifier, + auction_hash, + Some(stored_value), + ) + .await; + let resp = GetAuctionInfo::do_handle_request(Arc::new(binary_port_mock), None) + .await + .expect("should handle request"); + let expected_validators = BTreeMap::from([( + EraId::new(100), + BTreeMap::from([(public_key_1, U512::from(625))]), + )]); + + assert_eq!( + resp, + GetAuctionInfoResult { + api_version: CURRENT_API_VERSION, + auction_state: AuctionState::new( + *block_header.state_root_hash(), + block_header.height(), + expected_validators, + bid_kinds + ), + } + ); + } + + #[tokio::test] + async fn should_read_condor_auction_info_with_addressable_entity_on() { + let rng = &mut TestRng::new(); + let mut binary_port_mock = BinaryPortMock::new(); + let auction_hash: AddressableEntityHash = AddressableEntityHash::new(rng.gen()); + let block_header = TestBlockV1Builder::new() + .protocol_version(SUPPORTED_PROTOCOL_VERSION.clone()) + .build_versioned(rng) + .clone_header(); + let registry = BTreeMap::from([(AUCTION.to_string(), auction_hash)]); + let public_key_1 = PublicKey::random(rng); + let public_key_2 = PublicKey::random(rng); + let public_key_3 = PublicKey::random(rng); + let delegator_kind_1 = DelegatorKind::PublicKey(public_key_2); + let delegator_kind_2 = DelegatorKind::PublicKey(public_key_3); + let recipient_v2 = SeigniorageRecipientV2::new( + U512::from(125), + 50, + BTreeMap::from([(delegator_kind_1, U512::from(500))]), + BTreeMap::from([(delegator_kind_2, 75)]), + ); + let recipients_1: BTreeMap = + BTreeMap::from([(public_key_1.clone(), recipient_v2)]); + let v2_recipients: BTreeMap = + BTreeMap::from([(EraId::new(100), recipients_1)]); + let stored_value = StoredValue::CLValue(CLValue::from_t(v2_recipients.clone()).unwrap()); + let state_identifier = Some(casper_types::GlobalStateIdentifier::BlockHeight( + block_header.height(), + )); + let validator_bid = ValidatorBid::empty(PublicKey::random(rng), rng.gen()); + let bid_kind_1 = BidKind::Validator(Box::new(validator_bid)); + let bid_kinds = vec![bid_kind_1]; + binary_port_mock + .add_block_header_req_res(block_header.clone()) + .await; + binary_port_mock + .add_bid_kinds_fetch_res(bid_kinds.clone(), state_identifier) + .await; + binary_port_mock + .add_system_registry(state_identifier, registry) + .await; + binary_port_mock + .add_seigniorage_recipients_version_addressable_entity( + Some(2), + state_identifier, + auction_hash, + ) + .await; + binary_port_mock + .add_seigniorage_snapshot_under_addressable_entity( + state_identifier, + auction_hash, + Some(stored_value), + ) + .await; + let resp = GetAuctionInfo::do_handle_request(Arc::new(binary_port_mock), None) + .await + .expect("should handle request"); + let expected_validators = BTreeMap::from([( + EraId::new(100), + BTreeMap::from([(public_key_1, U512::from(625))]), + )]); + + assert_eq!( + resp, + GetAuctionInfoResult { + api_version: CURRENT_API_VERSION, + auction_state: AuctionState::new( + *block_header.state_root_hash(), + block_header.height(), + expected_validators, + bid_kinds + ), + } + ); + } +} diff --git a/rpc_sidecar/src/rpcs/test_utils.rs b/rpc_sidecar/src/rpcs/test_utils.rs new file mode 100644 index 00000000..9c809bdb --- /dev/null +++ b/rpc_sidecar/src/rpcs/test_utils.rs @@ -0,0 +1,219 @@ +use std::{collections::BTreeMap, convert::TryInto, sync::Arc}; + +use async_trait::async_trait; +use casper_binary_port::{ + BinaryRequest, BinaryResponse, BinaryResponseAndRequest, GetRequest, + GlobalStateEntityQualifier, GlobalStateQueryResult, GlobalStateRequest, InformationRequest, +}; +use casper_types::{ + addressable_entity::EntityKindTag, + bytesrepr::ToBytes, + system::auction::{ + Bid, BidKind, SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY, + SEIGNIORAGE_RECIPIENTS_SNAPSHOT_VERSION_KEY, + }, + AddressableEntityHash, BlockHeader, CLValue, GlobalStateIdentifier, Key, KeyTag, + ProtocolVersion, SemVer, StoredValue, +}; +use once_cell::sync::Lazy; +use tokio::sync::Mutex; + +use crate::{ClientError, NodeClient}; + +pub(crate) static PROTOCOL_VERSION: Lazy = + Lazy::new(|| ProtocolVersion::new(SemVer::new(2, 0, 0))); + +pub(crate) struct BinaryPortMock { + request_responses: Arc>>, +} + +impl BinaryPortMock { + pub fn new() -> Self { + Self { + request_responses: Arc::new(Mutex::new(vec![])), + } + } + + pub async fn add_block_header_req_res(&mut self, block_header: BlockHeader) { + let get_request = InformationRequest::BlockHeader(None) + .try_into() + .expect("should create request"); + let req = BinaryRequest::Get(get_request); + let res = BinaryResponse::from_option(Some(block_header), *PROTOCOL_VERSION); + self.when_then(req, res).await; + } + + pub async fn add_bid_kinds_fetch_res( + &mut self, + bid_kinds: Vec, + state_identifier: Option, + ) { + let req = GetRequest::State(Box::new(GlobalStateRequest::new( + state_identifier, + GlobalStateEntityQualifier::AllItems { + key_tag: KeyTag::BidAddr, + }, + ))); + let stored_values: Vec = + bid_kinds.into_iter().map(StoredValue::BidKind).collect(); + let res = BinaryResponse::from_value(stored_values, *PROTOCOL_VERSION); + self.when_then(BinaryRequest::Get(req), res).await; + } + + pub async fn add_bids_fetch_res( + &mut self, + bids: Vec, + state_identifier: Option, + ) { + let req = GetRequest::State(Box::new(GlobalStateRequest::new( + state_identifier, + GlobalStateEntityQualifier::AllItems { + key_tag: KeyTag::Bid, + }, + ))); + + let stored_values: Vec = bids + .into_iter() + .map(|b| StoredValue::Bid(Box::new(b))) + .collect(); + let res = BinaryResponse::from_value(stored_values, *PROTOCOL_VERSION); + self.when_then(BinaryRequest::Get(req), res).await; + } + + pub async fn add_system_registry( + &mut self, + state_identifier: Option, + registry: BTreeMap, + ) { + let req = GetRequest::State(Box::new(GlobalStateRequest::new( + state_identifier, + GlobalStateEntityQualifier::Item { + base_key: Key::SystemEntityRegistry, + path: vec![], + }, + ))); + let cl_value = CLValue::from_t(registry).unwrap(); + let stored_value = StoredValue::CLValue(cl_value); + + let res = BinaryResponse::from_value( + GlobalStateQueryResult::new(stored_value, vec![]), + *PROTOCOL_VERSION, + ); + self.when_then(BinaryRequest::Get(req), res).await; + } + + pub async fn add_seigniorage_recipients_version_addressable_entity( + &mut self, + maybe_seigniorage_recipients_version: Option, + state_identifier: Option, + auction_hash: AddressableEntityHash, + ) { + let base_key = Key::addressable_entity_key(EntityKindTag::System, auction_hash); + let req = GetRequest::State(Box::new(GlobalStateRequest::new( + state_identifier, + GlobalStateEntityQualifier::Item { + base_key, + path: vec![SEIGNIORAGE_RECIPIENTS_SNAPSHOT_VERSION_KEY.to_owned()], + }, + ))); + let res = BinaryResponse::from_option( + maybe_seigniorage_recipients_version.map(|v| { + let cl_value = CLValue::from_t(v).unwrap(); + GlobalStateQueryResult::new(StoredValue::CLValue(cl_value), vec![]) + }), + *PROTOCOL_VERSION, + ); + self.when_then(BinaryRequest::Get(req), res).await; + } + + pub async fn add_seigniorage_recipients_version_key_hash( + &mut self, + maybe_seigniorage_recipients_version: Option, + state_identifier: Option, + auction_hash: AddressableEntityHash, + ) { + let base_key = Key::Hash(auction_hash.value()); + let req = GetRequest::State(Box::new(GlobalStateRequest::new( + state_identifier, + GlobalStateEntityQualifier::Item { + base_key, + path: vec![SEIGNIORAGE_RECIPIENTS_SNAPSHOT_VERSION_KEY.to_owned()], + }, + ))); + let res = BinaryResponse::from_option( + maybe_seigniorage_recipients_version.map(|v| { + let cl_value = CLValue::from_t(v).unwrap(); + GlobalStateQueryResult::new(StoredValue::CLValue(cl_value), vec![]) + }), + *PROTOCOL_VERSION, + ); + self.when_then(BinaryRequest::Get(req), res).await; + } + + pub async fn add_seigniorage_snapshot_under_addressable_entity( + &mut self, + state_identifier: Option, + auction_hash: AddressableEntityHash, + maybe_seigniorage_snapshot: Option, + ) { + let base_key = Key::addressable_entity_key(EntityKindTag::System, auction_hash); + let req = GetRequest::State(Box::new(GlobalStateRequest::new( + state_identifier, + GlobalStateEntityQualifier::Item { + base_key, + path: vec![SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY.to_owned()], + }, + ))); + let res = BinaryResponse::from_option( + maybe_seigniorage_snapshot.map(|v| GlobalStateQueryResult::new(v, vec![])), + *PROTOCOL_VERSION, + ); + self.when_then(BinaryRequest::Get(req), res).await; + } + + pub async fn add_seigniorage_snapshot_under_key_hash( + &mut self, + state_identifier: Option, + auction_hash: AddressableEntityHash, + maybe_seigniorage_snapshot: Option, + ) { + let base_key = Key::Hash(auction_hash.value()); + let req = GetRequest::State(Box::new(GlobalStateRequest::new( + state_identifier, + GlobalStateEntityQualifier::Item { + base_key, + path: vec![SEIGNIORAGE_RECIPIENTS_SNAPSHOT_KEY.to_owned()], + }, + ))); + let res = BinaryResponse::from_option( + maybe_seigniorage_snapshot.map(|v| GlobalStateQueryResult::new(v, vec![])), + *PROTOCOL_VERSION, + ); + self.when_then(BinaryRequest::Get(req), res).await; + } + + pub async fn when_then(&self, when: BinaryRequest, then: BinaryResponse) { + let payload = when.to_bytes().unwrap(); + let response_and_request = BinaryResponseAndRequest::new(then, &payload, 0); + let mut guard = self.request_responses.lock().await; + guard.push((when, response_and_request)); + } +} + +#[async_trait] +impl NodeClient for BinaryPortMock { + async fn send_request( + &self, + req: BinaryRequest, + ) -> Result { + let mut guard = self.request_responses.lock().await; + let (request, response) = guard.remove(0); + if request != req { + panic!( + "Got unexpected request: {:?}. \n\n Expected {:?}", + req, request + ) + } + Ok(response) + } +}