Skip to content

Commit

Permalink
Merge pull request #1183 from delta1/elements-0.21
Browse files Browse the repository at this point in the history
backport to 0.21: change getnewblockhex to take multiple commitments
  • Loading branch information
psgreco authored Oct 13, 2022
2 parents c3834f7 + ac05517 commit 5638bfd
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 12 deletions.
8 changes: 5 additions & 3 deletions src/miner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ void BlockAssembler::resetBlock()
Optional<int64_t> BlockAssembler::m_last_block_num_txs{nullopt};
Optional<int64_t> BlockAssembler::m_last_block_weight{nullopt};

std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, std::chrono::seconds min_tx_age, DynaFedParamEntry* proposed_entry, CScript const* commit_script)
std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, std::chrono::seconds min_tx_age, DynaFedParamEntry* proposed_entry, const std::vector<CScript>* commit_scripts)
{
assert(min_tx_age >= std::chrono::seconds(0));
int64_t nTimeStart = GetTimeMicros();
Expand Down Expand Up @@ -206,8 +206,10 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
}
coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0;
// Non-consensus commitment output before finishing coinbase transaction
if (commit_script) {
coinbaseTx.vout.insert(coinbaseTx.vout.begin(), CTxOut(policyAsset, 0, *commit_script));
if (commit_scripts && !commit_scripts->empty()) {
for (auto commit_script: *commit_scripts) {
coinbaseTx.vout.insert(std::prev(coinbaseTx.vout.end()), CTxOut(policyAsset, 0, commit_script));
}
}
pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx));
pblocktemplate->vchCoinbaseCommitment = GenerateCoinbaseCommitment(*pblock, pindexPrev, chainparams.GetConsensus());
Expand Down
2 changes: 1 addition & 1 deletion src/miner.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ class BlockAssembler
explicit BlockAssembler(const CTxMemPool& mempool, const CChainParams& params, const Options& options);

/** Construct a new block template with coinbase to scriptPubKeyIn. min_tx_age is in seconds */
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);
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);

static Optional<int64_t> m_last_block_num_txs;
static Optional<int64_t> m_last_block_weight;
Expand Down
1 change: 1 addition & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "rawreissueasset", 1, "reissuances" },
{ "getnewblockhex", 0, "min_tx_age" },
{ "getnewblockhex", 1, "proposed_parameters" },
{ "getnewblockhex", 2, "commit_data" },
{ "testproposedblock", 1, "acceptnonstd" },
{ "issueasset", 0, "assetamount" },
{ "issueasset", 1, "tokenamount" },
Expand Down
33 changes: 25 additions & 8 deletions src/rpc/mining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1242,7 +1242,7 @@ UniValue getnewblockhex(const JSONRPCRequest& request)
"\nGets hex representation of a proposed, unmined new block\n",
{
{"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."},
{"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.",
{"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.",
{
{"signblockscript", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Hex-encoded block signing script to propose"},
{"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"},
Expand All @@ -1254,7 +1254,11 @@ UniValue getnewblockhex(const JSONRPCRequest& request)
},
},
"proposed_parameters"},
{"commit_data", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "Data in hex to be committed to in an additional coinbase output."},
{"commit_data", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "Array of data in hex to be committed to in additional coinbase outputs.",
{
{"", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Hex encoded string for commit data"},
},
},
},
RPCResult{
RPCResult::Type::STR_HEX, "blockhex", "the block hex",
Expand Down Expand Up @@ -1309,18 +1313,31 @@ UniValue getnewblockhex(const JSONRPCRequest& request)
proposed.m_serialize_type = 2;
}

// Any commitment required for non-consensus reasons.
// This will be placed in the first coinbase output.
CScript data_commitment;
// Any commitments required for non-consensus reasons.
// These will be placed in the first coinbase outputs.
std::vector<CScript> data_commitments;
if (!request.params[2].isNull()) {
std::vector<unsigned char> data_bytes = ParseHex(request.params[2].get_str());
data_commitment = CScript() << OP_RETURN << data_bytes;
UniValue commitments(UniValue::VARR);

// backwards compatibility: attempt to parse as a string first
if (request.params[2].isStr()) {
UniValue hex = request.params[2].get_str();
commitments.push_back(hex);
} else {
commitments = request.params[2].get_array();
}

for (unsigned int i = 0; i < commitments.size(); i++) {
std::vector<unsigned char> data_bytes = ParseHex(commitments[i].get_str());
CScript data_commitment = CScript() << OP_RETURN << data_bytes;
data_commitments.push_back(data_commitment);
}
}

CScript feeDestinationScript = Params().GetConsensus().mandatory_coinbase_destination;
if (feeDestinationScript == CScript()) feeDestinationScript = CScript() << OP_TRUE;
const NodeContext& node = EnsureNodeContext(request.context);
std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(*node.mempool, Params()).CreateNewBlock(feeDestinationScript, std::chrono::seconds(required_wait), &proposed, data_commitment.empty() ? nullptr : &data_commitment));
std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(*node.mempool, Params()).CreateNewBlock(feeDestinationScript, std::chrono::seconds(required_wait), &proposed, data_commitments.empty() ? nullptr : &data_commitments));
if (!pblocktemplate.get()) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet keypool empty");
}
Expand Down
135 changes: 135 additions & 0 deletions test/functional/rpc_getnewblockhex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/usr/bin/env python3
"""Test getnewblockhex
"""
from io import BytesIO

from test_framework.messages import CBlock
from test_framework.p2p import (
P2PDataStore,
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
hex_str_to_bytes,
)

class GetNewBlockHexTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = False
self.num_nodes = 1

def run_test(self):
self.log.info("Starting test...")
self.bootstrap_p2p()

node = self.nodes[0]

height = node.getblockcount()
assert_equal(height, 200)

self.log.info("Call getnewblockhex with no args")
hex = node.getnewblockhex()
(height, hash) = self.mine_block(hex)
assert_equal(height, 201)

self.log.info("Call getnewblockhex with single string commitment")
hex = node.getnewblockhex(0, None, "1234")
assert "6a021234" in hex
(height, hash) = self.mine_block(hex)
assert_equal(height, 202)
block = node.getblock(hash, 2)
vout = block["tx"][0]["vout"]
assert_equal(vout[0]["scriptPubKey"]["hex"], "6a021234")

self.log.info("Call getnewblockhex with single string commitment with spaces")
# ParseHex only validates hex chars, so spaces skipped
hex = node.getnewblockhex(0, None, "1234 5678")
assert "6a0412345678" in hex
(height, hash) = self.mine_block(hex)
assert_equal(height, 203)
block = node.getblock(hash, 2)
vout = block["tx"][0]["vout"]
assert_equal(vout[0]["scriptPubKey"]["hex"], "6a0412345678")

self.log.info("Call getnewblockhex with single commitment")
hex = node.getnewblockhex(0, None, ["1234"])
assert "6a021234" in hex
(height, hash) = self.mine_block(hex)
assert_equal(height, 204)
block = node.getblock(hash, 2)
vout = block["tx"][0]["vout"]
assert_equal(vout[0]["scriptPubKey"]["hex"], "6a021234")

self.log.info("Call getnewblockhex with multiple commitments")
hex = node.getnewblockhex(0, None, ["1234", "deadbeef"])
assert "6a021234" in hex
assert "6a04deadbeef" in hex
(height, hash) = self.mine_block(hex)
assert_equal(height, 205)
block = node.getblock(hash, 2)
vout = block["tx"][0]["vout"]
assert_equal(vout[0]["scriptPubKey"]["hex"], "6a021234")
assert_equal(vout[1]["scriptPubKey"]["hex"], "6a04deadbeef")

hex = node.getnewblockhex(0, None, ["1234", "dead", "cafe", "babe"])
assert "6a021234" in hex
assert "6a02dead" in hex
assert "6a02cafe" in hex
assert "6a02babe" in hex
(height, hash) = self.mine_block(hex)
assert_equal(height, 206)
block = node.getblock(hash, 2)
vout = block["tx"][0]["vout"]
assert_equal(vout[0]["scriptPubKey"]["hex"], "6a021234")
assert_equal(vout[1]["scriptPubKey"]["hex"], "6a02dead")
assert_equal(vout[2]["scriptPubKey"]["hex"], "6a02cafe")
assert_equal(vout[3]["scriptPubKey"]["hex"], "6a02babe")

self.log.info("Done.")

def mine_block(self, hex):
"""Mine and submit a block from a given hex template.
Returns a tuple of the new chain height and the block hash."""

bytes = hex_str_to_bytes(hex)
block = CBlock()
block.deserialize(BytesIO(bytes))
block.solve()
self.send_blocks([block], success=True)
height = self.nodes[0].getblockcount()
return (height, block.hash)

def bootstrap_p2p(self, timeout=10):
"""Add a P2P connection to the node.
Helper to connect and wait for version handshake."""
self.helper_peer = self.nodes[0].add_p2p_connection(P2PDataStore())
# We need to wait for the initial getheaders from the peer before we
# start populating our blockstore. If we don't, then we may run ahead
# to the next subtest before we receive the getheaders. We'd then send
# an INV for the next block and receive two getheaders - one for the
# IBD and one for the INV. We'd respond to both and could get
# unexpectedly disconnected if the DoS score for that error is 50.
self.helper_peer.wait_for_getheaders(timeout=timeout)

def reconnect_p2p(self, timeout=60):
"""Tear down and bootstrap the P2P connection to the node.
The node gets disconnected several times in this test. This helper
method reconnects the p2p and restarts the network thread."""
self.nodes[0].disconnect_p2ps()
self.bootstrap_p2p(timeout=timeout)

def send_blocks(self, blocks, success=True, reject_reason=None, force_send=False, reconnect=False, timeout=960):
"""Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block.
Call with success = False if the tip shouldn't advance to the most recent block."""
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)

if reconnect:
self.reconnect_p2p(timeout=timeout)


if __name__ == '__main__':
GetNewBlockHexTest().main()
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
'feature_assetsdir.py',
'feature_initial_reissuance_token.py',
'feature_progress.py',
'rpc_getnewblockhex.py',
# Longest test should go first, to favor running tests in parallel
'wallet_hd.py',
'wallet_hd.py --descriptors',
Expand Down

0 comments on commit 5638bfd

Please sign in to comment.