blockencodings.cpp
1 // Copyright (c) 2016-present 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 <chainparams.h> 7 #include <common/system.h> 8 #include <consensus/consensus.h> 9 #include <consensus/validation.h> 10 #include <crypto/sha256.h> 11 #include <crypto/siphash.h> 12 #include <logging.h> 13 #include <random.h> 14 #include <streams.h> 15 #include <txmempool.h> 16 #include <validation.h> 17 18 #include <unordered_map> 19 20 CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block, uint64_t nonce) 21 : nonce(nonce), 22 shorttxids(block.vtx.size() - 1), 23 prefilledtxn(1), 24 header(block) 25 { 26 FillShortTxIDSelector(); 27 // TODO: Use our mempool prior to block acceptance to predictively fill more than just the coinbase 28 prefilledtxn[0] = {0, block.vtx[0]}; 29 for (size_t i = 1; i < block.vtx.size(); i++) { 30 const CTransaction& tx = *block.vtx[i]; 31 shorttxids[i - 1] = GetShortID(tx.GetWitnessHash()); 32 } 33 } 34 35 void CBlockHeaderAndShortTxIDs::FillShortTxIDSelector() const 36 { 37 DataStream stream{}; 38 stream << header << nonce; 39 CSHA256 hasher; 40 hasher.Write((unsigned char*)&(*stream.begin()), stream.end() - stream.begin()); 41 uint256 shorttxidhash; 42 hasher.Finalize(shorttxidhash.begin()); 43 m_hasher.emplace(shorttxidhash.GetUint64(0), shorttxidhash.GetUint64(1)); 44 } 45 46 uint64_t CBlockHeaderAndShortTxIDs::GetShortID(const Wtxid& wtxid) const 47 { 48 static_assert(SHORTTXIDS_LENGTH == 6, "shorttxids calculation assumes 6-byte shorttxids"); 49 return (*Assert(m_hasher))(wtxid.ToUint256()) & 0xffffffffffffL; 50 } 51 52 /* Reconstructing a compact block is in the hot-path for block relay, 53 * so we want to do it as quickly as possible. Because this often 54 * involves iterating over the entire mempool, we put all the data we 55 * need (ie the wtxid and a reference to the actual transaction data) 56 * in a vector and iterate over the vector directly. This allows optimal 57 * CPU caching behaviour, at a cost of only 40 bytes per transaction. 58 */ 59 ReadStatus PartiallyDownloadedBlock::InitData(const CBlockHeaderAndShortTxIDs& cmpctblock, const std::vector<std::pair<Wtxid, CTransactionRef>>& extra_txn) 60 { 61 LogDebug(BCLog::CMPCTBLOCK, "Initializing PartiallyDownloadedBlock for block %s using a cmpctblock of %u bytes\n", cmpctblock.header.GetHash().ToString(), GetSerializeSize(cmpctblock)); 62 if (cmpctblock.header.IsNull() || (cmpctblock.shorttxids.empty() && cmpctblock.prefilledtxn.empty())) 63 return READ_STATUS_INVALID; 64 if (cmpctblock.shorttxids.size() + cmpctblock.prefilledtxn.size() > MAX_BLOCK_WEIGHT / MIN_SERIALIZABLE_TRANSACTION_WEIGHT) 65 return READ_STATUS_INVALID; 66 67 if (!header.IsNull() || !txn_available.empty()) return READ_STATUS_INVALID; 68 69 header = cmpctblock.header; 70 txn_available.resize(cmpctblock.BlockTxCount()); 71 72 int32_t lastprefilledindex = -1; 73 for (size_t i = 0; i < cmpctblock.prefilledtxn.size(); i++) { 74 if (cmpctblock.prefilledtxn[i].tx->IsNull()) 75 return READ_STATUS_INVALID; 76 77 lastprefilledindex += cmpctblock.prefilledtxn[i].index + 1; //index is a uint16_t, so can't overflow here 78 if (lastprefilledindex > std::numeric_limits<uint16_t>::max()) 79 return READ_STATUS_INVALID; 80 if ((uint32_t)lastprefilledindex > cmpctblock.shorttxids.size() + i) { 81 // If we are inserting a tx at an index greater than our full list of shorttxids 82 // plus the number of prefilled txn we've inserted, then we have txn for which we 83 // have neither a prefilled txn or a shorttxid! 84 return READ_STATUS_INVALID; 85 } 86 txn_available[lastprefilledindex] = cmpctblock.prefilledtxn[i].tx; 87 } 88 prefilled_count = cmpctblock.prefilledtxn.size(); 89 90 // Calculate map of txids -> positions and check mempool to see what we have (or don't) 91 // Because well-formed cmpctblock messages will have a (relatively) uniform distribution 92 // of short IDs, any highly-uneven distribution of elements can be safely treated as a 93 // READ_STATUS_FAILED. 94 std::unordered_map<uint64_t, uint16_t> shorttxids(cmpctblock.shorttxids.size()); 95 uint16_t index_offset = 0; 96 for (size_t i = 0; i < cmpctblock.shorttxids.size(); i++) { 97 while (txn_available[i + index_offset]) 98 index_offset++; 99 shorttxids[cmpctblock.shorttxids[i]] = i + index_offset; 100 // To determine the chance that the number of entries in a bucket exceeds N, 101 // we use the fact that the number of elements in a single bucket is 102 // binomially distributed (with n = the number of shorttxids S, and p = 103 // 1 / the number of buckets), that in the worst case the number of buckets is 104 // equal to S (due to std::unordered_map having a default load factor of 1.0), 105 // and that the chance for any bucket to exceed N elements is at most 106 // buckets * (the chance that any given bucket is above N elements). 107 // Thus: P(max_elements_per_bucket > N) <= S * (1 - cdf(binomial(n=S,p=1/S), N)). 108 // If we assume blocks of up to 16000, allowing 12 elements per bucket should 109 // only fail once per ~1 million block transfers (per peer and connection). 110 if (shorttxids.bucket_size(shorttxids.bucket(cmpctblock.shorttxids[i])) > 12) 111 return READ_STATUS_FAILED; 112 } 113 // TODO: in the shortid-collision case, we should instead request both transactions 114 // which collided. Falling back to full-block-request here is overkill. 115 if (shorttxids.size() != cmpctblock.shorttxids.size()) 116 return READ_STATUS_FAILED; // Short ID collision 117 118 std::vector<bool> have_txn(txn_available.size()); 119 { 120 LOCK(pool->cs); 121 for (const auto& [wtxid, txit] : pool->txns_randomized) { 122 uint64_t shortid = cmpctblock.GetShortID(wtxid); 123 std::unordered_map<uint64_t, uint16_t>::iterator idit = shorttxids.find(shortid); 124 if (idit != shorttxids.end()) { 125 if (!have_txn[idit->second]) { 126 txn_available[idit->second] = txit->GetSharedTx(); 127 have_txn[idit->second] = true; 128 mempool_count++; 129 } else { 130 // If we find two mempool txn that match the short id, just request it. 131 // This should be rare enough that the extra bandwidth doesn't matter, 132 // but eating a round-trip due to FillBlock failure would be annoying 133 if (txn_available[idit->second]) { 134 txn_available[idit->second].reset(); 135 mempool_count--; 136 } 137 } 138 } 139 // Though ideally we'd continue scanning for the two-txn-match-shortid case, 140 // the performance win of an early exit here is too good to pass up and worth 141 // the extra risk. 142 if (mempool_count == shorttxids.size()) 143 break; 144 } 145 } 146 147 for (size_t i = 0; i < extra_txn.size(); i++) { 148 uint64_t shortid = cmpctblock.GetShortID(extra_txn[i].first); 149 std::unordered_map<uint64_t, uint16_t>::iterator idit = shorttxids.find(shortid); 150 if (idit != shorttxids.end()) { 151 if (!have_txn[idit->second]) { 152 txn_available[idit->second] = extra_txn[i].second; 153 have_txn[idit->second] = true; 154 mempool_count++; 155 extra_count++; 156 } else { 157 // If we find two mempool/extra txn that match the short id, just 158 // request it. 159 // This should be rare enough that the extra bandwidth doesn't matter, 160 // but eating a round-trip due to FillBlock failure would be annoying 161 // Note that we don't want duplication between extra_txn and mempool to 162 // trigger this case, so we compare witness hashes first 163 if (txn_available[idit->second] && 164 txn_available[idit->second]->GetWitnessHash() != extra_txn[i].second->GetWitnessHash()) { 165 txn_available[idit->second].reset(); 166 mempool_count--; 167 extra_count--; 168 } 169 } 170 } 171 // Though ideally we'd continue scanning for the two-txn-match-shortid case, 172 // the performance win of an early exit here is too good to pass up and worth 173 // the extra risk. 174 if (mempool_count == shorttxids.size()) 175 break; 176 } 177 178 LogDebug(BCLog::CMPCTBLOCK, "Initialized PartiallyDownloadedBlock for block %s using a cmpctblock of %u bytes\n", cmpctblock.header.GetHash().ToString(), GetSerializeSize(cmpctblock)); 179 180 return READ_STATUS_OK; 181 } 182 183 bool PartiallyDownloadedBlock::IsTxAvailable(size_t index) const 184 { 185 if (header.IsNull()) return false; 186 187 assert(index < txn_available.size()); 188 return txn_available[index] != nullptr; 189 } 190 191 ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector<CTransactionRef>& vtx_missing, bool segwit_active) 192 { 193 if (header.IsNull()) return READ_STATUS_INVALID; 194 195 block = header; 196 block.vtx.resize(txn_available.size()); 197 198 size_t tx_missing_offset = 0; 199 for (size_t i = 0; i < txn_available.size(); i++) { 200 if (!txn_available[i]) { 201 if (tx_missing_offset >= vtx_missing.size()) { 202 return READ_STATUS_INVALID; 203 } 204 block.vtx[i] = vtx_missing[tx_missing_offset++]; 205 } else { 206 block.vtx[i] = std::move(txn_available[i]); 207 } 208 } 209 210 // Make sure we can't call FillBlock again. 211 header.SetNull(); 212 txn_available.clear(); 213 214 if (vtx_missing.size() != tx_missing_offset) { 215 return READ_STATUS_INVALID; 216 } 217 218 // Check for possible mutations early now that we have a seemingly good block 219 IsBlockMutatedFn check_mutated{m_check_block_mutated_mock ? m_check_block_mutated_mock : IsBlockMutated}; 220 if (check_mutated(/*block=*/block, /*check_witness_root=*/segwit_active)) { 221 return READ_STATUS_FAILED; // Possible Short ID collision 222 } 223 224 if (LogAcceptCategory(BCLog::CMPCTBLOCK, BCLog::Level::Debug)) { 225 const uint256 hash{block.GetHash()}; 226 uint32_t tx_missing_size{0}; 227 for (const auto& tx : vtx_missing) tx_missing_size += tx->ComputeTotalSize(); 228 LogDebug(BCLog::CMPCTBLOCK, "Successfully reconstructed block %s with %u txn prefilled, %u txn from mempool (incl at least %u from extra pool) and %u txn (%u bytes) requested\n", hash.ToString(), prefilled_count, mempool_count, extra_count, vtx_missing.size(), tx_missing_size); 229 if (vtx_missing.size() < 5) { 230 for (const auto& tx : vtx_missing) { 231 LogDebug(BCLog::CMPCTBLOCK, "Reconstructed block %s required tx %s\n", hash.ToString(), tx->GetHash().ToString()); 232 } 233 } 234 } 235 236 return READ_STATUS_OK; 237 }