Compare commits
56 Commits
experiment
...
htlc-exper
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e897916ce8 | ||
|
|
01819d3c78 | ||
|
|
8a4c43a0d0 | ||
|
|
aacec96569 | ||
|
|
cac76373ec | ||
|
|
f607bf25d3 | ||
|
|
cea9d8ead5 | ||
|
|
f78784973c | ||
|
|
42dbf738c6 | ||
|
|
474bc84252 | ||
|
|
62305c4213 | ||
|
|
170723d05e | ||
|
|
bcd76c2ebc | ||
|
|
a7a66ad43b | ||
|
|
03ecff9541 | ||
|
|
ebe5283ed2 | ||
|
|
192d0c3da2 | ||
|
|
82e8612b38 | ||
|
|
6b55b0d0a4 | ||
|
|
2ced899c6b | ||
|
|
e1e2f49957 | ||
|
|
e4eedbd206 | ||
|
|
918544a76b | ||
|
|
1fb1bea9aa | ||
|
|
221009829e | ||
|
|
2f4d1d7ee8 | ||
|
|
8db2ca6861 | ||
|
|
dcf4761940 | ||
|
|
0edfbfaab1 | ||
|
|
b031fbc244 | ||
|
|
15e217a6d4 | ||
|
|
aa812d4101 | ||
|
|
4b63245bfe | ||
|
|
e4e8298fcd | ||
|
|
62a8a7020c | ||
|
|
09c10b8d45 | ||
|
|
479abcbba2 | ||
|
|
b78aadabff | ||
|
|
e2c963983c | ||
|
|
c65bc369ce | ||
|
|
3d19cc50fb | ||
|
|
931cba9cde | ||
|
|
c5d52dd537 | ||
|
|
4cf3d03349 | ||
|
|
f6db54ac34 | ||
|
|
d711ca50d5 | ||
|
|
a3ee17119d | ||
|
|
e1a48c3fca | ||
|
|
d7dfa9cc24 | ||
|
|
447f99edf4 | ||
|
|
a3794e64f5 | ||
|
|
e5c9c1364c | ||
|
|
3595675d41 | ||
|
|
b206c0e6ce | ||
|
|
d937f9ffaa | ||
|
|
d537e80de1 |
5
.idea/.gitignore
generated
vendored
5
.idea/.gitignore
generated
vendored
@@ -1,5 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/side.iml" filepath="$PROJECT_DIR$/.idea/side.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
17
.idea/side.iml
generated
17
.idea/side.iml
generated
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="EMPTY_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/crates/bft-json-crdt/benches" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/crates/bft-json-crdt/bft-crdt-derive/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/crates/bft-json-crdt/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/crates/bft-json-crdt/tests" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/side-node/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/side-node/tests" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/side-watcher/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
1144
Cargo.lock
generated
1144
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Side Protocol
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
55
README.md
55
README.md
@@ -2,6 +2,10 @@
|
||||
|
||||
This is a proof of concept implementation of a BFT-CRDT blockchain system.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Install a recent version of Rust.
|
||||
|
||||
## Running in development
|
||||
|
||||
Run the watcher first:
|
||||
@@ -30,6 +34,8 @@ cargo watch -x "run -- run -- node3"
|
||||
cargo watch -x "run -- run -- node4"
|
||||
```
|
||||
|
||||
You can then type directly into each of the Side Node consoles. Messages will be relayed to each Side Node, and the transaction history will end up being the same on all nodes.
|
||||
|
||||
## Discussion
|
||||
|
||||
What we have here is a very simple system comprised of two key parts: the Side Node, and the Side Watcher.
|
||||
@@ -38,14 +44,20 @@ What we have here is a very simple system comprised of two key parts: the Side N
|
||||
|
||||
The Side Nodes make up a system of BFT-CRDT-producing nodes that can make a blockchain. Currently they can reliably send transactions to each other in a secure way, such that all nodes they communicate with can tell whether received transactions are obeying the rules of the system.
|
||||
|
||||
The Side Node does not download any chain state, and if one goes off-line it will miss transactions. This is expected at the moment and fairly easy to fix, with a bit of work.
|
||||
|
||||
Next dev tasks:
|
||||
|
||||
[ ] enable Side Nodes to download current P2P chain state so that they start out with a consistent copy of transaction data
|
||||
[ ] add smart contract execution engine (CosmWasm would be a good first choice)
|
||||
[ ] enable Side Nodes to download contract code for a given contract
|
||||
[ ] enable Side Nodes to download current contract state for a given contract
|
||||
[ ] switch to full P2P messaging instead of websockets
|
||||
[ ] take the Side Watcher out of the system by electing a Side Node as a leader, so that agreement about transaction inclusion can be reached for a given block.
|
||||
- [ ] we don't need a Watcher, the first node can act as a leader until people decide they don't want to trust it any more
|
||||
- [ ] the leader node can have a timer in it for block creation
|
||||
- [ ] code up the ability to switch leaders (can be a human decision at first, later an (optional) automated choice)
|
||||
- [ ] pick a commit and reveal scheme to remove MEV. One thing to investigate is [single-use seals](https://docs.rgb.info/distributed-computing-concepts/single-use-seals)
|
||||
- [ ] enable Side Nodes to download current P2P chain state so that they start - out with a consistent copy of transaction data, and also do catch-up after going off-line
|
||||
- [ ] remove the proc macro code from bft-json-crdt
|
||||
- [ ] add smart contract execution engine (CosmWasm would be a good first choice)
|
||||
- [ ] enable Side Nodes to download contract code for a given contract
|
||||
- [ ] enable Side Nodes to download current contract state for a given contract
|
||||
- [ ] switch to full P2P messaging instead of websockets
|
||||
|
||||
### Side Watcher
|
||||
|
||||
@@ -53,25 +65,44 @@ The Side Watcher is a simple relayer node that sits between the Side Chain (Cosm
|
||||
|
||||
To fulfill the promises in the Lite Paper, the Side Watcher needs to:
|
||||
|
||||
[ ] make a block for the P2P when the Side Chain creates a block
|
||||
[ ] submit P2P chain data to the Side Chain
|
||||
- [ ] make a block for the BFT-CRDT chain when the Side Chain creates a block
|
||||
- [ ] submit BFT-CRDT chain data to the Side Chain
|
||||
|
||||
Later, we will aim to remove the Side Watcher from the architecture, by (a) moving to pure P2P transactions between Side Nodes, and (b) doing leader election of a Side Node to reach agreement on the submitted block.
|
||||
|
||||
## Bitcoin integration
|
||||
|
||||
There is a Bitcoin client integrated into the node, which can do simple coin transfers using esplora and the Mutinynet server's Signet (30 second blocktime).
|
||||
There is an Esplora Bitcoin client integrated into the node
|
||||
|
||||
### Simple coin transfers
|
||||
|
||||
The client can do simple coin transfers using esplora and the Mutinynet server's Signet (30 second blocktime).
|
||||
|
||||
The client's demo driver can be run by doing:
|
||||
|
||||
```
|
||||
cargo run -- init dave
|
||||
cargo run -- init sammy
|
||||
cargo run -- btc
|
||||
cargo run -- btc-transfer
|
||||
```
|
||||
|
||||
You'll need to have funded the "dave" address prior to running the `btc` command - otherwise the transfer will fail gracefully.
|
||||
|
||||
I was using this primarily as a way to experiment with constructing and broadcasting Bitcoin transactions, with the hope that it would be possible to move on to more advanced constructions (e.g. state channels). However, now that I look at all the options, it seems that multi-party state channels in Bitcoin are (probably) impossible to construct.
|
||||
I have been using this primarily as a way to experiment with constructing and broadcasting Bitcoin transactions, with the hope that it would be possible to move on to more advanced constructions (e.g. state channels).
|
||||
|
||||
There is a second, unused Bitcoin client in place which uses Blockstream's Electrum server, but this didn't seem to be working properly with respect to Signet Bitcoin network during my testing, so I went with the esplora / Mutiny version instead.
|
||||
### HTLCs (in progress)
|
||||
|
||||
An experimental driver for Bitcoin Hash Time Locked Contracts (HTLCs).
|
||||
|
||||
|
||||
## Possible uses
|
||||
|
||||
### DKG
|
||||
|
||||
It strikes me that there are many, many systems which rely on a trusted setup, and which might be able to use Distributed Key Generation (DKG) instead. SNARK systems for instance all have this problem.
|
||||
|
||||
It is not necessarily the case that e.g. signer participants and validators are the same entities. Being able to quickly spin up a blockchain and use it to sign (potentially temporary or ephemeral) keyshare data might be pretty useful.
|
||||
|
||||
### Cross chain transfers
|
||||
|
||||
The ability to be part of multiple consensus groups at once might provide new opportunities for cross-chain transfers.
|
||||
|
||||
@@ -8,28 +8,26 @@ edition = "2021"
|
||||
[dependencies]
|
||||
anyhow = "1.0.86"
|
||||
async-trait = "0.1.52"
|
||||
bdk = { version = "0.29.0", default-feature = false, features = ["all-keys"] }
|
||||
bdk_esplora = "0.15.0"
|
||||
bdk_sqlite = "0.2.0"
|
||||
bdk_wallet = { version = "1.0.0-alpha.13", features = ["all-keys"] }
|
||||
bdk = { version = "0.29.0", features = [
|
||||
"compiler",
|
||||
"use-esplora-blocking",
|
||||
"std",
|
||||
"keys-bip39",
|
||||
], default-features = false }
|
||||
bft-json-crdt = { path = "../crates/bft-json-crdt" }
|
||||
bft-crdt-derive = { path = "../crates/bft-json-crdt/bft-crdt-derive" }
|
||||
bitcoin = { version = "0.32.2", features = ["rand"] }
|
||||
clap = { version = "4.5.4", features = ["derive"] }
|
||||
dirs = "5.0.1"
|
||||
electrum-client = "0.20"
|
||||
ezsockets = { version = "*", features = ["client"] }
|
||||
fastcrypto = "0.1.8"
|
||||
indexmap = { version = "2.2.6", features = ["serde"] }
|
||||
reqwest = { version = "*", features = ["blocking"] }
|
||||
# serde_cbor = "0.11.2" # move to this once we need to pack things in CBOR
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.117"
|
||||
sha256 = "1.5.0"
|
||||
tokio = { version = "1.37.0", features = ["full"] }
|
||||
toml = "0.8.14"
|
||||
tracing = "0.1.32"
|
||||
# tracing-subscriber = "0.3.9"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -6,6 +6,7 @@ use bft_json_crdt::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod keys;
|
||||
pub mod stdin;
|
||||
pub mod websocket;
|
||||
|
||||
#[add_crdt_fields]
|
||||
|
||||
@@ -39,7 +39,7 @@ impl ezsockets::ClientExt for Client {
|
||||
/// When we receive a text message, apply the bft-crdt operation contained in it to our
|
||||
/// local bft-crdt.
|
||||
async fn on_text(&mut self, text: String) -> Result<(), ezsockets::Error> {
|
||||
let string_sha = utils::shassy(text.clone());
|
||||
let string_sha = utils::sha256(text.clone());
|
||||
println!("received text, sha: {string_sha}");
|
||||
let incoming: bft_json_crdt::json_crdt::SignedOp = serde_json::from_str(&text).unwrap();
|
||||
let object_sha = utils::shappy(incoming.clone());
|
||||
|
||||
115
side-node/src/bitcoin/client.rs
Normal file
115
side-node/src/bitcoin/client.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use bdk::{
|
||||
bitcoin::{psbt::PartiallySignedTransaction, secp256k1::PublicKey, Network, Transaction},
|
||||
blockchain::EsploraBlockchain,
|
||||
database::MemoryDatabase,
|
||||
keys::ExtendedKey,
|
||||
template::Bip84,
|
||||
wallet::AddressInfo,
|
||||
KeychainKind, SignOptions, SyncOptions, Wallet,
|
||||
};
|
||||
|
||||
use crate::{bitcoin::keys, utils};
|
||||
|
||||
/// A client that uses Esplora to interact with the Bitcoin network.
|
||||
pub struct BitcoinClient {
|
||||
pub(crate) blockchain: bdk::blockchain::EsploraBlockchain,
|
||||
name: String,
|
||||
pub(crate) wallet: Wallet<MemoryDatabase>,
|
||||
pub(crate) public_key: PublicKey,
|
||||
}
|
||||
|
||||
impl BitcoinClient {
|
||||
pub(crate) fn sync(&self) -> anyhow::Result<()> {
|
||||
tracing::info!("syncing {}'s wallet", self.name);
|
||||
self.wallet.sync(&self.blockchain, SyncOptions::default())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn broadcast(&self, tx: &Transaction) -> anyhow::Result<()> {
|
||||
tracing::info!(
|
||||
"broadcasting transaction, output will be at https://mutinynet.com/tx/{}",
|
||||
tx.txid()
|
||||
);
|
||||
let _ = self.blockchain.broadcast(&tx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Builds and signs a send transaction to send coins between addresses.
|
||||
///
|
||||
/// Does NOT send it, you must call `broadcast` to do that.
|
||||
///
|
||||
/// We could split the creation and signing easily if needed.
|
||||
pub(crate) fn build_and_sign_send_tx(
|
||||
&mut self,
|
||||
recipient: AddressInfo,
|
||||
amount: u64,
|
||||
) -> Result<bdk::bitcoin::Transaction, anyhow::Error> {
|
||||
let mut tx_builder = self.wallet.build_tx();
|
||||
tx_builder
|
||||
.add_recipient(recipient.script_pubkey(), amount)
|
||||
.enable_rbf();
|
||||
let (mut psbt, _) = tx_builder.finish()?;
|
||||
let tx = self.sign(&mut psbt, true)?.extract_tx();
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
pub(crate) fn sign(
|
||||
&self,
|
||||
psbt: &mut PartiallySignedTransaction,
|
||||
finalize: bool,
|
||||
) -> Result<PartiallySignedTransaction, anyhow::Error> {
|
||||
tracing::info!("{} signing PSBT", self.name);
|
||||
|
||||
let options = SignOptions {
|
||||
try_finalize: finalize,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let finalized = self.wallet.sign(psbt, options)?;
|
||||
|
||||
// make sure the PSBT is finalized if we asked for it
|
||||
if finalize {
|
||||
assert!(finalized)
|
||||
}
|
||||
Ok(psbt.to_owned())
|
||||
}
|
||||
|
||||
/// Creates a Bitcoin descriptor wallet with the mnemonic in the given user directory.
|
||||
pub(crate) fn create(name: &str, network: Network) -> anyhow::Result<BitcoinClient> {
|
||||
let keys_dir = utils::home(name);
|
||||
|
||||
// let mnemonic_path = crate::utils::side_paths(keys_dir).1; // TODO: this tuple stinks
|
||||
// let words = fs::read_to_string(mnemonic_path).expect("couldn't read bitcoin key file");
|
||||
|
||||
let xkey: ExtendedKey = keys::load_from_file(&keys_dir)?;
|
||||
|
||||
let xprv = xkey
|
||||
.into_xprv(Network::Signet)
|
||||
.expect("couldn't turn xkey into xprv");
|
||||
|
||||
let secp = bdk::bitcoin::secp256k1::Secp256k1::new();
|
||||
|
||||
let external_descriptor1 = Bip84(xprv.clone(), KeychainKind::External);
|
||||
let external_descriptor2 = Bip84(xprv, KeychainKind::External);
|
||||
let internal_descriptor = Some(Bip84(xprv, KeychainKind::Internal));
|
||||
|
||||
let wallet = Wallet::new(
|
||||
external_descriptor1,
|
||||
internal_descriptor,
|
||||
network,
|
||||
MemoryDatabase::default(),
|
||||
)?;
|
||||
|
||||
let blockchain = EsploraBlockchain::new("https://mutinynet.com/api", 20);
|
||||
let external_public_key = external_descriptor2.0.private_key.public_key(&secp);
|
||||
|
||||
let esplora = BitcoinClient {
|
||||
name: name.to_string(),
|
||||
wallet,
|
||||
blockchain,
|
||||
public_key: external_public_key,
|
||||
};
|
||||
|
||||
Ok(esplora)
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
use crate::{bitcoin, utils};
|
||||
use bdk::bitcoin::psbt::PartiallySignedTransaction;
|
||||
use bdk::bitcoin::Network;
|
||||
use bdk::database::MemoryDatabase;
|
||||
use bdk::keys::ExtendedKey;
|
||||
use bdk::template::Bip84;
|
||||
use bdk::wallet::AddressIndex::{self, New};
|
||||
use bdk::wallet::AddressInfo;
|
||||
use bdk::{blockchain::ElectrumBlockchain, electrum_client, SyncOptions};
|
||||
use bdk::{FeeRate, KeychainKind, SignOptions, TransactionDetails, Wallet};
|
||||
|
||||
/// DEPRECATED
|
||||
///
|
||||
/// This is a bdk example that uses the Electrum client to interact with the Bitcoin network.
|
||||
/// Electrum is a light client that connects to a server to get information about the Bitcoin network.
|
||||
/// The BDK itself does not have the ability to connect to e.g. esplora servers. As the Blockstream Electrum Signet
|
||||
/// server does not appear to be picking up transactions properly at the moment, I've shifted over to using
|
||||
/// the (more complex) `bdk_wallet` crate and the esplora client there (see the other bitcoin client).
|
||||
///
|
||||
/// Note:the types below are all completely different than the types in `bdk_wallet`.
|
||||
pub async fn run() -> Result<(), anyhow::Error> {
|
||||
let dave = utils::home(&"dave".to_string());
|
||||
let sammy = utils::home(&"sammy".to_string());
|
||||
let dave_key = bitcoin::keys::load_from_file(&dave).unwrap();
|
||||
let sammy_key = bitcoin::keys::load_from_file(&sammy).unwrap();
|
||||
|
||||
let dave_wallet = create_wallet(dave_key)?;
|
||||
let sammy_wallet = create_wallet(sammy_key)?;
|
||||
|
||||
let dave_address = dave_wallet.get_address(AddressIndex::Peek(0))?.to_string();
|
||||
let sammy_address = sammy_wallet.get_address(AddressIndex::Peek(0))?.to_string();
|
||||
|
||||
println!("Dave's address: {}", dave_address);
|
||||
println!("Sammy's address: {}", sammy_address);
|
||||
|
||||
let blockchain = ElectrumBlockchain::from(electrum_client::Client::new(
|
||||
"ssl://electrum.blockstream.info:60002",
|
||||
)?);
|
||||
|
||||
println!("Syncing...");
|
||||
dave_wallet.sync(&blockchain, SyncOptions::default())?;
|
||||
|
||||
display_balance(&dave_wallet);
|
||||
display_balance(&sammy_wallet);
|
||||
|
||||
let (mut psbt, details) =
|
||||
build_sending_tx(&dave_wallet, sammy_wallet.get_address(New)?).expect("psbt build error");
|
||||
|
||||
println!("About to sign the transaction: {:?}", details);
|
||||
|
||||
dave_wallet.sign(&mut psbt, SignOptions::default())?;
|
||||
let _signed_tx = psbt.extract_tx();
|
||||
|
||||
// println!("Broadcasting...");
|
||||
// blockchain.broadcast(&signed_tx).expect("broadcast error");
|
||||
// println!("Transaction ID: {:?}", signed_tx.txid());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a BDK wallet using BIP 84 descriptor ("m/84h/1h/0h/0" and "m/84h/1h/0h/1")
|
||||
pub fn create_wallet(xkey: ExtendedKey) -> anyhow::Result<Wallet<MemoryDatabase>> {
|
||||
let xprv = xkey
|
||||
.into_xprv(Network::Testnet)
|
||||
.expect("couldn't turn xkey into xprv");
|
||||
|
||||
let external_descriptor = Bip84(xprv, KeychainKind::External);
|
||||
let internal_descriptor = Some(Bip84(xprv, KeychainKind::Internal));
|
||||
|
||||
let wallet = Wallet::new(
|
||||
external_descriptor,
|
||||
internal_descriptor,
|
||||
Network::Testnet,
|
||||
MemoryDatabase::default(),
|
||||
)?;
|
||||
|
||||
Ok(wallet)
|
||||
}
|
||||
|
||||
fn display_balance(wallet: &Wallet<MemoryDatabase>) {
|
||||
println!(
|
||||
"Wallet balance for {} after syncing: {:?} sats on network {}",
|
||||
wallet
|
||||
.get_address(bdk::wallet::AddressIndex::Peek(0))
|
||||
.expect("couldn't get address"),
|
||||
wallet.get_balance().expect("couldn't show balance"),
|
||||
wallet.network(),
|
||||
);
|
||||
}
|
||||
|
||||
fn build_sending_tx(
|
||||
wallet: &Wallet<MemoryDatabase>,
|
||||
recipient: AddressInfo,
|
||||
) -> anyhow::Result<(PartiallySignedTransaction, TransactionDetails), anyhow::Error> {
|
||||
let mut builder = wallet.build_tx();
|
||||
builder
|
||||
.add_recipient(recipient.script_pubkey(), 1000)
|
||||
.enable_rbf()
|
||||
.do_not_spend_change()
|
||||
.fee_rate(FeeRate::from_sat_per_vb(7.0));
|
||||
Ok(builder.finish()?)
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
use std::{collections::BTreeSet, fs, io::Write};
|
||||
|
||||
use bdk::keys::bip39::Mnemonic;
|
||||
use bdk_esplora::{
|
||||
esplora_client::{self, AsyncClient},
|
||||
EsploraAsyncExt,
|
||||
};
|
||||
use bdk_wallet::{
|
||||
bitcoin::{Address, Amount, Network, Script},
|
||||
chain::ConfirmationTimeHeightAnchor,
|
||||
keys::{DerivableKey, ExtendedKey},
|
||||
wallet::AddressInfo,
|
||||
KeychainKind, SignOptions, Wallet,
|
||||
};
|
||||
|
||||
use bdk_sqlite::{rusqlite::Connection, Store};
|
||||
|
||||
use crate::utils;
|
||||
|
||||
const STOP_GAP: usize = 50;
|
||||
const PARALLEL_REQUESTS: usize = 5;
|
||||
|
||||
/// A wallet that uses the Esplora client to interact with the Bitcoin network.
|
||||
pub struct EsploraWallet {
|
||||
client: AsyncClient,
|
||||
db: Store<KeychainKind, ConfirmationTimeHeightAnchor>,
|
||||
name: String,
|
||||
wallet: Wallet,
|
||||
}
|
||||
|
||||
impl EsploraWallet {
|
||||
/// Builds and signs a send transaction to send coins between addresses.
|
||||
///
|
||||
/// Does NOT send it, you must call `broadcast` to do that.
|
||||
///
|
||||
/// We could split the creation and signing easily if needed.
|
||||
pub(crate) fn build_and_sign_send_tx(
|
||||
&mut self,
|
||||
recipient: Address,
|
||||
amount: Amount,
|
||||
) -> Result<bitcoin::Transaction, anyhow::Error> {
|
||||
let mut tx_builder = self.wallet.build_tx();
|
||||
tx_builder
|
||||
.add_recipient(recipient.script_pubkey(), amount)
|
||||
.enable_rbf();
|
||||
let mut psbt = tx_builder.finish()?;
|
||||
let finalized = self.wallet.sign(&mut psbt, SignOptions::default())?;
|
||||
assert!(finalized);
|
||||
let tx = psbt.extract_tx()?;
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
/// Syncs the wallet with the latest state of the Bitcoin blockchain
|
||||
pub(crate) async fn sync(&mut self) -> Result<(), anyhow::Error> {
|
||||
print!("Syncing...");
|
||||
|
||||
fn generate_inspect(
|
||||
kind: KeychainKind,
|
||||
) -> impl FnMut(u32, &Script) + Send + Sync + 'static {
|
||||
let mut once = Some(());
|
||||
let mut stdout = std::io::stdout();
|
||||
move |spk_i, _| {
|
||||
match once.take() {
|
||||
Some(_) => print!("\nScanning keychain [{:?}]", kind),
|
||||
None => print!(" {:<3}", spk_i),
|
||||
};
|
||||
stdout.flush().expect("must flush");
|
||||
}
|
||||
}
|
||||
let request = self
|
||||
.wallet
|
||||
.start_full_scan()
|
||||
.inspect_spks_for_all_keychains({
|
||||
let mut once = BTreeSet::<KeychainKind>::new();
|
||||
move |keychain, spk_i, _| {
|
||||
match once.insert(keychain) {
|
||||
true => print!("\nScanning keychain [{:?}]", keychain),
|
||||
false => print!(" {:<3}", spk_i),
|
||||
}
|
||||
std::io::stdout().flush().expect("must flush")
|
||||
}
|
||||
})
|
||||
.inspect_spks_for_keychain(
|
||||
KeychainKind::External,
|
||||
generate_inspect(KeychainKind::External),
|
||||
)
|
||||
.inspect_spks_for_keychain(
|
||||
KeychainKind::Internal,
|
||||
generate_inspect(KeychainKind::Internal),
|
||||
);
|
||||
let mut update = self
|
||||
.client
|
||||
.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)
|
||||
.await?;
|
||||
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
|
||||
let _ = update.graph_update.update_last_seen_unconfirmed(now);
|
||||
self.wallet.apply_update(update)?;
|
||||
self.persist_local()?;
|
||||
println!("Sync complete for {}", self.name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn persist_local(&mut self) -> Result<(), anyhow::Error> {
|
||||
Ok(if let Some(changeset) = self.wallet.take_staged() {
|
||||
self.db.write(&changeset)?;
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the next unused address from the wallet.
|
||||
pub(crate) fn next_unused_address(&mut self) -> Result<AddressInfo, anyhow::Error> {
|
||||
let address = self.wallet.next_unused_address(KeychainKind::External);
|
||||
self.persist_local()?;
|
||||
println!(
|
||||
"Generated address: https://mutinynet.com/address/{}",
|
||||
address
|
||||
);
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
/// Returns the balance of the wallet.
|
||||
pub(crate) fn balance(&self) -> bdk_wallet::wallet::Balance {
|
||||
self.wallet.balance()
|
||||
}
|
||||
|
||||
/// Broadcasts a signed transaction to the network.
|
||||
pub(crate) async fn broadcast(
|
||||
&self,
|
||||
tx: &bitcoin::Transaction,
|
||||
) -> Result<(), esplora_client::Error> {
|
||||
println!(
|
||||
"{} broadcasting tx https://mutinynet.com/tx/{}",
|
||||
self.name,
|
||||
tx.compute_txid()
|
||||
);
|
||||
self.client.broadcast(tx).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a Bitcoin descriptor wallet with the mnemonic in the given user directory.
|
||||
pub(crate) fn create_wallet(name: &str, network: Network) -> anyhow::Result<EsploraWallet> {
|
||||
let keys_dir = utils::home(name);
|
||||
|
||||
let mnemonic_path = crate::utils::side_paths(keys_dir).1; // TODO: this tuple stinks
|
||||
let mnemonic_words = fs::read_to_string(mnemonic_path).expect("couldn't read bitcoin key file");
|
||||
|
||||
println!("Creating wallet from mnemonic: {mnemonic_words}");
|
||||
let mnemonic = Mnemonic::parse(mnemonic_words).unwrap();
|
||||
|
||||
// Generate the extended key
|
||||
let xkey: ExtendedKey = mnemonic
|
||||
.into_extended_key()
|
||||
.expect("couldn't turn mnemonic into xkey");
|
||||
|
||||
let xprv = xkey
|
||||
.into_xprv(Network::Signet)
|
||||
.expect("problem converting xkey to xprv")
|
||||
.to_string();
|
||||
|
||||
println!("Setting up esplora database for {name}");
|
||||
|
||||
let db_path = format!("/tmp/{name}-bdk-esplora-async-example.sqlite");
|
||||
let conn = Connection::open(db_path)?;
|
||||
let mut db = Store::new(conn)?;
|
||||
let external_descriptor = format!("wpkh({xprv}/84'/1'/0'/0/*)");
|
||||
let internal_descriptor = format!("wpkh({xprv}/84'/1'/0'/1/*)");
|
||||
let changeset = db.read().expect("couldn't read esplora database");
|
||||
|
||||
let wallet = Wallet::new_or_load(
|
||||
&external_descriptor,
|
||||
&internal_descriptor,
|
||||
changeset,
|
||||
network,
|
||||
)
|
||||
.expect("problem setting up wallet");
|
||||
|
||||
let client = esplora_client::Builder::new("https://mutinynet.com/api")
|
||||
.build_async()
|
||||
.expect("couldn't build esplora client");
|
||||
|
||||
let esplora = EsploraWallet {
|
||||
name: name.to_string(),
|
||||
wallet,
|
||||
db,
|
||||
client,
|
||||
};
|
||||
|
||||
Ok(esplora)
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod electrum;
|
||||
pub mod esplora;
|
||||
@@ -1,62 +0,0 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use bdk_wallet::bitcoin::{Address, Amount, Network};
|
||||
|
||||
use crate::bitcoin::clients;
|
||||
|
||||
/// Demonstrates the use of bdk with the Esplora client.
|
||||
///
|
||||
/// This is more complex than the bare `bdk` crate, but the esplora client works.
|
||||
///
|
||||
/// Also, it very handily works with the mutinynet.com esplora server, which is configured
|
||||
/// with 30 second block times.
|
||||
pub(crate) async fn run() -> Result<(), anyhow::Error> {
|
||||
simple_transfer().await
|
||||
}
|
||||
|
||||
async fn simple_transfer() -> Result<(), anyhow::Error> {
|
||||
let mut dave = clients::esplora::create_wallet("dave", Network::Signet)?;
|
||||
let mut sammy = clients::esplora::create_wallet("sammy", Network::Signet)?;
|
||||
|
||||
let _next_address = dave.next_unused_address()?;
|
||||
|
||||
let dave_balance = dave.balance();
|
||||
println!(
|
||||
"Dave wallet balance before syncing: {} sats",
|
||||
dave_balance.total()
|
||||
);
|
||||
|
||||
dave.sync().await?;
|
||||
|
||||
let dave_balance = dave.balance();
|
||||
println!("Wallet balance after syncing: {} sats", dave_balance);
|
||||
|
||||
let sammy_address = sammy.next_unused_address()?.address;
|
||||
println!("Sammy's address: {}", sammy_address);
|
||||
|
||||
let sammy_balance = sammy.balance();
|
||||
println!(
|
||||
"Sammy wallet balance before syncing: {} sats",
|
||||
sammy_balance
|
||||
);
|
||||
|
||||
sammy.sync().await?;
|
||||
|
||||
let sammy_balance = sammy.balance();
|
||||
println!("Sammy wallet balance after syncing: {} sats", sammy_balance);
|
||||
|
||||
let send_amount = Amount::from_sat(500);
|
||||
|
||||
if dave_balance.total() < send_amount {
|
||||
println!(
|
||||
"Please send at least {} sats to the receiving address",
|
||||
send_amount
|
||||
);
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
let tx = dave.build_and_sign_send_tx(sammy_address, send_amount)?;
|
||||
dave.broadcast(&tx).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
51
side-node/src/bitcoin/driver/create_htlc.rs
Normal file
51
side-node/src/bitcoin/driver/create_htlc.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use crate::bitcoin::{self, driver};
|
||||
use crate::utils;
|
||||
use bdk::wallet::AddressIndex::New;
|
||||
use bdk::SignOptions;
|
||||
|
||||
pub(crate) async fn run() -> anyhow::Result<()> {
|
||||
tracing::info!("starting htlc flow");
|
||||
let (dave, sammy) = driver::setup().await?;
|
||||
let _ = dave.sync();
|
||||
let _ = sammy.sync();
|
||||
|
||||
// Create an HTLC descriptor with a redeem identity, a hashlock from the preimage,
|
||||
// a refund timelock, and a refund identity
|
||||
let value = 500;
|
||||
let recipient = sammy.wallet.get_address(New)?.script_pubkey();
|
||||
let hash_preimage = "blah".to_string();
|
||||
let hashlock = utils::sha256(hash_preimage);
|
||||
let htlc = bitcoin::htlc::Htlc::new(dave.public_key, hashlock, 100, sammy.public_key);
|
||||
let htlc_descriptor = htlc.to_miniscript_descriptor();
|
||||
|
||||
// format a new commitment transaction like in Lightning
|
||||
let mut commitment_builder = dave.wallet.build_tx();
|
||||
commitment_builder.enable_rbf();
|
||||
let (psbt, _) = commitment_builder
|
||||
.finish()
|
||||
.expect("unable to build commitment");
|
||||
|
||||
// sign the commitment transaction
|
||||
let mut dave_psbt = dave.sign(&mut psbt.clone(), false)?;
|
||||
let sammy_psbt = sammy.sign(&mut psbt.clone(), false)?;
|
||||
|
||||
dave_psbt
|
||||
.combine(sammy_psbt)
|
||||
.expect("problem combining bitcoin PSBTs"); // these guys love mutability
|
||||
|
||||
let finalized = dave
|
||||
.wallet
|
||||
.finalize_psbt(&mut dave_psbt, SignOptions::default())
|
||||
.expect("couldn't finalize");
|
||||
|
||||
assert!(finalized);
|
||||
let tx = dave_psbt.extract_tx();
|
||||
|
||||
let _ = dave.broadcast(&tx)?;
|
||||
|
||||
let _ = sammy.sync();
|
||||
let sammy_balance = sammy.wallet.get_balance()?;
|
||||
tracing::info!("sammy balance: {}", sammy_balance);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
27
side-node/src/bitcoin/driver/mod.rs
Normal file
27
side-node/src/bitcoin/driver/mod.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use super::client::BitcoinClient;
|
||||
use bdk::bitcoin::Network;
|
||||
use tracing::Level;
|
||||
use tracing_subscriber::{filter, fmt, layer::Layer, prelude::*, Registry};
|
||||
|
||||
pub mod create_htlc;
|
||||
pub mod policy_transfer;
|
||||
pub mod simple_transfer;
|
||||
|
||||
async fn setup() -> Result<(BitcoinClient, BitcoinClient), anyhow::Error> {
|
||||
tracing_setup();
|
||||
let dave = BitcoinClient::create("dave", Network::Signet)?;
|
||||
let sammy = BitcoinClient::create("sammy", Network::Signet)?;
|
||||
|
||||
Ok((dave, sammy))
|
||||
}
|
||||
|
||||
fn tracing_setup() {
|
||||
// show only info level logs and above:
|
||||
let info = filter::LevelFilter::from_level(Level::INFO);
|
||||
|
||||
// set up the tracing subscriber:
|
||||
let subscriber = Registry::default().with(fmt::layer().with_filter(info));
|
||||
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||
|
||||
tracing::info!("Tracing initialized.");
|
||||
}
|
||||
23
side-node/src/bitcoin/driver/policy_transfer.rs
Normal file
23
side-node/src/bitcoin/driver/policy_transfer.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use bdk::miniscript::policy;
|
||||
|
||||
use crate::bitcoin;
|
||||
|
||||
/// A miniscript-based simple transfer, equivalent to the `simple_transfer`
|
||||
/// but using a Bitcoin miniscript policy. TODO: finish implementation, it's not
|
||||
/// working yet.
|
||||
pub(crate) async fn run() -> anyhow::Result<()> {
|
||||
let (dave, _sammy) = bitcoin::driver::setup().await?;
|
||||
|
||||
tracing::info!("starting transfer policy flow");
|
||||
|
||||
let policy_str = format!("addr(bc1qgw6xanldsz959z45y4dszehx4xkuzf7nfhya8x)");
|
||||
|
||||
let policy = policy::Concrete::<bdk::bitcoin::PublicKey>::from_str(&policy_str)
|
||||
.expect("policy compilation failed")
|
||||
.to_owned();
|
||||
|
||||
tracing::info!("policy: {}", policy);
|
||||
Ok(())
|
||||
}
|
||||
30
side-node/src/bitcoin/driver/simple_transfer.rs
Normal file
30
side-node/src/bitcoin/driver/simple_transfer.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use crate::bitcoin::client::BitcoinClient;
|
||||
use crate::bitcoin::driver;
|
||||
use bdk::wallet::AddressIndex;
|
||||
|
||||
/// Run the simplest transfer flow. There is no policy file,
|
||||
/// it's just a normal bitcoin transaction for a sanity check.
|
||||
pub async fn run() -> Result<(), anyhow::Error> {
|
||||
let (mut dave, sammy) = driver::setup().await?;
|
||||
|
||||
let send_amount = 500;
|
||||
let _ = ensure_enough_sats(&dave, send_amount);
|
||||
|
||||
let sammy_address = sammy.wallet.get_address(AddressIndex::New)?;
|
||||
let tx = dave.build_and_sign_send_tx(sammy_address, send_amount)?;
|
||||
dave.broadcast(&tx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Exit if the wallet does not have enough sats to send.
|
||||
fn ensure_enough_sats(wallet: &BitcoinClient, send_amount: u64) -> anyhow::Result<()> {
|
||||
if wallet.wallet.get_balance()?.get_total() < send_amount {
|
||||
tracing::error!(
|
||||
"Please send at least {} sats to the receiving address. Exiting.",
|
||||
send_amount
|
||||
);
|
||||
std::process::exit(0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
58
side-node/src/bitcoin/htlc.rs
Normal file
58
side-node/src/bitcoin/htlc.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use bdk::{
|
||||
bitcoin::secp256k1::PublicKey,
|
||||
miniscript::{descriptor::Wsh, policy::Concrete},
|
||||
};
|
||||
|
||||
/// A hash time locked contract between two parties.
|
||||
///
|
||||
/// If the hash preimage of the hashlock is revealed, the value is sent to the redeem_identity.
|
||||
///
|
||||
/// Alternately, if the refund timelock expires, the value can be refunded to the refund_identity.
|
||||
pub(crate) struct Htlc {
|
||||
redeem_identity: PublicKey,
|
||||
hashlock: String,
|
||||
refund_timelock: u64,
|
||||
refund_indentiy: PublicKey,
|
||||
}
|
||||
|
||||
impl Htlc {
|
||||
/// Create a new HTLC.
|
||||
pub(crate) fn new(
|
||||
redeem_identity: PublicKey,
|
||||
hashlock: String,
|
||||
refund_timelock: u64,
|
||||
refund_indentiy: PublicKey,
|
||||
) -> Self {
|
||||
Self {
|
||||
redeem_identity,
|
||||
hashlock,
|
||||
refund_timelock,
|
||||
refund_indentiy,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_miniscript_descriptor(&self) -> Wsh<String> {
|
||||
let htlc_descriptor = Wsh::new(
|
||||
self.to_miniscript_policy()
|
||||
.compile()
|
||||
.expect("Policy compilation only fails on resource limits or mixed timelocks"),
|
||||
)
|
||||
.expect("Resource limits");
|
||||
assert!(htlc_descriptor.sanity_check().is_ok());
|
||||
tracing::info!("descriptor: {}", htlc_descriptor);
|
||||
|
||||
htlc_descriptor
|
||||
}
|
||||
|
||||
fn to_miniscript_policy(&self) -> Concrete<String> {
|
||||
Concrete::<String>::from_str(&format!(
|
||||
"or(10@and(sha256({secret_hash}),pk({redeem_identity})),1@and(older({expiry}),pk({refund_identity})))",
|
||||
secret_hash = self.hashlock,
|
||||
redeem_identity = self.redeem_identity,
|
||||
refund_identity = self.refund_indentiy,
|
||||
expiry = self.refund_timelock
|
||||
)).expect("Policy compilation only fails on resource limits or mixed timelocks")
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,6 @@ use std::{
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
pub fn make_mnemonic() -> String {
|
||||
let mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
|
||||
Mnemonic::generate((WordCount::Words12, Language::English)).unwrap();
|
||||
mnemonic.to_string()
|
||||
}
|
||||
|
||||
/// Write the mnemonic to a file in the node's side directory
|
||||
///
|
||||
/// TODO: obviously spitting the mnemonic out to the console is not for production
|
||||
@@ -30,8 +24,15 @@ pub(crate) fn write(mnemonic_path: &PathBuf) -> Result<(), std::io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn load_from_file(side_dir: &PathBuf) -> anyhow::Result<ExtendedKey> {
|
||||
let mnemonic_path = crate::utils::side_paths(side_dir.clone()).1; // TODO: this tuple stinks
|
||||
let mnemonic_words = fs::read_to_string(mnemonic_path).expect("couldn't read bitcoin key file");
|
||||
println!("Creating extended key from mnemonic: {mnemonic_words}");
|
||||
generate_extended_key(mnemonic_words)
|
||||
}
|
||||
|
||||
/// Creates Signet Bitcoin descriptors from a mnemonic
|
||||
pub fn get(mnemonic_words: String) -> anyhow::Result<ExtendedKey> {
|
||||
fn generate_extended_key(mnemonic_words: String) -> anyhow::Result<ExtendedKey> {
|
||||
let mnemonic = Mnemonic::parse(mnemonic_words).unwrap();
|
||||
|
||||
// Generate the extended key
|
||||
@@ -42,9 +43,8 @@ pub fn get(mnemonic_words: String) -> anyhow::Result<ExtendedKey> {
|
||||
Ok(xkey)
|
||||
}
|
||||
|
||||
pub(crate) fn load_from_file(side_dir: &PathBuf) -> anyhow::Result<ExtendedKey> {
|
||||
let mnemonic_path = crate::utils::side_paths(side_dir.clone()).1; // TODO: this tuple stinks
|
||||
let mnemonic_words = fs::read_to_string(mnemonic_path).expect("couldn't read bitcoin key file");
|
||||
println!("Creating wallet from mnemonic: {mnemonic_words}");
|
||||
get(mnemonic_words)
|
||||
fn make_mnemonic() -> String {
|
||||
let mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
|
||||
Mnemonic::generate((WordCount::Words12, Language::English)).unwrap();
|
||||
mnemonic.to_string()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod clients;
|
||||
pub mod client;
|
||||
pub mod driver;
|
||||
pub mod htlc;
|
||||
pub mod keys;
|
||||
|
||||
@@ -17,8 +17,14 @@ pub(crate) struct Args {
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
/// Placeholder for future BTC commands
|
||||
Btc {},
|
||||
/// transfers bitcoin between two wallets using a driver program
|
||||
BtcTransfer {},
|
||||
|
||||
/// transfers bitcoin but this time uses a Miniscript policy
|
||||
BtcPolicyTransfer {},
|
||||
|
||||
/// sets up a Bitcoin HTLC
|
||||
BtcHtlc {},
|
||||
|
||||
/// runs the Side Node
|
||||
Run { name: String },
|
||||
|
||||
@@ -10,7 +10,6 @@ pub mod bitcoin;
|
||||
pub(crate) mod cli;
|
||||
pub(crate) mod init;
|
||||
pub mod node;
|
||||
pub(crate) mod stdin;
|
||||
pub mod utils;
|
||||
|
||||
#[tokio::main]
|
||||
@@ -29,9 +28,18 @@ pub async fn run() {
|
||||
let mut node = setup(name).await;
|
||||
node.start().await;
|
||||
}
|
||||
Some(Commands::Btc {}) => {
|
||||
let _ = bitcoin::driver::run().await;
|
||||
Some(Commands::BtcTransfer {}) => {
|
||||
let _ = bitcoin::driver::simple_transfer::run().await;
|
||||
}
|
||||
|
||||
Some(Commands::BtcPolicyTransfer {}) => {
|
||||
let _ = bitcoin::driver::policy_transfer::run().await;
|
||||
}
|
||||
|
||||
Some(Commands::BtcHtlc {}) => {
|
||||
let _ = bitcoin::driver::create_htlc::run().await;
|
||||
}
|
||||
|
||||
None => println!("No command provided. Exiting. See --help for more information."),
|
||||
}
|
||||
}
|
||||
@@ -41,15 +49,16 @@ async fn setup(name: &String) -> SideNode {
|
||||
// First, load up the keys and create a bft-bft-crdt
|
||||
let side_dir = utils::home(name);
|
||||
let bft_crdt_keys = bft_crdt::keys::load_from_file(&side_dir);
|
||||
let keys = bitcoin::keys::load_from_file(&side_dir).unwrap();
|
||||
let bitcoin_wallet = bitcoin::clients::electrum::create_wallet(keys).unwrap();
|
||||
// let keys = bitcoin::keys::load_from_file(&side_dir).unwrap();
|
||||
// let bitcoin_wallet =
|
||||
// bitcoin::clients::esplora::EsploraWallet::create_wallet(name, keys).unwrap();
|
||||
let crdt = BaseCrdt::<TransactionList>::new(&bft_crdt_keys);
|
||||
|
||||
// Channels for internal communication, and a tokio task for stdin input
|
||||
let (incoming_sender, incoming_receiver) = mpsc::channel::<SignedOp>(32);
|
||||
let (stdin_sender, stdin_receiver) = std::sync::mpsc::channel();
|
||||
task::spawn(async move {
|
||||
stdin::input(stdin_sender);
|
||||
bft_crdt::stdin::input(stdin_sender);
|
||||
});
|
||||
|
||||
// Finally, create the node and return it
|
||||
@@ -57,7 +66,7 @@ async fn setup(name: &String) -> SideNode {
|
||||
let node = SideNode::new(
|
||||
crdt,
|
||||
bft_crdt_keys,
|
||||
bitcoin_wallet,
|
||||
// bitcoin_wallet,
|
||||
incoming_receiver,
|
||||
stdin_receiver,
|
||||
handle,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use bdk::database::MemoryDatabase;
|
||||
use bft_json_crdt::json_crdt::{BaseCrdt, SignedOp};
|
||||
use fastcrypto::ed25519::Ed25519KeyPair;
|
||||
use tokio::sync::mpsc;
|
||||
@@ -8,7 +7,7 @@ use crate::{bft_crdt::websocket::Client, bft_crdt::TransactionList, utils};
|
||||
pub struct SideNode {
|
||||
crdt: BaseCrdt<TransactionList>,
|
||||
bft_crdt_keys: fastcrypto::ed25519::Ed25519KeyPair,
|
||||
bitcoin_wallet: bdk::Wallet<MemoryDatabase>,
|
||||
// _bitcoin_wallet: bdk::Wallet<MemoryDatabase>, // currently not read anywhere
|
||||
incoming_receiver: mpsc::Receiver<SignedOp>,
|
||||
stdin_receiver: std::sync::mpsc::Receiver<String>,
|
||||
handle: ezsockets::Client<Client>,
|
||||
@@ -18,7 +17,7 @@ impl SideNode {
|
||||
pub fn new(
|
||||
crdt: BaseCrdt<TransactionList>,
|
||||
bft_crdt_keys: Ed25519KeyPair,
|
||||
bitcoin_wallet: bdk::Wallet<MemoryDatabase>,
|
||||
// bitcoin_wallet: bdk::Wallet<MemoryDatabase>,
|
||||
incoming_receiver: mpsc::Receiver<SignedOp>,
|
||||
stdin_receiver: std::sync::mpsc::Receiver<String>,
|
||||
handle: ezsockets::Client<Client>,
|
||||
@@ -26,7 +25,7 @@ impl SideNode {
|
||||
let node = Self {
|
||||
crdt,
|
||||
bft_crdt_keys,
|
||||
bitcoin_wallet,
|
||||
// _bitcoin_wallet: bitcoin_wallet,
|
||||
incoming_receiver,
|
||||
stdin_receiver,
|
||||
handle,
|
||||
|
||||
@@ -42,7 +42,7 @@ pub fn shappy(op: SignedOp) -> String {
|
||||
sha256::digest(b).to_string()
|
||||
}
|
||||
|
||||
pub fn shassy(text: String) -> String {
|
||||
pub fn sha256(text: String) -> String {
|
||||
let b = text.into_bytes();
|
||||
sha256::digest(b).to_string()
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@ use bft_json_crdt::{
|
||||
json_crdt::{BaseCrdt, SignedOp},
|
||||
keypair::make_keypair,
|
||||
};
|
||||
use side_node::{
|
||||
bft_crdt::websocket::Client, bft_crdt::TransactionList, bitcoin, node::SideNode, utils,
|
||||
};
|
||||
use side_node::{bft_crdt::websocket::Client, bft_crdt::TransactionList, node::SideNode, utils};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -37,9 +35,9 @@ async fn test_distribute_via_websockets() {
|
||||
async fn setup(_: &str) -> SideNode {
|
||||
// First, load up the keys and create a bft-bft-crdt
|
||||
let bft_crdt_keys = make_keypair();
|
||||
let mnemonic_words = bitcoin::keys::make_mnemonic();
|
||||
let keys = bitcoin::keys::get(mnemonic_words).unwrap();
|
||||
let bitcoin_wallet = bitcoin::clients::electrum::create_wallet(keys).unwrap();
|
||||
// let mnemonic_words = bitcoin::keys::make_mnemonic();
|
||||
// let keys = bitcoin::keys::get(mnemonic_words).unwrap();
|
||||
// let bitcoin_wallet = bitcoin::clients::electrum::create_wallet(keys).unwrap();
|
||||
let crdt = BaseCrdt::<TransactionList>::new(&bft_crdt_keys);
|
||||
|
||||
// Channels for internal communication, and a tokio task for stdin input
|
||||
@@ -51,7 +49,7 @@ async fn setup(_: &str) -> SideNode {
|
||||
let node = SideNode::new(
|
||||
crdt,
|
||||
bft_crdt_keys,
|
||||
bitcoin_wallet,
|
||||
// bitcoin_wallet,
|
||||
incoming_receiver,
|
||||
stdin_receiver,
|
||||
handle,
|
||||
|
||||
Reference in New Issue
Block a user