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

Commit e837d8d

Browse files
committed
Upgrade bitcoind to 0.20.0, extend bitcoin env
1 parent ba47e7e commit e837d8d

File tree

4 files changed

+216
-30
lines changed

4 files changed

+216
-30
lines changed

Cargo.lock

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

scripts/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ serde_json = "1"
2525
shiplift = { version = "0.6", default-features = false }
2626
structopt = "0.3"
2727
tempfile = "3.1.0"
28+
thiserror = "1"
2829
tokio = { version = "0.2", features = ["fs", "rt-core", "time", "tcp", "signal"] }
2930
tokio-compat = "0.1"
3031
toml = "0.5"

scripts/src/docker/bitcoin.rs

Lines changed: 184 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ use crate::docker::{
1717
self, docker_daemon_ip, free_local_port::free_local_port, DockerImage, LogMessage,
1818
DOCKER_NETWORK,
1919
};
20+
use serde::export::{Formatter, TryFrom};
21+
use std::fmt;
22+
use std::fmt::Display;
2023

21-
const IMAGE: &str = "coblox/bitcoin-core:0.17.0";
24+
const IMAGE: &str = "coblox/bitcoin-core:0.20.0";
2225

23-
const USERNAME: &str = "bitcoin";
24-
const PASSWORD: &str = "t68ej4UX2pB0cLlGwSwHFBLKxXYgomkXyFyxuBmm2U8=";
26+
pub const USERNAME: &str = "bitcoin";
27+
pub const PASSWORD: &str = "t68ej4UX2pB0cLlGwSwHFBLKxXYgomkXyFyxuBmm2U8=";
2528

2629
#[derive(derive_more::Display, Copy, Clone)]
2730
#[display(fmt = "{}:{}", ip, port)]
@@ -60,6 +63,7 @@ pub async fn new_bitcoind_instance() -> anyhow::Result<BitcoindInstance> {
6063
"-debug=1",
6164
"-acceptnonstdtxn=0",
6265
"-txindex",
66+
"-fallbackfee=0.0002",
6367
]);
6468

6569
let p2p_port = free_local_port().await?;
@@ -86,10 +90,15 @@ pub async fn new_bitcoind_instance() -> anyhow::Result<BitcoindInstance> {
8690
LogMessage("Flushed wallet.dat"),
8791
vec![],
8892
)
89-
.await?;
93+
.await
94+
.context("unable to start bitcoind docker image")?;
9095

91-
let account_0 = fund_new_account(http_endpoint).await?;
92-
let account_1 = fund_new_account(http_endpoint).await?;
96+
let account_0 = fund_new_account(http_endpoint)
97+
.await
98+
.context("failed to fund first account")?;
99+
let account_1 = fund_new_account(http_endpoint)
100+
.await
101+
.context("failed to fund second account")?;
93102

94103
Ok(BitcoindInstance {
95104
p2p_uri,
@@ -115,16 +124,68 @@ async fn fund_new_account(endpoint: BitcoindHttpEndpoint) -> anyhow::Result<Acco
115124
}
116125

117126
pub async fn mine_a_block(endpoint: BitcoindHttpEndpoint) -> anyhow::Result<()> {
118-
reqwest::Client::new()
127+
let client = reqwest::Client::new();
128+
129+
let new_address: NewAddressResponse = client
130+
.post(&endpoint.to_string())
131+
.basic_auth(USERNAME, Some(PASSWORD))
132+
.json(&NewAddressRequest::new("bech32"))
133+
.send()
134+
.await
135+
.context("failed to create new address")?
136+
.json::<NewAddressResponse>()
137+
.await?;
138+
assert!(new_address.error.is_none());
139+
140+
let _ = client
119141
.post(&endpoint.to_string())
120142
.basic_auth(USERNAME, Some(PASSWORD))
121-
.json(&GenerateRequest::new(1))
143+
.json(&GenerateToAddressRequest::new(1, new_address.result))
122144
.send()
123145
.await?;
124146

125147
Ok(())
126148
}
127149

150+
pub struct DerivationPath {
151+
path: Vec<ChildNumber>,
152+
}
153+
154+
impl Display for DerivationPath {
155+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
156+
for i in 0..self.path.len() {
157+
let elem = self.path.get(i).unwrap();
158+
159+
let separator = if i == self.path.len() - 1 { "" } else { "/" };
160+
161+
match elem {
162+
ChildNumber::Normal { index } => {
163+
write!(f, "{:?}{:0}", index, separator)?;
164+
}
165+
ChildNumber::Hardened { index } => {
166+
write!(f, "{:?}h{:0}", index, separator)?;
167+
}
168+
}
169+
}
170+
171+
Ok(())
172+
}
173+
}
174+
175+
impl DerivationPath {
176+
pub fn bip44_bitcoin_testnet() -> anyhow::Result<Self> {
177+
Ok(Self {
178+
path: vec![
179+
ChildNumber::from_hardened_idx(44)?,
180+
ChildNumber::from_hardened_idx(1)?,
181+
ChildNumber::from_hardened_idx(0)?,
182+
ChildNumber::from_normal_idx(0)?,
183+
ChildNumber::from_normal_idx(0)?,
184+
],
185+
})
186+
}
187+
}
188+
128189
#[derive(Clone)]
129190
pub struct Account {
130191
pub master: ExtendedPrivKey,
@@ -140,17 +201,12 @@ impl Account {
140201
.context("failed to generate new random extended private key from seed")?;
141202

142203
// define derivation path to derive private keys from the master key
143-
let derivation_path = vec![
144-
ChildNumber::from_hardened_idx(44)?,
145-
ChildNumber::from_hardened_idx(1)?,
146-
ChildNumber::from_hardened_idx(0)?,
147-
ChildNumber::from_normal_idx(0)?,
148-
ChildNumber::from_normal_idx(0)?,
149-
];
204+
let derivation_path =
205+
DerivationPath::bip44_bitcoin_testnet().context("failed to create derivation path")?;
150206

151207
// derive a private key from the master key
152208
let priv_key = master
153-
.derive_priv(&Secp256k1::new(), &derivation_path)
209+
.derive_priv(&Secp256k1::new(), &derivation_path.path)
154210
.map(|secret_key| secret_key.private_key)?;
155211

156212
// 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
@@ -169,20 +225,55 @@ impl Account {
169225
}
170226

171227
#[derive(Debug, serde::Serialize)]
172-
pub struct GenerateRequest {
228+
pub struct NewAddressRequest {
229+
jsonrpc: String,
230+
id: String,
231+
method: String,
232+
params: Vec<String>,
233+
}
234+
235+
impl NewAddressRequest {
236+
pub fn new(address_format: &str) -> Self {
237+
let mut params = Vec::new();
238+
let label = "";
239+
params.push(label.to_owned());
240+
params.push(address_format.to_owned());
241+
242+
NewAddressRequest {
243+
jsonrpc: "1.0".to_string(),
244+
id: "getnewaddress".to_string(),
245+
method: "getnewaddress".to_string(),
246+
params,
247+
}
248+
}
249+
}
250+
251+
#[derive(Debug, serde::Serialize)]
252+
pub struct GenerateToAddressRequest {
173253
jsonrpc: String,
174254
id: String,
175255
method: String,
176-
params: Vec<u32>,
256+
params: Vec<serde_json::Value>,
177257
}
178258

179-
impl GenerateRequest {
180-
pub fn new(number: u32) -> Self {
181-
GenerateRequest {
259+
impl GenerateToAddressRequest {
260+
pub fn new(number: u32, address: Address) -> Self {
261+
let mut params = Vec::new();
262+
263+
let number = serde_json::Value::Number(
264+
serde_json::Number::try_from(number).expect("can convert to number"),
265+
);
266+
assert!(number.is_u64());
267+
params.push(number);
268+
269+
let address = serde_json::Value::String(address.to_string());
270+
params.push(address);
271+
272+
GenerateToAddressRequest {
182273
jsonrpc: "1.0".to_string(),
183-
id: "generate".to_string(),
184-
method: "generate".to_string(),
185-
params: vec![number],
274+
id: "generatetoaddress".to_string(),
275+
method: "generatetoaddress".to_string(),
276+
params,
186277
}
187278
}
188279
}
@@ -208,22 +299,48 @@ impl FundRequest {
208299

209300
#[derive(Debug, serde::Deserialize)]
210301
struct FundResponse {
211-
result: sha256d::Hash,
212-
error: Option<String>,
302+
result: Option<sha256d::Hash>,
303+
error: Option<JsonRpcError>,
304+
id: String,
305+
}
306+
307+
#[derive(Debug, serde::Deserialize)]
308+
struct NewAddressResponse {
309+
result: Address,
310+
error: Option<JsonRpcError>,
213311
id: String,
214312
}
215313

314+
#[derive(Debug, serde::Deserialize, thiserror::Error)]
315+
#[error("JSON-RPC request failed with code {code}: {message}")]
316+
pub struct JsonRpcError {
317+
code: i64,
318+
message: String,
319+
}
320+
216321
async fn fund(endpoint: &str, address: Address, amount: Amount) -> anyhow::Result<sha256d::Hash> {
217322
let client = reqwest::Client::new();
218323

219-
let _ = client
324+
let new_address = client
220325
.post(endpoint)
221326
.basic_auth(USERNAME, Some(PASSWORD))
222-
.json(&GenerateRequest::new(101))
327+
.json(&NewAddressRequest::new("bech32"))
223328
.send()
329+
.await
330+
.context("failed to create new address")?
331+
.json::<NewAddressResponse>()
224332
.await?;
333+
assert!(new_address.error.is_none());
225334

226-
let response = client
335+
let _ = client
336+
.post(endpoint)
337+
.basic_auth(USERNAME, Some(PASSWORD))
338+
.json(&GenerateToAddressRequest::new(101, new_address.result))
339+
.send()
340+
.await
341+
.context("failed to generate blocks")?;
342+
343+
let response: FundResponse = client
227344
.post(endpoint)
228345
.basic_auth(USERNAME, Some(PASSWORD))
229346
.json(&FundRequest::new(address, amount))
@@ -232,7 +349,15 @@ async fn fund(endpoint: &str, address: Address, amount: Amount) -> anyhow::Resul
232349
.json::<FundResponse>()
233350
.await?;
234351

235-
Ok(response.result)
352+
match response.error {
353+
None => match response.result {
354+
None => Err(anyhow::Error::msg(
355+
"no transaction hash returned without yielding error",
356+
)),
357+
Some(tx_hash) => Ok(tx_hash),
358+
},
359+
Some(error) => Err(anyhow::Error::new(error)),
360+
}
236361
}
237362

238363
fn derive_address(secret_key: secp256k1::SecretKey) -> Address {
@@ -250,3 +375,32 @@ fn derive_p2wpkh_regtest_address(public_key: secp256k1::PublicKey) -> Address {
250375
Network::Regtest,
251376
)
252377
}
378+
379+
#[cfg(test)]
380+
mod tests {
381+
use super::*;
382+
use std::str::FromStr;
383+
384+
#[test]
385+
fn format_derivation_path() {
386+
let derivation_path = DerivationPath::bip44_bitcoin_testnet().unwrap();
387+
388+
let to_string = derivation_path.to_string();
389+
assert_eq!(to_string, "44h/1h/0h/0/0")
390+
}
391+
392+
#[test]
393+
fn generate_to_address_request_does_not_panic() {
394+
let number = 101;
395+
let address = Address::from_str("2MubReUTptB6isbuFmsRiN3BPHaeHpiAjQM").unwrap();
396+
397+
let _ = GenerateToAddressRequest::new(number, address);
398+
}
399+
400+
#[test]
401+
fn new_address_request_does_not_panic() {
402+
let format = "bech32";
403+
404+
let _ = NewAddressRequest::new(format);
405+
}
406+
}

scripts/src/env/start.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use anyhow::Context;
22
use envfile::EnvFile;
33

4+
use crate::docker::bitcoin::{DerivationPath, PASSWORD, USERNAME};
45
use crate::{
56
docker::{
67
self,
@@ -76,6 +77,15 @@ pub async fn execute() -> anyhow::Result<Environment> {
7677
&format!("{}", bitcoind.account_1.master),
7778
);
7879
envfile.update("BITCOIN_P2P_URI", &bitcoind.p2p_uri.to_string());
80+
envfile.update("BITCOIN_HTTP_URI", &bitcoind.http_endpoint.to_string());
81+
envfile.update("BITCOIN_USERNAME", USERNAME);
82+
envfile.update("BITCOIN_PASSWORD", PASSWORD);
83+
envfile.update(
84+
"BITCOIN_DERIVATION_PATH",
85+
&*DerivationPath::bip44_bitcoin_testnet()
86+
.expect("can create derivation path")
87+
.to_string(),
88+
);
7989

8090
envfile.update("HTTP_URL_CND_0", &cnd_0.http_endpoint.to_string());
8191
envfile.update("HTTP_URL_CND_1", &cnd_1.http_endpoint.to_string());

0 commit comments

Comments
 (0)