Skip to content

Commit

Permalink
Improvements for the stake maturity tests & implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitry Saveliev <[email protected]>
  • Loading branch information
dsaveliev committed Apr 15, 2019
1 parent c0b2be5 commit c59516f
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 16 deletions.
12 changes: 11 additions & 1 deletion src/blockchain/blockchain_behavior.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
namespace blockchain {

Behavior::Behavior(const Parameters &parameters) 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);
Expand Down Expand Up @@ -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
2 changes: 2 additions & 0 deletions src/blockchain/blockchain_behavior.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class Behavior {
private:
const Parameters m_parameters;

void CheckConsistency() const;

public:
explicit Behavior(const Parameters &) noexcept;

Expand Down
2 changes: 1 addition & 1 deletion src/blockchain/blockchain_parameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/test/settings_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
blockchain::Behavior::NewFromParameters(blockchain_parameters);

Expand Down
106 changes: 92 additions & 14 deletions test/functional/proposer_stake_maturity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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()
Expand All @@ -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)

Expand Down Expand Up @@ -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]
Expand Down

0 comments on commit c59516f

Please sign in to comment.