mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 20:12:57 +01:00
Add partial-block block encodings API
This commit is contained in:
parent
5249daca5a
commit
85ad31ede7
@ -74,6 +74,7 @@ BITCOIN_CORE_H = \
|
|||||||
addrman.h \
|
addrman.h \
|
||||||
base58.h \
|
base58.h \
|
||||||
bloom.h \
|
bloom.h \
|
||||||
|
blockencodings.h \
|
||||||
chain.h \
|
chain.h \
|
||||||
chainparams.h \
|
chainparams.h \
|
||||||
chainparamsbase.h \
|
chainparamsbase.h \
|
||||||
@ -163,6 +164,7 @@ libbitcoin_server_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
|||||||
libbitcoin_server_a_SOURCES = \
|
libbitcoin_server_a_SOURCES = \
|
||||||
addrman.cpp \
|
addrman.cpp \
|
||||||
bloom.cpp \
|
bloom.cpp \
|
||||||
|
blockencodings.cpp \
|
||||||
chain.cpp \
|
chain.cpp \
|
||||||
checkpoints.cpp \
|
checkpoints.cpp \
|
||||||
httprpc.cpp \
|
httprpc.cpp \
|
||||||
|
158
src/blockencodings.cpp
Normal file
158
src/blockencodings.cpp
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
// 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 <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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
// Bucket selection is a simple Binomial distribution. If we assume blocks of
|
||||||
|
// 10,000 transactions, allowing up to 12 elements per bucket should only fail
|
||||||
|
// once every ~1.3 million blocks and once every 74,000 blocks in a worst-case
|
||||||
|
// 16,000-transaction block.
|
||||||
|
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);
|
||||||
|
for (CTxMemPool::txiter it = pool->mapTx.begin(); it != pool->mapTx.end(); it++) {
|
||||||
|
std::unordered_map<uint64_t, uint16_t>::iterator idit = shorttxids.find(cmpctblock.GetShortID(it->GetTx().GetHash()));
|
||||||
|
if (idit != shorttxids.end()) {
|
||||||
|
if (!have_txn[idit->second]) {
|
||||||
|
txn_available[idit->second] = it->GetSharedTx();
|
||||||
|
have_txn[idit->second] = true;
|
||||||
|
} 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
|
||||||
|
txn_available[idit->second].reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return READ_STATUS_OK;
|
||||||
|
}
|
205
src/blockencodings.h
Normal file
205
src/blockencodings.h
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
// 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;
|
||||||
|
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
|
Loading…
Reference in New Issue
Block a user