Merge #410: compact block RPC calls

c3fe935 test compact block rpcs (Gregory Sanders)
c3cbe6e compact 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:
Gregory Sanders 2018-10-29 12:46:30 -04:00
commit 3cf35fc090
No known key found for this signature in database
GPG key ID: 6BE2CED14A9917BC
5 changed files with 249 additions and 23 deletions

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -6,6 +6,7 @@
#include "miner.h"
#include "amount.h"
#include "blockencodings.h"
#include "chain.h"
#include "chainparams.h"
#include "coins.h"

View file

@ -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"} },