Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
b-yap committed Feb 23, 2024
1 parent 74c982d commit e2d1d29
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 82 deletions.
6 changes: 1 addition & 5 deletions clients/vault/src/issue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ use wallet::{
types::FilterWith, LedgerTxEnvMap, Slot, SlotTask, SlotTaskStatus, TransactionResponse,
};

use crate::{
oracle::{types::Slot as OracleSlot, OracleAgent},
ArcRwLock, Error, Event,
};
use crate::{oracle::OracleAgent, ArcRwLock, Error, Event};

fn is_vault(p1: &PublicKey, p2_raw: [u8; 32]) -> bool {
return *p1.as_binary() == p2_raw
Expand Down Expand Up @@ -307,7 +304,6 @@ pub async fn execute_issue(
slot: Slot,
sender: tokio::sync::oneshot::Sender<SlotTaskStatus>,
) {
let slot = OracleSlot::from(slot);
// Get the proof of the given slot
let proof =
match oracle_agent.get_proof(slot).await {
Expand Down
3 changes: 2 additions & 1 deletion clients/vault/src/oracle/collector/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use stellar_relay_lib::sdk::{
types::{GeneralizedTransactionSet, ScpEnvelope, TransactionSet},
TransactionSetType,
};
use wallet::Slot;

use crate::oracle::types::{
EnvelopesMap, LimitedFifoMap, Slot, TxSetHash, TxSetHashAndSlotMap, TxSetMap,
Expand All @@ -29,7 +30,7 @@ pub struct ScpMessageCollector {
txset_and_slot_map: Arc<RwLock<TxSetHashAndSlotMap>>,

/// The last slot with an SCPEnvelope
last_slot_index: u64,
last_slot_index: Slot,

public_network: bool,

Expand Down
1 change: 1 addition & 0 deletions clients/vault/src/oracle/collector/proof_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use stellar_relay_lib::sdk::{
types::{ScpEnvelope, ScpHistoryEntry, ScpStatementPledges, StellarMessage},
InitExt, TransactionSetType, XdrCodec,
};
use wallet::Slot;

use crate::oracle::{
constants::{get_min_externalized_messages, MAX_SLOTS_TO_REMEMBER},
Expand Down
2 changes: 1 addition & 1 deletion clients/vault/src/oracle/types/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use std::collections::BTreeMap;
use tokio::sync::mpsc;

use stellar_relay_lib::sdk::types::{Hash, StellarMessage, Uint64};
use wallet::Slot;

pub type Slot = Uint64;
pub type TxHash = Hash;
pub type TxSetHash = Hash;
pub type Filename = String;
Expand Down
7 changes: 6 additions & 1 deletion clients/wallet/src/horizon/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ pub struct FeeDistribution {

#[derive(Deserialize, Debug)]
pub struct FeeStats {
#[serde(deserialize_with = "de_string_to_u32")]
#[serde(deserialize_with = "de_string_to_u64")]
pub last_ledger: Slot,
#[serde(deserialize_with = "de_string_to_u32")]
pub last_ledger_base_fee: u32,
Expand Down Expand Up @@ -492,6 +492,11 @@ impl<C: HorizonClient> TransactionsResponseIter<C> {
}
}

/// returns the next TransactionResponse in reverse order
pub fn next_back(&mut self) -> Option<TransactionResponse> {
self.records.pop()
}

/// returns the TransactionResponse in the middle of the list
pub fn middle(&mut self) -> Option<TransactionResponse> {
if !self.is_empty() {
Expand Down
186 changes: 113 additions & 73 deletions clients/wallet/src/resubmissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,20 +215,58 @@ fn is_memo_match(tx1: &Transaction, tx2: &TransactionResponse) -> bool {
}

#[doc(hidden)]
/// A helper function which:
/// Returns true if the transaction in the middle of the list is a match;
/// Returns false if NOT a match;
/// Returns None if transaction can be found else where by narrowing down the search:
/// * if the sequence number of the transaction is > than what we're looking for, then update the
/// iterator by removing the first half of the list;
/// * else remove the last half of the list
/// A helper function which returns:
/// Ok(true) if both transactions match;
/// Ok(false) if the source account and the sequence number match, but NOT the MEMO;
/// Err(None) if it's absolutely NOT a match
/// Err(Some(SequenceNumber)) if the sequence number can be used for further logic checking
///
/// # Arguments
///
/// * `tx` - the transaction we want to confirm if it's already submitted
/// * `tx_resp` - the transaction response from Horizon
/// * `public_key` - the public key of the wallet
fn _check_transaction_match(
tx: &Transaction,
tx_resp: &TransactionResponse,
public_key: &PublicKey,
) -> Result<bool, Option<SequenceNumber>> {
// Make sure that we are the sender and not the receiver because otherwise an
// attacker could send a transaction to us with the target memo and we'd wrongly
// assume that we already submitted this transaction.
if !is_source_account_match(public_key, &tx_resp) {
return Err(None)
}

let Ok(source_account_sequence) = tx_resp.source_account_sequence() else {
tracing::warn!("_check_transaction_match(): cannot extract sequence number of transaction response: {tx_resp:?}");
return Err(None)
};

// check if the sequence number is the same as this response
if tx.seq_num == source_account_sequence {
// Check if the transaction contains the memo we want to send
return Ok(is_memo_match(tx, &tx_resp))
}

Err(Some(source_account_sequence))
}

#[doc(hidden)]
/// A helper function which returns:
/// true if the transaction in the MIDDLE of the list is a match;
/// false if NOT a match;
/// None if a match can be found else where by narrowing down the search:
/// * if the sequence number of the transaction is < than what we're looking for, then update the
/// iterator by removing the LAST half of the list;
/// * else remove the FIRST half of the list
///
/// # Arguments
///
/// * `iter` - the iterator to iterate over a list of `TransactionResponse`
/// * `tx` - the transaction we want to confirm if it's already submitted
/// * `public_key` - the public key of the wallet
fn _is_transaction_already_submitted(
fn check_middle_transaction_match(
iter: &mut TransactionsResponseIter<Client>,
tx: &Transaction,
public_key: &PublicKey,
Expand All @@ -239,34 +277,56 @@ fn _is_transaction_already_submitted(
return None
};

// Make sure that we are the sender and not the receiver because otherwise an
// attacker could send a transaction to us with the target memo and we'd wrongly
// assume that we already submitted this transaction.
if !is_source_account_match(public_key, &response) {
return None
match _check_transaction_match(tx, &response, public_key) {
Ok(res) => return Some(res),
Err(Some(source_account_sequence)) => {
// if the sequence number is GREATER than this response,
// then a match must be in the first half of the list.
if tx_sequence_num > source_account_sequence {
iter.remove_last_half_records();
}
// a match must be in the last half of the list.
else {
iter.remove_first_half_records();
}
},
_ => {},
}
None
}

let Ok(source_account_sequence) = response.source_account_sequence() else {
tracing::warn!("_is_transaction_already_submitted(): cannot extract sequence number of transaction response: {response:?}");
#[doc(hidden)]
/// A helper function which returns:
/// true if the LAST transaction of the list is a match;
/// false if NOT a match;
/// None if a match can be found else where:
/// * if the sequence number of the transaction is > than what we're looking for, then update the
/// iterator by jumping to the next page;
/// * if there's no next page, then a match will never be found. Return FALSE.
async fn check_last_transaction_match(
iter: &mut TransactionsResponseIter<Client>,
tx: &Transaction,
public_key: &PublicKey,
) -> Option<bool> {
let tx_sequence_num = tx.seq_num;
let Some(response) = iter.next_back() else {
return None
};

// check if the sequence number is the same as this response
if tx_sequence_num == source_account_sequence {
// Check if the transaction contains the memo we want to send
return Some(is_memo_match(tx, &response))
}

// if the sequence number is greater than this response,
// then the transaction must be in the first half of the list.
if tx_sequence_num > source_account_sequence {
iter.remove_last_half_records();
}
// the transaction must be in the last half of the list.
else {
iter.remove_first_half_records();
match _check_transaction_match(tx, &response, public_key) {
Ok(res) => return Some(res),
Err(Some(source_account_sequence)) => {
// if the sequence number is LESSER than this response,
// then a match is possible on the NEXT page
if tx_sequence_num < source_account_sequence {
if let None = iter.jump_to_next_page().await {
// there's no pages left, meaning there's no other transactions to compare
return Some(false)
}
}
},
_ => {},
}

None
}

Expand Down Expand Up @@ -325,7 +385,6 @@ impl StellarWallet {
/// returns true if a transaction already exists and WAS submitted successfully.
async fn is_transaction_already_submitted(&self, tx: &Transaction) -> bool {
let tx_sequence_num = tx.seq_num;
let default_page_size = SequenceNumber::from(DEFAULT_PAGE_SIZE);
let own_public_key = self.public_key();

// get the iterator
Expand All @@ -337,57 +396,38 @@ impl StellarWallet {
},
};

// iterate over the transactions
// iterate over the transactions, starting from
// the TOP (the largest sequence number/the latest transaction)
while let Some(response) = iter.next().await {
// Make sure that we are the sender and not the receiver because otherwise an
// attacker could send a transaction to us with the target memo and we'd wrongly
// assume that we already submitted this transaction.
if !is_source_account_match(&own_public_key, &response) {
// move to the next response
continue
}

let Ok(source_account_sequence) = response.source_account_sequence() else {
tracing::warn!("is_transaction_already_submitted(): cannot extract sequence number of transaction response: {response:?}");
// move to the next response
continue
let top_sequence_num = match _check_transaction_match(tx, &response, &own_public_key) {
// return result for partial match
Ok(res) => return res,
// continue if it is absolutely not a match
Err(None) => continue,
// further logic checking required
Err(Some(seq_num)) => seq_num,
};

// check if the sequence number is the same as this response
if tx_sequence_num == source_account_sequence {
// Check if the transaction contains the memo we want to send
return is_memo_match(tx, &response)
}

// if the sequence number is greater than this response,
// if the sequence number is GREATER than this response,
// no other transaction will ever match with it.
if tx_sequence_num > source_account_sequence {
if tx_sequence_num > top_sequence_num {
break
}

// if the sequence number is greater than the last response of the iterator,
// then the transaction is possibly in THIS page
if tx_sequence_num >= source_account_sequence.saturating_sub(default_page_size) {
// check the middle response OR remove half of the responses that won't match.
if let Some(result) =
_is_transaction_already_submitted(&mut iter, tx, &own_public_key)
{
// if the middle response matched (both source account and sequence number),
// return that result
return result
}
// if no match was found, move to the next response
continue
// check the middle response OR remove half of the responses that won't match.
if let Some(result) = check_middle_transaction_match(&mut iter, tx, &own_public_key) {
// if the middle response matched (both source account and sequence number),
// return that result
return result
}

// if the sequence number is lesser than the last response of the iterator,
// then the transaction is possibly on the NEXT page
if tx_sequence_num < source_account_sequence.saturating_sub(default_page_size) {
if let None = iter.jump_to_next_page().await {
// there's no pages left
break
}
// if no match was found, check the last response OR jump to the next page
if let Some(result) = check_last_transaction_match(&mut iter, tx, &own_public_key).await
{
return result
}

// if no match was found, continue to the next response
}

false
Expand Down Expand Up @@ -431,7 +471,7 @@ mod test {
#[tokio::test]
#[serial]
async fn check_is_transaction_already_submitted() {
let wallet = wallet_with_storage("resources/check_is_transaction_already_submitted")
let wallet = wallet_with_storage("resources/checkcheck_middle_transaction_match")
.expect("")
.clone();
let mut wallet = wallet.write().await;
Expand Down
2 changes: 1 addition & 1 deletion clients/wallet/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use primitives::stellar::TransactionEnvelope;
use std::{collections::HashMap, fmt, fmt::Formatter};

pub type PagingToken = u128;
pub type Slot = u32;
pub type Slot = u64;
pub type StatusCode = u16;
pub type LedgerTxEnvMap = HashMap<Slot, TransactionEnvelope>;

Expand Down

0 comments on commit e2d1d29

Please sign in to comment.