Skip to content
This repository was archived by the owner on Mar 23, 2021. It is now read-only.

Commit c85df0c

Browse files
Merge #253
253: Refactor towards more idiomatic async code r=mergify[bot] a=thomaseizinger This refactoring addresses several pain points of the current codebase: 1. We used to write to the env file from several places. This made it hard to understand, what was actually written to the file. It was also error prone because there were several concurrent tasks doing it. 2. Many APIs were stringly-typed. This made it hard to understand, what information is actually passed around. 3. The codebase was still using old Futures in many places. Using new futures allows us to use async/await which makes the code a lot shorter. Fixes #37. Fixes #141. Fixes #101. (Other PRs did work here as-well but with this one, things are actually fairly nice.) Fixes #40. Co-authored-by: Thomas Eizinger <thomas@eizinger.io>
2 parents 92cf33f + 05bf1aa commit c85df0c

File tree

15 files changed

+817
-940
lines changed

15 files changed

+817
-940
lines changed

Cargo.lock

Lines changed: 142 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ edition = "2018"
66

77
[dependencies]
88
anyhow = "1.0.25"
9+
async-std = "1.2.0"
10+
derive_more = "0.99.2"
911
dirs = "2"
1012
emerald-vault-core = { git = "http://github.com/thomaseizinger/emerald-vault.git", branch = "create-comit-app-compatible", default-features = false }
1113
envfile = "0.2"
@@ -16,7 +18,6 @@ hex = "0.4.0"
1618
hex-literal = "0.2.1"
1719
hex-serde = "0.1.0"
1820
lazy_static = "1.4"
19-
port_check = "0.1"
2021
reqwest = { version = "0.9", default-features = false }
2122
rust_bitcoin = { version = "0.19.1", package = "bitcoin" }
2223
secp256k1 = { version = "0.12", features = ["rand"] }

src/cnd_settings.rs

Lines changed: 0 additions & 127 deletions
This file was deleted.

src/docker/bitcoin.rs

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
use crate::docker::{
2+
self, free_local_port::free_local_port, DockerImage, LogMessage, DOCKER_NETWORK,
3+
};
4+
use anyhow::Context;
5+
use futures::compat::Future01CompatExt;
6+
use reqwest::r#async::Client;
7+
use rust_bitcoin::{
8+
self,
9+
hashes::sha256d,
10+
util::bip32::{ChildNumber, ExtendedPrivKey},
11+
Address, Amount, Network,
12+
};
13+
use secp256k1::{
14+
rand::{thread_rng, Rng},
15+
Secp256k1,
16+
};
17+
use shiplift::ContainerOptions;
18+
19+
const IMAGE: &str = "coblox/bitcoin-core:0.17.0";
20+
21+
const USERNAME: &str = "bitcoin";
22+
const PASSWORD: &str = "t68ej4UX2pB0cLlGwSwHFBLKxXYgomkXyFyxuBmm2U8=";
23+
24+
#[derive(derive_more::Display, Copy, Clone)]
25+
#[display(fmt = "127.0.0.1:{}", port)]
26+
pub struct BitcoindP2PUri {
27+
port: u16,
28+
}
29+
30+
#[derive(derive_more::Display, Copy, Clone)]
31+
#[display(fmt = "http://localhost:{}", port)]
32+
pub struct BitcoindHttpEndpoint {
33+
port: u16,
34+
}
35+
36+
pub struct BitcoindInstance {
37+
pub p2p_uri: BitcoindP2PUri,
38+
pub http_endpoint: BitcoindHttpEndpoint,
39+
pub account_0: Account,
40+
pub account_1: Account,
41+
}
42+
43+
pub async fn new_bitcoind_instance() -> anyhow::Result<BitcoindInstance> {
44+
let mut options_builder = ContainerOptions::builder(IMAGE);
45+
options_builder.name("bitcoin");
46+
options_builder.network_mode(DOCKER_NETWORK);
47+
options_builder.cmd(vec![
48+
"-regtest",
49+
"-server",
50+
"-rest",
51+
"-printtoconsole",
52+
"-bind=0.0.0.0:18444",
53+
"-rpcbind=0.0.0.0:18443",
54+
"-rpcauth=bitcoin:1c0e8f3de84926c04115e7da7e501346$a48f42ad32741dd1755649c8b98663b3ccbebeb75f196389f9a5c8a96b72edb3",
55+
"-rpcallowip=0.0.0.0/0",
56+
"-debug=1",
57+
"-acceptnonstdtxn=0",
58+
"-txindex",
59+
]);
60+
61+
let p2p_port = free_local_port().await?;
62+
options_builder.expose(18444, "tcp", p2p_port as u32);
63+
64+
let p2p_uri = BitcoindP2PUri { port: p2p_port };
65+
66+
let http_port = free_local_port().await?;
67+
options_builder.expose(18443, "tcp", http_port as u32);
68+
69+
let http_endpoint = BitcoindHttpEndpoint { port: http_port };
70+
71+
let options = options_builder.build();
72+
73+
docker::start(
74+
DockerImage(IMAGE),
75+
options,
76+
LogMessage("Flushed wallet.dat"),
77+
)
78+
.await?;
79+
80+
let account_0 = fund_new_account(http_endpoint).await?;
81+
let account_1 = fund_new_account(http_endpoint).await?;
82+
83+
Ok(BitcoindInstance {
84+
p2p_uri,
85+
http_endpoint,
86+
account_0,
87+
account_1,
88+
})
89+
}
90+
91+
async fn fund_new_account(endpoint: BitcoindHttpEndpoint) -> anyhow::Result<Account> {
92+
let account = Account::new_random()?;
93+
94+
let (_, address) = account.first_account();
95+
96+
fund(
97+
&endpoint.to_string(),
98+
address,
99+
Amount::from_sat(1_000_000_000),
100+
)
101+
.await?;
102+
103+
Ok(account)
104+
}
105+
106+
pub async fn mine_a_block(endpoint: BitcoindHttpEndpoint) -> anyhow::Result<()> {
107+
let _ = reqwest::r#async::Client::new()
108+
.post(&endpoint.to_string())
109+
.basic_auth(USERNAME, Some(PASSWORD))
110+
.json(&GenerateRequest::new(1))
111+
.send()
112+
.compat()
113+
.await?;
114+
115+
Ok(())
116+
}
117+
118+
#[derive(Clone)]
119+
pub struct Account {
120+
pub master: ExtendedPrivKey,
121+
first_account: rust_bitcoin::util::key::PrivateKey,
122+
}
123+
124+
impl Account {
125+
fn new_random() -> anyhow::Result<Self> {
126+
let mut seed = [0u8; 32];
127+
thread_rng().fill_bytes(&mut seed);
128+
129+
let master = ExtendedPrivKey::new_master(rust_bitcoin::Network::Regtest, &seed)
130+
.context("failed to generate new random extended private key from seed")?;
131+
132+
// define derivation path to derive private keys from the master key
133+
let derivation_path = vec![
134+
ChildNumber::from_hardened_idx(44)?,
135+
ChildNumber::from_hardened_idx(1)?,
136+
ChildNumber::from_hardened_idx(0)?,
137+
ChildNumber::from_normal_idx(0)?,
138+
ChildNumber::from_normal_idx(0)?,
139+
];
140+
141+
// derive a private key from the master key
142+
let priv_key = master
143+
.derive_priv(&Secp256k1::new(), &derivation_path)
144+
.map(|secret_key| secret_key.private_key)?;
145+
146+
// it is not great to store derived data in here but since the derivation can fail, it is better to fail early instead of later
147+
Ok(Self {
148+
master,
149+
first_account: priv_key,
150+
})
151+
}
152+
153+
fn first_account(&self) -> (rust_bitcoin::util::key::PrivateKey, rust_bitcoin::Address) {
154+
// derive an address from the private key
155+
let address = derive_address(self.first_account.key);
156+
157+
(self.first_account, address)
158+
}
159+
}
160+
161+
#[derive(Debug, serde::Serialize)]
162+
pub struct GenerateRequest {
163+
jsonrpc: String,
164+
id: String,
165+
method: String,
166+
params: Vec<u32>,
167+
}
168+
169+
impl GenerateRequest {
170+
pub fn new(number: u32) -> Self {
171+
GenerateRequest {
172+
jsonrpc: "1.0".to_string(),
173+
id: "generate".to_string(),
174+
method: "generate".to_string(),
175+
params: vec![number],
176+
}
177+
}
178+
}
179+
180+
#[derive(Debug, serde::Serialize)]
181+
struct FundRequest {
182+
jsonrpc: String,
183+
id: String,
184+
method: String,
185+
params: Vec<String>,
186+
}
187+
188+
impl FundRequest {
189+
fn new(address: Address, amount: Amount) -> Self {
190+
FundRequest {
191+
jsonrpc: "1.0".to_string(),
192+
id: "fund".to_string(),
193+
method: "sendtoaddress".to_string(),
194+
params: vec![address.to_string(), amount.as_btc().to_string()],
195+
}
196+
}
197+
}
198+
199+
#[derive(Debug, serde::Deserialize)]
200+
struct FundResponse {
201+
result: sha256d::Hash,
202+
error: Option<String>,
203+
id: String,
204+
}
205+
206+
async fn fund(endpoint: &str, address: Address, amount: Amount) -> anyhow::Result<sha256d::Hash> {
207+
let client = Client::new();
208+
209+
let _ = client
210+
.post(endpoint)
211+
.basic_auth(USERNAME, Some(PASSWORD))
212+
.json(&GenerateRequest::new(101))
213+
.send()
214+
.compat()
215+
.await?;
216+
217+
let mut response = client
218+
.post(endpoint)
219+
.basic_auth(USERNAME, Some(PASSWORD))
220+
.json(&FundRequest::new(address, amount))
221+
.send()
222+
.compat()
223+
.await?;
224+
225+
let response = response.json::<FundResponse>().compat().await?;
226+
227+
Ok(response.result)
228+
}
229+
230+
fn derive_address(secret_key: secp256k1::SecretKey) -> Address {
231+
let public_key =
232+
secp256k1::PublicKey::from_secret_key(&secp256k1::Secp256k1::new(), &secret_key);
233+
derive_p2wpkh_regtest_address(public_key)
234+
}
235+
236+
fn derive_p2wpkh_regtest_address(public_key: secp256k1::PublicKey) -> Address {
237+
Address::p2wpkh(
238+
&rust_bitcoin::PublicKey {
239+
compressed: true, // Only used for serialization
240+
key: public_key,
241+
},
242+
Network::Regtest,
243+
)
244+
}

0 commit comments

Comments
 (0)