From c59516fd265ec6bad9b5d20c420abbee58970716 Mon Sep 17 00:00:00 2001 From: Dmitry Saveliev Date: Fri, 12 Apr 2019 16:03:22 +0200 Subject: [PATCH] Improvements for the stake maturity tests & implementation Signed-off-by: Dmitry Saveliev --- src/blockchain/blockchain_behavior.cpp | 12 ++- src/blockchain/blockchain_behavior.h | 2 + src/blockchain/blockchain_parameters.h | 2 +- src/test/settings_tests.cpp | 2 + test/functional/proposer_stake_maturity.py | 106 ++++++++++++++++++--- 5 files changed, 108 insertions(+), 16 deletions(-) diff --git a/src/blockchain/blockchain_behavior.cpp b/src/blockchain/blockchain_behavior.cpp index ea24d51320..0a785bd545 100644 --- a/src/blockchain/blockchain_behavior.cpp +++ b/src/blockchain/blockchain_behavior.cpp @@ -9,7 +9,9 @@ namespace blockchain { Behavior::Behavior(const Parameters ¶meters) noexcept - : m_parameters(parameters) {} + : m_parameters(parameters) { + CheckConsistency(); +} Difficulty Behavior::CalculateDifficulty(Height height, ChainAccess &chain) const { return m_parameters.difficulty_function(m_parameters, height, chain); @@ -125,4 +127,12 @@ Behavior &Behavior::GetGlobal() { return *g_blockchain_behavior; } +void Behavior::CheckConsistency() const { + if (this->m_parameters.stake_maturity_activation_height < this->m_parameters.stake_maturity) { + throw std::logic_error( + "Invalid blockchain parameters: 'stake_maturity_activation_height' " + "must be greater or equal 'stake_maturity'"); + } +} + } // namespace blockchain diff --git a/src/blockchain/blockchain_behavior.h b/src/blockchain/blockchain_behavior.h index 5aa1b3c0f7..4497bf9c43 100644 --- a/src/blockchain/blockchain_behavior.h +++ b/src/blockchain/blockchain_behavior.h @@ -30,6 +30,8 @@ class Behavior { private: const Parameters m_parameters; + void CheckConsistency() const; + public: explicit Behavior(const Parameters &) noexcept; diff --git a/src/blockchain/blockchain_parameters.h b/src/blockchain/blockchain_parameters.h index 1991a26e73..dac83e8b34 100644 --- a/src/blockchain/blockchain_parameters.h +++ b/src/blockchain/blockchain_parameters.h @@ -160,7 +160,7 @@ struct Parameters { //! \brief Stake maturity must be ignored on the network start for activation height. //! //! If there are less than the required depth number of spendable/mature coins - //! in the system than the system will be stuck. + //! in the system then the system will be stuck. //! To make the situation when there are no mature coins for staking less likely //! we activate stake maturity validation only when the blockchain's heigth is bigger than activation height. Height stake_maturity_activation_height; diff --git a/src/test/settings_tests.cpp b/src/test/settings_tests.cpp index 7ac4cda687..1f5b77f634 100644 --- a/src/test/settings_tests.cpp +++ b/src/test/settings_tests.cpp @@ -20,6 +20,8 @@ BOOST_AUTO_TEST_CASE(pick_settings_test) { ArgsManager args_manager; blockchain::Parameters blockchain_parameters; blockchain_parameters.default_settings.stake_combine_maximum = v; + blockchain_parameters.stake_maturity = 2; + blockchain_parameters.stake_maturity_activation_height = 2; std::unique_ptr blockchain_behavior = blockchain::Behavior::NewFromParameters(blockchain_parameters); diff --git a/test/functional/proposer_stake_maturity.py b/test/functional/proposer_stake_maturity.py index 783f44487f..b879c65f6b 100755 --- a/test/functional/proposer_stake_maturity.py +++ b/test/functional/proposer_stake_maturity.py @@ -3,15 +3,49 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. +from test_framework.blocktools import ( + create_block, + create_coinbase, + get_tip_snapshot_meta, + sign_coinbase, +) from test_framework.util import ( assert_equal, - connect_nodes_bi, + connect_nodes, sync_blocks, wait_until, ) +from test_framework.mininode import ( + P2PInterface, + msg_witness_block, + network_thread_start, +) from test_framework.test_framework import UnitETestFramework +class P2P(P2PInterface): + def __init__(self): + super().__init__() + self.messages = [] + self.rejects = [] + + def reset_messages(self): + self.messages = [] + self.rejects = [] + + def on_commits(self, msg): + self.messages.append(msg) + + def on_reject(self, msg): + self.rejects.append(msg) + + def has_reject(self, err, block): + for r in self.rejects: + if r.reason == err and r.data == block: + return True + return False + + class ProposerStakeMaturityTest(UnitETestFramework): def set_test_params(self): @@ -24,19 +58,31 @@ def set_test_params(self): self.customchainparams = [{"stake_maturity_activation_height": 2}] * 2 def run_test(self): - nodes = self.nodes + def build_block_with_immature_stake(node): + height = node.getblockcount() + stakes = node.listunspent() + # Take the latest, immature stake + stake = sorted( + stakes, + key=lambda x: x['confirmations'], + reverse=False)[0] + snapshot_meta = get_tip_snapshot_meta(node) + coinbase = sign_coinbase( + node, create_coinbase( + height, stake, snapshot_meta.hash)) + + tip = int(node.getbestblockhash(), 16) + block_time = node.getblock( + self.nodes[0].getbestblockhash())['time'] + 1 + block = create_block(tip, coinbase, block_time) + + block.solve() + return block def has_synced_blockchain(i): status = nodes[i].proposerstatus() return status['wallets'][0]['status'] != 'NOT_PROPOSING_SYNCING_BLOCKCHAIN' - self.log.info("Waiting for nodes to have started up...") - wait_until(lambda: all(has_synced_blockchain(i) - for i in range(0, self.num_nodes)), timeout=5) - - self.log.info("Connecting nodes") - connect_nodes_bi(nodes, 0, 1) - def wait_until_all_have_reached_state(expected, which_nodes): def predicate(i): status = nodes[i].proposerstatus() @@ -45,11 +91,38 @@ def predicate(i): for i in which_nodes), timeout=5) return predicate - # none of the nodes has any money now, but a bunch of friends - for i in range(self.num_nodes): - status = nodes[i].proposerstatus() - assert_equal(status['incoming_connections'], self.num_nodes - 1) - assert_equal(status['outgoing_connections'], self.num_nodes - 1) + def assert_number_of_connections(node, incoming, outgoing): + status = node.proposerstatus() + assert_equal(status['incoming_connections'], incoming) + assert_equal(status['outgoing_connections'], outgoing) + + def check_reject(node, err, block): + wait_until(lambda: node.p2p.has_reject(err, block), timeout=5) + + nodes = self.nodes + + # Create P2P connections to the second node + self.nodes[1].add_p2p_connection(P2P()) + network_thread_start() + + self.log.info("Waiting untill the P2P connection is fully up...") + wait_until(lambda: self.nodes[1].p2p.got_verack(), timeout=10) + + self.log.info("Waiting for nodes to have started up...") + wait_until(lambda: all(has_synced_blockchain(i) + for i in range(0, self.num_nodes)), timeout=5) + + self.log.info("Connecting nodes") + connect_nodes(nodes[0], nodes[1].index) + + assert_number_of_connections( + self.nodes[0], + self.num_nodes - 1, + self.num_nodes - 1) + assert_number_of_connections( + self.nodes[1], + self.num_nodes, + self.num_nodes - 1) self.setup_stake_coins(*self.nodes) @@ -88,6 +161,11 @@ def predicate(i): # Second node still have two immature stake outputs self.check_node_balance(nodes[1], 10000, 8000) + # Try to send the block with immature stake + block = build_block_with_immature_stake(self.nodes[1]) + self.nodes[1].p2p.send_message(msg_witness_block(block)) + check_reject(self.nodes[1], b'bad-stake-immature', block.sha256) + def check_node_balance(self, node, balance, stakeable_balance): status = node.proposerstatus() wallet = status['wallets'][0]