Skip to content

Commit

Permalink
Merge #4640 #4689
Browse files Browse the repository at this point in the history
4640: burn implemented r=EdHastingsCasperAssociation a=hoffmannjan

Implements casper-network/ceps#92

4689: Use a shared enum for key prefixes r=EdHastingsCasperAssociation a=jacek-casper

I need a way to query global state by prefixes for https://app.zenhub.com/workspaces/core-protocol-60953fafb1945f0011a3592d/issues/gh/casper-network/casper-node/4686. The existing code manually encodes byte arrays wherever a lookup by prefix was needed, but that's difficult to keep track of and it's pretty prone to errors, so I decided to create an enum that represents all possible key prefix combinations (excluding ones that are too broad, like querying all system/account/contract entities). This enum allows us to have a single data type for all the prefix lookups. Its deserializer is tested against the Key serializer to ensure that it remains binary compatible with the `Key` type. 

This enum can also be a useful way to expose the capability of querying the GS by a prefix to binary port consumers in a safe way, because we can prevent them from making very large queries (like querying with just a key tag) and we can restrict which `KeyPrefix` variants we allow in the binary port.

Co-authored-by: Jan Hoffmann <[email protected]>
Co-authored-by: Ed Hastings <[email protected]>
Co-authored-by: Jacek Malec <[email protected]>
  • Loading branch information
4 people authored May 3, 2024
3 parents 7317868 + a6f12ce + 155b27f commit 679fde2
Show file tree
Hide file tree
Showing 40 changed files with 985 additions and 134 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

171 changes: 171 additions & 0 deletions binary_port/src/key_prefix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#[cfg(any(feature = "testing", test))]
use casper_types::testing::TestRng;
use casper_types::{
account::AccountHash,
bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH},
contract_messages::TopicNameHash,
system::{auction::BidAddrTag, mint::BalanceHoldAddrTag},
EntityAddr, KeyTag, URefAddr,
};
#[cfg(any(feature = "testing", test))]
use rand::Rng;

/// Key prefixes used for querying the global state.
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
pub enum KeyPrefix {
/// Retrieves all delegator bid addresses for a given validator.
DelegatorBidAddrsByValidator(AccountHash),
/// Retrieves all messages for a given entity.
MessagesByEntity(EntityAddr),
/// Retrieves all messages for a given entity and topic.
MessagesByEntityAndTopic(EntityAddr, TopicNameHash),
/// Retrieves all named keys for a given entity.
NamedKeysByEntity(EntityAddr),
/// Retrieves all gas balance holds for a given purse.
GasBalanceHoldsByPurse(URefAddr),
/// Retrieves all processing balance holds for a given purse.
ProcessingBalanceHoldsByPurse(URefAddr),
}

impl KeyPrefix {
/// Returns a random `KeyPrefix`.
#[cfg(any(feature = "testing", test))]
pub fn random(rng: &mut TestRng) -> Self {
match rng.gen_range(0..6) {
0 => KeyPrefix::DelegatorBidAddrsByValidator(rng.gen()),
1 => KeyPrefix::MessagesByEntity(rng.gen()),
2 => KeyPrefix::MessagesByEntityAndTopic(rng.gen(), rng.gen()),
3 => KeyPrefix::NamedKeysByEntity(rng.gen()),
4 => KeyPrefix::GasBalanceHoldsByPurse(rng.gen()),
5 => KeyPrefix::ProcessingBalanceHoldsByPurse(rng.gen()),
_ => unreachable!(),
}
}
}

impl ToBytes for KeyPrefix {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut result = bytesrepr::unchecked_allocate_buffer(self);
self.write_bytes(&mut result)?;
Ok(result)
}

fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
match self {
KeyPrefix::DelegatorBidAddrsByValidator(validator) => {
writer.push(KeyTag::BidAddr as u8);
writer.push(BidAddrTag::Delegator as u8);
validator.write_bytes(writer)?;
}
KeyPrefix::MessagesByEntity(entity) => {
writer.push(KeyTag::Message as u8);
entity.write_bytes(writer)?;
}
KeyPrefix::MessagesByEntityAndTopic(entity, topic) => {
writer.push(KeyTag::Message as u8);
entity.write_bytes(writer)?;
topic.write_bytes(writer)?;
}
KeyPrefix::NamedKeysByEntity(entity) => {
writer.push(KeyTag::NamedKey as u8);
entity.write_bytes(writer)?;
}
KeyPrefix::GasBalanceHoldsByPurse(uref) => {
writer.push(KeyTag::BalanceHold as u8);
writer.push(BalanceHoldAddrTag::Gas as u8);
uref.write_bytes(writer)?;
}
KeyPrefix::ProcessingBalanceHoldsByPurse(uref) => {
writer.push(KeyTag::BalanceHold as u8);
writer.push(BalanceHoldAddrTag::Processing as u8);
uref.write_bytes(writer)?;
}
}
Ok(())
}

fn serialized_length(&self) -> usize {
U8_SERIALIZED_LENGTH
+ match self {
KeyPrefix::DelegatorBidAddrsByValidator(validator) => {
U8_SERIALIZED_LENGTH + validator.serialized_length()
}
KeyPrefix::MessagesByEntity(entity) => entity.serialized_length(),
KeyPrefix::MessagesByEntityAndTopic(entity, topic) => {
entity.serialized_length() + topic.serialized_length()
}
KeyPrefix::NamedKeysByEntity(entity) => entity.serialized_length(),
KeyPrefix::GasBalanceHoldsByPurse(uref) => {
U8_SERIALIZED_LENGTH + uref.serialized_length()
}
KeyPrefix::ProcessingBalanceHoldsByPurse(uref) => {
U8_SERIALIZED_LENGTH + uref.serialized_length()
}
}
}
}

impl FromBytes for KeyPrefix {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (tag, remainder) = u8::from_bytes(bytes)?;
let result = match tag {
tag if tag == KeyTag::BidAddr as u8 => {
let (bid_addr_tag, remainder) = u8::from_bytes(remainder)?;
match bid_addr_tag {
tag if tag == BidAddrTag::Delegator as u8 => {
let (validator, remainder) = AccountHash::from_bytes(remainder)?;
(
KeyPrefix::DelegatorBidAddrsByValidator(validator),
remainder,
)
}
_ => return Err(bytesrepr::Error::Formatting),
}
}
tag if tag == KeyTag::Message as u8 => {
let (entity, remainder) = EntityAddr::from_bytes(remainder)?;
if remainder.is_empty() {
(KeyPrefix::MessagesByEntity(entity), remainder)
} else {
let (topic, remainder) = TopicNameHash::from_bytes(remainder)?;
(
KeyPrefix::MessagesByEntityAndTopic(entity, topic),
remainder,
)
}
}
tag if tag == KeyTag::NamedKey as u8 => {
let (entity, remainder) = EntityAddr::from_bytes(remainder)?;
(KeyPrefix::NamedKeysByEntity(entity), remainder)
}
tag if tag == KeyTag::BalanceHold as u8 => {
let (balance_hold_addr_tag, remainder) = u8::from_bytes(remainder)?;
let (uref, remainder) = URefAddr::from_bytes(remainder)?;
match balance_hold_addr_tag {
tag if tag == BalanceHoldAddrTag::Gas as u8 => {
(KeyPrefix::GasBalanceHoldsByPurse(uref), remainder)
}
tag if tag == BalanceHoldAddrTag::Processing as u8 => {
(KeyPrefix::ProcessingBalanceHoldsByPurse(uref), remainder)
}
_ => return Err(bytesrepr::Error::Formatting),
}
}
_ => return Err(bytesrepr::Error::Formatting),
};
Ok(result)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn bytesrepr_roundtrip() {
let rng = &mut TestRng::new();

let key_prefix = KeyPrefix::random(rng);
bytesrepr::test_serialization_roundtrip(&key_prefix);
}
}
2 changes: 2 additions & 0 deletions binary_port/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod error_code;
mod get_request;
mod global_state_query_result;
mod information_request;
mod key_prefix;
mod minimal_block_info;
mod node_status;
mod payload_type;
Expand All @@ -33,6 +34,7 @@ pub use error_code::ErrorCode;
pub use get_request::GetRequest;
pub use global_state_query_result::GlobalStateQueryResult;
pub use information_request::{InformationRequest, InformationRequestTag};
pub use key_prefix::KeyPrefix;
pub use minimal_block_info::MinimalBlockInfo;
pub use node_status::NodeStatus;
pub use payload_type::{PayloadEntity, PayloadType};
Expand Down
5 changes: 5 additions & 0 deletions execution_engine/src/runtime/mint_internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ where
fn allow_unrestricted_transfers(&self) -> bool {
self.context.engine_config().allow_unrestricted_transfers()
}

/// Validate URef against context access rights.
fn is_valid_uref(&self, uref: &URef) -> bool {
self.context.access_rights().has_access_rights_to_uref(uref)
}
}

// TODO: update Mint + StorageProvider to better handle errors
Expand Down
8 changes: 8 additions & 0 deletions execution_engine/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,14 @@ where
let result: Result<(), mint::Error> = mint_runtime.reduce_total_supply(amount);
CLValue::from_t(result).map_err(Self::reverter)
})(),
mint::METHOD_BURN => (|| {
mint_runtime.charge_system_contract_call(mint_costs.burn)?;

let purse: URef = Self::get_named_argument(runtime_args, mint::ARG_PURSE)?;
let amount: U512 = Self::get_named_argument(runtime_args, mint::ARG_AMOUNT)?;
let result: Result<(), mint::Error> = mint_runtime.burn(purse, amount);
CLValue::from_t(result).map_err(Self::reverter)
})(),
// Type: `fn create() -> URef`
mint::METHOD_CREATE => (|| {
mint_runtime.charge_system_contract_call(mint_costs.create)?;
Expand Down
4 changes: 2 additions & 2 deletions execution_engine/src/runtime_context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ where
}

/// Validates whether keys used in the `value` are not forged.
fn validate_value(&self, value: &StoredValue) -> Result<(), ExecError> {
pub(crate) fn validate_value(&self, value: &StoredValue) -> Result<(), ExecError> {
match value {
StoredValue::CLValue(cl_value) => self.validate_cl_value(cl_value),
StoredValue::NamedKey(named_key_value) => {
Expand Down Expand Up @@ -727,7 +727,7 @@ where
}

/// Validates if a [`Key`] refers to a [`URef`] and has a write bit set.
fn validate_writeable(&self, key: &Key) -> Result<(), ExecError> {
pub(crate) fn validate_writeable(&self, key: &Key) -> Result<(), ExecError> {
if self.is_writeable(key) {
Ok(())
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -705,8 +705,8 @@ where
/// Panics if the total supply can't be found.
pub fn total_supply(
&self,
maybe_post_state: Option<Digest>,
protocol_version: ProtocolVersion,
maybe_post_state: Option<Digest>,
) -> U512 {
let post_state = maybe_post_state
.or(self.post_state_hash)
Expand Down Expand Up @@ -759,7 +759,7 @@ where
let post_state = maybe_post_state
.or(self.post_state_hash)
.expect("builder must have a post-state hash");
let total_supply = self.total_supply(Some(post_state), protocol_version);
let total_supply = self.total_supply(protocol_version, Some(post_state));
let rate = self.round_seigniorage_rate(Some(post_state), protocol_version);
rate.checked_mul(&Ratio::from(total_supply))
.map(|ratio| ratio.to_integer())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ fn test_burning_fees(
.expect("should have rewards purse");
let rewards_purse_uref = rewards_purse_key.into_uref().expect("should be uref");
assert_eq!(builder.get_purse_balance(rewards_purse_uref), U512::zero());
let total_supply_before = builder.total_supply(None, protocol_version);
let total_supply_before = builder.total_supply(protocol_version, None);
// TODO: reevaluate this test, considering fee / refund / pricing modes
// let exec_request_1 = ExecuteRequestBuilder::module_bytes(
// *DEFAULT_ADMIN_ACCOUNT_ADDR,
Expand All @@ -139,7 +139,7 @@ fn test_burning_fees(
// U512::zero(),
// "proposer should not receive anything",
// );
let total_supply_after = builder.total_supply(None, protocol_version);
let total_supply_after = builder.total_supply(protocol_version, None);
assert_eq!(
total_supply_before - total_supply_after,
expected_burn_amount,
Expand All @@ -149,11 +149,11 @@ fn test_burning_fees(
TransferRequestBuilder::new(MINIMUM_ACCOUNT_CREATION_BALANCE, *ACCOUNT_1_ADDR)
.with_initiator(*DEFAULT_ADMIN_ACCOUNT_ADDR)
.build();
let total_supply_before = builder.total_supply(None, protocol_version);
let total_supply_before = builder.total_supply(protocol_version, None);
builder
.transfer_and_commit(transfer_request)
.expect_success();
let total_supply_after = builder.total_supply(None, protocol_version);
let total_supply_after = builder.total_supply(protocol_version, None);

match fee_handling {
FeeHandling::PayToProposer | FeeHandling::Accumulate | FeeHandling::NoFee => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fn should_not_distribute_rewards_but_compute_next_set() {

let protocol_version = DEFAULT_PROTOCOL_VERSION;
// initial token supply
let initial_supply = builder.total_supply(None, protocol_version);
let initial_supply = builder.total_supply(protocol_version, None);

for _ in 0..3 {
builder.distribute(
Expand Down Expand Up @@ -101,7 +101,7 @@ fn should_not_distribute_rewards_but_compute_next_set() {
era_info
);

let total_supply_after_distribution = builder.total_supply(None, protocol_version);
let total_supply_after_distribution = builder.total_supply(protocol_version, None);
assert_eq!(
initial_supply, total_supply_after_distribution,
"total supply of tokens should not increase after an auction is ran"
Expand Down
Loading

0 comments on commit 679fde2

Please sign in to comment.