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_BASE_SIZE / MIN_TRANSACTION_BASE_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; }
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_BASE_SIZE / MIN_TRANSACTION_BASE_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) { return READ_STATUS_INVALID; } txn_available[lastprefilledindex] = std::make_shared<CTransaction>(cmpctblock.prefilledtxn[i].tx); } prefilled_count = cmpctblock.prefilledtxn.size(); 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; if (shorttxids.bucket_size(shorttxids.bucket(cmpctblock.shorttxids[i])) > 12) return READ_STATUS_FAILED; } 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 (txn_available[idit->second]) { txn_available[idit->second].reset(); mempool_count--; } } } 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; }