diff --git a/src/ipc/capnp/common-types.h b/src/ipc/capnp/common-types.h index 309799b8aff..9b6fa464de9 100644 --- a/src/ipc/capnp/common-types.h +++ b/src/ipc/capnp/common-types.h @@ -127,6 +127,18 @@ decltype(auto) CustomReadField(TypeList, Priority<1>, InvokeContext& i }); } +//! Interpret empty Data fields as null CTransactionRef values. This is safe to +//! do because no CTransaction is ever serialized as empty Data, and it is +//! convenient because this allows std::vector to be passed as +//! List(Data) even if the vector contains null values, and even though Cap'n +//! Proto does not (currently) allow distinguishing between null and empty Data +//! values in a List. Interpreting empty Data values as null CTransactionRef +//! values works well for this purpose. +template +bool CustomHasField(TypeList, InvokeContext& invoke_context, const Input& input) +{ + return input.get().size() > 0; +} } // namespace mp #endif // BITCOIN_IPC_CAPNP_COMMON_TYPES_H diff --git a/src/ipc/test/ipc_test.capnp b/src/ipc/test/ipc_test.capnp index adb92825116..4aa196b6f52 100644 --- a/src/ipc/test/ipc_test.capnp +++ b/src/ipc/test/ipc_test.capnp @@ -18,6 +18,7 @@ interface FooInterface $Proxy.wrap("FooImplementation") { passOutPoint @1 (arg :Data) -> (result :Data); passUniValue @2 (arg :Text) -> (result :Text); passTransaction @3 (arg :Data) -> (result :Data); + passTransactions @6 (arg :List(Data)) -> (result :List(Data)); passVectorChar @4 (arg :Data) -> (result :Data); passScript @5 (arg :Data) -> (result :Data); } diff --git a/src/ipc/test/ipc_test.cpp b/src/ipc/test/ipc_test.cpp index 46366cef1f0..d5c689501da 100644 --- a/src/ipc/test/ipc_test.cpp +++ b/src/ipc/test/ipc_test.cpp @@ -103,6 +103,14 @@ void IpcPipeTest() CTransactionRef tx2{foo->passTransaction(tx1)}; BOOST_CHECK(*Assert(tx1) == *Assert(tx2)); + std::vector txs1; + txs1.push_back(tx1); + txs1.push_back(nullptr); + std::vector txs2(foo->passTransactions(txs1)); + BOOST_CHECK_EQUAL(txs2.size(), 2); + BOOST_CHECK(*Assert(txs1[0]) == *Assert(txs2[0])); + BOOST_CHECK(!txs2[1]); + std::vector vec1{'H', 'e', 'l', 'l', 'o'}; std::vector vec2{foo->passVectorChar(vec1)}; BOOST_CHECK_EQUAL(std::string_view(vec1.begin(), vec1.end()), std::string_view(vec2.begin(), vec2.end())); diff --git a/src/ipc/test/ipc_test.h b/src/ipc/test/ipc_test.h index 8ef3bc90fb3..392f2b48826 100644 --- a/src/ipc/test/ipc_test.h +++ b/src/ipc/test/ipc_test.h @@ -18,6 +18,7 @@ public: COutPoint passOutPoint(COutPoint o) { return o; } UniValue passUniValue(UniValue v) { return v; } CTransactionRef passTransaction(CTransactionRef t) { return t; } + std::vector passTransactions(std::vector t) { return t; } std::vector passVectorChar(std::vector v) { return v; } BlockValidationState passBlockState(BlockValidationState s) { return s; } CScript passScript(CScript s) { return s; } diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 2f68f414f0d..641252d2afa 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -919,6 +919,7 @@ public: bool submitSolution(uint32_t version, uint32_t timestamp, uint32_t nonce, CTransactionRef coinbase) override { + if (!coinbase) return false; AddMerkleRootAndCoinbase(m_block_template->block, std::move(coinbase), version, timestamp, nonce); std::string reason; std::string debug; diff --git a/test/functional/interface_ipc_mining.py b/test/functional/interface_ipc_mining.py index 4cd9c17c99f..b5a0328042c 100755 --- a/test/functional/interface_ipc_mining.py +++ b/test/functional/interface_ipc_mining.py @@ -511,9 +511,13 @@ class IPCMiningTest(BitcoinTestFramework): # lets node 2 accept/reject complete blocks independently. self.disconnect_nodes(1, 2) + self.log.debug("submitSolution should reject an empty coinbase") + submitted = (await template.submitSolution(ctx, 0, 0, 0, b"")).result + assert_equal(submitted, False) + self.log.debug("Submit solution that can't be deserialized") try: - await template.submitSolution(ctx, 0, 0, 0, b"") + await template.submitSolution(ctx, 0, 0, 0, b"\x00") raise AssertionError("submitSolution unexpectedly succeeded") except capnp.lib.capnp.KjException as e: assert_capnp_failed(e, "remote exception: std::exception: SpanReader::read(): end of data:") @@ -654,6 +658,13 @@ class IPCMiningTest(BitcoinTestFramework): raise AssertionError("submitBlock unexpectedly succeeded") except capnp.lib.capnp.KjException as e: assert_capnp_failed(e, "remote exception: std::exception: SpanReader::read(): end of data:") + + self.log.debug("Submit empty block data") + try: + await mining2.submitBlock(ctx2, b"") + raise AssertionError("submitBlock unexpectedly succeeded") + except capnp.lib.capnp.KjException as e: + assert_capnp_failed(e, "remote exception: std::exception: SpanReader::read(): end of data:") assert_equal(self.nodes[2].is_node_stopped(), False) asyncio.run(capnp.run(async_routine()))