diff --git a/contracts/mixnet/Cargo.lock b/contracts/mixnet/Cargo.lock index ed361dcf24..b36c6fb5a5 100644 --- a/contracts/mixnet/Cargo.lock +++ b/contracts/mixnet/Cargo.lock @@ -727,9 +727,9 @@ checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" [[package]] name = "syn" -version = "1.0.60" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663" dependencies = [ "proc-macro2", "quote", diff --git a/contracts/mixnet/schema/execute_msg.json b/contracts/mixnet/schema/execute_msg.json new file mode 100644 index 0000000000..b329acb2a8 --- /dev/null +++ b/contracts/mixnet/schema/execute_msg.json @@ -0,0 +1,349 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "anyOf": [ + { + "type": "object", + "required": [ + "bond_mixnode" + ], + "properties": { + "bond_mixnode": { + "type": "object", + "required": [ + "mix_node" + ], + "properties": { + "mix_node": { + "$ref": "#/definitions/MixNode" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unbond_mixnode" + ], + "properties": { + "unbond_mixnode": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "bond_gateway" + ], + "properties": { + "bond_gateway": { + "type": "object", + "required": [ + "gateway" + ], + "properties": { + "gateway": { + "$ref": "#/definitions/Gateway" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unbond_gateway" + ], + "properties": { + "unbond_gateway": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_state_params" + ], + "properties": { + "update_state_params": { + "$ref": "#/definitions/StateParams" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "delegate_to_mixnode" + ], + "properties": { + "delegate_to_mixnode": { + "type": "object", + "required": [ + "mix_identity" + ], + "properties": { + "mix_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "undelegate_from_mixnode" + ], + "properties": { + "undelegate_from_mixnode": { + "type": "object", + "required": [ + "mix_identity" + ], + "properties": { + "mix_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "delegate_to_gateway" + ], + "properties": { + "delegate_to_gateway": { + "type": "object", + "required": [ + "gateway_identity" + ], + "properties": { + "gateway_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "undelegate_from_gateway" + ], + "properties": { + "undelegate_from_gateway": { + "type": "object", + "required": [ + "gateway_identity" + ], + "properties": { + "gateway_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "reward_mixnode" + ], + "properties": { + "reward_mixnode": { + "type": "object", + "required": [ + "identity", + "uptime" + ], + "properties": { + "identity": { + "type": "string" + }, + "uptime": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "reward_gateway" + ], + "properties": { + "reward_gateway": { + "type": "object", + "required": [ + "identity", + "uptime" + ], + "properties": { + "identity": { + "type": "string" + }, + "uptime": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Gateway": { + "type": "object", + "required": [ + "clients_port", + "host", + "identity_key", + "location", + "mix_port", + "sphinx_key", + "version" + ], + "properties": { + "clients_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "host": { + "type": "string" + }, + "identity_key": { + "description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients", + "type": "string" + }, + "location": { + "type": "string" + }, + "mix_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "MixNode": { + "type": "object", + "required": [ + "host", + "http_api_port", + "identity_key", + "mix_port", + "sphinx_key", + "verloc_port", + "version" + ], + "properties": { + "host": { + "type": "string" + }, + "http_api_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "identity_key": { + "description": "Base58 encoded ed25519 EdDSA public key.", + "type": "string" + }, + "mix_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "type": "string" + }, + "verloc_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "type": "string" + } + } + }, + "StateParams": { + "type": "object", + "required": [ + "epoch_length", + "gateway_bond_reward_rate", + "gateway_delegation_reward_rate", + "minimum_gateway_bond", + "minimum_mixnode_bond", + "mixnode_active_set_size", + "mixnode_bond_reward_rate", + "mixnode_delegation_reward_rate" + ], + "properties": { + "epoch_length": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "gateway_bond_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "gateway_delegation_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "minimum_gateway_bond": { + "$ref": "#/definitions/Uint128" + }, + "minimum_mixnode_bond": { + "$ref": "#/definitions/Uint128" + }, + "mixnode_active_set_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mixnode_bond_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "mixnode_delegation_reward_rate": { + "$ref": "#/definitions/Decimal" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/handle_msg.json b/contracts/mixnet/schema/handle_msg.json deleted file mode 100644 index 8e3729ceb6..0000000000 --- a/contracts/mixnet/schema/handle_msg.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HandleMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "register_mixnode" - ], - "properties": { - "register_mixnode": { - "type": "object", - "required": [ - "mix_node" - ], - "properties": { - "mix_node": { - "$ref": "#/definitions/MixNode" - } - } - } - } - }, - { - "type": "object", - "required": [ - "un_register_mixnode" - ], - "properties": { - "un_register_mixnode": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "bond_gateway" - ], - "properties": { - "bond_gateway": { - "type": "object", - "required": [ - "gateway" - ], - "properties": { - "gateway": { - "$ref": "#/definitions/Gateway" - } - } - } - } - }, - { - "type": "object", - "required": [ - "unbond_gateway" - ], - "properties": { - "unbond_gateway": { - "type": "object" - } - } - } - ], - "definitions": { - "Gateway": { - "type": "object", - "required": [ - "clients_host", - "identity_key", - "location", - "mix_host", - "sphinx_key", - "version" - ], - "properties": { - "clients_host": { - "type": "string" - }, - "identity_key": { - "description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients", - "type": "string" - }, - "location": { - "type": "string" - }, - "mix_host": { - "type": "string" - }, - "sphinx_key": { - "type": "string" - }, - "version": { - "type": "string" - } - } - }, - "MixNode": { - "type": "object", - "required": [ - "host", - "identity_key", - "layer", - "location", - "sphinx_key", - "version" - ], - "properties": { - "host": { - "type": "string" - }, - "identity_key": { - "description": "Base58 encoded ed25519 EdDSA public key.", - "type": "string" - }, - "layer": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "location": { - "type": "string" - }, - "sphinx_key": { - "type": "string" - }, - "version": { - "type": "string" - } - } - } - } -} diff --git a/contracts/mixnet/schema/init_msg.json b/contracts/mixnet/schema/instantiate_msg.json similarity index 73% rename from contracts/mixnet/schema/init_msg.json rename to contracts/mixnet/schema/instantiate_msg.json index 2b274b4e6d..44588cf226 100644 --- a/contracts/mixnet/schema/init_msg.json +++ b/contracts/mixnet/schema/instantiate_msg.json @@ -1,5 +1,5 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InitMsg", + "title": "InstantiateMsg", "type": "object" } diff --git a/contracts/mixnet/schema/mix_node_bond.json b/contracts/mixnet/schema/mix_node_bond.json index bc6e6c97a2..21c52482ec 100644 --- a/contracts/mixnet/schema/mix_node_bond.json +++ b/contracts/mixnet/schema/mix_node_bond.json @@ -3,25 +3,40 @@ "title": "MixNodeBond", "type": "object", "required": [ - "amount", + "bond_amount", + "layer", "mix_node", - "owner" + "owner", + "total_delegation" ], "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } + "block_height": { + "default": 0, + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "bond_amount": { + "$ref": "#/definitions/Coin" + }, + "layer": { + "$ref": "#/definitions/Layer" }, "mix_node": { "$ref": "#/definitions/MixNode" }, "owner": { - "$ref": "#/definitions/HumanAddr" + "$ref": "#/definitions/Addr" + }, + "total_delegation": { + "$ref": "#/definitions/Coin" } }, "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Coin": { "type": "object", "required": [ @@ -37,44 +52,59 @@ } } }, - "HumanAddr": { - "type": "string" + "Layer": { + "type": "string", + "enum": [ + "Gateway", + "One", + "Two", + "Three" + ] }, "MixNode": { "type": "object", "required": [ "host", + "http_api_port", "identity_key", - "layer", - "location", + "mix_port", "sphinx_key", + "verloc_port", "version" ], "properties": { "host": { "type": "string" }, + "http_api_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, "identity_key": { "description": "Base58 encoded ed25519 EdDSA public key.", "type": "string" }, - "layer": { + "mix_port": { "type": "integer", - "format": "uint64", + "format": "uint16", "minimum": 0.0 }, - "location": { - "type": "string" - }, "sphinx_key": { "type": "string" }, + "verloc_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, "version": { "type": "string" } } }, "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" } } diff --git a/contracts/mixnet/schema/query_msg.json b/contracts/mixnet/schema/query_msg.json index 43c7bff800..5a9c13e1fd 100644 --- a/contracts/mixnet/schema/query_msg.json +++ b/contracts/mixnet/schema/query_msg.json @@ -20,18 +20,15 @@ "minimum": 0.0 }, "start_after": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } + "type": [ + "string", + "null" ] } } } - } + }, + "additionalProperties": false }, { "type": "object", @@ -50,10 +47,96 @@ "format": "uint32", "minimum": 0.0 }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "owns_mixnode" + ], + "properties": { + "owns_mixnode": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "owns_gateway" + ], + "properties": { + "owns_gateway": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "state_params" + ], + "properties": { + "state_params": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_mix_delegations" + ], + "properties": { + "get_mix_delegations": { + "type": "object", + "required": [ + "mix_identity" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "mix_identity": { + "type": "string" + }, "start_after": { "anyOf": [ { - "$ref": "#/definitions/HumanAddr" + "$ref": "#/definitions/Addr" }, { "type": "null" @@ -62,11 +145,179 @@ } } } - } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_reverse_mix_delegations" + ], + "properties": { + "get_reverse_mix_delegations": { + "type": "object", + "required": [ + "delegation_owner" + ], + "properties": { + "delegation_owner": { + "$ref": "#/definitions/Addr" + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_mix_delegation" + ], + "properties": { + "get_mix_delegation": { + "type": "object", + "required": [ + "address", + "mix_identity" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "mix_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_gateway_delegations" + ], + "properties": { + "get_gateway_delegations": { + "type": "object", + "required": [ + "gateway_identity" + ], + "properties": { + "gateway_identity": { + "type": "string" + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_reverse_gateway_delegations" + ], + "properties": { + "get_reverse_gateway_delegations": { + "type": "object", + "required": [ + "delegation_owner" + ], + "properties": { + "delegation_owner": { + "$ref": "#/definitions/Addr" + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_gateway_delegation" + ], + "properties": { + "get_gateway_delegation": { + "type": "object", + "required": [ + "address", + "gateway_identity" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "gateway_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "layer_distribution" + ], + "properties": { + "layer_distribution": { + "type": "object" + } + }, + "additionalProperties": false } ], "definitions": { - "HumanAddr": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", "type": "string" } } diff --git a/contracts/mixnet/schema/state.json b/contracts/mixnet/schema/state.json index d559e403ee..33ea553981 100644 --- a/contracts/mixnet/schema/state.json +++ b/contracts/mixnet/schema/state.json @@ -3,15 +3,91 @@ "title": "State", "type": "object", "required": [ - "owner" + "gateway_epoch_bond_reward", + "gateway_epoch_delegation_reward", + "mixnode_epoch_bond_reward", + "mixnode_epoch_delegation_reward", + "network_monitor_address", + "owner", + "params" ], "properties": { + "gateway_epoch_bond_reward": { + "$ref": "#/definitions/Decimal" + }, + "gateway_epoch_delegation_reward": { + "$ref": "#/definitions/Decimal" + }, + "mixnode_epoch_bond_reward": { + "$ref": "#/definitions/Decimal" + }, + "mixnode_epoch_delegation_reward": { + "$ref": "#/definitions/Decimal" + }, + "network_monitor_address": { + "$ref": "#/definitions/Addr" + }, "owner": { - "$ref": "#/definitions/HumanAddr" + "$ref": "#/definitions/Addr" + }, + "params": { + "$ref": "#/definitions/StateParams" } }, "definitions": { - "HumanAddr": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "StateParams": { + "type": "object", + "required": [ + "epoch_length", + "gateway_bond_reward_rate", + "gateway_delegation_reward_rate", + "minimum_gateway_bond", + "minimum_mixnode_bond", + "mixnode_active_set_size", + "mixnode_bond_reward_rate", + "mixnode_delegation_reward_rate" + ], + "properties": { + "epoch_length": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "gateway_bond_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "gateway_delegation_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "minimum_gateway_bond": { + "$ref": "#/definitions/Uint128" + }, + "minimum_mixnode_bond": { + "$ref": "#/definitions/Uint128" + }, + "mixnode_active_set_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mixnode_bond_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "mixnode_delegation_reward_rate": { + "$ref": "#/definitions/Decimal" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" } } diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 7dc7843723..88b76a96bf 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -10,7 +10,9 @@ use cosmwasm_std::{ entry_point, to_binary, Addr, Decimal, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Uint128, }; -use mixnet_contract::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StateParams}; +use mixnet_contract::{ + ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, RawDelegationData, StateParams, +}; pub const INITIAL_DEFAULT_EPOCH_LENGTH: u32 = 2; @@ -205,31 +207,68 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result Result { - use crate::storage::{gateways, gateways_read, mixnodes, mixnodes_read}; +pub fn migrate(mut deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + use crate::storage::{ + gateway_delegations, gateway_delegations_read, gateway_delegations_read_old, gateways_read, + mix_delegations, mix_delegations_read, mix_delegations_read_old, mixnodes_read, + }; + use crate::transactions::delegations; use cosmwasm_std::{Order, StdResult}; - use mixnet_contract::CURRENT_BLOCK_HEIGHT; use mixnet_contract::{GatewayBond, MixNodeBond}; - use std::sync::atomic::Ordering; - CURRENT_BLOCK_HEIGHT.store(env.block.height, Ordering::Relaxed); + // Read existing delegations data, drop invalid values, and rewrite delegations data with valid data only + fn overwrite_mixnode_delegations_data( + identity: &str, + deps: &mut DepsMut, + ) -> Result<(), ContractError> { + let delegations_bucket = mix_delegations_read(deps.storage, identity); + let old_delegations_bucket = mix_delegations_read_old(deps.storage, identity); + let mut delegations_vec = delegations(delegations_bucket)?; + let old_delegations = delegations::(old_delegations_bucket)?; + for delegation in old_delegations { + delegations_vec.push((delegation.0, RawDelegationData::new(delegation.1, 1))) + } - let bonds = mixnodes_read(deps.storage) + for (key, delegation) in delegations_vec { + mix_delegations(deps.storage, identity).save(&key, &delegation)?; + } + Ok(()) + } + + fn overwrite_gateway_delegations_data( + identity: &str, + deps: &mut DepsMut, + ) -> Result<(), ContractError> { + let delegations_bucket = gateway_delegations_read(deps.storage, identity); + let old_delegations_bucket = gateway_delegations_read_old(deps.storage, identity); + let mut delegations_vec = delegations(delegations_bucket)?; + let old_delegations = delegations::(old_delegations_bucket)?; + for delegation in old_delegations { + delegations_vec.push((delegation.0, RawDelegationData::new(delegation.1, 1))) + } + + for (key, delegation) in delegations_vec { + gateway_delegations(deps.storage, identity).save(&key, &delegation)?; + } + Ok(()) + } + + let mixnet_bonds = mixnodes_read(deps.storage) .range(None, None, Order::Ascending) .map(|res| res.map(|item| item.1)) .collect::>>()?; - for bond in bonds { - mixnodes(deps.storage).save(bond.identity().as_bytes(), &bond)?; + for bond in mixnet_bonds { + overwrite_mixnode_delegations_data(bond.identity(), &mut deps)?; } - let bonds = gateways_read(deps.storage) + let gateway_bonds = gateways_read(deps.storage) .range(None, None, Order::Ascending) .map(|res| res.map(|item| item.1)) .collect::>>()?; - for bond in bonds { - gateways(deps.storage).save(bond.identity().as_bytes(), &bond)?; + for bond in gateway_bonds { + overwrite_gateway_delegations_data(bond.identity(), &mut deps)?; } Ok(Default::default()) diff --git a/contracts/mixnet/src/storage.rs b/contracts/mixnet/src/storage.rs index c038691389..43f1398d89 100644 --- a/contracts/mixnet/src/storage.rs +++ b/contracts/mixnet/src/storage.rs @@ -36,6 +36,8 @@ const PREFIX_REVERSE_GATEWAY_DELEGATION: &[u8] = b"dg"; // Contract-level stuff +// TODO Unify bucket and mixnode storage functions + pub fn config(storage: &mut dyn Storage) -> Singleton { singleton(storage, CONFIG_KEY) } @@ -316,6 +318,14 @@ pub fn mix_delegations_read<'a>( ReadonlyBucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()]) } +// https://github.com/nymtech/nym/blob/122f5d9f2e5c1ced96e3b9ba0c74ef8b7dbde2c7/contracts/mixnet/src/storage.rs +pub fn mix_delegations_read_old<'a>( + storage: &'a dyn Storage, + mix_identity: IdentityKeyRef, +) -> ReadonlyBucket<'a, Uint128> { + ReadonlyBucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()]) +} + pub fn reverse_mix_delegations<'a>(storage: &'a mut dyn Storage, owner: &Addr) -> Bucket<'a, ()> { Bucket::multilevel(storage, &[PREFIX_REVERSE_MIX_DELEGATION, owner.as_bytes()]) } @@ -347,6 +357,16 @@ pub fn gateway_delegations_read<'a>( ) } +pub fn gateway_delegations_read_old<'a>( + storage: &'a dyn Storage, + gateway_identity: IdentityKeyRef, +) -> ReadonlyBucket<'a, Uint128> { + ReadonlyBucket::multilevel( + storage, + &[PREFIX_GATEWAY_DELEGATION, gateway_identity.as_bytes()], + ) +} + pub fn reverse_gateway_delegations<'a>( storage: &'a mut dyn Storage, owner: &Addr, diff --git a/contracts/mixnet/src/transactions.rs b/contracts/mixnet/src/transactions.rs index 2e753ebf06..781269a3f9 100644 --- a/contracts/mixnet/src/transactions.rs +++ b/contracts/mixnet/src/transactions.rs @@ -14,6 +14,8 @@ use cosmwasm_storage::ReadonlyBucket; use mixnet_contract::{ Gateway, GatewayBond, IdentityKey, Layer, MixNode, MixNodeBond, RawDelegationData, StateParams, }; +use serde::de::DeserializeOwned; +use serde::Serialize; const OLD_DELEGATIONS_CHUNK_SIZE: usize = 500; pub(crate) const MINIMUM_BLOCK_AGE_FOR_REWARDING: u64 = 17280; @@ -25,12 +27,15 @@ pub(crate) const MINIMUM_BLOCK_AGE_FOR_REWARDING: u64 = 17280; // 3. The node unbonds // 4. Some of the addresses that delegated in the past have not removed the delegation yet // 5. The node rebonds with the same identity -fn find_old_delegations(delegations_bucket: ReadonlyBucket) -> StdResult { +pub fn delegations( + delegations_bucket: ReadonlyBucket, +) -> StdResult, T)>> { // I think it's incredibly unlikely to ever read more than that // but in case we do, we should guard ourselves against possible // out of memory errors (wasm contracts can only allocate at most 2MB // of RAM, so we don't want to box the entire iterator) - let mut total_delegation = Coin::new(0, DENOM); + let mut delegations = Vec::new(); + // let mut total_delegation = Coin::new(0, DENOM); let mut start = None; loop { let iterator = delegations_bucket @@ -39,16 +44,23 @@ fn find_old_delegations(delegations_bucket: ReadonlyBucket) - let mut iterated = 0; - for delegation in iterator { + for result_tuple in iterator { iterated += 1; - if iterated == OLD_DELEGATIONS_CHUNK_SIZE + 1 { - // we reached start of next chunk, don't process it, mark it for the next iteration of the loop - start = Some(delegation?.0); - continue; + match result_tuple { + Ok((position, delegation)) => { + // We might skip some values due to deserializatio errors + if iterated > OLD_DELEGATIONS_CHUNK_SIZE { + // we reached start of next chunk, don't process it, mark it for the next iteration of the loop + start = Some(position); + continue; + } + delegations.push((position, delegation)) + } + Err(_e) => { + // Skip errors + continue; + } } - - let value = delegation?.1.amount; - total_delegation.amount += value; } if iterated <= OLD_DELEGATIONS_CHUNK_SIZE { @@ -57,7 +69,17 @@ fn find_old_delegations(delegations_bucket: ReadonlyBucket) - } } - Ok(total_delegation) + Ok(delegations) +} + +fn total_delegations(delegations_bucket: ReadonlyBucket) -> StdResult { + match delegations(delegations_bucket) { + Ok(delegations) => Ok(Coin::new( + delegations.iter().fold(0, |acc, x| acc + x.1.amount.u128()), + "upunk", + )), + Err(e) => Err(e), + } } fn validate_mixnode_bond(bond: &[Coin], minimum_bond: Uint128) -> Result<(), ContractError> { @@ -138,7 +160,7 @@ pub(crate) fn try_add_mixnode( // this might potentially require more gas if a significant number of delegations was there let delegations_bucket = mix_delegations_read(deps.storage, &bond.mix_node.identity_key); - let existing_delegation = find_old_delegations(delegations_bucket)?; + let existing_delegation = total_delegations(delegations_bucket)?; bond.total_delegation = existing_delegation; let identity = bond.identity(); @@ -270,7 +292,7 @@ pub(crate) fn try_add_gateway( // this might potentially require more gas if a significant number of delegations was there let delegations_bucket = gateway_delegations_read(deps.storage, &bond.gateway.identity_key); - let existing_delegation = find_old_delegations(delegations_bucket)?; + let existing_delegation = total_delegations(delegations_bucket)?; bond.total_delegation = existing_delegation; let identity = bond.identity(); @@ -3928,7 +3950,7 @@ pub mod tests { let node_identity: IdentityKey = "nodeidentity".into(); let read_bucket = mix_delegations_read(&deps.storage, &node_identity); - let old_delegations = find_old_delegations(read_bucket).unwrap(); + let old_delegations = total_delegations(read_bucket).unwrap(); assert_eq!(Coin::new(0, DENOM), old_delegations); } @@ -3945,14 +3967,14 @@ pub mod tests { OLD_DELEGATIONS_CHUNK_SIZE * 3 + 1, ]; - for total_delegations in num_delegations { + for delegations in num_delegations { let mut deps = helpers::init_contract(); let node_identity: IdentityKey = "nodeidentity".into(); // delegate some stake let mut write_bucket = mix_delegations(&mut deps.storage, &node_identity); - for i in 1..=total_delegations { + for i in 1..=delegations { let delegator = Addr::unchecked(format!("delegator{}", i)); let delegation = raw_delegation_fixture(i as u128); write_bucket @@ -3961,9 +3983,9 @@ pub mod tests { } let read_bucket = mix_delegations_read(&deps.storage, &node_identity); - let old_delegations = find_old_delegations(read_bucket).unwrap(); + let old_delegations = total_delegations(read_bucket).unwrap(); - let total_delegation = (1..=total_delegations as u128).into_iter().sum(); + let total_delegation = (1..=delegations as u128).into_iter().sum(); assert_eq!(Coin::new(total_delegation, DENOM), old_delegations); } }