Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Funding tx management (CKB Only) #16

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ bitcoin = { version = "0.30.2", features = ["serde"] }
bitcoin-bech32 = "0.12"
bech32 = "0.8"
libc = "0.2"

chrono = { version = "0.4", default-features = false, features = ["clock"] }
rand = "0.8.5"
serde_json = { version = "1.0" }
Expand All @@ -51,14 +50,15 @@ once_cell = "1.19.0"
tokio-util = { version = "0.7.10", features = ["rt"] }
molecule = { version = "0.7.5", default-features = false }
ckb-types = "0.114.0"
ckb-jsonrpc-types = "0.114.0"
ckb-crypto = "0.114.0"
serde_with = { version = "3.7.0", features = ["macros", "base64"] }
hex = "0.4.3"
tower = "0.4.13"
axum = "0.7.5"
bitflags = "2.5.0"
ckb-hash = "0.115.0"
secp256k1 = { version = "0.28.0", features = ["serde"] }
secp256k1 = { version = "0.28.0", features = ["serde", "recovery"] }
musig2 = { version = "0.0.11", features = ["secp256k1", "serde"] }
ractor = "0.9.7"

Expand All @@ -70,4 +70,3 @@ panic = "abort"

[dev-dependencies]
tempfile = "3.10.1"

201 changes: 201 additions & 0 deletions examples/funding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/// # How to Use This Example
///
/// Start CKB dev net first. Set the test account as the miner.
///
///
/// ```text
/// ckb init -c dev --force --ba-arg 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7
/// ```
///
/// Transfer some CKB to the address
///
/// ```text
/// ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqgx5lf4pczpamsfam48evs0c8nvwqqa59qapt46f`
/// ```
///
/// Run the example:
///
/// ```text
/// cargo run --example funding /tmp/ckb-local /tmp/ckb-remote
/// ```
use ckb_pcn_node::{
ckb::types::Hash256,
ckb_chain::{
CkbChainActor, CkbChainConfig, CkbChainMessage, FundingRequest, FundingTx, TraceTxRequest,
},
};
use ckb_types::packed;
use ractor::{call_t, cast, Actor, ActorRef};
use std::{env, path::PathBuf};

#[tokio::main]
pub async fn main() {
env_logger::init();

let (local_config, remote_config) = prepare();

let (local_actor, local_handle) = Actor::spawn(
Some("local actor".to_string()),
CkbChainActor {},
local_config,
)
.await
.expect("start local actor");

let (remote_actor, remote_handle) = Actor::spawn(
Some("remote actor".to_string()),
CkbChainActor {},
remote_config,
)
.await
.expect("start remote actor");

run(&local_actor, &remote_actor).await;

local_actor.stop(None);
local_handle.await.expect("Actor failed to exit cleanly");
remote_actor.stop(None);
remote_handle.await.expect("Actor failed to exit cleanly");
}

fn prepare() -> (CkbChainConfig, CkbChainConfig) {
let args: Vec<String> = env::args().collect();
let local_path = PathBuf::from(&args[1]);
let remote_path = PathBuf::from(&args[2]);
let _ = std::fs::create_dir_all(&local_path);
let _ = std::fs::create_dir_all(&remote_path);

// Dev net test account that has 20 billions of CKB tokens in the genesis block.
std::fs::write(
local_path.join("key"),
"d00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc",
)
.expect("Unable to write to file");
// privkey for ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqgx5lf4pczpamsfam48evs0c8nvwqqa59qapt46f
std::fs::write(
remote_path.join("key"),
"cccd5f7e693b60447623fb71a5983f15a426938c33699b1a81d1239cfa656cd1",
)
.expect("Unable to write to file");

let rpc_url = "http://127.0.0.1:8114".to_string();
return (
CkbChainConfig {
base_dir: Some(local_path),
rpc_url: rpc_url.clone(),
funding_source_lock_script_code_hash: make_h256(
"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8",
),
funding_source_lock_script_hash_type: ckb_jsonrpc_types::ScriptHashType::Type,
funding_cell_lock_script_code_hash: make_h256(
"0x8090ce20be9976e2407511502acebf74ac1cfed10d7b35b7f33f56c9bd0daec6",
),
funding_cell_lock_script_hash_type: ckb_jsonrpc_types::ScriptHashType::Type,
},
CkbChainConfig {
base_dir: Some(remote_path),
rpc_url: rpc_url.clone(),
funding_source_lock_script_code_hash: make_h256(
"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8",
),
funding_source_lock_script_hash_type: ckb_jsonrpc_types::ScriptHashType::Type,
funding_cell_lock_script_code_hash: make_h256(
"0x8090ce20be9976e2407511502acebf74ac1cfed10d7b35b7f33f56c9bd0daec6",
),
funding_cell_lock_script_hash_type: ckb_jsonrpc_types::ScriptHashType::Type,
},
);
}

const TIMEOUT_MS: u64 = 60000;

async fn run(local: &ActorRef<CkbChainMessage>, remote: &ActorRef<CkbChainMessage>) {
let mut tx = FundingTx::new();
let funding_cell_lock_script_args = packed::Bytes::default();

// The only party to fund goes first. Or the one who does not pay fee goes first for dual funding.
tx = call_t!(
local,
CkbChainMessage::Fund,
TIMEOUT_MS,
tx,
FundingRequest {
udt_info: None,
funding_cell_lock_script_args: funding_cell_lock_script_args.clone(),
local_amount: 12000000000,
local_fee_rate: 0,
remote_amount: 10000000000,
}
)
.expect("local calls")
.expect("local funds");
log_tx("local funded", &tx);

// The one who pays fee goes next.
tx = call_t!(
remote,
CkbChainMessage::Fund,
TIMEOUT_MS,
tx,
FundingRequest {
udt_info: None,
funding_cell_lock_script_args: funding_cell_lock_script_args.clone(),
local_amount: 10000000000,
local_fee_rate: 1000,
remote_amount: 12000000000,
}
)
.expect("remote calls")
.expect("remote funds");
log_tx("remote funded", &tx);

// Now tx is ready and the funding cell out point is available. Exchange and confirm commitment tx.

// Exchange funding tx signatures once commitment txs are confirmed.
tx = call_t!(local, CkbChainMessage::Sign, TIMEOUT_MS, tx)
.expect("local calls")
.expect("local signs");
log_tx("local signed", &tx);
tx = call_t!(remote, CkbChainMessage::Sign, TIMEOUT_MS, tx)
.expect("remote calls")
.expect("remote signs");
log_tx("remote signed", &tx);

let tx = tx.take().expect("take tx");
// Broadcast funding tx and waiting for confs
cast!(local, CkbChainMessage::SendTx(tx.clone())).expect("local sends");
log::info!("tx sent: {}", tx.hash());

let status = call_t!(
local,
CkbChainMessage::TraceTx,
TIMEOUT_MS,
TraceTxRequest {
tx_hash: tx.hash(),
confirmations: 3
}
)
.expect("remote calls");

log::info!("tx status of {}: {:?}", tx.hash(), status);
}

fn make_h256(hex: &str) -> Hash256 {
let mut buf = [0u8; 32];
hex::decode_to_slice(&hex[2..], &mut buf).expect("decode hash256");
Hash256::from(buf)
}

fn log_tx(name: &str, tx: &FundingTx) {
match tx.as_ref() {
Some(tx) => {
let tx: ckb_jsonrpc_types::TransactionView = tx.clone().into();
log::info!(
"tx[{}]: {}",
name,
serde_json::to_string_pretty(&tx).expect("serde json")
)
}
None => log::info!("tx[{}]: None", name),
}
}
Loading
Loading