Skip to content

Commit

Permalink
fix: store multiple doulbe spend attempts
Browse files Browse the repository at this point in the history
  • Loading branch information
RolandSherwin committed Jun 19, 2024
1 parent 58defbc commit a79e8e9
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 86 deletions.
80 changes: 43 additions & 37 deletions sn_client/src/audit/dag_crawling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const SPENDS_PROCESSING_BUFFER_SIZE: usize = 4096;

enum InternalGetNetworkSpend {
Spend(Box<SignedSpend>),
DoubleSpend(Box<SignedSpend>, Box<SignedSpend>),
DoubleSpend(Vec<SignedSpend>),
NotFound,
Error(Error),
}
Expand All @@ -33,17 +33,18 @@ impl Client {
pub async fn new_dag_with_genesis_only(&self) -> WalletResult<SpendDag> {
let genesis_addr = SpendAddress::from_unique_pubkey(&GENESIS_SPEND_UNIQUE_KEY);
let mut dag = SpendDag::new(genesis_addr);
let genesis_spend = match self.get_spend_from_network(genesis_addr).await {
Ok(s) => s,
Err(Error::Network(NetworkError::DoubleSpendAttempt(spend1, spend2))) => {
let addr = spend1.address();
println!("Double spend detected at Genesis: {addr:?}");
dag.insert(genesis_addr, *spend2);
*spend1
match self.get_spend_from_network(genesis_addr).await {
Ok(spend) => {
dag.insert(genesis_addr, spend);
}
Err(Error::Network(NetworkError::DoubleSpendAttempt(spends))) => {
println!("Double spend detected at Genesis: {genesis_addr:?}");
for spend in spends.into_iter() {
dag.insert(genesis_addr, spend);
}
}
Err(e) => return Err(WalletError::FailedToGetSpend(e.to_string())),
};
dag.insert(genesis_addr, genesis_spend);

Ok(dag)
}
Expand Down Expand Up @@ -163,7 +164,7 @@ impl Client {
.map_err(|e| WalletError::SpendProcessing(e.to_string()));
addrs_to_get.extend(for_further_track);
}
InternalGetNetworkSpend::DoubleSpend(_s1, _s2) => {
InternalGetNetworkSpend::DoubleSpend(_spends) => {
warn!("Detected double spend regarding {address:?}");
}
InternalGetNetworkSpend::NotFound => {
Expand Down Expand Up @@ -193,14 +194,27 @@ impl Client {
let mut utxos = BTreeSet::new();

// get first spend
let first_spend = match self.crawl_spend(spend_addr).await {
InternalGetNetworkSpend::Spend(s) => *s,
InternalGetNetworkSpend::DoubleSpend(s1, s2) => {
let mut txs_to_follow = match self.crawl_spend(spend_addr).await {
InternalGetNetworkSpend::Spend(spend) => {
let spend = *spend;
let txs = BTreeSet::from_iter([spend.spend.spent_tx.clone()]);

spend_processing
.send(*s2)
.send(spend)
.await
.map_err(|e| WalletError::SpendProcessing(e.to_string()))?;
*s1
txs
}
InternalGetNetworkSpend::DoubleSpend(spends) => {
let mut txs = BTreeSet::new();
for spend in spends.into_iter() {
txs.insert(spend.spend.spent_tx.clone());
spend_processing
.send(spend)
.await
.map_err(|e| WalletError::SpendProcessing(e.to_string()))?;
}
txs
}
InternalGetNetworkSpend::NotFound => {
// the cashnote was not spent yet, so it's an UTXO
Expand All @@ -212,13 +226,8 @@ impl Client {
return Err(WalletError::FailedToGetSpend(e.to_string()));
}
};
spend_processing
.send(first_spend.clone())
.await
.map_err(|e| WalletError::SpendProcessing(e.to_string()))?;

// use iteration instead of recursion to avoid stack overflow
let mut txs_to_follow = BTreeSet::from_iter([first_spend.spend.spent_tx]);
let mut known_tx = BTreeSet::new();
let mut gen: u32 = 0;
let start = std::time::Instant::now();
Expand Down Expand Up @@ -260,18 +269,15 @@ impl Client {
.await
.map_err(|e| WalletError::SpendProcessing(e.to_string()))?;
}
InternalGetNetworkSpend::DoubleSpend(s1, s2) => {
info!("Fetched double spend at {addr:?} from network, following both...");
next_gen_tx.insert(s1.spend.spent_tx.clone());
next_gen_tx.insert(s2.spend.spent_tx.clone());
spend_processing
.send(*s1.clone())
.await
.map_err(|e| WalletError::SpendProcessing(e.to_string()))?;
spend_processing
.send(*s2.clone())
.await
.map_err(|e| WalletError::SpendProcessing(e.to_string()))?;
InternalGetNetworkSpend::DoubleSpend(spends) => {
info!("Fetched double spend(s) of len {} at {addr:?} from network, following all of them.", spends.len());
for s in spends.into_iter() {
next_gen_tx.insert(s.spend.spent_tx.clone());
spend_processing
.send(s.clone())
.await
.map_err(|e| WalletError::SpendProcessing(e.to_string()))?;
}
}
InternalGetNetworkSpend::NotFound => {
info!("Reached UTXO at {addr:?}");
Expand Down Expand Up @@ -360,8 +366,8 @@ impl Client {
InternalGetNetworkSpend::Spend(s) => {
spends.insert(*s);
}
InternalGetNetworkSpend::DoubleSpend(s1, s2) => {
spends.extend([*s1, *s2]);
InternalGetNetworkSpend::DoubleSpend(s) => {
spends.extend(s.into_iter());
}
InternalGetNetworkSpend::NotFound => {
return Err(WalletError::FailedToGetSpend(format!(
Expand Down Expand Up @@ -498,9 +504,9 @@ impl Client {
debug!("DAG crawling: spend at {spend_addr:?} not found on the network");
InternalGetNetworkSpend::NotFound
}
Err(Error::Network(NetworkError::DoubleSpendAttempt(s1, s2))) => {
debug!("DAG crawling: got a double spend at {spend_addr:?} on the network");
InternalGetNetworkSpend::DoubleSpend(s1, s2)
Err(Error::Network(NetworkError::DoubleSpendAttempt(spends))) => {
debug!("DAG crawling: got a double spend(s) of len {} at {spend_addr:?} on the network", spends.len());
InternalGetNetworkSpend::DoubleSpend(spends)
}
Err(e) => {
debug!(
Expand Down
4 changes: 2 additions & 2 deletions sn_networking/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ pub enum NetworkError {
// ---------- Spend Errors
#[error("Spend not found: {0:?}")]
NoSpendFoundInsideRecord(SpendAddress),
#[error("A double spend was detected. Two diverging signed spends: {0:?}, {1:?}")]
DoubleSpendAttempt(Box<SignedSpend>, Box<SignedSpend>),
#[error("Double spend(s) was detected. The signed spends are: {0:?}")]
DoubleSpendAttempt(Vec<SignedSpend>),

// ---------- Store Error
#[error("No Store Cost Responses")]
Expand Down
3 changes: 2 additions & 1 deletion sn_networking/src/event/kad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use crate::{
driver::PendingGetClosestType, get_quorum_value, get_raw_signed_spends_from_record,
GetRecordCfg, GetRecordError, NetworkError, Result, SwarmDriver, CLOSE_GROUP_SIZE,
MAX_DOUBLE_SPEND_ATTEMPTS_PER_RECORD,
};
use itertools::Itertools;
use libp2p::kad::{
Expand Down Expand Up @@ -414,7 +415,7 @@ impl SwarmDriver {
info!("For record {pretty_key:?} task {query_id:?}, found split record for a spend, accumulated and sending them as a single record");
let accumulated_spends = accumulated_spends
.into_iter()
.take(2)
.take(MAX_DOUBLE_SPEND_ATTEMPTS_PER_RECORD)
.collect::<Vec<SignedSpend>>();

let bytes = try_serialize_record(&accumulated_spends, RecordKind::Spend)?;
Expand Down
3 changes: 3 additions & 0 deletions sn_networking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ pub const CLOSE_GROUP_SIZE: usize = 5;
/// that a replication of the record shall be sent/accepted to/by the peer.
pub const REPLICATION_PEERS_COUNT: usize = CLOSE_GROUP_SIZE + 2;

/// The maximum number of double spend attempts to store inside a record.
pub const MAX_DOUBLE_SPEND_ATTEMPTS_PER_RECORD: usize = 50;

/// Majority of a given group (i.e. > 1/2).
#[inline]
pub const fn close_group_majority() -> usize {
Expand Down
21 changes: 11 additions & 10 deletions sn_networking/src/transfers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,21 +236,22 @@ pub fn get_signed_spend_from_record(
address: &SpendAddress,
record: &Record,
) -> Result<SignedSpend> {
match get_raw_signed_spends_from_record(record)?.as_slice() {
[one, two, ..] => {
warn!("Found double spend for {address:?}");
Err(NetworkError::DoubleSpendAttempt(
Box::new(one.to_owned()),
Box::new(two.to_owned()),
))
let spends = get_raw_signed_spends_from_record(record)?;
match spends.as_slice() {
[] => {
error!("Found no spend for {address:?}");
Err(NetworkError::NoSpendFoundInsideRecord(*address))
}
[one] => {
trace!("Spend get for address: {address:?} successful");
Ok(one.clone())
}
_ => {
trace!("Found no spend for {address:?}");
Err(NetworkError::NoSpendFoundInsideRecord(*address))
_double_spends => {
warn!(
"Found double spend(s) of len {} for {address:?}",
spends.len()
);
Err(NetworkError::DoubleSpendAttempt(spends))
}
}
}
65 changes: 29 additions & 36 deletions sn_node/src/put_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@

use crate::{node::Node, quote::verify_quote_for_storecost, Error, Marker, Result};
use libp2p::kad::{Record, RecordKey};
use sn_networking::{get_raw_signed_spends_from_record, GetRecordError, NetworkError};
use sn_networking::{
get_raw_signed_spends_from_record, GetRecordError, NetworkError,
MAX_DOUBLE_SPEND_ATTEMPTS_PER_RECORD,
};
use sn_protocol::{
messages::CmdOk,
storage::{
Expand Down Expand Up @@ -363,7 +366,7 @@ impl Node {

// validate the signed spends against the network and the local knowledge
debug!("Validating spends for {pretty_key:?} with unique key: {unique_pubkey:?}");
let (spend1, maybe_spend2) = match self
let validated_spends = match self
.signed_spends_to_keep(spends_for_key.clone(), *unique_pubkey)
.await
{
Expand All @@ -373,12 +376,11 @@ impl Node {
return Err(e);
}
};
let validated_spends = maybe_spend2
.clone()
.map(|spend2| vec![spend1.clone(), spend2])
.unwrap_or_else(|| vec![spend1.clone()]);
let len = validated_spends.len();
debug!("Got {len} validated spends with key: {unique_pubkey:?} at {pretty_key:?}");

debug!(
"Got {} validated spends with key: {unique_pubkey:?} at {pretty_key:?}",
validated_spends.len()
);

// store the record into the local storage
let record = Record {
Expand All @@ -393,12 +395,9 @@ impl Node {
);

// report double spends
if let Some(spend2) = maybe_spend2 {
warn!("Got a double spend for the Spend PUT with unique_pubkey {unique_pubkey}");
return Err(NetworkError::DoubleSpendAttempt(
Box::new(spend1),
Box::new(spend2),
))?;
if validated_spends.len() > 1 {
warn!("Got double spend(s) of len {} for the Spend PUT with unique_pubkey {unique_pubkey}", validated_spends.len());
return Err(NetworkError::DoubleSpendAttempt(validated_spends))?;
}

self.record_metrics(Marker::ValidSpendRecordPutFromNetwork(&pretty_key));
Expand Down Expand Up @@ -627,7 +626,7 @@ impl Node {
&self,
signed_spends: Vec<SignedSpend>,
unique_pubkey: UniquePubkey,
) -> Result<(SignedSpend, Option<SignedSpend>)> {
) -> Result<Vec<SignedSpend>> {
let spend_addr = SpendAddress::from_unique_pubkey(&unique_pubkey);
debug!(
"Validating before storing spend at {spend_addr:?} with unique key: {unique_pubkey}"
Expand Down Expand Up @@ -687,28 +686,22 @@ impl Node {
}
}

// return the single unique spend to store
match all_verified_spends
let verified_spends = all_verified_spends
.into_iter()
.collect::<Vec<SignedSpend>>()
.as_slice()
{
[a] => {
debug!("Got a single valid spend for {unique_pubkey:?}");
Ok((a.to_owned(), None))
}
[a, b] => {
warn!("Got a double spend for {unique_pubkey:?}");
Ok((a.to_owned(), Some(b.to_owned())))
}
_ => {
debug!(
"No valid spends found while validating Spend PUT. Who is sending us garbage?"
);
Err(Error::InvalidRequest(format!(
"Found no valid spends while validating Spend PUT for {unique_pubkey:?}"
)))
}
.take(MAX_DOUBLE_SPEND_ATTEMPTS_PER_RECORD)
.collect::<Vec<SignedSpend>>();

if verified_spends.is_empty() {
debug!("No valid spends found while validating Spend PUT. Who is sending us garbage?");
Err(Error::InvalidRequest(format!(
"Found no valid spends while validating Spend PUT for {unique_pubkey:?}"
)))
} else if verified_spends.len() > 1 {
warn!("Got a double spend for {unique_pubkey:?}");
Ok(verified_spends)
} else {
debug!("Got a single valid spend for {unique_pubkey:?}");
Ok(verified_spends)
}
}
}
Expand Down

0 comments on commit a79e8e9

Please sign in to comment.