mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-13 12:33:42 +02:00
Merge #410: compact block RPC calls
c3fe935test compact block rpcs (Gregory Sanders)c3cbe6ecompact block RPC calls (Gregory Sanders) Pull request description: Combined with https://github.com/ElementsProject/elements/pull/339 should be pretty powerful for speed of block proposal transmission. Tree-SHA512: 305256b844a28542bb720ab86a36ec91982dd41c4d8e455e34ad3db12daa6e8b25ccfe2728fa514812655c2121c8d1d36e71f2af83f86b2de6c5d0c72541d79e
This commit is contained in:
commit
3cf35fc090
5 changed files with 249 additions and 23 deletions
|
|
@ -68,28 +68,45 @@ class BlockSignTest(test_framework.BitcoinTestFramework):
|
|||
# Have every node import its block signing private key.
|
||||
for i in range(self.num_nodes):
|
||||
self.nodes[i].importprivkey(self.wifs[i])
|
||||
if i + 1 < self.num_nodes:
|
||||
util.connect_nodes_bi(self.nodes, i, i + 1)
|
||||
else:
|
||||
util.connect_nodes_bi(self.nodes, 0, i)
|
||||
self.is_network_split = False
|
||||
self.sync_all()
|
||||
self.is_network_split = True
|
||||
|
||||
def check_height(self, expected_height):
|
||||
for n in self.nodes:
|
||||
util.assert_equal(n.getblockcount(), expected_height)
|
||||
|
||||
def mine_block(self):
|
||||
def mine_block(self, make_transactions):
|
||||
# mine block in round robin sense: depending on the block number, a node
|
||||
# is selected to create the block, others sign it and the selected node
|
||||
# broadcasts it
|
||||
mineridx = self.nodes[0].getblockcount() % self.num_nodes # assuming in sync
|
||||
mineridx_next = (self.nodes[0].getblockcount() + 1) % self.num_nodes
|
||||
miner = self.nodes[mineridx]
|
||||
miner_next = self.nodes[mineridx_next]
|
||||
blockcount = miner.getblockcount()
|
||||
|
||||
# Make a few transactions to make non-empty blocks for compact transmission
|
||||
if make_transactions:
|
||||
for i in range(5):
|
||||
miner.sendtoaddress(miner_next.getnewaddress(), int(miner.getbalance()["bitcoin"]/10), "", "", True)
|
||||
# miner makes a block
|
||||
block = miner.getnewblockhex()
|
||||
|
||||
# other nodes get fed compact blocks
|
||||
for i in range(self.required_signers):
|
||||
if i == mineridx:
|
||||
continue
|
||||
sketch = miner.getcompactsketch(block)
|
||||
compact_response = self.nodes[i].consumecompactsketch(sketch)
|
||||
if make_transactions:
|
||||
block_txn = self.nodes[i].consumegetblocktxn(block, compact_response["block_tx_req"])
|
||||
final_block = self.nodes[i].finalizecompactblock(sketch, block_txn, compact_response["found_transactions"])
|
||||
else:
|
||||
# If there's only coinbase, it should succeed immediately
|
||||
final_block = compact_response["blockhex"]
|
||||
# Block should be complete, sans signatures
|
||||
self.nodes[i].testproposedblock(final_block)
|
||||
|
||||
|
||||
# collect required_signers signatures
|
||||
sigs = []
|
||||
for i in range(self.required_signers):
|
||||
|
|
@ -99,28 +116,29 @@ class BlockSignTest(test_framework.BitcoinTestFramework):
|
|||
self.check_height(blockcount)
|
||||
sigs.append(self.nodes[i].signblock(block))
|
||||
|
||||
# miner submits
|
||||
result = miner.combineblocksigs(block, sigs)
|
||||
util.assert_equal(result["complete"], True)
|
||||
miner.submitblock(result["hex"])
|
||||
|
||||
# All must submit... we're not connected!
|
||||
for node in self.nodes:
|
||||
node.submitblock(result["hex"])
|
||||
|
||||
def mine_blocks(self, num_blocks):
|
||||
for i in range(num_blocks):
|
||||
self.mine_block()
|
||||
self.sync_all()
|
||||
self.mine_block(True)
|
||||
|
||||
def run_test(self):
|
||||
self.check_height(0)
|
||||
|
||||
# mine a block
|
||||
self.mine_block()
|
||||
self.sync_all()
|
||||
# mine a block with no transactions
|
||||
print("Mining and signing a single empty block")
|
||||
self.mine_block(False)
|
||||
|
||||
# mine blocks
|
||||
self.mine_blocks(100)
|
||||
self.sync_all()
|
||||
# mine blocks with transactions
|
||||
print("Mining and signing non-empty blocks")
|
||||
self.mine_blocks(10)
|
||||
|
||||
self.check_height(101)
|
||||
self.check_height(11)
|
||||
|
||||
if __name__ == '__main__':
|
||||
BlockSignTest(num_nodes=9, required_signers=7).main()
|
||||
|
|
|
|||
|
|
@ -45,7 +45,13 @@ uint64_t CBlockHeaderAndShortTxIDs::GetShortID(const uint256& txhash) const {
|
|||
return SipHashUint256(shorttxidk0, shorttxidk1, txhash) & 0xffffffffffffL;
|
||||
}
|
||||
|
||||
|
||||
std::vector<CTransactionRef> PartiallyDownloadedBlock::GetAvailableTx() {
|
||||
std::vector<CTransactionRef> found_tx;
|
||||
for (unsigned int i = 0; i < txn_available.size(); i++) {
|
||||
if (txn_available[i]) found_tx.push_back(txn_available[i]);
|
||||
}
|
||||
return found_tx;
|
||||
}
|
||||
|
||||
ReadStatus PartiallyDownloadedBlock::InitData(const CBlockHeaderAndShortTxIDs& cmpctblock, const std::vector<std::pair<uint256, CTransactionRef>>& extra_txn) {
|
||||
if (cmpctblock.header.IsNull() || (cmpctblock.shorttxids.empty() && cmpctblock.prefilledtxn.empty()))
|
||||
|
|
@ -175,7 +181,7 @@ bool PartiallyDownloadedBlock::IsTxAvailable(size_t index) const {
|
|||
return txn_available[index] ? true : false;
|
||||
}
|
||||
|
||||
ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector<CTransactionRef>& vtx_missing) {
|
||||
ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector<CTransactionRef>& vtx_missing, bool check_pow) {
|
||||
assert(!header.IsNull());
|
||||
uint256 hash = header.GetHash();
|
||||
block = header;
|
||||
|
|
@ -199,7 +205,7 @@ ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector<
|
|||
return READ_STATUS_INVALID;
|
||||
|
||||
CValidationState state;
|
||||
if (!CheckBlock(block, state, Params().GetConsensus())) {
|
||||
if (!CheckBlock(block, state, Params().GetConsensus(), check_pow)) {
|
||||
// TODO: We really want to just check merkle tree manually here,
|
||||
// but that is expensive, and CheckBlock caches a block's
|
||||
// "checked-status" (in the CBlock?). CBlock should be able to
|
||||
|
|
|
|||
|
|
@ -200,10 +200,11 @@ public:
|
|||
CBlockHeader header;
|
||||
PartiallyDownloadedBlock(CTxMemPool* poolIn) : pool(poolIn) {}
|
||||
|
||||
std::vector<CTransactionRef> GetAvailableTx();
|
||||
// extra_txn is a list of extra transactions to look at, in <witness hash, reference> form
|
||||
ReadStatus InitData(const CBlockHeaderAndShortTxIDs& cmpctblock, const std::vector<std::pair<uint256, CTransactionRef>>& extra_txn);
|
||||
bool IsTxAvailable(size_t index) const;
|
||||
ReadStatus FillBlock(CBlock& block, const std::vector<CTransactionRef>& vtx_missing);
|
||||
ReadStatus FillBlock(CBlock& block, const std::vector<CTransactionRef>& vtx_missing, bool check_pow = true);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include "miner.h"
|
||||
|
||||
#include "amount.h"
|
||||
#include "blockencodings.h"
|
||||
#include "chain.h"
|
||||
#include "chainparams.h"
|
||||
#include "coins.h"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include "utilstrencodings.h"
|
||||
#include "validationinterface.h"
|
||||
#include "policy/policy.h"
|
||||
#include "blockencodings.h"
|
||||
|
||||
#ifdef ENABLE_WALLET
|
||||
#include "wallet/wallet.h"
|
||||
|
|
@ -243,6 +244,202 @@ UniValue combineblocksigs(const JSONRPCRequest& request)
|
|||
return result;
|
||||
}
|
||||
|
||||
UniValue getcompactsketch(const JSONRPCRequest& request)
|
||||
{
|
||||
if (request.fHelp || request.params.size() != 1)
|
||||
throw runtime_error(
|
||||
"getcompactsketch block_hex\n"
|
||||
"\nGets hex representation of a proposed compact block sketch.\n"
|
||||
"It is consumed by `consumecompactsketch.`\n"
|
||||
"Arguments:\n"
|
||||
"1. \"block_hex\" (string, required), Hex serialized block proposal from `getnewblockhex`.\n"
|
||||
"\nResult\n"
|
||||
"blockhex (hex) The block hex\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("getcompactsketch", "")
|
||||
);
|
||||
|
||||
CBlock block;
|
||||
std::vector<unsigned char> block_bytes(ParseHex(request.params[0].get_str()));
|
||||
CDataStream ssBlock(block_bytes, SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssBlock >> block;
|
||||
|
||||
CBlockHeaderAndShortTxIDs cmpctblock(block, true);
|
||||
|
||||
CDataStream ssCompactBlock(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssCompactBlock << cmpctblock;
|
||||
return HexStr(ssCompactBlock.begin(), ssCompactBlock.end());
|
||||
|
||||
}
|
||||
|
||||
UniValue consumecompactsketch(const JSONRPCRequest& request)
|
||||
{
|
||||
if (request.fHelp || request.params.size() != 1)
|
||||
throw runtime_error(
|
||||
"consumecompactsketch sketch\n"
|
||||
"\nTakes hex representation of a proposed compact block sketch and fills it in\n"
|
||||
"using mempool. Returns the block if complete, and a list\n"
|
||||
"of missing transaction indices serialized as a native structure."
|
||||
"NOTE: The latest instance of this call will have a partially filled block\n"
|
||||
"cached in memory to be used in `consumegetblocktxn` to finalize the block.\n"
|
||||
"Arguments:\n"
|
||||
"1. \"sketch\" (string, required), Hex string of compact block sketch.\n"
|
||||
"\nResult\n"
|
||||
"{\n"
|
||||
" blockhex (hex) The filled block hex. Only returns when block is final\n"
|
||||
" block_tx_req (hex) The serialized structure of missing transaction indices, given to serving node\n"
|
||||
" found_transactions (hex) The serialized list of found transactions to be used in finalizecompactblock\n"
|
||||
"}\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("consumecompactsketch", "<sketch>")
|
||||
);
|
||||
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
|
||||
std::vector<unsigned char> compact_block_bytes(ParseHex(request.params[0].get_str()));
|
||||
CDataStream ssBlock(compact_block_bytes, SER_NETWORK, PROTOCOL_VERSION);
|
||||
CBlockHeaderAndShortTxIDs cmpctblock;
|
||||
ssBlock >> cmpctblock;
|
||||
|
||||
PartiallyDownloadedBlock partialBlock(&mempool);
|
||||
const std::vector<std::pair<uint256, CTransactionRef>> dummy;
|
||||
ReadStatus status = partialBlock.InitData(cmpctblock, dummy);
|
||||
if (status != READ_STATUS_OK) {
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Compact block decode failed");
|
||||
}
|
||||
|
||||
BlockTransactionsRequest req;
|
||||
std::vector<CTransactionRef> found(partialBlock.GetAvailableTx());
|
||||
for (size_t i = 0; i < cmpctblock.BlockTxCount(); i++) {
|
||||
if (!partialBlock.IsTxAvailable(i)) {
|
||||
req.indexes.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
CDataStream ssReq(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssReq << req;
|
||||
|
||||
CDataStream ssFound(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssFound << found;
|
||||
|
||||
if (req.indexes.empty()) {
|
||||
std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
|
||||
std::vector<CTransactionRef> dummy;
|
||||
ReadStatus status = partialBlock.FillBlock(*pblock, dummy, false /* don't get pow */);
|
||||
if (status == READ_STATUS_INVALID) {
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Bogus crap sketch.");
|
||||
} else if (status == READ_STATUS_FAILED) {
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Failed to complete block though all transactions were apparently found. Could be random short ID collision; requires full block instead.");
|
||||
} else if (status == READ_STATUS_CHECKBLOCK_FAILED) {
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Checkblock failed.");
|
||||
}
|
||||
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssBlock << *pblock;
|
||||
|
||||
ret.pushKV("blockhex", HexStr(ssBlock.begin(), ssBlock.end()));
|
||||
} else {
|
||||
ret.pushKV("block_tx_req", HexStr(ssReq.begin(), ssReq.end()));
|
||||
ret.pushKV("found_transactions", HexStr(ssFound.begin(), ssFound.end()));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
UniValue consumegetblocktxn(const JSONRPCRequest& request)
|
||||
{
|
||||
if (request.fHelp || request.params.size() != 2)
|
||||
throw runtime_error(
|
||||
"consumegetblocktxn full_block block_tx_req\n"
|
||||
"Consumes a transaction request for a compact block sketch."
|
||||
"Arguments:\n"
|
||||
"1. \"full_block\" (string, required), Hex serialied block that corresponds to the block request `block_tx_req`.\n"
|
||||
"2. \"block_tx_req\" (string, required), Hex serialied BlockTransactionsRequest, aka getblocktxn network message.\n"
|
||||
"\nResult\n"
|
||||
"block_transactions (hex) The serialized list of found transactions aka BlockTransactions\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("consumegetblocktxn", "<block_tx_req>")
|
||||
);
|
||||
|
||||
|
||||
CBlock block;
|
||||
std::vector<unsigned char> block_bytes(ParseHex(request.params[0].get_str()));
|
||||
CDataStream ssBlock(block_bytes, SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssBlock >> block;
|
||||
|
||||
// Take in BlockTransactionsRequest, return BlockTransactions
|
||||
std::vector<unsigned char> block_req(ParseHex(request.params[1].get_str()));
|
||||
CDataStream ssReq(block_req, SER_NETWORK, PROTOCOL_VERSION);
|
||||
|
||||
BlockTransactionsRequest req;
|
||||
ssReq >> req;
|
||||
|
||||
BlockTransactions resp(req);
|
||||
for (size_t i = 0; i < req.indexes.size(); i++) {
|
||||
if (req.indexes[i] >= block.vtx.size()) {
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Peer sent us a getblocktxn with out-of-bounds tx indices");
|
||||
}
|
||||
resp.txn[i] = block.vtx[req.indexes[i]];
|
||||
}
|
||||
|
||||
CDataStream ssResp(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssResp << resp;
|
||||
|
||||
return HexStr(ssResp.begin(), ssResp.end());
|
||||
}
|
||||
|
||||
UniValue finalizecompactblock(const JSONRPCRequest& request)
|
||||
{
|
||||
if (request.fHelp || request.params.size() != 3)
|
||||
throw runtime_error(
|
||||
"finalizecompactblock compact_hex block_transactions found_transactions\n"
|
||||
"Takes the two transaction lists, fills out the compact block and attempts to validate it."
|
||||
"Arguments:\n"
|
||||
"1. \"compact_hex\" (string, required), Hex serialized compact block.\n"
|
||||
"2. \"block_transactions\" (string, required), Hex serialized BlockTransactions, the response to getblocktxn.\n"
|
||||
"3. \"found_transactions\" (string, required), Hex serialized list of transactions that were found in response to receiving a compact sketch in `consumecompactsketch`.\n"
|
||||
"\nResult\n"
|
||||
"block (hex) The serialized final block.\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("finalizecompactblock", "<compact_hex> <block_transactions> <found_transactions>")
|
||||
);
|
||||
|
||||
// Compact block
|
||||
std::vector<unsigned char> compact_block_bytes(ParseHex(request.params[0].get_str()));
|
||||
CDataStream ssCompactBlock(compact_block_bytes, SER_NETWORK, PROTOCOL_VERSION);
|
||||
CBlockHeaderAndShortTxIDs cmpctblock;
|
||||
ssCompactBlock >> cmpctblock;
|
||||
|
||||
// BlockTransactions from the server
|
||||
std::vector<unsigned char> block_tx(ParseHex(request.params[1].get_str()));
|
||||
CDataStream ssResp(block_tx, SER_NETWORK, PROTOCOL_VERSION);
|
||||
|
||||
BlockTransactions transactions;
|
||||
ssResp >> transactions;
|
||||
|
||||
// Cached transactions
|
||||
std::vector<unsigned char> found_tx(ParseHex(request.params[2].get_str()));
|
||||
CDataStream ssFound(block_tx, SER_NETWORK, PROTOCOL_VERSION);
|
||||
|
||||
std::vector<CTransactionRef> found;
|
||||
ssFound >> found;
|
||||
|
||||
// Make mega-list
|
||||
found.insert(found.end(), transactions.txn.begin(), transactions.txn.end());
|
||||
|
||||
// Now construct the final block!
|
||||
PartiallyDownloadedBlock partialBlock(&mempool);
|
||||
|
||||
const std::vector<std::pair<uint256, CTransactionRef>> dummy;
|
||||
std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
|
||||
if (partialBlock.InitData(cmpctblock, dummy) != READ_STATUS_OK || partialBlock.FillBlock(*pblock, found, false /* pow_check*/) != READ_STATUS_OK) {
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Failed to complete block though all transactions were apparently found. Could be random short ID collision; requires full block instead.");
|
||||
}
|
||||
|
||||
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssBlock << *pblock;
|
||||
|
||||
return HexStr(ssBlock.begin(), ssBlock.end());
|
||||
}
|
||||
|
||||
UniValue getmininginfo(const JSONRPCRequest& request)
|
||||
{
|
||||
if (request.fHelp || request.params.size() != 0)
|
||||
|
|
@ -1020,7 +1217,10 @@ static const CRPCCommand commands[] =
|
|||
{ "generating", "generate", &generate, true, {"nblocks","maxtries"} },
|
||||
{ "generating", "combineblocksigs", &combineblocksigs, true, {"blockhex","signatures"} },
|
||||
{ "generating", "getnewblockhex", &getnewblockhex, true, {"required_age"} },
|
||||
|
||||
{ "generating", "getcompactsketch", &getcompactsketch, true, {"block_hex"} },
|
||||
{ "generating", "consumecompactsketch", &consumecompactsketch, true, {"sketch"} },
|
||||
{ "generating", "consumegetblocktxn", &consumegetblocktxn, true, {"full_block", "block_tx_req"} },
|
||||
{ "generating", "finalizecompactblock", &finalizecompactblock, true, {"compact_hex","block_transactions","found_transactions"} },
|
||||
{ "util", "estimatefee", &estimatefee, true, {"nblocks"} },
|
||||
{ "util", "estimatepriority", &estimatepriority, true, {"nblocks"} },
|
||||
{ "util", "estimatesmartfee", &estimatesmartfee, true, {"nblocks"} },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue