mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-15 12:51:00 +02:00
Merge pull request #1155 from delta1/getnewblockhex-commitments
rpc: change getnewblockhex to take multiple commitments
This commit is contained in:
commit
1cfcf1c360
7 changed files with 169 additions and 13 deletions
|
|
@ -104,7 +104,7 @@ SegWit transactions have one such witness for each input.
|
|||
| Script Witness | Yes | Varies | `Vector<hex>` | | The vector represents the witness stack.<br>Can be empty (length of 0). |
|
||||
| Peg-in Witness | Yes | Varies | `Vector<hex>` | | The vector represents the witness stack.<br>Can be empty (length of 0). |
|
||||
|
||||
The range proofs must be empty if their asociated amounts (issuance / inflation keys) are explicit.
|
||||
The range proofs must be empty if their associated amounts (issuance / inflation keys) are explicit.
|
||||
Refer [here](https://elementsproject.org/features/confidential-transactions/investigation) for more details on range proofs.
|
||||
|
||||
A non-empty peg-in witness stack should always have a length of 6, and the items should be interpreted as follows:
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ void BlockAssembler::resetBlock()
|
|||
nFees = 0;
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ public:
|
|||
explicit BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params, const Options& options);
|
||||
|
||||
/** Construct a new block template with coinbase to scriptPubKeyIn */
|
||||
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);
|
||||
|
||||
inline static std::optional<int64_t> m_last_block_num_txs{};
|
||||
inline static std::optional<int64_t> m_last_block_weight{};
|
||||
|
|
|
|||
|
|
@ -207,6 +207,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" },
|
||||
|
|
|
|||
|
|
@ -1277,7 +1277,7 @@ static RPCHelpMan getnewblockhex()
|
|||
"\nGets hex representation of a proposed, unmined new block\n",
|
||||
{
|
||||
{"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."},
|
||||
{"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"},
|
||||
|
|
@ -1289,7 +1289,11 @@ static RPCHelpMan getnewblockhex()
|
|||
},
|
||||
},
|
||||
"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",
|
||||
|
|
@ -1346,18 +1350,31 @@ static RPCHelpMan getnewblockhex()
|
|||
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 = EnsureAnyNodeContext(request.context);
|
||||
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));
|
||||
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));
|
||||
if (!pblocktemplate.get()) {
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet keypool empty");
|
||||
}
|
||||
|
|
|
|||
135
test/functional/rpc_getnewblockhex.py
Executable file
135
test/functional/rpc_getnewblockhex.py
Executable file
|
|
@ -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()
|
||||
|
|
@ -108,6 +108,7 @@ BASE_SCRIPTS = [
|
|||
'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 --legacy-wallet',
|
||||
'wallet_hd.py --descriptors',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue