mirror of
https://github.com/ChristopherA/Learning-Bitcoin-from-the-Command-Line.git
synced 2025-06-08 08:26:17 +00:00
396 lines
11 KiB
Markdown
396 lines
11 KiB
Markdown
# 18.5: Accessing Bitcoind with Rust
|
||
|
||
## Setup
|
||
|
||
We'll need `Rust` and `Cargo`. Installing them is easy:
|
||
|
||
```vim
|
||
$ curl https://sh.rustup.rs -sSf | sh
|
||
```
|
||
|
||
If everything goes well, we should see:
|
||
|
||
```vim
|
||
Rust is installed now. Great!
|
||
```
|
||
|
||
To set `Bitcoin Regtest` network up and allow communication with our Rust program we
|
||
will be using the following `bitcoind` configuration in `bitcoin.conf`
|
||
|
||
```vim
|
||
regtest=1
|
||
server=1
|
||
rpcuser=bitcoin
|
||
rpcpassword=password
|
||
[test]
|
||
rpcport=18443
|
||
```
|
||
|
||
> Note: Never use a simple password like that when on Bitcoin Mainnet!
|
||
|
||
### Create a New Project
|
||
|
||
We create a new project with `cargo new btc_test`:
|
||
|
||
```vim
|
||
gorazd@gorazd-MS-7C37:~/Projects/BlockchainCommons$ cargo new btc_test
|
||
Created binary (application) `btc_test` package
|
||
```
|
||
|
||
Let's move into the newly created project `btc_test`. We notice a "hello world" example
|
||
with the source code in `src/main.rs` and a `Cargo.toml` file. Let's run it with `cargo run`:
|
||
|
||
```vim
|
||
gorazd@gorazd-MS-7C37:~/Projects/BlockchainCommons/btc_test$ cargo run
|
||
Compiling btc_test v0.1.0 (/home/gorazd/Projects/BlockchainCommons/btc_test)
|
||
Finished dev [unoptimized + debuginfo] target(s) in 0.14s
|
||
Running `target/debug/btc_test`
|
||
Hello, world!
|
||
```
|
||
|
||
> Note: if you run into error “linker ‘cc’ not found”, you'll have to install a
|
||
C compiler. If on Linux, go ahead and install the [development tools](https://www.ostechnix.com/install-development-tools-linux/).
|
||
|
||
|
||
We will use `bitcoincore-rpc` crate (library), therefore we add it to our `Cargo.toml`
|
||
under section `dependencies` like so:
|
||
|
||
```rust
|
||
[dependencies]
|
||
bitcoincore-rpc = "0.11.0"
|
||
```
|
||
|
||
Running our example again will install our crate and
|
||
its dependencies.
|
||
|
||
```vim
|
||
gorazd@gorazd-MS-7C37:~/Projects/BlockchainCommons/btc_test$ cargo run
|
||
Updating crates.io index
|
||
...
|
||
Compiling bitcoin v0.23.0
|
||
Compiling bitcoincore-rpc-json v0.11.0
|
||
Compiling bitcoincore-rpc v0.11.0
|
||
Compiling btc_test v0.1.0 (/home/gorazd/Projects/BlockchainCommons/btc_test)
|
||
Finished dev [unoptimized + debuginfo] target(s) in 23.56s
|
||
Running `target/debug/btc_test`
|
||
Hello, world!
|
||
```
|
||
|
||
## Build Your Connection
|
||
|
||
Let us create a Bitcoin `RPC client` and modify the `main.rs`:
|
||
|
||
```rust
|
||
use bitcoincore_rpc::{Auth, Client};
|
||
|
||
fn main() {
|
||
let rpc = Client::new(
|
||
"http://localhost:18443".to_string(),
|
||
Auth::UserPass("bitcoin".to_string(), "password".to_string()),
|
||
)
|
||
.unwrap();
|
||
}
|
||
```
|
||
|
||
`Cargo run` should successfully compile and run the example with one warning
|
||
`warning: unused variable: rpc`
|
||
|
||
|
||
### Make an RPC Call
|
||
|
||
This is a simple RPC call without arguments:
|
||
|
||
```rust
|
||
let mining_info = rpc.get_mining_info().unwrap();
|
||
println!("{:#?}", mining_info);
|
||
```
|
||
|
||
The compiler will tell us to include traits into scope. So lets add them:
|
||
|
||
```rust
|
||
use bitcoincore_rpc::{Auth, Client, RpcApi};
|
||
```
|
||
|
||
If our properly configured `bitcoind` is running, executing our example should
|
||
result in:
|
||
|
||
```vim
|
||
gorazd@gorazd-MS-7C37:~/Projects/BlockchainCommons/btc_test$ cargo run
|
||
Compiling btc_test v0.1.0 (/home/gorazd/Projects/BlockchainCommons/btc_test)
|
||
Finished dev [unoptimized + debuginfo] target(s) in 0.80s
|
||
Running `target/debug/btc_test`
|
||
GetMiningInfoResult {
|
||
blocks: 5167,
|
||
current_block_weight: Some(
|
||
0,
|
||
),
|
||
current_block_tx: Some(
|
||
0,
|
||
),
|
||
difficulty: 0.00000000046565423739069247,
|
||
network_hash_ps: 1.764705882352941,
|
||
pooled_tx: 2,
|
||
chain: "regtest",
|
||
warnings: "",
|
||
}
|
||
```
|
||
|
||
If we wanted we could close the connection:
|
||
|
||
```rust
|
||
let _ = rpc.stop().unwrap();
|
||
```
|
||
|
||
|
||
## Manipulate Your Wallet
|
||
|
||
### Look Up Addresses
|
||
|
||
Here we will make our first call with an argument. To see the type of an argument,
|
||
we want to look at the function definition:
|
||
|
||
```rust
|
||
fn get_address_info(&self, address: &Address) -> Result<json::GetAddressInfoResult> {
|
||
self.call("getaddressinfo", &[address.to_string().into()])
|
||
}
|
||
```
|
||
|
||
We see that our argument is of type `Address` and that it will be borrowed. Further,
|
||
looking at the structure `Address`, we notice a convenient `trait` implemented which
|
||
allows us to create an `Address` out of a string:
|
||
|
||
```rust
|
||
impl FromStr for Address {
|
||
type Err = Error;
|
||
|
||
fn from_str(s: &str) -> Result<Address, Error> {
|
||
```
|
||
|
||
Now that we now what structure and trait we are dealing with, we bring them into
|
||
scope
|
||
|
||
```rust
|
||
use bitcoincore_rpc::bitcoin::Address;
|
||
use std::str::FromStr;
|
||
```
|
||
|
||
so we can use them:
|
||
|
||
```rust
|
||
let addr = Address::from_str("bcrt1qanga5jxx845q82h9qgjfuedps92lktqv073qct").unwrap();
|
||
let addr_info = rpc.get_address_info(&addr).unwrap();
|
||
println!("{:?}", addr_info);
|
||
```
|
||
|
||
Running our program results in:
|
||
|
||
```vim
|
||
GetAddressInfoResult { address: bcrt1qanga5jxx845q82h9qgjfuedps92lktqv073qct, script_pub_key: Script(OP_0 OP_PUSHBYTES_20 ecd1da48c63d6803aae502249e65a18155fb2c0c), is_mine: Some(true), is_watchonly: Some(false), is_script: Some(false), is_witness: Some(true), witness_version: Some(0), witness_program: Some([236, 209, 218, 72, 198, 61, 104, 3, 170, 229, 2, 36, 158, 101, 161, 129, 85, 251, 44, 12]), script: None, hex: None, pubkeys: None, n_signatures_required: None, pubkey: Some(PublicKey { compressed: true, key: PublicKey(f895d610ab1ceddfd87814b1f7a911fee1135a9347d4fd1754a06ddf84757c5c527a90804949b025d7272bef4d58a1324c18d7a8f6b7ffa949447bcb6a225e6e) }), embedded: None, is_compressed: None, label: "lbl", timestamp: Some(1582063890), hd_key_path: Some(m/0'/0'/99'), hd_seed_id: Some(00b332a133c03c4e613f0106dc814bcc79af60ff), labels: [GetAddressInfoResultLabel { name: "lbl", purpose: Receive }] }
|
||
```
|
||
|
||
> Note: this call doesn't work with recent versions of Bitcoin Core due to the
|
||
crate not addressing the latest API changes in Bitcoin Core.
|
||
We expect it to be solved in the next crate's release.
|
||
|
||
### Look Up Funds
|
||
|
||
We can look up our funds without optional arguments like so:
|
||
|
||
```rust
|
||
let balance = rpc.get_balance(None, None).unwrap();
|
||
println!("Balance: {:?} BTC", balance.as_btc());
|
||
```
|
||
|
||
```vim
|
||
Balance: 3433.71692741 BTC
|
||
```
|
||
|
||
### Create an Address
|
||
|
||
Here is an example of calling an RPC method with the optional arguments specified, i.e.
|
||
a label and an address type:
|
||
|
||
```rust
|
||
// Generate a new address
|
||
let myaddress = rpc
|
||
.get_new_address(Option::Some("BlockchainCommons"), Option::Some(json::AddressType::Bech32))
|
||
.unwrap();
|
||
println!("address: {:?}", myaddress);
|
||
```
|
||
|
||
If we have inspected our function's definition we bring the missing things into
|
||
scope. Otherwise the compiler will hint us to do so:
|
||
|
||
```rust
|
||
use bitcoincore_rpc::{json, Auth, Client, RpcApi};
|
||
```
|
||
|
||
Program execution results in:
|
||
|
||
```vim
|
||
address: bcrt1q0y0dk70lut5l3y4f0fe52am23egfmr63dejy9r
|
||
```
|
||
|
||
Now, we would like to have some bitcoins to our newly generated address. Since
|
||
we are on the `Regtest` network we can generate them ourselves:
|
||
|
||
```rust
|
||
// Generate 101 blocks to our address
|
||
let _ = rpc.generate_to_address(101, &myaddress);
|
||
```
|
||
|
||
## Create a Transaction
|
||
|
||
First, we list unspent transactions. Let's look at those with at least 3 BTC and take the first one:
|
||
|
||
```rust
|
||
let unspent = rpc
|
||
.list_unspent(
|
||
None,
|
||
None,
|
||
None,
|
||
None,
|
||
Option::Some(json::ListUnspentQueryOptions {
|
||
minimum_amount: Option::Some(Amount::from_btc(3.0).unwrap()),
|
||
maximum_amount: None,
|
||
maximum_count: None,
|
||
minimum_sum_amount: None,
|
||
}),
|
||
)
|
||
.unwrap();
|
||
|
||
let selected_tx = &unspent[0];
|
||
|
||
println!("selected unspent transaction: {:#?}", selected_tx);
|
||
```
|
||
Here it is:
|
||
|
||
```vim
|
||
selected unspent transaction: ListUnspentResultEntry {
|
||
txid: 34e283eb5b52c66aba9766bdda46eb038bc1138e992b593c22f7cbf1d2e9ba10,
|
||
vout: 0,
|
||
address: Some(
|
||
bcrt1q7lju6c0ynwerch0te4saxwxgm70ltd3lr9vj6l,
|
||
),
|
||
label: Some(
|
||
"",
|
||
),
|
||
redeem_script: None,
|
||
witness_script: None,
|
||
script_pub_key: Script(OP_0 OP_PUSHBYTES_20 f7e5cd61e49bb23c5debcd61d338c8df9ff5b63f),
|
||
amount: Amount(625000000 satoshi),
|
||
confirmations: 4691,
|
||
spendable: true,
|
||
solvable: true,
|
||
descriptor: None,
|
||
safe: true,
|
||
}
|
||
```
|
||
|
||
This will require to bring another structure into scope:
|
||
|
||
```rust
|
||
use bitcoincore_rpc::bitcoin::{Address, Amount};
|
||
```
|
||
|
||
We can now populate some variables: the available amount and
|
||
the utxo, the recipient's address and the amount we want to send.
|
||
|
||
```rust
|
||
let unspent_amount = selected_tx.amount;
|
||
|
||
let selected_utxos = json::CreateRawTransactionInput {
|
||
txid: selected_tx.txid,
|
||
vout: selected_tx.vout,
|
||
sequence: None,
|
||
};
|
||
|
||
let recipient = Address::from_str("bcrt1q6rhpng9evdsfnn833a4f4vej0asu6dk5srld6x").unwrap();
|
||
println!("recipient: {:?}", recipient);
|
||
|
||
// send all bitcoin in the UTXO except a minor value which will be paid to miners
|
||
let amount = unspent_amount - Amount::from_btc(0.00001).unwrap();
|
||
|
||
let mut output = HashMap::new();
|
||
output.insert(
|
||
"bcrt1q6rhpng9evdsfnn833a4f4vej0asu6dk5srld6x".to_string(),
|
||
amount,
|
||
);
|
||
```
|
||
|
||
Another trait is necessary for the output variable: HashMap. It allows us to store
|
||
values by key which we need to represent `{address : amount}` information.
|
||
|
||
```rust
|
||
use std::collections::HashMap;
|
||
```
|
||
|
||
We are ready to create a raw transaction:
|
||
|
||
```rust
|
||
let unsigned_tx = rpc
|
||
.create_raw_transaction(&[selected_utxos], &output, None, None)
|
||
.unwrap();
|
||
|
||
println!("unsigned tx {:#?}", unsigned_tx);
|
||
```
|
||
|
||
Here it is:
|
||
|
||
```vim
|
||
unsigned tx Transaction {
|
||
version: 2,
|
||
lock_time: 0,
|
||
input: [
|
||
TxIn {
|
||
previous_output: OutPoint {
|
||
txid: 34e283eb5b52c66aba9766bdda46eb038bc1138e992b593c22f7cbf1d2e9ba10,
|
||
vout: 0,
|
||
},
|
||
script_sig: Script(),
|
||
sequence: 4294967295,
|
||
witness: [],
|
||
},
|
||
],
|
||
output: [
|
||
TxOut {
|
||
value: 624999000,
|
||
script_pubkey: Script(OP_0 OP_PUSHBYTES_20 d0ee19a0b9636099ccf18f6a9ab3327f61cd36d4),
|
||
},
|
||
],
|
||
}
|
||
```
|
||
|
||
Finally, we can sign and broadcast our transaction:
|
||
|
||
```rust
|
||
// sign transaction
|
||
let signed_tx = rpc
|
||
.sign_raw_transaction_with_wallet(&unsigned_tx, None, None)
|
||
.unwrap();
|
||
|
||
println!("singed tx {:?}", signed_tx.transaction().unwrap());
|
||
|
||
// broadcast transaction
|
||
let txid_sent = rpc
|
||
.send_raw_transaction(&signed_tx.transaction().unwrap())
|
||
.unwrap();
|
||
|
||
println!("{:?}", txid_sent);
|
||
```
|
||
|
||
```vim
|
||
singed tx Transaction { version: 2, lock_time: 0, input: [TxIn { previous_output: OutPoint { txid: 34e283eb5b52c66aba9766bdda46eb038bc1138e992b593c22f7cbf1d2e9ba10, vout: 0 }, script_sig: Script(), sequence: 4294967295, witness: [[48, 68, 2, 32, 85, 113, 140, 197, 142, 140, 122, 26, 174, 71, 94, 152, 76, 104, 5, 111, 113, 192, 179, 1, 58, 6, 27, 141, 18, 50, 217, 53, 154, 26, 5, 98, 2, 32, 53, 148, 139, 57, 234, 151, 71, 149, 134, 202, 160, 136, 15, 144, 103, 232, 134, 37, 136, 184, 117, 159, 235, 92, 59, 102, 197, 213, 67, 64, 89, 207, 1], [3, 4, 197, 157, 36, 136, 177, 169, 182, 219, 121, 187, 251, 153, 207, 165, 173, 117, 142, 93, 181, 107, 185, 97, 10, 168, 210, 148, 67, 127, 246, 229, 12]] }], output: [TxOut { value: 624999000, script_pubkey: Script(OP_0 OP_PUSHBYTES_20 d0ee19a0b9636099ccf18f6a9ab3327f61cd36d4) }] }
|
||
5d2f1b7c6fc29967d820532c46200b35f62b6e6f8da614ae86922c20167f6d0e
|
||
```
|
||
|
||
## For More Information
|
||
|
||
You can now mine a block and try to see for yourself if the last transaction is really in the block.
|
||
If you need help look at the crate's [documentation](https://crates.io/crates/bitcoincore-rpc) or run some tests in its [repository](https://github.com/rust-bitcoin/rust-bitcoincore-rpc).
|
||
|
||
## Summary
|
||
|
||
We have shown how to access `bitcoind` in `Rust` and send a transaction
|
||
on the `Bitcoin Regtest Network` explaining all the steps required.
|