mirror of
https://github.com/ChristopherA/Learning-Bitcoin-from-the-Command-Line.git
synced 2025-06-07 16:06:26 +00:00
add Rust section: Accessing bitcoind with Rust
This commit is contained in:
parent
19826bd0e6
commit
18dfdb87aa
395
18_5_Accessing_Bitcoind_with_Rust.md
Normal file
395
18_5_Accessing_Bitcoind_with_Rust.md
Normal file
@ -0,0 +1,395 @@
|
|||||||
|
# 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.
|
@ -121,7 +121,7 @@ _This tutorial assumes that you have some minimal background of how to use the c
|
|||||||
* [18.2: Accessing Bitcoind with Java](18_2_Accessing_Bitcoind_with_Java.md)
|
* [18.2: Accessing Bitcoind with Java](18_2_Accessing_Bitcoind_with_Java.md)
|
||||||
* [18.3: Accessing Bitcoind with_Node_JS](18_3_Accessing_Bitcoind_with_NodeJS.md) — Needs Rewrite + Editing
|
* [18.3: Accessing Bitcoind with_Node_JS](18_3_Accessing_Bitcoind_with_NodeJS.md) — Needs Rewrite + Editing
|
||||||
* [18.4: Accessing Bitcoind with Python](18_4_Accessing_Bitcoind_with_Python.md)
|
* [18.4: Accessing Bitcoind with Python](18_4_Accessing_Bitcoind_with_Python.md)
|
||||||
* [18.5: Accessing Bitcoind with Rust] — Unwritten
|
* [18.5: Accessing Bitcoind with Rust](18_5_Accessing_Bitcoind_with_Rust.md)
|
||||||
* [18.6: Accessing Bitcoind with Swift] — Unwritten
|
* [18.6: Accessing Bitcoind with Swift] — Unwritten
|
||||||
|
|
||||||
### APPENDICES
|
### APPENDICES
|
||||||
@ -200,4 +200,3 @@ The following keys may be used to communicate sensitive information to developer
|
|||||||
| Christopher Allen | FDFE 14A5 4ECB 30FC 5D22 74EF F8D3 6C91 3574 05ED |
|
| Christopher Allen | FDFE 14A5 4ECB 30FC 5D22 74EF F8D3 6C91 3574 05ED |
|
||||||
|
|
||||||
You can import a key by running the following command with that individual’s fingerprint: `gpg --recv-keys "<fingerprint>"` Ensure that you put quotes around fingerprints that contain spaces.
|
You can import a key by running the following command with that individual’s fingerprint: `gpg --recv-keys "<fingerprint>"` Ensure that you put quotes around fingerprints that contain spaces.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user