diff --git a/core/src/consensus/mod.rs b/core/src/consensus/mod.rs index 144fa85158..32dfac013d 100644 --- a/core/src/consensus/mod.rs +++ b/core/src/consensus/mod.rs @@ -16,7 +16,7 @@ mod bit_set; mod null_engine; -mod signer; +pub(crate) mod signer; mod simple_poa; mod solo; pub mod stake; diff --git a/core/src/consensus/stake/action_data.rs b/core/src/consensus/stake/action_data.rs index ba5a47b4d1..7b8d71afe3 100644 --- a/core/src/consensus/stake/action_data.rs +++ b/core/src/consensus/stake/action_data.rs @@ -597,6 +597,9 @@ impl Candidates { pub fn len(&self) -> usize { self.0.len() } + pub fn is_empty(&self) -> bool { + self.0.len() == 0 + } #[cfg(test)] pub fn get_index(&self, account: &Address) -> Option { @@ -715,6 +718,9 @@ impl Jail { pub fn len(&self) -> usize { self.0.len() } + pub fn is_empty(&self) -> bool { + self.0.len() == 0 + } pub fn add(&mut self, candidate: Candidate, custody_until: u64, released_at: u64) { assert!(custody_until <= released_at); diff --git a/core/src/consensus/stake/distribute.rs b/core/src/consensus/stake/distribute.rs index 7caee8cd4c..67e9c4c315 100644 --- a/core/src/consensus/stake/distribute.rs +++ b/core/src/consensus/stake/distribute.rs @@ -19,7 +19,10 @@ use std::collections::hash_map; use std::collections::HashMap; use std::convert::TryFrom; -pub fn fee_distribute(total_min_fee: u64, stakes: &HashMap) -> FeeDistributeIter<'_> { +pub fn fee_distribute( + total_min_fee: u64, + stakes: &HashMap, +) -> FeeDistributeIter<'_> { FeeDistributeIter { total_stakes: stakes.values().sum(), total_min_fee, diff --git a/core/src/consensus/stake/mod.rs b/core/src/consensus/stake/mod.rs index f45d7caac1..5ba457be2a 100644 --- a/core/src/consensus/stake/mod.rs +++ b/core/src/consensus/stake/mod.rs @@ -29,11 +29,14 @@ use parking_lot::RwLock; use primitives::{Bytes, H256}; use rlp::{Decodable, Rlp}; use std::collections::btree_map::BTreeMap; +use std::collections::hash_map::RandomState; use std::collections::HashMap; use std::sync::{Arc, Weak}; -pub use self::action_data::{Banned, CurrentValidators, NextValidators, PreviousValidators, Validator}; -use self::action_data::{Candidates, Delegation, IntermediateRewards, Jail, ReleaseResult, StakeAccount, Stakeholders}; +pub use self::action_data::{ + Banned, Candidates, CurrentValidators, Jail, NextValidators, PreviousValidators, Validator, +}; +use self::action_data::{Delegation, IntermediateRewards, ReleaseResult, StakeAccount, Stakeholders}; pub use self::actions::Action; pub use self::distribute::fee_distribute; use super::ValidatorSet; @@ -330,7 +333,10 @@ pub fn drain_current_rewards(state: &mut TopLevelState) -> StateResult) -> StateResult<()> { +pub fn update_calculated_rewards( + state: &mut TopLevelState, + values: HashMap, +) -> StateResult<()> { let mut rewards = IntermediateRewards::load_from_state(state)?; rewards.update_calculated(values.into_iter().collect()); rewards.save_to_state(state) diff --git a/core/src/consensus/tendermint/mod.rs b/core/src/consensus/tendermint/mod.rs index 125fa04c2b..27d22fae3d 100644 --- a/core/src/consensus/tendermint/mod.rs +++ b/core/src/consensus/tendermint/mod.rs @@ -29,7 +29,7 @@ use self::chain_notify::TendermintChainNotify; pub use self::message::{ConsensusMessage, VoteOn, VoteStep}; pub use self::params::{TendermintParams, TimeGapParams, TimeoutParams}; pub use self::types::{Height, Step, View}; -use super::{stake, ValidatorSet}; +pub use super::{stake, ValidatorSet}; use crate::client::ConsensusClient; use crate::codechain_machine::CodeChainMachine; use crate::snapshot_notify::NotifySender as SnapshotNotifySender; diff --git a/core/src/lib.rs b/core/src/lib.rs index f231094474..bb1a08533e 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -31,6 +31,7 @@ extern crate codechain_types as ctypes; extern crate codechain_vm as cvm; #[cfg(test)] extern crate rand_xorshift; +extern crate rlp; #[macro_use] extern crate rlp_derive; #[macro_use] @@ -63,16 +64,20 @@ mod tests; pub use crate::account_provider::{AccountProvider, Error as AccountProviderError}; pub use crate::block::Block; pub use crate::client::snapshot_notify; +pub use crate::client::ConsensusClient; pub use crate::client::{ AccountData, AssetClient, BlockChainClient, BlockChainTrait, ChainNotify, Client, ClientConfig, DatabaseClient, EngineClient, EngineInfo, ExecuteClient, ImportBlock, MiningBlockChainClient, Shard, SnapshotClient, StateInfo, TermInfo, TestBlockChainClient, }; +pub use crate::consensus::signer::EngineSigner; +pub use crate::consensus::stake; pub use crate::consensus::{EngineType, TimeGapParams}; pub use crate::db::{COL_PEER, COL_STATE, NUM_COLUMNS}; pub use crate::error::{BlockImportError, Error, ImportError}; pub use crate::miner::{MemPoolFees, Miner, MinerOptions, MinerService}; pub use crate::peer_db::PeerDb; +pub use crate::rlp::Encodable; pub use crate::scheme::Scheme; pub use crate::service::ClientService; pub use crate::transaction::{ diff --git a/core/src/miner/miner.rs b/core/src/miner/miner.rs index 97afce1cc0..d568cf76c7 100644 --- a/core/src/miner/miner.rs +++ b/core/src/miner/miner.rs @@ -586,6 +586,10 @@ impl MinerService for Miner { } } + fn get_author_address(&self) -> Address { + self.params.read().author + } + fn set_extra_data(&self, extra_data: Bytes) { self.params.write().extra_data = extra_data; } diff --git a/core/src/miner/mod.rs b/core/src/miner/mod.rs index 7d459d537e..d602bd110e 100644 --- a/core/src/miner/mod.rs +++ b/core/src/miner/mod.rs @@ -53,6 +53,9 @@ pub trait MinerService: Send + Sync { /// Set the author that we will seal blocks as. fn set_author(&self, author: Address) -> Result<(), AccountProviderError>; + ///Get the address that sealed the block. + fn get_author_address(&self) -> Address; + /// Set the extra_data that we will seal blocks with. fn set_extra_data(&self, extra_data: Bytes); diff --git a/foundry/auto_self_nominate.rs b/foundry/auto_self_nominate.rs new file mode 100644 index 0000000000..43b74f8b34 --- /dev/null +++ b/foundry/auto_self_nominate.rs @@ -0,0 +1,196 @@ +// Copyright 2020 Kodebox, Inc. +// This file is part of CodeChain. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::config::load_config; +use ccore::stake::Action::SelfNominate; +use ccore::stake::{Banned, Candidates, Jail, CUSTOM_ACTION_HANDLER_ID}; +use ccore::{ + AccountProvider, AccountProviderError, BlockId, ConsensusClient, Encodable, SignedTransaction, + UnverifiedTransaction, +}; +use ckey::PlatformAddress; +use ckey::{Address, Public, Signature}; +use ckeystore::DecryptedAccount; +use clap::ArgMatches; +use codechain_types::transaction::{Action, Transaction}; +use primitives::{Bytes, H256}; +use std::sync::Arc; +use std::thread; +use std::time::Duration; + +const NEED_NOMINATION_UNDER_TERM_LEFT: u64 = 1; +#[derive(Clone)] +struct SelfSigner { + account_provider: Arc, + signer: Option<(Address, Public)>, + decrypted_account: Option, +} +impl SelfSigner { + pub fn new(ap: Arc, address: Address) -> Self { + let public = { + let account = ap.get_unlocked_account(&address).expect("The address must be registered in AccountProvider"); + account.public().expect("Cannot get public from account") + }; + Self { + account_provider: ap, + signer: Some((address, public)), + decrypted_account: None, + } + } + + pub fn sign_ecdsa(&self, hash: H256) -> Result { + let address = self.signer.map(|(address, _public)| address).unwrap_or_else(Default::default); + let result = match &self.decrypted_account { + Some(account) => account.sign(&hash)?, + None => { + let account = self.account_provider.get_unlocked_account(&address)?; + account.sign(&hash)? + } + }; + Ok(result) + } + + pub fn address(&self) -> Option<&Address> { + self.signer.as_ref().map(|(address, _)| address) + } +} + +pub struct AutoSelfNomination { + client: Arc, + signer: SelfSigner, +} + +impl AutoSelfNomination { + pub fn new(client: Arc, ap: Arc, address: Address) -> Arc { + Arc::new(Self { + client, + signer: SelfSigner::new(ap, address), + }) + } + + pub fn send_self_nominate_transaction(&self, matches: &ArgMatches) { + let config = load_config(matches).unwrap(); + let account_address = config.mining.engine_signer.unwrap(); + let defualt_metadata = config.mining.self_nomination_metadata.unwrap(); + let target_deposite = config.mining.self_target_deposit.unwrap(); + let interval = config.mining.self_nomination_interval.unwrap(); + let self_client = self.client.clone(); + let self_signer = self.signer.clone(); + thread::Builder::new() + .name("Auto Self Nomination".to_string()) + .spawn(move || loop { + AutoSelfNomination::send( + &self_client, + &self_signer, + &account_address, + &defualt_metadata, + target_deposite, + ); + thread::sleep(Duration::from_millis(interval)); + }) + .unwrap(); + } + + fn send( + client: &Arc, + signer: &SelfSigner, + account_address: &PlatformAddress, + metadata: &str, + targetdep: u64, + ) { + let metabytes = metadata.rlp_bytes(); + let mut dep = targetdep; + let address = account_address.address(); + let block_id = BlockId::Latest; + let state = client.state_at(block_id).unwrap(); + let current_term = client.current_term_id(block_id).unwrap(); + let banned = Banned::load_from_state(&state).unwrap(); + if banned.is_banned(address) { + cwarn!(ENGINE, "Account is banned"); + return + } + let jailed = Jail::load_from_state(&state).unwrap(); + if jailed.get_prisoner(&address).is_some() { + let prisoner = jailed.get_prisoner(&address).unwrap(); + + if prisoner.custody_until <= (current_term) { + cwarn!(ENGINE, "Account is still in custody"); + return + } + } + let candidate = Candidates::load_from_state(&state).unwrap(); + if candidate.get_candidate(&address).is_some() { + let candidate_need_nomination = candidate.get_candidate(&address).unwrap(); + if candidate_need_nomination.nomination_ends_at <= current_term + NEED_NOMINATION_UNDER_TERM_LEFT { + cdebug!(ENGINE, "No need self nominate"); + return + } + if candidate_need_nomination.deposit.lt(&targetdep) { + dep = targetdep.min(targetdep); + } else { + dep = 0 as u64; + } + } + + AutoSelfNomination::self_nomination_transaction(&client, &signer, dep, metabytes); + } + + fn self_nomination_transaction( + client: &Arc, + signer: &SelfSigner, + deposit: u64, + metadata: Bytes, + ) { + let network_id = client.network_id(); + let seq = match signer.address() { + Some(address) => client.latest_seq(address), + None => { + cwarn!(ENGINE, "Signer was not assigned"); + return + } + }; + let selfnominate = SelfNominate { + deposit, + metadata, + }; + let tx = Transaction { + seq, + fee: 0, + network_id, + action: Action::Custom { + handler_id: CUSTOM_ACTION_HANDLER_ID, + bytes: selfnominate.rlp_bytes(), + }, + }; + + let signature = match signer.sign_ecdsa(*tx.hash()) { + Ok(signature) => signature, + Err(e) => { + cerror!(ENGINE, "Could not sign the message:{}", e); + return + } + }; + let unverified = UnverifiedTransaction::new(tx, signature); + let signed = SignedTransaction::try_new(unverified).expect("secret is valid so it's recoverable"); + + match client.queue_own_transaction(signed) { + Ok(_) => {} + Err(e) => { + cerror!(ENGINE, "Failed to queue self nominate transaction: {}", e); + } + } + } +} diff --git a/foundry/config/mod.rs b/foundry/config/mod.rs index 245597af94..8ccf7ff4f1 100644 --- a/foundry/config/mod.rs +++ b/foundry/config/mod.rs @@ -220,6 +220,10 @@ pub struct Mining { pub engine_signer: Option, pub mem_pool_size: Option, pub mem_pool_mem_limit: Option, + pub self_nomination_metadata: Option, + pub self_target_deposit: Option, + pub self_nomination_enable: bool, + pub self_nomination_interval: Option, pub mem_pool_fee_bump_shift: Option, pub allow_create_shard: Option, pub force_sealing: Option, @@ -387,6 +391,15 @@ impl Mining { if other.engine_signer.is_some() { self.engine_signer = other.engine_signer; } + if other.self_nomination_metadata.is_some() { + self.self_nomination_metadata = other.self_nomination_metadata.clone(); + } + if other.self_target_deposit.is_some() { + self.self_target_deposit = other.self_target_deposit; + } + if other.self_nomination_interval.is_some() { + self.self_nomination_interval = other.self_nomination_interval; + } if other.mem_pool_size.is_some() { self.mem_pool_size = other.mem_pool_size; } @@ -456,6 +469,22 @@ impl Mining { if let Some(engine_signer) = matches.value_of("engine-signer") { self.engine_signer = Some(engine_signer.parse().map_err(|_| "Invalid address format")?); } + if let Some(self_nomination_metadata) = matches.value_of("self-nomination-metadata") { + self.self_nomination_metadata = + Some(self_nomination_metadata.parse().map_err(|_| "Invalid self nomination metadata format")?); + } + if let Some(self_nomination_target_deposit) = matches.value_of("self-nomination-target-deposit") { + self.self_target_deposit = Some( + self_nomination_target_deposit.parse().map_err(|_| "Invalid self nomination target deposit format")?, + ); + } + if let Some(self_nomination_interval) = matches.value_of("self-nomination-interval") { + self.self_nomination_interval = + Some(self_nomination_interval.parse().map_err(|_| "Invalid self nomination interval format")?); + } + if matches.is_present("enable-auto-self-nomination") { + self.self_nomination_enable = true; + } if matches.is_present("no-miner") { self.author = None; self.engine_signer = None; diff --git a/foundry/config/presets/config.dev.toml b/foundry/config/presets/config.dev.toml index 53b6224325..f6556cbb5c 100644 --- a/foundry/config/presets/config.dev.toml +++ b/foundry/config/presets/config.dev.toml @@ -13,6 +13,7 @@ reseal_on_txs = "all" reseal_min_period = 0 reseal_max_period = 120000 no_reseal_timer = false +self_nomination_enable = false allowed_past_gap = 30000 allowed_future_gap = 5000 diff --git a/foundry/config/presets/config.prod.toml b/foundry/config/presets/config.prod.toml index e54ed4f17b..7c8b2e18f0 100644 --- a/foundry/config/presets/config.prod.toml +++ b/foundry/config/presets/config.prod.toml @@ -6,6 +6,7 @@ chain = "mainnet" [mining] mem_pool_mem_limit = 512 # MB mem_pool_size = 524288 +self_nomination_enable =false mem_pool_fee_bump_shift = 3 # 12.5% allow_create_shard = false force_sealing = true @@ -15,7 +16,7 @@ reseal_max_period = 120000 no_reseal_timer = false allowed_past_gap = 30000 allowed_future_gap = 5000 - +i [network] disable = false interface = "0.0.0.0" diff --git a/foundry/foundry.yml b/foundry/foundry.yml index c385818eb4..2df3bd25d3 100644 --- a/foundry/foundry.yml +++ b/foundry/foundry.yml @@ -160,6 +160,21 @@ args: long: engine-signer help: Specify the address which should be used to sign consensus messages and issue blocks. takes_value: true + - self-nomination-metadata: + long: self-nomination-metadata + help: Specify metadata which should be used to do self nomination. + takes_value: true + - self-nomination-target-deposit: + long: self-nomination-target-deposit + help: Specify the amount of deposit need to provide deposite for candidatory in the process of self nomination. + takes_value: true + - self-nomination-interval: + long: self-nomination-interval + help: Specify the time(ms) interval for self nomination process. + takes_value: true + - enable-auto-self-nomination: + long: enable-auto-self-nomination + help: Run auto self nomination thread. - password-path: long: password-path help: Specify the password file path. diff --git a/foundry/main.rs b/foundry/main.rs index 7a3649fe5f..306729ecae 100644 --- a/foundry/main.rs +++ b/foundry/main.rs @@ -33,6 +33,7 @@ extern crate codechain_timer as ctimer; use panic_hook; +mod auto_self_nominate; mod config; mod constants; mod dummy_network_service; diff --git a/foundry/run_node.rs b/foundry/run_node.rs index 834016fe94..1ee7e4712c 100644 --- a/foundry/run_node.rs +++ b/foundry/run_node.rs @@ -14,13 +14,14 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +use crate::auto_self_nominate::AutoSelfNomination; use crate::config::{self, load_config}; use crate::constants::{DEFAULT_DB_PATH, DEFAULT_KEYS_PATH}; use crate::dummy_network_service::DummyNetworkService; use crate::json::PasswordFile; use crate::rpc::{rpc_http_start, rpc_ipc_start, rpc_ws_start, setup_rpc_server}; use crate::rpc_apis::ApiDependencies; -use ccore::{snapshot_notify, EngineClient}; +use ccore::{snapshot_notify, ConsensusClient, EngineClient}; use ccore::{ AccountProvider, AccountProviderError, ChainNotify, ClientConfig, ClientService, EngineInfo, EngineType, Miner, MinerService, PeerDb, Scheme, NUM_COLUMNS, @@ -71,6 +72,11 @@ fn network_start( Ok(service) } +fn self_nominate_start(c: Arc, matches: &ArgMatches, ap: Arc, address: Address) { + let auto_self_nominate = AutoSelfNomination::new(c, ap, address); + auto_self_nominate.send_self_nominate_transaction(matches); +} + fn discovery_start( service: &NetworkService, cfg: &config::Network, @@ -304,6 +310,12 @@ pub fn run_node(matches: &ArgMatches<'_>) -> Result<(), String> { Arc::new(DummyNetworkService::new()) } }; + if config.mining.self_nomination_enable { + let c = client.client(); + let address = miner.get_author_address(); + let accountp = ap.clone(); + self_nominate_start(c, matches, accountp, address); + } let (rpc_server, ipc_server, ws_server) = { let rpc_apis_deps = ApiDependencies { diff --git a/keystore/src/account/decrypted_account.rs b/keystore/src/account/decrypted_account.rs index d9b4ce08ec..6169da3bd4 100644 --- a/keystore/src/account/decrypted_account.rs +++ b/keystore/src/account/decrypted_account.rs @@ -19,6 +19,7 @@ use ckey::{ }; /// An opaque wrapper for secret. +#[derive(Clone)] pub struct DecryptedAccount { secret: Secret, } diff --git a/network/src/p2p/handler.rs b/network/src/p2p/handler.rs index 2cb3a4dcdc..ba65471ace 100644 --- a/network/src/p2p/handler.rs +++ b/network/src/p2p/handler.rs @@ -242,7 +242,7 @@ impl Handler { let mut result = HashMap::with_capacity(network_usage_in_10_seconds.len()); let now = Instant::now(); for (name, times) in &mut *network_usage_in_10_seconds { - remove_outdated_network_usage(times, &now); + remove_outdated_network_usage(times, now); let total = times.iter().map(|(_, usage)| usage).sum(); if total != 0 { result.insert(name.clone(), total); @@ -1198,9 +1198,9 @@ impl ::std::fmt::Display for Error { } } -fn remove_outdated_network_usage(usage_per_extension: &mut VecDeque<(Instant, usize)>, now: &Instant) { +fn remove_outdated_network_usage(usage_per_extension: &mut VecDeque<(Instant, usize)>, now: Instant) { while let Some((time, size)) = usage_per_extension.pop_front() { - if *now < time { + if now < time { usage_per_extension.push_front((time, size)); break } @@ -1209,6 +1209,6 @@ fn remove_outdated_network_usage(usage_per_extension: &mut VecDeque<(Instant, us fn insert_network_usage(usage_per_extension: &mut VecDeque<(Instant, usize)>, network_message_size: usize) { let now = Instant::now(); - remove_outdated_network_usage(usage_per_extension, &now); + remove_outdated_network_usage(usage_per_extension, now); usage_per_extension.push_back((now + Duration::from_secs(10), network_message_size)); } diff --git a/test/src/config/mem-pool-min-fee1.toml b/test/src/config/mem-pool-min-fee1.toml index 7d22d4bdb7..44d5c8c97d 100644 --- a/test/src/config/mem-pool-min-fee1.toml +++ b/test/src/config/mem-pool-min-fee1.toml @@ -2,6 +2,7 @@ [mining] min_pay_transaction_cost = 150 +self_nomination_enable = false [network] diff --git a/test/src/config/mem-pool-min-fee2.toml b/test/src/config/mem-pool-min-fee2.toml index d9bce6d64b..884c759ab9 100644 --- a/test/src/config/mem-pool-min-fee2.toml +++ b/test/src/config/mem-pool-min-fee2.toml @@ -2,6 +2,7 @@ [mining] min_pay_transaction_cost = 200 +self_nomination_enable = false [network] diff --git a/test/src/e2e.dynval/2/selfnomination.test.ts b/test/src/e2e.dynval/2/selfnomination.test.ts new file mode 100644 index 0000000000..a74b623b52 --- /dev/null +++ b/test/src/e2e.dynval/2/selfnomination.test.ts @@ -0,0 +1,125 @@ +// Copyright 2020 Kodebox, Inc. +// This file is part of CodeChain. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import { expect } from "chai"; +import { H512 } from "codechain-primitives/lib"; +import * as stake from "codechain-stakeholder-sdk"; +import "mocha"; + +import { validators } from "../../../tendermint.dynval/constants"; +import { PromiseExpect } from "../../helper/promise"; +import { + findNode, + selfNominate, + setTermTestTimeout, + withNodes +} from "../setup"; + +describe("Auto Self Nomination", function() { + const promiseExpect = new PromiseExpect(); + const NOMINATION_EXPIRATION = 2; + const TERM_SECOND = 30; + + describe("Alice doesn't self nominate in NOMINATION_EXPIRATION, Bob sends auto self nomination", async function() { + const initialValidators = validators.slice(0, 3); + const alice = validators[3]; + const bob = validators[4]; + const { nodes } = withNodes(this, { + promiseExpect, + overrideParams: { + termSeconds: TERM_SECOND, + nominationExpiration: NOMINATION_EXPIRATION + }, + validators: [ + ...initialValidators.map((validator, index) => ({ + signer: validator, + delegation: 5000 - index, + deposit: 100000 + })), + { signer: alice, autoSelfNominate: false }, + { signer: bob, autoSelfNominate: true } + ] + }); + it.only("Alice be eligible after 2 terms and Bob did auto self nomination", async function() { + const termWaiter = setTermTestTimeout(this, { + terms: 3, + params: { + termSeconds: TERM_SECOND + } + }); + + const aliceNode = findNode(nodes, alice); + const bobNode = findNode(nodes, bob); + const selfNominationHash = await selfNominate( + aliceNode.sdk, + alice, + 10 + ); + const bobselfNomination = await selfNominate(bobNode.sdk, bob, 10); + await aliceNode.waitForTx(selfNominationHash); + await bobNode.waitForTx(bobselfNomination); + + const beforeCandidates = await stake.getCandidates(nodes[0].sdk); + + expect( + beforeCandidates.map(candidate => candidate.pubkey.toString()) + ).to.includes(H512.ensure(alice.publicKey).toString()); + expect( + beforeCandidates.map(candidate => candidate.pubkey.toString()) + ).to.includes(H512.ensure(bob.publicKey).toString()); + + await termWaiter.waitNodeUntilTerm(nodes[0], { + target: 4, + termPeriods: 3 + }); + + const [ + currentValidators, + banned, + candidates, + jailed + ] = await Promise.all([ + stake.getValidators(nodes[0].sdk), + stake.getBanned(nodes[0].sdk), + stake.getCandidates(nodes[0].sdk), + stake.getJailed(nodes[0].sdk) + ]); + + expect( + currentValidators.map(validator => validator.pubkey.toString()) + ).not.to.includes(alice.publicKey); + expect( + banned.map(ban => ban.getAccountId().toString()) + ).not.to.includes(alice.accountId); + expect( + candidates.map(candidate => candidate.pubkey.toString()) + ).not.to.includes(alice.publicKey); + expect(jailed.map(jail => jail.address)).not.to.includes( + alice.platformAddress.toString() + ); + expect( + currentValidators.map(validator => validator.pubkey.toString()) + ).not.to.includes(bob.publicKey); + expect( + candidates.map(candidate => candidate.pubkey.toString()) + ).to.includes(bob.publicKey); + }); + }); + + afterEach(function() { + promiseExpect.checkFulfilled(); + }); +}); diff --git a/test/src/e2e.dynval/setup.ts b/test/src/e2e.dynval/setup.ts index 3a52d365eb..4528f76d69 100644 --- a/test/src/e2e.dynval/setup.ts +++ b/test/src/e2e.dynval/setup.ts @@ -37,6 +37,7 @@ interface ValidatorConfig { signer: Signer; deposit?: U64Value; delegation?: U64Value; + autoSelfNominate?: boolean; } interface NodePropertyModifier { @@ -141,17 +142,28 @@ async function createNodes(options: { const nodes: (CodeChain & T)[] = []; for (let i = 0; i < validators.length; i++) { const { signer: validator } = validators[i]; + const argv = [ + "--engine-signer", + validator.platformAddress.value, + "--password-path", + `test/tendermint.dynval/${validator.platformAddress.value}/password.json`, + "--force-sealing" + ]; + if (validators[i].autoSelfNominate) { + argv.push( + "--enable-auto-self-nomination", + "--self-nomination-metadata", + "", + "--self-nomination-target-deposit", + "10", + "--self-nomination-interval", + "10" + ); + } const modifier = modify(validator, i); const node = new CodeChain({ chain, - argv: [ - "--engine-signer", - validator.platformAddress.value, - "--password-path", - `test/tendermint.dynval/${validator.platformAddress.value}/password.json`, - "--force-sealing", - ...modifier.additionalArgv - ], + argv: [...argv, ...modifier.additionalArgv], additionalKeysPath: `tendermint.dynval/${validator.platformAddress.value}/keys` }); nodes[i] = Object.assign(node, modifier.nodeAdditionalProperties);