136 changes: 0 additions & 136 deletions sn_domain/src/node_transfers.rs

This file was deleted.

29 changes: 1 addition & 28 deletions sn_domain/src/storage/mod.rs
Expand Up @@ -7,32 +7,5 @@
// permissions and limitations relating to use of the SAFE Network Software.

pub mod registers;
pub mod spends;

pub use self::{
registers::{RegisterReplica, RegisterStorage},
spends::SpendStorage,
};

use sn_protocol::error::StorageError;
use std::{
path::{Path, PathBuf},
result,
};
use xor_name::XorName;

// A specialised `Result` type used within this storage implementation.
type Result<T> = result::Result<T, StorageError>;

const BIT_TREE_DEPTH: usize = 20;

// Helper that returns the prefix tree path of depth BIT_TREE_DEPTH for a given xorname
// Example:
// - with a xorname with starting bits `010001110110....`
// - and a BIT_TREE_DEPTH of `6`
// returns the path `ROOT_PATH/0/1/0/0/0/1`
fn prefix_tree_path(root: &Path, xorname: XorName) -> PathBuf {
let bin = format!("{xorname:b}");
let prefix_dir_path: PathBuf = bin.chars().take(BIT_TREE_DEPTH).map(String::from).collect();
root.join(prefix_dir_path)
}
pub use self::registers::{RegisterReplica, RegisterStorage};
58 changes: 30 additions & 28 deletions sn_domain/src/storage/registers/mod.rs
Expand Up @@ -10,21 +10,17 @@ pub mod reg_crdt;
pub mod reg_replica;

pub use reg_replica::RegisterReplica;

use super::{prefix_tree_path, Result};

use bincode::serialize;
use sn_protocol::{
error::{Error as ProtocolError, StorageError as Error},
error::{Error, Result},
messages::{
EditRegister, QueryResponse, RegisterCmd, RegisterQuery, ReplicatedRegisterLog,
SignedRegisterCreate, SignedRegisterEdit,
},
storage::{
registers::{Action, EntryHash, User},
DataAuthority, RegisterAddress,
},
storage::{Action, DataAuthority, EntryHash, RegisterAddress, User},
};
use xor_name::XorName;

use bincode::serialize;
use std::path::{Path, PathBuf};
use tokio::{
fs::{create_dir_all, read, remove_file, File},
Expand All @@ -35,6 +31,7 @@ use walkdir::WalkDir;

pub(super) type RegisterLog = Vec<RegisterCmd>;

const BIT_TREE_DEPTH: usize = 20;
const REGISTERS_STORE_DIR_NAME: &str = "registers";

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -65,8 +62,7 @@ impl RegisterStorage {
Get(address) => QueryResponse::GetRegister(
self.get_register(address, Action::Read, requester)
.await
.map(|reg_replica| reg_replica.into())
.map_err(ProtocolError::Storage),
.map(|reg_replica| reg_replica.into()),
),
Read(address) => self.read_register(*address, requester).await,
GetOwner(address) => self.get_owner(*address, requester).await,
Expand Down Expand Up @@ -254,8 +250,7 @@ impl RegisterStorage {
let result = match self.get_register(&address, Action::Read, requester).await {
Ok(register) => Ok(register.read()),
Err(error) => Err(error),
}
.map_err(ProtocolError::Storage);
};

QueryResponse::ReadRegister(result)
}
Expand All @@ -264,8 +259,7 @@ impl RegisterStorage {
let result = match self.get_register(&address, Action::Read, requester).await {
Ok(res) => Ok(res.owner()),
Err(error) => Err(error),
}
.map_err(ProtocolError::Storage);
};

QueryResponse::GetRegisterOwner(result)
}
Expand All @@ -279,8 +273,7 @@ impl RegisterStorage {
let result = self
.get_register(&address, Action::Read, requester)
.await
.and_then(|register| register.get(hash).map(|c| c.clone()))
.map_err(ProtocolError::Storage);
.and_then(|register| register.get(hash).map(|c| c.clone()));

QueryResponse::GetRegisterEntry(result)
}
Expand All @@ -294,8 +287,7 @@ impl RegisterStorage {
let result = self
.get_register(&address, Action::Read, requester)
.await
.and_then(|register| register.permissions(user))
.map_err(ProtocolError::Storage);
.and_then(|register| register.permissions(user));

QueryResponse::GetRegisterUserPermissions(result)
}
Expand All @@ -304,8 +296,7 @@ impl RegisterStorage {
let result = self
.get_register(&address, Action::Read, requester_pk)
.await
.map(|register| register.policy().clone())
.map_err(ProtocolError::Storage);
.map(|register| register.policy().clone());

QueryResponse::GetRegisterPolicy(result)
}
Expand Down Expand Up @@ -573,18 +564,18 @@ fn list_files_in(path: &Path) -> Vec<PathBuf> {
#[cfg(test)]
mod test {
use super::{Error, RegisterReplica, RegisterStorage};
use bincode::serialize;
use bls::SecretKey;
use eyre::{bail, Result};
use rand::{distributions::Alphanumeric, Rng};
use sn_protocol::{
error::Error as ProtocolError,
messages::{
CreateRegister, EditRegister, QueryResponse, RegisterCmd, RegisterQuery,
SignedRegisterCreate, SignedRegisterEdit,
},
storage::registers::{DataAuthority, EntryHash, Policy, User},
};

use bincode::serialize;
use bls::SecretKey;
use eyre::{bail, Result};
use rand::{distributions::Alphanumeric, Rng};
use std::collections::BTreeSet;
use xor_name::XorName;

Expand Down Expand Up @@ -889,7 +880,7 @@ mod test {
.await;
match res {
QueryResponse::GetRegisterEntry(Err(e)) => {
assert_eq!(e, ProtocolError::Storage(Error::NoSuchEntry(hash)))
assert_eq!(e, Error::NoSuchEntry(hash))
}
QueryResponse::GetRegisterEntry(Ok(entry)) => {
panic!("Should not exist any entry for random hash! {entry:?}")
Expand Down Expand Up @@ -919,7 +910,7 @@ mod test {
.await;
match res {
QueryResponse::GetRegisterUserPermissions(Err(e)) => {
assert_eq!(e, ProtocolError::Storage(Error::NoSuchUser(user)))
assert_eq!(e, Error::NoSuchUser(user))
}
QueryResponse::GetRegisterUserPermissions(Ok(perms)) => {
panic!("Should not exist any permissions for random user! {perms:?}",)
Expand Down Expand Up @@ -994,3 +985,14 @@ mod test {
Ok(RegisterCmd::Create(SignedRegisterCreate { op, auth }))
}
}

// Helper that returns the prefix tree path of depth BIT_TREE_DEPTH for a given xorname
// Example:
// - with a xorname with starting bits `010001110110....`
// - and a BIT_TREE_DEPTH of `6`
// returns the path `ROOT_PATH/0/1/0/0/0/1`
fn prefix_tree_path(root: &Path, xorname: XorName) -> PathBuf {
let bin = format!("{xorname:b}");
let prefix_dir_path: PathBuf = bin.chars().take(BIT_TREE_DEPTH).map(String::from).collect();
root.join(prefix_dir_path)
}
11 changes: 6 additions & 5 deletions sn_domain/src/storage/registers/reg_crdt.rs
Expand Up @@ -6,17 +6,18 @@
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use super::Result;

use crdts::{merkle_reg::MerkleReg, CmRDT, CvRDT};
use serde::{Deserialize, Serialize};
use sn_protocol::{
error::StorageError as Error,
error::Error,
storage::{
registers::{CrdtOperation, Entry, EntryHash, RegisterCrdt, User},
RegisterAddress,
},
};

use super::Result;

use crdts::{merkle_reg::MerkleReg, CmRDT, CvRDT};
use serde::{Deserialize, Serialize};
use std::{
collections::BTreeSet,
fmt::{self, Debug, Display, Formatter},
Expand Down
11 changes: 6 additions & 5 deletions sn_domain/src/storage/registers/reg_replica.rs
Expand Up @@ -6,17 +6,18 @@
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use super::{reg_crdt::RegisterCrdtImpl, Result};

use self_encryption::MIN_ENCRYPTABLE_BYTES;
use serde::{Deserialize, Serialize};
use sn_protocol::{
error::StorageError as Error,
error::Error,
storage::{
registers::{Action, Entry, EntryHash, Permissions, Policy, Register, RegisterOp, User},
RegisterAddress,
},
};

use super::{reg_crdt::RegisterCrdtImpl, Result};

use self_encryption::MIN_ENCRYPTABLE_BYTES;
use serde::{Deserialize, Serialize};
use std::{collections::BTreeSet, hash::Hash};
use xor_name::XorName;

Expand Down
533 changes: 0 additions & 533 deletions sn_domain/src/storage/spends.rs

This file was deleted.

1 change: 1 addition & 0 deletions sn_domain/src/wallet/local_store.rs
Expand Up @@ -232,6 +232,7 @@ impl SendWallet for LocalWallet {
.extend(created_dbcs.clone());

// Last of all, register the spend in the network.
println!("Sending transfer to the network: {transfer:#?}");
if let Err(error) = client.send(transfer.clone()).await {
println!("The transfer was not successfully registered in the network: {error:?}. It will be retried later.");
let _ = self.wallet.unconfirmed_txs.push(transfer);
Expand Down
5 changes: 2 additions & 3 deletions sn_networking/src/cmd.rs
Expand Up @@ -16,7 +16,7 @@ use libp2p::{
Multiaddr, PeerId,
};
use sn_protocol::{
messages::{QueryResponse, ReplicatedData, Request, Response},
messages::{ReplicatedData, Request, Response},
storage::Chunk,
NetworkAddress,
};
Expand Down Expand Up @@ -61,7 +61,7 @@ pub enum SwarmCmd {
/// Get data from the kademlia store
GetData {
key: RecordKey,
sender: oneshot::Sender<Result<QueryResponse>>,
sender: oneshot::Sender<Result<Vec<u8>>>,
},
StoreReplicatedData {
replicated_data: ReplicatedData,
Expand All @@ -85,7 +85,6 @@ impl SwarmDriver {
let _ = self.pending_query.insert(query_id, sender);
}
SwarmCmd::PutProvidedDataAsRecord { record } => {
// TODO: when do we remove records. Do we need to?
let _ = self
.swarm
.behaviour_mut()
Expand Down
6 changes: 2 additions & 4 deletions sn_networking/src/event.rs
Expand Up @@ -29,7 +29,7 @@ use libp2p::{
Multiaddr, PeerId,
};
use sn_protocol::{
messages::{Cmd, QueryResponse, ReplicatedData, Request, Response},
messages::{Cmd, ReplicatedData, Request, Response},
storage::Chunk,
NetworkAddress,
};
Expand Down Expand Up @@ -384,9 +384,7 @@ impl SwarmDriver {
);
if let Some(sender) = self.pending_query.remove(&id) {
sender
.send(Ok(QueryResponse::GetChunk(Ok(Chunk::new(
peer_record.record.value.into(),
)))))
.send(Ok(peer_record.record.value))
.map_err(|_| Error::InternalMsgChannelDropped)?;
}
}
Expand Down
17 changes: 9 additions & 8 deletions sn_networking/src/lib.rs
Expand Up @@ -46,7 +46,7 @@ use libp2p::{
};
use rand::Rng;
use sn_protocol::{
messages::{QueryResponse, ReplicatedData, Request, Response},
messages::{ReplicatedData, Request, Response},
NetworkAddress,
};
use sn_record_store::{
Expand Down Expand Up @@ -102,7 +102,7 @@ pub struct SwarmDriver {
pending_dial: HashMap<PeerId, oneshot::Sender<Result<()>>>,
pending_get_closest_peers: PendingGetClosest,
pending_requests: HashMap<RequestId, oneshot::Sender<Result<Response>>>,
pending_query: HashMap<QueryId, oneshot::Sender<Result<QueryResponse>>>,
pending_query: HashMap<QueryId, oneshot::Sender<Result<Vec<u8>>>>,
local: bool,
dialed_peers: CircularVec<PeerId>,
}
Expand Down Expand Up @@ -468,18 +468,19 @@ impl Network {
.await)
}

/// Send `Request` to the closest peers. If `self` is among the closest_peers, the `Request` is
/// Send `Request` to the closest peers and ignore reply
/// If `self` is among the closest_peers, the `Request` is
/// forwarded to itself and handled. Then a corresponding `Response` is created and is
/// forwarded to iself. Hence the flow remains the same and there is no branching at the upper
/// layers.
pub async fn fire_and_forget_to_closest(&self, request: &Request) -> Result<()> {
pub async fn send_req_no_reply_to_closest(&self, request: &Request) -> Result<()> {
info!(
"Sending {request:?} with dst {:?} to the closest peers.",
request.dst()
);
let closest_peers = self.node_get_closest_peers(&request.dst()).await?;
for peer in closest_peers {
self.fire_and_forget(request.clone(), peer).await?;
self.send_req_ignore_reply(request.clone(), peer).await?;
}
Ok(())
}
Expand All @@ -500,8 +501,8 @@ impl Network {
.await)
}

/// Get `Key` from our Storage
pub async fn get_provided_data(&self, key: RecordKey) -> Result<Result<QueryResponse>> {
/// Get `key` from our Storage
pub async fn get_provided_data(&self, key: RecordKey) -> Result<Result<Vec<u8>>> {
let (sender, receiver) = oneshot::channel();
self.send_swarm_cmd(SwarmCmd::GetData { key, sender })
.await?;
Expand Down Expand Up @@ -551,7 +552,7 @@ impl Network {
}

/// Send `Request` to the the given `PeerId` and do _not_ await a response.
pub async fn fire_and_forget(&self, req: Request, peer: PeerId) -> Result<()> {
pub async fn send_req_ignore_reply(&self, req: Request, peer: PeerId) -> Result<()> {
let (sender, _) = oneshot::channel();
let swarm_cmd = SwarmCmd::SendRequest { req, peer, sender };
self.send_swarm_cmd(swarm_cmd).await
Expand Down
2 changes: 1 addition & 1 deletion sn_node/Cargo.toml
Expand Up @@ -46,7 +46,7 @@ self_encryption = "~0.28.0"
serde = { version = "1.0.133", features = [ "derive", "rc" ]}
sn_build_info= { path="../sn_build_info", version="0.1.0" }
sn_peers_acquisition= { path="../sn_peers_acquisition", version="0.1.0" }
sn_dbc = { version = "17.0.0", features = ["serdes"] }
sn_dbc = { version = "18.0.0", features = ["serdes"] }
sn_domain = { path = "../sn_domain", version = "0.1.0" }
sn_client = { path = "../sn_client", version = "0.85.0" }
sn_logging = { path = "../sn_logging", version = "0.1.0" }
Expand Down
370 changes: 36 additions & 334 deletions sn_node/src/api.rs

Large diffs are not rendered by default.

11 changes: 5 additions & 6 deletions sn_node/src/lib.rs
Expand Up @@ -46,28 +46,27 @@ extern crate tracing;
mod api;
mod error;
mod event;
mod spendbook;

use spendbook::SpendBook;

pub use self::{
api::RunningNode,
event::{NodeEvent, NodeEventsChannel, NodeEventsReceiver},
};

use self::api::TransferAction;

use libp2p::{Multiaddr, PeerId};
use sn_domain::{node_transfers::Transfers, storage::RegisterStorage};
use sn_domain::storage::RegisterStorage;
use sn_networking::Network;
use tokio::sync::mpsc;

/// `Node` represents a single node in the distributed network. It handles
/// network events, processes incoming requests, interacts with the data
/// storage, and broadcasts node-related events.
pub struct Node {
network: Network,
registers: RegisterStorage,
transfers: Transfers,
spendbook: SpendBook,
events_channel: NodeEventsChannel,
/// Peers that are dialed at startup of node.
initial_peers: Vec<(PeerId, Multiaddr)>,
transfer_actor: mpsc::Sender<TransferAction>,
}
352 changes: 352 additions & 0 deletions sn_node/src/spendbook.rs
@@ -0,0 +1,352 @@
// Copyright 2023 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use sn_domain::dbc_genesis::{is_genesis_parent_tx, GENESIS_DBC};
use sn_networking::{close_group_majority, Network};
use sn_protocol::error::{Error, Result};
use sn_protocol::messages::{Query, QueryResponse, Request, Response};
use sn_protocol::storage::DbcAddress;

use std::collections::{BTreeMap, BTreeSet};

use libp2p::kad::{Record, RecordKey};
use sn_dbc::{DbcTransaction, SignedSpend};
use tokio::sync::RwLock;

/// The entitiy managing spends in a Node
#[derive(Default)]
pub(crate) struct SpendBook {
Comment on lines +21 to +23
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oetyng raised the legitimate question wether we should rename this to something else:

I think `SpendBook` is not the right level of abstraction here.

I was talking with Dirvine about this recently, and we agreed that the `SpendBook` is _the entire network_.

A `spend` is like a single entry (word) on one page of that book.
A single node holds a page out of the book. 

But a node doesn't hold the book.

_Originally posted by @oetyng in https://github.com/maidsafe/safe_network/pull/284#discussion_r1209093170_

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing complicated I think. It's just a store of spends, right?

So, like chunk store, it's spend store? But there are of course a possible number of variations of that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that here it's just a lock, i'd call it SpendLock myself.

I wouldn't sweat it too much here though, as discussed yesterday we should be bale to remove this lock entirely with #315

/// This RW lock is here to prevent race conditions on spendbook querries
/// that would enable double spends
rw_lock: RwLock<()>,
}

impl SpendBook {
/// Get a SpendBook entry for a given DbcAddress
pub(crate) async fn spend_get(
&self,
network: &Network,
address: DbcAddress,
) -> Result<SignedSpend> {
trace!("Spend get for address: {address:?}");
let _double_spend_guard = self.rw_lock.read().await;
trace!("Handling spend get for address: {address:?}");

// get spend from kad
let signed_spend_bytes = match network
.get_provided_data(RecordKey::new(address.name()))
.await
{
Ok(Ok(signed_spend_bytes)) => signed_spend_bytes,
Ok(Err(err)) | Err(err) => {
error!("Error getting spend from local store: {err}");
return Err(Error::SpendNotFound(address));
}
};

// deserialize spend
let signed_spend = match bincode::deserialize(&signed_spend_bytes) {
Ok(s) => s,
Err(e) => {
error!("Failed to get spend because deserialization failed: {e:?}");
return Err(Error::FailedToGetSpend(address));
}
};

trace!("Spend get for address: {address:?} successful");
Ok(signed_spend)
}

/// Put a SpendBook entry for a given SignedSpend
pub(crate) async fn spend_put(
&self,
network: &Network,
signed_spend: SignedSpend,
) -> Result<DbcAddress> {
let dbc_id = signed_spend.dbc_id();
let dbc_addr = DbcAddress::from_dbc_id(dbc_id);

trace!("Spend put for {dbc_id:?} at {dbc_addr:?}");
let _double_spend_guard = self.rw_lock.write().await;
trace!("Handling spend put for {dbc_id:?} at {dbc_addr:?}");

// check DBC spend
if let Err(e) = verify_spend_dbc(network, &signed_spend).await {
error!("Failed to store spend for {dbc_id:?} because DBC verification failed: {e:?}");
return Err(Error::FailedToStoreSpend(dbc_addr));
}

// serialize spend
let signed_spend_bytes = match bincode::serialize(&signed_spend) {
Ok(b) => b,
Err(e) => {
error!("Failed to store spend for {dbc_id:?} because serialization failed: {e:?}");
return Err(Error::FailedToStoreSpend(dbc_addr));
}
};

// create a kad record and upload it
let kademlia_record = Record {
key: RecordKey::new(dbc_addr.name()),
value: signed_spend_bytes,
publisher: None,
expires: None,
};
if let Err(e) = network.put_data_as_record(kademlia_record).await {
error!("Failed to store spend {dbc_id:?}: {e:?}");
return Err(Error::FailedToStoreSpend(dbc_addr));
}

trace!("Spend put for {dbc_id:?} at {dbc_addr:?} successful");
Ok(dbc_addr)
}

/// Checks if two spends make up a valid double spend
pub(crate) fn is_valid_double_spend(spend_one: &SignedSpend, spend_two: &SignedSpend) -> bool {
spend_one != spend_two // the spends are not the same one
&& spend_one.dbc_id() == spend_two.dbc_id() // the spent DBC has the same dbc_id
&& spend_one.verify(spend_one.spent_tx_hash()).is_ok() // the signature 1 is valid
&& spend_two.verify(spend_two.spent_tx_hash()).is_ok() // the signature 2 is valid
}
}

/// Checks if the spend already exists in the network.
async fn check_for_double_spend(network: &Network, signed_spend: &SignedSpend) -> Result<()> {
let dbc_addr = DbcAddress::from_dbc_id(signed_spend.dbc_id());
let spends = match get_spend(network, dbc_addr).await {
Ok(s) => s,
Err(Error::DoubleSpendAttempt {
spend_one,
spend_two,
}) => {
return Err(Error::DoubleSpendAttempt {
spend_one,
spend_two,
})?;
}
Err(e) => {
trace!(
"Get spend returned error while checking for double spend for {dbc_addr:?}: {e:?}"
);
vec![]
}
};

for s in spends {
if SpendBook::is_valid_double_spend(&s, signed_spend) {
return Err(Error::DoubleSpendAttempt {
spend_one: Box::new(signed_spend.clone()),
spend_two: Box::new(s),
})?;
}
}

Ok(())
}

/// Verifies a spend to make sure it is safe to store it on the Network
/// - check if the DBC Spend is valid
/// - check if the parents of this DBC exist on the Network (recursively meaning it comes from Genesis)
/// - check if another Spend for the same DBC exists on the Network (double spend)
async fn verify_spend_dbc(network: &Network, signed_spend: &SignedSpend) -> Result<()> {
if let Err(e) = signed_spend.verify(signed_spend.spent_tx_hash()) {
return Err(Error::InvalidSpendSignature(format!(
"while verifying spend for {:?}: {e:?}",
signed_spend.dbc_id()
)));
}
check_parent_spends(network, signed_spend).await?;
check_for_double_spend(network, signed_spend).await?;

Ok(())
}

/// Fetch all parent spends from the network and check them
/// they should all exist as valid spends for this current spend attempt to be valid
async fn check_parent_spends(network: &Network, signed_spend: &SignedSpend) -> Result<()> {
trace!("Getting parent_spends for {:?}", signed_spend.dbc_id());
let parent_spends = match get_parent_spends(network, &signed_spend.spent_tx()).await {
Ok(parent_spends) => parent_spends,
Err(e) => return Err(e)?,
};

trace!("Validating parent_spends for {:?}", signed_spend.dbc_id());
validate_parent_spends(signed_spend, &signed_spend.spent_tx(), parent_spends)?;

trace!("Validated parent_spends for {:?}", signed_spend.dbc_id());
Ok(())
}

/// The src_tx is the tx where the dbc to spend, was created.
/// The signed_spend.dbc_id() shall exist among its outputs.
fn validate_parent_spends(
signed_spend: &SignedSpend,
spent_tx: &DbcTransaction,
parent_spends: BTreeSet<SignedSpend>,
) -> Result<()> {
// The parent_spends will be different spends,
// one for each input that went into creating the signed_spend.
for parent_spend in &parent_spends {
// The dst tx of the parent must be the src tx of the spend.
if signed_spend.dbc_creation_tx_hash() != parent_spend.spent_tx_hash() {
return Err(Error::TxTrailMismatch {
signed_src_tx_hash: signed_spend.dbc_creation_tx_hash(),
parent_dst_tx_hash: parent_spend.spent_tx_hash(),
});
}
}

// We have gotten all the parent inputs from the network, so the network consider them all valid.
// But the source tx corresponding to the signed_spend, might not match the parents' details, so that's what we check here.
let known_parent_blinded_amounts: Vec<_> = parent_spends
.iter()
.map(|s| s.spend.blinded_amount)
.collect();

if is_genesis_parent_tx(spent_tx) && signed_spend.dbc_id() == &GENESIS_DBC.id {
return Ok(());
}

// Here we check that the spend that is attempted, was created in a valid tx.
let src_tx_validity = spent_tx.verify(&known_parent_blinded_amounts);
if src_tx_validity.is_err() {
return Err(Error::InvalidSourceTxProvided {
signed_src_tx_hash: signed_spend.dbc_creation_tx_hash(),
provided_src_tx_hash: spent_tx.hash(),
});
}

Ok(())
}

/// Fetch all parent spends from the network
async fn get_parent_spends(
network: &Network,
spent_tx: &DbcTransaction,
) -> Result<BTreeSet<SignedSpend>> {
// These will be different spends, one for each input that went into
// creating the above spend passed in to this function.
let mut all_parent_spends = BTreeSet::new();

if is_genesis_parent_tx(spent_tx) {
trace!("Return with empty parent_spends for genesis");
return Ok(all_parent_spends);
}

// First we fetch all parent spends from the network.
// They shall naturally all exist as valid spends for this current
// spend attempt to be valid.
for parent_input in &spent_tx.inputs {
let parent_address = DbcAddress::from_dbc_id(&parent_input.dbc_id());
// This call makes sure we get the same spend from all in the close group.
// If we receive a spend here, it is assumed to be valid. But we will verify
// that anyway, in the code right after this for loop.
trace!("getting parent_spend for {:?}", parent_address.name());
let parent_spend = get_network_valid_spend(network, parent_address).await?;
trace!("got parent_spend for {:?}", parent_address.name());
let _ = all_parent_spends.insert(parent_spend);
}

Ok(all_parent_spends)
}

/// Retrieve spends from the closest peers and checks if majority agrees on it
/// If majority agrees, return the agreed spend
async fn get_network_valid_spend(network: &Network, address: DbcAddress) -> Result<SignedSpend> {
let spends = get_spend(network, address).await?;
let valid_spends: Vec<_> = spends
.iter()
.filter(|signed_spend| signed_spend.verify(signed_spend.spent_tx_hash()).is_ok())
.collect();

if valid_spends.len() >= close_group_majority() {
use itertools::*;
let resp_count_by_spend: BTreeMap<&SignedSpend, usize> = valid_spends
.clone()
.into_iter()
.map(|x| (x, 1))
.into_group_map()
.into_iter()
.map(|(spend, vec_of_ones)| (spend, vec_of_ones.len()))
.collect();

if resp_count_by_spend.keys().len() > 1 {
let mut proof = resp_count_by_spend.keys().take(2);
if let (Some(spend_one), Some(spend_two)) = (proof.next(), proof.next()) {
return Err(Error::DoubleSpendAttempt {
spend_one: Box::new(spend_one.to_owned().clone()),
spend_two: Box::new(spend_two.to_owned().clone()),
})?;
}
}

let majority_agreement = resp_count_by_spend
.into_iter()
.max_by_key(|(_, count)| *count)
.map(|(k, _)| k);

if let Some(agreed_spend) = majority_agreement {
return Ok(agreed_spend.clone());
}
}

warn!(
"The spend for addr: {address:?} is not recognised by majority of peers in its close group"
);
Err(Error::InsufficientValidSpendsFound(address))
}

/// Requests spends from the closest peers
async fn get_spend(network: &Network, address: DbcAddress) -> Result<Vec<SignedSpend>> {
let request = Request::Query(Query::GetSpend(address));
let responses = network.node_send_to_closest(&request).await.map_err(|e| {
warn!("Error while fetching spends on the Network for {address:?}: {e:?}");
Error::FailedToGetSpend(address)
})?;

// Get all Ok results of the expected response type `GetDbcSpend`.
let mut double_spend_answer = None;
let spends: Vec<_> = responses
.iter()
.flatten()
.flat_map(|resp| {
match resp {
Response::Query(QueryResponse::GetDbcSpend(Ok(signed_spend))) => {
Some(signed_spend.clone())
}
Response::Query(QueryResponse::GetDbcSpend(Err(Error::DoubleSpendAttempt{ spend_one, spend_two }))) => {
if SpendBook::is_valid_double_spend(spend_one, spend_two) {
warn!("Double spend attempt reported by peer: {spend_one:?} and {spend_two:?}");
double_spend_answer = Some((spend_one, spend_two));
} else {
warn!("Ignoring invalid double spend reported by dirty liar peer");
}
None
}
Response::Query(QueryResponse::GetDbcSpend(Err(e))) => {
warn!("Peer sent us an error while getting spend from network: {e:?}");
None
}
_ => {
// TODO check what it means if we get a different response type

Check notice

Code scanning / devskim

A "TODO" or similar was left in source code, possibly indicating incomplete functionality Note

Suspicious comment
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the Response::Query(QueryResponse::GetDbcSpend(Err(e))) type should be simplified.
The possible errors trimmed down to what is possible in this specific case.
This type is too vast.

None
}
}
})
.collect();

// check if peers reported double spend
if let Some((spend_one, spend_two)) = double_spend_answer {
return Err(Error::DoubleSpendAttempt {
spend_one: spend_one.to_owned(),
spend_two: spend_two.to_owned(),
})?;
}

Ok(spends)
}
2 changes: 1 addition & 1 deletion sn_protocol/Cargo.toml
Expand Up @@ -16,6 +16,6 @@ crdts = { version = "7.3", default-features = false, features = ["merkle"] }
custom_debug = "~0.5.0"
libp2p = { version="0.51", features = ["identify", "kad"] }
serde = { version = "1.0.133", features = [ "derive", "rc" ]}
sn_dbc = { version = "17.0.0", features = ["serdes"] }
sn_dbc = { version = "18.0.0", features = ["serdes"] }
thiserror = "1.0.23"
xor_name = "5.0.0"
68 changes: 56 additions & 12 deletions sn_protocol/src/error/storage.rs → sn_protocol/src/error.rs
Expand Up @@ -11,16 +11,18 @@ use crate::storage::{
ChunkAddress, DbcAddress, RegisterAddress,
};

use sn_dbc::SignedSpend;
use xor_name::XorName;

use serde::{Deserialize, Serialize};
use sn_dbc::{Hash, SignedSpend};
use thiserror::Error;
use xor_name::XorName;

/// A specialised `Result` type for protocol crate.
pub type Result<T> = std::result::Result<T, Error>;

/// Errors related to storage operation on the network.
/// Main error types for the SAFE protocol.
#[derive(Error, Clone, PartialEq, Eq, Serialize, Deserialize, custom_debug::Debug)]
#[non_exhaustive]
pub enum StorageError {
pub enum Error {
/// Chunk not found.
#[error("Chunk not found: {0:?}")]
ChunkNotFound(ChunkAddress),
Expand Down Expand Up @@ -70,18 +72,26 @@ pub enum StorageError {
/// Spend not found.
#[error("Spend not found: {0:?}")]
SpendNotFound(DbcAddress),
/// We failed to store spend
#[error("Spend was not stored: {0:?}")]
SpendNotStored(DbcAddress),
/// A double spend attempt was detected.
#[error("A double spend attempt was detected. Incoming and existing spend are not the same: {new:?}. Existing: {existing:?}")]
/// Insufficient valid spends found to make it valid, less that majority of closest peers
#[error("Insufficient valid spends found: {0:?}")]
InsufficientValidSpendsFound(DbcAddress),
/// Node failed to store spend
#[error("Failed to store spend: {0:?}")]
FailedToStoreSpend(DbcAddress),
/// Node failed to get spend
#[error("Failed to get spend: {0:?}")]
FailedToGetSpend(DbcAddress),
/// A double spend was detected.
#[error(
"A double spend was detected. Two diverging signed spends: {spend_one:?}, {spend_two:?}"
)]
DoubleSpendAttempt {
/// New spend that we received.
#[debug(skip)]
new: Box<SignedSpend>,
spend_one: Box<SignedSpend>,
/// Existing spend of same id that we already have.
#[debug(skip)]
existing: Box<SignedSpend>,
spend_two: Box<SignedSpend>,
},
/// A spend that was attempted to be added was already marked as double spend.
#[error("A spend that was attempted to be added was already marked as double spend: {0:?}")]
Expand All @@ -93,4 +103,38 @@ pub enum StorageError {
/// Cannot verify a Spend signature.
#[error("Spend signature is invalid: {0}")]
InvalidSpendSignature(String),

///
#[error("Contacting close group of parent spends failed: {0}.")]
SpendParentCloseGroupIssue(String),
/// One or more parent spends of a requested spend had a different dst tx hash than the signed spend src tx hash.
#[error(
"The signed spend src tx ({signed_src_tx_hash:?}) did not match the provided source tx's hash: {provided_src_tx_hash:?}"
)]
TxSourceMismatch {
/// The signed spend src tx hash.
signed_src_tx_hash: Hash,
/// The hash of the provided source tx.
provided_src_tx_hash: Hash,
},
/// One or more parent spends of a requested spend had a different dst tx hash than the signed spend src tx hash.
#[error(
"The signed spend src tx ({signed_src_tx_hash:?}) did not match a valid parent's dst tx hash: {parent_dst_tx_hash:?}. The trail is invalid."
)]
TxTrailMismatch {
/// The signed spend src tx hash.
signed_src_tx_hash: Hash,
/// The dst hash of a parent signed spend.
parent_dst_tx_hash: Hash,
},
/// The provided source tx did not check out when verified with all supposed inputs to it (i.e. our spends parents).
#[error(
"The provided source tx (with hash {provided_src_tx_hash:?}) when verified with all supposed inputs to it (i.e. our spends parents).."
)]
InvalidSourceTxProvided {
/// The signed spend src tx hash.
signed_src_tx_hash: Hash,
/// The hash of the provided source tx.
provided_src_tx_hash: Hash,
},
}
32 changes: 0 additions & 32 deletions sn_protocol/src/error/mod.rs

This file was deleted.

55 changes: 0 additions & 55 deletions sn_protocol/src/error/transfer.rs

This file was deleted.

18 changes: 6 additions & 12 deletions sn_protocol/src/messages/cmd.rs
Expand Up @@ -13,7 +13,7 @@ use crate::{

use super::{RegisterCmd, ReplicatedData};

use sn_dbc::{DbcTransaction, SignedSpend};
use sn_dbc::SignedSpend;

use serde::{Deserialize, Serialize};

Expand All @@ -37,15 +37,9 @@ pub enum Cmd {
/// [`SignedSpend`] write operation.
///
/// [`SignedSpend`]: sn_dbc::SignedSpend
SpendDbc {
/// The spend to be recorded.
/// It contains the transaction it is being spent in.
#[debug(skip)]
signed_spend: Box<SignedSpend>,
/// The transaction that this spend was created in.
#[debug(skip)]
parent_tx: Box<DbcTransaction>,
},
/// The spend to be recorded.
/// It contains the transaction it is being spent in.
SpendDbc(#[debug(skip)] SignedSpend),
/// [`ReplicatedData`] write operation.
///
/// [`ReplicatedData`]: crate::messages::ReplicatedData
Expand All @@ -60,7 +54,7 @@ impl Cmd {
NetworkAddress::from_chunk_address(ChunkAddress::new(*chunk.name()))
}
Cmd::Register(cmd) => NetworkAddress::from_register_address(cmd.dst()),
Cmd::SpendDbc { signed_spend, .. } => {
Cmd::SpendDbc(signed_spend) => {
NetworkAddress::from_dbc_address(DbcAddress::from_dbc_id(signed_spend.dbc_id()))
}
Cmd::Replicate(replicated_data) => replicated_data.dst(),
Expand All @@ -77,7 +71,7 @@ impl std::fmt::Display for Cmd {
Cmd::Register(cmd) => {
write!(f, "Cmd::Register({:?})", cmd.name()) // more qualification needed
}
Cmd::SpendDbc { signed_spend, .. } => {
Cmd::SpendDbc(signed_spend) => {
write!(f, "Cmd::SpendDbc({:?})", signed_spend.dbc_id())
}
Cmd::Replicate(replicated_data) => {
Expand Down
68 changes: 0 additions & 68 deletions sn_protocol/src/messages/event.rs

This file was deleted.

31 changes: 3 additions & 28 deletions sn_protocol/src/messages/mod.rs
Expand Up @@ -8,35 +8,27 @@

//! Data messages and their possible responses.
mod cmd;
mod event;
mod node_id;
mod query;
mod register;
mod response;
mod spend;

use crate::storage::Chunk;

pub use self::{
cmd::Cmd,
event::Event,
node_id::NodeId,
query::Query,
register::{
CreateRegister, EditRegister, RegisterCmd, RegisterQuery, ReplicatedRegisterLog,
SignedRegisterCreate, SignedRegisterEdit,
},
response::{CmdResponse, QueryResponse},
spend::SpendQuery,
};

use super::{
storage::{Chunk, DbcAddress},
NetworkAddress,
};

use sn_dbc::SignedSpend;
use super::NetworkAddress;

use serde::{Deserialize, Serialize};
use std::{collections::BTreeSet, fmt::Debug};
use xor_name::XorName;

/// A request to peers in the network
Expand All @@ -46,8 +38,6 @@ pub enum Request {
Cmd(Cmd),
/// A query sent to peers. Queries are read-only.
Query(Query),
/// A fact sent to peers.
Event(Event),
}

/// A response to peers in the network.
Expand All @@ -59,8 +49,6 @@ pub enum Response {
Query(QueryResponse),
}

/// Messages to replicated data among nodes on the network
#[allow(clippy::large_enum_variant)]
#[derive(custom_debug::Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub enum ReplicatedData {
/// A chunk of data.
Expand All @@ -69,12 +57,6 @@ pub enum ReplicatedData {
RegisterWrite(RegisterCmd),
/// An entire op log of a register.
RegisterLog(ReplicatedRegisterLog),
/// A valid spend.
#[debug(skip)]
ValidSpend(SignedSpend),
/// A dbc marked as having attempted double spend.
#[debug(skip)]
DoubleSpend((DbcAddress, BTreeSet<SignedSpend>)),
}

impl Request {
Expand All @@ -83,7 +65,6 @@ impl Request {
match self {
Request::Cmd(cmd) => cmd.dst(),
Request::Query(query) => query.dst(),
Request::Event(event) => event.dst(),
}
}
}
Expand All @@ -95,8 +76,6 @@ impl ReplicatedData {
Self::Chunk(chunk) => *chunk.name(),
Self::RegisterLog(log) => *log.address.name(),
Self::RegisterWrite(cmd) => *cmd.dst().name(),
Self::ValidSpend(spend) => *DbcAddress::from_dbc_id(spend.dbc_id()).name(),
Self::DoubleSpend((address, _)) => *address.name(),
}
}

Expand All @@ -106,10 +85,6 @@ impl ReplicatedData {
Self::Chunk(chunk) => NetworkAddress::from_chunk_address(*chunk.address()),
Self::RegisterLog(log) => NetworkAddress::from_register_address(log.address),
Self::RegisterWrite(cmd) => NetworkAddress::from_register_address(cmd.dst()),
Self::ValidSpend(spend) => {
NetworkAddress::from_dbc_address(DbcAddress::from_dbc_id(spend.dbc_id()))
}
Self::DoubleSpend((address, _)) => NetworkAddress::from_dbc_address(*address),
}
}
}
Expand Down
22 changes: 14 additions & 8 deletions sn_protocol/src/messages/query.rs
Expand Up @@ -6,9 +6,12 @@
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use crate::{storage::ChunkAddress, NetworkAddress};
use crate::{
storage::{ChunkAddress, DbcAddress},
NetworkAddress,
};

use super::{RegisterQuery, SpendQuery};
use super::RegisterQuery;

use serde::{Deserialize, Serialize};

Expand All @@ -32,10 +35,13 @@ pub enum Query {
///
/// [`Register`]: crate::storage::Register
Register(RegisterQuery),
/// [`Spend`] read operation.
/// Retrieve a [`SignedSpend`] at the given address.
///
/// [`Spend`]: super::transfers::SpendQuery.
Spend(SpendQuery),
/// This should eventually lead to a [`GetDbcSpend`] response.
///
/// [`SignedSpend`]: sn_dbc::SignedSpend
/// [`GetDbcSpend`]: super::QueryResponse::GetDbcSpend
GetSpend(DbcAddress),
}

impl Query {
Expand All @@ -44,7 +50,7 @@ impl Query {
match self {
Query::GetChunk(address) => NetworkAddress::from_chunk_address(*address),
Query::Register(query) => NetworkAddress::from_register_address(query.dst()),
Query::Spend(query) => NetworkAddress::from_dbc_address(query.dst()),
Query::GetSpend(address) => NetworkAddress::from_dbc_address(*address),
}
}
}
Expand All @@ -58,8 +64,8 @@ impl std::fmt::Display for Query {
Query::Register(query) => {
write!(f, "Query::Register({:?})", query.dst()) // more qualification needed
}
Query::Spend(query) => {
write!(f, "Query::Spend({query:?})")
Query::GetSpend(address) => {
write!(f, "Query::GetSpend({address:?})")
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion sn_protocol/src/messages/response.rs
Expand Up @@ -34,7 +34,7 @@ pub enum QueryResponse {
///
/// Response to [`GetDbcSpend`]
///
/// [`GetDbcSpend`]: crate::messages::SpendQuery::GetDbcSpend
/// [`GetDbcSpend`]: crate::messages::Query::GetSpend
#[debug(skip)]
GetDbcSpend(Result<SignedSpend>),
//
Expand Down
37 changes: 0 additions & 37 deletions sn_protocol/src/messages/spend.rs

This file was deleted.