Skip to content

Commit e9d76a1

Browse files
committed
Merge bitcoin#8068: Compact Blocks
48efec8 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)
2 parents 9e45ef1 + 48efec8 commit e9d76a1

18 files changed

+1161
-53
lines changed

doc/bips.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.13.0**):
2626
* [`BIP 125`](https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki): Opt-in full replace-by-fee signaling honoured in mempool and mining as of **v0.12.0** ([PR 6871](https://github.com/bitcoin/bitcoin/pull/6871)).
2727
* [`BIP 130`](https://github.com/bitcoin/bips/blob/master/bip-0130.mediawiki): direct headers announcement is negotiated with peer versions `>=70012` as of **v0.12.0** ([PR 6494](https://github.com/bitcoin/bitcoin/pull/6494)).
2828
* [`BIP 133`](https://github.com/bitcoin/bips/blob/master/bip-0133.mediawiki): feefilter messages are respected and sent for peer versions `>=70013` as of **v0.13.0** ([PR 7542](https://github.com/bitcoin/bitcoin/pull/7542)).
29+
* [`BIP 152`](https://github.com/bitcoin/bips/blob/master/bip-0152.mediawiki): Compact block transfer and related optimizations are used as of **v0.13.0** ([PR 8068](https://github.com/bitcoin/bitcoin/pull/8068)).

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ BITCOIN_CORE_H = \
7474
addrman.h \
7575
base58.h \
7676
bloom.h \
77+
blockencodings.h \
7778
chain.h \
7879
chainparams.h \
7980
chainparamsbase.h \
@@ -163,6 +164,7 @@ libbitcoin_server_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
163164
libbitcoin_server_a_SOURCES = \
164165
addrman.cpp \
165166
bloom.cpp \
167+
blockencodings.cpp \
166168
chain.cpp \
167169
checkpoints.cpp \
168170
httprpc.cpp \

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ BITCOIN_TESTS =\
4545
test/base58_tests.cpp \
4646
test/base64_tests.cpp \
4747
test/bip32_tests.cpp \
48+
test/blockencodings_tests.cpp \
4849
test/bloom_tests.cpp \
4950
test/Checkpoints_tests.cpp \
5051
test/coins_tests.cpp \

src/blockencodings.cpp

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// Copyright (c) 2016 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include "blockencodings.h"
6+
#include "consensus/consensus.h"
7+
#include "consensus/validation.h"
8+
#include "chainparams.h"
9+
#include "hash.h"
10+
#include "random.h"
11+
#include "streams.h"
12+
#include "txmempool.h"
13+
#include "main.h"
14+
#include "util.h"
15+
16+
#include <unordered_map>
17+
18+
#define MIN_TRANSACTION_SIZE (::GetSerializeSize(CTransaction(), SER_NETWORK, PROTOCOL_VERSION))
19+
20+
CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block) :
21+
nonce(GetRand(std::numeric_limits<uint64_t>::max())),
22+
shorttxids(block.vtx.size() - 1), prefilledtxn(1), header(block) {
23+
FillShortTxIDSelector();
24+
//TODO: Use our mempool prior to block acceptance to predictively fill more than just the coinbase
25+
prefilledtxn[0] = {0, block.vtx[0]};
26+
for (size_t i = 1; i < block.vtx.size(); i++) {
27+
const CTransaction& tx = block.vtx[i];
28+
shorttxids[i - 1] = GetShortID(tx.GetHash());
29+
}
30+
}
31+
32+
void CBlockHeaderAndShortTxIDs::FillShortTxIDSelector() const {
33+
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
34+
stream << header << nonce;
35+
CSHA256 hasher;
36+
hasher.Write((unsigned char*)&(*stream.begin()), stream.end() - stream.begin());
37+
uint256 shorttxidhash;
38+
hasher.Finalize(shorttxidhash.begin());
39+
shorttxidk0 = shorttxidhash.GetUint64(0);
40+
shorttxidk1 = shorttxidhash.GetUint64(1);
41+
}
42+
43+
uint64_t CBlockHeaderAndShortTxIDs::GetShortID(const uint256& txhash) const {
44+
static_assert(SHORTTXIDS_LENGTH == 6, "shorttxids calculation assumes 6-byte shorttxids");
45+
return SipHashUint256(shorttxidk0, shorttxidk1, txhash) & 0xffffffffffffL;
46+
}
47+
48+
49+
50+
ReadStatus PartiallyDownloadedBlock::InitData(const CBlockHeaderAndShortTxIDs& cmpctblock) {
51+
if (cmpctblock.header.IsNull() || (cmpctblock.shorttxids.empty() && cmpctblock.prefilledtxn.empty()))
52+
return READ_STATUS_INVALID;
53+
if (cmpctblock.shorttxids.size() + cmpctblock.prefilledtxn.size() > MAX_BLOCK_SIZE / MIN_TRANSACTION_SIZE)
54+
return READ_STATUS_INVALID;
55+
56+
assert(header.IsNull() && txn_available.empty());
57+
header = cmpctblock.header;
58+
txn_available.resize(cmpctblock.BlockTxCount());
59+
60+
int32_t lastprefilledindex = -1;
61+
for (size_t i = 0; i < cmpctblock.prefilledtxn.size(); i++) {
62+
if (cmpctblock.prefilledtxn[i].tx.IsNull())
63+
return READ_STATUS_INVALID;
64+
65+
lastprefilledindex += cmpctblock.prefilledtxn[i].index + 1; //index is a uint16_t, so cant overflow here
66+
if (lastprefilledindex > std::numeric_limits<uint16_t>::max())
67+
return READ_STATUS_INVALID;
68+
if ((uint32_t)lastprefilledindex > cmpctblock.shorttxids.size() + i) {
69+
// If we are inserting a tx at an index greater than our full list of shorttxids
70+
// plus the number of prefilled txn we've inserted, then we have txn for which we
71+
// have neither a prefilled txn or a shorttxid!
72+
return READ_STATUS_INVALID;
73+
}
74+
txn_available[lastprefilledindex] = std::make_shared<CTransaction>(cmpctblock.prefilledtxn[i].tx);
75+
}
76+
prefilled_count = cmpctblock.prefilledtxn.size();
77+
78+
// Calculate map of txids -> positions and check mempool to see what we have (or dont)
79+
// Because well-formed cmpctblock messages will have a (relatively) uniform distribution
80+
// of short IDs, any highly-uneven distribution of elements can be safely treated as a
81+
// READ_STATUS_FAILED.
82+
std::unordered_map<uint64_t, uint16_t> shorttxids(cmpctblock.shorttxids.size());
83+
uint16_t index_offset = 0;
84+
for (size_t i = 0; i < cmpctblock.shorttxids.size(); i++) {
85+
while (txn_available[i + index_offset])
86+
index_offset++;
87+
shorttxids[cmpctblock.shorttxids[i]] = i + index_offset;
88+
// To determine the chance that the number of entries in a bucket exceeds N,
89+
// we use the fact that the number of elements in a single bucket is
90+
// binomially distributed (with n = the number of shorttxids S, and p =
91+
// 1 / the number of buckets), that in the worst case the number of buckets is
92+
// equal to S (due to std::unordered_map having a default load factor of 1.0),
93+
// and that the chance for any bucket to exceed N elements is at most
94+
// buckets * (the chance that any given bucket is above N elements).
95+
// Thus: P(max_elements_per_bucket > N) <= S * (1 - cdf(binomial(n=S,p=1/S), N)).
96+
// If we assume blocks of up to 16000, allowing 12 elements per bucket should
97+
// only fail once per ~1 million block transfers (per peer and connection).
98+
if (shorttxids.bucket_size(shorttxids.bucket(cmpctblock.shorttxids[i])) > 12)
99+
return READ_STATUS_FAILED;
100+
}
101+
// TODO: in the shortid-collision case, we should instead request both transactions
102+
// which collided. Falling back to full-block-request here is overkill.
103+
if (shorttxids.size() != cmpctblock.shorttxids.size())
104+
return READ_STATUS_FAILED; // Short ID collision
105+
106+
std::vector<bool> have_txn(txn_available.size());
107+
LOCK(pool->cs);
108+
const std::vector<std::pair<uint256, CTxMemPool::txiter> >& vTxHashes = pool->vTxHashes;
109+
for (size_t i = 0; i < vTxHashes.size(); i++) {
110+
uint64_t shortid = cmpctblock.GetShortID(vTxHashes[i].first);
111+
std::unordered_map<uint64_t, uint16_t>::iterator idit = shorttxids.find(shortid);
112+
if (idit != shorttxids.end()) {
113+
if (!have_txn[idit->second]) {
114+
txn_available[idit->second] = vTxHashes[i].second->GetSharedTx();
115+
have_txn[idit->second] = true;
116+
mempool_count++;
117+
} else {
118+
// If we find two mempool txn that match the short id, just request it.
119+
// This should be rare enough that the extra bandwidth doesn't matter,
120+
// but eating a round-trip due to FillBlock failure would be annoying
121+
if (txn_available[idit->second]) {
122+
txn_available[idit->second].reset();
123+
mempool_count--;
124+
}
125+
}
126+
}
127+
// Though ideally we'd continue scanning for the two-txn-match-shortid case,
128+
// the performance win of an early exit here is too good to pass up and worth
129+
// the extra risk.
130+
if (mempool_count == shorttxids.size())
131+
break;
132+
}
133+
134+
LogPrint("cmpctblock", "Initialized PartiallyDownloadedBlock for block %s using a cmpctblock of size %lu\n", cmpctblock.header.GetHash().ToString(), cmpctblock.GetSerializeSize(SER_NETWORK, PROTOCOL_VERSION));
135+
136+
return READ_STATUS_OK;
137+
}
138+
139+
bool PartiallyDownloadedBlock::IsTxAvailable(size_t index) const {
140+
assert(!header.IsNull());
141+
assert(index < txn_available.size());
142+
return txn_available[index] ? true : false;
143+
}
144+
145+
ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector<CTransaction>& vtx_missing) const {
146+
assert(!header.IsNull());
147+
block = header;
148+
block.vtx.resize(txn_available.size());
149+
150+
size_t tx_missing_offset = 0;
151+
for (size_t i = 0; i < txn_available.size(); i++) {
152+
if (!txn_available[i]) {
153+
if (vtx_missing.size() <= tx_missing_offset)
154+
return READ_STATUS_INVALID;
155+
block.vtx[i] = vtx_missing[tx_missing_offset++];
156+
} else
157+
block.vtx[i] = *txn_available[i];
158+
}
159+
if (vtx_missing.size() != tx_missing_offset)
160+
return READ_STATUS_INVALID;
161+
162+
CValidationState state;
163+
if (!CheckBlock(block, state, Params().GetConsensus())) {
164+
// TODO: We really want to just check merkle tree manually here,
165+
// but that is expensive, and CheckBlock caches a block's
166+
// "checked-status" (in the CBlock?). CBlock should be able to
167+
// check its own merkle root and cache that check.
168+
if (state.CorruptionPossible())
169+
return READ_STATUS_FAILED; // Possible Short ID collision
170+
return READ_STATUS_INVALID;
171+
}
172+
173+
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());
174+
if (vtx_missing.size() < 5) {
175+
for(const CTransaction& tx : vtx_missing)
176+
LogPrint("cmpctblock", "Reconstructed block %s required tx %s\n", header.GetHash().ToString(), tx.GetHash().ToString());
177+
}
178+
179+
return READ_STATUS_OK;
180+
}

0 commit comments

Comments
 (0)