Skip to content

Commit 15b2bab

Browse files
committed
Fix & add some mempool integration tests
1 parent 4fde6da commit 15b2bab

File tree

2 files changed

+204
-21
lines changed

2 files changed

+204
-21
lines changed

node/tests/integration_tests.rs

Lines changed: 194 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::path::{PathBuf};
22
use std::str::FromStr;
33
use protocol::bitcoin::{Amount, FeeRate};
44
use protocol::constants::RENEWAL_INTERVAL;
5-
use protocol::Covenant;
5+
use protocol::{Covenant};
66
use protocol::script::SpaceScript;
77
use spaced::rpc::{BidParams, ExecuteParams, OpenParams, RegisterParams, RpcClient, RpcWalletRequest, RpcWalletTxBuilder, TransferSpacesParams};
88
use spaced::wallets::{AddressKind, WalletResponse};
@@ -13,8 +13,8 @@ const ALICE: &str = "wallet_99";
1313
const BOB: &str = "wallet_98";
1414
const EVE: &str = "wallet_93";
1515

16-
const TEST_SPACE : &str = "@example123";
17-
const TEST_INITIAL_BID : u64 = 5000;
16+
const TEST_SPACE: &str = "@example123";
17+
const TEST_INITIAL_BID: u64 = 5000;
1818

1919

2020
/// alice opens [TEST_SPACE] for auction
@@ -54,6 +54,7 @@ async fn it_should_open_a_space_for_auction(rig: &TestRig) -> anyhow::Result<()>
5454
/// Bob outbids alice by 1 sat
5555
async fn it_should_allow_outbidding(rig: &TestRig) -> anyhow::Result<()> {
5656
// Bob outbids alice
57+
rig.wait_until_synced().await?;
5758
rig.wait_until_wallet_synced(BOB).await?;
5859
rig.wait_until_wallet_synced(ALICE).await?;
5960
let bobs_spaces = rig.spaced.client.wallet_list_spaces(BOB).await?;
@@ -67,7 +68,7 @@ async fn it_should_allow_outbidding(rig: &TestRig) -> anyhow::Result<()> {
6768
name: TEST_SPACE.to_string(),
6869
amount: TEST_INITIAL_BID + 1,
6970
}),
70-
],false).await.expect("send request");
71+
], false).await.expect("send request");
7172

7273
println!("{}", serde_json::to_string_pretty(&result).unwrap());
7374
rig.mine_blocks(1, None).await?;
@@ -82,7 +83,7 @@ async fn it_should_allow_outbidding(rig: &TestRig) -> anyhow::Result<()> {
8283
assert_eq!(alices_spaces.len() - 1, alice_spaces_updated.len(), "alice must have one less space");
8384
assert_eq!(bobs_spaces.len() + 1, bob_spaces_updated.len(), "bob must have a new space");
8485
assert_eq!(alices_balance_updated.balance, alices_balance.balance +
85-
Amount::from_sat(TEST_INITIAL_BID+662), "alice must be refunded this exact amount");
86+
Amount::from_sat(TEST_INITIAL_BID + 662), "alice must be refunded this exact amount");
8687

8788
let fullspaceout = rig.spaced.client.get_space(TEST_SPACE).await?;
8889
let fullspaceout = fullspaceout.expect("a fullspace out");
@@ -91,7 +92,7 @@ async fn it_should_allow_outbidding(rig: &TestRig) -> anyhow::Result<()> {
9192
match space.covenant {
9293
Covenant::Bid { total_burned, burn_increment, claim_height, .. } => {
9394
assert!(claim_height.is_none(), "none for pre-auctions");
94-
assert_eq!(total_burned, Amount::from_sat(TEST_INITIAL_BID+1), "total burned");
95+
assert_eq!(total_burned, Amount::from_sat(TEST_INITIAL_BID + 1), "total burned");
9596
assert_eq!(burn_increment, Amount::from_sat(1), "burn increment only 1 sat");
9697
}
9798
_ => panic!("expected a bid covenant")
@@ -124,18 +125,45 @@ async fn it_should_only_accept_forced_zero_value_bid_increments_and_revoke(rig:
124125
amount: last_bid.to_sat(),
125126
}),
126127
],
127-
false).await.is_err(), "shouldn't be able to bid with same value unless forced");
128+
false).await.is_err(), "shouldn't be able to bid with same value unless forced");
128129

129-
// force
130-
let result = wallet_do(
131-
rig, EVE,
132-
vec![
133-
RpcWalletRequest::Bid(BidParams {
134-
name: TEST_SPACE.to_string(),
135-
amount: last_bid.to_sat(),
136-
}),
137-
],
138-
true).await.expect("send request");
130+
// force only
131+
assert!(rig.spaced.client.wallet_send_request(
132+
EVE,
133+
RpcWalletTxBuilder {
134+
bidouts: None,
135+
requests: vec![
136+
RpcWalletRequest::Bid(BidParams {
137+
name: TEST_SPACE.to_string(),
138+
amount: last_bid.to_sat(),
139+
}),
140+
],
141+
fee_rate: Some(FeeRate::from_sat_per_vb(1).expect("fee")),
142+
dust: None,
143+
force: true,
144+
confirmed_only: false,
145+
skip_tx_check: false,
146+
},
147+
).await.is_err(), "should require skip tx check");
148+
149+
// force & skip tx check
150+
let result = rig.spaced.client.wallet_send_request(
151+
EVE,
152+
RpcWalletTxBuilder {
153+
bidouts: None,
154+
requests: vec![
155+
RpcWalletRequest::Bid(BidParams {
156+
name: TEST_SPACE.to_string(),
157+
amount: last_bid.to_sat(),
158+
}),
159+
],
160+
fee_rate: Some(FeeRate::from_sat_per_vb(1).expect("fee")),
161+
dust: None,
162+
force: true,
163+
confirmed_only: false,
164+
skip_tx_check: true,
165+
},
166+
).await?;
139167

140168
println!("{}", serde_json::to_string_pretty(&result).unwrap());
141169
rig.mine_blocks(1, None).await?;
@@ -149,7 +177,7 @@ async fn it_should_only_accept_forced_zero_value_bid_increments_and_revoke(rig:
149177

150178
assert_eq!(bob_spaces.len() - 1, bob_spaces_updated.len(), "bob must have one less space");
151179
assert_eq!(bob_balance_updated.balance, bob_balance.balance +
152-
Amount::from_sat(last_bid.to_sat()+662), "alice must be refunded this exact amount");
180+
Amount::from_sat(last_bid.to_sat() + 662), "alice must be refunded this exact amount");
153181
assert_eq!(eve_spaces_updated.len(), eve_spaces.len(), "eve must have the same number of spaces");
154182

155183
let fullspaceout = rig.spaced.client.get_space(TEST_SPACE).await?;
@@ -283,7 +311,7 @@ async fn it_should_allow_applying_script_in_batch(rig: &TestRig) -> anyhow::Resu
283311
all_spaces_2.iter().for_each(|s| {
284312
let space = s.space.as_ref().expect("space");
285313
match &space.covenant {
286-
Covenant::Transfer { expire_height, data} => {
314+
Covenant::Transfer { expire_height, data } => {
287315
count += 1;
288316
assert_eq!(*expire_height, expected_expire_height, "must refresh expire height");
289317
assert!(data.is_some(), "must be data set");
@@ -298,6 +326,150 @@ async fn it_should_allow_applying_script_in_batch(rig: &TestRig) -> anyhow::Resu
298326
Ok(())
299327
}
300328

329+
330+
331+
// Alice places an unconfirmed bid on @test2.
332+
// Bob attempts to replace it but fails due to a lack of confirmed bid & funding utxos.
333+
// Eve, with confirmed bid outputs/funds, successfully replaces the bid.
334+
async fn it_should_replace_mempool_bids(rig: &TestRig) -> anyhow::Result<()> {
335+
// create some confirmed bid outs for Eve
336+
rig.spaced.client.wallet_send_request(
337+
EVE,
338+
RpcWalletTxBuilder {
339+
bidouts: Some(2),
340+
requests: vec![],
341+
fee_rate: Some(FeeRate::from_sat_per_vb(2).expect("fee")),
342+
dust: None,
343+
force: false,
344+
confirmed_only: false,
345+
skip_tx_check: false,
346+
},
347+
).await?;
348+
rig.mine_blocks(1, None).await?;
349+
350+
rig.wait_until_wallet_synced(ALICE).await?;
351+
rig.wait_until_wallet_synced(BOB).await?;
352+
rig.wait_until_wallet_synced(EVE).await?;
353+
354+
let response = wallet_do(rig, ALICE, vec![
355+
RpcWalletRequest::Bid(BidParams {
356+
name: "@test2".to_string(),
357+
amount: 1000,
358+
})], false).await?;
359+
360+
let response = serde_json::to_string_pretty(&response).unwrap();
361+
println!("{}", response);
362+
363+
let response = wallet_do(rig, BOB, vec![
364+
RpcWalletRequest::Bid(BidParams {
365+
name: "@test2".to_string(),
366+
amount: 1000,
367+
})], false).await?;
368+
369+
let response = serde_json::to_string_pretty(&response).unwrap();
370+
371+
println!("{}", response);
372+
373+
assert!(response.contains("hint"), "should have a hint about replacement errors");
374+
375+
let replacement = rig.spaced.client.wallet_send_request(
376+
BOB,
377+
RpcWalletTxBuilder {
378+
bidouts: None,
379+
requests: vec![
380+
RpcWalletRequest::Bid(BidParams {
381+
name: "@test2".to_string(),
382+
amount: 1000,
383+
})],
384+
fee_rate: Some(FeeRate::from_sat_per_vb(2).expect("fee")),
385+
dust: None,
386+
force: false,
387+
confirmed_only: false,
388+
skip_tx_check: false,
389+
},
390+
).await?;
391+
392+
let response = serde_json::to_string_pretty(&replacement).unwrap();
393+
println!("{}", response);
394+
395+
assert!(response.contains("hint"), "should have a hint about confirmed only");
396+
assert!(response.contains("replacement-adds-unconfirmed"), "expected a replacement-adds-unconfirmed in the message");
397+
398+
// now let Eve try a replacement since she has confirmed outputs
399+
let replacement = rig.spaced.client.wallet_send_request(
400+
EVE,
401+
RpcWalletTxBuilder {
402+
bidouts: None,
403+
requests: vec![
404+
RpcWalletRequest::Bid(BidParams {
405+
name: "@test2".to_string(),
406+
amount: 1000,
407+
})],
408+
fee_rate: Some(FeeRate::from_sat_per_vb(2).expect("fee")),
409+
dust: None,
410+
force: false,
411+
confirmed_only: false,
412+
skip_tx_check: false,
413+
},
414+
).await?;
415+
416+
let response = serde_json::to_string_pretty(&replacement).unwrap();
417+
println!("{}", response);
418+
419+
for tx_res in replacement.result {
420+
assert!(tx_res.error.is_none(), "Eve should have no problem replacing")
421+
}
422+
423+
// Alice won't be able to build off other transactions from the double spent bid
424+
// even when Eve bid gets confirmed. Wallet must remove double spent tx.
425+
rig.mine_blocks(1, None).await?;
426+
rig.wait_until_wallet_synced(ALICE).await?;
427+
let txs = rig.spaced.client.wallet_list_transactions(
428+
ALICE,
429+
1000, 0
430+
).await?;
431+
let unconfirmed : Vec<_> = txs.iter().filter(|tx| !tx.confirmed).collect();
432+
assert_eq!(unconfirmed.len(), 0, "there should be no stuck unconfirmed transactions");
433+
Ok(())
434+
}
435+
436+
async fn it_should_maintain_locktime_when_fee_bumping(rig: &TestRig) -> anyhow::Result<()> {
437+
rig.wait_until_wallet_synced(ALICE).await?;
438+
439+
let response = rig.spaced.client.wallet_send_request(
440+
ALICE,
441+
RpcWalletTxBuilder {
442+
bidouts: Some(2),
443+
requests: vec![],
444+
fee_rate: Some(FeeRate::from_sat_per_vb(1).expect("fee")),
445+
dust: None,
446+
force: false,
447+
confirmed_only: false,
448+
skip_tx_check: false
449+
},
450+
).await?;
451+
452+
println!("{}", serde_json::to_string_pretty(&response).unwrap());
453+
454+
let txid = response.result[0].txid;
455+
for tx_res in response.result{
456+
assert!(tx_res.error.is_none(), "should not be error");
457+
}
458+
459+
let tx = rig.get_raw_transaction(&txid).await?;
460+
461+
let bump = rig.spaced.client.wallet_bump_fee(
462+
ALICE, txid, FeeRate::from_sat_per_vb(4).expect("fee"), false
463+
).await?;
464+
assert_eq!(bump.len(), 1, "should only be 1 tx");
465+
assert!(bump[0].error.is_none(), "should be no errors");
466+
467+
let replacement = rig.get_raw_transaction(&bump[0].txid).await?;
468+
469+
assert_eq!(tx.lock_time, replacement.lock_time, "locktimes must not change");
470+
Ok(())
471+
}
472+
301473
#[tokio::test]
302474
async fn run_auction_tests() -> anyhow::Result<()> {
303475
let rig = TestRig::new_with_regtest_preset().await?;
@@ -315,10 +487,10 @@ async fn run_auction_tests() -> anyhow::Result<()> {
315487
it_should_allow_outbidding(&rig).await?;
316488
it_should_only_accept_forced_zero_value_bid_increments_and_revoke(&rig).await?;
317489
it_should_allow_claim_on_or_after_claim_height(&rig).await?;
318-
319-
// batch tests
320490
it_should_allow_batch_transfers_refreshing_expire_height(&rig).await?;
321491
it_should_allow_applying_script_in_batch(&rig).await?;
492+
it_should_replace_mempool_bids(&rig).await?;
493+
it_should_maintain_locktime_when_fee_bumping(&rig).await?;
322494

323495
Ok(())
324496
}
@@ -333,6 +505,7 @@ async fn wallet_do(rig: &TestRig, wallet: &str, requests: Vec<RpcWalletRequest>,
333505
dust: None,
334506
force,
335507
confirmed_only: false,
508+
skip_tx_check: false,
336509
},
337510
).await?;
338511
Ok(res)

testutil/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,16 @@ impl TestRig {
317317
)
318318
}
319319

320+
pub async fn get_raw_transaction(&self, txid: &Txid) -> Result<Transaction> {
321+
let c = self.bitcoind.clone();
322+
let txid = txid.clone();
323+
Ok(
324+
tokio::task::spawn_blocking(move || c.client.get_raw_transaction(&txid, None))
325+
.await
326+
.expect("handle")?,
327+
)
328+
}
329+
320330
/// Reorg a number of blocks of a given size `count`.
321331
/// Refer to [`SpaceD::mine_empty_block`] for more information.
322332
///

0 commit comments

Comments
 (0)