diff --git a/Cargo.lock b/Cargo.lock index 710e1786..c8fa5baf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -628,6 +628,7 @@ dependencies = [ "hyper 0.14.31", "juliet", "metrics", + "num-traits", "num_cpus", "once_cell", "portpicker", @@ -636,6 +637,7 @@ dependencies = [ "regex", "schemars", "serde", + "serde-map-to-array", "serde_json", "tempfile", "thiserror 1.0.69", diff --git a/resources/test/rpc_schema.json b/resources/test/rpc_schema.json index 1e1ec08a..d1f7f8b7 100644 --- a/resources/test/rpc_schema.json +++ b/resources/test/rpc_schema.json @@ -2425,7 +2425,31 @@ ] } ], - "bids": [] + "bids": [ + { + "public_key": "01197f6b23e16c8532c6abc838facd5ea789be0c76b2920334039bfa8b3d368d61", + "bid": { + "validator_public_key": "01197f6b23e16c8532c6abc838facd5ea789be0c76b2920334039bfa8b3d368d61", + "bonding_purse": "uref-fafafafafafafafafafafafafafafafafafafafafafafafafafafafafafafafa-007", + "staked_amount": "20", + "delegation_rate": 0, + "vesting_schedule": null, + "delegators": [ + { + "delegator_public_key": "014508a07aa941707f3eb2db94c8897a80b2c1197476b6de213ac273df7d86c4ff", + "delegator": { + "delegator_public_key": "014508a07aa941707f3eb2db94c8897a80b2c1197476b6de213ac273df7d86c4ff", + "staked_amount": "10", + "bonding_purse": "uref-fbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbfb-007", + "validator_public_key": "01197f6b23e16c8532c6abc838facd5ea789be0c76b2920334039bfa8b3d368d61", + "vesting_schedule": null + } + } + ], + "inactive": false + } + } + ] } } } @@ -8539,4 +8563,4 @@ } } } -} \ No newline at end of file +} diff --git a/rpc_sidecar/Cargo.toml b/rpc_sidecar/Cargo.toml index a3e3aba9..8df779b0 100644 --- a/rpc_sidecar/Cargo.toml +++ b/rpc_sidecar/Cargo.toml @@ -27,12 +27,14 @@ hyper = "0.14.26" juliet = { version ="0.3", features = ["tracing"] } metrics = { workspace = true } num_cpus = "1" +num-traits = { version = "0.2.10", default-features = false } once_cell.workspace = true portpicker = "0.1.1" rand = "0.8.3" schemars = { version = "0.8.16", features = ["preserve_order", "impl_json_schema"] } serde = { workspace = true, default-features = true, features = ["derive"] } serde_json = { version = "1", features = ["preserve_order"] } +serde-map-to-array = "1.1.0" thiserror = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tokio-util = { version = "0.6.4", features = ["codec"] } diff --git a/rpc_sidecar/src/rpcs/state.rs b/rpc_sidecar/src/rpcs/state.rs index 41e6979b..cec1da9e 100644 --- a/rpc_sidecar/src/rpcs/state.rs +++ b/rpc_sidecar/src/rpcs/state.rs @@ -1,5 +1,7 @@ //! RPCs related to the state. +mod auction_state; + use std::{collections::BTreeMap, str, sync::Arc}; use crate::node_client::{EntityResponse, PackageResponse}; @@ -17,6 +19,7 @@ use super::{ ApiVersion, Error, NodeClient, RpcError, RpcWithOptionalParams, RpcWithParams, CURRENT_API_VERSION, }; +use auction_state::AuctionState; use casper_binary_port::{ DictionaryItemIdentifier, EntityIdentifier as PortEntityIdentifier, GlobalStateQueryResult, PackageIdentifier as PortPackageIdentifier, PurseIdentifier as PortPurseIdentifier, @@ -36,7 +39,7 @@ use casper_types::{ }, AUCTION, }, - AddressableEntity, AddressableEntityHash, AuctionState, BlockHash, BlockHeader, BlockHeaderV2, + AddressableEntity, AddressableEntityHash, BlockHash, BlockHeader, BlockHeaderV2, BlockIdentifier, BlockTime, BlockV2, CLValue, Digest, EntityAddr, EntryPoint, EntryPointValue, EraId, GlobalStateIdentifier, Key, KeyTag, Package, PackageHash, PublicKey, SecretKey, StoredValue, URef, U512, diff --git a/rpc_sidecar/src/rpcs/state/auction_state.rs b/rpc_sidecar/src/rpcs/state/auction_state.rs new file mode 100644 index 00000000..21d329c4 --- /dev/null +++ b/rpc_sidecar/src/rpcs/state/auction_state.rs @@ -0,0 +1,213 @@ +use std::collections::{btree_map::Entry, BTreeMap}; + +use once_cell::sync::Lazy; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_map_to_array::KeyValueJsonSchema; +use serde_map_to_array::{BTreeMapToArray, KeyValueLabels}; + +use casper_types::{ + system::auction::{ + Bid, BidKind, DelegatorBid, DelegatorKind, EraValidators, Staking, ValidatorBid, + }, + Digest, EraId, PublicKey, U512, +}; + +use crate::rpcs::docs::DocExample; + +static ERA_VALIDATORS: Lazy = Lazy::new(|| { + use casper_types::SecretKey; + + let secret_key_1 = SecretKey::ed25519_from_bytes([42; SecretKey::ED25519_LENGTH]).unwrap(); + let public_key_1 = PublicKey::from(&secret_key_1); + + let mut validator_weights = BTreeMap::new(); + validator_weights.insert(public_key_1, U512::from(10)); + + let mut era_validators = BTreeMap::new(); + era_validators.insert(EraId::from(10u64), validator_weights); + + era_validators +}); + +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) +}); + +/// A validator's weight. +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct JsonValidatorWeights { + public_key: PublicKey, + weight: U512, +} + +/// The validators for the given era. +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct JsonEraValidators { + era_id: EraId, + validator_weights: Vec, +} + +/// Data structure summarizing auction contract data. +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct AuctionState { + /// Global state hash. + pub state_root_hash: Digest, + /// Block height. + pub block_height: u64, + /// Era validators. + pub era_validators: Vec, + /// All bids. + #[serde(with = "BTreeMapToArray::")] + bids: BTreeMap, +} + +impl AuctionState { + /// Create new instance of `AuctionState` + 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 { + public_key: public_key.clone(), + weight: *weight, + }); + } + json_era_validators.push(JsonEraValidators { + era_id: *era_id, + validator_weights: json_validator_weights, + }); + } + + let staking = { + let mut staking: Staking = BTreeMap::new(); + for bid_kind in bids.iter().filter(|x| x.is_unified()) { + if let BidKind::Unified(bid) = bid_kind { + let public_key = bid.validator_public_key().clone(); + let validator_bid = ValidatorBid::unlocked( + bid.validator_public_key().clone(), + *bid.bonding_purse(), + *bid.staked_amount(), + *bid.delegation_rate(), + 0, + u64::MAX, + 0, + ); + let mut delegators: BTreeMap = BTreeMap::new(); + for (delegator_public_key, delegator) in bid.delegators() { + delegators.insert( + DelegatorKind::PublicKey(delegator_public_key.clone()), + DelegatorBid::from(delegator.clone()), + ); + } + staking.insert(public_key, (validator_bid, delegators)); + } + } + + for bid_kind in bids.iter().filter(|x| x.is_validator()) { + if let BidKind::Validator(validator_bid) = bid_kind { + let public_key = validator_bid.validator_public_key().clone(); + staking.insert(public_key, (*validator_bid.clone(), BTreeMap::new())); + } + } + + for bid_kind in bids.iter().filter(|x| x.is_delegator()) { + if let BidKind::Delegator(delegator_bid) = bid_kind { + let validator_public_key = delegator_bid.validator_public_key().clone(); + if let Entry::Occupied(mut occupant) = + staking.entry(validator_public_key.clone()) + { + let (_, delegators) = occupant.get_mut(); + delegators.insert( + delegator_bid.delegator_kind().clone(), + *delegator_bid.clone(), + ); + } + } + } + staking + }; + + let mut bids: BTreeMap = BTreeMap::new(); + for (public_key, (validator_bid, delegators)) in staking { + let bid = Bid::from_non_unified(validator_bid, delegators); + bids.insert(public_key, bid); + } + + 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() + } +} + +struct BidLabels; + +impl KeyValueLabels for BidLabels { + const KEY: &'static str = "public_key"; + const VALUE: &'static str = "bid"; +} + +impl KeyValueJsonSchema for BidLabels { + const JSON_SCHEMA_KV_NAME: Option<&'static str> = Some("PublicKeyAndBid"); + const JSON_SCHEMA_KV_DESCRIPTION: Option<&'static str> = + Some("A bid associated with the given public key."); + const JSON_SCHEMA_KEY_DESCRIPTION: Option<&'static str> = Some("The public key of the bidder."); + const JSON_SCHEMA_VALUE_DESCRIPTION: Option<&'static str> = Some("The bid details."); +}