Browse Source
0.1348efec8
Fix some minor compact block issues that came up in review (Matt Corallo)ccd06b9
Elaborate bucket size math (Pieter Wuille)0d4cb48
Use vTxHashes to optimize InitData significantly (Matt Corallo)8119026
Provide a flat list of txid/terators to txn in CTxMemPool (Matt Corallo)678ee97
Add BIP 152 to implemented BIPs list (Matt Corallo)56ba516
Add reconstruction debug logging (Matt Corallo)2f34a2e
Get our "best three" peers to announce blocks using cmpctblocks (Matt Corallo)927f8ee
Add ability to fetch CNode by NodeId (Matt Corallo)d25cd3e
Add receiver-side protocol implementation for CMPCTBLOCK stuff (Matt Corallo)9c837d5
Add sender-side protocol implementation for CMPCTBLOCK stuff (Matt Corallo)00c4078
Add protocol messages for short-ids blocks (Matt Corallo)e3b2222
Add some blockencodings tests (Matt Corallo)f4f8f14
Add TestMemPoolEntryHelper::FromTx version for CTransaction (Matt Corallo)85ad31e
Add partial-block block encodings API (Matt Corallo)5249dac
Add COMPACTSIZE wrapper similar to VARINT for serialization (Matt Corallo)cbda71c
Move context-required checks from CheckBlockHeader to Contextual... (Matt Corallo)7c29ec9
If AcceptBlockHeader returns true, pindex will be set. (Matt Corallo)96806c3
Stop trimming when mapTx is empty (Pieter Wuille)
Wladimir J. van der Laan
9 years ago
18 changed files with 1161 additions and 53 deletions
@ -0,0 +1,180 @@
@@ -0,0 +1,180 @@
|
||||
// Copyright (c) 2016 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "blockencodings.h" |
||||
#include "consensus/consensus.h" |
||||
#include "consensus/validation.h" |
||||
#include "chainparams.h" |
||||
#include "hash.h" |
||||
#include "random.h" |
||||
#include "streams.h" |
||||
#include "txmempool.h" |
||||
#include "main.h" |
||||
#include "util.h" |
||||
|
||||
#include <unordered_map> |
||||
|
||||
#define MIN_TRANSACTION_SIZE (::GetSerializeSize(CTransaction(), SER_NETWORK, PROTOCOL_VERSION)) |
||||
|
||||
CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block) : |
||||
nonce(GetRand(std::numeric_limits<uint64_t>::max())), |
||||
shorttxids(block.vtx.size() - 1), prefilledtxn(1), header(block) { |
||||
FillShortTxIDSelector(); |
||||
//TODO: Use our mempool prior to block acceptance to predictively fill more than just the coinbase
|
||||
prefilledtxn[0] = {0, block.vtx[0]}; |
||||
for (size_t i = 1; i < block.vtx.size(); i++) { |
||||
const CTransaction& tx = block.vtx[i]; |
||||
shorttxids[i - 1] = GetShortID(tx.GetHash()); |
||||
} |
||||
} |
||||
|
||||
void CBlockHeaderAndShortTxIDs::FillShortTxIDSelector() const { |
||||
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); |
||||
stream << header << nonce; |
||||
CSHA256 hasher; |
||||
hasher.Write((unsigned char*)&(*stream.begin()), stream.end() - stream.begin()); |
||||
uint256 shorttxidhash; |
||||
hasher.Finalize(shorttxidhash.begin()); |
||||
shorttxidk0 = shorttxidhash.GetUint64(0); |
||||
shorttxidk1 = shorttxidhash.GetUint64(1); |
||||
} |
||||
|
||||
uint64_t CBlockHeaderAndShortTxIDs::GetShortID(const uint256& txhash) const { |
||||
static_assert(SHORTTXIDS_LENGTH == 6, "shorttxids calculation assumes 6-byte shorttxids"); |
||||
return SipHashUint256(shorttxidk0, shorttxidk1, txhash) & 0xffffffffffffL; |
||||
} |
||||
|
||||
|
||||
|
||||
ReadStatus PartiallyDownloadedBlock::InitData(const CBlockHeaderAndShortTxIDs& cmpctblock) { |
||||
if (cmpctblock.header.IsNull() || (cmpctblock.shorttxids.empty() && cmpctblock.prefilledtxn.empty())) |
||||
return READ_STATUS_INVALID; |
||||
if (cmpctblock.shorttxids.size() + cmpctblock.prefilledtxn.size() > MAX_BLOCK_SIZE / MIN_TRANSACTION_SIZE) |
||||
return READ_STATUS_INVALID; |
||||
|
||||
assert(header.IsNull() && txn_available.empty()); |
||||
header = cmpctblock.header; |
||||
txn_available.resize(cmpctblock.BlockTxCount()); |
||||
|
||||
int32_t lastprefilledindex = -1; |
||||
for (size_t i = 0; i < cmpctblock.prefilledtxn.size(); i++) { |
||||
if (cmpctblock.prefilledtxn[i].tx.IsNull()) |
||||
return READ_STATUS_INVALID; |
||||
|
||||
lastprefilledindex += cmpctblock.prefilledtxn[i].index + 1; //index is a uint16_t, so cant overflow here
|
||||
if (lastprefilledindex > std::numeric_limits<uint16_t>::max()) |
||||
return READ_STATUS_INVALID; |
||||
if ((uint32_t)lastprefilledindex > cmpctblock.shorttxids.size() + i) { |
||||
// If we are inserting a tx at an index greater than our full list of shorttxids
|
||||
// plus the number of prefilled txn we've inserted, then we have txn for which we
|
||||
// have neither a prefilled txn or a shorttxid!
|
||||
return READ_STATUS_INVALID; |
||||
} |
||||
txn_available[lastprefilledindex] = std::make_shared<CTransaction>(cmpctblock.prefilledtxn[i].tx); |
||||
} |
||||
prefilled_count = cmpctblock.prefilledtxn.size(); |
||||
|
||||
// Calculate map of txids -> positions and check mempool to see what we have (or dont)
|
||||
// Because well-formed cmpctblock messages will have a (relatively) uniform distribution
|
||||
// of short IDs, any highly-uneven distribution of elements can be safely treated as a
|
||||
// READ_STATUS_FAILED.
|
||||
std::unordered_map<uint64_t, uint16_t> shorttxids(cmpctblock.shorttxids.size()); |
||||
uint16_t index_offset = 0; |
||||
for (size_t i = 0; i < cmpctblock.shorttxids.size(); i++) { |
||||
while (txn_available[i + index_offset]) |
||||
index_offset++; |
||||
shorttxids[cmpctblock.shorttxids[i]] = i + index_offset; |
||||
// To determine the chance that the number of entries in a bucket exceeds N,
|
||||
// we use the fact that the number of elements in a single bucket is
|
||||
// binomially distributed (with n = the number of shorttxids S, and p =
|
||||
// 1 / the number of buckets), that in the worst case the number of buckets is
|
||||
// equal to S (due to std::unordered_map having a default load factor of 1.0),
|
||||
// and that the chance for any bucket to exceed N elements is at most
|
||||
// buckets * (the chance that any given bucket is above N elements).
|
||||
// Thus: P(max_elements_per_bucket > N) <= S * (1 - cdf(binomial(n=S,p=1/S), N)).
|
||||
// If we assume blocks of up to 16000, allowing 12 elements per bucket should
|
||||
// only fail once per ~1 million block transfers (per peer and connection).
|
||||
if (shorttxids.bucket_size(shorttxids.bucket(cmpctblock.shorttxids[i])) > 12) |
||||
return READ_STATUS_FAILED; |
||||
} |
||||
// TODO: in the shortid-collision case, we should instead request both transactions
|
||||
// which collided. Falling back to full-block-request here is overkill.
|
||||
if (shorttxids.size() != cmpctblock.shorttxids.size()) |
||||
return READ_STATUS_FAILED; // Short ID collision
|
||||
|
||||
std::vector<bool> have_txn(txn_available.size()); |
||||
LOCK(pool->cs); |
||||
const std::vector<std::pair<uint256, CTxMemPool::txiter> >& vTxHashes = pool->vTxHashes; |
||||
for (size_t i = 0; i < vTxHashes.size(); i++) { |
||||
uint64_t shortid = cmpctblock.GetShortID(vTxHashes[i].first); |
||||
std::unordered_map<uint64_t, uint16_t>::iterator idit = shorttxids.find(shortid); |
||||
if (idit != shorttxids.end()) { |
||||
if (!have_txn[idit->second]) { |
||||
txn_available[idit->second] = vTxHashes[i].second->GetSharedTx(); |
||||
have_txn[idit->second] = true; |
||||
mempool_count++; |
||||
} else { |
||||
// If we find two mempool txn that match the short id, just request it.
|
||||
// This should be rare enough that the extra bandwidth doesn't matter,
|
||||
// but eating a round-trip due to FillBlock failure would be annoying
|
||||
if (txn_available[idit->second]) { |
||||
txn_available[idit->second].reset(); |
||||
mempool_count--; |
||||
} |
||||
} |
||||
} |
||||
// Though ideally we'd continue scanning for the two-txn-match-shortid case,
|
||||
// the performance win of an early exit here is too good to pass up and worth
|
||||
// the extra risk.
|
||||
if (mempool_count == shorttxids.size()) |
||||
break; |
||||
} |
||||
|
||||
LogPrint("cmpctblock", "Initialized PartiallyDownloadedBlock for block %s using a cmpctblock of size %lu\n", cmpctblock.header.GetHash().ToString(), cmpctblock.GetSerializeSize(SER_NETWORK, PROTOCOL_VERSION)); |
||||
|
||||
return READ_STATUS_OK; |
||||
} |
||||
|
||||
bool PartiallyDownloadedBlock::IsTxAvailable(size_t index) const { |
||||
assert(!header.IsNull()); |
||||
assert(index < txn_available.size()); |
||||
return txn_available[index] ? true : false; |
||||
} |
||||
|
||||
ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector<CTransaction>& vtx_missing) const { |
||||
assert(!header.IsNull()); |
||||
block = header; |
||||
block.vtx.resize(txn_available.size()); |
||||
|
||||
size_t tx_missing_offset = 0; |
||||
for (size_t i = 0; i < txn_available.size(); i++) { |
||||
if (!txn_available[i]) { |
||||
if (vtx_missing.size() <= tx_missing_offset) |
||||
return READ_STATUS_INVALID; |
||||
block.vtx[i] = vtx_missing[tx_missing_offset++]; |
||||
} else |
||||
block.vtx[i] = *txn_available[i]; |
||||
} |
||||
if (vtx_missing.size() != tx_missing_offset) |
||||
return READ_STATUS_INVALID; |
||||
|
||||
CValidationState state; |
||||
if (!CheckBlock(block, state, Params().GetConsensus())) { |
||||
// 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
|
||||
// check its own merkle root and cache that check.
|
||||
if (state.CorruptionPossible()) |
||||
return READ_STATUS_FAILED; // Possible Short ID collision
|
||||
return READ_STATUS_INVALID; |
||||
} |
||||
|
||||
LogPrint("cmpctblock", "Successfully reconstructed block %s with %lu txn prefilled, %lu txn from mempool and %lu txn requested\n", header.GetHash().ToString(), prefilled_count, mempool_count, vtx_missing.size()); |
||||
if (vtx_missing.size() < 5) { |
||||
for(const CTransaction& tx : vtx_missing) |
||||
LogPrint("cmpctblock", "Reconstructed block %s required tx %s\n", header.GetHash().ToString(), tx.GetHash().ToString()); |
||||
} |
||||
|
||||
return READ_STATUS_OK; |
||||
} |
@ -0,0 +1,206 @@
@@ -0,0 +1,206 @@
|
||||
// Copyright (c) 2016 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_BLOCK_ENCODINGS_H |
||||
#define BITCOIN_BLOCK_ENCODINGS_H |
||||
|
||||
#include "primitives/block.h" |
||||
|
||||
#include <memory> |
||||
|
||||
class CTxMemPool; |
||||
|
||||
// Dumb helper to handle CTransaction compression at serialize-time
|
||||
struct TransactionCompressor { |
||||
private: |
||||
CTransaction& tx; |
||||
public: |
||||
TransactionCompressor(CTransaction& txIn) : tx(txIn) {} |
||||
|
||||
ADD_SERIALIZE_METHODS; |
||||
|
||||
template <typename Stream, typename Operation> |
||||
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { |
||||
READWRITE(tx); //TODO: Compress tx encoding
|
||||
} |
||||
}; |
||||
|
||||
class BlockTransactionsRequest { |
||||
public: |
||||
// A BlockTransactionsRequest message
|
||||
uint256 blockhash; |
||||
std::vector<uint16_t> indexes; |
||||
|
||||
ADD_SERIALIZE_METHODS; |
||||
|
||||
template <typename Stream, typename Operation> |
||||
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { |
||||
READWRITE(blockhash); |
||||
uint64_t indexes_size = (uint64_t)indexes.size(); |
||||
READWRITE(COMPACTSIZE(indexes_size)); |
||||
if (ser_action.ForRead()) { |
||||
size_t i = 0; |
||||
while (indexes.size() < indexes_size) { |
||||
indexes.resize(std::min((uint64_t)(1000 + indexes.size()), indexes_size)); |
||||
for (; i < indexes.size(); i++) { |
||||
uint64_t index = 0; |
||||
READWRITE(COMPACTSIZE(index)); |
||||
if (index > std::numeric_limits<uint16_t>::max()) |
||||
throw std::ios_base::failure("index overflowed 16 bits"); |
||||
indexes[i] = index; |
||||
} |
||||
} |
||||
|
||||
uint16_t offset = 0; |
||||
for (size_t i = 0; i < indexes.size(); i++) { |
||||
if (uint64_t(indexes[i]) + uint64_t(offset) > std::numeric_limits<uint16_t>::max()) |
||||
throw std::ios_base::failure("indexes overflowed 16 bits"); |
||||
indexes[i] = indexes[i] + offset; |
||||
offset = indexes[i] + 1; |
||||
} |
||||
} else { |
||||
for (size_t i = 0; i < indexes.size(); i++) { |
||||
uint64_t index = indexes[i] - (i == 0 ? 0 : (indexes[i - 1] + 1)); |
||||
READWRITE(COMPACTSIZE(index)); |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
|
||||
class BlockTransactions { |
||||
public: |
||||
// A BlockTransactions message
|
||||
uint256 blockhash; |
||||
std::vector<CTransaction> txn; |
||||
|
||||
BlockTransactions() {} |
||||
BlockTransactions(const BlockTransactionsRequest& req) : |
||||
blockhash(req.blockhash), txn(req.indexes.size()) {} |
||||
|
||||
ADD_SERIALIZE_METHODS; |
||||
|
||||
template <typename Stream, typename Operation> |
||||
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { |
||||
READWRITE(blockhash); |
||||
uint64_t txn_size = (uint64_t)txn.size(); |
||||
READWRITE(COMPACTSIZE(txn_size)); |
||||
if (ser_action.ForRead()) { |
||||
size_t i = 0; |
||||
while (txn.size() < txn_size) { |
||||
txn.resize(std::min((uint64_t)(1000 + txn.size()), txn_size)); |
||||
for (; i < txn.size(); i++) |
||||
READWRITE(REF(TransactionCompressor(txn[i]))); |
||||
} |
||||
} else { |
||||
for (size_t i = 0; i < txn.size(); i++) |
||||
READWRITE(REF(TransactionCompressor(txn[i]))); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
// Dumb serialization/storage-helper for CBlockHeaderAndShortTxIDs and PartiallyDownlaodedBlock
|
||||
struct PrefilledTransaction { |
||||
// Used as an offset since last prefilled tx in CBlockHeaderAndShortTxIDs,
|
||||
// as a proper transaction-in-block-index in PartiallyDownloadedBlock
|
||||
uint16_t index; |
||||
CTransaction tx; |
||||
|
||||
ADD_SERIALIZE_METHODS; |
||||
|
||||
template <typename Stream, typename Operation> |
||||
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { |
||||
uint64_t idx = index; |
||||
READWRITE(COMPACTSIZE(idx)); |
||||
if (idx > std::numeric_limits<uint16_t>::max()) |
||||
throw std::ios_base::failure("index overflowed 16-bits"); |
||||
index = idx; |
||||
READWRITE(REF(TransactionCompressor(tx))); |
||||
} |
||||
}; |
||||
|
||||
typedef enum ReadStatus_t |
||||
{ |
||||
READ_STATUS_OK, |
||||
READ_STATUS_INVALID, // Invalid object, peer is sending bogus crap
|
||||
READ_STATUS_FAILED, // Failed to process object
|
||||
} ReadStatus; |
||||
|
||||
class CBlockHeaderAndShortTxIDs { |
||||
private: |
||||
mutable uint64_t shorttxidk0, shorttxidk1; |
||||
uint64_t nonce; |
||||
|
||||
void FillShortTxIDSelector() const; |
||||
|
||||
friend class PartiallyDownloadedBlock; |
||||
|
||||
static const int SHORTTXIDS_LENGTH = 6; |
||||
protected: |
||||
std::vector<uint64_t> shorttxids; |
||||
std::vector<PrefilledTransaction> prefilledtxn; |
||||
|
||||
public: |
||||
CBlockHeader header; |
||||
|
||||
// Dummy for deserialization
|
||||
CBlockHeaderAndShortTxIDs() {} |
||||
|
||||
CBlockHeaderAndShortTxIDs(const CBlock& block); |
||||
|
||||
uint64_t GetShortID(const uint256& txhash) const; |
||||
|
||||
size_t BlockTxCount() const { return shorttxids.size() + prefilledtxn.size(); } |
||||
|
||||
ADD_SERIALIZE_METHODS; |
||||
|
||||
template <typename Stream, typename Operation> |
||||
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { |
||||
READWRITE(header); |
||||
READWRITE(nonce); |
||||
|
||||
uint64_t shorttxids_size = (uint64_t)shorttxids.size(); |
||||
READWRITE(COMPACTSIZE(shorttxids_size)); |
||||
if (ser_action.ForRead()) { |
||||
size_t i = 0; |
||||
while (shorttxids.size() < shorttxids_size) { |
||||
shorttxids.resize(std::min((uint64_t)(1000 + shorttxids.size()), shorttxids_size)); |
||||
for (; i < shorttxids.size(); i++) { |
||||
uint32_t lsb = 0; uint16_t msb = 0; |
||||
READWRITE(lsb); |
||||
READWRITE(msb); |
||||
shorttxids[i] = (uint64_t(msb) << 32) | uint64_t(lsb); |
||||
static_assert(SHORTTXIDS_LENGTH == 6, "shorttxids serialization assumes 6-byte shorttxids"); |
||||
} |
||||
} |
||||
} else { |
||||
for (size_t i = 0; i < shorttxids.size(); i++) { |
||||
uint32_t lsb = shorttxids[i] & 0xffffffff; |
||||
uint16_t msb = (shorttxids[i] >> 32) & 0xffff; |
||||
READWRITE(lsb); |
||||
READWRITE(msb); |
||||
} |
||||
} |
||||
|
||||
READWRITE(prefilledtxn); |
||||
|
||||
if (ser_action.ForRead()) |
||||
FillShortTxIDSelector(); |
||||
} |
||||
}; |
||||
|
||||
class PartiallyDownloadedBlock { |
||||
protected: |
||||
std::vector<std::shared_ptr<const CTransaction> > txn_available; |
||||
size_t prefilled_count = 0, mempool_count = 0; |
||||
CTxMemPool* pool; |
||||
public: |
||||
CBlockHeader header; |
||||
PartiallyDownloadedBlock(CTxMemPool* poolIn) : pool(poolIn) {} |
||||
|
||||
ReadStatus InitData(const CBlockHeaderAndShortTxIDs& cmpctblock); |
||||
bool IsTxAvailable(size_t index) const; |
||||
ReadStatus FillBlock(CBlock& block, const std::vector<CTransaction>& vtx_missing) const; |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,315 @@
@@ -0,0 +1,315 @@
|
||||
// Copyright (c) 2011-2015 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "blockencodings.h" |
||||
#include "consensus/merkle.h" |
||||
#include "chainparams.h" |
||||
#include "random.h" |
||||
|
||||
#include "test/test_bitcoin.h" |
||||
|
||||
#include <boost/test/unit_test.hpp> |
||||
|
||||
struct RegtestingSetup : public TestingSetup { |
||||
RegtestingSetup() : TestingSetup(CBaseChainParams::REGTEST) {} |
||||
}; |
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(blockencodings_tests, RegtestingSetup) |
||||
|
||||
static CBlock BuildBlockTestCase() { |
||||
CBlock block; |
||||
CMutableTransaction tx; |
||||
tx.vin.resize(1); |
||||
tx.vin[0].scriptSig.resize(10); |
||||
tx.vout.resize(1); |
||||
tx.vout[0].nValue = 42; |
||||
|
||||
block.vtx.resize(3); |
||||
block.vtx[0] = tx; |
||||
block.nVersion = 42; |
||||
block.hashPrevBlock = GetRandHash(); |
||||
block.nBits = 0x207fffff; |
||||
|
||||
tx.vin[0].prevout.hash = GetRandHash(); |
||||
tx.vin[0].prevout.n = 0; |
||||
block.vtx[1] = tx; |
||||
|
||||
tx.vin.resize(10); |
||||
for (size_t i = 0; i < tx.vin.size(); i++) { |
||||
tx.vin[i].prevout.hash = GetRandHash(); |
||||
tx.vin[i].prevout.n = 0; |
||||
} |
||||
block.vtx[2] = tx; |
||||
|
||||
bool mutated; |
||||
block.hashMerkleRoot = BlockMerkleRoot(block, &mutated); |
||||
assert(!mutated); |
||||
while (!CheckProofOfWork(block.GetHash(), block.nBits, Params().GetConsensus())) ++block.nNonce; |
||||
return block; |
||||
} |
||||
|
||||
// Number of shared use_counts we expect for a tx we havent touched
|
||||
// == 2 (mempool + our copy from the GetSharedTx call)
|
||||
#define SHARED_TX_OFFSET 2 |
||||
|
||||
BOOST_AUTO_TEST_CASE(SimpleRoundTripTest) |
||||
{ |
||||
CTxMemPool pool(CFeeRate(0)); |
||||
TestMemPoolEntryHelper entry; |
||||
CBlock block(BuildBlockTestCase()); |
||||
|
||||
pool.addUnchecked(block.vtx[2].GetHash(), entry.FromTx(block.vtx[2])); |
||||
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2].GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0); |
||||
|
||||
// Do a simple ShortTxIDs RT
|
||||
{ |
||||
CBlockHeaderAndShortTxIDs shortIDs(block); |
||||
|
||||
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); |
||||
stream << shortIDs; |
||||
|
||||
CBlockHeaderAndShortTxIDs shortIDs2; |
||||
stream >> shortIDs2; |
||||
|
||||
PartiallyDownloadedBlock partialBlock(&pool); |
||||
BOOST_CHECK(partialBlock.InitData(shortIDs2) == READ_STATUS_OK); |
||||
BOOST_CHECK( partialBlock.IsTxAvailable(0)); |
||||
BOOST_CHECK(!partialBlock.IsTxAvailable(1)); |
||||
BOOST_CHECK( partialBlock.IsTxAvailable(2)); |
||||
|
||||
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2].GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 1); |
||||
|
||||
std::list<CTransaction> removed; |
||||
pool.removeRecursive(block.vtx[2], removed); |
||||
BOOST_CHECK_EQUAL(removed.size(), 1); |
||||
|
||||
CBlock block2; |
||||
std::vector<CTransaction> vtx_missing; |
||||
BOOST_CHECK(partialBlock.FillBlock(block2, vtx_missing) == READ_STATUS_INVALID); // No transactions
|
||||
|
||||
vtx_missing.push_back(block.vtx[2]); // Wrong transaction
|
||||
partialBlock.FillBlock(block2, vtx_missing); // Current implementation doesn't check txn here, but don't require that
|
||||
bool mutated; |
||||
BOOST_CHECK(block.hashMerkleRoot != BlockMerkleRoot(block2, &mutated)); |
||||
|
||||
vtx_missing[0] = block.vtx[1]; |
||||
CBlock block3; |
||||
BOOST_CHECK(partialBlock.FillBlock(block3, vtx_missing) == READ_STATUS_OK); |
||||
BOOST_CHECK_EQUAL(block.GetHash().ToString(), block3.GetHash().ToString()); |
||||
BOOST_CHECK_EQUAL(block.hashMerkleRoot.ToString(), BlockMerkleRoot(block3, &mutated).ToString()); |
||||
BOOST_CHECK(!mutated); |
||||
} |
||||
} |
||||
|
||||
class TestHeaderAndShortIDs { |
||||
// Utility to encode custom CBlockHeaderAndShortTxIDs
|
||||
public: |
||||
CBlockHeader header; |
||||
uint64_t nonce; |
||||
std::vector<uint64_t> shorttxids; |
||||
std::vector<PrefilledTransaction> prefilledtxn; |
||||
|
||||
TestHeaderAndShortIDs(const CBlockHeaderAndShortTxIDs& orig) { |
||||
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); |
||||
stream << orig; |
||||
stream >> *this; |
||||
} |
||||
TestHeaderAndShortIDs(const CBlock& block) : |
||||
TestHeaderAndShortIDs(CBlockHeaderAndShortTxIDs(block)) {} |
||||
|
||||
uint64_t GetShortID(const uint256& txhash) const { |
||||
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); |
||||
stream << *this; |
||||
CBlockHeaderAndShortTxIDs base; |
||||
stream >> base; |
||||
return base.GetShortID(txhash); |
||||
} |
||||
|
||||
ADD_SERIALIZE_METHODS; |
||||
|
||||
template <typename Stream, typename Operation> |
||||
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { |
||||
READWRITE(header); |
||||
READWRITE(nonce); |
||||
size_t shorttxids_size = shorttxids.size(); |
||||
READWRITE(VARINT(shorttxids_size)); |
||||
shorttxids.resize(shorttxids_size); |
||||
for (size_t i = 0; i < shorttxids.size(); i++) { |
||||
uint32_t lsb = shorttxids[i] & 0xffffffff; |
||||
uint16_t msb = (shorttxids[i] >> 32) & 0xffff; |
||||
READWRITE(lsb); |
||||
READWRITE(msb); |
||||
shorttxids[i] = (uint64_t(msb) << 32) | uint64_t(lsb); |
||||
} |
||||
READWRITE(prefilledtxn); |
||||
} |
||||
}; |
||||
|
||||
BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest) |
||||
{ |
||||
CTxMemPool pool(CFeeRate(0)); |
||||
TestMemPoolEntryHelper entry; |
||||
CBlock block(BuildBlockTestCase()); |
||||
|
||||
pool.addUnchecked(block.vtx[2].GetHash(), entry.FromTx(block.vtx[2])); |
||||
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2].GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0); |
||||
|
||||
// Test with pre-forwarding tx 1, but not coinbase
|
||||
{ |
||||
TestHeaderAndShortIDs shortIDs(block); |
||||
shortIDs.prefilledtxn.resize(1); |
||||
shortIDs.prefilledtxn[0] = {1, block.vtx[1]}; |
||||
shortIDs.shorttxids.resize(2); |
||||
shortIDs.shorttxids[0] = shortIDs.GetShortID(block.vtx[0].GetHash()); |
||||
shortIDs.shorttxids[1] = shortIDs.GetShortID(block.vtx[2].GetHash()); |
||||
|
||||
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); |
||||
stream << shortIDs; |
||||
|
||||
CBlockHeaderAndShortTxIDs shortIDs2; |
||||
stream >> shortIDs2; |
||||
|
||||
PartiallyDownloadedBlock partialBlock(&pool); |
||||
BOOST_CHECK(partialBlock.InitData(shortIDs2) == READ_STATUS_OK); |
||||
BOOST_CHECK(!partialBlock.IsTxAvailable(0)); |
||||
BOOST_CHECK( partialBlock.IsTxAvailable(1)); |
||||
BOOST_CHECK( partialBlock.IsTxAvailable(2)); |
||||
|
||||
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2].GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 1); |
||||
|
||||
CBlock block2; |
||||
std::vector<CTransaction> vtx_missing; |
||||
BOOST_CHECK(partialBlock.FillBlock(block2, vtx_missing) == READ_STATUS_INVALID); // No transactions
|
||||
|
||||
vtx_missing.push_back(block.vtx[1]); // Wrong transaction
|
||||
partialBlock.FillBlock(block2, vtx_missing); // Current implementation doesn't check txn here, but don't require that
|
||||
bool mutated; |
||||
BOOST_CHECK(block.hashMerkleRoot != BlockMerkleRoot(block2, &mutated)); |
||||
|
||||
vtx_missing[0] = block.vtx[0]; |
||||
CBlock block3; |
||||
BOOST_CHECK(partialBlock.FillBlock(block3, vtx_missing) == READ_STATUS_OK); |
||||
BOOST_CHECK_EQUAL(block.GetHash().ToString(), block3.GetHash().ToString()); |
||||
BOOST_CHECK_EQUAL(block.hashMerkleRoot.ToString(), BlockMerkleRoot(block3, &mutated).ToString()); |
||||
BOOST_CHECK(!mutated); |
||||
|
||||
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2].GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 1); |
||||
} |
||||
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2].GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0); |
||||
} |
||||
|
||||
BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest) |
||||
{ |
||||
CTxMemPool pool(CFeeRate(0)); |
||||
TestMemPoolEntryHelper entry; |
||||
CBlock block(BuildBlockTestCase()); |
||||
|
||||
pool.addUnchecked(block.vtx[1].GetHash(), entry.FromTx(block.vtx[1])); |
||||
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[1].GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0); |
||||
|
||||
// Test with pre-forwarding coinbase + tx 2 with tx 1 in mempool
|
||||
{ |
||||
TestHeaderAndShortIDs shortIDs(block); |
||||
shortIDs.prefilledtxn.resize(2); |
||||
shortIDs.prefilledtxn[0] = {0, block.vtx[0]}; |
||||
shortIDs.prefilledtxn[1] = {1, block.vtx[2]}; // id == 1 as it is 1 after index 1
|
||||
shortIDs.shorttxids.resize(1); |
||||
shortIDs.shorttxids[0] = shortIDs.GetShortID(block.vtx[1].GetHash()); |
||||
|
||||
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); |
||||
stream << shortIDs; |
||||
|
||||
CBlockHeaderAndShortTxIDs shortIDs2; |
||||
stream >> shortIDs2; |
||||
|
||||
PartiallyDownloadedBlock partialBlock(&pool); |
||||
BOOST_CHECK(partialBlock.InitData(shortIDs2) == READ_STATUS_OK); |
||||
BOOST_CHECK( partialBlock.IsTxAvailable(0)); |
||||
BOOST_CHECK( partialBlock.IsTxAvailable(1)); |
||||
BOOST_CHECK( partialBlock.IsTxAvailable(2)); |
||||
|
||||
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[1].GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 1); |
||||
|
||||
CBlock block2; |
||||
std::vector<CTransaction> vtx_missing; |
||||
BOOST_CHECK(partialBlock.FillBlock(block2, vtx_missing) == READ_STATUS_OK); |
||||
BOOST_CHECK_EQUAL(block.GetHash().ToString(), block2.GetHash().ToString()); |
||||
bool mutated; |
||||
BOOST_CHECK_EQUAL(block.hashMerkleRoot.ToString(), BlockMerkleRoot(block2, &mutated).ToString()); |
||||
BOOST_CHECK(!mutated); |
||||
|
||||
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[1].GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 1); |
||||
} |
||||
BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[1].GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0); |
||||
} |
||||
|
||||
BOOST_AUTO_TEST_CASE(EmptyBlockRoundTripTest) |
||||
{ |
||||
CTxMemPool pool(CFeeRate(0)); |
||||
CMutableTransaction coinbase; |
||||
coinbase.vin.resize(1); |
||||
coinbase.vin[0].scriptSig.resize(10); |
||||
coinbase.vout.resize(1); |
||||
coinbase.vout[0].nValue = 42; |
||||
|
||||
CBlock block; |
||||
block.vtx.resize(1); |
||||
block.vtx[0] = coinbase; |
||||
block.nVersion = 42; |
||||
block.hashPrevBlock = GetRandHash(); |
||||
block.nBits = 0x207fffff; |
||||
|
||||
bool mutated; |
||||
block.hashMerkleRoot = BlockMerkleRoot(block, &mutated); |
||||
assert(!mutated); |
||||
while (!CheckProofOfWork(block.GetHash(), block.nBits, Params().GetConsensus())) ++block.nNonce; |
||||
|
||||
// Test simple header round-trip with only coinbase
|
||||
{ |
||||
CBlockHeaderAndShortTxIDs shortIDs(block); |
||||
|
||||
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); |
||||
stream << shortIDs; |
||||
|
||||
CBlockHeaderAndShortTxIDs shortIDs2; |
||||
stream >> shortIDs2; |
||||
|
||||
PartiallyDownloadedBlock partialBlock(&pool); |
||||
BOOST_CHECK(partialBlock.InitData(shortIDs2) == READ_STATUS_OK); |
||||
BOOST_CHECK(partialBlock.IsTxAvailable(0)); |
||||
|
||||
CBlock block2; |
||||
std::vector<CTransaction> vtx_missing; |
||||
BOOST_CHECK(partialBlock.FillBlock(block2, vtx_missing) == READ_STATUS_OK); |
||||
BOOST_CHECK_EQUAL(block.GetHash().ToString(), block2.GetHash().ToString()); |
||||
bool mutated; |
||||
BOOST_CHECK_EQUAL(block.hashMerkleRoot.ToString(), BlockMerkleRoot(block2, &mutated).ToString()); |
||||
BOOST_CHECK(!mutated); |
||||
} |
||||
} |
||||
|
||||
BOOST_AUTO_TEST_CASE(TransactionsRequestSerializationTest) { |
||||
BlockTransactionsRequest req1; |
||||
req1.blockhash = GetRandHash(); |
||||
req1.indexes.resize(4); |
||||
req1.indexes[0] = 0; |
||||
req1.indexes[1] = 1; |
||||
req1.indexes[2] = 3; |
||||
req1.indexes[3] = 4; |
||||
|
||||
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); |
||||
stream << req1; |
||||
|
||||
BlockTransactionsRequest req2; |
||||
stream >> req2; |
||||
|
||||
BOOST_CHECK_EQUAL(req1.blockhash.ToString(), req2.blockhash.ToString()); |
||||
BOOST_CHECK_EQUAL(req1.indexes.size(), req2.indexes.size()); |
||||
BOOST_CHECK_EQUAL(req1.indexes[0], req2.indexes[0]); |
||||
BOOST_CHECK_EQUAL(req1.indexes[1], req2.indexes[1]); |
||||
BOOST_CHECK_EQUAL(req1.indexes[2], req2.indexes[2]); |
||||
BOOST_CHECK_EQUAL(req1.indexes[3], req2.indexes[3]); |
||||
} |
||||
|
||||
BOOST_AUTO_TEST_SUITE_END() |
Loading…
Reference in new issue