Skip to content

Commit 9a7eece

Browse files
committed
Merge bitcoin#31981: Add checkBlock() to Mining interface
a18e572 test: more template verification tests (Sjors Provoost) 10c9088 test: move gbt proposal mode tests to new file (Sjors Provoost) 94959b8 Add checkBlock to Mining interface (Sjors Provoost) 6077157 ipc: drop BlockValidationState special handling (Sjors Provoost) 74690f4 validation: refactor TestBlockValidity (Sjors Provoost) Pull request description: This PR adds the IPC equivalent of the `getblocktemplate` RPC in `proposal` mode. In order to do so it has `TestBlockValidity` return error reasons as a string instead of `BlockValidationState`. This avoids complexity in IPC code for handling the latter struct. The new Mining interface method is used in `miner_tests`. It's not used by the `getblocktemplate` and `generateblock` RPC calls, see bitcoin#31981 (comment) The `inconclusive-not-best-prevblk` check is moved from RPC code to `TestBlockValidity`. Test coverage is increased by `mining_template_verification.py`. Superseedes bitcoin#31564 ## Background ### Verifying block templates (no PoW) Stratum v2 allows miners to generate their own block template. Pools may wish (or need) to verify these templates. This typically involves comparing mempools, asking miners to providing missing transactions and then reconstructing the proposed block.[^0] This is not sufficient to ensure a proposed block is actually valid. In some schemes miners could take advantage of incomplete validation[^1]. The Stratum Reference Implementation (SRI), currently the only Stratum v2 implementation, collects all missing mempool transactions, but does not yet fully verify the block.[^2]. It could use the `getblocktemplate` RPC in `proposal` mode, but using IPC is more performant, as it avoids serialising up to 4 MB of transaction data as JSON. (although SRI could use this PR, the Template Provider role doesn't need it, so this is _not_ part of bitcoin#31098) [^0]: https://github.com/stratum-mining/sv2-spec/blob/main/06-Job-Declaration-Protocol.md [^1]: https://delvingbitcoin.org/t/pplns-with-job-declaration/1099/45?u=sjors [^2]: https://github.com/stratum-mining/stratum/blob/v1.1.0/roles/jd-server/src/lib/job_declarator/message_handler.rs#L196 ACKs for top commit: davidgumberg: reACK bitcoin@a18e572 achow101: ACK a18e572 TheCharlatan: ACK a18e572 ryanofsky: Code review ACK a18e572 just adding another NONFATAL_UNREACHABLE since last review Tree-SHA512: 1a6c29f45a1666114f10f55aed155980b90104db27761c78aada4727ce3129e6ae7a522d90a56314bd767bd7944dfa46e85fb9f714370fc83e6a585be7b044f1
2 parents 5e6dbfd + a18e572 commit 9a7eece

File tree

16 files changed

+493
-229
lines changed

16 files changed

+493
-229
lines changed

src/interfaces/mining.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,21 @@ class Mining
114114
*/
115115
virtual std::unique_ptr<BlockTemplate> createNewBlock(const node::BlockCreateOptions& options = {}) = 0;
116116

117+
/**
118+
* Checks if a given block is valid.
119+
*
120+
* @param[in] block the block to check
121+
* @param[in] options verification options: the proof-of-work check can be
122+
* skipped in order to verify a template generated by
123+
* external software.
124+
* @param[out] reason failure reason (BIP22)
125+
* @param[out] debug more detailed rejection reason
126+
* @returns whether the block is valid
127+
*
128+
* For signets the challenge verification is skipped when check_pow is false.
129+
*/
130+
virtual bool checkBlock(const CBlock& block, const node::BlockCheckOptions& options, std::string& reason, std::string& debug) = 0;
131+
117132
//! Get internal node context. Useful for RPC and testing,
118133
//! but not accessible across processes.
119134
virtual node::NodeContext* context() { return nullptr; }

src/ipc/capnp/mining-types.h

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,7 @@
1414
#include <validation.h>
1515

1616
namespace mp {
17-
// Custom serialization for BlockValidationState.
18-
void CustomBuildMessage(InvokeContext& invoke_context,
19-
const BlockValidationState& src,
20-
ipc::capnp::messages::BlockValidationState::Builder&& builder);
21-
void CustomReadMessage(InvokeContext& invoke_context,
22-
const ipc::capnp::messages::BlockValidationState::Reader& reader,
23-
BlockValidationState& dest);
17+
// Custom serializations
2418
} // namespace mp
2519

2620
#endif // BITCOIN_IPC_CAPNP_MINING_TYPES_H

src/ipc/capnp/mining.capnp

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ interface Mining $Proxy.wrap("interfaces::Mining") {
1818
getTip @2 (context :Proxy.Context) -> (result: Common.BlockRef, hasResult: Bool);
1919
waitTipChanged @3 (context :Proxy.Context, currentTip: Data, timeout: Float64) -> (result: Common.BlockRef);
2020
createNewBlock @4 (options: BlockCreateOptions) -> (result: BlockTemplate);
21+
checkBlock @5 (block: Data, options: BlockCheckOptions) -> (reason: Text, debug: Text, result: Bool);
2122
}
2223

2324
interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") {
@@ -45,12 +46,7 @@ struct BlockWaitOptions $Proxy.wrap("node::BlockWaitOptions") {
4546
feeThreshold @1 : Int64 $Proxy.name("fee_threshold");
4647
}
4748

48-
# Note: serialization of the BlockValidationState C++ type is somewhat fragile
49-
# and using the struct can be awkward. It would be good if testBlockValidity
50-
# method were changed to return validity information in a simpler format.
51-
struct BlockValidationState {
52-
mode @0 :Int32;
53-
result @1 :Int32;
54-
rejectReason @2 :Text;
55-
debugMessage @3 :Text;
49+
struct BlockCheckOptions $Proxy.wrap("node::BlockCheckOptions") {
50+
checkMerkleRoot @0 :Bool $Proxy.name("check_merkle_root");
51+
checkPow @1 :Bool $Proxy.name("check_pow");
5652
}

src/ipc/capnp/mining.cpp

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,4 @@
88
#include <mp/proxy-types.h>
99

1010
namespace mp {
11-
void CustomBuildMessage(InvokeContext& invoke_context,
12-
const BlockValidationState& src,
13-
ipc::capnp::messages::BlockValidationState::Builder&& builder)
14-
{
15-
if (src.IsValid()) {
16-
builder.setMode(0);
17-
} else if (src.IsInvalid()) {
18-
builder.setMode(1);
19-
} else if (src.IsError()) {
20-
builder.setMode(2);
21-
} else {
22-
assert(false);
23-
}
24-
builder.setResult(static_cast<int>(src.GetResult()));
25-
builder.setRejectReason(src.GetRejectReason());
26-
builder.setDebugMessage(src.GetDebugMessage());
27-
}
28-
29-
void CustomReadMessage(InvokeContext& invoke_context,
30-
const ipc::capnp::messages::BlockValidationState::Reader& reader,
31-
BlockValidationState& dest)
32-
{
33-
if (reader.getMode() == 0) {
34-
assert(reader.getResult() == 0);
35-
assert(reader.getRejectReason().size() == 0);
36-
assert(reader.getDebugMessage().size() == 0);
37-
} else if (reader.getMode() == 1) {
38-
dest.Invalid(static_cast<BlockValidationResult>(reader.getResult()), reader.getRejectReason(), reader.getDebugMessage());
39-
} else if (reader.getMode() == 2) {
40-
assert(reader.getResult() == 0);
41-
dest.Error(reader.getRejectReason());
42-
assert(reader.getDebugMessage().size() == 0);
43-
} else {
44-
assert(false);
45-
}
46-
}
4711
} // namespace mp

src/node/interfaces.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,15 @@ class MinerImpl : public Mining
984984
return std::make_unique<BlockTemplateImpl>(assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(), m_node);
985985
}
986986

987+
bool checkBlock(const CBlock& block, const node::BlockCheckOptions& options, std::string& reason, std::string& debug) override
988+
{
989+
LOCK(chainman().GetMutex());
990+
BlockValidationState state{TestBlockValidity(chainman().ActiveChainstate(), block, /*check_pow=*/options.check_pow, /*=check_merkle_root=*/options.check_merkle_root)};
991+
reason = state.GetRejectReason();
992+
debug = state.GetDebugMessage();
993+
return state.IsValid();
994+
}
995+
987996
NodeContext* context() override { return &m_node; }
988997
ChainstateManager& chainman() { return *Assert(m_node.chainman); }
989998
KernelNotifications& notifications() { return *Assert(m_node.notifications); }

src/node/miner.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,10 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock()
179179
pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus());
180180
pblock->nNonce = 0;
181181

182-
BlockValidationState state;
183-
if (m_options.test_block_validity && !TestBlockValidity(state, chainparams, m_chainstate, *pblock, pindexPrev,
184-
/*fCheckPOW=*/false, /*fCheckMerkleRoot=*/false)) {
185-
throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, state.ToString()));
182+
if (m_options.test_block_validity) {
183+
if (BlockValidationState state{TestBlockValidity(m_chainstate, *pblock, /*check_pow=*/false, /*check_merkle_root=*/false)}; !state.IsValid()) {
184+
throw std::runtime_error(strprintf("TestBlockValidity failed: %s", state.ToString()));
185+
}
186186
}
187187
const auto time_2{SteadyClock::now()};
188188

src/node/types.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <cstddef>
1818
#include <policy/policy.h>
1919
#include <script/script.h>
20+
#include <uint256.h>
2021
#include <util/time.h>
2122

2223
namespace node {
@@ -85,6 +86,17 @@ struct BlockWaitOptions {
8586
CAmount fee_threshold{MAX_MONEY};
8687
};
8788

89+
struct BlockCheckOptions {
90+
/**
91+
* Set false to omit the merkle root check
92+
*/
93+
bool check_merkle_root{true};
94+
95+
/**
96+
* Set false to omit the proof-of-work check
97+
*/
98+
bool check_pow{true};
99+
};
88100
} // namespace node
89101

90102
#endif // BITCOIN_NODE_TYPES_H

src/rpc/mining.cpp

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -388,8 +388,7 @@ static RPCHelpMan generateblock()
388388
block.vtx.insert(block.vtx.end(), txs.begin(), txs.end());
389389
RegenerateCommitments(block, chainman);
390390

391-
BlockValidationState state;
392-
if (!TestBlockValidity(state, chainman.GetParams(), chainman.ActiveChainstate(), block, chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock), /*fCheckPOW=*/false, /*fCheckMerkleRoot=*/false)) {
391+
if (BlockValidationState state{TestBlockValidity(chainman.ActiveChainstate(), block, /*check_pow=*/false, /*check_merkle_root=*/false)}; !state.IsValid()) {
393392
throw JSONRPCError(RPC_VERIFY_ERROR, strprintf("TestBlockValidity failed: %s", state.ToString()));
394393
}
395394
}
@@ -745,13 +744,7 @@ static RPCHelpMan getblocktemplate()
745744
return "duplicate-inconclusive";
746745
}
747746

748-
// TestBlockValidity only supports blocks built on the current Tip
749-
if (block.hashPrevBlock != tip) {
750-
return "inconclusive-not-best-prevblk";
751-
}
752-
BlockValidationState state;
753-
TestBlockValidity(state, chainman.GetParams(), chainman.ActiveChainstate(), block, chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock), /*fCheckPOW=*/false, /*fCheckMerkleRoot=*/true);
754-
return BIP22ValidationResult(state);
747+
return BIP22ValidationResult(TestBlockValidity(chainman.ActiveChainstate(), block, /*check_pow=*/false, /*check_merkle_root=*/true));
755748
}
756749

757750
const UniValue& aClientRules = oparam.find_value("rules");

src/test/ipc_test.capnp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,5 @@ interface FooInterface $Proxy.wrap("FooImplementation") {
1919
passUniValue @2 (arg :Text) -> (result :Text);
2020
passTransaction @3 (arg :Data) -> (result :Data);
2121
passVectorChar @4 (arg :Data) -> (result :Data);
22-
passBlockState @5 (arg :Mining.BlockValidationState) -> (result :Mining.BlockValidationState);
23-
passScript @6 (arg :Data) -> (result :Data);
22+
passScript @5 (arg :Data) -> (result :Data);
2423
}

src/test/ipc_test.cpp

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -102,25 +102,6 @@ void IpcPipeTest()
102102
std::vector<char> vec2{foo->passVectorChar(vec1)};
103103
BOOST_CHECK_EQUAL(std::string_view(vec1.begin(), vec1.end()), std::string_view(vec2.begin(), vec2.end()));
104104

105-
BlockValidationState bs1;
106-
bs1.Invalid(BlockValidationResult::BLOCK_MUTATED, "reject reason", "debug message");
107-
BlockValidationState bs2{foo->passBlockState(bs1)};
108-
BOOST_CHECK_EQUAL(bs1.IsValid(), bs2.IsValid());
109-
BOOST_CHECK_EQUAL(bs1.IsError(), bs2.IsError());
110-
BOOST_CHECK_EQUAL(bs1.IsInvalid(), bs2.IsInvalid());
111-
BOOST_CHECK_EQUAL(static_cast<int>(bs1.GetResult()), static_cast<int>(bs2.GetResult()));
112-
BOOST_CHECK_EQUAL(bs1.GetRejectReason(), bs2.GetRejectReason());
113-
BOOST_CHECK_EQUAL(bs1.GetDebugMessage(), bs2.GetDebugMessage());
114-
115-
BlockValidationState bs3;
116-
BlockValidationState bs4{foo->passBlockState(bs3)};
117-
BOOST_CHECK_EQUAL(bs3.IsValid(), bs4.IsValid());
118-
BOOST_CHECK_EQUAL(bs3.IsError(), bs4.IsError());
119-
BOOST_CHECK_EQUAL(bs3.IsInvalid(), bs4.IsInvalid());
120-
BOOST_CHECK_EQUAL(static_cast<int>(bs3.GetResult()), static_cast<int>(bs4.GetResult()));
121-
BOOST_CHECK_EQUAL(bs3.GetRejectReason(), bs4.GetRejectReason());
122-
BOOST_CHECK_EQUAL(bs3.GetDebugMessage(), bs4.GetDebugMessage());
123-
124105
auto script1{CScript() << OP_11};
125106
auto script2{foo->passScript(script1)};
126107
BOOST_CHECK_EQUAL(HexStr(script1), HexStr(script2));

0 commit comments

Comments
 (0)