From 255c2bce8321c1cb9a4262c194a1036732fefa10 Mon Sep 17 00:00:00 2001 From: viquezclaudio Date: Thu, 23 Feb 2023 14:20:37 -0600 Subject: [PATCH 1/3] Expose GetTransactionsByAddress to JS Provide functionality that queries the network in order to get transactions that belong to some specific address. For this particular request, history nodes would provide the transaction details and an inclusion proof of those transactions. The final list of transactions are serialized and returned to JS via a JsValue --- Cargo.lock | 1 + blockchain/src/blockchain/history_sync.rs | 8 +- blockchain/src/history/history_store.rs | 5 +- blockchain/src/history/mod.rs | 2 - consensus/src/consensus/consensus_proxy.rs | 122 +++++++++++++++++- consensus/src/consensus/mod.rs | 7 +- consensus/src/messages/handlers.rs | 43 +++--- consensus/src/messages/mod.rs | 28 ++-- network-interface/src/network.rs | 10 ++ .../src/connection_pool/behaviour.rs | 28 ++++ network-libp2p/src/error.rs | 3 + network-libp2p/src/network.rs | 69 ++++++++++ network-mock/src/network.rs | 8 ++ .../transaction/src/history_proof.rs | 7 +- primitives/transaction/src/lib.rs | 2 + web-client/Cargo.toml | 3 +- web-client/src/address.rs | 4 + web-client/src/lib.rs | 40 +++++- web-client/src/transaction.rs | 15 +++ 19 files changed, 354 insertions(+), 51 deletions(-) rename blockchain/src/history/history_tree_proof.rs => primitives/transaction/src/history_proof.rs (94%) diff --git a/Cargo.lock b/Cargo.lock index f5bf38f490..e33ed29502 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4500,6 +4500,7 @@ dependencies = [ "nimiq-transaction-builder", "nimiq-zkp-component", "parking_lot 0.12.1", + "serde", "serde-wasm-bindgen", "tracing", "wasm-bindgen", diff --git a/blockchain/src/blockchain/history_sync.rs b/blockchain/src/blockchain/history_sync.rs index 925d525785..0f71c34e06 100644 --- a/blockchain/src/blockchain/history_sync.rs +++ b/blockchain/src/blockchain/history_sync.rs @@ -9,9 +9,11 @@ use nimiq_blockchain_interface::{ use nimiq_database::WriteTransaction; use nimiq_primitives::coin::Coin; use nimiq_primitives::policy::Policy; -use nimiq_transaction::extended_transaction::{ExtTxData, ExtendedTransaction}; -use nimiq_transaction::inherent::{Inherent, InherentType}; -use nimiq_transaction::Transaction; +use nimiq_transaction::{ + extended_transaction::{ExtTxData, ExtendedTransaction}, + inherent::{Inherent, InherentType}, + Transaction, +}; use crate::Blockchain; diff --git a/blockchain/src/history/history_store.rs b/blockchain/src/history/history_store.rs index 6b258eb3dc..993aaa1d59 100644 --- a/blockchain/src/history/history_store.rs +++ b/blockchain/src/history/history_store.rs @@ -18,12 +18,11 @@ use nimiq_mmr::store::memory::MemoryStore; use nimiq_primitives::policy::Policy; use nimiq_transaction::{ extended_transaction::{ExtTxData, ExtendedTransaction}, + history_proof::HistoryTreeProof, inherent::InherentType, }; -use crate::history::{ - mmr_store::MMRStore, ordered_hash::OrderedHash, HistoryTreeChunk, HistoryTreeProof, -}; +use crate::history::{mmr_store::MMRStore, ordered_hash::OrderedHash, HistoryTreeChunk}; /// A struct that contains databases to store history trees (which are Merkle Mountain Ranges /// constructed from the list of extended transactions in an epoch) and extended transactions (which diff --git a/blockchain/src/history/mod.rs b/blockchain/src/history/mod.rs index f26cf63559..2d81c5b109 100644 --- a/blockchain/src/history/mod.rs +++ b/blockchain/src/history/mod.rs @@ -1,9 +1,7 @@ pub use history_store::HistoryStore; pub use history_tree_chunk::{HistoryTreeChunk, CHUNK_SIZE}; -pub use history_tree_proof::HistoryTreeProof; mod history_store; mod history_tree_chunk; -mod history_tree_proof; mod mmr_store; mod ordered_hash; diff --git a/consensus/src/consensus/consensus_proxy.rs b/consensus/src/consensus/consensus_proxy.rs index 2c2fc907ec..7f3b2dd407 100644 --- a/consensus/src/consensus/consensus_proxy.rs +++ b/consensus/src/consensus/consensus_proxy.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -5,10 +6,19 @@ use tokio::sync::broadcast::Sender as BroadcastSender; use tokio_stream::wrappers::BroadcastStream; use nimiq_blockchain_proxy::BlockchainProxy; -use nimiq_network_interface::network::Network; +use nimiq_keys::Address; +use nimiq_network_interface::{ + network::{CloseReason, Network}, + peer_info::Services, + request::{OutboundRequestError, RequestError}, +}; use nimiq_primitives::account::AccountType; -use nimiq_transaction::{ControlTransactionTopic, Transaction, TransactionTopic}; +use nimiq_transaction::{ + extended_transaction::ExtendedTransaction, ControlTransactionTopic, Transaction, + TransactionTopic, +}; +use crate::messages::{RequestTransactionsByAddress, RequestTransactionsProof}; use crate::ConsensusEvent; pub struct ConsensusProxy { @@ -44,4 +54,112 @@ impl ConsensusProxy { pub fn subscribe_events(&self) -> BroadcastStream { BroadcastStream::new(self.events.subscribe()) } + + pub async fn request_transactions_by_address( + &self, + address: Address, + min_peers: usize, + max: Option, + ) -> Result, RequestError> { + // First we tell the network to provide us with a vector that contains all the connected peers that support such services + // Note: If the network could not provide enough peers that satisfies our requirement, then an error would be returned + let peers = self + .network + .get_peers_by_services(Services::TRANSACTION_INDEX, min_peers) + .await + .map_err(|error| { + log::error!( + err = %error, + "The transactions by address request couldn't be fulfilled" + ); + + RequestError::OutboundRequest(OutboundRequestError::SendError) + })?; + + let mut verified_transactions = HashMap::new(); + + // At this point we obtained a list of connected peers that could satisfy our request, + // so we perform the request to each of those peers: + for peer_id in peers { + let response = self + .network + .request::( + RequestTransactionsByAddress { + address: address.clone(), + max, + }, + peer_id, + ) + .await; + + match response { + Ok(response) => { + // Now we request proofs for each transaction we requested. + for transaction in response.transactions { + // If the transaction was already verified, then we don't need to verify it again + if verified_transactions.contains_key(&transaction.tx_hash()) { + continue; + } + + let response = self + .network + .request::( + RequestTransactionsProof { + hashes: vec![transaction.tx_hash()], + block_number: transaction.block_number, + }, + peer_id, + ) + .await; + match response { + Ok(proof_response) => { + // We verify the transaction using the proof + if let Some(proof) = proof_response.proof { + if let Some(block) = proof_response.block { + // TODO: We are currently assuming that the provided block was included in the chain + // but we also need some additional information to prove the block is part of the chain. + let verification_result = proof + .verify(block.history_root().clone()) + .map_or(false, |result| result); + + if verification_result { + verified_transactions + .insert(transaction.tx_hash(), transaction); + } else { + // The proof didn't verify so we disconnect from this peer + log::debug!(peer=%peer_id,"Disconnecting from peer because the transaction proof didn't verify"); + self.network + .disconnect_peer(peer_id, CloseReason::Other) + .await; + break; + } + } else { + // If we receive a proof but we do not recieve a block, we disconnect from the peer + log::debug!(peer=%peer_id,"Disconnecting from peer due to an inconsistency in the transaction proof response"); + self.network + .disconnect_peer(peer_id, CloseReason::Other) + .await; + break; + } + } else { + log::debug!(peer=%peer_id, "We requested a transaction proof but the peer didn't provide any"); + } + } + Err(error) => { + // If there was a request error with this peer we don't request anymore proofs from it + log::error!(peer=%peer_id, err=%error,"There was an error requesting transactions proof from peer"); + break; + } + } + } + } + Err(error) => { + // If there was a request error with this peer we log an error + log::error!(peer=%peer_id, err=%error,"There was an error requesting transactions from peer"); + } + } + } + + Ok(verified_transactions.into_values().collect()) + } } diff --git a/consensus/src/consensus/mod.rs b/consensus/src/consensus/mod.rs index fa2b459d78..9edac7ddce 100644 --- a/consensus/src/consensus/mod.rs +++ b/consensus/src/consensus/mod.rs @@ -20,7 +20,9 @@ use nimiq_zkp_component::zkp_component::ZKPComponentProxy; use crate::consensus::head_requests::{HeadRequests, HeadRequestsResult}; #[cfg(feature = "full")] -use crate::messages::{RequestBatchSet, RequestHistoryChunk, RequestTransactionsProof}; +use crate::messages::{ + RequestBatchSet, RequestHistoryChunk, RequestTransactionsByAddress, RequestTransactionsProof, +}; use crate::messages::{RequestBlock, RequestHead, RequestMacroChain, RequestMissingBlocks}; #[cfg(feature = "full")] use crate::sync::live::state_queue::RequestChunk; @@ -155,6 +157,9 @@ impl Consensus { let stream = network.receive_requests::(); executor.exec(Box::pin(request_handler(network, stream, blockchain))); + + let stream = network.receive_requests::(); + executor.exec(Box::pin(request_handler(network, stream, blockchain))); } BlockchainProxy::Light(_) => {} } diff --git a/consensus/src/messages/handlers.rs b/consensus/src/messages/handlers.rs index 726d8b60be..eb47db9c80 100644 --- a/consensus/src/messages/handlers.rs +++ b/consensus/src/messages/handlers.rs @@ -307,24 +307,31 @@ impl Handle>> for RequestTrans let blockchain = blockchain.read(); let hashes = self.hashes.iter().collect(); - let proof = blockchain - .history_store - .prove(self.epoch_number, hashes, None); - - let block_height = self.epoch_number * Policy::blocks_per_epoch(); - - let block = blockchain - .chain_store - .get_block_at(block_height, false, None) - .ok() - .and_then(|block| { - if block.is_macro() { - // We expect a macro block - Some(block) - } else { - None - } - }); + let mut block = None; + + let proof = + blockchain + .history_store + .prove(Policy::epoch_at(self.block_number), hashes, None); + + //If we obtained a proof, we need to supply the corresponding block + if proof.is_some() { + block = blockchain + .chain_store + .get_block_at(self.block_number, false, None) + .ok() + .and_then(|block| { + if block.is_macro() { + // We expect a macro block + Some(block) + } else { + None + } + }); + if block.is_none() { + log::error!("We are supplying a transaction proof but not a block, which can be interpreted as malicious behaviour"); + } + } ResponseTransactionsProof { proof, block } } diff --git a/consensus/src/messages/mod.rs b/consensus/src/messages/mod.rs index 3686ea02fa..8d53acdeb7 100644 --- a/consensus/src/messages/mod.rs +++ b/consensus/src/messages/mod.rs @@ -3,13 +3,13 @@ use std::fmt::{Debug, Formatter}; use beserial::{Deserialize, Serialize}; use nimiq_block::{Block, MacroBlock}; #[cfg(feature = "full")] -use nimiq_blockchain::{HistoryTreeChunk, HistoryTreeProof}; +use nimiq_blockchain::HistoryTreeChunk; use nimiq_hash::Blake2bHash; -#[cfg(feature = "full")] use nimiq_keys::Address; use nimiq_network_interface::request::{RequestCommon, RequestMarker}; -#[cfg(feature = "full")] -use nimiq_transaction::extended_transaction::ExtendedTransaction; +use nimiq_transaction::{ + extended_transaction::ExtendedTransaction, history_proof::HistoryTreeProof, +}; mod handlers; @@ -211,22 +211,19 @@ impl RequestCommon for RequestHead { const MAX_REQUESTS: u32 = MAX_REQUEST_RESPONSE_HEAD; } -#[cfg(feature = "full")] #[derive(Serialize, Deserialize)] pub struct ResponseTransactionsProof { - proof: Option, - block: Option, + pub proof: Option, + pub block: Option, } -#[cfg(feature = "full")] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RequestTransactionsProof { #[beserial(len_type(u16, limit = 128))] - hashes: Vec, - epoch_number: u32, + pub hashes: Vec, + pub block_number: u32, } -#[cfg(feature = "full")] impl RequestCommon for RequestTransactionsProof { type Kind = RequestMarker; const TYPE_ID: u16 = 213; @@ -234,14 +231,12 @@ impl RequestCommon for RequestTransactionsProof { const MAX_REQUESTS: u32 = MAX_REQUEST_TRANSACTIONS_PROOF; } -#[cfg(feature = "full")] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RequestTransactionsByAddress { - address: Address, - max: Option, + pub address: Address, + pub max: Option, } -#[cfg(feature = "full")] impl RequestCommon for RequestTransactionsByAddress { type Kind = RequestMarker; const TYPE_ID: u16 = 214; @@ -249,9 +244,8 @@ impl RequestCommon for RequestTransactionsByAddress { const MAX_REQUESTS: u32 = MAX_REQUEST_TRANSACTIONS_BY_ADDRESS; } -#[cfg(feature = "full")] #[derive(Serialize, Deserialize)] pub struct ResponseTransactionsByAddress { #[beserial(len_type(u16, limit = 128))] - transactions: Vec, + pub transactions: Vec, } diff --git a/network-interface/src/network.rs b/network-interface/src/network.rs index 85b5970b8f..baad45f8db 100644 --- a/network-interface/src/network.rs +++ b/network-interface/src/network.rs @@ -81,6 +81,16 @@ pub trait Network: Send + Sync + Unpin + 'static { /// If the peer isn't found, `None` is returned. fn get_peer_info(&self, peer_id: Self::PeerId) -> Option; + /// Gets the set of connected peers that provide the supplied services. + /// If we currently don't have min number of connected peer that provides those services, + /// we dial peers. + /// If there aren't enough peers in the network that provides the required services, we return an error + async fn get_peers_by_services( + &self, + services: Services, + min_peers: usize, + ) -> Result, Self::Error>; + /// Returns true when the given peer provides the services flags that are required by us fn peer_provides_required_services(&self, peer_id: Self::PeerId) -> bool; diff --git a/network-libp2p/src/connection_pool/behaviour.rs b/network-libp2p/src/connection_pool/behaviour.rs index ea862f795e..3376e59f5b 100644 --- a/network-libp2p/src/connection_pool/behaviour.rs +++ b/network-libp2p/src/connection_pool/behaviour.rs @@ -364,6 +364,34 @@ impl ConnectionPoolBehaviour { .choose_multiple(&mut thread_rng(), num_peers) } + /// This function is used to select a list of peers, based on services flag, in order to dial them. + /// num_peers is used to specify how many peers are selected + /// The number of peers returned equals num_peers unless there are less available peers + pub fn choose_peers_to_dial_by_services( + &self, + services: Services, + num_peers: usize, + ) -> Vec { + let contacts = self.contacts.read(); + let own_contact = contacts.get_own_contact(); + let own_peer_id = own_contact.peer_id(); + + contacts + .query(services) + .filter_map(|contact| { + let peer_id = contact.peer_id(); + if peer_id != own_peer_id + && self.peer_ids.can_dial(peer_id) + && contact.addresses().count() > 0 + { + Some(*peer_id) + } else { + None + } + }) + .choose_multiple(&mut thread_rng(), num_peers) + } + fn choose_seeds_to_dial(&self) -> Vec { // We prefer to connect to non-seed peers. Thus, we only choose any seeds here if we're // not already dialing any peers and at most one seed at a time. diff --git a/network-libp2p/src/error.rs b/network-libp2p/src/error.rs index fa125912db..a9baf24b37 100644 --- a/network-libp2p/src/error.rs +++ b/network-libp2p/src/error.rs @@ -14,6 +14,9 @@ pub enum NetworkError { #[error("Network action was cancelled")] Cancelled, + #[error("We could not find any peer that satisfies the desired services")] + PeersNotFound, + #[error("Serialization error: {0}")] Serialization(#[from] beserial::SerializingError), diff --git a/network-libp2p/src/network.rs b/network-libp2p/src/network.rs index 4d0fc8ed3c..df94802f12 100644 --- a/network-libp2p/src/network.rs +++ b/network-libp2p/src/network.rs @@ -137,6 +137,11 @@ pub(crate) enum NetworkAction { ListenOn { listen_addresses: Vec, }, + ConnectPeersByServices { + services: Services, + num_peers: usize, + output: oneshot::Sender>, + }, StartConnecting, RestartConnecting, StopConnecting, @@ -1296,6 +1301,27 @@ impl Network { NetworkAction::StopConnecting => { swarm.behaviour_mut().pool.stop_connecting(); } + NetworkAction::ConnectPeersByServices { + services, + num_peers, + output, + } => { + let peers_candidates = swarm + .behaviour_mut() + .pool + .choose_peers_to_dial_by_services(services, num_peers); + let mut successful_peers = vec![]; + + for peer_id in peers_candidates { + if Swarm::dial(swarm, DialOpts::peer_id(peer_id).build()).is_ok() { + successful_peers.push(peer_id); + } + } + + if output.send(successful_peers).is_err() { + error!("Could not send sucessful peers vector"); + } + } NetworkAction::DisconnectPeer { peer_id } => { if swarm.disconnect_peer_id(peer_id).is_err() { warn!(%peer_id, "Peer already closed"); @@ -1737,6 +1763,49 @@ impl NetworkInterface for Network { } } + async fn get_peers_by_services( + &self, + services: Services, + min_peers: usize, + ) -> Result, NetworkError> { + let (output_tx, output_rx) = oneshot::channel(); + let connected_peers = self.get_peers(); + let mut filtered_peers = vec![]; + + // First we try to get the connected peers that support the desired services + for peer_id in connected_peers.iter() { + if let Some(peer_info) = self.get_peer_info(*peer_id) { + if peer_info.get_services().contains(services) { + filtered_peers.push(*peer_id); + } + } + } + + // If we don't have enough connected peer that support the desired services, + // we tell the network to connect to new peers that supports such services. + if filtered_peers.len() < min_peers { + let num_peers = min_peers - filtered_peers.len(); + + self.action_tx + .send(NetworkAction::ConnectPeersByServices { + services, + num_peers, + output: output_tx, + }) + .await?; + + filtered_peers.extend(output_rx.await?.iter()); + } + + // If filtered_peers is still less than the minimum required, + // we return an error. + if filtered_peers.len() < min_peers { + return Err(NetworkError::PeersNotFound); + } + + Ok(filtered_peers) + } + async fn disconnect_peer(&self, peer_id: PeerId, _close_reason: CloseReason) { if let Err(error) = self .action_tx diff --git a/network-mock/src/network.rs b/network-mock/src/network.rs index cc590ff5f3..d2c3246a46 100644 --- a/network-mock/src/network.rs +++ b/network-mock/src/network.rs @@ -581,4 +581,12 @@ impl Network for MockNetwork { fn get_peer_info(&self, peer_id: Self::PeerId) -> Option { self.peers.read().get(&peer_id).cloned() } + + async fn get_peers_by_services( + &self, + _services: Services, + _peers: usize, + ) -> Result, MockNetworkError> { + Ok(self.get_peers()) + } } diff --git a/blockchain/src/history/history_tree_proof.rs b/primitives/transaction/src/history_proof.rs similarity index 94% rename from blockchain/src/history/history_tree_proof.rs rename to primitives/transaction/src/history_proof.rs index a8b217fc64..82c27b98a3 100644 --- a/blockchain/src/history/history_tree_proof.rs +++ b/primitives/transaction/src/history_proof.rs @@ -4,13 +4,14 @@ use beserial::{ }; use nimiq_hash::Blake2bHash; use nimiq_mmr::mmr::proof::Proof; -use nimiq_transaction::extended_transaction::ExtendedTransaction; + +use crate::extended_transaction::ExtendedTransaction; /// Struct containing a vector of extended transactions together with a Merkle proof for them. It /// allows one to prove/verify that specific transactions are part of the History Tree. pub struct HistoryTreeProof { - pub(crate) proof: Proof, - pub(crate) positions: Vec, + pub proof: Proof, + pub positions: Vec, pub history: Vec, } diff --git a/primitives/transaction/src/lib.rs b/primitives/transaction/src/lib.rs index 5a9675071b..e1b92f9560 100644 --- a/primitives/transaction/src/lib.rs +++ b/primitives/transaction/src/lib.rs @@ -28,6 +28,7 @@ use crate::account::AccountTransactionVerification; pub mod account; pub mod extended_transaction; +pub mod history_proof; pub mod inherent; pub mod reward; @@ -205,6 +206,7 @@ impl PartialEq for ExecutedTransaction { } #[derive(Clone, Eq, Debug)] +#[cfg_attr(feature = "serde-derive", derive(serde::Serialize, serde::Deserialize))] #[repr(C)] pub struct Transaction { pub data: Vec, diff --git a/web-client/Cargo.toml b/web-client/Cargo.toml index 4ac529cf2b..a6ba4520a1 100644 --- a/web-client/Cargo.toml +++ b/web-client/Cargo.toml @@ -24,6 +24,7 @@ crate-type = ["cdylib"] futures = { package = "futures-util", version = "0.3" } js-sys = "0.3" log = { package = "tracing", version = "0.1", features = ["log"] } +serde = {version = "1.0.152", features = ["derive"] } serde-wasm-bindgen = "0.4" wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" @@ -37,7 +38,7 @@ nimiq-hash = { path = "../hash" } nimiq-keys = { path = "../keys" } nimiq-network-interface = { path = "../network-interface" } nimiq-primitives = {path = "../primitives", features = ["coin", "networks"]} -nimiq-transaction = { path = "../primitives/transaction" } +nimiq-transaction = { path = "../primitives/transaction", features=["serde-derive"] } nimiq-transaction-builder = { path = "../transaction-builder" } [dependencies.nimiq] diff --git a/web-client/src/address.rs b/web-client/src/address.rs index e1ea081155..6e30a7eaab 100644 --- a/web-client/src/address.rs +++ b/web-client/src/address.rs @@ -41,4 +41,8 @@ impl Address { pub fn native_ref(&self) -> &nimiq_keys::Address { &self.inner } + + pub fn native(&self) -> nimiq_keys::Address { + self.inner.clone() + } } diff --git a/web-client/src/lib.rs b/web-client/src/lib.rs index b571624f8b..5f3d48d2df 100644 --- a/web-client/src/lib.rs +++ b/web-client/src/lib.rs @@ -23,9 +23,11 @@ use nimiq_network_interface::{ network::{CloseReason, Network, NetworkEvent}, Multiaddr, }; +use nimiq_transaction::ExecutedTransaction; +use crate::address::Address; use crate::peer_info::PeerInfo; -use crate::transaction::Transaction; +use crate::transaction::{Transaction, Transactions}; use crate::transaction_builder::TransactionBuilder; use crate::utils::{from_network_id, to_network_id}; @@ -556,6 +558,42 @@ impl Client { self.listener_id += 1; self.listener_id } + + /// This function is used to query the network for transactions from some specific address, that + /// have been included in the chain. + /// The obtained transactions correspond to extended transactions. + /// They are verified before being returned. + /// Up to a max number of transactions are returned from newest to oldest. + /// A minimum number of peers needs to be specified. + /// If the network does not have at least min_peers to query, then an error is returned + #[wasm_bindgen(js_name = getTransactionsByAddress)] + pub async fn get_transations_from_address( + &self, + address: Address, + max: u16, + min_peers: usize, + ) -> Result { + let transactions = self + .inner + .consensus_proxy() + .request_transactions_by_address(address.native(), min_peers, Some(max)) + .await?; + + let executed_txs: Vec = transactions + .into_iter() + .map(|ext_transaction| ext_transaction.into_transaction().unwrap()) + .collect(); + + // TODO: We are converting executed transactions into regular transactions, which loses the execution result of the txn + // This could be provided as an aditional field. + let js_transactions: Vec = executed_txs + .into_iter() + .map(|executed_tx| Transaction::from_native(executed_tx.get_raw_transaction().clone())) + .collect(); + + // We serialize to JSON using `serde-wasm-bindgen`, in order to be able to use the transactions from JS side (we return a JsValue) + Ok(serde_wasm_bindgen::to_value(&Transactions::new(js_transactions)).unwrap()) + } } #[wasm_bindgen] diff --git a/web-client/src/transaction.rs b/web-client/src/transaction.rs index a531f5fcb5..53b2b974a2 100644 --- a/web-client/src/transaction.rs +++ b/web-client/src/transaction.rs @@ -10,6 +10,20 @@ use crate::address::Address; use crate::key_pair::KeyPair; use crate::utils::{from_network_id, to_network_id}; +#[wasm_bindgen] +#[derive(serde::Serialize, serde::Deserialize)] +/// This struct reperesents a list of transactions. +/// It is simply a wrapper around Transaction to represent more than one transaction +pub struct Transactions { + txns: Vec, +} + +impl Transactions { + pub fn new(txns: Vec) -> Self { + Transactions { txns } + } +} + /// Transactions describe a transfer of value, usually from the sender to the recipient. /// However, transactions can also have no value, when they are used to _signal_ a change in the staking contract. /// @@ -18,6 +32,7 @@ use crate::utils::{from_network_id, to_network_id}; /// Transactions require a valid signature proof over their serialized content. /// Furthermore, transactions are only valid for 2 hours after their validity-start block height. #[wasm_bindgen(inspectable)] +#[derive(Clone, serde::Serialize, serde::Deserialize)] pub struct Transaction { inner: nimiq_transaction::Transaction, } From f121544e76c9622ea0bbe246ee6f9a2231e89c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren?= Date: Thu, 2 Mar 2023 23:37:17 +0100 Subject: [PATCH 2/3] Fix proofs for RequestTransactionsProof for ongoing epochs --- consensus/src/consensus/consensus_proxy.rs | 5 +-- consensus/src/messages/handlers.rs | 40 +++++++++++++++------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/consensus/src/consensus/consensus_proxy.rs b/consensus/src/consensus/consensus_proxy.rs index 7f3b2dd407..bcedfaf5c1 100644 --- a/consensus/src/consensus/consensus_proxy.rs +++ b/consensus/src/consensus/consensus_proxy.rs @@ -123,8 +123,9 @@ impl ConsensusProxy { .map_or(false, |result| result); if verification_result { - verified_transactions - .insert(transaction.tx_hash(), transaction); + for tx in proof.history { + verified_transactions.insert(tx.tx_hash(), tx); + } } else { // The proof didn't verify so we disconnect from this peer log::debug!(peer=%peer_id,"Disconnecting from peer because the transaction proof didn't verify"); diff --git a/consensus/src/messages/handlers.rs b/consensus/src/messages/handlers.rs index eb47db9c80..574cbffbb9 100644 --- a/consensus/src/messages/handlers.rs +++ b/consensus/src/messages/handlers.rs @@ -314,20 +314,34 @@ impl Handle>> for RequestTrans .history_store .prove(Policy::epoch_at(self.block_number), hashes, None); - //If we obtained a proof, we need to supply the corresponding block + // If we obtained a proof, we need to supply the corresponding block if proof.is_some() { - block = blockchain - .chain_store - .get_block_at(self.block_number, false, None) - .ok() - .and_then(|block| { - if block.is_macro() { - // We expect a macro block - Some(block) - } else { - None - } - }); + // If the block_number to proof is in a finalized epoch, use the epoch's finalization block + let election_block_number = if Policy::is_election_block_at(self.block_number) { + self.block_number + } else { + Policy::election_block_after(self.block_number) + }; + + if election_block_number <= blockchain.block_number() { + block = blockchain + .chain_store + .get_block_at(election_block_number, false, None) + .ok(); + } else { + // Otherwise, the transaction proof was made at the current history store state, so return the current + // head block to prove it. + let mut head = blockchain.head(); + + // Convert to light block by removing the block body + match head { + Block::Macro(ref mut block) => block.body = None, + Block::Micro(ref mut block) => block.body = None, + } + + block = Some(head); + } + if block.is_none() { log::error!("We are supplying a transaction proof but not a block, which can be interpreted as malicious behaviour"); } From 22f575c0b260a09e22bdb59903d643fe3fd8b884 Mon Sep 17 00:00:00 2001 From: viquezclaudio Date: Thu, 2 Mar 2023 18:07:19 -0600 Subject: [PATCH 3/3] Applied further PR comments --- network-libp2p/src/connection_pool/behaviour.rs | 2 +- network-libp2p/src/network.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/network-libp2p/src/connection_pool/behaviour.rs b/network-libp2p/src/connection_pool/behaviour.rs index 3376e59f5b..b73c0f8caa 100644 --- a/network-libp2p/src/connection_pool/behaviour.rs +++ b/network-libp2p/src/connection_pool/behaviour.rs @@ -365,7 +365,7 @@ impl ConnectionPoolBehaviour { } /// This function is used to select a list of peers, based on services flag, in order to dial them. - /// num_peers is used to specify how many peers are selected + /// `num_peers` is used to specify how many peers are selected /// The number of peers returned equals num_peers unless there are less available peers pub fn choose_peers_to_dial_by_services( &self, diff --git a/network-libp2p/src/network.rs b/network-libp2p/src/network.rs index df94802f12..1fa3af14b8 100644 --- a/network-libp2p/src/network.rs +++ b/network-libp2p/src/network.rs @@ -1781,8 +1781,8 @@ impl NetworkInterface for Network { } } - // If we don't have enough connected peer that support the desired services, - // we tell the network to connect to new peers that supports such services. + // If we don't have enough connected peers that support the desired services, + // we tell the network to connect to new peers that support such services. if filtered_peers.len() < min_peers { let num_peers = min_peers - filtered_peers.len();