Skip to content

Commit 1cfcf1c

Browse files
authored
Merge pull request #1155 from delta1/getnewblockhex-commitments
rpc: change getnewblockhex to take multiple commitments
2 parents f49e97d + 5d10ff9 commit 1cfcf1c

File tree

7 files changed

+169
-13
lines changed

7 files changed

+169
-13
lines changed

doc/elements-tx-format.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ SegWit transactions have one such witness for each input.
104104
| Script Witness | Yes | Varies | `Vector<hex>` | | The vector represents the witness stack.<br>Can be empty (length of 0). |
105105
| Peg-in Witness | Yes | Varies | `Vector<hex>` | | The vector represents the witness stack.<br>Can be empty (length of 0). |
106106

107-
The range proofs must be empty if their asociated amounts (issuance / inflation keys) are explicit.
107+
The range proofs must be empty if their associated amounts (issuance / inflation keys) are explicit.
108108
Refer [here](https://elementsproject.org/features/confidential-transactions/investigation) for more details on range proofs.
109109

110110
A non-empty peg-in witness stack should always have a length of 6, and the items should be interpreted as follows:

src/miner.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ void BlockAssembler::resetBlock()
112112
nFees = 0;
113113
}
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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ class BlockAssembler
159159
explicit BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params, const Options& options);
160160

161161
/** Construct a new block template with coinbase to scriptPubKeyIn */
162-
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);
162+
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);
163163

164164
inline static std::optional<int64_t> m_last_block_num_txs{};
165165
inline static std::optional<int64_t> m_last_block_weight{};

src/rpc/client.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
207207
{ "rawreissueasset", 1, "reissuances" },
208208
{ "getnewblockhex", 0, "min_tx_age" },
209209
{ "getnewblockhex", 1, "proposed_parameters" },
210+
{ "getnewblockhex", 2, "commit_data" },
210211
{ "testproposedblock", 1, "acceptnonstd" },
211212
{ "issueasset", 0, "assetamount" },
212213
{ "issueasset", 1, "tokenamount" },

src/rpc/mining.cpp

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,7 +1277,7 @@ static RPCHelpMan getnewblockhex()
12771277
"\nGets hex representation of a proposed, unmined new block\n",
12781278
{
12791279
{"min_tx_age", RPCArg::Type::NUM, RPCArg::Default{0}, "How many seconds a transaction must have been in the mempool to be included in the block proposal. This may help with faster block convergence among functionaries using compact blocks."},
1280-
{"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.",
1280+
{"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.",
12811281
{
12821282
{"signblockscript", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Hex-encoded block signing script to propose"},
12831283
{"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"},
@@ -1289,7 +1289,11 @@ static RPCHelpMan getnewblockhex()
12891289
},
12901290
},
12911291
"proposed_parameters"},
1292-
{"commit_data", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "Data in hex to be committed to in an additional coinbase output."},
1292+
{"commit_data", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "Array of data in hex to be committed to in additional coinbase outputs.",
1293+
{
1294+
{"", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Hex encoded string for commit data"},
1295+
},
1296+
},
12931297
},
12941298
RPCResult{
12951299
RPCResult::Type::STR_HEX, "blockhex", "the block hex",
@@ -1346,18 +1350,31 @@ static RPCHelpMan getnewblockhex()
13461350
proposed.m_serialize_type = 2;
13471351
}
13481352

1349-
// Any commitment required for non-consensus reasons.
1350-
// This will be placed in the first coinbase output.
1351-
CScript data_commitment;
1353+
// Any commitments required for non-consensus reasons.
1354+
// These will be placed in the first coinbase outputs.
1355+
std::vector<CScript> data_commitments;
13521356
if (!request.params[2].isNull()) {
1353-
std::vector<unsigned char> data_bytes = ParseHex(request.params[2].get_str());
1354-
data_commitment = CScript() << OP_RETURN << data_bytes;
1357+
UniValue commitments(UniValue::VARR);
1358+
1359+
// backwards compatibility: attempt to parse as a string first
1360+
if (request.params[2].isStr()) {
1361+
UniValue hex = request.params[2].get_str();
1362+
commitments.push_back(hex);
1363+
} else {
1364+
commitments = request.params[2].get_array();
1365+
}
1366+
1367+
for (unsigned int i = 0; i < commitments.size(); i++) {
1368+
std::vector<unsigned char> data_bytes = ParseHex(commitments[i].get_str());
1369+
CScript data_commitment = CScript() << OP_RETURN << data_bytes;
1370+
data_commitments.push_back(data_commitment);
1371+
}
13551372
}
13561373

13571374
CScript feeDestinationScript = Params().GetConsensus().mandatory_coinbase_destination;
13581375
if (feeDestinationScript == CScript()) feeDestinationScript = CScript() << OP_TRUE;
13591376
const NodeContext& node = EnsureAnyNodeContext(request.context);
1360-
std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(chainman.ActiveChainstate(), *node.mempool, Params()).CreateNewBlock(feeDestinationScript, std::chrono::seconds(required_wait), &proposed, data_commitment.empty() ? nullptr : &data_commitment));
1377+
std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(chainman.ActiveChainstate(), *node.mempool, Params()).CreateNewBlock(feeDestinationScript, std::chrono::seconds(required_wait), &proposed, data_commitments.empty() ? nullptr : &data_commitments));
13611378
if (!pblocktemplate.get()) {
13621379
throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet keypool empty");
13631380
}

test/functional/rpc_getnewblockhex.py

Lines changed: 135 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
'feature_assetsdir.py',
109109
'feature_initial_reissuance_token.py',
110110
'feature_progress.py',
111+
'rpc_getnewblockhex.py',
111112
# Longest test should go first, to favor running tests in parallel
112113
'wallet_hd.py --legacy-wallet',
113114
'wallet_hd.py --descriptors',

0 commit comments

Comments
 (0)