Skip to content

Commit 5638bfd

Browse files
authored
Merge pull request #1183 from delta1/elements-0.21
backport to 0.21: change getnewblockhex to take multiple commitments
2 parents c3834f7 + ac05517 commit 5638bfd

File tree

6 files changed

+168
-12
lines changed

6 files changed

+168
-12
lines changed

src/miner.cpp

+5-3
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ void BlockAssembler::resetBlock()
112112
Optional<int64_t> BlockAssembler::m_last_block_num_txs{nullopt};
113113
Optional<int64_t> BlockAssembler::m_last_block_weight{nullopt};
114114

115-
std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, std::chrono::seconds min_tx_age, DynaFedParamEntry* proposed_entry, CScript const* commit_script)
115+
std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, std::chrono::seconds min_tx_age, DynaFedParamEntry* proposed_entry, const std::vector<CScript>* commit_scripts)
116116
{
117117
assert(min_tx_age >= std::chrono::seconds(0));
118118
int64_t nTimeStart = GetTimeMicros();
@@ -206,8 +206,10 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
206206
}
207207
coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0;
208208
// Non-consensus commitment output before finishing coinbase transaction
209-
if (commit_script) {
210-
coinbaseTx.vout.insert(coinbaseTx.vout.begin(), CTxOut(policyAsset, 0, *commit_script));
209+
if (commit_scripts && !commit_scripts->empty()) {
210+
for (auto commit_script: *commit_scripts) {
211+
coinbaseTx.vout.insert(std::prev(coinbaseTx.vout.end()), CTxOut(policyAsset, 0, commit_script));
212+
}
211213
}
212214
pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx));
213215
pblocktemplate->vchCoinbaseCommitment = GenerateCoinbaseCommitment(*pblock, pindexPrev, chainparams.GetConsensus());

src/miner.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ class BlockAssembler
158158
explicit BlockAssembler(const CTxMemPool& mempool, const CChainParams& params, const Options& options);
159159

160160
/** Construct a new block template with coinbase to scriptPubKeyIn. min_tx_age is in seconds */
161-
std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn, std::chrono::seconds min_tx_age = std::chrono::seconds(0), DynaFedParamEntry* = nullptr, CScript const* commit_script = nullptr);
161+
std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn, std::chrono::seconds min_tx_age = std::chrono::seconds(0), DynaFedParamEntry* = nullptr, const std::vector<CScript>* commit_scripts = nullptr);
162162

163163
static Optional<int64_t> m_last_block_num_txs;
164164
static Optional<int64_t> m_last_block_weight;

src/rpc/client.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
204204
{ "rawreissueasset", 1, "reissuances" },
205205
{ "getnewblockhex", 0, "min_tx_age" },
206206
{ "getnewblockhex", 1, "proposed_parameters" },
207+
{ "getnewblockhex", 2, "commit_data" },
207208
{ "testproposedblock", 1, "acceptnonstd" },
208209
{ "issueasset", 0, "assetamount" },
209210
{ "issueasset", 1, "tokenamount" },

src/rpc/mining.cpp

+25-8
Original file line numberDiff line numberDiff line change
@@ -1242,7 +1242,7 @@ UniValue getnewblockhex(const JSONRPCRequest& request)
12421242
"\nGets hex representation of a proposed, unmined new block\n",
12431243
{
12441244
{"min_tx_age", RPCArg::Type::NUM, /* default */ "0", "How many seconds a transaction must have been in the mempool to be inluded in the block proposal. This may help with faster block convergence among functionaries using compact blocks."},
1245-
{"proposed_parameters", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED , "Parameters to be used in dynamic federations blocks as proposals. During a period of `-dynamic_epoch_length` blocks, 4/5 of total blocks must signal these parameters for the proposal to become activated in the next epoch.",
1245+
{"proposed_parameters", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "Parameters to be used in dynamic federations blocks as proposals. During a period of `-dynamic_epoch_length` blocks, 4/5 of total blocks must signal these parameters for the proposal to become activated in the next epoch.",
12461246
{
12471247
{"signblockscript", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Hex-encoded block signing script to propose"},
12481248
{"max_block_witness", RPCArg::Type::NUM, RPCArg::Optional::NO, "Total size in witness bytes that are allowed in the dynamic federations block witness for blocksigning"},
@@ -1254,7 +1254,11 @@ UniValue getnewblockhex(const JSONRPCRequest& request)
12541254
},
12551255
},
12561256
"proposed_parameters"},
1257-
{"commit_data", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "Data in hex to be committed to in an additional coinbase output."},
1257+
{"commit_data", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "Array of data in hex to be committed to in additional coinbase outputs.",
1258+
{
1259+
{"", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Hex encoded string for commit data"},
1260+
},
1261+
},
12581262
},
12591263
RPCResult{
12601264
RPCResult::Type::STR_HEX, "blockhex", "the block hex",
@@ -1309,18 +1313,31 @@ UniValue getnewblockhex(const JSONRPCRequest& request)
13091313
proposed.m_serialize_type = 2;
13101314
}
13111315

1312-
// Any commitment required for non-consensus reasons.
1313-
// This will be placed in the first coinbase output.
1314-
CScript data_commitment;
1316+
// Any commitments required for non-consensus reasons.
1317+
// These will be placed in the first coinbase outputs.
1318+
std::vector<CScript> data_commitments;
13151319
if (!request.params[2].isNull()) {
1316-
std::vector<unsigned char> data_bytes = ParseHex(request.params[2].get_str());
1317-
data_commitment = CScript() << OP_RETURN << data_bytes;
1320+
UniValue commitments(UniValue::VARR);
1321+
1322+
// backwards compatibility: attempt to parse as a string first
1323+
if (request.params[2].isStr()) {
1324+
UniValue hex = request.params[2].get_str();
1325+
commitments.push_back(hex);
1326+
} else {
1327+
commitments = request.params[2].get_array();
1328+
}
1329+
1330+
for (unsigned int i = 0; i < commitments.size(); i++) {
1331+
std::vector<unsigned char> data_bytes = ParseHex(commitments[i].get_str());
1332+
CScript data_commitment = CScript() << OP_RETURN << data_bytes;
1333+
data_commitments.push_back(data_commitment);
1334+
}
13181335
}
13191336

13201337
CScript feeDestinationScript = Params().GetConsensus().mandatory_coinbase_destination;
13211338
if (feeDestinationScript == CScript()) feeDestinationScript = CScript() << OP_TRUE;
13221339
const NodeContext& node = EnsureNodeContext(request.context);
1323-
std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(*node.mempool, Params()).CreateNewBlock(feeDestinationScript, std::chrono::seconds(required_wait), &proposed, data_commitment.empty() ? nullptr : &data_commitment));
1340+
std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(*node.mempool, Params()).CreateNewBlock(feeDestinationScript, std::chrono::seconds(required_wait), &proposed, data_commitments.empty() ? nullptr : &data_commitments));
13241341
if (!pblocktemplate.get()) {
13251342
throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet keypool empty");
13261343
}

test/functional/rpc_getnewblockhex.py

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/usr/bin/env python3
2+
"""Test getnewblockhex
3+
"""
4+
from io import BytesIO
5+
6+
from test_framework.messages import CBlock
7+
from test_framework.p2p import (
8+
P2PDataStore,
9+
)
10+
from test_framework.test_framework import BitcoinTestFramework
11+
from test_framework.util import (
12+
assert_equal,
13+
hex_str_to_bytes,
14+
)
15+
16+
class GetNewBlockHexTest(BitcoinTestFramework):
17+
def set_test_params(self):
18+
self.setup_clean_chain = False
19+
self.num_nodes = 1
20+
21+
def run_test(self):
22+
self.log.info("Starting test...")
23+
self.bootstrap_p2p()
24+
25+
node = self.nodes[0]
26+
27+
height = node.getblockcount()
28+
assert_equal(height, 200)
29+
30+
self.log.info("Call getnewblockhex with no args")
31+
hex = node.getnewblockhex()
32+
(height, hash) = self.mine_block(hex)
33+
assert_equal(height, 201)
34+
35+
self.log.info("Call getnewblockhex with single string commitment")
36+
hex = node.getnewblockhex(0, None, "1234")
37+
assert "6a021234" in hex
38+
(height, hash) = self.mine_block(hex)
39+
assert_equal(height, 202)
40+
block = node.getblock(hash, 2)
41+
vout = block["tx"][0]["vout"]
42+
assert_equal(vout[0]["scriptPubKey"]["hex"], "6a021234")
43+
44+
self.log.info("Call getnewblockhex with single string commitment with spaces")
45+
# ParseHex only validates hex chars, so spaces skipped
46+
hex = node.getnewblockhex(0, None, "1234 5678")
47+
assert "6a0412345678" in hex
48+
(height, hash) = self.mine_block(hex)
49+
assert_equal(height, 203)
50+
block = node.getblock(hash, 2)
51+
vout = block["tx"][0]["vout"]
52+
assert_equal(vout[0]["scriptPubKey"]["hex"], "6a0412345678")
53+
54+
self.log.info("Call getnewblockhex with single commitment")
55+
hex = node.getnewblockhex(0, None, ["1234"])
56+
assert "6a021234" in hex
57+
(height, hash) = self.mine_block(hex)
58+
assert_equal(height, 204)
59+
block = node.getblock(hash, 2)
60+
vout = block["tx"][0]["vout"]
61+
assert_equal(vout[0]["scriptPubKey"]["hex"], "6a021234")
62+
63+
self.log.info("Call getnewblockhex with multiple commitments")
64+
hex = node.getnewblockhex(0, None, ["1234", "deadbeef"])
65+
assert "6a021234" in hex
66+
assert "6a04deadbeef" in hex
67+
(height, hash) = self.mine_block(hex)
68+
assert_equal(height, 205)
69+
block = node.getblock(hash, 2)
70+
vout = block["tx"][0]["vout"]
71+
assert_equal(vout[0]["scriptPubKey"]["hex"], "6a021234")
72+
assert_equal(vout[1]["scriptPubKey"]["hex"], "6a04deadbeef")
73+
74+
hex = node.getnewblockhex(0, None, ["1234", "dead", "cafe", "babe"])
75+
assert "6a021234" in hex
76+
assert "6a02dead" in hex
77+
assert "6a02cafe" in hex
78+
assert "6a02babe" in hex
79+
(height, hash) = self.mine_block(hex)
80+
assert_equal(height, 206)
81+
block = node.getblock(hash, 2)
82+
vout = block["tx"][0]["vout"]
83+
assert_equal(vout[0]["scriptPubKey"]["hex"], "6a021234")
84+
assert_equal(vout[1]["scriptPubKey"]["hex"], "6a02dead")
85+
assert_equal(vout[2]["scriptPubKey"]["hex"], "6a02cafe")
86+
assert_equal(vout[3]["scriptPubKey"]["hex"], "6a02babe")
87+
88+
self.log.info("Done.")
89+
90+
def mine_block(self, hex):
91+
"""Mine and submit a block from a given hex template.
92+
93+
Returns a tuple of the new chain height and the block hash."""
94+
95+
bytes = hex_str_to_bytes(hex)
96+
block = CBlock()
97+
block.deserialize(BytesIO(bytes))
98+
block.solve()
99+
self.send_blocks([block], success=True)
100+
height = self.nodes[0].getblockcount()
101+
return (height, block.hash)
102+
103+
def bootstrap_p2p(self, timeout=10):
104+
"""Add a P2P connection to the node.
105+
106+
Helper to connect and wait for version handshake."""
107+
self.helper_peer = self.nodes[0].add_p2p_connection(P2PDataStore())
108+
# We need to wait for the initial getheaders from the peer before we
109+
# start populating our blockstore. If we don't, then we may run ahead
110+
# to the next subtest before we receive the getheaders. We'd then send
111+
# an INV for the next block and receive two getheaders - one for the
112+
# IBD and one for the INV. We'd respond to both and could get
113+
# unexpectedly disconnected if the DoS score for that error is 50.
114+
self.helper_peer.wait_for_getheaders(timeout=timeout)
115+
116+
def reconnect_p2p(self, timeout=60):
117+
"""Tear down and bootstrap the P2P connection to the node.
118+
119+
The node gets disconnected several times in this test. This helper
120+
method reconnects the p2p and restarts the network thread."""
121+
self.nodes[0].disconnect_p2ps()
122+
self.bootstrap_p2p(timeout=timeout)
123+
124+
def send_blocks(self, blocks, success=True, reject_reason=None, force_send=False, reconnect=False, timeout=960):
125+
"""Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block.
126+
127+
Call with success = False if the tip shouldn't advance to the most recent block."""
128+
self.helper_peer.send_blocks_and_test(blocks, self.nodes[0], success=success, reject_reason=reject_reason, force_send=force_send, timeout=timeout, expect_disconnect=reconnect)
129+
130+
if reconnect:
131+
self.reconnect_p2p(timeout=timeout)
132+
133+
134+
if __name__ == '__main__':
135+
GetNewBlockHexTest().main()

test/functional/test_runner.py

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
'feature_assetsdir.py',
108108
'feature_initial_reissuance_token.py',
109109
'feature_progress.py',
110+
'rpc_getnewblockhex.py',
110111
# Longest test should go first, to favor running tests in parallel
111112
'wallet_hd.py',
112113
'wallet_hd.py --descriptors',

0 commit comments

Comments
 (0)