From 5c2ea4624d10d16ce272986ee20a8906d491509b Mon Sep 17 00:00:00 2001 From: Ash Manning Date: Sat, 20 Jul 2024 01:38:05 +0800 Subject: [PATCH] add posix script output --- Cargo.lock | 96 +++++++-- Cargo.toml | 7 +- src/gen_tx.rs | 540 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 617 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7deb6d..70c2359 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,7 +101,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -191,7 +191,7 @@ dependencies = [ [[package]] name = "bip300301" version = "0.1.1" -source = "git+https://github.com/Ash-L2L/bip300301.git?rev=8ce8316c6bf58b54b95c36b439402f2bb5f2a8dd#8ce8316c6bf58b54b95c36b439402f2bb5f2a8dd" +source = "git+https://github.com/Ash-L2L/bip300301.git?rev=056e5700bb956cd5c20f27646499ca34f9c74531#056e5700bb956cd5c20f27646499ca34f9c74531" dependencies = [ "base64 0.21.7", "bitcoin 0.31.2", @@ -220,9 +220,14 @@ dependencies = [ "cc", "clap", "futures", + "integer-sqrt", "jsonrpsee", "libc", + "rand", + "serde", "serde_json", + "serde_path_to_error", + "serde_tuple", "thiserror", "tokio", "tracing", @@ -457,7 +462,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -594,7 +599,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.68", ] [[package]] @@ -605,7 +610,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -738,7 +743,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -1097,6 +1102,15 @@ dependencies = [ "serde", ] +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + [[package]] name = "iovec" version = "0.1.4" @@ -1259,7 +1273,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -1412,7 +1426,7 @@ checksum = "a7ce64b975ed4f123575d11afd9491f2e37bbd5813fbfbc0f09ae1fbddea74e0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -1530,7 +1544,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -1846,22 +1860,22 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -1875,6 +1889,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.6" @@ -1884,6 +1908,27 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_tuple" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f025b91216f15a2a32aa39669329a475733590a015835d1783549a56d09427" +dependencies = [ + "serde", + "serde_tuple_macros", +] + +[[package]] +name = "serde_tuple_macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4076151d1a2b688e25aaf236997933c66e18b870d0369f8b248b8ab2be630d7e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "serde_with" version = "3.8.2" @@ -1911,7 +1956,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -1992,6 +2037,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.68" @@ -2039,7 +2095,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -2123,7 +2179,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -2255,7 +2311,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -2413,7 +2469,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.68", "wasm-bindgen-shared", ] @@ -2447,7 +2503,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index d287f6f..d63e6d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,9 +13,14 @@ bitcoin_0_31 = { package = "bitcoin", version = "0.31.1" } bitcoincore-zmq = { version = "1.5.0", features = ["async"] } clap = { version = "4.5.7", features = ["derive"] } futures = "0.3.30" +integer-sqrt = "0.1.5" jsonrpsee = "0.23.2" libc = "0.2.155" +rand = "0.8.5" +serde = "1.0.204" serde_json = "1.0.120" +serde_path_to_error = "0.1.16" +serde_tuple = "0.5.0" thiserror = "1.0.61" tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"] } tracing = "0.1.40" @@ -23,7 +28,7 @@ tracing-subscriber = "0.3.18" [dependencies.bip300301] git = "https://github.com/Ash-L2L/bip300301.git" -rev = "8ce8316c6bf58b54b95c36b439402f2bb5f2a8dd" +rev = "056e5700bb956cd5c20f27646499ca34f9c74531" features = ["tracing"] [dev-dependencies] diff --git a/src/gen_tx.rs b/src/gen_tx.rs index 80c30cc..085d9ab 100644 --- a/src/gen_tx.rs +++ b/src/gen_tx.rs @@ -1,19 +1,37 @@ -use std::str::FromStr; +use std::{ + cmp::Ordering, + collections::VecDeque, + net::{Ipv4Addr, SocketAddr, SocketAddrV4}, + str::FromStr, + time::{Duration, SystemTime}, +}; +use bip300301::{client::BlockTemplate, MainClient as _}; use bitcoin::{ absolute::LockTime, address::NetworkUnchecked, amount::ParseAmountError, + block::Header, + constants::{COINBASE_MATURITY, SUBSIDY_HALVING_INTERVAL}, + hashes::{sha256d, Hash}, hex::{DisplayHex, FromHex}, key::Secp256k1, - opcodes::all::{OP_CAT, OP_EQUAL}, + opcodes::{ + all::{OP_CAT, OP_EQUAL}, + OP_TRUE, + }, secp256k1::rand::rngs::OsRng, taproot::{ControlBlock, LeafVersion, TaprootBuilder, TaprootError}, transaction::Version, - Address, Amount, Denomination, OutPoint, PublicKey, ScriptBuf, Transaction, - TxIn, TxOut, Txid, Witness, + Address, Amount, Block, BlockHash, CompactTarget, Denomination, OutPoint, + PublicKey, ScriptBuf, Sequence, Target, Transaction, TxIn, TxMerkleNode, + TxOut, Txid, Witness, }; use clap::{Args, Parser, Subcommand, ValueEnum}; +use integer_sqrt::IntegerSquareRoot; +use rand::{prelude::SliceRandom, Rng}; +use serde::{Deserialize, Serialize}; +use serde_tuple::Deserialize_tuple; use thiserror::Error; #[derive(Clone, Debug)] @@ -102,8 +120,72 @@ struct SpendArgs { witness_data: Vec, } +const DEFAULT_SOCKET_ADDR: SocketAddr = + SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 8332)); + +#[derive(Clone, Debug, Parser)] +struct RpcAuth { + /// Bitcoin node RPC pass + #[arg(long, default_value = "")] + rpc_pass: String, + /// Bitcoin node RPC user + #[arg(long, default_value = "")] + rpc_user: String, +} + +/// Specification for how many invalid txs will be in a block, and the reason +/// that they are invalid +#[derive(Clone, Copy, Debug, Deserialize_tuple)] +struct BlockSpec { + /// Number of txs with too few stack items for op_cat + too_few_stack_items: usize, + /// Number of txs that concatenate two stack elements where the + /// concatenation exceeds the stack element size limit + concatenation_exceeds_stack_element_size_limit: usize, + /// Number of txs that concatenate two stack elements, where the + /// concatenation is not equal to the third stack element + concatenation_not_equal: usize, +} + +impl BlockSpec { + fn txs_required(&self) -> usize { + let BlockSpec { + too_few_stack_items, + concatenation_exceeds_stack_element_size_limit, + concatenation_not_equal, + } = self; + too_few_stack_items + + concatenation_exceeds_stack_element_size_limit + + concatenation_not_equal + } +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(transparent)] +struct BlocksSpec(Vec); + +impl FromStr for BlocksSpec { + type Err = serde_path_to_error::Error; + + fn from_str(s: &str) -> Result { + let mut deserializer = serde_json::Deserializer::from_str(s); + let res = serde_path_to_error::deserialize(&mut deserializer)?; + Ok(Self(res)) + } +} + #[derive(Subcommand)] enum Command { + /// Generate a script + GenScript { + /// Socket address for the node RPC server + #[arg(long, default_value_t = DEFAULT_SOCKET_ADDR)] + rpc_addr: SocketAddr, + #[command(flatten)] + rpc_auth: RpcAuth, + /// Blocks spec as a JSON string + blocks_spec: BlocksSpec, + }, /// Generate a p2tr address and control block to use when spending P2trAddress, /// Spend a previously generated p2tr output @@ -196,11 +278,459 @@ fn spend( Ok(()) } -fn main() -> anyhow::Result<()> { +#[derive(Debug)] +struct OutputPosixScriptBuilder { + rpc_addr: SocketAddr, + rpc_auth: RpcAuth, + script: Vec, +} + +impl OutputPosixScriptBuilder { + fn new(rpc_addr: SocketAddr, rpc_auth: RpcAuth) -> Self { + Self { + rpc_addr, + rpc_auth, + script: Vec::new(), + } + } + + /// Use curl to send an RPC request to the node + fn curl_rpc( + &mut self, + comment: Option<&str>, + method: &str, + params: Params, + ) where + Params: Serialize, + { + let request = serde_json::json!({ + "jsonrpc": "2.0", + "id": "bip347-enforcer-test", + "method": method, + "params": params + }); + let mut cmd = [ + "curl", + &format!("'{}'", &self.rpc_addr), + "-H", + "'Content-Type: application/json'", + "--user", + &format!("'{}:{}'", self.rpc_auth.rpc_user, self.rpc_auth.rpc_pass), + "--data-binary", + &format!("'{}'", serde_json::to_string(&request).unwrap()), + ] + .join(" "); + if let Some(comment) = comment { + cmd = comment + .lines() + .map(|line| format!("# {line}")) + .chain(std::iter::once(cmd)) + .collect::>() + .join("\n"); + } + self.script.push(cmd) + } + + fn finalize(self) -> String { + let mut res = self.script.join("\n\n"); + if !res.is_empty() && !res.ends_with('\n') { + res.push('\n'); + } + res + } +} + +/// Script with no spend requirements +fn unlocked_script() -> ScriptBuf { + ScriptBuf::builder().push_opcode(OP_TRUE).into_script() +} + +/// Example OP_CAT script that checks that the concatenation of two stack +/// elements is equal to a third stack element +fn op_cat_script() -> ScriptBuf { + ScriptBuf::builder() + .push_opcode(OP_CAT) + .push_opcode(OP_EQUAL) + .into_script() +} + +/// P2TR scriptpubkey and control block +fn op_cat_p2tr() -> (ScriptBuf, ControlBlock) { + let secp = Secp256k1::new(); + let (_sk, pk) = secp.generate_keypair(&mut OsRng); + let pk: PublicKey = pk.into(); + let tr_spend_info = TaprootBuilder::new() + .add_leaf(0, script()) + .unwrap() + .finalize(&secp, pk.into()) + .unwrap(); + let spk = ScriptBuf::new_p2tr_tweaked(tr_spend_info.output_key()); + let control_block = tr_spend_info + .control_block(&(script(), LeafVersion::TapScript)) + .unwrap(); + (spk, control_block) +} + +const MAX_WITNESS_ELEMENT_SIZE: usize = 520; + +/// Spend the OP_CAT outpoint, with the specified witness elements before the +/// script and control block +fn spend_op_cat_outpoint( + spend_outpoint: OutPoint, + control_block: ControlBlock, + mut witness: Witness, +) -> Transaction { + witness.push(op_cat_script()); + witness.push(control_block.serialize()); + let txin = TxIn { + previous_output: spend_outpoint, + witness, + // FIXME: check this + ..Default::default() + }; + let txout = TxOut { + value: Amount::ONE_SAT, + script_pubkey: unlocked_script(), + }; + Transaction { + version: Version::TWO, + lock_time: LockTime::ZERO, + input: vec![txin], + output: vec![txout], + } +} + +/// Tx spending an `op_cat_script` output with too few stack items in the +/// witness +fn too_few_stack_items_tx( + spend_outpoint: OutPoint, + control_block: ControlBlock, +) -> Transaction { + let mut witness = Witness::new(); + if OsRng.gen() { + let stack_item_len = OsRng.gen_range(0..=MAX_WITNESS_ELEMENT_SIZE); + let witness_element: Vec = + (0..stack_item_len).map(|_| OsRng.gen()).collect(); + witness.push(witness_element); + } + spend_op_cat_outpoint(spend_outpoint, control_block, witness) +} + +/// Tx spending an `op_cat_script` output, where the witness includes two +/// elements such that their concatenation exceeds the stack element size limit +fn concatenation_exceeds_stack_element_size_limit_tx( + spend_outpoint: OutPoint, + control_block: ControlBlock, +) -> Transaction { + let mut witness = Witness::new(); + // uniformly sample witness element sizes x, y such that + // 0 < x <= 520, 0 < y <= 520, 520 < x + y + let (len0, len1) = loop { + let len0 = OsRng.gen_range(1..=MAX_WITNESS_ELEMENT_SIZE); + let len1 = OsRng.gen_range(1..=MAX_WITNESS_ELEMENT_SIZE); + match (len0 + len1).cmp(&MAX_WITNESS_ELEMENT_SIZE) { + Ordering::Greater => break (len0, len1), + Ordering::Equal => continue, + Ordering::Less => + // reflection in y = 520 - x + { + break ( + MAX_WITNESS_ELEMENT_SIZE - len1, + MAX_WITNESS_ELEMENT_SIZE - len0, + ) + } + } + }; + let witness0: Vec = (0..len0).map(|_| OsRng.gen()).collect(); + witness.push(witness0); + let witness1: Vec = (0..len1).map(|_| OsRng.gen()).collect(); + witness.push(witness1); + spend_op_cat_outpoint(spend_outpoint, control_block, witness) +} + +/// Tx spending an `op_cat_script` output, where the witness includes two +/// elements such that their concatenation is not equal to the third +fn concatenation_not_equal_tx( + spend_outpoint: OutPoint, + control_block: ControlBlock, +) -> Transaction { + let mut witness = Witness::new(); + // uniformly sample witness element sizes x, y such that + // 0 <= x <= 520, 0 <= y <= 520, x + y <= 520 + let (len0, len1) = { + // This is equivalent to uniformly sampling up to T(520+1), where + // T is the triangle function that computes the kth triangular number, + // where T(0) = 0. + // Let r be the triange root of the sample. Then: + // * x will be 520 - r + // * y will be T(r) less than the sample. + let triangle = |k: usize| (k * (k + 1)) / 2; + let triangle_root = |x: usize| (((8 * x) + 1).integer_sqrt() - 1) / 2; + let sample = OsRng.gen_range(0..triangle(MAX_WITNESS_ELEMENT_SIZE + 1)); + let r = triangle_root(sample); + (MAX_WITNESS_ELEMENT_SIZE - r, sample - triangle(r)) + }; + let witness0: Vec = (0..len0).map(|_| OsRng.gen()).collect(); + let witness1: Vec = (0..len1).map(|_| OsRng.gen()).collect(); + let concatenated: Vec = [witness0.clone(), witness1.clone()].concat(); + let claimed_concatenation: Vec = loop { + let len = OsRng.gen_range(0..=MAX_WITNESS_ELEMENT_SIZE); + let bytes = (0..len).map(|_| OsRng.gen()).collect(); + if bytes != concatenated { + break bytes; + } else { + continue; + } + }; + witness.push(claimed_concatenation); + witness.push(witness0); + witness.push(witness1); + spend_op_cat_outpoint(spend_outpoint, control_block, witness) +} + +fn gen_block( + prev_blockhash: BlockHash, + target: CompactTarget, + txs: Vec, +) -> Block { + let header = Header { + version: bitcoin::block::Version::NO_SOFT_FORK_SIGNALLING, + prev_blockhash, + merkle_root: TxMerkleNode::all_zeros(), + time: SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() as u32, + bits: target, + nonce: 0, + }; + let mut block = Block { + header, + txdata: txs, + }; + block.header.merkle_root = block.compute_merkle_root().unwrap(); + let target = Target::from_compact(target); + let mut nonce = block.header.nonce; + let mut header_bytes = bitcoin::consensus::serialize(&block.header); + loop { + let header_hash = sha256d::Hash::hash(&header_bytes).to_byte_array(); + if Target::from_le_bytes(header_hash) < target { + break; + } + nonce += 1; + let nonce_bytes = nonce.to_be_bytes(); + header_bytes[76] = nonce_bytes[0]; + header_bytes[77] = nonce_bytes[1]; + header_bytes[78] = nonce_bytes[2]; + header_bytes[79] = nonce_bytes[3]; + } + block.header = bitcoin::consensus::deserialize(&header_bytes).unwrap(); + assert!(block.header.validate_pow(target).is_ok()); + block +} + +/// Generate N + 1 txs from a block spec. +/// +/// The first transaction spends the outpoint to generate N+1 outputs, +/// where N is the number of txs described by the block spec. +/// The first N outputs are 1 sat, and the final output is the remainder. +/// +/// The subsequent N txs are described by the block spec, but with randomized +/// order. +fn gen_txs( + spend_outpoint: OutPoint, + spend_outpoint_value: Amount, + block_spec: BlockSpec, +) -> Vec { + let first_txin = TxIn { + previous_output: spend_outpoint, + // FIXME: check that this is correct + witness: { + let mut wit = Witness::new(); + wit.push(unlocked_script()); + wit + }, + ..Default::default() + }; + let (mut first_txouts, mut control_blocks): (Vec<_>, VecDeque<_>) = (0 + ..block_spec.txs_required()) + .map(|_| { + let (script_pubkey, control_block) = op_cat_p2tr(); + let txout = TxOut { + value: Amount::ONE_SAT, + script_pubkey, + }; + (txout, control_block) + }) + .unzip(); + first_txouts.push(TxOut { + value: spend_outpoint_value + - (Amount::ONE_SAT * block_spec.txs_required() as u64), + script_pubkey: ScriptBuf::new_p2wsh(&unlocked_script().wscript_hash()), + }); + let first_tx = Transaction { + version: Version::TWO, + lock_time: LockTime::ZERO, + input: vec![first_txin], + output: first_txouts, + }; + let first_txid = first_tx.compute_txid(); + let mut res = Vec::new(); + let mut vout = 0; + for _ in 0..block_spec.too_few_stack_items { + let spend_outpoint = OutPoint { + txid: first_txid, + vout, + }; + vout += 1; + let control_block = control_blocks.pop_front().unwrap(); + res.push(too_few_stack_items_tx(spend_outpoint, control_block)); + } + for _ in 0..block_spec.concatenation_exceeds_stack_element_size_limit { + let spend_outpoint = OutPoint { + txid: first_txid, + vout, + }; + vout += 1; + let control_block = control_blocks.pop_front().unwrap(); + res.push(concatenation_exceeds_stack_element_size_limit_tx( + spend_outpoint, + control_block, + )); + } + for _ in 0..block_spec.concatenation_not_equal { + let spend_outpoint = OutPoint { + txid: first_txid, + vout, + }; + vout += 1; + let control_block = control_blocks.pop_front().unwrap(); + res.push(concatenation_not_equal_tx(spend_outpoint, control_block)); + } + // Randomize order + res.shuffle(&mut OsRng); + // Add first tx and reverse so that it is first in the result + res.push(first_tx); + res.reverse(); + res +} + +fn block_subsidy(network: bitcoin::Network, height: u32) -> Amount { + #[allow(clippy::wildcard_in_or_patterns)] + let halving_interval = match network { + bitcoin::Network::Regtest => 150, + bitcoin::Network::Bitcoin | bitcoin::Network::Testnet | _ => { + SUBSIDY_HALVING_INTERVAL + } + }; + let epoch = height / halving_interval; + Amount::from_int_btc(50) / (1 << epoch) +} + +async fn gen_script( + network: bitcoin::Network, + rpc_addr: SocketAddr, + rpc_auth: RpcAuth, + blocks_spec: BlocksSpec, +) -> anyhow::Result<()> { + let mut posix_script_builder = + OutputPosixScriptBuilder::new(rpc_addr, rpc_auth.clone()); + const REQUEST_TIMEOUT: Duration = Duration::from_secs(120); + let client = bip300301::client( + rpc_addr, + &rpc_auth.rpc_pass, + Some(REQUEST_TIMEOUT), + &rpc_auth.rpc_user, + )?; + let BlockTemplate { + height, + prev_blockhash, + target, + .. + } = client.get_block_template(Default::default()).await?; + let prev_blockhash = BlockHash::from_byte_array(*prev_blockhash.as_ref()); + let addr = Address::p2wsh(&unlocked_script(), network); + let coinbase_txin = TxIn { + previous_output: OutPoint::null(), + script_sig: ScriptBuf::builder().push_int(height as i64).into_script(), + // FIXME: Verify that this is correct + sequence: Sequence::MAX, + witness: Witness::new(), + }; + let coinbase_value = block_subsidy(network, height); + let coinbase_txout = TxOut { + value: coinbase_value, + script_pubkey: addr.script_pubkey(), + }; + let coinbase_tx = Transaction { + version: Version::TWO, + lock_time: LockTime::from_height(height + COINBASE_MATURITY)?, + input: vec![coinbase_txin], + output: vec![coinbase_txout], + }; + let coinbase_txid = coinbase_tx.compute_txid(); + let block = gen_block( + prev_blockhash, + CompactTarget::from_consensus(target.to_consensus()), + vec![coinbase_tx], + ); + // Generate some blocks so that an output is available to spend + posix_script_builder.curl_rpc( + Some("Mine a block, so that the coinbase output can be used in later txs"), + "submitblock", + [ bitcoin::consensus::serialize(&block).to_lower_hex_string() ], + ); + posix_script_builder.curl_rpc( + Some("Generate some blocks, so that the coinbase output is available to spend"), + "generatetoaddress", + [ + serde_json::Value::Number(COINBASE_MATURITY.into()), + serde_json::Value::String(addr.to_string()), + ], + ); + let mut spend_outpoint = OutPoint { + txid: coinbase_txid, + vout: 0, + }; + let mut spend_outpoint_value = coinbase_value; + for block_spec in blocks_spec.0.into_iter() { + let txs = gen_txs(spend_outpoint, spend_outpoint_value, block_spec); + spend_outpoint = OutPoint { + txid: txs[0].compute_txid(), + vout: txs[0].output.len() as u32 - 1, + }; + spend_outpoint_value = txs[0].output.last().unwrap().value; + let raw_txs: Vec<_> = txs + .iter() + .map(|tx| bitcoin::consensus::serialize(tx).to_lower_hex_string()) + .collect(); + posix_script_builder.curl_rpc( + Some(&format!( + "Generate a block with {} failing txs", + block_spec.txs_required() + )), + "generateblock", + serde_json::json!([addr.to_string(), raw_txs]), + ); + } + println!("{}", posix_script_builder.finalize()); + Ok(()) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { let cli = Cli::parse(); match cli.command { Command::P2trAddress => p2tr_address(cli.network.into()), Command::Spend(spend_args) => spend(cli.network.into(), *spend_args)?, + Command::GenScript { + rpc_addr, + rpc_auth, + blocks_spec, + } => { + gen_script(cli.network.into(), rpc_addr, rpc_auth, blocks_spec) + .await? + } } Ok(()) }