Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements for the stake maturity tests & implementation #977

Merged
merged 2 commits into from
Apr 16, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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