mirror of
https://github.com/ChristopherA/Learning-Bitcoin-from-the-Command-Line.git
synced 2025-06-07 16:06:26 +00:00
commit
9ed9b4d3d4
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.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.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
|
||||
|
||||
### 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 |
|
||||
|
||||
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.
|
||||
|
||||
|
@ -0,0 +1,74 @@
|
||||
-----BEGIN PGP SIGNED MESSAGE-----
|
||||
Hash: SHA512
|
||||
|
||||
# Contributor License Agreement
|
||||
|
||||
Version 1.0
|
||||
|
||||
Name: Gorazd Kovacic
|
||||
|
||||
E-Mail: gorazdko@gmail.com
|
||||
|
||||
Legal Jurisdiction: Wyoming, United States of America
|
||||
|
||||
Project: https://github.com/BlockchainCommons/Learning-Bitcoin-from-the-Command-Line
|
||||
|
||||
Date: 06-27-2020
|
||||
|
||||
## Purpose
|
||||
|
||||
This agreement gives Blockchain Commons, LLC the permission it needs in order to accept my contributions into its open software project and to manage the intellectual property in that project over time.
|
||||
|
||||
## License
|
||||
|
||||
I hereby license Blockchain Commons, LLC to:
|
||||
|
||||
1. do anything with my contributions that would otherwise infringe my copyright in them
|
||||
|
||||
2. do anything with my contributions that would otherwise infringe patents that I can or become able to license
|
||||
|
||||
3. sublicense these rights to others on any terms they like
|
||||
|
||||
## Reliability
|
||||
|
||||
I understand that Blockchain Commons will rely on this license. I may not revoke this license.
|
||||
|
||||
## Awareness
|
||||
|
||||
I promise that I am familiar with legal rules, like ["work made for hire" rules](http://worksmadeforhire.com), that can give employers and clients ownership of intellectual property in work that I do. I am also aware that legal agreements I might sign, like confidential information and invention assignment agreements, will usually give ownership of intellectual property in my work to employers, clients, and companies that I found. If someone else owns intellectual property in my work, I need their permission to license it.
|
||||
|
||||
## Copyright Guarantee
|
||||
|
||||
I promise not to offer contributions to the project that contain copyrighted work that I do not have legally binding permission to contribute under these terms. When I offer a contribution with permission, I promise to document in the contribution who owns copyright in what work, and how they gave permission to contribute it. If I later become aware that one of my contributions may have copyrighted work of others that I did not have permission to contribute, I will notify Blockchain Commons, in confidence, immediately.
|
||||
|
||||
## Patent Guarantee
|
||||
|
||||
I promise not to offer contributions to the project that I know infringe patents of others that I do not have permission to contribute under these terms.
|
||||
|
||||
## Open Source Guarantee
|
||||
|
||||
I promise not to offer contributions that contain or depend on the work of others, unless that work is available under a license that [Blue Oak Council rates bronze or better](https://blueoakconcil.org/list), such as the MIT License, two- or three-clause BSD License, the Apache License Version 2.0, or the Blue Oak Model License 1.0.0. When I offer a contribution containing or depending on others' work, I promise to document in the contribution who licenses that work, along with copies of their license terms.
|
||||
|
||||
## Disclaimers
|
||||
|
||||
***As far as the law allows, my contributions come as is, without any warranty or condition. Other than under [Copyright Guarantee](#copyright-guarantee), [Patent Guarantee](#patent-guarantee), or [Open Source Guarantee](#open-source-guarantee), I will not be liable to anyone for any damages related to my contributions or this contributor license agreement, under any kind of legal claim.***
|
||||
|
||||
- ---
|
||||
|
||||
To sign this Contributor License Agreement, fill in `$name`, `$email`, and `$date` above. Then sign using GPG using the following command `gpg --armor --clearsign --output ./signed-cla/CLA.YOURGITHUBNAME.YOURGPGFINGERPRINT.asc CLA.md`, then either submit your signed Contributor License Agreement to this repo as a GPG signed Pull Request or email it to [ChristopherA@BlockchainCommons.com](mailto:ChristopherA@BlockchainCommons.com).
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQIzBAEBCgAdFiEEQfDqFpmnTB4vpBtTjPlrw/+du84FAl73XO4ACgkQjPlrw/+d
|
||||
u84JUg//TT6ySwiHIjl+gZiIZQjiNIpPqrpeb8pmJvoL1+CkJgidPR03pMbuz5XT
|
||||
TGczVIreLa9jf3K96Sj/0uRcTi5mZWE6pfAZT6hTVPiajHPri14oDrXB0Yar1btx
|
||||
Oq+3b+ryb6BOfXKub4Kl1JP8PyUanBD/1/j4jRAZcxOjHOEUdw5gJYH3VpKm3+ds
|
||||
RaMABUmpPd6dOCrfGojbAeajcWSP1KpX2zxDYxJvnYZ8f3spl2mVb8ajqHt7T3FK
|
||||
Kff2YQBT3HE3Ln81bu2SbhE7E5X5BWasNe1oH73jS9T2CsXkretg68lVUR2Cw7C2
|
||||
uHtbn8zA2ySntb6xyInHHA7uhBNRtnxwm9QSRgq/PisuIqA9qMnhDSSSVXopi6FE
|
||||
iF4Zf5NOq0l2ZIJQpNEOXG7WnMpqaXr7GYqq47PBaY80H+UdnLyVmJxattPDSV5e
|
||||
tiBUF3862PWeDxsIe3jbce4N8SPstcqgnTGiSTcNft7WzhW6pxDhYDaNa/oqJ3bk
|
||||
zAkX6LAG1MhNsGnnOZ75e9hEjFZg7grVmjwRbyo+2PgNy1e0+UNTCndCM7q3fhjO
|
||||
AE3hNB+YGQGiXJmD5oh4zFVeJldIkRBjOdagOmvrQizJsWpgh9xD0jEfL5dxcq9c
|
||||
/1InfBONgGnEwXSengCHVVEOIJeB/VPxd1y6O3240szyJxYe6p0=
|
||||
=UKxv
|
||||
-----END PGP SIGNATURE-----
|
Loading…
x
Reference in New Issue
Block a user