Skip to content

Commit

Permalink
Handle bisection sketch
Browse files Browse the repository at this point in the history
If a peer responded to our request with a bisection sketch,
attempt to decode it to find missing transactions by combining
it with the initial sketch.
If success, share/request missing transactions.
If failure, send all the transactions we had for the peer.
In case of a partial failure, apply this logic accordingly to the parts.
  • Loading branch information
naumenkogs committed Nov 19, 2020
1 parent 152e9bc commit b5c92a4
Showing 1 changed file with 149 additions and 23 deletions.
172 changes: 149 additions & 23 deletions src/net_processing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,12 @@ struct Peer {
// When reconciliation initialized by us is done, update local q for future reconciliations.
if (action == LocalQAction::Q_RECOMPUTE) {
assert(m_outgoing_recon != ReconPhase::NONE);
uint8_t local_set_size = m_local_set.size();
uint8_t local_set_size;
if (m_outgoing_recon == ReconPhase::BISEC_REQUESTED) {
local_set_size = m_local_set_snapshot.size();
} else {
local_set_size = m_local_set.size();
}
uint8_t remote_set_size = local_set_size + actual_local_missing - actual_remote_missing;
uint8_t set_size_diff = std::abs(local_set_size - remote_set_size);
uint8_t min_size = std::min(local_set_size, remote_set_size);
Expand Down Expand Up @@ -4273,12 +4278,13 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat
return;
}

// Received a response to the reconciliation request.
// Received a response to the reconciliation request (initial request or request for bisection if initial failed).
// May leak tx-related privacy if we announce local transactions right away, if a peer is strategic about sending
// sketches to us via different connections (requires attacker to occupy multiple outgoing connections).
if (msg_type == NetMsgType::SKETCH) {
if (peer->m_recon_state == nullptr) return;
if (peer->m_recon_state->m_outgoing_recon != Peer::ReconState::ReconPhase::INIT_REQUESTED) return;
if (peer->m_recon_state->m_outgoing_recon != Peer::ReconState::ReconPhase::INIT_REQUESTED &&
peer->m_recon_state->m_outgoing_recon != Peer::ReconState::ReconPhase::BISEC_REQUESTED) return;

std::vector<uint8_t> skdata;
vRecv >> skdata;
Expand All @@ -4294,7 +4300,11 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat

minisketch* working_sketch; // Contains sketch of the set difference (full or low chunk)
minisketch* local_sketch; // Stores local sketch (full or low chunk)
local_sketch = peer->m_recon_state->ComputeSketch(Peer::ReconState::BisectionChunk::BISECTION_NONE, remote_sketch_capacity, false);
if (peer->m_recon_state->m_outgoing_recon == Peer::ReconState::ReconPhase::INIT_REQUESTED) {
local_sketch = peer->m_recon_state->ComputeSketch(Peer::ReconState::BisectionChunk::BISECTION_NONE, remote_sketch_capacity, false);
} else {
local_sketch = peer->m_recon_state->ComputeSketch(Peer::ReconState::BisectionChunk::BISECTION_LOW, remote_sketch_capacity, false);
}
if (local_sketch != nullptr) {
working_sketch = minisketch_clone(local_sketch);
minisketch_merge(working_sketch, remote_sketch);
Expand All @@ -4311,29 +4321,145 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat
std::vector<uint32_t> local_missing;
if (decoded) {
// Reconciliation over the current working chunk succeeded
// Initial reconciliation succeeded
// Send/request transactions which found to be missing
LogPrint(BCLog::NET, "Outgoing reconciliation with peer=%i succeeded without bisection\n", pfrom.GetId());
std::vector<uint256> remote_missing = peer->m_recon_state->GetRelevantIDsFromShortIDs(differences, num_differences, local_missing);
AnnounceTxs(remote_missing, &pfrom, msgMaker, &m_connman);
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::RECONCILDIFF, uint8_t(RECON_SUCCESS), local_missing));
peer->m_recon_state->FinalizeReconciliation(true, Peer::ReconState::LocalQAction::Q_RECOMPUTE, local_missing.size(), remote_missing.size());
if (peer->m_recon_state->m_outgoing_recon == Peer::ReconState::ReconPhase::INIT_REQUESTED) {
// Initial reconciliation succeeded
// Send/request transactions which found to be missing
LogPrint(BCLog::NET, "Outgoing reconciliation with peer=%I succeeded without bisection\n", pfrom.GetId());
std::vector<uint256> remote_missing = peer->m_recon_state->GetRelevantIDsFromShortIDs(differences, num_differences, local_missing);
AnnounceTxs(remote_missing, &pfrom, msgMaker, &m_connman);
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::RECONCILDIFF, uint8_t(RECON_SUCCESS), local_missing));
peer->m_recon_state->FinalizeReconciliation(true, Peer::ReconState::LocalQAction::Q_RECOMPUTE, local_missing.size(), remote_missing.size());
} else {
// Reconciliation over the low chunk succeeded.
// Attempt to reconcile over the high chunks.

LogPrint(BCLog::NET, "Outgoing reconciliation with peer=%I succeeded after bisection (low chunk)\n", pfrom.GetId());
// Compute missing transactions in the low chunk.
std::vector<uint256> remote_missing = peer->m_recon_state->GetRelevantIDsFromShortIDs(differences, num_differences, local_missing);

// Attempt to find the difference over the high chunk, which can be derived from the low.
assert(peer->m_recon_state->m_remote_sketch_snapshot != nullptr); // Otherwise reconciliation should have been cancelled.
minisketch* working_sketch_high = minisketch_clone(peer->m_recon_state->m_remote_sketch_snapshot);
minisketch_merge(working_sketch_high, remote_sketch); // Working sketch now stores high chunk of the remote set.

if (peer->m_recon_state->m_local_sketch_snapshot != nullptr) {
// Computes sketch of the local high chunk.
minisketch* local_sketch_high = minisketch_clone(peer->m_recon_state->m_local_sketch_snapshot);
if (local_sketch != nullptr) {
minisketch_merge(local_sketch_high, local_sketch);
}

// Combine high chunks to compute the high chunk diff.
minisketch_merge(working_sketch_high, local_sketch_high);
}

// Attempt to decode the set difference (high chunk).
uint64_t differences_high_chunk[RECONCIL_MAX_DIFF];
num_differences = minisketch_decode(working_sketch_high, RECONCIL_MAX_DIFF, differences_high_chunk);
max_possible_differences = minisketch_compute_max_elements(RECON_FIELD_SIZE, remote_sketch_capacity, RECON_FALSE_POSITIVE_COEF);
bool high_chunk_decoded = (0 <= num_differences) && (num_differences <= ssize_t(max_possible_differences));

if (high_chunk_decoded) {
// High chunk decoded and low chunk decoded. Bisection fully succeeded.
// Request and send only transactions which were identified to be missing.

LogPrint(BCLog::NET, "Outgoing reconciliation with peer=%I succeeded after bisection (high chunk)\n", pfrom.GetId());
std::vector<uint32_t> local_missing_high;
std::vector<uint256> remote_missing_high = peer->m_recon_state->GetRelevantIDsFromShortIDs(differences_high_chunk, num_differences, local_missing_high);
local_missing.insert(local_missing.end(), local_missing_high.begin(), local_missing_high.end());
remote_missing.insert(remote_missing.end(), remote_missing_high.begin(), remote_missing_high.end());
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::RECONCILDIFF, uint8_t(RECON_SUCCESS), local_missing));
AnnounceTxs(remote_missing, &pfrom, msgMaker, &m_connman);
peer->m_recon_state->FinalizeReconciliation(false, Peer::ReconState::LocalQAction::Q_RECOMPUTE, local_missing.size(), remote_missing.size());
} else {
// High chunk failed to decode and Low chunk decoded. Bisection partially succeeded.
// From the high chunk, send all transactions. From the low chunk, send and request those identified to be missing.
// The peer will announce their transactions from the high chunk to us.

m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::RECONCILDIFF, uint8_t(RECON_HIGH_FAILED), local_missing));
for (uint256 wtxid : peer->m_recon_state->m_local_set_snapshot) {
uint32_t short_txid = peer->m_recon_state->ComputeShortID(wtxid);
if (short_txid > BISECTION_MEDIAN) // use only high chunk, low chunk transactions are already in the vector
remote_missing.push_back(wtxid);
}
AnnounceTxs(remote_missing, &pfrom, msgMaker, &m_connman);
LogPrint(BCLog::NET, "Outgoing reconciliation with peer=%I failed after bisection (high chunk)\n", pfrom.GetId());
peer->m_recon_state->FinalizeReconciliation(false, Peer::ReconState::LocalQAction::Q_INCREASE, 0, 0);
}
}
return;
} else {
// Reconciliation over the current working chunk failed.
// Initial reconciliation failed.
// Store the received sketch and the local sketch, request bisection.

assert(remote_sketch != nullptr);
// Prepare to request bisection.
peer->m_recon_state->m_remote_sketch_snapshot = minisketch_clone(remote_sketch);
if (local_sketch != nullptr) {
peer->m_recon_state->m_local_sketch_snapshot = minisketch_clone(local_sketch);
if (peer->m_recon_state->m_outgoing_recon == Peer::ReconState::ReconPhase::INIT_REQUESTED) {
// Initial reconciliation failed.
// Store the received sketch and the local sketch, request bisection.

assert(remote_sketch != nullptr);
// Prepare to request bisection.
peer->m_recon_state->m_remote_sketch_snapshot = minisketch_clone(remote_sketch);
if (local_sketch != nullptr) {
peer->m_recon_state->m_local_sketch_snapshot = minisketch_clone(local_sketch);
}
peer->m_recon_state->m_local_set_snapshot = peer->m_recon_state->m_local_set;
peer->m_recon_state->m_local_set.clear();
LogPrint(BCLog::NET, "Outgoing reconciliation with peer=%I initially failed, requesting bisection\n", pfrom.GetId());
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::REQBISEC));
peer->m_recon_state->m_outgoing_recon = Peer::ReconState::ReconPhase::BISEC_REQUESTED;
} else {
// Reconciliation over the low chunk failed during bisection.
// Announce all local transactions from the low chunk.
// Attempt to reconstruct the difference from locally computed high chunk.

LogPrint(BCLog::NET, "Outgoing reconciliation with peer=%I failed after bisection (low chunk)\n", pfrom.GetId());
assert(peer->m_recon_state->m_remote_sketch_snapshot != nullptr); // Otherwise should have been cancelled
minisketch* working_sketch_high = minisketch_clone(peer->m_recon_state->m_remote_sketch_snapshot);
minisketch_merge(working_sketch_high, remote_sketch); // Sketch of the remote high chunk

// Empty snapshot implies there were no transactions locally
if (peer->m_recon_state->m_local_sketch_snapshot != nullptr) {
minisketch* local_sketch_high = minisketch_clone(peer->m_recon_state->m_local_sketch_snapshot);
if (local_sketch != nullptr) {
minisketch_merge(local_sketch_high, local_sketch); // Sketch of the local high chunk
}
minisketch_merge(working_sketch_high, local_sketch_high); // Sketch of the difference in high chunks
}

// Attempt to find difference in high chunks
uint64_t differences_high_chunk[RECONCIL_MAX_DIFF];
num_differences = minisketch_decode(working_sketch_high, RECONCIL_MAX_DIFF, differences_high_chunk);
max_possible_differences = minisketch_compute_max_elements(RECON_FIELD_SIZE, remote_sketch_capacity, RECON_FALSE_POSITIVE_COEF);
bool high_chunk_decoded = (0 <= num_differences) && (num_differences <= ssize_t(max_possible_differences));

std::vector<uint256> remote_missing;
if (high_chunk_decoded) {
// Reconciliation over the high chunks succeeded.
// Send/request only missing transactions from the high chunk, and send all from the low (because low failed).
// Can’t request missing low because we have no idea which those are, but peer will send them after finding out
// that low chunk decoding failed.

LogPrint(BCLog::NET, "Outgoing reconciliation with peer=%I succeeded after bisection (high chunk)\n", pfrom.GetId());
std::vector<uint32_t> local_missing_high;
remote_missing = peer->m_recon_state->GetRelevantIDsFromShortIDs(differences_high_chunk, num_differences, local_missing_high);
for (uint256 local_wtxid : peer->m_recon_state->m_local_set_snapshot) {
uint32_t short_id = peer->m_recon_state->ComputeShortID(local_wtxid);
if (short_id <= BISECTION_MEDIAN) {
remote_missing.push_back(local_wtxid);
}
}
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::RECONCILDIFF, uint8_t(RECON_LOW_FAILED), local_missing_high));
peer->m_recon_state->FinalizeReconciliation(false, Peer::ReconState::LocalQAction::Q_INCREASE, 0, 0);
} else {
// Reconciliation over the high chunks also failed.
// Announce all local transactions.
// All remote transactions will be announced by peer due to the reconciliation failure flag.

LogPrint(BCLog::NET, "Outgoing reconciliation with peer=%I failed after bisection (high chunk)\n", pfrom.GetId());
remote_missing.insert(remote_missing.end(), peer->m_recon_state->m_local_set_snapshot.begin(), peer->m_recon_state->m_local_set_snapshot.end());
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::RECONCILDIFF, uint8_t(RECON_FAILED), std::vector<uint32_t>()));
peer->m_recon_state->FinalizeReconciliation(false, Peer::ReconState::LocalQAction::Q_SET_DEFAULT, 0, 0);
}
AnnounceTxs(remote_missing, &pfrom, msgMaker, &m_connman);
}
peer->m_recon_state->m_local_set.clear();
LogPrint(BCLog::NET, "Outgoing reconciliation with peer=%i initially failed, requesting bisection\n", pfrom.GetId());
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::REQBISEC));
peer->m_recon_state->m_outgoing_recon = Peer::ReconState::ReconPhase::BISEC_REQUESTED;
}
return;
}
Expand Down

0 comments on commit b5c92a4

Please sign in to comment.