-
Notifications
You must be signed in to change notification settings - Fork 13
Fix broadcasting to a peer who already has tx #556
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,8 @@ pub enum ClientError { | |
| SendError, | ||
| /// A channel was dropped before sending its value back. | ||
| RecvError, | ||
| /// No peer requested the transaction within the configured broadcast timeout. | ||
| BroadcastTimeout, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we make a new error variant for this particular method (see |
||
| } | ||
|
|
||
| impl core::fmt::Display for ClientError { | ||
|
|
@@ -39,6 +41,12 @@ impl core::fmt::Display for ClientError { | |
| ClientError::RecvError => { | ||
| write!(f, "the sender of data was dropped from memory.") | ||
| } | ||
| ClientError::BroadcastTimeout => { | ||
| write!( | ||
| f, | ||
| "no peer requested the transaction within the configured timeout." | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -633,6 +633,98 @@ async fn tx_can_broadcast() { | |
| .unwrap(); | ||
| } | ||
|
|
||
| // Verify that broadcasting a transaction that the peer already has. | ||
| #[tokio::test] | ||
| async fn tx_broadcast_does_not_hang_when_peer_has_tx() { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we just use the existing transaction broadcasting test instead of creating a new one? |
||
| let amount_to_us = Amount::from_sat(100_000); | ||
| let amount_to_op_return = Amount::from_sat(50_000); | ||
| let (bitcoind, socket_addr) = start_bitcoind(true).unwrap(); | ||
| let rpc = &bitcoind.client; | ||
| let tempdir = tempfile::TempDir::new().unwrap().path().to_owned(); | ||
| let mut rng = StdRng::seed_from_u64(20002); | ||
| let secret = SecretKey::new(&mut rng); | ||
| let secp = Secp256k1::new(); | ||
| let keypair = Keypair::from_secret_key(&secp, &secret); | ||
| let (internal_key, _) = keypair.x_only_public_key(); | ||
| let send_to_this_address = Address::p2tr(&secp, internal_key, None, KnownHrp::Regtest); | ||
| let miner = rpc.new_address().unwrap(); | ||
| mine_blocks(rpc, &miner, 110, 10).await; | ||
| let tx_info = rpc | ||
| .send_to_address(&send_to_this_address, amount_to_us) | ||
| .unwrap(); | ||
| let txid = tx_info.txid().unwrap(); | ||
| let tx_details = rpc.get_transaction(txid).unwrap().details; | ||
| let (vout, amt) = tx_details | ||
| .iter() | ||
| .find(|detail| detail.address.eq(&send_to_this_address.to_string())) | ||
| .map(|detail| (detail.vout, detail.amount)) | ||
| .unwrap(); | ||
| let txout = TxOut { | ||
| script_pubkey: miner.script_pubkey(), | ||
| value: amount_to_op_return, | ||
| }; | ||
| let outpoint = OutPoint { txid, vout }; | ||
| let txin = TxIn { | ||
| previous_output: outpoint, | ||
| script_sig: ScriptBuf::default(), | ||
| sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, | ||
| witness: Witness::default(), | ||
| }; | ||
| let mut unsigned_tx = Transaction { | ||
| version: bitcoin::transaction::Version::TWO, | ||
| lock_time: absolute::LockTime::ZERO, | ||
| input: vec![txin], | ||
| output: vec![txout], | ||
| }; | ||
| let input_index = 0; | ||
| let sighash_type = TapSighashType::Default; | ||
| let prevout = TxOut { | ||
| script_pubkey: send_to_this_address.script_pubkey(), | ||
| value: Amount::from_btc(amt.abs()).unwrap(), | ||
| }; | ||
| let prevouts = vec![prevout]; | ||
| let prevouts = Prevouts::All(&prevouts); | ||
| let mut sighasher = SighashCache::new(&mut unsigned_tx); | ||
| let sighash = sighasher | ||
| .taproot_key_spend_signature_hash(input_index, &prevouts, sighash_type) | ||
| .unwrap(); | ||
| let tweaked: bitcoin::key::TweakedKeypair = keypair.tap_tweak(&secp, None); | ||
| let msg = bitcoin::secp256k1::Message::from(sighash); | ||
| let signature = secp.sign_schnorr(&msg, &tweaked.to_keypair()); | ||
| let signature = bitcoin::taproot::Signature { | ||
| signature, | ||
| sighash_type, | ||
| }; | ||
| *sighasher.witness_mut(input_index).unwrap() = Witness::p2tr_key_spend(&signature); | ||
| let tx = sighasher.into_transaction().to_owned(); | ||
| // Submit the tx directly to bitcoind via RPC — now the peer already has it. | ||
| rpc.send_raw_transaction(&tx).unwrap(); | ||
| println!("Submitted tx {} to bitcoind via RPC", tx.compute_txid()); | ||
| // Now broadcast the same tx via kyoto with a zero timeout. | ||
| let host = (IpAddr::V4(*socket_addr.ip()), Some(socket_addr.port())); | ||
| let (node, client) = bip157::Builder::new(bitcoin::Network::Regtest) | ||
| .add_peer(host) | ||
| .data_dir(tempdir) | ||
| .chain_state(ChainState::Checkpoint(HeaderCheckpoint::from_genesis( | ||
| bitcoin::Network::Regtest, | ||
| ))) | ||
| .broadcast_timeout(Duration::ZERO) | ||
| .build(); | ||
| tokio::task::spawn(async move { node.run().await }); | ||
| let Client { | ||
| requester, | ||
| info_rx: _, | ||
| warn_rx: _, | ||
| event_rx: _, | ||
| } = client; | ||
| let result = requester.broadcast_tx(tx).await; | ||
| assert!( | ||
| matches!(result, Err(bip157::ClientError::BroadcastTimeout)), | ||
| "expected BroadcastTimeout, got: {:?}", | ||
| result, | ||
| ); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn dns_works() { | ||
| let hostname = bip157::lookup_host("seed.bitcoin.sipa.be").await; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a fan of having this associated with the
Requester. If a user would like to manage their own timeout, they should usebroadcast_tx. Otherwise they should use a new method on the requester calledbroadcast_tx_with_timeout