diff --git a/.cargo/config b/.cargo/config.toml similarity index 100% rename from .cargo/config rename to .cargo/config.toml diff --git a/.github/actions/build-helper/action.yml b/.github/actions/build-helper/action.yml index 2dde18f23..2eac0ffc7 100644 --- a/.github/actions/build-helper/action.yml +++ b/.github/actions/build-helper/action.yml @@ -14,6 +14,6 @@ runs: run: docker build -t witnet-rust/${{ inputs.imagename }}:latest -f ./docker/cross-compilation/${{ inputs.imagename }}/Dockerfile ./docker/cross-compilation/ - name: Upload image - uses: ishworkh/docker-image-artifact-upload@v1 + uses: ishworkh/container-image-artifact-upload@v1.1.1 with: image: "witnet-rust/${{ inputs.imagename }}:latest" diff --git a/.github/actions/build-linux/action.yml b/.github/actions/build-linux/action.yml index 035ba3e29..82322a51e 100644 --- a/.github/actions/build-linux/action.yml +++ b/.github/actions/build-linux/action.yml @@ -11,7 +11,7 @@ runs: steps: # Download Helper Image built in the previous jobs - name: Downloading helper Image - uses: ishworkh/docker-image-artifact-download@v1 + uses: ishworkh/container-image-artifact-upload@v1.1.1 with: image: "witnet-rust/${{ inputs.target }}:latest" diff --git a/.github/actions/download-releases/action.yml b/.github/actions/download-releases/action.yml index e95faf148..c803dc150 100644 --- a/.github/actions/download-releases/action.yml +++ b/.github/actions/download-releases/action.yml @@ -17,7 +17,7 @@ runs: # Download & Hash Releases # Macos - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: macos-release path: all-releases/macos/ @@ -29,7 +29,7 @@ runs: mv all-releases/macos/witnet_toolkit release/witnet_toolkit-x86_64-apple-darwin # Windows - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: windows-release path: all-releases/windows/ @@ -41,7 +41,7 @@ runs: mv all-releases/windows/witnet_toolkit.exe release/witnet_toolkit-x86_64-pc-windows-msvc.exe # x86_64 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: x86_64-release path: all-releases/x86_64/ @@ -53,7 +53,7 @@ runs: mv all-releases/x86_64/witnet_toolkit release/witnet_toolkit-x86_64-unknown-linux-gnu # armv7 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: armv7-release path: all-releases/armv7/ @@ -65,7 +65,7 @@ runs: mv all-releases/armv7/witnet_toolkit release/witnet_toolkit-armv7-unknown-linux-gnueabihf # aarch64 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: aarch64-release path: all-releases/aarch64/ diff --git a/.github/workflows/bridge.yml b/.github/workflows/bridge.yml index 9276cb4b1..8e4638990 100644 --- a/.github/workflows/bridge.yml +++ b/.github/workflows/bridge.yml @@ -12,19 +12,25 @@ on: required: false default: true +env: + CARGO_TERM_COLOR: always + jobs: Bridge: runs-on: ubuntu-latest environment: tags if: ${{ github.event.workflow_run.conclusion == 'success' || inputs.force }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Protobuf run: | sudo apt install -y protobuf-compiler protoc --version + - name: Show Rust toolchain + run: rustup show + - name: Build witnet-centralized-ethereum-bridge run: | cargo build -p witnet-centralized-ethereum-bridge --release @@ -44,7 +50,7 @@ jobs: docker image ls - name: Login to Docker hub Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index d402a0e54..ba8046014 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -5,6 +5,9 @@ on: tags: - '*' +env: + CARGO_TERM_COLOR: always + jobs: #?####################################################################################################?# #? ?# @@ -14,7 +17,7 @@ jobs: aarch64: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/build-helper name: Building Docker Image with: @@ -23,7 +26,7 @@ jobs: armv7: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/build-helper name: Building Docker Image with: @@ -32,7 +35,7 @@ jobs: x86_64: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ./.github/actions/build-helper name: Building Docker Image with: @@ -59,7 +62,7 @@ jobs: runs-on: ubuntu-latest steps: # Checkout Repo - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Download Images & Build Binary - uses: ./.github/actions/build-linux @@ -68,7 +71,7 @@ jobs: target: aarch64-unknown-linux-gnu # Upload Build Releases - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: aarch64-release path: | @@ -80,7 +83,7 @@ jobs: runs-on: ubuntu-latest steps: # Checkout Repo - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Download Images & Build Binary - uses: ./.github/actions/build-linux @@ -89,7 +92,7 @@ jobs: target: armv7-unknown-linux-gnueabihf # Upload Build Releases - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: armv7-release path: | @@ -101,7 +104,7 @@ jobs: runs-on: ubuntu-latest steps: # Checkout Repo - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Download Images & Build Binary - uses: ./.github/actions/build-linux @@ -110,7 +113,7 @@ jobs: target: x86_64-unknown-linux-gnu # Upload Build Releases - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: x86_64-release path: | @@ -123,18 +126,21 @@ jobs: runs-on: macos-latest steps: # Checkout Repo - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Install Protobuf - name: Install Protobuf run: brew install protobuf + - name: Show Rust toolchain + run: rustup show + # MacOS Build - name: Building Macos Binary run: MACOSX_DEPLOYMENT_TARGET=10.14 OPENSSL_STATIC=1 OPENSSL_DIR="/usr/local/opt/openssl" cargo build --release -p witnet -p witnet_toolkit # Upload Build Releases - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: macos-release path: | @@ -147,7 +153,7 @@ jobs: runs-on: windows-latest steps: # Checkout Repo - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Install Protobuf - name: Install Protobuf @@ -163,6 +169,7 @@ jobs: vcpkg.exe install openssl:x64-windows-static vcpkg.exe integrate install + # Windows Build - name: Build Windows run: | @@ -175,7 +182,7 @@ jobs: run: LDD.exe target\release\witnet.exe | Select-String -Pattern "ssl" # Upload Build Releases - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: windows-release path: | @@ -194,7 +201,7 @@ jobs: environment: tags steps: # Checkout Repo - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Download Releases - uses: ./.github/actions/download-releases @@ -203,7 +210,7 @@ jobs: # Import GPG Key - name: Import GPG key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v5 + uses: crazy-max/ghaction-import-gpg@v6 with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.GPG_PASSPHRASE }} @@ -223,7 +230,7 @@ jobs: gpg --output SHA256SUMS.asc --default-key info@witnet.foundation --detach-sig --clearsign SHA256SUMS && rm SHA256SUMS # Artifact Final Release Files - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: final-release path: release/ @@ -235,10 +242,10 @@ jobs: contents: write steps: # Checkout Repo - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Download Release Files - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: final-release path: release/ @@ -262,10 +269,10 @@ jobs: environment: tags steps: # Checkout Repo - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Login to Docker Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/midnight.yml b/.github/workflows/midnight.yml index 9964b0fcf..c5ae086d2 100644 --- a/.github/workflows/midnight.yml +++ b/.github/workflows/midnight.yml @@ -4,16 +4,19 @@ on: schedule: - cron: '0 0 * * *' +env: + CARGO_TERM_COLOR: always + jobs: deps_audit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Cargo dependencies security audit - uses: actions-rs/audit-check@v1 + uses: rustsec/audit-check@v1.4.1 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -22,13 +25,15 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 + + - name: Install Just command runner + uses: taiki-e/install-action@just - name: Install environment dependencies run: | sudo apt-get update -y -qq sudo apt-get install -y g++-9 cmake libcurl4-openssl-dev libelf-dev libdw-dev cmake gcc binutils-dev protobuf-compiler librocksdb-dev - just || curl -LSfs https://japaric.github.io/trust/install.sh | sh -s -- --git casey/just --target x86_64-unknown-linux-musl --to ~/.cargo/bin - name: Load persistent storage run: | diff --git a/.github/workflows/push.yml b/.github/workflows/validate.yml similarity index 75% rename from .github/workflows/push.yml rename to .github/workflows/validate.yml index dc88d69c9..a2af9aff7 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/validate.yml @@ -1,6 +1,9 @@ -name: Check on every push +name: Check on every pull request -on: [push, pull_request] +on: [pull_request] + +env: + CARGO_TERM_COLOR: always jobs: build_ubuntu: @@ -24,7 +27,7 @@ jobs: LD_LIBRARY_PATH: /usr/local/lib steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 # Ensure some subcrate versions match that of the main crate - uses: ./.github/actions/versions-match @@ -48,17 +51,14 @@ jobs: with: subcrate: bridges/centralized-ethereum - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - default: true - components: rustfmt, clippy + - name: Show Rust toolchain + run: rustup show + + - name: Install Just command runner + uses: taiki-e/install-action@just - name: Formatter - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + run: just fmt - name: Install compilation system dependencies run: | @@ -74,16 +74,10 @@ jobs: sudo make install-shared - name: Clippy - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --all --all-targets --all-features -- ${{ env.CLIPPY_LINTS }} -A clippy::many-single-char-names + run: just clippy - name: Test - uses: actions-rs/cargo@v1 - with: - command: test - args: --all --verbose + run: cargo test --all --verbose # This is used to ensure that Cargo.lock is up to date - name: Check for unstaged files diff --git a/bridges/centralized-ethereum/src/actors/dr_sender/tests.rs b/bridges/centralized-ethereum/src/actors/dr_sender/tests.rs index 58ae02e43..2a314c26f 100644 --- a/bridges/centralized-ethereum/src/actors/dr_sender/tests.rs +++ b/bridges/centralized-ethereum/src/actors/dr_sender/tests.rs @@ -4,14 +4,14 @@ use witnet_data_structures::chain::{RADAggregate, RADRequest, RADRetrieve, RADTa #[test] fn deserialize_empty_dr() { // An empty data request is invalid with error 0xE0: BridgeMalformedRequest - let err = deserialize_and_validate_dr_bytes(&[], 1).unwrap_err(); + let err = deserialize_and_validate_dr_bytes(&[], 0, 1).unwrap_err(); assert_eq!(err.encode_cbor(), vec![216, 39, 129, 24, 224]); } #[test] fn deserialize_dr_not_protobuf() { // A malformed data request is invalid with error 0xE0: BridgeMalformedRequest - let err = deserialize_and_validate_dr_bytes(&[1, 2, 3, 4], 1).unwrap_err(); + let err = deserialize_and_validate_dr_bytes(&[1, 2, 3, 4], 0, 1).unwrap_err(); assert_eq!(err.encode_cbor(), vec![216, 39, 129, 24, 224]); } @@ -55,7 +55,8 @@ fn deserialize_dr_high_value() { let dro_bytes = dro.to_pb_bytes().unwrap(); // Setting the maximum allowed value to 1 nanowit below that will result in an error 0xE1: // BridgePoorIncentives - let err = deserialize_and_validate_dr_bytes(&dro_bytes, total_value - 1).unwrap_err(); + let err = + deserialize_and_validate_dr_bytes(&dro_bytes, 1_000_000_000, total_value - 1).unwrap_err(); assert_eq!(err.encode_cbor(), vec![216, 39, 129, 24, 225]); } @@ -78,7 +79,7 @@ fn deserialize_dr_collateral_one_nanowit() { assert_eq!(total_value, 20_000_000); let dro_bytes = dro.to_pb_bytes().unwrap(); - let err = deserialize_and_validate_dr_bytes(&dro_bytes, total_value).unwrap_err(); + let err = deserialize_and_validate_dr_bytes(&dro_bytes, 1, total_value).unwrap_err(); assert_eq!(err.encode_cbor(), vec![216, 39, 129, 24, 224]); } @@ -95,7 +96,7 @@ fn deserialize_dr_value_overflow() { }; let dro_bytes = dro.to_pb_bytes().unwrap(); - let err = deserialize_and_validate_dr_bytes(&dro_bytes, 1).unwrap_err(); + let err = deserialize_and_validate_dr_bytes(&dro_bytes, 0, 1).unwrap_err(); assert_eq!(err.encode_cbor(), vec![216, 39, 129, 24, 224]); } @@ -115,6 +116,7 @@ fn deserialize_and_validate_dr_bytes_wip_0022() { let dro_bytes = dro.to_pb_bytes().unwrap(); let witnet_dr_max_value_nanowits = 100_000_000_000; let err = - deserialize_and_validate_dr_bytes(&dro_bytes, witnet_dr_max_value_nanowits).unwrap_err(); + deserialize_and_validate_dr_bytes(&dro_bytes, 1_000_000_000, witnet_dr_max_value_nanowits) + .unwrap_err(); assert_eq!(err.encode_cbor(), vec![216, 39, 129, 24, 224]); } diff --git a/bridges/centralized-ethereum/src/actors/wit_poller.rs b/bridges/centralized-ethereum/src/actors/wit_poller.rs index b3525495f..d314a9e7e 100644 --- a/bridges/centralized-ethereum/src/actors/wit_poller.rs +++ b/bridges/centralized-ethereum/src/actors/wit_poller.rs @@ -1,3 +1,17 @@ +use std::{convert::TryFrom, time::Duration}; + +use actix::prelude::*; +use serde_json::json; + +use witnet_data_structures::{ + chain::{Block, ConsensusConstants, DataRequestInfo, Epoch, EpochConstants, Hash, Hashable}, + get_protocol_version_activation_epoch, get_protocol_version_period, + proto::versioning::ProtocolVersion::{V1_7, V2_0}, +}; +use witnet_net::client::tcp::{jsonrpc, JsonRpcClient}; +use witnet_node::utils::stop_system_if_panicking; +use witnet_util::timestamp::get_timestamp; + use crate::{ actors::{ dr_database::{DrDatabase, DrInfoBridge, DrState, GetAllPendingDrs, SetDrInfoBridge}, @@ -5,15 +19,6 @@ use crate::{ }, config::Config, }; -use actix::prelude::*; -use serde_json::json; -use std::{convert::TryFrom, time::Duration}; -use witnet_data_structures::chain::{ - Block, ConsensusConstants, DataRequestInfo, Epoch, EpochConstants, Hash, Hashable, -}; -use witnet_net::client::tcp::{jsonrpc, JsonRpcClient}; -use witnet_node::utils::stop_system_if_panicking; -use witnet_util::timestamp::get_timestamp; /// WitPoller actor checks periodically the state of the requests in Witnet to call DrReporter /// in case of found a tally @@ -214,7 +219,7 @@ async fn get_dr_timestamp( } }; let block_epoch = block.block_header.beacon.checkpoint; - let consensus_constants = match get_consensus_constants(witnet_client.clone()).await { + match get_consensus_constants(witnet_client.clone()).await { Ok(x) => x, Err(()) => { log::error!("Failed to get consensus constants from witnet client, will retry later"); @@ -222,8 +227,10 @@ async fn get_dr_timestamp( } }; let epoch_constants = EpochConstants { - checkpoint_zero_timestamp: consensus_constants.checkpoint_zero_timestamp, - checkpoints_period: consensus_constants.checkpoints_period, + checkpoint_zero_timestamp: get_protocol_version_activation_epoch(V1_7).into(), + checkpoints_period: get_protocol_version_period(V1_7), + checkpoint_zero_timestamp_v2: get_protocol_version_activation_epoch(V2_0).into(), + checkpoints_period_v2: get_protocol_version_period(V2_0), }; let timestamp = convert_block_epoch_to_timestamp( epoch_constants, @@ -264,6 +271,11 @@ async fn get_consensus_constants( fn convert_block_epoch_to_timestamp(epoch_constants: EpochConstants, epoch: Epoch) -> u64 { // In case of error, return timestamp 0 - u64::try_from(epoch_constants.epoch_timestamp(epoch).unwrap_or(0)) - .expect("Epoch timestamp should return a positive value") + u64::try_from( + epoch_constants + .epoch_timestamp(epoch) + .unwrap_or((0, false)) + .0, + ) + .expect("Epoch timestamp should return a positive value") } diff --git a/bridges/centralized-ethereum/src/main.rs b/bridges/centralized-ethereum/src/main.rs index c6f20d8f7..aab040692 100644 --- a/bridges/centralized-ethereum/src/main.rs +++ b/bridges/centralized-ethereum/src/main.rs @@ -119,7 +119,10 @@ fn run(callback: fn()) -> Result<(), String> { // Initialize Storage Manager let mut node_config = NodeConfig::default(); - node_config.storage.db_path = config.storage.db_path.clone(); + node_config + .storage + .db_path + .clone_from(&config.storage.db_path); storage_mngr::start_from_config(node_config); }); diff --git a/config/src/config.rs b/config/src/config.rs index d18e5e8cf..f558039f5 100644 --- a/config/src/config.rs +++ b/config/src/config.rs @@ -432,13 +432,13 @@ pub struct Tapi { /// Configuration related to protocol versions. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct Protocol { - pub v1_7: Option, - pub v1_8: Option, - pub v2_0: Option, + pub v1_7: Option<(Epoch, u16)>, + pub v1_8: Option<(Epoch, u16)>, + pub v2_0: Option<(Epoch, u16)>, } impl Protocol { - pub fn iter(&self) -> IntoIter<(ProtocolVersion, Option), 3> { + pub fn iter(&self) -> IntoIter<(ProtocolVersion, Option<(Epoch, u16)>), 3> { [ (ProtocolVersion::V1_7, self.v1_7), (ProtocolVersion::V1_8, self.v1_8), diff --git a/config/src/defaults.rs b/config/src/defaults.rs index 7d0576316..93bb42112 100644 --- a/config/src/defaults.rs +++ b/config/src/defaults.rs @@ -246,7 +246,7 @@ pub trait Defaults { /// Set the limit of retrievals per epoch to 65_535. /// This in practice equals no limit enforcement. fn mining_data_request_max_retrievals_per_epoch(&self) -> u16 { - core::u16::MAX + u16::MAX } /// Genesis block path, "./genesis_block.json" by default @@ -481,8 +481,8 @@ pub trait Defaults { 100 } - fn protocol_versions(&self) -> HashMap { - [(ProtocolVersion::V1_7, 0)].into_iter().collect() + fn protocol_versions(&self) -> HashMap { + [(ProtocolVersion::V1_7, (0, 45))].into_iter().collect() } } diff --git a/data_structures/src/capabilities.rs b/data_structures/src/capabilities.rs index c3bee6efb..b9a496133 100644 --- a/data_structures/src/capabilities.rs +++ b/data_structures/src/capabilities.rs @@ -9,6 +9,8 @@ pub enum Capability { Witnessing = 1, } +pub const ALL_CAPABILITIES: [Capability; 2] = [Capability::Mining, Capability::Witnessing]; + #[derive(Copy, Clone, Debug, Default, Deserialize, PartialEq, Serialize)] pub struct CapabilityMap where diff --git a/data_structures/src/chain/mod.rs b/data_structures/src/chain/mod.rs index 303dd595c..d72ff5c52 100644 --- a/data_structures/src/chain/mod.rs +++ b/data_structures/src/chain/mod.rs @@ -426,8 +426,10 @@ pub struct BlockTransactions { /// A list of signed tally transactions pub tally_txns: Vec, /// A list of signed stake transactions + #[serde(default)] pub stake_txns: Vec, /// A list of signed unstake transactions + #[serde(default)] pub unstake_txns: Vec, } @@ -735,19 +737,21 @@ pub struct BlockHeader { pub struct BlockMerkleRoots { /// A 256-bit hash based on the mint transaction committed to this block pub mint_hash: Hash, - /// A 256-bit hash based on all of the value transfer transactions committed to this block + /// A 256-bit hash based on all the value transfer transactions committed to this block pub vt_hash_merkle_root: Hash, - /// A 256-bit hash based on all of the data request transactions committed to this block + /// A 256-bit hash based on all the data request transactions committed to this block pub dr_hash_merkle_root: Hash, - /// A 256-bit hash based on all of the commit transactions committed to this block + /// A 256-bit hash based on all the commit transactions committed to this block pub commit_hash_merkle_root: Hash, - /// A 256-bit hash based on all of the reveal transactions committed to this block + /// A 256-bit hash based on all the reveal transactions committed to this block pub reveal_hash_merkle_root: Hash, - /// A 256-bit hash based on all of the tally transactions committed to this block + /// A 256-bit hash based on all the tally transactions committed to this block pub tally_hash_merkle_root: Hash, - /// A 256-bit hash based on all of the stake transactions committed to this block + /// A 256-bit hash based on all the stake transactions committed to this block + #[serde(default)] pub stake_hash_merkle_root: Hash, - /// A 256-bit hash based on all of the unstake transactions committed to this block + /// A 256-bit hash based on all the unstake transactions committed to this block + #[serde(default)] pub unstake_hash_merkle_root: Hash, } @@ -1642,7 +1646,7 @@ impl KeyedSignature { compact: &[u8], message: &[u8], ) -> Result { - let recid = RecoveryId::from_i32(compact[0] as i32) + let recid = RecoveryId::from_i32(i32::from(compact[0])) .map_err(|e| Secp256k1ConversionError::Secp256k1 { inner: e })?; let recoverable = RecoverableSignature::from_compact(&compact[1..], recid) .map_err(|e| Secp256k1ConversionError::Secp256k1 { inner: e })?; @@ -3603,6 +3607,8 @@ pub struct DataRequestInfo { pub current_reveal_round: u16, /// Current stage, or None if finished pub current_stage: Option, + /// Too many witnesses requested + pub too_many_witnesses: bool, } impl Default for DataRequestInfo { @@ -3616,6 +3622,7 @@ impl Default for DataRequestInfo { current_commit_round: 0, current_reveal_round: 0, current_stage: Some(DataRequestStage::COMMIT), + too_many_witnesses: false, } } } @@ -3707,14 +3714,20 @@ impl DataRequestState { } } + pub fn set_too_many_witnesses(&mut self) { + self.info.too_many_witnesses = true; + } + /// Advance to the next stage. /// Since the data requests are updated by looking at the transactions from a valid block, /// the only issue would be that there were no commits in that block. - pub fn update_stage(&mut self, extra_rounds: u16) { + pub fn update_stage(&mut self, extra_rounds: u16, too_many_witnesses: bool) { self.stage = match self.stage { DataRequestStage::COMMIT => { if self.info.commits.is_empty() { - if self.info.current_commit_round <= extra_rounds { + if too_many_witnesses { + DataRequestStage::TALLY + } else if self.info.current_commit_round <= extra_rounds { self.info.current_commit_round += 1; DataRequestStage::COMMIT } else { @@ -4399,11 +4412,11 @@ where total_rep * n / remaining_rep }; - return u32::try_from(factor).unwrap_or(u32::max_value()); + return u32::try_from(factor).unwrap_or(u32::MAX); } } - u32::max_value() + u32::MAX } /// Witnessing Acts Counter @@ -4469,6 +4482,12 @@ pub struct EpochConstants { /// Period between checkpoints, in seconds pub checkpoints_period: u16, + + /// Timestamp of checkpoint (in seconds) when v2 started) + pub checkpoint_zero_timestamp_v2: i64, + + /// Period between checkpoints, in seconds, starting at v2 + pub checkpoints_period_v2: u16, } // This default is only used for tests @@ -4478,6 +4497,10 @@ impl Default for EpochConstants { checkpoint_zero_timestamp: 0, // This cannot be 0 because we would divide by zero checkpoints_period: 1, + // Variables for v2 + checkpoint_zero_timestamp_v2: i64::MAX, + // This cannot be 0 because we would divide by zero + checkpoints_period_v2: 1, } } } @@ -4485,41 +4508,100 @@ impl Default for EpochConstants { impl EpochConstants { /// Calculate the last checkpoint (current epoch) at the supplied timestamp pub fn epoch_at(&self, timestamp: i64) -> Result { - let zero = self.checkpoint_zero_timestamp; - let period = self.checkpoints_period; - let elapsed = timestamp - zero; + if timestamp >= self.checkpoint_zero_timestamp_v2 { + let epochs_pre_v2 = match Epoch::try_from( + self.checkpoint_zero_timestamp_v2 - self.checkpoint_zero_timestamp, + ) { + Ok(epoch) => epoch / Epoch::from(self.checkpoints_period), + Err(_) => { + return Err(EpochCalculationError::CheckpointZeroInTheFuture( + self.checkpoint_zero_timestamp, + )); + } + }; + let epochs_post_v2 = + match Epoch::try_from(timestamp - self.checkpoint_zero_timestamp_v2) { + Ok(epoch) => epoch / Epoch::from(self.checkpoints_period_v2), + Err(_) => { + return Err(EpochCalculationError::CheckpointZeroInTheFuture( + self.checkpoint_zero_timestamp, + )); + } + }; - Epoch::try_from(elapsed) - .map(|epoch| epoch / Epoch::from(period)) - .map_err(|_| EpochCalculationError::CheckpointZeroInTheFuture(zero)) + Ok(epochs_pre_v2 + epochs_post_v2) + } else { + Epoch::try_from(timestamp - self.checkpoint_zero_timestamp) + .map(|epoch| epoch / Epoch::from(self.checkpoints_period)) + .map_err(|_| { + EpochCalculationError::CheckpointZeroInTheFuture(self.checkpoint_zero_timestamp) + }) + } } /// Calculate the timestamp for a checkpoint (the start of an epoch) - pub fn epoch_timestamp(&self, epoch: Epoch) -> Result { - let zero = self.checkpoint_zero_timestamp; - let period = self.checkpoints_period; - - Epoch::from(period) + /// + /// The return type is a tuple where the first item is the UNIX timestamp of corresponding the + /// start of the queried protocol epoch, and the second item is a flag that tells if the epoch + /// will happen after the 2_x hard fork. + pub fn epoch_timestamp(&self, epoch: Epoch) -> Result<(i64, bool), EpochCalculationError> { + let epoch_timestamp = Epoch::from(self.checkpoints_period) .checked_mul(epoch) - .filter(|&x| x <= Epoch::max_value() as Epoch) + .filter(|&x| x <= Epoch::MAX as Epoch) .map(i64::from) - .and_then(|x| x.checked_add(zero)) - .ok_or(EpochCalculationError::Overflow) + .and_then(|x| x.checked_add(self.checkpoint_zero_timestamp)) + .ok_or(EpochCalculationError::Overflow); + + let epoch_timestamp = match epoch_timestamp { + Ok(timestamp) => timestamp, + Err(error) => { + return Err(error); + } + }; + + let mut in_v2 = false; + let timestamp = if epoch_timestamp >= self.checkpoint_zero_timestamp_v2 { + in_v2 = true; + + let epochs_pre_v2 = ((self.checkpoint_zero_timestamp_v2 + - self.checkpoint_zero_timestamp) + / self.checkpoints_period as i64) as u32; + + self.checkpoint_zero_timestamp_v2 + + i64::from((epoch - epochs_pre_v2) * Epoch::from(self.checkpoints_period_v2)) + } else { + epoch_timestamp + }; + + Ok((timestamp, in_v2)) } /// Calculate the timestamp for when block mining should happen. pub fn block_mining_timestamp(&self, epoch: Epoch) -> Result { - let start = self.epoch_timestamp(epoch)?; + let (start, in_v2) = self.epoch_timestamp(epoch)?; // TODO: analyze when should nodes start mining a block // Start mining at the midpoint of the epoch - let seconds_before_next_epoch = self.checkpoints_period / 2; + let checkpoints_period = if in_v2 { + self.checkpoints_period_v2 + } else { + self.checkpoints_period + }; + + let seconds_before_next_epoch = checkpoints_period / 2; start - .checked_add(i64::from( - self.checkpoints_period - seconds_before_next_epoch, - )) + .checked_add(i64::from(checkpoints_period - seconds_before_next_epoch)) .ok_or(EpochCalculationError::Overflow) } + + pub fn get_epoch_period(&self, epoch: Epoch) -> Result { + let (_, in_v2) = self.epoch_timestamp(epoch)?; + if in_v2 { + Ok(self.checkpoints_period_v2) + } else { + Ok(self.checkpoints_period) + } + } } #[derive(Debug, PartialEq, Eq)] @@ -6263,7 +6345,7 @@ mod tests { fn rep_threshold_zero() { let rep_engine = ReputationEngine::new(1000); - assert_eq!(rep_engine.threshold_factor(1), u32::max_value()); + assert_eq!(rep_engine.threshold_factor(1), u32::MAX); } #[test] @@ -6369,7 +6451,7 @@ mod tests { .unwrap(); rep_engine.ars_mut().push_activity(vec![id2]); - assert_eq!(rep_engine.threshold_factor(10), u32::max_value()); + assert_eq!(rep_engine.threshold_factor(10), u32::MAX); } #[test] @@ -6436,7 +6518,7 @@ mod tests { assert_eq!(rep_engine.threshold_factor(6), 15); assert_eq!(rep_engine.threshold_factor(7), 50); assert_eq!(rep_engine.threshold_factor(8), 100); - assert_eq!(rep_engine.threshold_factor(9), u32::max_value()); + assert_eq!(rep_engine.threshold_factor(9), u32::MAX); } #[test] diff --git a/data_structures/src/data_request.rs b/data_structures/src/data_request.rs index f7d375116..4e7421a88 100644 --- a/data_structures/src/data_request.rs +++ b/data_structures/src/data_request.rs @@ -5,12 +5,14 @@ use std::{ use serde::{Deserialize, Serialize}; +use crate::proto::versioning::ProtocolVersion; use crate::{ chain::{ tapi::ActiveWips, DataRequestInfo, DataRequestOutput, DataRequestStage, DataRequestState, Epoch, Hash, Hashable, PublicKeyHash, ValueTransferOutput, }, error::{DataRequestError, TransactionError}, + get_protocol_version, radon_report::{RadonReport, Stage, TypeLike}, transaction::{CommitTransaction, DRTransaction, RevealTransaction, TallyTransaction}, }; @@ -242,7 +244,12 @@ impl DataRequestPool { /// for reveal (the node should send a reveal transaction). /// This function must be called after `add_data_requests_from_block`, in order to update /// the stage of all the data requests. - pub fn update_data_request_stages(&mut self) -> Vec { + pub fn update_data_request_stages( + &mut self, + validator_count: Option, + epoch: Option, + ) -> Vec { + let validator_count = validator_count.unwrap_or(crate::DEFAULT_VALIDATOR_COUNT_FOR_TESTS); let waiting_for_reveal = &mut self.waiting_for_reveal; let data_requests_by_epoch = &mut self.data_requests_by_epoch; let extra_rounds = self.extra_rounds; @@ -252,7 +259,12 @@ impl DataRequestPool { .filter_map(|(dr_pointer, dr_state)| { // We can notify the user that a data request from "my_claims" is available // for reveal. - dr_state.update_stage(extra_rounds); + let too_many_witnesses = data_request_has_too_many_witnesses( + &dr_state.data_request, + validator_count, + epoch, + ); + dr_state.update_stage(extra_rounds, too_many_witnesses); match dr_state.stage { DataRequestStage::REVEAL => { // When a data request changes from commit stage to reveal stage, it should @@ -368,6 +380,14 @@ impl DataRequestPool { self.data_request_pool.get(dr_pointer) } + /// Get the detailed state of a data request. + pub fn data_request_state_mutable( + &mut self, + dr_pointer: &Hash, + ) -> Option<&mut DataRequestState> { + self.data_request_pool.get_mut(dr_pointer) + } + /// Get the data request info of the finished data requests, to be persisted to the storage pub fn finished_data_requests(&mut self) -> Vec { std::mem::take(&mut self.to_be_stored) @@ -526,6 +546,19 @@ pub fn calculate_reward_collateral_ratio( saturating_div_ceil(dr_collateral, witness_reward) } +/// Function to check if a data request requires too many witnesses +pub fn data_request_has_too_many_witnesses( + dr_output: &DataRequestOutput, + validator_count: usize, + epoch: Option, +) -> bool { + if get_protocol_version(epoch) < ProtocolVersion::V2_0 { + false + } else { + usize::from(dr_output.witnesses) > validator_count / 4 + } +} + /// Saturating version of `u64::div_ceil`. /// /// Calculates the quotient of `lhs` and `rhs`, rounding the result towards positive infinity. @@ -761,7 +794,7 @@ mod tests { ); assert!(p.to_be_stored.is_empty()); - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); (epoch, fake_block_hash, p, dr_transaction.hash()) } @@ -796,7 +829,7 @@ mod tests { ); assert!(p.to_be_stored.is_empty()); - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); (epoch, fake_block_hash, p, dr_transaction.hash()) } @@ -831,7 +864,7 @@ mod tests { ); assert!(p.to_be_stored.is_empty()); - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); (epoch, fake_block_hash, p, dr_transaction.hash()) } @@ -873,7 +906,7 @@ mod tests { assert!(p.data_requests_by_epoch[&epoch].contains(&dr_pointer)); // Update stages - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); // Now in reveal stage assert_eq!( @@ -920,7 +953,7 @@ mod tests { ); // Update stages - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); // Now in tally stage assert_eq!( @@ -945,7 +978,7 @@ mod tests { assert_eq!(p.data_request_state(&dr_pointer), None); // Update stages - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); assert_eq!(p.to_be_stored.len(), 1); assert_eq!( @@ -1015,7 +1048,7 @@ mod tests { .unwrap(); // Update stages - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); // Now in reveal stage 1 assert_eq!( @@ -1036,7 +1069,7 @@ mod tests { .unwrap(); // Update stages - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); // Now in reveal stage 2 assert_eq!( p.data_request_pool[&dr_pointer].stage, @@ -1048,7 +1081,7 @@ mod tests { ); // Update stages - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); // Now in reveal stage 3 assert_eq!( p.data_request_pool[&dr_pointer].stage, @@ -1060,7 +1093,7 @@ mod tests { ); // Update stages - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); // Now in tally stage assert_eq!( p.data_request_pool[&dr_pointer].stage, @@ -1106,7 +1139,7 @@ mod tests { .unwrap(); // Update stages - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); // Now in reveal stage 1 assert_eq!( @@ -1127,7 +1160,7 @@ mod tests { .unwrap(); // Update stages - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); // Now in reveal stage 2 assert_eq!( p.data_request_pool[&dr_pointer].stage, @@ -1149,7 +1182,7 @@ mod tests { .unwrap(); // Update stages - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); // Now in tally stage assert_eq!( p.data_request_pool[&dr_pointer].stage, @@ -1192,7 +1225,7 @@ mod tests { .unwrap(); // Update stages - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); // Now in reveal stage 1 assert_eq!( p.data_request_pool[&dr_pointer].stage, @@ -1204,7 +1237,7 @@ mod tests { ); // Update stages - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); // Now in reveal stage 2 assert_eq!( p.data_request_pool[&dr_pointer].stage, @@ -1216,7 +1249,7 @@ mod tests { ); // Update stages - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); // Now in reveal stage 3 assert_eq!( p.data_request_pool[&dr_pointer].stage, @@ -1228,7 +1261,7 @@ mod tests { ); // Update stages - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); // Now in tally stage, after 3 reveal stages with no reveals assert_eq!( p.data_request_pool[&dr_pointer].stage, @@ -1284,7 +1317,7 @@ mod tests { ); // Update stages. This will return our reveal transaction - let my_reveals = p.update_data_request_stages(); + let my_reveals = p.update_data_request_stages(None, None); assert_eq!(my_reveals.len(), 1); let my_reveal = &my_reveals[0]; assert_eq!(my_reveal, &reveal_transaction); @@ -1325,7 +1358,7 @@ mod tests { // Since extra_commit_rounds = 0, updating again in commit stage will // move the data request to tally stage - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); // Now in tally stage assert_eq!( @@ -1352,7 +1385,7 @@ mod tests { assert_eq!(p.data_request_pool[&dr_pointer].backup_witnesses(), 1); // Second commitment round - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); assert_eq!( p.data_request_pool[&dr_pointer].stage, DataRequestStage::COMMIT @@ -1364,7 +1397,7 @@ mod tests { assert_eq!(p.data_request_pool[&dr_pointer].backup_witnesses(), 2); // Third commitment round - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); assert_eq!( p.data_request_pool[&dr_pointer].stage, DataRequestStage::COMMIT @@ -1377,7 +1410,7 @@ mod tests { // Since extra_commit_rounds = 1, updating again in commit stage will // move the data request to tally stage - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); // Now in tally stage assert_eq!( @@ -1403,7 +1436,7 @@ mod tests { ); assert_eq!(p.data_request_pool[&dr_pointer].backup_witnesses(), 1); - assert!(p.update_data_request_stages().is_empty()); + assert!(p.update_data_request_stages(None, None).is_empty()); // Add commit and update stage let (_fake_block_hash, p, dr_pointer) = diff --git a/data_structures/src/error.rs b/data_structures/src/error.rs index 8b4c994d5..4e00ba991 100644 --- a/data_structures/src/error.rs +++ b/data_structures/src/error.rs @@ -362,6 +362,10 @@ pub enum BlockError { mint_epoch: Epoch, block_epoch: Epoch, }, + #[fail( + display = "Mint transaction should be set to default after the activation of Witnet 2.0" + )] + InvalidMintTransaction, #[fail(display = "The block has an invalid PoE")] NotValidPoe, #[fail( diff --git a/data_structures/src/lib.rs b/data_structures/src/lib.rs index 67365e300..825fb5813 100644 --- a/data_structures/src/lib.rs +++ b/data_structures/src/lib.rs @@ -23,6 +23,8 @@ use crate::{ proto::versioning::ProtocolVersion, }; +pub const DEFAULT_VALIDATOR_COUNT_FOR_TESTS: usize = 1000; + /// Module containing functions to generate Witnet's protocol messages pub mod builders; @@ -137,14 +139,18 @@ pub fn get_protocol_version(epoch: Option) -> ProtocolVersion { } /// Let the protocol versions controller know about the a protocol version, and its activation epoch. -pub fn register_protocol_version(protocol_version: ProtocolVersion, epoch: Epoch) { +pub fn register_protocol_version( + protocol_version: ProtocolVersion, + epoch: Epoch, + checkpoint_period: u16, +) { log::debug!( "Registering protocol version {protocol_version}, which enters into force at epoch {epoch}" ); // This unwrap is safe as long as the lock is not poisoned. // The lock can only become poisoned when a writer panics. let mut protocol_info = PROTOCOL.write().unwrap(); - protocol_info.register(epoch, protocol_version); + protocol_info.register(epoch, protocol_version, checkpoint_period); } /// Set the protocol version that we are running. @@ -161,6 +167,23 @@ pub fn refresh_protocol_version(current_epoch: Epoch) { set_protocol_version(current_version) } +pub fn get_protocol_version_activation_epoch(protocol_version: ProtocolVersion) -> Epoch { + // This unwrap is safe as long as the lock is not poisoned. + // The lock can only become poisoned when a writer panics. + let protocol = PROTOCOL.write().unwrap(); + protocol.all_versions.get_activation_epoch(protocol_version) +} + +pub fn get_protocol_version_period(protocol_version: ProtocolVersion) -> u16 { + // This unwrap is safe as long as the lock is not poisoned. + // The lock can only become poisoned when a writer panics. + let protocol = PROTOCOL.write().unwrap(); + match protocol.all_checkpoints_periods.get(&protocol_version) { + Some(period) => *period, + None => u16::MAX, + } +} + #[cfg(test)] mod tests { use super::*; @@ -181,9 +204,9 @@ mod tests { assert_eq!(version, ProtocolVersion::V1_7); // Register the different protocol versions - register_protocol_version(ProtocolVersion::V1_7, 100); - register_protocol_version(ProtocolVersion::V1_8, 200); - register_protocol_version(ProtocolVersion::V2_0, 300); + register_protocol_version(ProtocolVersion::V1_7, 100, 45); + register_protocol_version(ProtocolVersion::V1_8, 200, 45); + register_protocol_version(ProtocolVersion::V2_0, 300, 20); // The initial protocol version should be the default one let version = get_protocol_version(Some(0)); diff --git a/data_structures/src/proto/mod.rs b/data_structures/src/proto/mod.rs index 2e7ba0e2c..d6f03d0d1 100644 --- a/data_structures/src/proto/mod.rs +++ b/data_structures/src/proto/mod.rs @@ -257,7 +257,7 @@ impl ProtobufConvert for u8 { } fn from_pb(pb: Self::ProtoStruct) -> Result { ensure!( - pb <= Self::ProtoStruct::from(Self::max_value()), + pb <= Self::ProtoStruct::from(Self::MAX), "Integer out of range" ); Ok(pb as Self) @@ -272,7 +272,7 @@ impl ProtobufConvert for i8 { } fn from_pb(pb: Self::ProtoStruct) -> Result { ensure!( - pb <= Self::ProtoStruct::from(Self::max_value()), + pb <= Self::ProtoStruct::from(Self::MAX), "Integer out of range" ); Ok(Self::try_from(pb)?) @@ -286,7 +286,7 @@ impl ProtobufConvert for u16 { } fn from_pb(pb: Self::ProtoStruct) -> Result { ensure!( - pb <= Self::ProtoStruct::from(Self::max_value()), + pb <= Self::ProtoStruct::from(Self::MAX), "Integer out of range" ); Ok(Self::try_from(pb)?) @@ -300,7 +300,7 @@ impl ProtobufConvert for i16 { } fn from_pb(pb: Self::ProtoStruct) -> Result { ensure!( - pb <= Self::ProtoStruct::from(Self::max_value()), + pb <= Self::ProtoStruct::from(Self::MAX), "Integer out of range" ); Ok(Self::try_from(pb)?) diff --git a/data_structures/src/proto/versioning.rs b/data_structures/src/proto/versioning.rs index 244c52037..545e375a6 100644 --- a/data_structures/src/proto/versioning.rs +++ b/data_structures/src/proto/versioning.rs @@ -1,7 +1,11 @@ +use std::{ + cmp::Ordering, + collections::{BTreeMap, HashMap}, +}; + use failure::{Error, Fail}; use protobuf::Message as _; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, HashMap}; use strum_macros::{Display, EnumString}; use crate::chain::Epoch; @@ -27,11 +31,14 @@ use crate::{ pub struct ProtocolInfo { pub current_version: ProtocolVersion, pub all_versions: VersionsMap, + pub all_checkpoints_periods: HashMap, } impl ProtocolInfo { - pub fn register(&mut self, epoch: Epoch, version: ProtocolVersion) { - self.all_versions.register(epoch, version) + pub fn register(&mut self, epoch: Epoch, version: ProtocolVersion, checkpoint_period: u16) { + self.all_versions.register(epoch, version); + self.all_checkpoints_periods + .insert(version, checkpoint_period); } } @@ -56,8 +63,19 @@ impl VersionsMap { .copied() .unwrap_or_default() } + + pub fn get_activation_epoch(&self, version: ProtocolVersion) -> Epoch { + match self.efv.get(&version) { + Some(epoch) => *epoch, + None => Epoch::MAX, + } + } } +/// An enumeration of different protocol versions. +/// +/// IMPORTANT NOTE: when adding new versions here in the future, make sure to also add them in +/// `impl PartialOrd for ProtocolVersion`. #[derive( Clone, Copy, Debug, Default, Deserialize, Display, EnumString, Eq, Hash, PartialEq, Serialize, )] @@ -78,6 +96,31 @@ impl ProtocolVersion { } } +impl PartialOrd for ProtocolVersion { + /// Implement comparisons for protocol versions so that expressions like `< V2_0` can be used. + /// + /// IMPORTANT NOTE: all future versions need to be added here. + fn partial_cmp(&self, other: &Self) -> Option { + use ProtocolVersion::*; + + Some(match (self, other) { + // Control equality first + (x, y) if x == y => Ordering::Equal, + // V1_7 is the lowest version + (V1_7, _) => Ordering::Less, + // V2_0 is the highest version + (V2_0, _) => Ordering::Greater, + // Versions that are not the lowest or the highest need explicit comparisons + (V1_8, V1_7) => Ordering::Greater, + (V1_8, V2_0) => Ordering::Less, + // the compiler doesn't know, but this is actually unreachable if you think about it + _ => { + unreachable!() + } + }) + } +} + pub trait Versioned: ProtobufConvert { type LegacyType: protobuf::Message; diff --git a/data_structures/src/radon_error.rs b/data_structures/src/radon_error.rs index 8f43df30a..fe6f97438 100644 --- a/data_structures/src/radon_error.rs +++ b/data_structures/src/radon_error.rs @@ -50,6 +50,8 @@ pub enum RadonErrors { InsufficientCommits = 0x52, /// Generic error during tally execution TallyExecution = 0x53, + /// Insufficient stakers to resolve the data request + TooManyWitnesses = 0x54, /// Invalid reveal serialization (malformed reveals are converted to this value) MalformedReveal = 0x60, /// Failed to encode reveal diff --git a/data_structures/src/staking/errors.rs b/data_structures/src/staking/errors.rs index 03ac013a8..ba8313b87 100644 --- a/data_structures/src/staking/errors.rs +++ b/data_structures/src/staking/errors.rs @@ -86,6 +86,15 @@ where /// A withdrawer address. withdrawer: Address, }, + /// Tried to add stake to a validator with a different withdrawer than the one initially set. + #[fail( + display = "Validator {} already has a different withdrawer set", + validator + )] + DifferentWithdrawer { + /// A validator address. + validator: Address, + }, /// Tried to query for a stake entry without providing a validator or a withdrawer address. #[fail( display = "Tried to query a stake entry without providing a validator or a withdrawer address" diff --git a/data_structures/src/staking/helpers.rs b/data_structures/src/staking/helpers.rs index c32ea04cf..ba2afc8dd 100644 --- a/data_structures/src/staking/helpers.rs +++ b/data_structures/src/staking/helpers.rs @@ -1,12 +1,24 @@ -use std::fmt::{Debug, Display, Formatter}; -use std::{rc::Rc, str::FromStr, sync::RwLock}; +use std::{ + collections::BTreeMap, + fmt::{Debug, Display, Formatter}, + iter::Sum, + marker::PhantomData, + ops::{Add, Div, Mul, Rem, Sub}, + rc::Rc, + str::FromStr, + sync::RwLock, +}; use failure::Error; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use num_traits::{Saturating, Zero}; +use serde::{ + de::{DeserializeOwned, MapAccess, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; -use crate::{chain::PublicKeyHash, proto::ProtobufConvert}; - -use crate::staking::prelude::*; +use crate::{ + chain::PublicKeyHash, proto::ProtobufConvert, staking::prelude::*, wit::PrecisionLoss, +}; /// Just a type alias for consistency of using the same data type to represent power. pub type Power = u64; @@ -14,6 +26,14 @@ pub type Power = u64; /// The resulting type for all the fallible functions in this module. pub type StakesResult = Result>; +/// The resulting type for functions in this module that return a single stake entry. +pub type StakesEntryResult = + StakesResult, Address, Coins, Epoch>; + +/// The resulting type for functions in this module that return a vector of stake entries. +pub type StakesVecResult = + StakesResult>, Address, Coins, Epoch>; + /// Newtype for a reference-counted and read-write-locked instance of `Stake`. /// /// This newtype is needed for implementing `PartialEq` manually on the locked data, which cannot be done directly @@ -180,3 +200,148 @@ pub enum CensusStrategy { /// power. Evenly(usize) = 3, } + +impl Serialize for Stakes +where + Address: Default + Ord, + Coins: Ord, + Epoch: Default, + StakeKey
: Serialize, + SyncStake: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.by_key.serialize(serializer) + } +} + +impl<'de, Address, Coins, Epoch, Power> Deserialize<'de> for Stakes +where + Address: Clone + Debug + Default + DeserializeOwned + Display + Ord + Send + Sync + 'static, + Coins: Copy + + Debug + + Default + + Display + + DeserializeOwned + + From + + Mul + + Mul + + Ord + + PrecisionLoss + + Send + + Sub + + Sum + + Sync + + Zero + + Div + + Rem, + Epoch: Copy + + Debug + + Default + + DeserializeOwned + + Display + + From + + Saturating + + Send + + Sub + + Sync + + Add + + Div, + Power: + Add + Copy + Default + DeserializeOwned + Div + Ord + Sum, + u64: From + From, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(StakesVisitor::::default()) + } +} + +#[derive(Default)] +struct StakesVisitor { + phantom_address: PhantomData
, + phantom_coins: PhantomData, + phantom_epoch: PhantomData, + phantom_power: PhantomData, +} + +impl<'de, Address, Coins, Epoch, Power> Visitor<'de> for StakesVisitor +where + Address: Clone + Debug + Default + Deserialize<'de> + Display + Ord + Send + Sync + 'static, + Coins: Copy + + Debug + + Default + + Deserialize<'de> + + Display + + From + + Mul + + Mul + + Ord + + PrecisionLoss + + Send + + Sub + + Sum + + Sync + + Zero + + Div + + Rem, + Epoch: Copy + + Debug + + Default + + Deserialize<'de> + + Display + + From + + Send + + Saturating + + Sub + + Sync + + Add + + Div, + Power: Add + Copy + Default + Div + Ord + Sum, + u64: From + From, +{ + type Value = Stakes; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + formatter.write_str("Stakes") + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut entries = + , SyncStake>>::new(); + + while let Some((key, value)) = map.next_entry()? { + entries.insert(key, value); + } + + let stakes = Stakes::with_entries(entries); + + Ok(stakes) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_cloning_assumptions() { + let a = + SyncStake::::from(Stake::from_parts(123, Default::default())); + let b = a.clone(); + + { + let mut value = b.value.write().unwrap(); + value.coins = 456; + } + + assert_eq!(a.value.read().unwrap().coins, 456); + } +} diff --git a/data_structures/src/staking/stake.rs b/data_structures/src/staking/stake.rs index 3f58e40be..4652f977f 100644 --- a/data_structures/src/staking/stake.rs +++ b/data_structures/src/staking/stake.rs @@ -1,9 +1,11 @@ +use std::fmt::{Debug, Display}; use std::{marker::PhantomData, ops::*}; use serde::{Deserialize, Serialize}; +use crate::wit::{PrecisionLoss, WIT_DECIMAL_PLACES}; + use super::prelude::*; -use std::fmt::{Debug, Display}; /// A data structure that keeps track of a staker's staked coins and the epochs for different /// capabilities. @@ -36,7 +38,8 @@ where + Debug + Display + Send - + Sync, + + Sync + + PrecisionLoss, Epoch: Copy + Default + num_traits::Saturating @@ -73,18 +76,22 @@ where } let coins_before = self.coins; - let epoch_before = self.epochs.get(Capability::Mining); - - let product_before = coins_before * epoch_before; - let product_added = coins * epoch; - let coins_after = coins_before + coins; - let epoch_after = Epoch::from( - (u64::from(product_before + product_added) / u64::from(coins_after)) as u32, - ); - self.coins = coins_after; - self.epochs.update_all(epoch_after); + + for capability in ALL_CAPABILITIES { + let epoch_before = self.epochs.get(capability); + let product_before = coins_before.lose_precision(WIT_DECIMAL_PLACES) * epoch_before; + let product_added = coins.lose_precision(WIT_DECIMAL_PLACES) * epoch; + + #[allow(clippy::cast_possible_truncation)] + let epoch_after = Epoch::from( + (u64::from(product_before + product_added) + / u64::from(coins_after.lose_precision(WIT_DECIMAL_PLACES))) + as u32, + ); + self.epochs.update(capability, epoch_after); + } Ok(coins_after) } @@ -104,7 +111,8 @@ where /// Derives the power of an identity in the network on a certain epoch from an entry. Most /// normally, the epoch is the current epoch. pub fn power(&self, capability: Capability, current_epoch: Epoch) -> Power { - self.coins * (current_epoch.saturating_sub(self.epochs.get(capability))) + self.coins.lose_precision(WIT_DECIMAL_PLACES) + * (current_epoch.saturating_sub(self.epochs.get(capability))) } /// Remove a certain amount of staked coins. diff --git a/data_structures/src/staking/stakes.rs b/data_structures/src/staking/stakes.rs index 4f8ad67cd..2f7d0b174 100644 --- a/data_structures/src/staking/stakes.rs +++ b/data_structures/src/staking/stakes.rs @@ -1,17 +1,19 @@ use std::{ collections::{btree_map::Entry, BTreeMap}, fmt::{Debug, Display}, - ops::{Add, Div, Mul, Sub}, + iter::Sum, + ops::{Add, Div, Mul, Rem, Sub}, }; use itertools::Itertools; +use num_traits::Saturating; use serde::{Deserialize, Serialize}; use crate::{ chain::PublicKeyHash, get_environment, transaction::{StakeTransaction, UnstakeTransaction}, - wit::Wit, + wit::{PrecisionLoss, Wit}, }; use super::prelude::*; @@ -61,7 +63,7 @@ where /// /// This structure holds indexes of stake entries. Because the entries themselves are reference /// counted and write-locked, we can have as many indexes here as we need at a negligible cost. -#[derive(Clone, Debug, Deserialize, Default, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, PartialEq)] pub struct Stakes where Address: Default + Ord, @@ -69,7 +71,7 @@ where Epoch: Default, { /// A listing of all the stake entries, indexed by their stake key. - by_key: BTreeMap, SyncStake>, + pub(crate) by_key: BTreeMap, SyncStake>, /// A listing of all the stake entries, indexed by validator. by_validator: BTreeMap>>, /// A listing of all the stake entries, indexed by withdrawer. @@ -84,13 +86,12 @@ where /// the minimum through TAPI or whatever. Maybe what we can do is set a skip directive for the Serialize macro so /// it never gets persisted and rather always read from constants, or hide the field and the related method /// behind a #[test] thing. - #[serde(skip)] minimum_stakeable: Option, } impl Stakes where - Address: Default + Send + Sync + Display, + Address: Clone + Debug + Default + Ord + Send + Sync + Display + 'static, Coins: Copy + Default + Ord @@ -104,21 +105,26 @@ where + Debug + Send + Sync - + Display, - Address: Clone + Ord + 'static + Debug, + + Display + + Sum + + Div + + Rem + + PrecisionLoss, Epoch: Copy + Default - + num_traits::Saturating + + Saturating + Sub + From + Debug + Display + Send - + Sync, - Power: Copy + Default + Ord + Add + Div, + + Sync + + Add + + Div, + Power: Copy + Default + Ord + Add + Div + Sum, u64: From + From, { - /// Register a certain amount of additional stake for a certain address and epoch. + /// Register a certain amount of additional stake for a certain address, capability and epoch. pub fn add_stake( &mut self, key: ISK, @@ -131,6 +137,7 @@ where let key = key.into(); // Find or create a matching stake entry + let stake_found = self.by_key.contains_key(&key); let stake = self.by_key.entry(key.clone()).or_default(); // Actually increase the number of coins @@ -139,24 +146,16 @@ where .write()? .add_stake(coins, epoch, self.minimum_stakeable)?; - // Update the position of the staker in the `by_coins` index - // If this staker was not indexed by coins, this will index it now - let coins_and_addresses = CoinsAndAddresses { - coins, - addresses: key, - }; - self.by_coins.remove(&coins_and_addresses); - self.by_coins - .insert(coins_and_addresses.clone(), stake.clone()); - - let validator_key = coins_and_addresses.clone().addresses.validator; - self.by_validator.remove(&validator_key); - self.by_validator.insert(validator_key, vec![stake.clone()]); - - let withdrawer_key = coins_and_addresses.addresses.withdrawer; - self.by_withdrawer.remove(&withdrawer_key); - self.by_withdrawer - .insert(withdrawer_key, vec![stake.clone()]); + // Update all indexes if needed + index_coins(&mut self.by_coins, key.clone(), stake.clone()); + if !stake_found { + index_addresses( + &mut self.by_validator, + &mut self.by_withdrawer, + key, + stake.clone(), + ); + } Ok(stake.value.read()?.clone()) } @@ -199,22 +198,26 @@ where /// Tells what is the power of an identity in the network on a certain epoch. pub fn query_power( &self, - key: ISK, + validator: ISK, capability: Capability, epoch: Epoch, ) -> StakesResult where - ISK: Into>, + ISK: Into
, { - let key = key.into(); + let validator = validator.into(); - Ok(self - .by_key - .get(&key) - .ok_or(StakesError::EntryNotFound { key })? - .value - .read()? - .power(capability, epoch)) + let validator = self + .by_validator + .get(&validator) + .ok_or(StakesError::ValidatorNotFound { validator })?; + + Ok(validator + .iter() + .map(|stake| stake.value.read().unwrap().power(capability, epoch)) + .collect::>() + .into_iter() + .sum()) } /// For a given capability, obtain the full list of stakers ordered by their power in that @@ -282,22 +285,56 @@ where /// epoch. pub fn reset_age( &mut self, - key: ISK, + validator: ISK, capability: Capability, current_epoch: Epoch, + reset_factor: u32, ) -> StakesResult<(), Address, Coins, Epoch> where - ISK: Into>, + ISK: Into
, { - let key = key.into(); + let validator = validator.into(); - let mut stake = self - .by_key - .get_mut(&key) - .ok_or(StakesError::EntryNotFound { key })? + let stakes = self + .by_validator + .get_mut(&validator) + .ok_or(StakesError::ValidatorNotFound { validator })?; + stakes.iter_mut().for_each(|stake| { + let old_epoch = stake.value.write().unwrap().epochs.get(capability); + let update_epoch = (current_epoch - old_epoch) / Epoch::from(reset_factor); + stake + .value + .write() + .unwrap() + .epochs + .update(capability, old_epoch + update_epoch); + }); + + Ok(()) + } + + /// Add a reward to the validator's balance + pub fn add_reward( + &mut self, + validator: ISK, + coins: Coins, + current_epoch: Epoch, + ) -> StakesResult<(), Address, Coins, Epoch> + where + ISK: Into
, + { + let validator = validator.into(); + + let stakes = self + .by_validator + .get_mut(&validator) + .ok_or(StakesError::ValidatorNotFound { validator })?; + + let _ = stakes[0] .value - .write()?; - stake.epochs.update(capability, current_epoch); + .write() + .unwrap() + .add_stake(coins, current_epoch, Some(0.into())); Ok(()) } @@ -310,11 +347,44 @@ where } } + /// Creates an instance of `Stakes` that is initialized with a existing set of stake entries. + /// + /// This is specially convenient after loading stakes from storage, as this function rebuilds + /// all the indexes at once to preserve write locks and reference counts. + pub fn with_entries( + entries: BTreeMap, SyncStake>, + ) -> Self { + let mut stakes = Stakes { + by_key: entries, + ..Default::default() + }; + stakes.reindex(); + + stakes + } + + /// Rebuild all indexes based on the entries found in `by_key`. + pub fn reindex(&mut self) { + self.by_validator.clear(); + self.by_withdrawer.clear(); + self.by_coins.clear(); + + for (key, stake) in &self.by_key { + index_coins(&mut self.by_coins, key.clone(), stake.clone()); + index_addresses( + &mut self.by_validator, + &mut self.by_withdrawer, + key.clone(), + stake.clone(), + ); + } + } + /// Query stakes based on different keys. pub fn query_stakes( &mut self, query: TIQSK, - ) -> StakesResult>, Address, Coins, Epoch> + ) -> StakesVecResult where TIQSK: TryInto>, { @@ -326,12 +396,53 @@ where } } + /// Query the total amount of stake based on different keys. + pub fn query_total_stake( + &mut self, + query: TIQSK, + ) -> StakesResult + where + TIQSK: TryInto>, + { + self.query_stakes(query) + .map(|v| v.iter().fold(Coins::zero(), |a, b| a + b.coins)) + } + + /// Return the total number of validators. + pub fn validator_count(&self) -> usize { + self.by_validator.len() + } + + /// Query stakes to check for an existing validator / withdrawer pair. + pub fn check_validator_withdrawer( + &self, + validator: ISK, + withdrawer: ISK, + ) -> StakesResult<(), Address, Coins, Epoch> + where + ISK: Into
, + { + let validator = validator.into(); + let withdrawer = withdrawer.into(); + + if !self.by_validator.contains_key(&validator) { + Ok(()) + } else { + let stake_key = StakeKey::from((validator.clone(), withdrawer)); + if self.by_key.contains_key(&stake_key) { + Ok(()) + } else { + Err(StakesError::DifferentWithdrawer { validator }) + } + } + } + /// Query stakes by stake key. #[inline(always)] fn query_by_key( &self, key: StakeKey
, - ) -> StakesResult, Address, Coins, Epoch> { + ) -> StakesEntryResult { Ok(self .by_key .get(&key) @@ -346,11 +457,13 @@ where fn query_by_validator( &self, validator: Address, - ) -> StakesResult>, Address, Coins, Epoch> { - Ok(self + ) -> StakesVecResult { + let validator = self .by_validator .get(&validator) - .ok_or(StakesError::ValidatorNotFound { validator })? + .ok_or(StakesError::ValidatorNotFound { validator })?; + + Ok(validator .iter() .map(|stake| stake.value.read().unwrap().clone()) .collect()) @@ -361,17 +474,72 @@ where fn query_by_withdrawer( &self, withdrawer: Address, - ) -> StakesResult>, Address, Coins, Epoch> { - Ok(self + ) -> StakesVecResult { + let withdrawer = self .by_withdrawer .get(&withdrawer) - .ok_or(StakesError::WithdrawerNotFound { withdrawer })? + .ok_or(StakesError::WithdrawerNotFound { withdrawer })?; + + Ok(withdrawer .iter() .map(|stake| stake.value.read().unwrap().clone()) .collect()) } } +/// Update the position of the staker in a `by_coins` index. +/// If this stake entry was not indexed by coins, this will add it to the index. +/// +/// This function was made static instead of adding it to `impl Stakes` because of limitations +pub fn index_coins( + by_coins: &mut BTreeMap< + CoinsAndAddresses, + SyncStake, + >, + key: StakeKey
, + stake: SyncStake, +) where + Address: Clone + Default + Ord, + Coins: Copy + Default + Ord, + Epoch: Clone + Default, + Power: Clone + Default, +{ + let coins_and_addresses = CoinsAndAddresses { + coins: stake.value.read().unwrap().coins, + addresses: key.clone(), + }; + + by_coins.remove(&coins_and_addresses); + by_coins.insert(coins_and_addresses.clone(), stake.clone()); +} + +/// Upsert a stake entry into those indexes that allow querying by validator or withdrawer. +pub fn index_addresses( + by_validator: &mut BTreeMap>>, + by_withdrawer: &mut BTreeMap>>, + key: StakeKey
, + stake: SyncStake, +) where + Address: Clone + Default + Ord, + Coins: Clone + Default + Ord, + Epoch: Clone + Default, + Power: Clone + Default, +{ + let validator_key = key.validator; + if let Some(validator) = by_validator.get_mut(&validator_key) { + validator.push(stake.clone()); + } else { + by_validator.insert(validator_key, vec![stake.clone()]); + } + + let withdrawer_key = key.withdrawer; + if let Some(withdrawer) = by_withdrawer.get_mut(&withdrawer_key) { + withdrawer.push(stake); + } else { + by_withdrawer.insert(withdrawer_key, vec![stake]); + } +} + /// Adds stake, based on the data from a stake transaction. /// /// This function was made static instead of adding it to `impl Stakes` because it is not generic over `Address` and @@ -385,13 +553,15 @@ where Epoch: Copy + Default + Sub - + num_traits::Saturating + + Saturating + From + Debug + Display + Send - + Sync, - Power: Add + Copy + Default + Div + Ord + Debug, + + Sync + + Add + + Div, + Power: Add + Copy + Default + Div + Ord + Debug + Sum, Wit: Mul, u64: From + From, { @@ -426,18 +596,20 @@ where pub fn process_unstake_transaction( stakes: &mut Stakes, transaction: &UnstakeTransaction, -) -> StakingResult<(), PublicKeyHash, Wit, Epoch> +) -> StakesResult<(), PublicKeyHash, Wit, Epoch> where Epoch: Copy + Default + Sub - + num_traits::Saturating + + Saturating + From + Debug + Display + Send - + Sync, - Power: Add + Copy + Default + Div + Ord + Debug, + + Sync + + Add + + Div, + Power: Add + Copy + Default + Div + Ord + Debug + Sum, Wit: Mul, u64: From + From, { @@ -475,13 +647,15 @@ where Epoch: Copy + Default + Sub - + num_traits::Saturating + + Saturating + From + Debug + Send + Sync - + Display, - Power: Add + Copy + Default + Div + Ord + Debug, + + Display + + Add + + Div, + Power: Add + Copy + Default + Div + Ord + Debug + Sum, Wit: Mul, u64: From + From, { @@ -503,13 +677,15 @@ where Epoch: Copy + Default + Sub - + num_traits::Saturating + + Saturating + From + Debug + Send + Sync - + Display, - Power: Add + Copy + Default + Div + Ord + Debug, + + Display + + Add + + Div, + Power: Add + Copy + Default + Div + Ord + Debug + Sum, Wit: Mul, u64: From + From, { @@ -544,21 +720,15 @@ mod tests { // Let's check default power assert_eq!( - stakes.query_power(alice_charlie, Capability::Mining, 0), - Err(StakesError::EntryNotFound { - key: StakeKey { - validator: alice.into(), - withdrawer: charlie.into() - }, + stakes.query_power(alice, Capability::Mining, 0), + Err(StakesError::ValidatorNotFound { + validator: alice.into(), }) ); assert_eq!( - stakes.query_power(alice_charlie, Capability::Mining, 1_000), - Err(StakesError::EntryNotFound { - key: StakeKey { - validator: alice.into(), - withdrawer: charlie.into() - }, + stakes.query_power(alice, Capability::Mining, 1_000), + Err(StakesError::ValidatorNotFound { + validator: alice.into(), }) ); @@ -575,20 +745,11 @@ mod tests { ); // Let's see how Alice's stake accrues power over time + assert_eq!(stakes.query_power(alice, Capability::Mining, 99), Ok(0)); + assert_eq!(stakes.query_power(alice, Capability::Mining, 100), Ok(0)); + assert_eq!(stakes.query_power(alice, Capability::Mining, 101), Ok(100)); assert_eq!( - stakes.query_power(alice_charlie, Capability::Mining, 99), - Ok(0) - ); - assert_eq!( - stakes.query_power(alice_charlie, Capability::Mining, 100), - Ok(0) - ); - assert_eq!( - stakes.query_power(alice_charlie, Capability::Mining, 101), - Ok(100) - ); - assert_eq!( - stakes.query_power(alice_charlie, Capability::Mining, 200), + stakes.query_power(alice, Capability::Mining, 200), Ok(10_000) ); @@ -604,19 +765,19 @@ mod tests { ) ); assert_eq!( - stakes.query_power(alice_charlie, Capability::Mining, 299), + stakes.query_power(alice, Capability::Mining, 299), Ok(19_950) ); assert_eq!( - stakes.query_power(alice_charlie, Capability::Mining, 300), + stakes.query_power(alice, Capability::Mining, 300), Ok(20_100) ); assert_eq!( - stakes.query_power(alice_charlie, Capability::Mining, 301), + stakes.query_power(alice, Capability::Mining, 301), Ok(20_250) ); assert_eq!( - stakes.query_power(alice_charlie, Capability::Mining, 400), + stakes.query_power(alice, Capability::Mining, 400), Ok(35_100) ); @@ -634,41 +795,32 @@ mod tests { // Before Bob stakes, Alice has all the power assert_eq!( - stakes.query_power(alice_charlie, Capability::Mining, 999), + stakes.query_power(alice, Capability::Mining, 999), Ok(124950) ); - assert_eq!( - stakes.query_power(bob_david, Capability::Mining, 999), - Ok(0) - ); + assert_eq!(stakes.query_power(bob, Capability::Mining, 999), Ok(0)); // New stakes don't change power in the same epoch assert_eq!( - stakes.query_power(alice_charlie, Capability::Mining, 1_000), + stakes.query_power(alice, Capability::Mining, 1_000), Ok(125100) ); - assert_eq!( - stakes.query_power(bob_david, Capability::Mining, 1_000), - Ok(0) - ); + assert_eq!(stakes.query_power(bob, Capability::Mining, 1_000), Ok(0)); // Shortly after, Bob's stake starts to gain power assert_eq!( - stakes.query_power(alice_charlie, Capability::Mining, 1_001), + stakes.query_power(alice, Capability::Mining, 1_001), Ok(125250) ); - assert_eq!( - stakes.query_power(bob_david, Capability::Mining, 1_001), - Ok(500) - ); + assert_eq!(stakes.query_power(bob, Capability::Mining, 1_001), Ok(500)); // After enough time, Bob overpowers Alice assert_eq!( - stakes.query_power(alice_charlie, Capability::Mining, 2_000), + stakes.query_power(alice, Capability::Mining, 2_000), Ok(275_100) ); assert_eq!( - stakes.query_power(bob_david, Capability::Mining, 2_000), + stakes.query_power(bob, Capability::Mining, 2_000), Ok(500_000) ); } @@ -693,27 +845,24 @@ mod tests { // Let's really start our test at epoch 100 assert_eq!( - stakes.query_power(alice_charlie, Capability::Mining, 100), + stakes.query_power(alice, Capability::Mining, 100), Ok(1_000) ); + assert_eq!(stakes.query_power(bob, Capability::Mining, 100), Ok(1_600)); assert_eq!( - stakes.query_power(bob_david, Capability::Mining, 100), - Ok(1_600) - ); - assert_eq!( - stakes.query_power(charlie_erin, Capability::Mining, 100), + stakes.query_power(charlie, Capability::Mining, 100), Ok(2_100) ); assert_eq!( - stakes.query_power(alice_charlie, Capability::Witnessing, 100), + stakes.query_power(alice, Capability::Witnessing, 100), Ok(1_000) ); assert_eq!( - stakes.query_power(bob_david, Capability::Witnessing, 100), + stakes.query_power(bob, Capability::Witnessing, 100), Ok(1_600) ); assert_eq!( - stakes.query_power(charlie_erin, Capability::Witnessing, 100), + stakes.query_power(charlie, Capability::Witnessing, 100), Ok(2_100) ); assert_eq!( @@ -735,30 +884,24 @@ mod tests { // Now let's slash Charlie's mining coin age right after stakes - .reset_age(charlie_erin, Capability::Mining, 101) + .reset_age(charlie, Capability::Mining, 101, 1) .unwrap(); assert_eq!( - stakes.query_power(alice_charlie, Capability::Mining, 101), + stakes.query_power(alice, Capability::Mining, 101), Ok(1_010) ); + assert_eq!(stakes.query_power(bob, Capability::Mining, 101), Ok(1_620)); + assert_eq!(stakes.query_power(charlie, Capability::Mining, 101), Ok(0)); assert_eq!( - stakes.query_power(bob_david, Capability::Mining, 101), - Ok(1_620) - ); - assert_eq!( - stakes.query_power(charlie_erin, Capability::Mining, 101), - Ok(0) - ); - assert_eq!( - stakes.query_power(alice_charlie, Capability::Witnessing, 101), + stakes.query_power(alice, Capability::Witnessing, 101), Ok(1_010) ); assert_eq!( - stakes.query_power(bob_david, Capability::Witnessing, 101), + stakes.query_power(bob, Capability::Witnessing, 101), Ok(1_620) ); assert_eq!( - stakes.query_power(charlie_erin, Capability::Witnessing, 101), + stakes.query_power(charlie, Capability::Witnessing, 101), Ok(2_130) ); assert_eq!( @@ -780,27 +923,24 @@ mod tests { // Don't panic, Charlie! After enough time, you can take over again ;) assert_eq!( - stakes.query_power(alice_charlie, Capability::Mining, 300), + stakes.query_power(alice, Capability::Mining, 300), Ok(3_000) ); + assert_eq!(stakes.query_power(bob, Capability::Mining, 300), Ok(5_600)); assert_eq!( - stakes.query_power(bob_david, Capability::Mining, 300), - Ok(5_600) - ); - assert_eq!( - stakes.query_power(charlie_erin, Capability::Mining, 300), + stakes.query_power(charlie, Capability::Mining, 300), Ok(5_970) ); assert_eq!( - stakes.query_power(alice_charlie, Capability::Witnessing, 300), + stakes.query_power(alice, Capability::Witnessing, 300), Ok(3_000) ); assert_eq!( - stakes.query_power(bob_david, Capability::Witnessing, 300), + stakes.query_power(bob, Capability::Witnessing, 300), Ok(5_600) ); assert_eq!( - stakes.query_power(charlie_erin, Capability::Witnessing, 300), + stakes.query_power(charlie, Capability::Witnessing, 300), Ok(8_100) ); assert_eq!( @@ -821,6 +961,59 @@ mod tests { ); } + #[test] + fn test_rank_proportional_reset() { + // First, lets create a setup with a few stakers + let mut stakes = Stakes::::with_minimum(5); + let alice = "Alice"; + let bob = "Bob"; + let charlie = "Charlie"; + let david = "David"; + let erin = "Erin"; + + let alice_bob = (alice, bob); + let bob_charlie = (bob, charlie); + let charlie_david = (charlie, david); + let david_erin = (david, erin); + let erin_alice = (erin, alice); + + stakes.add_stake(alice_bob, 10, 0).unwrap(); + stakes.add_stake(bob_charlie, 20, 10).unwrap(); + stakes.add_stake(charlie_david, 30, 20).unwrap(); + stakes.add_stake(david_erin, 40, 30).unwrap(); + stakes.add_stake(erin_alice, 50, 40).unwrap(); + + // Power of validators at epoch 90: + // alice_bob: 10 * (90 - 0) = 900 + // bob_charlie: 20 * (90 - 10) = 1600 + // charlie_david: 30 * (90 - 20) = 2100 + // david_erin: 40 * (90 - 30) = 2400 + // erin_alice: 50 * (90 - 40) = 2500 + let rank_subset: Vec<_> = stakes.rank(Capability::Mining, 90).take(4).collect(); + for (i, (stake_key, _)) in rank_subset.into_iter().enumerate() { + let _ = stakes.reset_age( + stake_key.validator, + Capability::Mining, + 90, + (i + 1).try_into().unwrap(), + ); + } + + // Slashed with a factor 1 / 1 + assert_eq!(stakes.query_power(erin, Capability::Mining, 90), Ok(0)); + // Slashed with a factor 1 / 2 + assert_eq!(stakes.query_power(david, Capability::Mining, 90), Ok(1200)); + // Slashed with a factor 1 / 3 + assert_eq!( + stakes.query_power(charlie, Capability::Mining, 90), + Ok(1410) + ); + // Slashed with a factor 1 / 4 + assert_eq!(stakes.query_power(bob, Capability::Mining, 90), Ok(1200)); + // Not slashed + assert_eq!(stakes.query_power(alice, Capability::Mining, 90), Ok(900)); + } + #[test] fn test_query_stakes() { // First, lets create a setup with a few stakers @@ -891,4 +1084,59 @@ mod tests { }) ); } + + #[test] + fn test_serde() { + use bincode; + + let mut stakes = Stakes::::with_minimum(5); + let alice = String::from("Alice"); + let bob = String::from("Bob"); + + let alice_bob = (alice.clone(), bob.clone()); + stakes.add_stake(alice_bob, 123, 456).ok(); + + let serialized = bincode::serialize(&stakes).unwrap().clone(); + let mut deserialized: Stakes = + bincode::deserialize(serialized.as_slice()).unwrap(); + + deserialized + .reset_age(alice.clone(), Capability::Mining, 789, 1) + .ok(); + deserialized.query_by_validator(alice).unwrap(); + + let epoch = deserialized.query_by_withdrawer(bob.clone()).unwrap()[0] + .epochs + .mining; + + assert_eq!(epoch, 789); + } + + #[test] + fn test_validator_withdrawer_pair() { + // First, lets create a setup with a few stakers + let mut stakes = Stakes::::with_minimum(5); + let alice = "Alice"; + let bob = "Bob"; + let charlie = "Charlie"; + + // Validator not used yet, so we can stake with any (validator, withdrawer) pair + assert_eq!(stakes.check_validator_withdrawer(alice, bob), Ok(())); + assert_eq!(stakes.check_validator_withdrawer(alice, charlie), Ok(())); + + // Use the validator with a (validator, withdrawer) pair + stakes.add_stake((alice, bob), 10, 0).unwrap(); + + // The validator is used, we can still stake as long as the correct withdrawer is used + assert_eq!(stakes.check_validator_withdrawer(alice, bob), Ok(())); + + // Validator used with another withdrawer address, throw an error + let valid_pair = stakes.check_validator_withdrawer(alice, charlie); + assert_eq!( + valid_pair, + Err(StakesError::DifferentWithdrawer { + validator: alice.into() + }) + ); + } } diff --git a/data_structures/src/transaction_factory.rs b/data_structures/src/transaction_factory.rs index 7556a9266..a6127f869 100644 --- a/data_structures/src/transaction_factory.rs +++ b/data_structures/src/transaction_factory.rs @@ -303,13 +303,13 @@ pub trait OutputsCollection { utxo_strategy: &UtxoSelectionStrategy, max_weight: u32, ) -> Result { - // On error just assume the value is u64::max_value(), hoping that it is + // On error just assume the value is u64::MAX, hoping that it is // impossible to pay for this transaction let output_value: u64 = transaction_outputs_sum(&outputs) - .unwrap_or(u64::max_value()) + .unwrap_or(u64::MAX) .checked_add( dr_output - .map(|o| o.checked_total_value().unwrap_or(u64::max_value())) + .map(|o| o.checked_total_value().unwrap_or(u64::MAX)) .unwrap_or_default(), ) .ok_or(TransactionError::OutputValueOverflow)?; @@ -675,7 +675,7 @@ pub fn transaction_inputs_sum( })?; // Verify that commits are only accepted after the time lock expired - let epoch_timestamp = epoch_constants.epoch_timestamp(epoch)?; + let (epoch_timestamp, _) = epoch_constants.epoch_timestamp(epoch)?; let vt_time_lock = i64::try_from(vt_output.time_lock)?; if vt_time_lock > epoch_timestamp { return Err(TransactionError::TimeLock { diff --git a/data_structures/src/utxo_pool.rs b/data_structures/src/utxo_pool.rs index 4bceeb3cd..defc799e3 100644 --- a/data_structures/src/utxo_pool.rs +++ b/data_structures/src/utxo_pool.rs @@ -311,26 +311,23 @@ impl OwnUnspentOutputsPool { map: HashMap::default(), } } - pub fn get(&self, k: &Q) -> Option<&u64> + pub fn get(&self, k: &Q) -> Option<&u64> where OutputPointer: std::borrow::Borrow, - Q: std::hash::Hash + Eq, { self.map.get(k) } - pub fn get_mut(&mut self, k: &Q) -> Option<&mut u64> + pub fn get_mut(&mut self, k: &Q) -> Option<&mut u64> where OutputPointer: std::borrow::Borrow, - Q: std::hash::Hash + Eq, { self.map.get_mut(k) } - pub fn contains_key(&self, k: &Q) -> bool + pub fn contains_key(&self, k: &Q) -> bool where OutputPointer: std::borrow::Borrow, - Q: std::hash::Hash + Eq, { self.map.contains_key(k) } diff --git a/data_structures/src/wit.rs b/data_structures/src/wit.rs index 0023df3ea..63149e643 100644 --- a/data_structures/src/wit.rs +++ b/data_structures/src/wit.rs @@ -1,4 +1,4 @@ -use std::{fmt, ops::*}; +use std::{fmt, iter::Sum, ops::*}; use serde::{Deserialize, Serialize}; @@ -78,6 +78,14 @@ impl Div for Wit { } } +impl Rem for Wit { + type Output = Self; + + fn rem(self, rhs: Self) -> Self::Output { + Self::from_nanowits(self.nanowits() % rhs.nanowits()) + } +} + impl Mul for Wit { type Output = Self; @@ -138,6 +146,35 @@ impl From for u64 { } } +impl Sum for Wit { + fn sum(iter: I) -> Self + where + I: Iterator, + { + let mut total = Wit::from_nanowits(0); + for w in iter { + total = total + w; + } + total + } +} + +pub trait PrecisionLoss: Copy { + fn lose_precision(self, digits: u8) -> Self; +} + +impl PrecisionLoss for u64 { + fn lose_precision(self, digits: u8) -> u64 { + self / 10_u64.pow(digits as u32) + } +} + +impl PrecisionLoss for Wit { + fn lose_precision(self, digits: u8) -> Wit { + Wit(self.0.lose_precision(digits)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/node/src/actors/chain_manager/handlers.rs b/node/src/actors/chain_manager/handlers.rs index 16de95ee7..72bb2c24d 100644 --- a/node/src/actors/chain_manager/handlers.rs +++ b/node/src/actors/chain_manager/handlers.rs @@ -14,11 +14,14 @@ use witnet_config::defaults::{ PSEUDO_CONSENSUS_CONSTANTS_WIP0027_COLLATERAL_AGE, }; use witnet_data_structures::{ + capabilities::Capability, chain::{ tapi::ActiveWips, Block, ChainState, CheckpointBeacon, DataRequestInfo, Epoch, Hash, Hashable, NodeStats, PublicKeyHash, SuperBlockVote, SupplyInfo, ValueTransferOutput, }, error::{ChainInfoError, TransactionError::DataRequestNotFound}, + get_protocol_version, + proto::versioning::ProtocolVersion, staking::errors::StakesError, transaction::{ DRTransaction, StakeTransaction, Transaction, UnstakeTransaction, VTTransaction, @@ -72,6 +75,8 @@ pub const SYNCED_BANNER: &str = r" ║ (balance, reputation, proposed blocks, etc.) ║ ╚════════════════════════════════════════════════════╝"; +const MINING_REPLICATION_FACTOR: usize = 4; + //////////////////////////////////////////////////////////////////////////////////////// // ACTOR MESSAGE HANDLERS //////////////////////////////////////////////////////////////////////////////////////// @@ -156,6 +161,7 @@ impl Handler> for ChainManager { match self.chain_state { ChainState { reputation_engine: Some(_), + ref mut stakes, .. } => { if self.epoch_constants.is_none() || self.vrf_ctx.is_none() { @@ -180,6 +186,25 @@ impl Handler> for ChainManager { "There was no valid block candidate to consolidate for epoch {}", previous_epoch ); + if get_protocol_version(Some(previous_epoch)) == ProtocolVersion::V2_0 { + let rank_subset: Vec<_> = stakes + .rank(Capability::Mining, previous_epoch) + .take(MINING_REPLICATION_FACTOR) + .collect(); + for (i, (stake_key, _)) in rank_subset.into_iter().enumerate() { + log::warn!( + "Slashed the power of {} as it did not propose a block", + stake_key.validator + ); + let _ = stakes.reset_age( + stake_key.validator, + Capability::Mining, + msg.checkpoint, + // This should never fail + (i + 1).try_into().unwrap(), + ); + } + } } // Send last beacon on block consolidation @@ -919,7 +944,7 @@ impl Handler for ChainManager { let beacon_consensus = peers_beacons.superblock_consensus(consensus_threshold); let outbound_limit = peers_beacons.outbound_limit; let pb_len = peers_beacons.pb.len(); - self.last_received_beacons = peers_beacons.pb.clone(); + self.last_received_beacons.clone_from(&peers_beacons.pb); let peers_needed_for_consensus = outbound_limit .map(|x| { // ceil(x * consensus_threshold / 100) @@ -1423,7 +1448,7 @@ impl Handler for ChainManager { fn handle(&mut self, msg: QueryStake, _ctx: &mut Self::Context) -> Self::Result { // build address from public key hash - let stakes = self.chain_state.stakes.query_stakes(msg.key); + let stakes = self.chain_state.stakes.query_total_stake(msg.key); stakes.map_err(StakesError::from).map_err(Into::into) } diff --git a/node/src/actors/chain_manager/mining.rs b/node/src/actors/chain_manager/mining.rs index e98083cd7..5fa84d01b 100644 --- a/node/src/actors/chain_manager/mining.rs +++ b/node/src/actors/chain_manager/mining.rs @@ -25,18 +25,17 @@ use witnet_data_structures::{ tapi::{after_second_hard_fork, ActiveWips}, Block, BlockHeader, BlockMerkleRoots, BlockTransactions, Bn256PublicKey, CheckpointBeacon, CheckpointVRF, DataRequestOutput, EpochConstants, Hash, Hashable, Input, PublicKeyHash, - TransactionsPool, ValueTransferOutput, + RADTally, TransactionsPool, ValueTransferOutput, }, data_request::{ calculate_witness_reward, calculate_witness_reward_before_second_hard_fork, create_tally, - DataRequestPool, + data_request_has_too_many_witnesses, DataRequestPool, }, error::TransactionError, get_environment, get_protocol_version, proto::versioning::{ProtocolVersion, VersionedHashable}, radon_error::RadonError, radon_report::{RadonReport, ReportContext, TypeLike}, - staking::prelude::*, transaction::{ CommitTransaction, CommitTransactionBody, DRTransactionBody, MintTransaction, RevealTransaction, RevealTransactionBody, StakeTransactionBody, TallyTransaction, @@ -46,6 +45,7 @@ use witnet_data_structures::{ utxo_pool::{UnspentOutputsPool, UtxoDiff}, vrf::{BlockEligibilityClaim, DataRequestEligibilityClaim, VrfMessage}, wit::Wit, + DEFAULT_VALIDATOR_COUNT_FOR_TESTS, }; use witnet_futures_utils::TryFutureExt2; use witnet_rad::{ @@ -61,8 +61,8 @@ use witnet_validations::{ }, validations::{ block_reward, calculate_liars_and_errors_count_from_tally, dr_transaction_fee, - merkle_tree_root, st_transaction_fee, tally_bytes_on_encode_error, update_utxo_diff, - vt_transaction_fee, + merkle_tree_root, run_tally, st_transaction_fee, tally_bytes_on_encode_error, + update_utxo_diff, vt_transaction_fee, }, }; @@ -112,12 +112,15 @@ impl ChainManager { let mut beacon = chain_info.highest_block_checkpoint; let mut vrf_input = chain_info.highest_vrf_output; - if get_protocol_version(self.current_epoch) == ProtocolVersion::V2_0 { - let key = StakeKey::from((own_pkh, own_pkh)); + // The highest checkpoint beacon should contain the current epoch + beacon.checkpoint = current_epoch; + vrf_input.checkpoint = current_epoch; + + let target_hash = if get_protocol_version(self.current_epoch) == ProtocolVersion::V2_0 { let eligibility = self .chain_state .stakes - .mining_eligibility(key, current_epoch) + .mining_eligibility(own_pkh, current_epoch) .map_err(ChainManagerError::Staking)?; match eligibility { @@ -129,13 +132,7 @@ impl ChainManager { return Ok(()); } } - } - // The highest checkpoint beacon should contain the current epoch - beacon.checkpoint = current_epoch; - vrf_input.checkpoint = current_epoch; - - let target_hash = if get_protocol_version(self.current_epoch) == ProtocolVersion::V2_0 { Hash::max() } else { let rep_engine = self.chain_state.reputation_engine.as_ref().unwrap().clone(); @@ -163,6 +160,8 @@ impl ChainManager { target_hash }; + let validator_count = self.chain_state.stakes.validator_count(); + signature_mngr::vrf_prove(VrfMessage::block_mining(vrf_input)) .map(move |res| { res.map_err(|e| log::error!("Failed to create block eligibility proof: {}", e)) @@ -205,11 +204,12 @@ impl ChainManager { }; // Build the block using the supplied beacon and eligibility proof + let block_number = act.chain_state.block_number(); let (block_header, txns) = build_block( ( &mut act.transactions_pool, &act.chain_state.unspent_outputs_pool, - &act.chain_state.data_request_pool, + &mut act.chain_state.data_request_pool, ), max_vt_weight, max_dr_weight, @@ -219,7 +219,7 @@ impl ChainManager { &tally_transactions, own_pkh, epoch_constants, - act.chain_state.block_number(), + block_number, collateral_minimum, bn256_public_key, act.external_address, @@ -228,6 +228,7 @@ impl ChainManager { halving_period, tapi_version, &active_wips, + Some(validator_count), ); // Sign the block hash @@ -333,7 +334,11 @@ impl ChainManager { let (collateral_age, checkpoint_period) = match &self.chain_state.chain_info { Some(x) => ( x.consensus_constants.collateral_age, - x.consensus_constants.checkpoints_period, + // Unwraps should be safe if we have a chain_info object + self.epoch_constants + .unwrap() + .get_epoch_period(current_epoch) + .unwrap(), ), None => { log::error!("ChainInfo is None"); @@ -370,7 +375,7 @@ impl ChainManager { let cloned_retrieval_count2 = Arc::clone(¤t_retrieval_count); let added_retrieval_count = u16::try_from(dr_state.data_request.data_request.retrieve.len()) - .unwrap_or(core::u16::MAX); + .unwrap_or(u16::MAX); let collateral_amount = Wit::from_nanowits(if dr_state.data_request.collateral == 0 { self.chain_state @@ -551,6 +556,7 @@ impl ChainManager { rad_request, timeout: data_request_timeout, active_wips, + too_many_witnesses: false, }) .map(move |res| res.map(move |result| match result { @@ -735,6 +741,7 @@ impl ChainManager { script: dr_state.data_request.data_request.tally.clone(), commits_count, active_wips: active_wips_inside_move.clone(), + too_many_witnesses: false, }) .await .unwrap_or_else(|e| { @@ -801,7 +808,11 @@ impl ChainManager { /// TODO: simplify function signature, e.g. through merging multiple related fields into new data structures. #[allow(clippy::too_many_arguments)] pub fn build_block( - pools_ref: (&mut TransactionsPool, &UnspentOutputsPool, &DataRequestPool), + pools_ref: ( + &mut TransactionsPool, + &UnspentOutputsPool, + &mut DataRequestPool, + ), max_vt_weight: u32, max_dr_weight: u32, max_st_weight: u32, @@ -819,7 +830,9 @@ pub fn build_block( halving_period: u32, tapi_signals: u32, active_wips: &ActiveWips, + validator_count: Option, ) -> (BlockHeader, BlockTransactions) { + let validator_count = validator_count.unwrap_or(DEFAULT_VALIDATOR_COUNT_FOR_TESTS); let (transactions_pool, unspent_outputs_pool, dr_pool) = pools_ref; let epoch = beacon.checkpoint; let mut utxo_diff = UtxoDiff::new(unspent_outputs_pool, block_number); @@ -971,6 +984,52 @@ pub fn build_block( dr_tx.hash(), ); + // Number of data request witnesses should be at most the number of validators divided by four + if data_request_has_too_many_witnesses( + &dr_tx.body.dr_output, + validator_count, + Some(epoch), + ) { + log::debug!("Data request {} has too many witnesses", dr_tx.hash()); + + // Temporarily insert the data request into the dr_pool + if let Err(e) = dr_pool.process_data_request(dr_tx, epoch, &Hash::default()) { + log::error!("Error adding data request to the data request pool: {}", e); + } + if let Some(dr_state) = dr_pool.data_request_state_mutable(&dr_tx.hash()) { + dr_state.update_stage(0, true); + } else { + log::error!("Could not find data request state"); + } + + // The result of `RunTally` will be published as tally + let tally_script = RADTally { + filters: vec![], + reducer: 0, + }; + let tally_result = run_tally( + vec![], // reveals + &tally_script, + 0.0, // minimum consensus percentage + 0, // commit count + active_wips, + true, // too many witnesses + ); + + let pkh = dr_tx.signatures[0].public_key.pkh(); + tally_txns.push(create_tally( + dr_tx.hash(), + &dr_tx.body.dr_output, + pkh, + &tally_result, + vec![], // No revealers + HashSet::new(), // No committers + collateral_minimum, + tally_bytes_on_encode_error(), + active_wips, + )); + } + data_request_txns.push(dr_tx.clone()); transaction_fees = transaction_fees.saturating_add(transaction_fee); dr_weight = new_dr_weight; @@ -983,60 +1042,72 @@ pub fn build_block( } } - let mut included_validators = HashSet::::new(); - for st_tx in transactions_pool.st_iter() { - let validator_pkh = st_tx.body.output.authorization.public_key.pkh(); - if included_validators.contains(&validator_pkh) { - log::debug!( - "Cannot include more than one stake transaction for {} in a single block", - validator_pkh - ); - continue; - } + let protocol_version = get_protocol_version(Some(epoch)); - let transaction_weight = st_tx.weight(); - let transaction_fee = match st_transaction_fee(st_tx, &utxo_diff, epoch, epoch_constants) { - Ok(x) => x, - Err(e) => { - log::warn!( - "Error when calculating transaction fee for transaction: {}", - e + if protocol_version != ProtocolVersion::V1_7 { + let mut included_validators = HashSet::::new(); + for st_tx in transactions_pool.st_iter() { + let validator_pkh = st_tx.body.output.authorization.public_key.pkh(); + if included_validators.contains(&validator_pkh) { + log::debug!( + "Cannot include more than one stake transaction for {} in a single block", + validator_pkh ); continue; } - }; - let new_st_weight = st_weight.saturating_add(transaction_weight); - if new_st_weight <= max_st_weight { - update_utxo_diff( - &mut utxo_diff, - st_tx.body.inputs.iter(), - st_tx.body.change.iter(), - st_tx.hash(), - ); - stake_txns.push(st_tx.clone()); - transaction_fees = transaction_fees.saturating_add(transaction_fee); - st_weight = new_st_weight; - } + let transaction_weight = st_tx.weight(); + let transaction_fee = + match st_transaction_fee(st_tx, &utxo_diff, epoch, epoch_constants) { + Ok(x) => x, + Err(e) => { + log::warn!( + "Error when calculating transaction fee for transaction: {}", + e + ); + continue; + } + }; - // The condition to stop is if the free space in the block for VTTransactions - // is less than the minimum stake transaction weight - if st_weight > max_st_weight.saturating_sub(min_st_weight) { - break; - } + let new_st_weight = st_weight.saturating_add(transaction_weight); + if new_st_weight <= max_st_weight { + update_utxo_diff( + &mut utxo_diff, + st_tx.body.inputs.iter(), + st_tx.body.change.iter(), + st_tx.hash(), + ); + stake_txns.push(st_tx.clone()); + transaction_fees = transaction_fees.saturating_add(transaction_fee); + st_weight = new_st_weight; + } - included_validators.insert(validator_pkh); + // The condition to stop is if the free space in the block for VTTransactions + // is less than the minimum stake transaction weight + if st_weight > max_st_weight.saturating_sub(min_st_weight) { + break; + } + + included_validators.insert(validator_pkh); + } } // Include Mint Transaction by miner - let reward = block_reward(epoch, initial_block_reward, halving_period) + transaction_fees; - let mint = MintTransaction::with_external_address( - epoch, - reward, - own_pkh, - external_address, - external_percentage, - ); + let mint = if protocol_version == ProtocolVersion::V2_0 { + let mut mint = MintTransaction::default(); + mint.epoch = epoch; + + mint + } else { + let reward = block_reward(epoch, initial_block_reward, halving_period) + transaction_fees; + MintTransaction::with_external_address( + epoch, + reward, + own_pkh, + external_address, + external_percentage, + ) + }; // Compute `hash_merkle_root` and build block header let vt_hash_merkle_root = merkle_tree_root(&value_transfer_txns); @@ -1045,9 +1116,7 @@ pub fn build_block( let reveal_hash_merkle_root = merkle_tree_root(&reveal_txns); let tally_hash_merkle_root = merkle_tree_root(&tally_txns); - let protocol = get_protocol_version(Some(beacon.checkpoint)); - - let stake_hash_merkle_root = if protocol == ProtocolVersion::V1_7 { + let stake_hash_merkle_root = if protocol_version == ProtocolVersion::V1_7 { log::debug!("Legacy protocol: the default stake hash merkle root will be used"); Hash::default() } else { @@ -1055,7 +1124,7 @@ pub fn build_block( merkle_tree_root(&stake_txns) }; - let unstake_hash_merkle_root = if protocol == ProtocolVersion::V1_7 { + let unstake_hash_merkle_root = if protocol_version == ProtocolVersion::V1_7 { Hash::default() } else { merkle_tree_root(&unstake_txns) @@ -1125,7 +1194,7 @@ mod tests { transaction_pool.insert(transaction.clone(), 0); let unspent_outputs_pool = UnspentOutputsPool::default(); - let dr_pool = DataRequestPool::default(); + let mut dr_pool = DataRequestPool::default(); // Set `max_vt_weight` and `max_dr_weight` to zero (no transaction should be included) let max_vt_weight = 0; @@ -1141,7 +1210,7 @@ mod tests { // Build empty block (because max weight is zero) let (block_header, txns) = build_block( - (&mut transaction_pool, &unspent_outputs_pool, &dr_pool), + (&mut transaction_pool, &unspent_outputs_pool, &mut dr_pool), max_vt_weight, max_dr_weight, max_st_weight, @@ -1159,6 +1228,7 @@ mod tests { HALVING_PERIOD, 0, &active_wips, + None, ); let block = Block::new(block_header, KeyedSignature::default(), txns); @@ -1187,11 +1257,12 @@ mod tests { transaction_pool.insert(transaction, 0); let unspent_outputs_pool = UnspentOutputsPool::default(); - let dr_pool = DataRequestPool::default(); + let mut dr_pool = DataRequestPool::default(); // Set `max_vt_weight` and `max_dr_weight` to zero (no transaction should be included) let max_vt_weight = 0; let max_dr_weight = 0; + let max_st_weight = 0; // Fields required to mine a block let block_beacon = CheckpointBeacon::default(); @@ -1218,9 +1289,10 @@ mod tests { // Build empty block (because max weight is zero) let (block_header, txns) = build_block( - (&mut transaction_pool, &unspent_outputs_pool, &dr_pool), + (&mut transaction_pool, &unspent_outputs_pool, &mut dr_pool), max_vt_weight, max_dr_weight, + max_st_weight, block_beacon, block_proof, &[], @@ -1235,6 +1307,7 @@ mod tests { HALVING_PERIOD, 0, &active_wips, + None, ); // Create a KeyedSignature @@ -1331,7 +1404,7 @@ mod tests { unspent_outputs_pool.insert(output1_pointer, output1, 0); assert!(unspent_outputs_pool.contains_key(&output1_pointer)); - let dr_pool = DataRequestPool::default(); + let mut dr_pool = DataRequestPool::default(); // Fields required to mine a block let block_beacon = CheckpointBeacon::default(); @@ -1347,7 +1420,7 @@ mod tests { // Build block with let (block_header, txns) = build_block( - (&mut transaction_pool, &unspent_outputs_pool, &dr_pool), + (&mut transaction_pool, &unspent_outputs_pool, &mut dr_pool), max_vt_weight, max_dr_weight, max_st_weight, @@ -1365,6 +1438,7 @@ mod tests { HALVING_PERIOD, 0, &active_wips, + None, ); let block = Block::new(block_header, KeyedSignature::default(), txns); @@ -1434,7 +1508,7 @@ mod tests { unspent_outputs_pool.insert(output1_pointer, output1, 0); assert!(unspent_outputs_pool.contains_key(&output1_pointer)); - let dr_pool = DataRequestPool::default(); + let mut dr_pool = DataRequestPool::default(); // Fields required to mine a block let block_beacon = CheckpointBeacon::default(); @@ -1450,7 +1524,7 @@ mod tests { // Build block with let (block_header, txns) = build_block( - (&mut transaction_pool, &unspent_outputs_pool, &dr_pool), + (&mut transaction_pool, &unspent_outputs_pool, &mut dr_pool), max_vt_weight, max_dr_weight, max_st_weight, @@ -1468,6 +1542,7 @@ mod tests { HALVING_PERIOD, 0, &active_wips, + None, ); let block = Block::new(block_header, KeyedSignature::default(), txns); @@ -1551,7 +1626,7 @@ mod tests { unspent_outputs_pool.insert(output1_pointer, output1, 0); assert!(unspent_outputs_pool.contains_key(&output1_pointer)); - let dr_pool = DataRequestPool::default(); + let mut dr_pool = DataRequestPool::default(); // Fields required to mine a block let block_beacon = CheckpointBeacon::default(); @@ -1567,7 +1642,7 @@ mod tests { // Build block with let (block_header, txns) = build_block( - (&mut transaction_pool, &unspent_outputs_pool, &dr_pool), + (&mut transaction_pool, &unspent_outputs_pool, &mut dr_pool), max_vt_weight, max_dr_weight, max_st_weight, @@ -1585,6 +1660,7 @@ mod tests { HALVING_PERIOD, 0, &active_wips, + None, ); let block = Block::new(block_header, KeyedSignature::default(), txns); @@ -1651,7 +1727,7 @@ mod tests { unspent_outputs_pool.insert(output1_pointer, output1, 0); assert!(unspent_outputs_pool.contains_key(&output1_pointer)); - let dr_pool = DataRequestPool::default(); + let mut dr_pool = DataRequestPool::default(); // Fields required to mine a block let block_beacon = CheckpointBeacon::default(); @@ -1667,7 +1743,7 @@ mod tests { // Build block with let (block_header, txns) = build_block( - (&mut transaction_pool, &unspent_outputs_pool, &dr_pool), + (&mut transaction_pool, &unspent_outputs_pool, &mut dr_pool), max_vt_weight, max_dr_weight, max_st_weight, @@ -1685,6 +1761,7 @@ mod tests { HALVING_PERIOD, 0, &active_wips, + None, ); let block = Block::new(block_header, KeyedSignature::default(), txns); diff --git a/node/src/actors/chain_manager/mod.rs b/node/src/actors/chain_manager/mod.rs index 92c5720a9..3ab54101d 100644 --- a/node/src/actors/chain_manager/mod.rs +++ b/node/src/actors/chain_manager/mod.rs @@ -71,6 +71,7 @@ use witnet_data_structures::{ data_request::DataRequestPool, get_environment, get_protocol_version, proto::versioning::ProtocolVersion, + radon_error::RadonError, radon_report::{RadonReport, ReportContext}, staking::prelude::*, superblock::{ARSIdentities, AddSuperBlockVote, SuperBlockConsensus}, @@ -79,17 +80,18 @@ use witnet_data_structures::{ visitor::{StatefulVisitor, Visitor}, LastBeacon, }, - utxo_pool::{Diff, OwnUnspentOutputsPool, UnspentOutputsPool, UtxoWriteBatch}, + utxo_pool::{Diff, OwnUnspentOutputsPool, UnspentOutputsPool, UtxoDiff, UtxoWriteBatch}, vrf::VrfCtx, wit::Wit, }; -use witnet_rad::types::RadonTypes; +use witnet_rad::{error::RadError::TooManyWitnesses, types::RadonTypes}; use witnet_util::timestamp::seconds_to_human_string; use witnet_validations::{ eligibility::legacy::VrfSlots, validations::{ - compare_block_candidates, validate_block, validate_block_transactions, - validate_new_transaction, validate_rad_request, verify_signatures, + compare_block_candidates, dr_transaction_fee, st_transaction_fee, ut_transaction_fee, + validate_block, validate_block_transactions, validate_new_transaction, + validate_rad_request, verify_signatures, vt_transaction_fee, }, }; @@ -820,16 +822,36 @@ impl ChainManager { // than the other to avoid the "activeness" comparison is_active }; + let power = self + .chain_state + .stakes + .query_power( + *block_pkh, + Capability::Mining, + block.block_header.beacon.checkpoint, + ) + .unwrap_or(0); + let best_candidate_power = self + .chain_state + .stakes + .query_power( + best_pkh, + Capability::Mining, + best_candidate.block.block_header.beacon.checkpoint, + ) + .unwrap_or(0); if compare_block_candidates( hash_block, reputation, vrf_proof, is_active, + power, best_hash, best_candidate.reputation, best_candidate.vrf_proof, best_candidate_is_active, + best_candidate_power, &target_vrf_slots, protocol_version, ) != Ordering::Greater @@ -952,6 +974,7 @@ impl ChainManager { let block_hash = block.hash(); let block_epoch = block.block_header.beacon.checkpoint; let block_signals = block.block_header.signals; + let validator_count = stakes.validator_count(); // Update `highest_block_checkpoint` let beacon = CheckpointBeacon { @@ -997,6 +1020,43 @@ impl ChainManager { chain_info.highest_block_checkpoint = beacon; chain_info.highest_vrf_output = vrf_input; + let miner_pkh = block.block_header.proof.proof.pkh(); + + // Reset the coin age of the miner for all staked coins + if get_protocol_version(Some(block_epoch)) == ProtocolVersion::V2_0 { + let _ = stakes.reset_age(miner_pkh, Capability::Mining, current_epoch, 1); + + let epoch_constants = self.epoch_constants.unwrap(); + let utxo_diff = + UtxoDiff::new(&self.chain_state.unspent_outputs_pool, block_epoch); + + let mut transaction_fees = 0; + for vt_tx in &block.txns.value_transfer_txns { + transaction_fees += + vt_transaction_fee(vt_tx, &utxo_diff, current_epoch, epoch_constants) + .unwrap_or_default(); + } + for dr_tx in &block.txns.data_request_txns { + transaction_fees += + dr_transaction_fee(dr_tx, &utxo_diff, current_epoch, epoch_constants) + .unwrap_or_default(); + } + for st_tx in &block.txns.stake_txns { + transaction_fees += + st_transaction_fee(st_tx, &utxo_diff, current_epoch, epoch_constants) + .unwrap_or_default(); + } + for ut_tx in &block.txns.unstake_txns { + transaction_fees += ut_transaction_fee(ut_tx).unwrap_or_default(); + } + + let _ = stakes.add_reward( + miner_pkh, + Wit::from(50_000_000_000) + Wit::from(transaction_fees), + current_epoch, + ); + } + let rep_info = update_pools( &block, &mut self.chain_state.unspent_outputs_pool, @@ -1009,12 +1069,6 @@ impl ChainManager { self.sm_state, ); - let miner_pkh = block.block_header.proof.proof.pkh(); - - // Reset the coin age of the miner for all staked coins - let key = StakeKey::from((miner_pkh, miner_pkh)); - let _ = stakes.reset_age(key, Capability::Mining, current_epoch); - // Do not update reputation or stakes when consolidating genesis block if block_hash != chain_info.consensus_constants.genesis_hash { update_reputation( @@ -1065,7 +1119,7 @@ impl ChainManager { let reveals = self .chain_state .data_request_pool - .update_data_request_stages(); + .update_data_request_stages(Some(validator_count), Some(current_epoch)); for reveal in reveals { // Send AddTransaction message to self @@ -1091,7 +1145,7 @@ impl ChainManager { let reveals = self .chain_state .data_request_pool - .update_data_request_stages(); + .update_data_request_stages(Some(validator_count), Some(current_epoch)); for reveal in reveals { // Send AddTransaction message to self @@ -1117,7 +1171,7 @@ impl ChainManager { let reveals = self .chain_state .data_request_pool - .update_data_request_stages(); + .update_data_request_stages(Some(validator_count), Some(current_epoch)); show_info_dr(&self.chain_state.data_request_pool, &block); @@ -1225,13 +1279,18 @@ impl ChainManager { }) .into_actor(act) }) - .map_ok(|res, act, ctx| { + .map_ok(move |res, act, ctx| { // Broadcast vote between one and ("superblock_period" - 5) epoch checkpoints later. // This is used to prevent the race condition described in issue #1573 // It is also used to spread the CPU load by checking superblock votes along // the superblock period with a safe margin let mut rng = rand::thread_rng(); - let checkpoints_period = act.consensus_constants().checkpoints_period; + // Should be safe here to just call unwraps + let checkpoints_period = act + .epoch_constants + .unwrap() + .get_epoch_period(current_epoch) + .unwrap(); let superblock_period = act.consensus_constants().superblock_period; let end_range = if superblock_period > 5 { (superblock_period - 5) * checkpoints_period @@ -1513,6 +1572,7 @@ impl ChainManager { required_reward_collateral_ratio, &active_wips, chain_info.consensus_constants.superblock_period, + &self.chain_state.stakes, )) .into_actor(self) .and_then(|fee, act, _ctx| { @@ -2063,6 +2123,7 @@ impl ChainManager { &consensus_constants, &active_wips, None, + &act.chain_state.stakes, ); async { // Short-circuit if validation failed @@ -2871,6 +2932,7 @@ pub fn process_validations( consensus_constants, active_wips, transaction_visitor, + stakes, )?; if !resynchronizing { @@ -2954,7 +3016,17 @@ fn update_pools( ) -> ReputationInfo { let mut rep_info = ReputationInfo::new(); + let mut data_requests_with_too_many_witnesses = HashSet::::new(); for ta_tx in &block.txns.tally_txns { + // Track data requests which were already processed with a TooManyWitnesses error + if RadonTypes::try_from(ta_tx.tally.as_slice()) + == Ok(RadonTypes::RadonError( + RadonError::try_from(TooManyWitnesses).unwrap(), + )) + { + data_requests_with_too_many_witnesses.insert(ta_tx.dr_pointer); + } + // Process tally transactions: used to update reputation engine rep_info.update(ta_tx, data_request_pool, own_pkh, node_stats); @@ -2971,6 +3043,11 @@ fn update_pools( } for dr_tx in &block.txns.data_request_txns { + if data_requests_with_too_many_witnesses.contains(&dr_tx.hash()) { + log::debug!("Skipping data request {} as it was already processed with a TooManyWitnesses error", dr_tx.hash()); + transactions_pool.dr_remove(dr_tx); + continue; + } if let Err(e) = data_request_pool.process_data_request( dr_tx, block.block_header.beacon.checkpoint, @@ -3543,7 +3620,7 @@ mod tests { dr_pool.process_commit(&co_tx, &Hash::default()).unwrap(); dr_pool.process_commit(&co_tx2, &Hash::default()).unwrap(); dr_pool.process_commit(&co_tx3, &Hash::default()).unwrap(); - dr_pool.update_data_request_stages(); + dr_pool.update_data_request_stages(None, None); dr_pool.process_reveal(&re_tx1, &Hash::default()).unwrap(); dr_pool.process_reveal(&re_tx2, &Hash::default()).unwrap(); @@ -4002,6 +4079,8 @@ mod tests { chain_manager.epoch_constants = Some(EpochConstants { checkpoint_zero_timestamp: 0, checkpoints_period: 1_000, + checkpoint_zero_timestamp_v2: i64::MAX, + checkpoints_period_v2: 1, }); chain_manager.chain_state.chain_info = Some(ChainInfo { environment: Environment::default(), @@ -4043,11 +4122,14 @@ mod tests { Reputation(0), vrf_hash_1, false, + Default::default(), block_2.hash(), Reputation(0), vrf_hash_2, false, + Default::default(), &VrfSlots::new(vec![Hash::default()]), + ProtocolVersion::V1_7, ), Ordering::Greater ); @@ -4128,6 +4210,8 @@ mod tests { chain_manager.epoch_constants = Some(EpochConstants { checkpoint_zero_timestamp: 0, checkpoints_period: 1_000, + checkpoint_zero_timestamp_v2: i64::MAX, + checkpoints_period_v2: 1, }); chain_manager.chain_state.chain_info = Some(ChainInfo { environment: Environment::default(), diff --git a/node/src/actors/epoch_manager/mod.rs b/node/src/actors/epoch_manager/mod.rs index e3c7ffdde..a87331f1d 100644 --- a/node/src/actors/epoch_manager/mod.rs +++ b/node/src/actors/epoch_manager/mod.rs @@ -7,6 +7,8 @@ use rand::Rng; use witnet_data_structures::{ chain::{Epoch, EpochConstants}, error::EpochCalculationError, + get_protocol_version_activation_epoch, get_protocol_version_period, + proto::versioning::ProtocolVersion, }; use witnet_util::timestamp::{ duration_between_timestamps, get_timestamp, get_timestamp_nanos, update_global_timestamp, @@ -85,15 +87,15 @@ impl EpochManager { pub fn set_checkpoint_zero_and_period( &mut self, checkpoint_zero_timestamp: i64, - mut checkpoints_period: u16, + checkpoints_period: u16, + checkpoint_zero_timestamp_v2: i64, + checkpoints_period_v2: u16, ) { - if checkpoints_period == 0 { - log::warn!("Setting the checkpoint period to the minimum value of 1 second"); - checkpoints_period = 1; - } self.constants = Some(EpochConstants { checkpoint_zero_timestamp, checkpoints_period, + checkpoint_zero_timestamp_v2, + checkpoints_period_v2, }); } /// Calculate the last checkpoint (current epoch) at the supplied timestamp @@ -113,7 +115,10 @@ impl EpochManager { pub fn epoch_timestamp(&self, epoch: Epoch) -> EpochResult { match &self.constants { // Calculate (period * epoch + zero) with overflow checks - Some(x) => Ok(x.epoch_timestamp(epoch)?), + Some(x) => { + let (timestamp, _) = x.epoch_timestamp(epoch)?; + Ok(timestamp) + } None => Err(EpochManagerError::UnknownEpochConstants), } } @@ -122,9 +127,15 @@ impl EpochManager { config_mngr::get() .into_actor(self) .and_then(|config, act, ctx| { + let checkpoint_zero_timestamp_v2 = + config.consensus_constants.checkpoint_zero_timestamp + + get_protocol_version_activation_epoch(ProtocolVersion::V2_0) as i64 + * config.consensus_constants.checkpoints_period as i64; act.set_checkpoint_zero_and_period( config.consensus_constants.checkpoint_zero_timestamp, config.consensus_constants.checkpoints_period, + checkpoint_zero_timestamp_v2, + get_protocol_version_period(ProtocolVersion::V2_0), ); log::info!( "Checkpoint zero timestamp: {}, checkpoints period: {}", diff --git a/node/src/actors/inventory_manager/handlers.rs b/node/src/actors/inventory_manager/handlers.rs index d2a798372..ef5d2664b 100644 --- a/node/src/actors/inventory_manager/handlers.rs +++ b/node/src/actors/inventory_manager/handlers.rs @@ -465,7 +465,7 @@ mod tests { unspent_outputs_pool.insert(output1_pointer, output1, 0); assert!(unspent_outputs_pool.contains_key(&output1_pointer)); - let dr_pool = DataRequestPool::default(); + let mut dr_pool = DataRequestPool::default(); // Fields required to mine a block let block_beacon = CheckpointBeacon { @@ -478,7 +478,7 @@ mod tests { let active_wips = ActiveWips::default(); let (block_header, txns) = build_block( - (&mut transaction_pool, &unspent_outputs_pool, &dr_pool), + (&mut transaction_pool, &unspent_outputs_pool, &mut dr_pool), max_vt_weight, max_dr_weight, max_st_weight, @@ -496,6 +496,7 @@ mod tests { HALVING_PERIOD, 0, &active_wips, + None, ); Block::new(block_header, KeyedSignature::default(), txns) diff --git a/node/src/actors/json_rpc/api.rs b/node/src/actors/json_rpc/api.rs index 5423809c5..c0096c4f3 100644 --- a/node/src/actors/json_rpc/api.rs +++ b/node/src/actors/json_rpc/api.rs @@ -26,7 +26,8 @@ use witnet_data_structures::{ tapi::ActiveWips, Block, DataRequestOutput, Epoch, Hash, Hashable, KeyedSignature, PublicKeyHash, RADType, StakeOutput, StateMachine, SyncStatus, }, - get_environment, + get_environment, get_protocol_version, + proto::versioning::ProtocolVersion, staking::prelude::*, transaction::Transaction, vrf::VrfMessage, @@ -626,8 +627,8 @@ pub async fn get_block_chain(params: Result, Error>) format!( "out of bounds: {} must be between -{} and {} inclusive", x, - u32::max_value(), - u32::max_value() + u32::MAX, + u32::MAX ) })?; @@ -799,14 +800,44 @@ pub async fn get_block(params: Params) -> Result { .map(|txn| txn.hash()) .collect(); - Some(serde_json::json!({ + let mut hashes = Some(serde_json::json!({ "mint" : output.txns.mint.hash(), "value_transfer" : vtt_hashes, "data_request" : drt_hashes, "commit" : ct_hashes, "reveal" : rt_hashes, "tally" : tt_hashes - })) + })); + + if get_protocol_version(Some(block_epoch)) == ProtocolVersion::V2_0 { + let st_hashes: Vec<_> = output + .txns + .stake_txns + .iter() + .map(|txn| txn.hash()) + .collect(); + if let Some(ref mut hashes) = hashes { + hashes + .as_object_mut() + .expect("The result of getBlock should be an object") + .insert("stake".to_string(), serde_json::json!(st_hashes)); + } + + let ut_hashes: Vec<_> = output + .txns + .unstake_txns + .iter() + .map(|txn| txn.hash()) + .collect(); + if let Some(ref mut hashes) = hashes { + hashes + .as_object_mut() + .expect("The result of getBlock should be an object") + .insert("unstake".to_string(), serde_json::json!(ut_hashes)); + } + } + + hashes } else { None }; @@ -825,10 +856,40 @@ pub async fn get_block(params: Params) -> Result { .iter() .map(|txn| txn.weight()) .collect(); - Some(serde_json::json!({ - "value_transfer" : vtt_weights, - "data_request" : drt_weights, - })) + + let mut weights = Some(serde_json::json!({ + "value_transfer": vtt_weights, + "data_request": drt_weights, + })); + + if get_protocol_version(Some(block_epoch)) == ProtocolVersion::V2_0 { + let st_weights: Vec<_> = output + .txns + .stake_txns + .iter() + .map(|txn| txn.weight()) + .collect(); + if let Some(ref mut weights) = weights { + weights + .as_object_mut() + .expect("The result of getBlock should be an object") + .insert("stake".to_string(), st_weights.into()); + } + + let ut_weights: Vec<_> = output + .txns + .unstake_txns + .iter() + .map(|txn| txn.weight()) + .collect(); + if let Some(ref mut weights) = weights { + weights + .as_object_mut() + .expect("The result of getBlock should be an object") + .insert("unstake".to_string(), ut_weights.into()); + } + } + weights } else { None }; @@ -2107,7 +2168,7 @@ pub async fn authorize_stake(params: Result) -> JsonRpcRe .await } -/// Param for query_stakes +/// Param for query_stakes #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum QueryStakesArgument { /// To query by stake validator diff --git a/node/src/actors/messages.rs b/node/src/actors/messages.rs index 7522b8f17..ca1b395da 100644 --- a/node/src/actors/messages.rs +++ b/node/src/actors/messages.rs @@ -27,7 +27,7 @@ use witnet_data_structures::{ }, fee::{deserialize_fee_backwards_compatible, Fee}, radon_report::RadonReport, - staking::{aux::StakeKey, stakes::QueryStakesKey}, + staking::prelude::*, transaction::{ CommitTransaction, DRTransaction, RevealTransaction, StakeTransaction, Transaction, UnstakeTransaction, VTTransaction, @@ -1036,6 +1036,8 @@ pub struct ResolveRA { /// Active Witnet protocol improvements as of the current epoch. /// Used to select the correct version of the validation logic. pub active_wips: ActiveWips, + /// Whether too many witnesses have been requested. + pub too_many_witnesses: bool, } /// Message for running the tally step of a data request. @@ -1052,6 +1054,8 @@ pub struct RunTally { /// Active Witnet protocol improvements as of the block that will include this tally. /// Used to select the correct version of the validation logic. pub active_wips: ActiveWips, + /// Variable indicating if the amount of requested witnesses exceeds a certain fraction of the amount of stakers + pub too_many_witnesses: bool, } impl Message for ResolveRA { diff --git a/node/src/actors/rad_manager/handlers.rs b/node/src/actors/rad_manager/handlers.rs index 32c525112..dec5e741e 100644 --- a/node/src/actors/rad_manager/handlers.rs +++ b/node/src/actors/rad_manager/handlers.rs @@ -84,8 +84,13 @@ impl Handler for RadManager { // Evaluate tally precondition to ensure that at least 20% of the data sources are not errors. // This stage does not need to evaluate the postcondition. - let clause_result = - evaluate_tally_precondition_clause(retrieve_responses, 0.2, 1, &msg.active_wips); + let clause_result = evaluate_tally_precondition_clause( + retrieve_responses, + 0.2, + 1, + &msg.active_wips, + false, + ); match clause_result { Ok(TallyPreconditionClauseResult::MajorityOfValues { values, @@ -135,6 +140,7 @@ impl Handler for RadManager { msg.min_consensus_ratio, msg.commits_count, &msg.active_wips, + msg.too_many_witnesses, ) }; @@ -273,6 +279,7 @@ mod tests { rad_request, timeout: None, active_wips, + too_many_witnesses: false, }) .await .unwrap() @@ -312,6 +319,7 @@ mod tests { rad_request, timeout: None, active_wips, + too_many_witnesses: false, }) .await .unwrap() diff --git a/node/src/actors/session/handlers.rs b/node/src/actors/session/handlers.rs index c7aafa6e8..6deac268e 100644 --- a/node/src/actors/session/handlers.rs +++ b/node/src/actors/session/handlers.rs @@ -81,6 +81,7 @@ enum HandshakeError { impl WriteHandler for Session {} /// Payload for the notification for a specific epoch +#[allow(dead_code)] #[derive(Debug)] pub struct EpochPayload; diff --git a/node/src/actors/sessions_manager/actor.rs b/node/src/actors/sessions_manager/actor.rs index a23863846..062dfeb36 100644 --- a/node/src/actors/sessions_manager/actor.rs +++ b/node/src/actors/sessions_manager/actor.rs @@ -1,7 +1,10 @@ use super::SessionsManager; use crate::config_mngr; use actix::prelude::*; -use witnet_data_structures::chain::EpochConstants; +use witnet_data_structures::{ + chain::EpochConstants, get_protocol_version_activation_epoch, get_protocol_version_period, + proto::versioning::ProtocolVersion, +}; use witnet_util::timestamp::get_timestamp; @@ -39,16 +42,18 @@ impl Actor for SessionsManager { .set_range_limit(config.connections.reject_sybil_inbounds_range_limit); // Initialized epoch from config - let mut checkpoints_period = config.consensus_constants.checkpoints_period; + let checkpoints_period = config.consensus_constants.checkpoints_period; let checkpoint_zero_timestamp = config.consensus_constants.checkpoint_zero_timestamp; - if checkpoints_period == 0 { - log::warn!("Setting the checkpoint period to the minimum value of 1 second"); - checkpoints_period = 1; - } + let checkpoint_zero_timestamp_v2 = checkpoint_zero_timestamp + + get_protocol_version_activation_epoch(ProtocolVersion::V2_0) as i64 + * checkpoints_period as i64; + let checkpoints_period_v2 = get_protocol_version_period(ProtocolVersion::V2_0); let epoch_constants = EpochConstants { checkpoint_zero_timestamp, checkpoints_period, + checkpoint_zero_timestamp_v2, + checkpoints_period_v2, }; act.current_epoch = epoch_constants .epoch_at(get_timestamp()) diff --git a/node/tests/actors/epoch_manager.rs b/node/tests/actors/epoch_manager.rs index c3b1ac527..63f9566da 100644 --- a/node/tests/actors/epoch_manager.rs +++ b/node/tests/actors/epoch_manager.rs @@ -5,8 +5,15 @@ use witnet_node::actors::epoch_manager::{EpochManager, EpochManagerError}; fn epoch_zero_range() { let zero = 1000; let period = 90; + let zero_v2 = i64::MAX; + let period_v2 = 1; let mut em = EpochManager::default(); - em.set_checkpoint_zero_and_period(zero, u16::try_from(period).unwrap()); + em.set_checkpoint_zero_and_period( + zero, + u16::try_from(period).unwrap(), + zero_v2, + u16::try_from(period_v2).unwrap(), + ); // [1000, 1089] are in epoch 0 for now in zero..zero + period { @@ -28,8 +35,10 @@ fn epoch_zero_in_the_future() { let zero = 1000; let now = 999; let period = 90u16; + let zero_v2 = i64::MAX; + let period_v2 = 1u16; let mut em = EpochManager::default(); - em.set_checkpoint_zero_and_period(zero, period); + em.set_checkpoint_zero_and_period(zero, period, zero_v2, period_v2); assert_eq!( em.epoch_at(now), @@ -46,3 +55,56 @@ fn epoch_unknown() { Err(EpochManagerError::UnknownEpochConstants) ); } + +#[test] +fn epoch_v2() { + let zero = 1000; + let period = 50; + let zero_v2 = 2000; + let period_v2 = 25; + let mut em = EpochManager::default(); + em.set_checkpoint_zero_and_period( + zero, + u16::try_from(period).unwrap(), + zero_v2, + u16::try_from(period_v2).unwrap(), + ); + + // [1000, 1049] are in epoch 0 + for now in zero..zero + period { + assert_eq!(em.epoch_at(now), Ok(0), "Error at {}", now); + } + + // 1050 is the start of epoch 1 + assert_eq!( + em.epoch_at(zero + period), + Ok(1), + "Error at {}", + zero + period + ); + + // [1100, 1149] is part of period 2 + for now in zero + 2 * period..zero + 3 * period { + assert_eq!(em.epoch_at(now), Ok(2), "Error at {}", now); + } + + // [1950, 1050] are part of epoch 19 and more + for now in zero + 19 * period..zero + 21 * period { + if now < 2000 { + assert_eq!(em.epoch_at(now), Ok(19), "Error at {}", now); + } else if now < 2025 { + assert_eq!(em.epoch_at(now), Ok(20), "Error at {}", now); + } else { + assert_eq!(em.epoch_at(now), Ok(21), "Error at {}", now); + } + } + + // Epoch 1 to 20, block time of 50 seconds + assert_eq!(em.epoch_timestamp(0), Ok(1000), "Error at {}", 1000); + assert_eq!(em.epoch_timestamp(10), Ok(1500), "Error at {}", 1500); + assert_eq!(em.epoch_timestamp(20), Ok(2000), "Error at {}", 2000); + // Epoch 21 to 40, block time of 25 seconds + assert_eq!(em.epoch_timestamp(21), Ok(2025), "Error at {}", 2025); + assert_eq!(em.epoch_timestamp(30), Ok(2250), "Error at {}", 2250); + assert_eq!(em.epoch_timestamp(40), Ok(2500), "Error at {}", 2500); +} diff --git a/p2p/tests/sessions.rs b/p2p/tests/sessions.rs index ef3766e5f..0eb14f3f7 100644 --- a/p2p/tests/sessions.rs +++ b/p2p/tests/sessions.rs @@ -375,15 +375,13 @@ fn p2p_sessions_consolidate() { assert!(sessions .outbound_unconsolidated .collection - .get(&outbound_address) - .is_some()); + .contains_key(&outbound_address)); assert_eq!(sessions.outbound_consolidated.collection.len(), 0); assert_eq!(sessions.inbound_consolidated.collection.len(), 0); assert!(sessions .inbound_unconsolidated .collection - .get(&inbound_address) - .is_some()); + .contains_key(&inbound_address)); // Consolidate session assert!(sessions @@ -397,14 +395,12 @@ fn p2p_sessions_consolidate() { assert!(sessions .outbound_consolidated .collection - .get(&outbound_address) - .is_some()); + .contains_key(&outbound_address)); assert_eq!(sessions.outbound_unconsolidated.collection.len(), 0); assert!(sessions .inbound_consolidated .collection - .get(&inbound_address) - .is_some()); + .contains_key(&inbound_address)); } /// Check the consensus of outbound consolidated sessions @@ -434,8 +430,7 @@ fn p2p_sessions_consensus() { assert!(sessions .outbound_consolidated .collection - .get(&outbound_address) - .is_some()); + .contains_key(&outbound_address)); assert_eq!(sessions.outbound_unconsolidated.collection.len(), 0); assert_eq!(sessions.outbound_consolidated.collection.len(), 1); diff --git a/partial_struct/tests/partial_struct_derive.rs b/partial_struct/tests/partial_struct_derive.rs index 84e368498..abfafa718 100644 --- a/partial_struct/tests/partial_struct_derive.rs +++ b/partial_struct/tests/partial_struct_derive.rs @@ -5,6 +5,7 @@ use partial_struct::PartialStruct; fn test_partial_derive() { #[derive(PartialStruct, Debug, Clone, PartialEq)] #[partial_struct(derive(Debug, Clone, PartialEq))] + #[allow(dead_code)] struct Obj; let p = PartialObj; diff --git a/rad/src/conditions.rs b/rad/src/conditions.rs index 181888b5b..b550ebb37 100644 --- a/rad/src/conditions.rs +++ b/rad/src/conditions.rs @@ -44,7 +44,12 @@ pub fn evaluate_tally_precondition_clause( minimum_consensus: f64, num_commits: usize, active_wips: &ActiveWips, + too_many_witnesses: bool, ) -> Result { + // Short-circuit if there are not enough stakers + if too_many_witnesses { + return Err(RadError::TooManyWitnesses); + } // Short-circuit if there were no commits if num_commits == 0 { return Err(RadError::InsufficientCommits); diff --git a/rad/src/error.rs b/rad/src/error.rs index a413f47c4..ed293f0e8 100644 --- a/rad/src/error.rs +++ b/rad/src/error.rs @@ -293,6 +293,9 @@ pub enum RadError { /// No commits received #[fail(display = "Insufficient commits received")] InsufficientCommits, + /// No commits received + #[fail(display = "Too many witnesses request for the amount of stakers")] + TooManyWitnesses, /// No reveals received #[fail(display = "No reveals received")] NoReveals, @@ -435,6 +438,7 @@ impl RadError { RadonErrors::ScriptTooManyCalls => RadError::ScriptTooManyCalls, RadonErrors::Overflow => RadError::Overflow, RadonErrors::InsufficientCommits => RadError::InsufficientCommits, + RadonErrors::TooManyWitnesses => RadError::TooManyWitnesses, RadonErrors::NoReveals => RadError::NoReveals, RadonErrors::SourceScriptNotCBOR => RadError::SourceScriptNotCBOR, RadonErrors::SourceScriptNotArray => RadError::SourceScriptNotArray, @@ -585,6 +589,7 @@ impl RadError { RadError::Overflow => RadonErrors::Overflow, RadError::DivisionByZero => RadonErrors::DivisionByZero, RadError::InsufficientCommits => RadonErrors::InsufficientCommits, + RadError::TooManyWitnesses => RadonErrors::TooManyWitnesses, RadError::NoReveals => RadonErrors::NoReveals, RadError::RetrieveTimeout => RadonErrors::RetrieveTimeout, RadError::InsufficientConsensus { .. } => RadonErrors::InsufficientConsensus, diff --git a/rad/src/lib.rs b/rad/src/lib.rs index 3c3b836d0..b2dd74ca2 100644 --- a/rad/src/lib.rs +++ b/rad/src/lib.rs @@ -64,6 +64,7 @@ pub fn try_data_request( settings: RadonScriptExecutionSettings, inputs_injection: Option<&[&str]>, witnessing: Option>, + too_many_witnesses: bool, ) -> RADRequestExecutionReport { #[cfg(not(test))] let active_wips = current_active_wips(); @@ -116,6 +117,7 @@ pub fn try_data_request( 0.2, 1, ¤t_active_wips(), + too_many_witnesses, ); let aggregation_report = match clause_result { @@ -1550,6 +1552,7 @@ mod tests { RadonScriptExecutionSettings::enable_all(), None, None, + false, ); let tally_result = report.tally.into_inner(); @@ -1593,6 +1596,7 @@ mod tests { RadonScriptExecutionSettings::enable_all(), None, None, + false, ); let tally_result = report.tally.into_inner(); @@ -1645,6 +1649,7 @@ mod tests { RadonScriptExecutionSettings::enable_all(), None, None, + false, ); let tally_result = report.tally.into_inner(); @@ -1697,6 +1702,7 @@ mod tests { RadonScriptExecutionSettings::enable_all(), None, None, + false, ); let tally_result = report.tally.into_inner(); @@ -1749,6 +1755,7 @@ mod tests { RadonScriptExecutionSettings::enable_all(), None, None, + false, ); let tally_result = report.tally.into_inner(); @@ -1814,6 +1821,7 @@ mod tests { RadonScriptExecutionSettings::enable_all(), Some(&["1", "1", "error"]), None, + false, ); let tally_result = report.tally.into_inner(); diff --git a/rad/src/operators/integer.rs b/rad/src/operators/integer.rs index 407522dfa..3fdaaa2d3 100644 --- a/rad/src/operators/integer.rs +++ b/rad/src/operators/integer.rs @@ -1,4 +1,4 @@ -use std::{borrow::ToOwned, convert::TryFrom, i128}; +use std::{borrow::ToOwned, convert::TryFrom}; use serde_cbor::value::{from_value, Value}; @@ -122,7 +122,7 @@ fn test_integer_absolute() { assert_eq!(absolute(&positive_integer).unwrap(), positive_integer); assert_eq!(absolute(&negative_integer).unwrap(), positive_integer); assert_eq!( - absolute(&RadonInteger::from(i128::min_value())) + absolute(&RadonInteger::from(i128::MIN)) .unwrap_err() .to_string(), "Overflow error".to_string(), @@ -157,7 +157,7 @@ fn test_integer_multiply() { let value = Value::Integer(3); assert_eq!( - multiply(&RadonInteger::from(i128::max_value()), &[value]) + multiply(&RadonInteger::from(i128::MAX), &[value]) .unwrap_err() .to_string(), "Overflow error".to_string(), @@ -215,7 +215,7 @@ fn test_integer_negate() { assert_eq!(negate(&negative_integer).unwrap(), positive_integer); assert_eq!( - negate(&RadonInteger::from(i128::min_value())) + negate(&RadonInteger::from(i128::MIN)) .unwrap_err() .to_string(), "Overflow error".to_string(), @@ -259,7 +259,7 @@ fn test_integer_power() { assert_eq!(power(&rad_int, &[value]).unwrap(), RadonInteger::from(1000)); - let rad_int = RadonInteger::from(i128::max_value()); + let rad_int = RadonInteger::from(i128::MAX); let value = Value::Integer(3); assert_eq!( power(&rad_int, &[value]).unwrap_err().to_string(), diff --git a/rad/src/reducers/average.rs b/rad/src/reducers/average.rs index 6709920ba..3b1ded748 100644 --- a/rad/src/reducers/average.rs +++ b/rad/src/reducers/average.rs @@ -63,7 +63,7 @@ pub fn mean(input: &RadonArray, return_policy: MeanReturnPolicy) -> Result Ok(RadonTypes::from(RadonFloat::from(std::f64::NAN))), + None => Ok(RadonTypes::from(RadonFloat::from(f64::NAN))), Some(RadonTypes::Float(_)) => { let sum = value.iter().try_fold(0f64, |sum, item| match item { RadonTypes::Float(f64_value) => Ok(sum + f64_value.value()), @@ -244,7 +244,7 @@ mod tests { fn test_average_mean_empty() { let input = RadonArray::from(vec![]); let output = mean(&input, MeanReturnPolicy::ReturnFloat).unwrap(); - assert_eq!(output, RadonTypes::from(RadonFloat::from(std::f64::NAN))); + assert_eq!(output, RadonTypes::from(RadonFloat::from(f64::NAN))); } #[test] diff --git a/rad/src/reducers/deviation.rs b/rad/src/reducers/deviation.rs index da4fb6809..9ae078229 100644 --- a/rad/src/reducers/deviation.rs +++ b/rad/src/reducers/deviation.rs @@ -17,7 +17,7 @@ pub fn standard(input: &RadonArray) -> Result { let value_len = value.len(); match value.first() { - None => Ok(RadonTypes::from(RadonFloat::from(std::f64::NAN))), + None => Ok(RadonTypes::from(RadonFloat::from(f64::NAN))), Some(RadonTypes::Float(_)) => { let mean_value = mean(input, MeanReturnPolicy::ReturnFloat)?; let mean_float = if let RadonTypes::Float(f) = mean_value { @@ -235,7 +235,7 @@ mod tests { fn test_operate_deviation_standard_empty() { let input = RadonArray::from(vec![]); let output = standard(&input).unwrap(); - assert_eq!(output, RadonTypes::from(RadonFloat::from(std::f64::NAN))); + assert_eq!(output, RadonTypes::from(RadonFloat::from(f64::NAN))); } #[test] diff --git a/rad/src/reducers/mod.rs b/rad/src/reducers/mod.rs index 89f96e726..829f2fc32 100644 --- a/rad/src/reducers/mod.rs +++ b/rad/src/reducers/mod.rs @@ -161,9 +161,7 @@ mod tests { assert_eq!(output, expected); // Deactivate WIP-0017 - active_wips - .active_wips - .remove(&"WIP0017-0018-0019".to_string()); + active_wips.active_wips.remove("WIP0017-0018-0019"); context.active_wips = Some(active_wips); let expected_err = RadError::UnsupportedReducer { diff --git a/src/cli/mod.rs b/src/cli/mod.rs index a48344572..1c76f8a3d 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -58,9 +58,9 @@ pub fn exec( witnet_data_structures::set_environment(config.environment); log::debug!("{:#?}", config); - for (version, epoch) in config.protocol.iter() { - if let Some(epoch) = epoch { - register_protocol_version(version, epoch); + for (version, epoch_period) in config.protocol.iter() { + if let Some((epoch, period)) = epoch_period { + register_protocol_version(version, epoch, period); } } diff --git a/src/cli/node/json_rpc_client.rs b/src/cli/node/json_rpc_client.rs index 38094b119..0012d728c 100644 --- a/src/cli/node/json_rpc_client.rs +++ b/src/cli/node/json_rpc_client.rs @@ -1423,6 +1423,7 @@ pub fn data_request_report( non_error_min, commits_count, &active_wips, + false, ); local_tally = Some(report.into_inner()); diff --git a/toolkit/src/data_requests.rs b/toolkit/src/data_requests.rs index 23577dd7f..84dd1b9ed 100644 --- a/toolkit/src/data_requests.rs +++ b/toolkit/src/data_requests.rs @@ -52,7 +52,7 @@ pub fn try_data_request( } else { RadonScriptExecutionSettings::disable_all() }; - let report = witnet_rad::try_data_request(request, settings, None, None); + let report = witnet_rad::try_data_request(request, settings, None, None, false); Ok(report) } diff --git a/validations/benches/reppoe.rs b/validations/benches/reppoe.rs index 15803bf11..c18f258fa 100644 --- a/validations/benches/reppoe.rs +++ b/validations/benches/reppoe.rs @@ -7,8 +7,6 @@ use witnet_data_structures::chain::{ Alpha, Environment, PublicKeyHash, Reputation, ReputationEngine, }; -// To benchmark the old algorithm, comment out this import: -use witnet_validations::validations; // To benchmark the old algorithm, comment out the line that says cfg any: #[cfg(any())] mod validations { @@ -31,7 +29,7 @@ mod validations { // The probability of being eligible is `factor / total_active_reputation` let factor = u64::from(num_witnesses) * my_reputation; - let max = u64::max_value(); + let max = u64::MAX; // Check for overflow: when the probability is more than 100%, cap it to 100% let target = if factor >= total_active_reputation { max @@ -87,7 +85,7 @@ fn be( } // Initialize cache rep_eng.total_active_reputation(); - validations::calculate_reppoe_threshold( + witnet_validations::eligibility::legacy::calculate_reppoe_threshold( &rep_eng, &my_pkh, num_witnesses, @@ -101,7 +99,7 @@ fn be( if invalidate_threshold_cache { rep_eng.clear_threshold_cache(); } - validations::calculate_reppoe_threshold( + witnet_validations::eligibility::legacy::calculate_reppoe_threshold( &rep_eng, &my_pkh, num_witnesses, diff --git a/validations/src/eligibility/current.rs b/validations/src/eligibility/current.rs index b4c48f247..3819d1626 100644 --- a/validations/src/eligibility/current.rs +++ b/validations/src/eligibility/current.rs @@ -1,8 +1,10 @@ use std::{ fmt::{Debug, Display}, - ops::{Add, Div, Mul, Sub}, + iter::Sum, + ops::{Add, Div, Mul, Rem, Sub}, }; -use witnet_data_structures::staking::prelude::*; + +use witnet_data_structures::{staking::prelude::*, wit::PrecisionLoss}; const MINING_REPLICATION_FACTOR: usize = 4; const WITNESSING_MAX_ROUNDS: usize = 4; @@ -37,7 +39,7 @@ impl From for Eligible { pub trait Eligibility where Address: Debug + Display + Sync + Send + 'static, - Coins: Debug + Display + Sync + Send + 'static, + Coins: Debug + Display + Sync + Send + Sum + 'static, Epoch: Debug + Display + Sync + Send + 'static, { /// Tells whether a VRF proof meets the requirements to become eligible for mining. Unless an error occurs, returns @@ -45,19 +47,19 @@ where /// `IneligibilityReason`. fn mining_eligibility( &self, - key: ISK, + validator: ISK, epoch: Epoch, ) -> StakesResult where - ISK: Into>; + ISK: Into
; /// Tells whether a VRF proof meets the requirements to become eligible for mining. Because this function returns a /// simple `bool`, it is best-effort: both lack of eligibility and any error cases are mapped to `false`. - fn mining_eligibility_bool(&self, key: ISK, epoch: Epoch) -> bool + fn mining_eligibility_bool(&self, validator: ISK, epoch: Epoch) -> bool where - ISK: Into>, + ISK: Into
, { - matches!(self.mining_eligibility(key, epoch), Ok(Eligible::Yes)) + matches!(self.mining_eligibility(validator, epoch), Ok(Eligible::Yes)) } /// Tells whether a VRF proof meets the requirements to become eligible for witnessing. Unless an error occurs, @@ -65,28 +67,28 @@ where /// `IneligibilityReason`. fn witnessing_eligibility( &self, - key: ISK, + validator: ISK, epoch: Epoch, witnesses: u8, round: u8, ) -> StakesResult where - ISK: Into>; + ISK: Into
; /// Tells whether a VRF proof meets the requirements to become eligible for witnessing. Because this function /// returns a simple `bool`, it is best-effort: both lack of eligibility and any error cases are mapped to `false`. fn witnessing_eligibility_bool( &self, - key: ISK, + validator: ISK, epoch: Epoch, witnesses: u8, round: u8, ) -> bool where - ISK: Into>, + ISK: Into
, { matches!( - self.witnessing_eligibility(key, epoch, witnesses, round), + self.witnessing_eligibility(validator, epoch, witnesses, round), Ok(Eligible::Yes) ) } @@ -108,8 +110,12 @@ where + Sub + Mul + Mul + + Div + + Rem + + PrecisionLoss + Sync + Send + + Sum + 'static, Epoch: Copy + Debug @@ -117,6 +123,8 @@ where + Display + num_traits::Saturating + Sub + + Add + + Div + From + Sync + Send @@ -128,18 +136,20 @@ where + Sub + Mul + Div - + From, + + From + + Sum + + Display, u64: From + From, { fn mining_eligibility( &self, - key: ISK, + validator: ISK, epoch: Epoch, ) -> StakesResult where - ISK: Into>, + ISK: Into
, { - let power = match self.query_power(key, Capability::Mining, epoch) { + let power = match self.query_power(validator, Capability::Mining, epoch) { Ok(p) => p, Err(e) => { // Early exit if the stake key does not exist @@ -150,26 +160,13 @@ where } }; - let mut rank = self.rank(Capability::Mining, epoch); - - // Requirement no. 3 from the WIP: - // "the mining power of the block proposer is greater than `max_power / rf`" - // (This goes before no. 2 because it is cheaper, computation-wise, and we want validations to exit ASAP) - // TODO: verify if defaulting to 0 makes sense - let (_, max_power) = rank.next().unwrap_or_default(); - let threshold = max_power / Power::from(MINING_REPLICATION_FACTOR as u64); - if power <= threshold { - return Ok(IneligibilityReason::InsufficientPower.into()); - } - // Requirement no. 2 from the WIP: // "the mining power of the block proposer is in the `rf / stakers`th quantile among the mining powers of all // the stakers" - let stakers = self.stakes_count(); - let quantile = stakers / MINING_REPLICATION_FACTOR; // TODO: verify if defaulting to 0 makes sense - let (_, threshold) = rank.nth(quantile).unwrap_or_default(); - if power <= threshold { + let mut rank = self.rank(Capability::Mining, epoch); + let (_, threshold) = rank.nth(MINING_REPLICATION_FACTOR - 1).unwrap_or_default(); + if power < threshold { return Ok(IneligibilityReason::InsufficientPower.into()); } @@ -185,7 +182,7 @@ where round: u8, ) -> StakesResult where - ISK: Into>, + ISK: Into
, { let power = match self.query_power(key, Capability::Witnessing, epoch) { Ok(p) => p, @@ -199,7 +196,7 @@ where }; let mut rank = self.rank(Capability::Mining, epoch); - let rf = 2usize.pow(round as u32) * witnesses as usize; + let rf = 2usize.pow(u32::from(round)) * witnesses as usize; // Requirement no. 2 from the WIP: // "the witnessing power of the block proposer is in the `rf / stakers`th quantile among the witnessing powers @@ -242,25 +239,25 @@ mod tests { #[test] fn test_mining_eligibility_no_stakers() { let stakes = >::with_minimum(100u64); - let isk = ("validator", "withdrawer"); + let isk = "validator"; assert_eq!( stakes.mining_eligibility(isk, 0), Ok(Eligible::No(IneligibilityReason::NotStaking)) ); - assert_eq!(stakes.mining_eligibility_bool(isk, 0), false); + assert!(!stakes.mining_eligibility_bool(isk, 0)); assert_eq!( stakes.mining_eligibility(isk, 100), Ok(Eligible::No(IneligibilityReason::NotStaking)) ); - assert_eq!(stakes.mining_eligibility_bool(isk, 100), false); + assert!(!stakes.mining_eligibility_bool(isk, 100)); } #[test] fn test_mining_eligibility_absolute_power() { let mut stakes = >::with_minimum(100u64); - let isk = ("validator", "withdrawer"); + let isk = "validator"; stakes.add_stake(isk, 1_000, 0).unwrap(); @@ -268,34 +265,34 @@ mod tests { stakes.mining_eligibility(isk, 0), Ok(Eligible::No(IneligibilityReason::InsufficientPower)) ); - assert_eq!(stakes.mining_eligibility_bool(isk, 0), false); + assert!(!stakes.mining_eligibility_bool(isk, 0)); assert_eq!(stakes.mining_eligibility(isk, 100), Ok(Eligible::Yes)); - assert_eq!(stakes.mining_eligibility_bool(isk, 100), true); + assert!(stakes.mining_eligibility_bool(isk, 100)); } #[test] fn test_witnessing_eligibility_no_stakers() { let stakes = >::with_minimum(100u64); - let isk = ("validator", "withdrawer"); + let isk = "validator"; assert_eq!( stakes.witnessing_eligibility(isk, 0, 10, 0), Ok(Eligible::No(IneligibilityReason::NotStaking)) ); - assert_eq!(stakes.witnessing_eligibility_bool(isk, 0, 10, 0), false); + assert!(!stakes.witnessing_eligibility_bool(isk, 0, 10, 0)); assert_eq!( stakes.witnessing_eligibility(isk, 100, 10, 0), Ok(Eligible::No(IneligibilityReason::NotStaking)) ); - assert_eq!(stakes.witnessing_eligibility_bool(isk, 100, 10, 0), false); + assert!(!stakes.witnessing_eligibility_bool(isk, 100, 10, 0)); } #[test] fn test_witnessing_eligibility_absolute_power() { let mut stakes = >::with_minimum(100u64); - let isk = ("validator", "withdrawer"); + let isk = "validator"; stakes.add_stake(isk, 1_000, 0).unwrap(); @@ -303,12 +300,12 @@ mod tests { stakes.witnessing_eligibility(isk, 0, 10, 0), Ok(Eligible::No(IneligibilityReason::InsufficientPower)) ); - assert_eq!(stakes.witnessing_eligibility_bool(isk, 0, 10, 0), false); + assert!(!stakes.witnessing_eligibility_bool(isk, 0, 10, 0)); assert_eq!( stakes.witnessing_eligibility(isk, 100, 10, 0), Ok(Eligible::Yes) ); - assert_eq!(stakes.witnessing_eligibility_bool(isk, 100, 10, 0), true); + assert!(stakes.witnessing_eligibility_bool(isk, 100, 10, 0)); } } diff --git a/validations/src/eligibility/legacy.rs b/validations/src/eligibility/legacy.rs index 23a0a55ba..aea7759c7 100644 --- a/validations/src/eligibility/legacy.rs +++ b/validations/src/eligibility/legacy.rs @@ -10,7 +10,7 @@ pub fn calculate_randpoe_threshold( epochs_with_minimum_difficulty: u32, active_wips: &ActiveWips, ) -> (Hash, f64) { - let max = u64::max_value(); + let max = u64::MAX; let minimum_difficulty = std::cmp::max(1, minimum_difficulty); let target = if block_epoch <= epochs_with_minimum_difficulty { max / u64::from(minimum_difficulty) @@ -42,7 +42,7 @@ pub fn calculate_reppoe_threshold( // never be eligible for a data request let my_eligibility = u64::from(rep_eng.get_eligibility(pkh)) + 1; - let max = u64::max_value(); + let max = u64::MAX; // Compute target eligibility and hard-cap it if required let target = if active_wips.wip0016() { let factor = u64::from(num_witnesses); diff --git a/validations/src/tests/compare_block_candidates.rs b/validations/src/tests/compare_block_candidates.rs index fc61c36cc..fb715a41a 100644 --- a/validations/src/tests/compare_block_candidates.rs +++ b/validations/src/tests/compare_block_candidates.rs @@ -1,4 +1,8 @@ -use witnet_data_structures::chain::{tapi::current_active_wips, Hash, Reputation}; +use witnet_data_structures::{ + chain::{tapi::current_active_wips, Hash, Reputation}, + proto::versioning::ProtocolVersion, + staking::prelude::Power, +}; use std::cmp::Ordering; @@ -14,6 +18,8 @@ fn test_compare_candidate_same_section() { let vrf_2 = Hash::SHA256([2; 32]); // Only one section and all VRFs are valid let vrf_sections = VrfSlots::default(); + // Dummy zero power variable for tests before Witnet 2.0 + let power_zero = Power::from(0_u64); // The candidate with reputation always wins for &bh_i in &[bh_1, bh_2] { @@ -28,11 +34,14 @@ fn test_compare_candidate_same_section() { rep_1, vrf_i, act_i, + power_zero, bh_j, rep_2, vrf_j, act_j, + power_zero, &vrf_sections, + ProtocolVersion::V1_7, ), Ordering::Less ); @@ -42,11 +51,14 @@ fn test_compare_candidate_same_section() { rep_2, vrf_i, act_i, + power_zero, bh_j, rep_1, vrf_j, act_j, + power_zero, &vrf_sections, + ProtocolVersion::V1_7, ), Ordering::Greater ); @@ -68,11 +80,14 @@ fn test_compare_candidate_same_section() { rep_1, vrf_i, true, + power_zero, bh_j, rep_1, vrf_j, false, + power_zero, &vrf_sections, + ProtocolVersion::V1_7, ), Ordering::Greater ); @@ -82,11 +97,14 @@ fn test_compare_candidate_same_section() { rep_2, vrf_i, false, + power_zero, bh_j, rep_2, vrf_j, true, + power_zero, &vrf_sections, + ProtocolVersion::V1_7, ), Ordering::Less ); @@ -104,11 +122,14 @@ fn test_compare_candidate_same_section() { rep_1, vrf_1, true, + power_zero, bh_j, rep_1, vrf_2, true, + power_zero, &vrf_sections, + ProtocolVersion::V1_7, ), Ordering::Greater ); @@ -118,11 +139,14 @@ fn test_compare_candidate_same_section() { rep_1, vrf_2, true, + power_zero, bh_j, rep_1, vrf_1, true, + power_zero, &vrf_sections, + ProtocolVersion::V1_7, ), Ordering::Less ); @@ -136,11 +160,14 @@ fn test_compare_candidate_same_section() { rep_1, vrf_1, true, + power_zero, bh_2, rep_1, vrf_1, true, + power_zero, &vrf_sections, + ProtocolVersion::V1_7, ), Ordering::Greater ); @@ -150,11 +177,14 @@ fn test_compare_candidate_same_section() { rep_1, vrf_1, true, + power_zero, bh_1, rep_1, vrf_1, true, + power_zero, &vrf_sections, + ProtocolVersion::V1_7, ), Ordering::Less ); @@ -166,11 +196,14 @@ fn test_compare_candidate_same_section() { rep_1, vrf_1, true, + power_zero, bh_1, rep_1, vrf_1, true, + power_zero, &vrf_sections, + ProtocolVersion::V1_7, ), Ordering::Equal ); @@ -188,6 +221,8 @@ fn test_compare_candidate_different_section() { let vrf_1 = vrf_sections.target_hashes()[0]; // Candidate 2 is in section 1 let vrf_2 = vrf_sections.target_hashes()[1]; + // Dummy zero power variable for tests before Witnet 2.0 + let power_zero = Power::from(0_u64); // The candidate in the lower section always wins for &bh_i in &[bh_1, bh_2] { @@ -202,11 +237,14 @@ fn test_compare_candidate_different_section() { rep_i, vrf_1, act_i, + power_zero, bh_j, rep_j, vrf_2, act_j, + power_zero, &vrf_sections, + ProtocolVersion::V1_7, ), Ordering::Greater ); @@ -216,11 +254,14 @@ fn test_compare_candidate_different_section() { rep_i, vrf_2, act_i, + power_zero, bh_j, rep_j, vrf_1, act_j, + power_zero, &vrf_sections, + ProtocolVersion::V1_7, ), Ordering::Less ); @@ -242,6 +283,8 @@ fn test_compare_candidate_different_reputation_bigger_than_zero() { let vrf_2 = Hash::SHA256([2; 32]); // Only one section and all VRFs are valid let vrf_sections = VrfSlots::default(); + // Dummy zero power variable for tests before Witnet 2.0 + let power_zero = Power::from(0_u64); // In case of active nodes with reputation, the difference will be the vrf not the reputation assert_eq!( @@ -250,11 +293,14 @@ fn test_compare_candidate_different_reputation_bigger_than_zero() { rep_1, vrf_1, true, + power_zero, bh_2, rep_2, vrf_2, true, + power_zero, &vrf_sections, + ProtocolVersion::V1_7, ), Ordering::Greater ); @@ -265,12 +311,88 @@ fn test_compare_candidate_different_reputation_bigger_than_zero() { rep_1, vrf_2, true, + power_zero, bh_2, rep_2, vrf_1, true, + power_zero, &vrf_sections, + ProtocolVersion::V1_7, ), Ordering::Less ); } + +#[test] +fn test_compare_candidates_witnet_pos() { + let bh_1 = Hash::SHA256([10; 32]); + let bh_2 = Hash::SHA256([20; 32]); + let rep_1 = Reputation(0); + let rep_2 = Reputation(0); + let vrf_1 = Hash::SHA256([1; 32]); + let vrf_2 = Hash::SHA256([2; 32]); + let vrf_sections = VrfSlots::default(); + let power_1 = Power::from(10_u64); + let power_2 = Power::from(5_u64); + + // The first staker proposing the first block wins because his power is higher or vrf and block hash are lower + for power in &[power_1, power_2] { + for vrf in &[vrf_1, vrf_2] { + for bh in &[bh_1, bh_2] { + let ordering = if *power == power_2 && *vrf == vrf_2 && *bh == bh_2 { + Ordering::Equal + } else { + Ordering::Greater + }; + assert_eq!( + compare_block_candidates( + *bh, + rep_1, + *vrf, + true, + *power, + bh_2, + rep_2, + vrf_2, + true, + power_2, + &vrf_sections, + ProtocolVersion::V2_0, + ), + ordering + ); + } + } + } + + // The second staker proposing the second block wins because his power is higher or vrf and block hash are lower + for power in &[power_1, power_2] { + for vrf in &[vrf_1, vrf_2] { + for bh in &[bh_1, bh_2] { + let ordering = if *power == power_2 && *vrf == vrf_2 && *bh == bh_2 { + Ordering::Equal + } else { + Ordering::Less + }; + assert_eq!( + compare_block_candidates( + bh_2, + rep_2, + vrf_2, + true, + power_2, + *bh, + rep_1, + *vrf, + true, + *power, + &vrf_sections, + ProtocolVersion::V2_0, + ), + ordering + ); + } + } + } +} diff --git a/validations/src/tests/mod.rs b/validations/src/tests/mod.rs index 5b51b0991..2b99182d2 100644 --- a/validations/src/tests/mod.rs +++ b/validations/src/tests/mod.rs @@ -23,12 +23,15 @@ use witnet_data_structures::{ calculate_tally_change, calculate_witness_reward, create_tally, DataRequestPool, }, error::{BlockError, DataRequestError, Secp256k1ConversionError, TransactionError}, + proto::versioning::ProtocolVersion, radon_error::RadonError, radon_report::{RadonReport, ReportContext, TypeLike}, + staking::prelude::Stakes, transaction::*, transaction_factory::transaction_outputs_sum, utxo_pool::{UnspentOutputsPool, UtxoDiff}, vrf::{BlockEligibilityClaim, DataRequestEligibilityClaim, VrfCtx}, + wit::Wit, }; use witnet_protected::Protected; use witnet_rad::{ @@ -1083,7 +1086,7 @@ fn vtt_input_value_overflow() { let mut signatures_to_verify = vec![]; let vto_21 = ValueTransferOutput { pkh: MY_PKH_1.parse().unwrap(), - value: u64::max_value(), + value: u64::MAX, time_lock: 0, }; let vto_13 = ValueTransferOutput { @@ -1100,7 +1103,7 @@ fn vtt_input_value_overflow() { // The total output value should not overflow let vto0 = ValueTransferOutput { pkh: MY_PKH_1.parse().unwrap(), - value: u64::max_value() - 10, + value: u64::MAX - 10, time_lock: 0, }; let vto1 = ValueTransferOutput { @@ -1132,7 +1135,7 @@ fn vtt_output_value_overflow() { let mut signatures_to_verify = vec![]; let vto_21 = ValueTransferOutput { pkh: MY_PKH_1.parse().unwrap(), - value: u64::max_value() - 1_000, + value: u64::MAX - 1_000, time_lock: 0, }; let vto_13 = ValueTransferOutput { @@ -1149,7 +1152,7 @@ fn vtt_output_value_overflow() { // The total output value should overflow let vto0 = ValueTransferOutput { pkh: MY_PKH_1.parse().unwrap(), - value: u64::max_value(), + value: u64::MAX, time_lock: 0, }; let vto1 = ValueTransferOutput { @@ -1182,6 +1185,8 @@ fn vtt_timelock() { let epoch_constants = EpochConstants { checkpoint_zero_timestamp: 0, checkpoints_period: 1_000, + checkpoint_zero_timestamp_v2: 500, + checkpoints_period_v2: 1_000, }; let test_vtt_epoch = |epoch, time_lock| { @@ -1397,12 +1402,12 @@ fn genesis_vtt_value_overflow() { let outputs = vec![ ValueTransferOutput { pkh, - value: u64::max_value(), + value: u64::MAX, time_lock: 0, }, ValueTransferOutput { pkh, - value: u64::max_value(), + value: u64::MAX, time_lock: 0, }, ]; @@ -1734,14 +1739,14 @@ fn data_request_input_not_enough_value() { #[test] fn data_request_output_value_overflow() { - // Try to create a value transfer output with 2 outputs with value near u64::max_value() + // Try to create a value transfer output with 2 outputs with value near u64::MAX // This may cause an overflow in the fee validations if implemented incorrectly // The current implementation handles this by rejecting data requests with more than 1 value // transfer output let mut signatures_to_verify = vec![]; let vto_21 = ValueTransferOutput { pkh: MY_PKH_1.parse().unwrap(), - value: u64::max_value() - 1_000, + value: u64::MAX - 1_000, time_lock: 0, }; let vto_13 = ValueTransferOutput { @@ -1757,7 +1762,7 @@ fn data_request_output_value_overflow() { let vto0 = ValueTransferOutput { pkh: MY_PKH_1.parse().unwrap(), - value: u64::max_value() - 1_000, + value: u64::MAX - 1_000, time_lock: 0, }; let vto1 = ValueTransferOutput { @@ -1770,7 +1775,7 @@ fn data_request_output_value_overflow() { // but the sum of vto0 + vto1 + dr_output should overflow assert_eq!( transaction_outputs_sum(&[vto0.clone(), vto1.clone()]).unwrap(), - u64::max_value() + u64::MAX ); let dr_output = DataRequestOutput { @@ -1828,7 +1833,7 @@ fn test_drtx(dr_output: DataRequestOutput) -> Result<(), failure::Error> { EpochConstants::default(), &mut signatures_to_verify, ONE_WIT, - u32::max_value(), + u32::MAX, REQUIRED_REWARD_COLLATERAL_RATIO, &all_wips_active(), ) @@ -2230,7 +2235,7 @@ fn data_request_http_post_before_wip_activation() { EpochConstants::default(), &mut signatures_to_verify, ONE_WIT, - u32::max_value(), + u32::MAX, REQUIRED_REWARD_COLLATERAL_RATIO, &active_wips, ) @@ -2298,7 +2303,7 @@ fn data_request_http_get_with_headers_before_wip_activation() { EpochConstants::default(), &mut signatures_to_verify, ONE_WIT, - u32::max_value(), + u32::MAX, REQUIRED_REWARD_COLLATERAL_RATIO, &active_wips, ) @@ -2356,7 +2361,7 @@ fn data_request_parse_xml_before_wip_activation() { EpochConstants::default(), &mut signatures_to_verify, ONE_WIT, - u32::max_value(), + u32::MAX, REQUIRED_REWARD_COLLATERAL_RATIO, &active_wips, ) @@ -2412,7 +2417,7 @@ fn data_request_parse_xml_after_wip_activation() { EpochConstants::default(), &mut signatures_to_verify, ONE_WIT, - u32::max_value(), + u32::MAX, REQUIRED_REWARD_COLLATERAL_RATIO, &active_wips, ) @@ -2473,7 +2478,7 @@ fn data_request_value_overflow() { }; // Test different combinations of overflowing values let x = test_drtx(DataRequestOutput { - witness_reward: u64::max_value(), + witness_reward: u64::MAX, ..dro.clone() }); assert_eq!( @@ -2481,8 +2486,8 @@ fn data_request_value_overflow() { TransactionError::FeeOverflow, ); let x = test_drtx(DataRequestOutput { - witness_reward: u64::max_value() / u64::from(u16::max_value()), - witnesses: u16::max_value(), + witness_reward: u64::MAX / u64::from(u16::MAX), + witnesses: u16::MAX, ..dro.clone() }); assert_eq!( @@ -2490,7 +2495,7 @@ fn data_request_value_overflow() { TransactionError::FeeOverflow, ); let x = test_drtx(DataRequestOutput { - commit_and_reveal_fee: u64::max_value(), + commit_and_reveal_fee: u64::MAX, ..dro }); assert_eq!( @@ -3510,7 +3515,7 @@ fn commitment_dr_in_reveal_stage() { dr_pool .process_data_request(&dr_transaction, dr_epoch, &Hash::default()) .unwrap(); - dr_pool.update_data_request_stages(); + dr_pool.update_data_request_stages(None, Some(dr_epoch)); // Insert valid proof let mut cb = CommitTransactionBody::default(); @@ -3521,7 +3526,7 @@ fn commitment_dr_in_reveal_stage() { let c_tx = CommitTransaction::new(cb, vec![cs]); dr_pool.process_commit(&c_tx, &block_hash).unwrap(); - dr_pool.update_data_request_stages(); + dr_pool.update_data_request_stages(None, Some(dr_epoch)); let mut signatures_to_verify = vec![]; let superblock_period = 1; @@ -3947,6 +3952,8 @@ fn commitment_timelock() { let epoch_constants = EpochConstants { checkpoint_zero_timestamp: 0, checkpoints_period: 1_000, + checkpoint_zero_timestamp_v2: 500, + checkpoints_period_v2: 1_000, }; let test_commit_epoch = |epoch, time_lock| { let mut dr_pool = DataRequestPool::default(); @@ -4082,9 +4089,9 @@ fn dr_pool_with_dr_in_reveal_stage() -> (DataRequestPool, Hash) { dr_pool .process_data_request(&dr_transaction, epoch, &Hash::default()) .unwrap(); - dr_pool.update_data_request_stages(); + dr_pool.update_data_request_stages(None, Some(epoch)); dr_pool.process_commit(&c_tx, &block_hash).unwrap(); - dr_pool.update_data_request_stages(); + dr_pool.update_data_request_stages(None, Some(epoch)); (dr_pool, dr_pointer) } @@ -4326,7 +4333,7 @@ fn reveal_valid_commitment() { dr_pool .process_data_request(&dr_transaction, epoch, &Hash::default()) .unwrap(); - dr_pool.update_data_request_stages(); + dr_pool.update_data_request_stages(None, Some(epoch)); // Hack: get public key by signing an empty transaction let public_key = sign_tx(PRIV_KEY_1, &RevealTransactionBody::default()).public_key; @@ -4353,7 +4360,7 @@ fn reveal_valid_commitment() { dr_pool .process_commit(&commit_transaction, &fake_block_hash) .unwrap(); - dr_pool.update_data_request_stages(); + dr_pool.update_data_request_stages(None, Some(epoch)); let fee = validate_reveal_transaction(&reveal_transaction, &dr_pool, &mut signatures_to_verify) .unwrap(); @@ -4379,7 +4386,7 @@ fn reveal_valid_commitment() { dr_pool .process_reveal(&reveal_transaction, &fake_block_hash) .unwrap(); - dr_pool.update_data_request_stages(); + dr_pool.update_data_request_stages(None, Some(epoch)); // Validate trying to include a reveal previously included let error = @@ -4462,7 +4469,7 @@ fn create_commits_reveals( if liars_count + errors_count > commits_count { panic!("Liars number plus errors number can not be bigger than commits number"); } - if commits_count > u8::max_value() as usize { + if commits_count > u8::MAX as usize { panic!("High commits number produces overflow in the test"); } @@ -4524,7 +4531,7 @@ fn include_commits( for commit in commits.iter().take(commits_count) { dr_pool.process_commit(commit, &fake_block_hash).unwrap(); } - dr_pool.update_data_request_stages(); + dr_pool.update_data_request_stages(None, None); } fn include_reveals( @@ -4538,7 +4545,7 @@ fn include_reveals( for reveal in reveals.iter().take(reveals_count) { dr_pool.process_reveal(reveal, &fake_block_hash).unwrap(); } - dr_pool.update_data_request_stages(); + dr_pool.update_data_request_stages(None, None); } fn get_rewarded_and_slashed( @@ -4631,7 +4638,7 @@ fn dr_pool_with_dr_in_tally_all_errors( dr_pool .process_data_request(&dr_transaction, epoch, &Hash::default()) .unwrap(); - dr_pool.update_data_request_stages(); + dr_pool.update_data_request_stages(None, Some(epoch)); // Include commits and reveals in DataRequestPool include_commits(&mut dr_pool, commits_count, commits.clone()); @@ -4744,7 +4751,7 @@ fn dr_pool_with_dr_in_tally_stage_generic( dr_pool .process_data_request(&dr_transaction, epoch, &Hash::default()) .unwrap(); - dr_pool.update_data_request_stages(); + dr_pool.update_data_request_stages(None, Some(epoch)); // Include commits and reveals in DataRequestPool include_commits(&mut dr_pool, commits_count, commits.clone()); @@ -4971,7 +4978,14 @@ fn tally_dr_not_tally_stage() { TallyTransaction::new(dr_pointer, tally_value, vec![vt0], vec![], vec![]); let mut dr_pool = DataRequestPool::default(); - let x = validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + Some(epoch), + ); assert_eq!( x.unwrap_err().downcast::().unwrap(), TransactionError::DataRequestNotFound { hash: dr_pointer }, @@ -4979,8 +4993,15 @@ fn tally_dr_not_tally_stage() { dr_pool .process_data_request(&dr_transaction, epoch, &Hash::default()) .unwrap(); - dr_pool.update_data_request_stages(); - let x = validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips); + dr_pool.update_data_request_stages(None, Some(epoch)); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + Some(epoch), + ); assert_eq!( x.unwrap_err().downcast::().unwrap(), DataRequestError::NotTallyStage @@ -4989,8 +5010,15 @@ fn tally_dr_not_tally_stage() { dr_pool .process_commit(&commit_transaction, &fake_block_hash) .unwrap(); - dr_pool.update_data_request_stages(); - let x = validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips); + dr_pool.update_data_request_stages(None, Some(epoch)); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + Some(epoch), + ); assert_eq!( x.unwrap_err().downcast::().unwrap(), DataRequestError::NotTallyStage @@ -4999,9 +5027,16 @@ fn tally_dr_not_tally_stage() { dr_pool .process_reveal(&reveal_transaction, &fake_block_hash) .unwrap(); - dr_pool.update_data_request_stages(); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + dr_pool.update_data_request_stages(None, Some(epoch)); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + Some(epoch), + ) + .map(|_| ()); x.unwrap(); } @@ -5071,7 +5106,14 @@ fn tally_invalid_consensus() { slashed, error_witnesses, ); - let x = validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ); assert_eq!( x.unwrap_err().downcast::().unwrap(), TransactionError::MismatchedConsensus { @@ -5142,6 +5184,8 @@ fn tally_valid_1_reveal_5_commits() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -5261,7 +5305,7 @@ fn generic_tally_test_inner( dr_pool .process_data_request(&dr_transaction, epoch, &Hash::default()) .unwrap(); - dr_pool.update_data_request_stages(); + dr_pool.update_data_request_stages(None, Some(epoch)); // Include commits and reveals in DataRequestPool include_commits(&mut dr_pool, commits_count, commits); @@ -5333,6 +5377,8 @@ fn tally_valid_1_reveal_5_commits_invalid_value() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); assert_eq!( @@ -5405,6 +5451,8 @@ fn tally_valid_1_reveal_5_commits_with_absurd_timelock() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); assert_eq!( @@ -5479,8 +5527,15 @@ fn tally_valid() { error_witnesses, ); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); x.unwrap(); } @@ -5535,8 +5590,15 @@ fn tally_too_many_outputs() { slashed, error_witnesses, ); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); assert_eq!( x.unwrap_err().downcast::().unwrap(), TransactionError::WrongNumberOutputs { @@ -5575,8 +5637,15 @@ fn tally_too_less_outputs() { let tally_transaction = TallyTransaction::new(dr_pointer, tally_value, vec![vt0], slashed, error_witnesses); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); assert_eq!( x.unwrap_err().downcast::().unwrap(), TransactionError::WrongNumberOutputs { @@ -5648,8 +5717,15 @@ fn tally_invalid_change() { slashed, error_witnesses, ); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); assert_eq!( x.unwrap_err().downcast::().unwrap(), TransactionError::InvalidTallyChange { @@ -5697,8 +5773,15 @@ fn tally_double_reward() { slashed, error_witnesses, ); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); assert_eq!( x.unwrap_err().downcast::().unwrap(), TransactionError::MultipleRewards { pkh: rewarded[0] }, @@ -5743,8 +5826,15 @@ fn tally_reveal_not_found() { slashed, error_witnesses, ); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); assert_eq!( x.unwrap_err().downcast::().unwrap(), TransactionError::RevealNotFound, @@ -5790,8 +5880,15 @@ fn tally_invalid_reward() { slashed, error_witnesses, ); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); assert_eq!( x.unwrap_err().downcast::().unwrap(), TransactionError::InvalidReward { @@ -5840,8 +5937,15 @@ fn tally_valid_2_reveals() { slashed, error_witnesses, ); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); x.unwrap(); } @@ -5906,6 +6010,8 @@ fn tally_valid_3_reveals_dr_liar() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -5967,8 +6073,15 @@ fn tally_valid_3_reveals_dr_liar_invalid() { slashed_witnesses.clone(), error_witnesses, ); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); assert_eq!( x.unwrap_err().downcast::().unwrap(), @@ -6053,6 +6166,8 @@ fn tally_valid_5_reveals_1_liar_1_error() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -6115,6 +6230,8 @@ fn tally_valid_3_reveals_1_error() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -6177,6 +6294,8 @@ fn tally_valid_3_reveals_1_error_invalid_reward() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); assert_eq!( @@ -6245,6 +6364,8 @@ fn tally_valid_3_reveals_mark_all_as_error() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); assert_eq!( @@ -6316,6 +6437,8 @@ fn tally_dishonest_reward() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); @@ -6369,6 +6492,7 @@ fn create_tally_validation_dr_liar() { min_consensus, 3, &active_wips, + false, ); let script = RADTally { filters: vec![RADFilter { @@ -6396,8 +6520,15 @@ fn create_tally_validation_dr_liar() { &active_wips, ); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); x.unwrap(); } @@ -6458,6 +6589,7 @@ fn create_tally_validation_5_reveals_1_liar_1_error() { min_consensus, 5, &active_wips, + false, ); let script = RADTally { filters: vec![RADFilter { @@ -6497,8 +6629,15 @@ fn create_tally_validation_5_reveals_1_liar_1_error() { &active_wips, ); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); x.unwrap(); } @@ -6538,6 +6677,7 @@ fn create_tally_validation_4_commits_2_reveals() { min_consensus, 4, &active_wips, + false, ); let script = RADTally { filters: vec![RADFilter { @@ -6565,8 +6705,15 @@ fn create_tally_validation_4_commits_2_reveals() { &active_wips, ); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); x.unwrap(); } @@ -6580,7 +6727,8 @@ fn tally_valid_zero_commits() { // Tally value: Insufficient commits Error let min_consensus = 0.0; - let clause_result = evaluate_tally_precondition_clause(vec![], min_consensus, 0, &active_wips); + let clause_result = + evaluate_tally_precondition_clause(vec![], min_consensus, 0, &active_wips, false); let script = RADTally::default(); let report = construct_report_from_clause_result(clause_result, &script, 0, &active_wips); let report = evaluate_tally_postcondition_clause(report, min_consensus, 0); @@ -6592,8 +6740,15 @@ fn tally_valid_zero_commits() { }; let tally_transaction = TallyTransaction::new(dr_pointer, tally_value, vec![vt0], slashed, error_witnesses); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); x.unwrap(); } @@ -6615,7 +6770,8 @@ fn create_tally_validation_zero_commits() { // Tally value: Insufficient commits Error let min_consensus = 0.51; - let clause_result = evaluate_tally_precondition_clause(vec![], min_consensus, 0, &active_wips); + let clause_result = + evaluate_tally_precondition_clause(vec![], min_consensus, 0, &active_wips, false); let script = RADTally::default(); let report = construct_report_from_clause_result(clause_result, &script, 0, &active_wips); let report = evaluate_tally_postcondition_clause(report, min_consensus, 0); @@ -6630,8 +6786,15 @@ fn create_tally_validation_zero_commits() { tally_bytes_on_encode_error(), &active_wips, ); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); x.unwrap(); } @@ -6645,7 +6808,8 @@ fn tally_invalid_zero_commits() { // Tally value: Insufficient commits Error let min_consensus = 0.0; - let clause_result = evaluate_tally_precondition_clause(vec![], min_consensus, 0, &active_wips); + let clause_result = + evaluate_tally_precondition_clause(vec![], min_consensus, 0, &active_wips, false); let script = RADTally::default(); let report = construct_report_from_clause_result(clause_result, &script, 0, &active_wips); let report = evaluate_tally_postcondition_clause(report, min_consensus, 0); @@ -6667,8 +6831,15 @@ fn tally_invalid_zero_commits() { slashed, error_witnesses, ); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); assert_eq!( x.unwrap_err().downcast::().unwrap(), TransactionError::WrongNumberOutputs { @@ -6697,7 +6868,8 @@ fn tally_valid_zero_reveals() { // Tally value: NoReveals commits Error let min_consensus = 0.51; - let clause_result = evaluate_tally_precondition_clause(vec![], min_consensus, 5, &active_wips); + let clause_result = + evaluate_tally_precondition_clause(vec![], min_consensus, 5, &active_wips, false); let script = RADTally::default(); let report = construct_report_from_clause_result(clause_result, &script, 0, &active_wips); let report = evaluate_tally_postcondition_clause(report, min_consensus, 5); @@ -6741,8 +6913,15 @@ fn tally_valid_zero_reveals() { slashed, error_witnesses, ); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); x.unwrap(); } @@ -6765,7 +6944,8 @@ fn create_tally_validation_zero_reveals() { // Tally value: NoReveals commits Error let min_consensus = 0.51; - let clause_result = evaluate_tally_precondition_clause(vec![], min_consensus, 5, &active_wips); + let clause_result = + evaluate_tally_precondition_clause(vec![], min_consensus, 5, &active_wips, false); let script = RADTally::default(); let report = construct_report_from_clause_result(clause_result, &script, 0, &active_wips); let report = evaluate_tally_postcondition_clause(report, min_consensus, 5); @@ -6787,8 +6967,15 @@ fn create_tally_validation_zero_reveals() { tally_bytes_on_encode_error(), &active_wips, ); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); x.unwrap(); } @@ -6812,7 +6999,8 @@ fn create_tally_validation_zero_reveals_zero_collateral() { // Tally value: NoReveals commits Error let min_consensus = 0.51; - let clause_result = evaluate_tally_precondition_clause(vec![], min_consensus, 5, &active_wips); + let clause_result = + evaluate_tally_precondition_clause(vec![], min_consensus, 5, &active_wips, false); let script = RADTally::default(); let report = construct_report_from_clause_result(clause_result, &script, 0, &active_wips); let report = evaluate_tally_postcondition_clause(report, min_consensus, 5); @@ -6834,8 +7022,15 @@ fn create_tally_validation_zero_reveals_zero_collateral() { tally_bytes_on_encode_error(), &active_wips, ); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); x.unwrap(); } @@ -7067,6 +7262,8 @@ fn tally_valid_4_reveals_all_liars() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -7124,6 +7321,8 @@ fn tally_valid_4_reveals_all_liars_attacker_pkh() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); // The attacker_pkh has not participated in the commit/reveal process, so the error is "CommitNotFound" @@ -7185,6 +7384,8 @@ fn tally_valid_4_reveals_2_liars_2_true() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -7244,6 +7445,8 @@ fn tally_valid_4_reveals_2_errors_2_true() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -7301,6 +7504,8 @@ fn tally_valid_4_reveals_1_liar_2_true() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -7362,6 +7567,8 @@ fn tally_valid_4_reveals_invalid_script_arg() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -7423,6 +7630,8 @@ fn tally_valid_3_reveals_1_no_reveal_invalid_script_arg() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -7476,6 +7685,8 @@ fn tally_valid_4_reveals_majority_of_errors() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -7538,6 +7749,8 @@ fn tally_valid_3_reveals_1_no_reveal_majority_of_errors() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -7593,6 +7806,8 @@ fn tally_valid_2_reveals_2_no_reveals_majority_of_errors_insufficient_consensus( &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -7654,6 +7869,8 @@ fn tally_valid_4_reveals_majority_of_errors_insufficient_consensus() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -7714,6 +7931,8 @@ fn tally_valid_3_reveals_1_no_reveal_majority_of_errors_insufficient_consensus() &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -7780,6 +7999,8 @@ fn tally_valid_rng() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -7834,6 +8055,8 @@ fn tally_valid_rng_wrong_bytes_len() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -7910,6 +8133,8 @@ fn tally_valid_rng_one_error() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -7993,6 +8218,8 @@ fn tally_valid_rng_all_errors() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -8063,6 +8290,8 @@ fn tally_valid_rng_one_invalid_type() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -8118,6 +8347,8 @@ fn tally_valid_rng_all_invalid_type() { &dr_pool, ONE_WIT, ¤t_active_wips(), + None, + None, ) .map(|_| ()); x.unwrap(); @@ -8173,8 +8404,15 @@ fn tally_unserializable_value() { slashed, error_witnesses, ); - let x = - validate_tally_transaction(&tally_transaction, &dr_pool, ONE_WIT, &active_wips).map(|_| ()); + let x = validate_tally_transaction( + &tally_transaction, + &dr_pool, + ONE_WIT, + &active_wips, + None, + None, + ) + .map(|_| ()); x.unwrap(); } @@ -8234,6 +8472,8 @@ fn tally_unhandled_intercept_with_message() { &dr_pool, ONE_WIT, &active_wips, + None, + None, ) .map(|_| ()); x.unwrap(); @@ -8242,6 +8482,8 @@ fn tally_unhandled_intercept_with_message() { &dr_pool, ONE_WIT, &active_wips, + None, + None, ) .map(|_| ()); assert_eq!( @@ -8264,6 +8506,8 @@ fn tally_unhandled_intercept_with_message() { &dr_pool, ONE_WIT, &active_wips, + None, + None, ) .map(|_| ()); assert_eq!( @@ -8278,6 +8522,8 @@ fn tally_unhandled_intercept_with_message() { &dr_pool, ONE_WIT, &active_wips, + None, + None, ) .map(|_| ()); x.unwrap(); @@ -8357,6 +8603,8 @@ fn tally_unhandled_intercept_mode_tie_has_no_message() { &dr_pool, ONE_WIT, &active_wips, + None, + None, ) .map(|_| ()); x.unwrap(); @@ -8365,6 +8613,8 @@ fn tally_unhandled_intercept_mode_tie_has_no_message() { &dr_pool, ONE_WIT, &active_wips, + None, + None, ) .map(|_| ()); assert_eq!( @@ -8387,6 +8637,8 @@ fn tally_unhandled_intercept_mode_tie_has_no_message() { &dr_pool, ONE_WIT, &active_wips, + None, + None, ) .map(|_| ()); assert_eq!( @@ -8401,6 +8653,8 @@ fn tally_unhandled_intercept_mode_tie_has_no_message() { &dr_pool, ONE_WIT, &active_wips, + None, + None, ) .map(|_| ()); x.unwrap(); @@ -8449,6 +8703,8 @@ fn tally_error_encode_reveal_wip() { &dr_pool, ONE_WIT, &active_wips, + None, + None, ) .map(|_| ()); assert_eq!( @@ -8468,6 +8724,8 @@ fn tally_error_encode_reveal_wip() { &dr_pool, ONE_WIT, &active_wips, + None, + None, ) .map(|_| ()); x.unwrap(); @@ -8478,6 +8736,7 @@ fn st_no_inputs() { let utxo_set = UnspentOutputsPool::default(); let block_number = 0; let utxo_diff = UtxoDiff::new(&utxo_set, block_number); + let stakes = Default::default(); // Try to create a stake tx with no inputs let st_output = StakeOutput { @@ -8493,6 +8752,7 @@ fn st_no_inputs() { Epoch::default(), EpochConstants::default(), &mut vec![], + &stakes, ); assert_eq!( x.unwrap_err().downcast::().unwrap(), @@ -8513,6 +8773,7 @@ fn st_one_input_but_no_signature() { .parse() .unwrap(), ); + let stakes = Default::default(); // No signatures but 1 input let stake_output = StakeOutput { @@ -8528,6 +8789,7 @@ fn st_one_input_but_no_signature() { Epoch::default(), EpochConstants::default(), &mut signatures_to_verify, + &stakes, ); assert_eq!( x.unwrap_err().downcast::().unwrap(), @@ -8549,6 +8811,7 @@ fn st_below_min_stake() { .parse() .unwrap(), ); + let stakes = Default::default(); // No signatures but 1 input let stake_output = StakeOutput { @@ -8564,6 +8827,7 @@ fn st_below_min_stake() { Epoch::default(), EpochConstants::default(), &mut signatures_to_verify, + &stakes, ); assert_eq!( x.unwrap_err().downcast::().unwrap(), @@ -8707,6 +8971,7 @@ fn test_block_with_drpool_and_utxo_set bool>( let vrf = &mut VrfCtx::secp256k1().unwrap(); let rep_eng = ReputationEngine::new(100); let block_number = 100_000; + let stakes = Stakes::::default(); let consensus_constants = ConsensusConstants { checkpoint_zero_timestamp: 0, @@ -8808,6 +9073,8 @@ fn test_block_with_drpool_and_utxo_set bool>( &rep_eng, &consensus_constants, &active_wips, + ProtocolVersion::V1_7, + &stakes, )?; verify_signatures_test(signatures_to_verify)?; let mut signatures_to_verify = vec![]; @@ -8824,6 +9091,7 @@ fn test_block_with_drpool_and_utxo_set bool>( &consensus_constants, &active_wips, None, + &stakes, )?; verify_signatures_test(signatures_to_verify)?; @@ -8986,6 +9254,7 @@ fn block_difficult_proof() { .push_activity((0..512).map(|x| PublicKeyHash::from_hex(&format!("{:040}", x)).unwrap())); let mut utxo_set = UnspentOutputsPool::default(); let block_number = 0; + let stakes = Stakes::::default(); let consensus_constants = ConsensusConstants { checkpoint_zero_timestamp: 0, @@ -9083,6 +9352,8 @@ fn block_difficult_proof() { &rep_eng, &consensus_constants, ¤t_active_wips(), + ProtocolVersion::V1_7, + &stakes, )?; verify_signatures_test(signatures_to_verify)?; let mut signatures_to_verify = vec![]; @@ -9099,6 +9370,7 @@ fn block_difficult_proof() { &consensus_constants, ¤t_active_wips(), None, + &stakes, )?; verify_signatures_test(signatures_to_verify)?; @@ -9376,7 +9648,7 @@ fn block_duplicated_reveals() { dr_pool .process_commit(&commit_transaction2, &last_block_hash) .unwrap(); - dr_pool.update_data_request_stages(); + dr_pool.update_data_request_stages(None, Some(dr_epoch)); let x = test_block_with_drpool( |b| { @@ -9693,6 +9965,7 @@ fn test_blocks_with_limits( let rep_eng = ReputationEngine::new(100); let mut utxo_set = UnspentOutputsPool::default(); let block_number = 0; + let stakes = Stakes::::default(); let consensus_constants = ConsensusConstants { checkpoint_zero_timestamp: 0, @@ -9790,6 +10063,8 @@ fn test_blocks_with_limits( &rep_eng, &consensus_constants, ¤t_active_wips(), + ProtocolVersion::V1_7, + &stakes, )?; verify_signatures_test(signatures_to_verify)?; let mut signatures_to_verify = vec![]; @@ -9807,6 +10082,7 @@ fn test_blocks_with_limits( &consensus_constants, ¤t_active_wips(), None, + &stakes, )?; verify_signatures_test(signatures_to_verify)?; @@ -10300,6 +10576,8 @@ fn genesis_block_after_not_bootstrap_hash() { &rep_eng, &consensus_constants, ¤t_active_wips(), + ProtocolVersion::V1_7, + &Stakes::::default(), ); assert_eq!(signatures_to_verify, vec![]); @@ -10316,7 +10594,7 @@ fn genesis_block_after_not_bootstrap_hash() { fn genesis_block_value_overflow() { let outputs = vec![ValueTransferOutput { pkh: MY_PKH_1.parse().unwrap(), - value: u64::max_value(), + value: u64::MAX, time_lock: 0, }]; @@ -10332,6 +10610,7 @@ fn genesis_block_value_overflow() { let dr_pool = DataRequestPool::default(); let rep_eng = ReputationEngine::new(100); let utxo_set = UnspentOutputsPool::default(); + let stakes = Stakes::::default(); let current_epoch = 0; let block_number = 0; @@ -10380,6 +10659,8 @@ fn genesis_block_value_overflow() { &rep_eng, &consensus_constants, ¤t_active_wips(), + ProtocolVersion::V1_7, + &stakes, ) .unwrap(); assert_eq!(signatures_to_verify, vec![]); @@ -10398,13 +10679,13 @@ fn genesis_block_value_overflow() { &consensus_constants, ¤t_active_wips(), None, + &stakes, ); assert_eq!(signatures_to_verify, vec![]); assert_eq!( x.unwrap_err().downcast::().unwrap(), BlockError::GenesisValueOverflow { - max_total_value: u64::max_value() - - total_block_reward(INITIAL_BLOCK_REWARD, HALVING_PERIOD), + max_total_value: u64::MAX - total_block_reward(INITIAL_BLOCK_REWARD, HALVING_PERIOD), }, ); } @@ -10418,6 +10699,7 @@ fn genesis_block_full_validate() { let dr_pool = DataRequestPool::default(); let rep_eng = ReputationEngine::new(100); let utxo_set = UnspentOutputsPool::default(); + let stakes = Stakes::::default(); let current_epoch = 0; let block_number = 0; @@ -10465,6 +10747,8 @@ fn genesis_block_full_validate() { &rep_eng, &consensus_constants, ¤t_active_wips(), + ProtocolVersion::V1_7, + &stakes, ) .unwrap(); assert_eq!(signatures_to_verify, vec![]); @@ -10483,6 +10767,7 @@ fn genesis_block_full_validate() { &consensus_constants, ¤t_active_wips(), None, + &stakes, ) .unwrap(); assert_eq!(signatures_to_verify, vec![]); @@ -10525,6 +10810,7 @@ fn validate_block_transactions_uses_block_number_in_utxo_diff() { let vrf = &mut VrfCtx::secp256k1().unwrap(); let rep_eng = ReputationEngine::new(100); let utxo_set = UnspentOutputsPool::default(); + let stakes = Stakes::::default(); let secret_key = SecretKey { bytes: Protected::from(PRIV_KEY_1.to_vec()), @@ -10572,6 +10858,7 @@ fn validate_block_transactions_uses_block_number_in_utxo_diff() { &consensus_constants, ¤t_active_wips(), None, + &stakes, ) .unwrap() }; @@ -10623,6 +10910,7 @@ fn validate_commit_transactions_included_in_utxo_diff() { let mut dr_pool = DataRequestPool::default(); let vrf = &mut VrfCtx::secp256k1().unwrap(); let rep_eng = ReputationEngine::new(100); + let stakes = Stakes::::default(); let dro = DataRequestOutput { witness_reward: DEFAULT_WITNESS_REWARD, @@ -10737,6 +11025,7 @@ fn validate_commit_transactions_included_in_utxo_diff() { &consensus_constants, ¤t_active_wips(), None, + &stakes, ) .unwrap() }; @@ -10886,6 +11175,7 @@ fn validate_required_tally_not_found() { let mut dr_pool = DataRequestPool::default(); dr_pool.data_request_pool.insert(dr_pointer, dr_state); + let stakes = Stakes::::default(); let b = Block::default(); @@ -10901,6 +11191,7 @@ fn validate_required_tally_not_found() { &ConsensusConstants::default(), ¤t_active_wips(), None, + &stakes, ) .unwrap_err(); diff --git a/validations/src/tests/reppoe.rs b/validations/src/tests/reppoe.rs index fcdac1805..6898061f7 100644 --- a/validations/src/tests/reppoe.rs +++ b/validations/src/tests/reppoe.rs @@ -560,7 +560,7 @@ fn reppoe_overflow_v1() { let id2 = PublicKeyHash::from_bytes(&[2; 20]).unwrap(); rep_engine.ars_mut().push_activity(vec![id0]); rep_engine.ars_mut().push_activity(vec![id1]); - add_rep(&mut rep_engine, 10, id0, u32::max_value() - 2); + add_rep(&mut rep_engine, 10, id0, u32::MAX - 2); // Test big values that result in < 100% // Active identity with 100% of the reputation @@ -601,7 +601,7 @@ fn reppoe_overflow_v2() { let id2 = PublicKeyHash::from_bytes(&[2; 20]).unwrap(); rep_engine.ars_mut().push_activity(vec![id0]); rep_engine.ars_mut().push_activity(vec![id1]); - add_rep(&mut rep_engine, 10, id0, u32::max_value() - 2); + add_rep(&mut rep_engine, 10, id0, u32::MAX - 2); // Test big values that result in < 100% // Active identity with 100% of the reputation @@ -642,7 +642,7 @@ fn reppoe_overflow_v3() { let id2 = PublicKeyHash::from_bytes(&[2; 20]).unwrap(); rep_engine.ars_mut().push_activity(vec![id0]); rep_engine.ars_mut().push_activity(vec![id1]); - add_rep(&mut rep_engine, 10, id0, u32::max_value() - 2); + add_rep(&mut rep_engine, 10, id0, u32::MAX - 2); // Test big values that result in < 100% // Active identity with 100% of the reputation @@ -682,7 +682,7 @@ fn reppoe_much_rep_trapezoid_v1() { let id2 = PublicKeyHash::from_bytes(&[2; 20]).unwrap(); rep_engine.ars_mut().push_activity(vec![id0]); rep_engine.ars_mut().push_activity(vec![id1]); - add_rep(&mut rep_engine, 10, id0, u32::max_value() - 4); + add_rep(&mut rep_engine, 10, id0, u32::MAX - 4); add_rep(&mut rep_engine, 10, id1, 1); // Test big values that result in < 100% @@ -723,7 +723,7 @@ fn reppoe_much_rep_trapezoid_v2() { let id2 = PublicKeyHash::from_bytes(&[2; 20]).unwrap(); rep_engine.ars_mut().push_activity(vec![id0]); rep_engine.ars_mut().push_activity(vec![id1]); - add_rep(&mut rep_engine, 10, id0, u32::max_value() - 4); + add_rep(&mut rep_engine, 10, id0, u32::MAX - 4); add_rep(&mut rep_engine, 10, id1, 1); // Test big values that result in < 100% @@ -766,7 +766,7 @@ fn reppoe_much_rep_trapezoid_v3() { let id2 = PublicKeyHash::from_bytes(&[2; 20]).unwrap(); rep_engine.ars_mut().push_activity(vec![id0]); rep_engine.ars_mut().push_activity(vec![id1]); - add_rep(&mut rep_engine, 10, id0, u32::max_value() - 4); + add_rep(&mut rep_engine, 10, id0, u32::MAX - 4); add_rep(&mut rep_engine, 10, id1, 1); // Test big values that result in < 100% diff --git a/validations/src/tests/tally_precondition.rs b/validations/src/tests/tally_precondition.rs index 142427662..31b89a0fb 100644 --- a/validations/src/tests/tally_precondition.rs +++ b/validations/src/tests/tally_precondition.rs @@ -26,7 +26,7 @@ fn test_tally_precondition_clause_3_ints_vs_1_float() { rad_rep_float, ]; let tally_precondition_clause_result = - evaluate_tally_precondition_clause(v, 0.70, 4, ¤t_active_wips()).unwrap(); + evaluate_tally_precondition_clause(v, 0.70, 4, ¤t_active_wips(), false).unwrap(); if let TallyPreconditionClauseResult::MajorityOfValues { values, @@ -53,7 +53,7 @@ fn test_tally_precondition_clause_full_consensus() { let v = vec![rad_rep_int.clone(), rad_rep_int]; let tally_precondition_clause_result = - evaluate_tally_precondition_clause(v, 0.99, 2, ¤t_active_wips()).unwrap(); + evaluate_tally_precondition_clause(v, 0.99, 2, ¤t_active_wips(), false).unwrap(); if let TallyPreconditionClauseResult::MajorityOfValues { values, @@ -80,7 +80,7 @@ fn test_tally_precondition_clause_exact_consensus() { let v = vec![rad_rep_int.clone(), rad_rep_int]; let tally_precondition_clause_result = - evaluate_tally_precondition_clause(v, 1., 2, ¤t_active_wips()).unwrap(); + evaluate_tally_precondition_clause(v, 1., 2, ¤t_active_wips(), false).unwrap(); if let TallyPreconditionClauseResult::MajorityOfValues { values, @@ -114,7 +114,7 @@ fn test_tally_precondition_clause_3_ints_vs_1_error() { rad_rep_int, ]; let tally_precondition_clause_result = - evaluate_tally_precondition_clause(v, 0.70, 4, ¤t_active_wips()).unwrap(); + evaluate_tally_precondition_clause(v, 0.70, 4, ¤t_active_wips(), false).unwrap(); if let TallyPreconditionClauseResult::MajorityOfValues { values, @@ -151,7 +151,7 @@ fn test_tally_precondition_clause_majority_of_errors() { rad_rep_int, ]; let tally_precondition_clause_result = - evaluate_tally_precondition_clause(v, 0.70, 4, ¤t_active_wips()).unwrap(); + evaluate_tally_precondition_clause(v, 0.70, 4, ¤t_active_wips(), false).unwrap(); if let TallyPreconditionClauseResult::MajorityOfErrors { errors_mode } = tally_precondition_clause_result @@ -179,8 +179,8 @@ fn test_tally_precondition_clause_mode_tie() { rad_rep_float, rad_rep_int, ]; - let out = - evaluate_tally_precondition_clause(v.clone(), 0.49, 4, ¤t_active_wips()).unwrap_err(); + let out = evaluate_tally_precondition_clause(v.clone(), 0.49, 4, ¤t_active_wips(), false) + .unwrap_err(); assert_eq!( out, @@ -218,7 +218,7 @@ fn test_tally_precondition_clause_3_errors_vs_2_ints_and_2_floats() { rad_rep_int, ]; let tally_precondition_clause_result = - evaluate_tally_precondition_clause(v, 0.40, 7, ¤t_active_wips()).unwrap(); + evaluate_tally_precondition_clause(v, 0.40, 7, ¤t_active_wips(), false).unwrap(); if let TallyPreconditionClauseResult::MajorityOfErrors { errors_mode } = tally_precondition_clause_result @@ -235,7 +235,8 @@ fn test_tally_precondition_clause_3_errors_vs_2_ints_and_2_floats() { #[test] fn test_tally_precondition_clause_no_commits() { let v = vec![]; - let out = evaluate_tally_precondition_clause(v, 0.51, 0, ¤t_active_wips()).unwrap_err(); + let out = + evaluate_tally_precondition_clause(v, 0.51, 0, ¤t_active_wips(), false).unwrap_err(); assert_eq!(out, RadError::InsufficientCommits); } @@ -243,7 +244,8 @@ fn test_tally_precondition_clause_no_commits() { #[test] fn test_tally_precondition_clause_no_reveals() { let v = vec![]; - let out = evaluate_tally_precondition_clause(v, 0.51, 1, ¤t_active_wips()).unwrap_err(); + let out = + evaluate_tally_precondition_clause(v, 0.51, 1, ¤t_active_wips(), false).unwrap_err(); assert_eq!(out, RadError::NoReveals); } @@ -263,7 +265,7 @@ fn test_tally_precondition_clause_all_errors() { rad_rep_err, ]; let tally_precondition_clause_result = - evaluate_tally_precondition_clause(v, 0.51, 4, ¤t_active_wips()).unwrap(); + evaluate_tally_precondition_clause(v, 0.51, 4, ¤t_active_wips(), false).unwrap(); if let TallyPreconditionClauseResult::MajorityOfErrors { errors_mode } = tally_precondition_clause_result @@ -291,7 +293,8 @@ fn test_tally_precondition_clause_insufficient_consensus() { rad_rep_float, rad_rep_int, ]; - let out = evaluate_tally_precondition_clause(v, 0.51, 4, ¤t_active_wips()).unwrap_err(); + let out = + evaluate_tally_precondition_clause(v, 0.51, 4, ¤t_active_wips(), false).unwrap_err(); assert_eq!( out, @@ -318,7 +321,8 @@ fn test_tally_precondition_clause_errors_insufficient_consensus() { ); let v = vec![rad_rep_err1, rad_rep_err2]; - let out = evaluate_tally_precondition_clause(v, 0.51, 2, ¤t_active_wips()).unwrap_err(); + let out = + evaluate_tally_precondition_clause(v, 0.51, 2, ¤t_active_wips(), false).unwrap_err(); assert_eq!( out, @@ -345,8 +349,8 @@ fn test_tally_precondition_clause_errors_mode_tie() { ); let v = vec![rad_rep_err1, rad_rep_err2]; - let out = - evaluate_tally_precondition_clause(v.clone(), 0.49, 2, ¤t_active_wips()).unwrap_err(); + let out = evaluate_tally_precondition_clause(v.clone(), 0.49, 2, ¤t_active_wips(), false) + .unwrap_err(); assert_eq!( out, diff --git a/validations/src/validations.rs b/validations/src/validations.rs index 5e034bade..c87de9fa2 100644 --- a/validations/src/validations.rs +++ b/validations/src/validations.rs @@ -28,13 +28,14 @@ use witnet_data_structures::{ }, data_request::{ calculate_reward_collateral_ratio, calculate_tally_change, calculate_witness_reward, - calculate_witness_reward_before_second_hard_fork, create_tally, DataRequestPool, + calculate_witness_reward_before_second_hard_fork, create_tally, + data_request_has_too_many_witnesses, DataRequestPool, }, error::{BlockError, DataRequestError, TransactionError}, get_protocol_version, proto::versioning::{ProtocolVersion, VersionedHashable}, radon_report::{RadonReport, ReportContext}, - staking::{prelude::StakeKey, stakes::Stakes}, + staking::{prelude::Power, stakes::Stakes}, transaction::{ CommitTransaction, DRTransaction, MintTransaction, RevealTransaction, StakeTransaction, TallyTransaction, Transaction, UnstakeTransaction, VTTransaction, @@ -127,11 +128,15 @@ pub fn st_transaction_fee( ) -> Result { let in_value = transaction_inputs_sum(&st_tx.body.inputs, utxo_diff, epoch, epoch_constants)?; let out_value = st_tx.body.output.value; + let change_value = match &st_tx.body.change { + Some(change) => change.value, + None => 0, + }; - if out_value > in_value { + if out_value + change_value > in_value { Err(TransactionError::NegativeFee.into()) } else { - Ok(in_value - out_value) + Ok(in_value - out_value - change_value) } } @@ -228,7 +233,7 @@ pub fn validate_commit_collateral( } // Verify that commits are only accepted after the time lock expired - let epoch_timestamp = epoch_constants.epoch_timestamp(epoch)?; + let (epoch_timestamp, _) = epoch_constants.epoch_timestamp(epoch)?; let vt_time_lock = i64::try_from(vt_output.time_lock)?; if vt_time_lock > epoch_timestamp { return Err(TransactionError::TimeLock { @@ -303,29 +308,37 @@ pub fn validate_mint_transaction( .into()); } - let mint_value = transaction_outputs_sum(&mint_tx.outputs)?; - let block_reward_value = block_reward(mint_tx.epoch, initial_block_reward, halving_period); - // Mint value must be equal to block_reward + transaction fees - if mint_value != total_fees + block_reward_value { - return Err(BlockError::MismatchedMintValue { - mint_value, - fees_value: total_fees, - reward_value: block_reward_value, + if get_protocol_version(Some(block_epoch)) != ProtocolVersion::V2_0 { + let mint_value = transaction_outputs_sum(&mint_tx.outputs)?; + let block_reward_value = block_reward(mint_tx.epoch, initial_block_reward, halving_period); + // Mint value must be equal to block_reward + transaction fees + if mint_value != total_fees + block_reward_value { + return Err(BlockError::MismatchedMintValue { + mint_value, + fees_value: total_fees, + reward_value: block_reward_value, + } + .into()); } - .into()); - } - if mint_tx.outputs.len() > 2 { - return Err(BlockError::TooSplitMint.into()); - } + if mint_tx.outputs.len() > 2 { + return Err(BlockError::TooSplitMint.into()); + } - for (idx, output) in mint_tx.outputs.iter().enumerate() { - if output.value == 0 { - return Err(TransactionError::ZeroValueOutput { - tx_hash: mint_tx.hash(), - output_id: idx, + for (idx, output) in mint_tx.outputs.iter().enumerate() { + if output.value == 0 { + return Err(TransactionError::ZeroValueOutput { + tx_hash: mint_tx.hash(), + output_id: idx, + } + .into()); } - .into()); + } + } else { + let mut valid_mint_tx = MintTransaction::default(); + valid_mint_tx.epoch = block_epoch; + if *mint_tx != valid_mint_tx { + return Err(BlockError::InvalidMintTransaction.into()); } } @@ -695,7 +708,7 @@ pub fn validate_commit_transaction( // commit time_lock was disabled in the first hard fork if !active_wips.wip_0008() { // Verify that commits are only accepted after the time lock expired - let epoch_timestamp = epoch_constants.epoch_timestamp(epoch)?; + let (epoch_timestamp, _) = epoch_constants.epoch_timestamp(epoch)?; let dr_time_lock = i64::try_from(dr_output.data_request.time_lock)?; if dr_time_lock > epoch_timestamp { return Err(TransactionError::TimeLock { @@ -798,6 +811,7 @@ pub fn run_tally_panic_safe( non_error_min: f64, commits_count: usize, active_wips: &ActiveWips, + too_many_witnesses: bool, ) -> RadonReport { let unwind_fn = || { let results = serial_iter_decode( @@ -819,7 +833,14 @@ pub fn run_tally_panic_safe( active_wips, ); - run_tally(results, tally, non_error_min, commits_count, active_wips) + run_tally( + results, + tally, + non_error_min, + commits_count, + active_wips, + too_many_witnesses, + ) }; match panic::catch_unwind(unwind_fn) { @@ -842,10 +863,16 @@ pub fn run_tally( non_error_min: f64, commits_count: usize, active_wips: &ActiveWips, + too_many_witnesses: bool, ) -> RadonReport { let results_len = results.len(); - let clause_result = - evaluate_tally_precondition_clause(results, non_error_min, commits_count, active_wips); + let clause_result = evaluate_tally_precondition_clause( + results, + non_error_min, + commits_count, + active_wips, + too_many_witnesses, + ); let mut report = construct_report_from_clause_result(clause_result, tally, results_len, active_wips); if active_wips.wips_0009_0011_0012() { @@ -867,6 +894,7 @@ fn create_expected_tally_transaction( dr_pool: &DataRequestPool, collateral_minimum: u64, active_wips: &ActiveWips, + too_many_witnesses: bool, ) -> Result<(TallyTransaction, DataRequestState), failure::Error> { // Get DataRequestState let dr_pointer = ta_tx.dr_pointer; @@ -897,6 +925,7 @@ fn create_expected_tally_transaction( non_error_min, commit_length, active_wips, + too_many_witnesses, ); let ta_tx = create_tally( dr_pointer, @@ -940,9 +969,28 @@ pub fn validate_tally_transaction<'a>( dr_pool: &DataRequestPool, collateral_minimum: u64, active_wips: &ActiveWips, + validator_count: Option, + epoch: Option, ) -> Result<(Vec<&'a ValueTransferOutput>, u64), failure::Error> { - let (expected_ta_tx, dr_state) = - create_expected_tally_transaction(ta_tx, dr_pool, collateral_minimum, active_wips)?; + let validator_count = + validator_count.unwrap_or(witnet_data_structures::DEFAULT_VALIDATOR_COUNT_FOR_TESTS); + let too_many_witnesses; + if let Some(dr_state) = dr_pool.data_request_state(&ta_tx.dr_pointer) { + too_many_witnesses = + data_request_has_too_many_witnesses(&dr_state.data_request, validator_count, epoch); + } else { + return Err(TransactionError::DataRequestNotFound { + hash: ta_tx.dr_pointer, + } + .into()); + } + let (expected_ta_tx, dr_state) = create_expected_tally_transaction( + ta_tx, + dr_pool, + collateral_minimum, + active_wips, + too_many_witnesses, + )?; let sorted_out_of_consensus = ta_tx.out_of_consensus.iter().cloned().sorted().collect(); let sorted_expected_out_of_consensus = expected_ta_tx @@ -1056,7 +1104,7 @@ pub fn validate_tally_transaction<'a>( if is_after_second_hard_fork { if honests_count > 0 { // Make sure every rewarded address is a revealer - if dr_state.info.reveals.get(&output.pkh).is_none() { + if !dr_state.info.reveals.contains_key(&output.pkh) { return Err(TransactionError::RevealNotFound.into()); } // Make sure every rewarded address passed the tally function, a.k.a. "is honest" / "is not a liar" @@ -1086,7 +1134,7 @@ pub fn validate_tally_transaction<'a>( } } else { // Make sure every rewarded address is a committer - if dr_state.info.commits.get(&output.pkh).is_none() { + if !dr_state.info.commits.contains_key(&output.pkh) { return Err(TransactionError::CommitNotFound.into()); } // Validation of the reward, must be equal to the collateral @@ -1102,7 +1150,7 @@ pub fn validate_tally_transaction<'a>( // Old logic used before second hard fork if reveals_count > 0 { // Make sure every rewarded address is a revealer - if dr_state.info.reveals.get(&output.pkh).is_none() { + if !dr_state.info.reveals.contains_key(&output.pkh) { return Err(TransactionError::RevealNotFound.into()); } // Make sure every rewarded address passed the tally function, a.k.a. "is honest" / "is not a liar" @@ -1200,6 +1248,7 @@ pub fn validate_stake_transaction<'a>( epoch: Epoch, epoch_constants: EpochConstants, signatures_to_verify: &mut Vec, + stakes: &Stakes, ) -> Result, failure::Error> { // Check that the amount of coins to stake is equal or greater than the minimum allowed if st_tx.body.output.value < MIN_STAKE_NANOWITS { @@ -1209,6 +1258,12 @@ pub fn validate_stake_transaction<'a>( })?; } + // A stake transaction can only stake on an existing validator if the withdrawer address is the same + stakes.check_validator_withdrawer( + st_tx.body.output.key.validator, + st_tx.body.output.key.withdrawer, + )?; + validate_transaction_signature( &st_tx.signatures, &st_tx.body.inputs, @@ -1609,6 +1664,7 @@ pub fn validate_block_transactions( consensus_constants: &ConsensusConstants, active_wips: &ActiveWips, mut visitor: Option<&mut dyn Visitor>, + stakes: &Stakes, ) -> Result { let epoch = block.block_header.beacon.checkpoint; let is_genesis = block.is_genesis(&consensus_constants.genesis_hash); @@ -1618,7 +1674,7 @@ pub fn validate_block_transactions( // When validating genesis block, keep track of total value created // The value created in the genesis block cannot be greater than 2^64 - the total block reward, // So the total amount is always representable by a u64 - let max_total_value_genesis = u64::max_value() + let max_total_value_genesis = u64::MAX - total_block_reward( consensus_constants.initial_block_reward, consensus_constants.halving_period, @@ -1775,6 +1831,8 @@ pub fn validate_block_transactions( dr_pool, consensus_constants.collateral_minimum, active_wips, + Some(stakes.validator_count()), + Some(epoch), )?; if !active_wips.wips_0009_0011_0012() && transaction.tally == tally_bytes_on_encode_error() @@ -1911,6 +1969,7 @@ pub fn validate_block_transactions( epoch, epoch_constants, signatures_to_verify, + stakes, )?; total_fee += fee; @@ -2071,8 +2130,7 @@ pub fn validate_block( } else { let target_hash = if protocol_version == ProtocolVersion::V2_0 { let validator = block.block_sig.public_key.pkh(); - let validator_key = StakeKey::from((validator, validator)); - let eligibility = stakes.mining_eligibility(validator_key, block_epoch); + let eligibility = stakes.mining_eligibility(validator, block_epoch); if eligibility == Ok(Eligible::No(InsufficientPower)) || eligibility == Ok(Eligible::No(NotStaking)) { @@ -2159,6 +2217,7 @@ pub fn validate_new_transaction( required_reward_collateral_ratio: u64, active_wips: &ActiveWips, superblock_period: u16, + stakes: &Stakes, ) -> Result { let utxo_diff = UtxoDiff::new(unspent_outputs_pool, block_number); @@ -2211,6 +2270,7 @@ pub fn validate_new_transaction( current_epoch, epoch_constants, signatures_to_verify, + stakes, ) .map(|(_, _, fee, _, _)| fee), _ => Err(TransactionError::NotValidTransaction.into()), @@ -2308,20 +2368,27 @@ pub fn compare_block_candidates( b1_rep: Reputation, b1_vrf_hash: Hash, b1_is_active: bool, + b1_power: Power, b2_hash: Hash, b2_rep: Reputation, b2_vrf_hash: Hash, b2_is_active: bool, + b2_power: Power, s: &VrfSlots, version: ProtocolVersion, ) -> Ordering { - let ordering = if version == ProtocolVersion::V2_0 { - // Bigger vrf hash implies worse block candidate - b1_vrf_hash - .cmp(&b2_vrf_hash) - .reverse() - // Bigger block implies worse block candidate - .then(b1_hash.cmp(&b2_hash).reverse()) + if version == ProtocolVersion::V2_0 { + match b1_power.cmp(&b2_power) { + // Equal power, first compare VRF hash and finally the block hash + Ordering::Equal => { + b1_vrf_hash + .cmp(&b2_vrf_hash) + .reverse() + // Bigger block implies worse block candidate + .then(b1_hash.cmp(&b2_hash).reverse()) + } + ord => ord, + } } else { let section1 = s.slot(&b1_vrf_hash); let section2 = s.slot(&b2_vrf_hash); @@ -2349,9 +2416,7 @@ pub fn compare_block_candidates( .then(b1_vrf_hash.cmp(&b2_vrf_hash).reverse()) // Bigger block implies worse block candidate .then(b1_hash.cmp(&b2_hash).reverse()) - }; - - ordering + } } /// Blocking process to verify signatures diff --git a/wallet/src/actors/app/state.rs b/wallet/src/actors/app/state.rs index ac6d3a5e1..adca89af1 100644 --- a/wallet/src/actors/app/state.rs +++ b/wallet/src/actors/app/state.rs @@ -10,6 +10,7 @@ use witnet_net::client::tcp::jsonrpc::Subscribe; use super::*; /// Struct to manage the App actor state and its invariants. +#[allow(dead_code)] #[derive(Default)] pub struct State { pub node_subscriptions: Arc>>, diff --git a/wallet/src/actors/worker/methods.rs b/wallet/src/actors/worker/methods.rs index 23efd591a..407ae8949 100644 --- a/wallet/src/actors/worker/methods.rs +++ b/wallet/src/actors/worker/methods.rs @@ -16,6 +16,8 @@ use witnet_data_structures::{ StateMachine, ValueTransferOutput, }, fee::AbsoluteFee, + get_protocol_version, + proto::versioning::VersionedHashable, transaction::Transaction, }; use witnet_futures_utils::TryFutureExt2; @@ -55,6 +57,7 @@ impl Worker { RadonScriptExecutionSettings::enable_all(), None, Some(self.params.witnessing.clone()), + false, ) } @@ -983,6 +986,7 @@ impl Worker { let wallet_data = wallet.public_data()?; let last_sync = wallet_data.last_sync; let last_confirmed = wallet_data.last_confirmed; + let protocol_version = get_protocol_version(Some(block_beacon.checkpoint)); let (needs_clear_pending, needs_indexing) = if block_beacon.hash_prev_block == last_sync.hash_prev_block && (block_beacon.checkpoint == 0 || block_beacon.checkpoint > last_sync.checkpoint) @@ -1007,7 +1011,7 @@ impl Worker { // Wallet pending state should be cleared and new block indexed (true, true) } else if block_beacon.checkpoint == last_confirmed.checkpoint - && block.hash() == last_confirmed.hash_prev_block + && block.versioned_hash(protocol_version) == last_confirmed.hash_prev_block { log::debug!( "Tried to process a block #{} that was already confirmed in our chain #{} (cleaning pending state)", diff --git a/wallet/src/db/mod.rs b/wallet/src/db/mod.rs index f5ac6370b..b9846b0a7 100644 --- a/wallet/src/db/mod.rs +++ b/wallet/src/db/mod.rs @@ -47,6 +47,7 @@ pub trait Database { K: AsRef<[u8]>, V: serde::de::DeserializeOwned; + #[allow(dead_code)] fn contains(&self, key: &Key) -> Result where K: AsRef<[u8]>; diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 698466166..6679b4b0c 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -21,7 +21,11 @@ use actix::prelude::*; use failure::Error; use witnet_config::config::Config; -use witnet_data_structures::chain::{CheckpointBeacon, EpochConstants}; +use witnet_data_structures::{ + chain::{CheckpointBeacon, EpochConstants}, + get_protocol_version_activation_epoch, get_protocol_version_period, + proto::versioning::ProtocolVersion, +}; use witnet_net::client::tcp::JsonRpcClient; use witnet_validations::witnessing::validate_witnessing_config; @@ -47,10 +51,20 @@ pub fn run(conf: Config) -> Result<(), Error> { let db_file_name = conf.wallet.db_file_name; let node_urls = conf.wallet.node_url; let rocksdb_opts = conf.rocksdb.to_rocksdb_options(); + + let checkpoints_period = conf.consensus_constants.checkpoints_period; + let checkpoint_zero_timestamp = conf.consensus_constants.checkpoint_zero_timestamp; + let checkpoint_zero_timestamp_v2 = checkpoint_zero_timestamp + + get_protocol_version_activation_epoch(ProtocolVersion::V2_0) as i64 + * checkpoints_period as i64; + let checkpoints_period_v2 = get_protocol_version_period(ProtocolVersion::V2_0); let epoch_constants = EpochConstants { - checkpoint_zero_timestamp: conf.consensus_constants.checkpoint_zero_timestamp, - checkpoints_period: conf.consensus_constants.checkpoints_period, + checkpoint_zero_timestamp, + checkpoints_period, + checkpoint_zero_timestamp_v2, + checkpoints_period_v2, }; + let genesis_hash = conf.consensus_constants.genesis_hash; let genesis_prev_hash = conf.consensus_constants.bootstrap_hash; diff --git a/wallet/src/params.rs b/wallet/src/params.rs index 9653308f9..cc7cc397b 100644 --- a/wallet/src/params.rs +++ b/wallet/src/params.rs @@ -37,6 +37,7 @@ pub struct Params { pub witnessing: WitnessingConfig, } +#[allow(dead_code)] #[derive(Clone)] pub struct NodeParams { /// Reference to the JSON-RPC client actor. diff --git a/wallet/src/repository/wallet/mod.rs b/wallet/src/repository/wallet/mod.rs index 5fde718d0..9e7d9fc55 100644 --- a/wallet/src/repository/wallet/mod.rs +++ b/wallet/src/repository/wallet/mod.rs @@ -819,7 +819,7 @@ where // Check if any input or output is from the wallet (input is an UTXO or output points to any wallet's pkh) if inputs .iter() - .any(|input| state.utxo_set.get(&input.output_pointer().into()).is_some()) + .any(|input| state.utxo_set.contains_key(&input.output_pointer().into())) || outputs.iter().any(check_db_and_transient) { filtered_txns.push(txn.clone()); @@ -2047,8 +2047,12 @@ where fn convert_block_epoch_to_timestamp(epoch_constants: EpochConstants, epoch: Epoch) -> u64 { // In case of error, return timestamp 0 - u64::try_from(epoch_constants.epoch_timestamp(epoch).unwrap_or(0)) - .expect("Epoch timestamp should return a positive value") + match epoch_constants.epoch_timestamp(epoch) { + Ok((timestamp, _)) => { + u64::try_from(timestamp).expect("Epoch timestamp should return a positive value") + } + Err(_) => 0, + } } // Extract inputs and output from a transaction diff --git a/wallet/src/repository/wallet/tests/mod.rs b/wallet/src/repository/wallet/tests/mod.rs index dd8ea949c..63078555a 100644 --- a/wallet/src/repository/wallet/tests/mod.rs +++ b/wallet/src/repository/wallet/tests/mod.rs @@ -494,7 +494,7 @@ fn test_create_transaction_components_which_value_overflows() { }, model::OutputInfo { pkh, - amount: std::u64::MAX - 1, + amount: u64::MAX - 1, time_lock: 0, }, ), @@ -505,7 +505,7 @@ fn test_create_transaction_components_which_value_overflows() { index: 0, }; let new_balance = model::BalanceInfo { - available: std::u64::MAX, + available: u64::MAX, locked: 0u64, }; @@ -516,7 +516,7 @@ fn test_create_transaction_components_which_value_overflows() { let (wallet, _db) = factories::wallet(Some(db)); let mut state = wallet.state.write().unwrap(); let pkh = factories::pkh(); - let value = std::u64::MAX; + let value = u64::MAX; let fee = Fee::default(); let time_lock = 0; let utxo_strategy = UtxoSelectionStrategy::Random { from: None }; @@ -976,7 +976,7 @@ fn test_index_transaction_errors_if_balance_overflow() { }, ValueTransferOutput { pkh: address.pkh, - value: std::u64::MAX, + value: u64::MAX, time_lock: 0, }, ]; diff --git a/wallet/src/types.rs b/wallet/src/types.rs index 4286895ee..f157c58bb 100644 --- a/wallet/src/types.rs +++ b/wallet/src/types.rs @@ -259,6 +259,7 @@ pub enum Event { } /// Format of the output of getTransaction +#[allow(dead_code)] #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct GetTransactionResponse { diff --git a/wallet/tests/data_requests.rs b/wallet/tests/data_requests.rs index a6d6a9969..55ad13d50 100644 --- a/wallet/tests/data_requests.rs +++ b/wallet/tests/data_requests.rs @@ -109,6 +109,7 @@ fn test_data_request_report_json_serialization() { RadonScriptExecutionSettings::enable_all(), Some(&inputs), None, + false, ); // Number of retrieval reports should match number of sources