feat: add broadcast method on esplora blocking client

This commit is contained in:
thunderbiscuit 2023-11-17 13:25:33 -05:00
parent 26352edfbe
commit 15c1f19c96
No known key found for this signature in database
GPG Key ID: 88253696EB836462
10 changed files with 168 additions and 9 deletions

2
.gitignore vendored
View File

@ -18,7 +18,7 @@ bdk.kt
# Swift related # Swift related
/.build /.build
/.swiftpm .swiftpm
/Packages /Packages
/*.xcodeproj /*.xcodeproj
xcuserdata/ xcuserdata/

View File

@ -3,6 +3,7 @@ package org.bitcoindevkit
import org.junit.Test import org.junit.Test
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.runner.RunWith import org.junit.runner.RunWith
import kotlin.test.assertTrue
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class LiveWalletTest { class LiveWalletTest {
@ -20,4 +21,38 @@ class LiveWalletTest {
assert(wallet.getBalance().total() > 0uL) assert(wallet.getBalance().total() > 0uL)
} }
@Test
fun testBroadcastTransaction() {
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
val wallet = Wallet.newNoPersist(descriptor, null, Network.TESTNET)
val esploraClient = EsploraClient("https://mempool.space/testnet/api")
val update = esploraClient.scan(wallet, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.getBalance().total()}")
println("New address: ${wallet.getAddress(AddressIndex.New).address}")
assert(wallet.getBalance().total() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.getAddress(AddressIndex.New).address} and try again."
}
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.TESTNET)
val psbt: PartiallySignedTransaction = TxBuilder()
.addRecipient(recipient.scriptPubkey(), 4200uL)
.feeRate(4.0f)
.finish(wallet)
println(psbt.serialize())
assertTrue(psbt.serialize().startsWith("cHNi"), "PSBT should start with 'cHNi'")
val walletDidSign = wallet.sign(psbt)
assertTrue(walletDidSign)
val tx: Transaction = psbt.extractTx()
println("Txid is: ${tx.txid()}")
esploraClient.broadcast(tx)
}
} }

View File

@ -221,6 +221,9 @@ interface EsploraClient {
[Throws=BdkError] [Throws=BdkError]
Update scan(Wallet wallet, u64 stop_gap, u64 parallel_requests); Update scan(Wallet wallet, u64 stop_gap, u64 parallel_requests);
[Throws=BdkError]
void broadcast(Transaction transaction);
}; };
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------

View File

@ -209,6 +209,12 @@ impl From<BdkTransaction> for Transaction {
} }
} }
impl From<Transaction> for BdkTransaction {
fn from(tx: Transaction) -> Self {
tx.inner
}
}
pub struct PartiallySignedTransaction { pub struct PartiallySignedTransaction {
pub(crate) inner: Mutex<BdkPartiallySignedTransaction>, pub(crate) inner: Mutex<BdkPartiallySignedTransaction>,
} }

View File

@ -1,4 +1,5 @@
use crate::wallet::{Update, Wallet}; use crate::wallet::{Update, Wallet};
use std::ops::Deref;
use bdk::wallet::Update as BdkUpdate; use bdk::wallet::Update as BdkUpdate;
use bdk::Error as BdkError; use bdk::Error as BdkError;
@ -56,7 +57,12 @@ impl EsploraClient {
// pub fn sync(); // pub fn sync();
// pub fn broadcast(); pub fn broadcast(&self, transaction: Arc<crate::bitcoin::Transaction>) -> Result<(), BdkError> {
let bdk_transaction: bdk::bitcoin::Transaction = transaction.deref().clone().into();
self.0
.broadcast(&bdk_transaction)
.map_err(|e| BdkError::Generic(e.to_string()))
}
// pub fn estimate_fee(); // pub fn estimate_fee();
} }

View File

@ -7,8 +7,8 @@ use std::collections::HashSet;
use bdk::bitcoin::blockdata::script::ScriptBuf as BdkScriptBuf; use bdk::bitcoin::blockdata::script::ScriptBuf as BdkScriptBuf;
use bdk::bitcoin::OutPoint as BdkOutPoint; use bdk::bitcoin::OutPoint as BdkOutPoint;
use bdk::wallet::Update as BdkUpdate; use bdk::wallet::Update as BdkUpdate;
use bdk::{SignOptions, Wallet as BdkWallet};
use bdk::{Error as BdkError, FeeRate}; use bdk::{Error as BdkError, FeeRate};
use bdk::{SignOptions, Wallet as BdkWallet};
use bdk::wallet::tx_builder::ChangeSpendPolicy; use bdk::wallet::tx_builder::ChangeSpendPolicy;
use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::{Arc, Mutex, MutexGuard};
@ -88,10 +88,9 @@ impl Wallet {
// sign_options: Option<SignOptions>, // sign_options: Option<SignOptions>,
) -> Result<bool, BdkError> { ) -> Result<bool, BdkError> {
let mut psbt = psbt.inner.lock().unwrap(); let mut psbt = psbt.inner.lock().unwrap();
self.get_wallet().sign( self.get_wallet()
&mut psbt, .sign(&mut psbt, SignOptions::default())
SignOptions::default(), .map_err(|e| BdkError::Generic(e.to_string()))
).map_err(|e| BdkError::Generic(e.to_string()))
} }
} }

View File

@ -1,6 +1,7 @@
package org.bitcoindevkit package org.bitcoindevkit
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertTrue
class LiveWalletTest { class LiveWalletTest {
@Test @Test
@ -15,4 +16,38 @@ class LiveWalletTest {
assert(wallet.getBalance().total() > 0uL) assert(wallet.getBalance().total() > 0uL)
} }
@Test
fun testBroadcastTransaction() {
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
val wallet = Wallet.newNoPersist(descriptor, null, Network.TESTNET)
val esploraClient = EsploraClient("https://mempool.space/testnet/api")
val update = esploraClient.scan(wallet, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.getBalance().total()}")
println("New address: ${wallet.getAddress(AddressIndex.New).address.asString()}")
assert(wallet.getBalance().total() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.getAddress(AddressIndex.New).address} and try again."
}
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.TESTNET)
val psbt: PartiallySignedTransaction = TxBuilder()
.addRecipient(recipient.scriptPubkey(), 4200uL)
.feeRate(2.0f)
.finish(wallet)
println(psbt.serialize())
assertTrue(psbt.serialize().startsWith("cHNi"), "PSBT should start with 'cHNi'")
val walletDidSign = wallet.sign(psbt)
assertTrue(walletDidSign)
val tx: Transaction = psbt.extractTx()
println("Txid is: ${tx.txid()}")
esploraClient.broadcast(tx)
}
} }

View File

@ -23,6 +23,41 @@ class TestLiveWallet(unittest.TestCase):
self.assertGreater(wallet.get_balance().total(), 0) self.assertGreater(wallet.get_balance().total(), 0)
def test_broadcast_transaction(self):
descriptor: bdk.Descriptor = bdk.Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
bdk.Network.TESTNET
)
wallet: bdk.Wallet = bdk.Wallet.new_no_persist(
descriptor,
None,
bdk.Network.TESTNET
)
esploraClient: bdk.EsploraClient = bdk.EsploraClient(url = "https://mempool.space/testnet/api")
update = esploraClient.scan(
wallet = wallet,
stop_gap = 10,
parallel_requests = 1
)
wallet.apply_update(update)
self.assertGreater(wallet.get_balance().total(), 0)
recipient = bdk.Address(
address = "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
network = bdk.Network.TESTNET
)
psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=4200).fee_rate(2.0).finish(wallet)
# print(psbt.serialize())
self.assertTrue(psbt.serialize().startswith("cHNi"), "The PSBT should start with cHNi")
walletDidSign = wallet.sign(psbt)
self.assertTrue(walletDidSign)
tx = psbt.extract_tx()
esploraClient.broadcast(tx)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -4,7 +4,7 @@ import XCTest
final class LiveTxBuilderTests: XCTestCase { final class LiveTxBuilderTests: XCTestCase {
func testTxBuilder() throws { func testTxBuilder() throws {
let descriptor = try Descriptor( let descriptor = try Descriptor(
descriptor: "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
network: Network.testnet network: Network.testnet
) )
let wallet = try Wallet.newNoPersist( let wallet = try Wallet.newNoPersist(

View File

@ -4,7 +4,7 @@ import XCTest
final class LiveWalletTests: XCTestCase { final class LiveWalletTests: XCTestCase {
func testSyncedBalance() throws { func testSyncedBalance() throws {
let descriptor = try Descriptor( let descriptor = try Descriptor(
descriptor: "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
network: Network.testnet network: Network.testnet
) )
let wallet = try Wallet.newNoPersist( let wallet = try Wallet.newNoPersist(
@ -22,4 +22,44 @@ final class LiveWalletTests: XCTestCase {
XCTAssertGreaterThan(wallet.getBalance().total(), UInt64(0)) XCTAssertGreaterThan(wallet.getBalance().total(), UInt64(0))
} }
func testBroadcastTransaction() throws {
let descriptor = try Descriptor(
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
network: Network.testnet
)
let wallet = try Wallet.newNoPersist(
descriptor: descriptor,
changeDescriptor: nil,
network: .testnet
)
let esploraClient = EsploraClient(url: "https://mempool.space/testnet/api")
let update = try esploraClient.scan(
wallet: wallet,
stopGap: 10,
parallelRequests: 1
)
try wallet.applyUpdate(update: update)
XCTAssertGreaterThan(wallet.getBalance().total(), UInt64(0), "Wallet must have positive balance, please add funds")
print("Balance: \(wallet.getBalance().total())")
let recipient: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .testnet)
let psbt: PartiallySignedTransaction = try
TxBuilder()
.addRecipient(script: recipient.scriptPubkey(), amount: 4200)
.feeRate(satPerVbyte: 2.0)
.finish(wallet: wallet)
print(psbt.serialize())
XCTAssertTrue(psbt.serialize().hasPrefix("cHNi"), "PSBT should start with cHNI")
let walletDidSign: Bool = try wallet.sign(psbt: psbt)
XCTAssertTrue(walletDidSign, "Wallet did not sign transaction")
let tx: Transaction = psbt.extractTx()
print(tx.txid())
try esploraClient.broadcast(transaction: tx)
}
} }