Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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/llmq/ehf_signals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,26 @@ CEHFSignalsHandler::~CEHFSignalsHandler()
sigman.UnregisterRecoveredSigsListener(this);
}

static bool IsSignalWindowOpen(const Consensus::BIP9Deployment& deployment, int64_t time_past)
{
return deployment.nStartTime != Consensus::BIP9Deployment::NEVER_ACTIVE && deployment.nStartTime <= time_past &&
time_past <= deployment.nTimeout;
}

void CEHFSignalsHandler::UpdatedBlockTip(const CBlockIndex* const pindexNew)
{
if (!DeploymentActiveAfter(pindexNew, Params().GetConsensus(), Consensus::DEPLOYMENT_V20)) return;

const auto ehfSignals = m_chainman.ActiveChainstate().ChainHelper().ehf_manager->GetSignalsStage(pindexNew);
const int64_t time_past = pindexNew->GetMedianTimePast();
for (const auto& deployment : Params().GetConsensus().vDeployments) {
// Skip deployments that do not use dip0023
if (!deployment.useEHF) continue;
// IsValidMNActivation() is permissive for validation (it accepts bits of NEVER_ACTIVE deployments
// and bits outside of any window), so only sign inside the deployment's own start/timeout window
if (!IsSignalWindowOpen(deployment, time_past)) continue;
// Try to sign only activable deployments that haven't been mined yet
if (ehfSignals.find(deployment.bit) == ehfSignals.end() && Params().IsValidMNActivation(deployment.bit, pindexNew->GetMedianTimePast())) {
if (ehfSignals.find(deployment.bit) == ehfSignals.end() && Params().IsValidMNActivation(deployment.bit, time_past)) {
trySignEHFSignal(deployment.bit, pindexNew);
}
}
Expand Down
35 changes: 34 additions & 1 deletion test/functional/feature_mnehf.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ def add_options(self, parser):
self.add_wallet_options(parser)

def set_test_params(self):
extra_args = [["-vbparams=testdummy:0:999999999999:0:4:4:4:5:1", "-persistmempool=0"]] * 2
# testdummy starts as NEVER_ACTIVE, run_test opens its signalling window later
extra_args = [["-vbparams=testdummy:-2:999999999999:0:4:4:4:5:1", "-persistmempool=0"]] * 2
# One masternode is enough: the EHF signal only needs a recovered signature of any quorum
self.set_dash_test_params(2, 1, extra_args=extra_args)
self.set_dash_llmq_test_params(1, 1)
Expand Down Expand Up @@ -99,6 +100,37 @@ def check_fork(self, expected):
self.log.info(f"height: {self.nodes[0].getblockcount()} status: {status}")
assert_equal(status, expected)

def ehf_signals_in_mempool(self, node, version_bit):
return [txid for txid in node.getrawmempool()
if node.getrawtransaction(txid, True).get('mnhfTx', {}).get('signal', {}).get('versionBit') == version_bit]

def check_no_ehf_signing(self, reason):
self.log.info(f"Check that masternodes do not sign testdummy {reason}")
mn_node = self.mninfo[0].get_node(self)
# Masternodes try to sign on every new tip, so an attempt is logged once the tip callbacks are done
with mn_node.assert_debug_log(expected_msgs=[], unexpected_msgs=["bit=28 at height"]):
self.generate(self.nodes[0], 2, sync_fun=self.sync_blocks)
mn_node.syncwithvalidationinterfacequeue()
assert_equal(self.ehf_signals_in_mempool(mn_node, 28), [])

def test_no_signing_outside_window(self):
node = self.nodes[0]
self.check_no_ehf_signing("while it is NEVER_ACTIVE")

self.restart_all_nodes(params=[0, 1])
self.check_no_ehf_signing("after its timeout")

start_time = self.mocktime + 600
self.restart_all_nodes(params=[start_time, 999999999999])
self.check_no_ehf_signing("before its start time")

self.log.info("Check that masternodes sign testdummy once its start time is reached")
self.bump_mocktime(600, update_schedulers=False)
while node.getblockheader(node.getbestblockhash())['mediantime'] < start_time:
self.generate(node, 1)
mn_node = self.mninfo[0].get_node(self)
self.wait_until(lambda: len(self.ehf_signals_in_mempool(mn_node, 28)) == 1)

def ensure_tx_is_not_mined(self, tx_id):
try:
assert_equal(self.nodes[0].getrawtransaction(tx_id, 1)['height'], -1)
Expand Down Expand Up @@ -131,6 +163,7 @@ def run_test(self):
self.set_sporks()
self.log.info("Mine a quorum...")
self.mine_quorum_single_member()
self.test_no_signing_outside_window()
self.check_fork('defined')

key = ECKey()
Expand Down
Loading