Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion client/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,8 @@ pub fn http_client_with_auth(url: &str, auth_token: &str) -> Result<HttpClient,
"Authorization",
HeaderValue::from_str(&format!("Basic {auth_token}")).unwrap(),
);
HttpClientBuilder::default().set_headers(headers).build(url)
HttpClientBuilder::default()
.set_headers(headers)
.request_timeout(std::time::Duration::from_secs(1200))
.build(url)
}
10 changes: 7 additions & 3 deletions client/src/bin/space-cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,11 @@ enum Commands {
/// List won spaces including ones
/// still in auction with a winning bid
#[command(name = "listspaces")]
ListSpaces,
ListSpaces {
/// Use v2 implementation
#[arg(long)]
v2: bool,
},
/// List unspent auction outputs i.e. outputs that can be
/// auctioned off in the bidding process
#[command(name = "listbidouts")]
Expand Down Expand Up @@ -768,9 +772,9 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client
.await?;
print_list_transactions(txs, cli.format);
}
Commands::ListSpaces => {
Commands::ListSpaces { v2 } => {
let tip = cli.client.get_server_info().await?;
let spaces = cli.client.wallet_list_spaces(&cli.wallet).await?;
let spaces = cli.client.wallet_list_spaces(&cli.wallet, Some(v2)).await?;
print_list_spaces_response(tip.tip.height, spaces, cli.format);
}
Commands::Balance => {
Expand Down
4 changes: 3 additions & 1 deletion client/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ pub trait Rpc {
async fn wallet_list_spaces(
&self,
wallet: &str,
v2: Option<bool>,
) -> Result<ListSpacesResponse, ErrorObjectOwned>;

#[method(name = "walletlistunspent")]
Expand Down Expand Up @@ -1089,10 +1090,11 @@ impl RpcServer for RpcServerImpl {
async fn wallet_list_spaces(
&self,
wallet: &str,
v2: Option<bool>,
) -> Result<ListSpacesResponse, ErrorObjectOwned> {
self.wallet(&wallet)
.await?
.send_list_spaces()
.send_list_spaces(v2.unwrap_or(false))
.await
.map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::<String>))
}
Expand Down
121 changes: 116 additions & 5 deletions client/src/wallets.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::{collections::BTreeMap, str::FromStr, time::Duration};

use std::collections::{HashMap, HashSet};
use anyhow::anyhow;
use clap::ValueEnum;
use futures::{stream::FuturesUnordered, StreamExt};
Expand Down Expand Up @@ -181,6 +181,7 @@ pub enum WalletCommand {
resp: crate::rpc::Responder<anyhow::Result<Vec<TxInfo>>>,
},
ListSpaces {
v2: bool,
resp: crate::rpc::Responder<anyhow::Result<ListSpacesResponse>>,
},
Buy {
Expand Down Expand Up @@ -477,8 +478,12 @@ impl RpcWallet {
let transactions = Self::list_transactions(wallet, count, skip);
_ = resp.send(transactions);
}
WalletCommand::ListSpaces { resp } => {
let result = Self::list_spaces(wallet, state);
WalletCommand::ListSpaces { v2, resp } => {
let result = if v2 {
Self::list_spaces_v2(wallet, state)
} else {
Self::list_spaces(wallet, state)
};
_ = resp.send(result);
}
WalletCommand::ListBidouts { resp } => {
Expand Down Expand Up @@ -810,6 +815,112 @@ impl RpcWallet {
Ok(())
}

fn list_spaces_v2(
wallet: &mut SpacesWallet,
chain: &mut LiveSnapshot,
) -> anyhow::Result<ListSpacesResponse> {
let fn_start = std::time::Instant::now();

let unspent = wallet.list_unspent_with_details(chain)?;
log::info!("list_spaces: list_unspent_with_details took {:?} ({} results)", fn_start.elapsed(), unspent.len());

let t = std::time::Instant::now();
let owned_spaces: HashSet<_> = unspent
.iter()
.filter_map(|out| out.space.as_ref().map(|s| s.name.to_string()))
.collect();
log::info!("list_spaces: owned_spaces collect took {:?} ({} spaces)", t.elapsed(), owned_spaces.len());

let t = std::time::Instant::now();
let mut recent_events: HashMap<Txid, Vec<TxEvent>> = HashMap::new();
for (txid, event) in wallet.list_recent_events()? {
if !event.space.as_ref().is_some_and(|s| owned_spaces.contains(s)) {
recent_events.entry(txid).or_default().push(event);
}
}
let event_count: usize = recent_events.values().map(|v| v.len()).sum();
log::info!("list_spaces: list_recent_events + filter took {:?} ({} txids, {} events)", t.elapsed(), recent_events.len(), event_count);

let t = std::time::Instant::now();
let mut recent_events_with_txs = Vec::new();
for tx in wallet.transactions() {
let Some(events) = recent_events.remove(&tx.tx_node.txid) else {
continue;
};
for event in events {
recent_events_with_txs.push((Some(tx.clone()), event));
}
if recent_events.is_empty() {
break;
}
}
recent_events_with_txs
.extend(recent_events.into_values().flatten().map(|e| (None, e)));
log::info!("list_spaces: transactions join took {:?} ({} matched events)", t.elapsed(), recent_events_with_txs.len());

let t = std::time::Instant::now();
let mut pending = vec![];
let mut outbid = vec![];
let mut chain_lookups = 0u32;
for (tx, event) in recent_events_with_txs {
let name = SLabel::from_str(event.space.as_ref().unwrap()).expect("valid space name");
if tx.as_ref()
.is_some_and(|tx| !tx.chain_position.is_confirmed()) {
pending.push(name);
continue;
}
let spacehash = SpaceKey::from(Sha256::hash(name.as_ref()));
chain_lookups += 1;
let space = chain.get_space_info(&spacehash)?;
if let Some(space) = space {
if space.spaceout.space.as_ref().unwrap().is_owned() {
continue;
}
if tx.is_none() {
outbid.push(space);
continue;
}
if event.previous_spaceout
.is_some_and(|input| input == space.outpoint()) {
continue;
}
outbid.push(space);
}
}
log::info!("list_spaces: outbid/pending classification took {:?} ({} chain lookups, {} pending, {} outbid)", t.elapsed(), chain_lookups, pending.len(), outbid.len());

let t = std::time::Instant::now();
let mut owned = vec![];
let mut winning = vec![];
for wallet_output in unspent.into_iter().filter(|output| output.space.is_some()) {
let entry = FullSpaceOut {
txid: wallet_output.output.outpoint.txid,
spaceout: SpaceOut {
n: wallet_output.output.outpoint.vout as _,
space: wallet_output.space,
script_pubkey: wallet_output.output.txout.script_pubkey,
value: wallet_output.output.txout.value,
},
};

if entry.spaceout.space.as_ref().expect("space").is_owned() {
owned.push(entry);
} else {
winning.push(entry);
}
}
log::info!("list_spaces: owned/winning split took {:?} ({} owned, {} winning)", t.elapsed(), owned.len(), winning.len());

log::info!("list_spaces: total elapsed {:?}", fn_start.elapsed());

Ok(ListSpacesResponse {
pending,
winning,
outbid,
owned,
})
}

fn list_spaces(
wallet: &mut SpacesWallet,
state: &mut LiveSnapshot,
Expand Down Expand Up @@ -1503,9 +1614,9 @@ impl RpcWallet {
resp_rx.await?
}

pub async fn send_list_spaces(&self) -> anyhow::Result<ListSpacesResponse> {
pub async fn send_list_spaces(&self, v2: bool) -> anyhow::Result<ListSpacesResponse> {
let (resp, resp_rx) = oneshot::channel();
self.sender.send(WalletCommand::ListSpaces { resp }).await?;
self.sender.send(WalletCommand::ListSpaces { v2, resp }).await?;
resp_rx.await?
}

Expand Down
44 changes: 22 additions & 22 deletions client/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ async fn it_should_open_a_space_for_auction(rig: &TestRig) -> anyhow::Result<()>
assert!(tx_res.error.is_none(), "expect no errors for simple open");
}
assert_eq!(response.result.len(), 2, "must be 2 transactions");
let alices_spaces = rig.spaced.client.wallet_list_spaces(ALICE).await?;
let alices_spaces = rig.spaced.client.wallet_list_spaces(ALICE, None).await?;
assert!(alices_spaces.pending.first().is_some_and(|s| s.to_string() == TEST_SPACE), "must be a pending space");

rig.mine_blocks(1, None).await?;
rig.wait_until_synced().await?;
let alices_spaces = rig.spaced.client.wallet_list_spaces(ALICE).await?;
let alices_spaces = rig.spaced.client.wallet_list_spaces(ALICE, None).await?;
assert!(alices_spaces.pending.is_empty(), "must have no pending spaces");

let fullspaceout = rig.spaced.client.get_space(TEST_SPACE).await?;
Expand Down Expand Up @@ -83,8 +83,8 @@ async fn it_should_allow_outbidding(rig: &TestRig) -> anyhow::Result<()> {
rig.wait_until_synced().await?;
rig.wait_until_wallet_synced(BOB).await?;
rig.wait_until_wallet_synced(ALICE).await?;
let bobs_spaces = rig.spaced.client.wallet_list_spaces(BOB).await?;
let alices_spaces = rig.spaced.client.wallet_list_spaces(ALICE).await?;
let bobs_spaces = rig.spaced.client.wallet_list_spaces(BOB, None).await?;
let alices_spaces = rig.spaced.client.wallet_list_spaces(ALICE, None).await?;
let alices_balance = rig.spaced.client.wallet_get_balance(ALICE).await?;

let result = wallet_do(
Expand All @@ -101,15 +101,15 @@ async fn it_should_allow_outbidding(rig: &TestRig) -> anyhow::Result<()> {

println!("{}", serde_json::to_string_pretty(&result).unwrap());

let bob_spaces_updated = rig.spaced.client.wallet_list_spaces(BOB).await?;
let bob_spaces_updated = rig.spaced.client.wallet_list_spaces(BOB, None).await?;
assert!(bob_spaces_updated.pending.first().is_some_and(|s| s.to_string() == TEST_SPACE), "must be a pending space");

rig.mine_blocks(1, None).await?;
rig.wait_until_synced().await?;
rig.wait_until_wallet_synced(BOB).await?;
rig.wait_until_wallet_synced(ALICE).await?;
let bob_spaces_updated = rig.spaced.client.wallet_list_spaces(BOB).await?;
let alice_spaces_updated = rig.spaced.client.wallet_list_spaces(ALICE).await?;
let bob_spaces_updated = rig.spaced.client.wallet_list_spaces(BOB, None).await?;
let alice_spaces_updated = rig.spaced.client.wallet_list_spaces(ALICE, None).await?;
let alices_balance_updated = rig.spaced.client.wallet_get_balance(ALICE).await?;

assert_eq!(
Expand Down Expand Up @@ -189,8 +189,8 @@ async fn it_should_only_accept_forced_zero_value_bid_increments_and_revoke(
// Bob outbids alice
rig.wait_until_wallet_synced(BOB).await?;
rig.wait_until_wallet_synced(EVE).await?;
let eve_spaces = rig.spaced.client.wallet_list_spaces(EVE).await?;
let bob_spaces = rig.spaced.client.wallet_list_spaces(BOB).await?;
let eve_spaces = rig.spaced.client.wallet_list_spaces(EVE, None).await?;
let bob_spaces = rig.spaced.client.wallet_list_spaces(BOB, None).await?;
let bob_balance = rig.spaced.client.wallet_get_balance(BOB).await?;

let fullspaceout = rig
Expand Down Expand Up @@ -271,9 +271,9 @@ async fn it_should_only_accept_forced_zero_value_bid_increments_and_revoke(
rig.wait_until_synced().await?;
rig.wait_until_wallet_synced(BOB).await?;
rig.wait_until_wallet_synced(ALICE).await?;
let bob_spaces_updated = rig.spaced.client.wallet_list_spaces(BOB).await?;
let bob_spaces_updated = rig.spaced.client.wallet_list_spaces(BOB, None).await?;
let bob_balance_updated = rig.spaced.client.wallet_get_balance(BOB).await?;
let eve_spaces_updated = rig.spaced.client.wallet_list_spaces(EVE).await?;
let eve_spaces_updated = rig.spaced.client.wallet_list_spaces(EVE, None).await?;

assert_eq!(
bob_spaces.winning.len() - 1,
Expand Down Expand Up @@ -320,7 +320,7 @@ async fn it_should_allow_claim_on_or_after_claim_height(rig: &TestRig) -> anyhow

rig.wait_until_synced().await?;
rig.wait_until_wallet_synced(wallet).await?;
let all_spaces = rig.spaced.client.wallet_list_spaces(wallet).await?;
let all_spaces = rig.spaced.client.wallet_list_spaces(wallet, None).await?;

let result = wallet_do(
rig,
Expand All @@ -339,7 +339,7 @@ async fn it_should_allow_claim_on_or_after_claim_height(rig: &TestRig) -> anyhow

rig.wait_until_synced().await?;
rig.wait_until_wallet_synced(wallet).await?;
let all_spaces_2 = rig.spaced.client.wallet_list_spaces(wallet).await?;
let all_spaces_2 = rig.spaced.client.wallet_list_spaces(wallet, None).await?;

assert_eq!(
all_spaces.owned.len() + 1,
Expand Down Expand Up @@ -367,7 +367,7 @@ async fn it_should_allow_batch_transfers_refreshing_expire_height(
) -> anyhow::Result<()> {
rig.wait_until_wallet_synced(ALICE).await?;
rig.wait_until_synced().await?;
let all_spaces = rig.spaced.client.wallet_list_spaces(ALICE).await?;
let all_spaces = rig.spaced.client.wallet_list_spaces(ALICE, None).await?;
let registered_spaces: Vec<_> = all_spaces
.owned
.iter()
Expand Down Expand Up @@ -399,7 +399,7 @@ async fn it_should_allow_batch_transfers_refreshing_expire_height(

rig.wait_until_synced().await?;
rig.wait_until_wallet_synced(ALICE).await?;
let all_spaces_2 = rig.spaced.client.wallet_list_spaces(ALICE).await?;
let all_spaces_2 = rig.spaced.client.wallet_list_spaces(ALICE, None).await?;

assert_eq!(
all_spaces.owned.len(),
Expand Down Expand Up @@ -432,7 +432,7 @@ async fn it_should_allow_batch_transfers_refreshing_expire_height(
async fn it_should_allow_applying_script_in_batch(rig: &TestRig) -> anyhow::Result<()> {
rig.wait_until_wallet_synced(ALICE).await?;
rig.wait_until_synced().await?;
let all_spaces = rig.spaced.client.wallet_list_spaces(ALICE).await?;
let all_spaces = rig.spaced.client.wallet_list_spaces(ALICE, None).await?;
let registered_spaces: Vec<_> = all_spaces
.owned
.iter()
Expand Down Expand Up @@ -465,7 +465,7 @@ async fn it_should_allow_applying_script_in_batch(rig: &TestRig) -> anyhow::Resu

rig.wait_until_synced().await?;
rig.wait_until_wallet_synced(ALICE).await?;
let all_spaces_2 = rig.spaced.client.wallet_list_spaces(ALICE).await?;
let all_spaces_2 = rig.spaced.client.wallet_list_spaces(ALICE, None).await?;

assert_eq!(
all_spaces.owned.len(),
Expand Down Expand Up @@ -998,7 +998,7 @@ async fn it_can_batch_txs(rig: &TestRig) -> anyhow::Result<()> {
let bob_spaces = rig
.spaced
.client
.wallet_list_spaces(BOB)
.wallet_list_spaces(BOB, None)
.await
.expect("bob spaces");
assert!(
Expand All @@ -1017,7 +1017,7 @@ async fn it_can_batch_txs(rig: &TestRig) -> anyhow::Result<()> {
let alice_spaces = rig
.spaced
.client
.wallet_list_spaces(ALICE)
.wallet_list_spaces(ALICE, None)
.await
.expect("alice spaces");
let batch1 = alice_spaces
Expand Down Expand Up @@ -1166,7 +1166,7 @@ async fn it_should_allow_buy_sell(rig: &TestRig) -> anyhow::Result<()> {
let alice_spaces = rig
.spaced
.client
.wallet_list_spaces(ALICE)
.wallet_list_spaces(ALICE, None)
.await
.expect("alice spaces");
let space = alice_spaces
Expand Down Expand Up @@ -1227,7 +1227,7 @@ async fn it_should_allow_buy_sell(rig: &TestRig) -> anyhow::Result<()> {
let bob_spaces = rig
.spaced
.client
.wallet_list_spaces(BOB)
.wallet_list_spaces(BOB, None)
.await
.expect("bob spaces");

Expand Down Expand Up @@ -1260,7 +1260,7 @@ async fn it_should_allow_sign_verify_messages(rig: &TestRig) -> anyhow::Result<(
let alice_spaces = rig
.spaced
.client
.wallet_list_spaces(BOB)
.wallet_list_spaces(BOB, None)
.await
.expect("bob spaces");
let space = alice_spaces
Expand Down
Loading