Skip to content

Commit

Permalink
deploymentinfo: report signalling
Browse files Browse the repository at this point in the history
  • Loading branch information
ajtowns committed Sep 2, 2024
1 parent 0790a0a commit c06dd9e
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 0 deletions.
25 changes: 25 additions & 0 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,24 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softfo
case ThresholdState::LOCKED_IN:
case ThresholdState::ACTIVE:
bip9.pushKV("signal_abandon", strprintf("%08x", chainman.GetConsensus().vDeployments[id].signal_abandon));

// Report observed signalling
{
UniValue signals(UniValue::VARR);
for (const auto& signal_info : chainman.m_versionbitscache.GetSignalInfo(blockindex, chainman.GetConsensus(), id)) {
UniValue s(UniValue::VOBJ);
if (signal_info.revision == -1) {
// don't report self-activation signals if already active
if (signal_info.activate && current_state == ThresholdState::ACTIVE) continue;
} else {
s.pushKV("revision", signal_info.revision);
}
s.pushKV("height", signal_info.height);
s.pushKV("action", (signal_info.activate ? "activate" : "abandon"));
signals.push_back(s);
}
bip9.pushKV("signals", signals);
}
break;
case ThresholdState::DEACTIVATING:
case ThresholdState::ABANDONED:
Expand Down Expand Up @@ -1355,6 +1373,13 @@ const std::vector<RPCResult> RPCHelpForDeployment{
{RPCResult::Type::STR, "status_next", "status of deployment at the next block"},
{RPCResult::Type::STR_HEX, "signal_activate", /*optional=*/true, "version number to trigger deployment activation"},
{RPCResult::Type::STR_HEX, "signal_abandon", /*optional=*/true, "version number to trigger deployment abandonment"},
{RPCResult::Type::ARR, "signals", /*optional=*/true, "indicates blocks that signalled in the last period",
{{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::NUM, "revision", /*optional=*/true, "revision being signalled"},
{RPCResult::Type::NUM, "height", "height of the signalling block"},
{RPCResult::Type::STR, "action", "action signalled (activate or abandon)"},
}}}},
}},
};

Expand Down
33 changes: 33 additions & 0 deletions src/test/fuzz/versionbits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>

#include <algorithm>
#include <cstdint>
#include <limits>
#include <memory>
#include <vector>

bool operator==(const SignalInfo& a, const SignalInfo& b)
{
return a.height == b.height && a.revision == b.revision && a.activate == b.activate;
}

namespace {
class TestConditionChecker : public AbstractThresholdConditionChecker
{
Expand Down Expand Up @@ -165,6 +171,18 @@ FUZZ_TARGET(versionbits, .init = initialize)
// Now that we have chosen time and versions, setup to mine blocks
Blocks blocks(block_start_time, interval);

const auto siginfo_nosignal = [&]() -> std::optional<SignalInfo> {
int year, number, revision;
if (checker.BINANA(year, number, revision)) {
if ((ver_nosignal & 0xFFFFFF00l) == (ver_activate & 0xFFFFFF00l)) {
return SignalInfo{.height = 0, .revision = static_cast<uint8_t>(ver_nosignal & 0xFF), .activate = true};
} else if ((ver_nosignal & 0xFFFFFF00l) == (ver_abandon & 0xFFFFFF00l)) {
return SignalInfo{.height = 0, .revision = static_cast<uint8_t>(ver_nosignal & 0xFF), .activate = false};
}
}
return std::nullopt;
}();

/* Strategy:
* * we mine n*period blocks, with zero/one of
* those blocks signalling activation, and zero/one of
Expand Down Expand Up @@ -207,8 +225,13 @@ FUZZ_TARGET(versionbits, .init = initialize)
bool sig_abandon = false;
int next_active = 0;
int next_abandon = 0;

std::vector<SignalInfo> exp_siginfo = checker.GetSignalInfo(nullptr); // dummy
assert(exp_siginfo.empty());

for (int b = 1; b <= period; ++b) {
CBlockIndex* current_block = blocks.tip();
const int next_height = (current_block != nullptr ? current_block->nHeight + 1 : 0);

if (current_block != nullptr) {
// state and since don't change within the period
Expand All @@ -221,14 +244,24 @@ FUZZ_TARGET(versionbits, .init = initialize)
while (b > next_abandon) next_abandon += 1 + fuzzed_data_provider.ConsumeIntegral<uint8_t>();
while (b > next_active) next_active += 1 + fuzzed_data_provider.ConsumeIntegral<uint8_t>();
if (b == next_abandon) {
exp_siginfo.push_back({.height = next_height, .revision = -1, .activate = false});
blocks.mine_block(ver_abandon);
sig_abandon = true;
} else if (b == next_active) {
exp_siginfo.push_back({.height = next_height, .revision = -1, .activate = true});
blocks.mine_block(ver_activate);
sig_active = true;
} else {
if (siginfo_nosignal) {
exp_siginfo.push_back(*siginfo_nosignal);
exp_siginfo.back().height = next_height;
}
blocks.mine_block(ver_nosignal);
}

const std::vector<SignalInfo> siginfo = checker.GetSignalInfo(blocks.tip());
assert(siginfo.size() == exp_siginfo.size());
assert(std::equal(siginfo.begin(), siginfo.end(), exp_siginfo.rbegin(), exp_siginfo.rend()));
}

// grab the final block and the state it's left us in
Expand Down
41 changes: 41 additions & 0 deletions src/versionbits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,42 @@ int AbstractThresholdConditionChecker::GetStateSinceHeightFor(const CBlockIndex*
return pindexPrev->nHeight + 1;
}

std::vector<SignalInfo> AbstractThresholdConditionChecker::GetSignalInfo(const CBlockIndex* pindex) const
{
std::vector<SignalInfo> result;

int year = 0, number = 0, revision = 0;
const bool check_other_versions = BINANA(year, number, revision);

const int32_t activate = ActivateVersion();
const int32_t abandon = AbandonVersion();
const int period = Period();

while (pindex != nullptr) {
if (pindex->nVersion == activate) {
result.push_back({ .height = pindex->nHeight, .revision = -1, .activate = true });
} else if (pindex->nVersion == abandon) {
result.push_back({ .height = pindex->nHeight, .revision = -1, .activate = false });
} else if (check_other_versions) {
if ((pindex->nVersion & 0x07FFFF00l) == (activate & 0x07FFFF00l)) {
SignalInfo s;
s.height = pindex->nHeight;
s.revision = static_cast<uint8_t>(pindex->nVersion & 0xFF);
if ((pindex->nVersion & 0xF8000000l) == VERSIONBITS_TOP_ACTIVE) {
s.activate = true;
result.push_back(s);
} else if ((pindex->nVersion & 0xF8000000l) == VERSIONBITS_TOP_ABANDON) {
s.activate = false;
result.push_back(s);
}
}
}
if (pindex->nHeight % period == 0) break;
pindex = pindex->pprev;
}
return result;
}

namespace
{
/**
Expand Down Expand Up @@ -212,6 +248,11 @@ int VersionBitsCache::StateSinceHeight(const CBlockIndex* pindexPrev, const Cons
return VersionBitsConditionChecker(params, pos).GetStateSinceHeightFor(pindexPrev, m_caches[pos]);
}

std::vector<SignalInfo> VersionBitsCache::GetSignalInfo(const CBlockIndex* pindex, const Consensus::Params& params, Consensus::DeploymentPos pos) const
{
return VersionBitsConditionChecker(params, pos).GetSignalInfo(pindex);
}

int32_t VersionBitsCache::ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Params& params)
{
int32_t nVersion = VERSIONBITS_TOP_BITS;
Expand Down
13 changes: 13 additions & 0 deletions src/versionbits.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ enum class ThresholdState {
// will either be nullptr or a block with (height + 1) % Period() == 0.
typedef std::map<const CBlockIndex*, ThresholdState> ThresholdConditionCache;

struct SignalInfo
{
int height;
int revision; // -1 = this revision
bool activate; // true = activate, false = abandon
};

/**
* Abstract class that implements BIP9-style threshold logic, and caches results.
*/
Expand All @@ -56,6 +63,9 @@ class AbstractThresholdConditionChecker {

/** Report BINANA id, based on nVersion signalling standard */
bool BINANA(int& year, int& number, int& revision) const;

/** Returns signalling information */
std::vector<SignalInfo> GetSignalInfo(const CBlockIndex* pindex) const;
};

/** BIP 9 allows multiple softforks to be deployed in parallel. We cache
Expand All @@ -80,6 +90,9 @@ class VersionBitsCache
/** Report BINANA details, based on nVersion signalling standard */
bool BINANA(int& year, int& number, int& revision, const Consensus::Params& params, Consensus::DeploymentPos pos) const;

/** Returns signalling information */
std::vector<SignalInfo> GetSignalInfo(const CBlockIndex* pindex, const Consensus::Params& params, Consensus::DeploymentPos pos) const;

void Clear() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
};

Expand Down
1 change: 1 addition & 0 deletions test/functional/rpc_blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ def check_signalling_deploymentinfo_result(self, gdi_result, height, blockhash):
'since': 144,
'signal_activate': "30000000",
'signal_abandon': "50000000",
'signals': [],
},
'active': False
},
Expand Down

0 comments on commit c06dd9e

Please sign in to comment.