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

send command #618

Merged
merged 13 commits into from
Oct 7, 2022
41 changes: 11 additions & 30 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ anyhow = { version = "1.0.56", features = ["backtrace"] }
axum = "0.5.6"
axum-server = "0.4.0"
bitcoin = "0.29.1"
bitcoincore-rpc = "0.16.0"
bitcoincore-rpc = { version = "0.16.0", git = "https://github.com/casey/rust-bitcoincore-rpc", branch = "ord" }
raphjaph marked this conversation as resolved.
Show resolved Hide resolved
boilerplate = { version = "0.2.1", features = ["axum"] }
chrono = "0.4.19"
clap = { version = "3.1.0", features = ["derive"] }
Expand Down
5 changes: 5 additions & 0 deletions src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ pub(crate) struct Options {
bitcoin_data_dir: Option<PathBuf>,
#[clap(long, help = "Limit index to <HEIGHT_LIMIT> blocks.")]
pub(crate) height_limit: Option<u64>,
#[clap(
long,
help = "Add this flag to use the send functionality of the wallet."
)]
pub(crate) reckless: bool,
raphjaph marked this conversation as resolved.
Show resolved Hide resolved
}

#[derive(ValueEnum, Copy, Clone, Debug)]
Expand Down
3 changes: 3 additions & 0 deletions src/subcommand/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use {super::*, bitcoincore_rpc::RpcApi};

mod identify;
mod list;
mod send;

fn list_unspent(options: Options) -> Result<Vec<(OutPoint, Vec<(u64, u64)>)>> {
let index = Index::open(&options)?;
Expand All @@ -27,13 +28,15 @@ fn list_unspent(options: Options) -> Result<Vec<(OutPoint, Vec<(u64, u64)>)>> {
pub(crate) enum Wallet {
Identify,
List,
Send(send::Send),
}

impl Wallet {
pub(crate) fn run(self, options: Options) -> Result<()> {
match self {
Self::Identify => identify::run(options),
Self::List => list::run(options),
Self::Send(send) => send.run(options),
}
}
}
Expand Down
47 changes: 47 additions & 0 deletions src/subcommand/wallet/send.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use {
super::*, bitcoin::util::amount::Amount, bitcoincore_rpc::json::CreateRawTransactionInput,
std::collections::HashMap,
};

#[derive(Debug, Parser)]
pub(crate) struct Send {
ordinal: Ordinal,
address: String,
raphjaph marked this conversation as resolved.
Show resolved Hide resolved
}

impl Send {
pub(crate) fn run(self, options: Options) -> Result {
if options.chain.network() == Network::Bitcoin {
bail!("Send command is not allowed on mainnet yet. Try on regtest/signet/testnet.")
raphjaph marked this conversation as resolved.
Show resolved Hide resolved
}

let index = Index::open(&options)?;
index.index()?;

let satpoint = index.find(self.ordinal.0)?.unwrap();
raphjaph marked this conversation as resolved.
Show resolved Hide resolved
let output = satpoint.outpoint;

let client = options.bitcoin_rpc_client()?;

let amount = client
.get_transaction(&output.txid, Some(true))?
.amount
.to_sat() as u64;
raphjaph marked this conversation as resolved.
Show resolved Hide resolved

let inputs = vec![CreateRawTransactionInput {
txid: output.txid,
vout: output.vout,
sequence: None,
}];

let mut outputs = HashMap::new();
outputs.insert(self.address, Amount::from_sat(amount));

let tx = client.create_raw_transaction_hex(&inputs, &outputs, None, None)?;
raphjaph marked this conversation as resolved.
Show resolved Hide resolved
let signed_tx = client.sign_raw_transaction_with_wallet(tx, None, None)?.hex;
let txid = client.send_raw_transaction(&signed_tx)?;

println!("{txid}");
Ok(())
}
}
3 changes: 1 addition & 2 deletions test-bitcoincore-rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ repository = "https://github.com/casey/ord"

[dependencies]
bitcoin = { version = "0.29.1", features = ["serde"] }
bitcoincore-rpc = "0.16.0"
bitcoincore-rpc-json = "0.16.0"
bitcoincore-rpc = { version = "0.16.0", git = "https://github.com/casey/rust-bitcoincore-rpc", branch = "ord" }
hex = "0.4.3"
jsonrpc-core = "18.0.0"
jsonrpc-core-client = "18.0.0"
Expand Down
137 changes: 133 additions & 4 deletions test-bitcoincore-rpc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
use {
bitcoin::{
blockdata::constants::COIN_VALUE, blockdata::script, consensus::encode::serialize,
hash_types::BlockHash, hashes::Hash, Amount, Block, BlockHeader, Network, OutPoint,
PackedLockTime, Script, Sequence, Transaction, TxIn, TxMerkleNode, TxOut, Txid, Witness, Wtxid,
blockdata::constants::COIN_VALUE,
blockdata::script,
consensus::encode::{deserialize, serialize},
hash_types::BlockHash,
hashes::Hash,
util::amount::SignedAmount,
Amount, Block, BlockHeader, Network, OutPoint, PackedLockTime, Script, Sequence, Transaction,
TxIn, TxMerkleNode, TxOut, Txid, Witness, Wtxid,
},
bitcoincore_rpc::json::{
Bip125Replaceable, CreateRawTransactionInput, GetBlockHeaderResult, GetRawTransactionResult,
GetTransactionResult, ListUnspentResultEntry, SignRawTransactionResult, WalletTxInfo,
},
bitcoincore_rpc_json::{GetBlockHeaderResult, GetRawTransactionResult, ListUnspentResultEntry},
jsonrpc_core::{IoHandler, Value},
jsonrpc_http_server::{CloseHandle, ServerBuilder},
std::collections::BTreeMap,
std::{
collections::HashMap,
sync::{Arc, Mutex, MutexGuard},
thread,
},
Expand Down Expand Up @@ -206,6 +215,33 @@ pub trait Api {
#[rpc(name = "getblock")]
fn getblock(&self, blockhash: BlockHash, verbosity: u64) -> Result<String, jsonrpc_core::Error>;

#[rpc(name = "createrawtransaction")]
fn create_raw_transaction(
&self,
utxos: Vec<CreateRawTransactionInput>,
outs: HashMap<String, f64>,
raphjaph marked this conversation as resolved.
Show resolved Hide resolved
locktime: Option<i64>,
replaceable: Option<bool>,
) -> Result<String, jsonrpc_core::Error>;

#[rpc(name = "signrawtransactionwithwallet")]
fn sign_raw_transaction_with_wallet(
&self,
tx: String,
utxos: Option<()>,
sighash_type: Option<()>,
) -> Result<Value, jsonrpc_core::Error>;

#[rpc(name = "sendrawtransaction")]
fn send_raw_transaction(&self, tx: String) -> Result<String, jsonrpc_core::Error>;

#[rpc(name = "gettransaction")]
fn get_transaction(
&self,
txid: Txid,
include_watchonly: Option<bool>,
) -> Result<Value, jsonrpc_core::Error>;

#[rpc(name = "getrawtransaction")]
fn get_raw_transaction(
&self,
Expand Down Expand Up @@ -285,6 +321,99 @@ impl Api for Server {
}
}

fn create_raw_transaction(
&self,
utxos: Vec<CreateRawTransactionInput>,
outs: HashMap<String, f64>,
locktime: Option<i64>,
replaceable: Option<bool>,
) -> Result<String, jsonrpc_core::Error> {
assert_eq!(locktime, None, "locktime param not supported");
assert_eq!(replaceable, None, "replaceable param not supported");

let tx = Transaction {
version: 0,
lock_time: PackedLockTime(0),
input: utxos
.iter()
.map(|input| TxIn {
previous_output: OutPoint::new(input.txid, input.vout),
script_sig: Script::new(),
sequence: Sequence(0),
witness: Witness::new(),
})
.collect(),
output: outs
.iter()
.map(|(_address, amount)| TxOut {
value: *amount as u64,
raphjaph marked this conversation as resolved.
Show resolved Hide resolved
script_pubkey: Script::new(),
})
.collect(),
};

Ok(hex::encode(serialize(&tx)))
}

fn sign_raw_transaction_with_wallet(
&self,
tx: String,
utxos: Option<()>,
sighash_type: Option<()>,
) -> Result<Value, jsonrpc_core::Error> {
assert_eq!(utxos, None, "utxos param not supported");
assert_eq!(sighash_type, None, "sighash_type param not supported");

Ok(
serde_json::to_value(SignRawTransactionResult {
hex: hex::decode(tx).unwrap(),
complete: true,
errors: None,
})
.unwrap(),
)
}

fn send_raw_transaction(&self, tx: String) -> Result<String, jsonrpc_core::Error> {
let tx: Transaction = deserialize(&hex::decode(tx).unwrap()).unwrap();
self.state.lock().unwrap().mempool.push(tx.clone());

Ok(tx.txid().to_string())
}

fn get_transaction(
&self,
txid: Txid,
_include_watchonly: Option<bool>,
) -> Result<Value, jsonrpc_core::Error> {
match self.state.lock().unwrap().transactions.get(&txid) {
Some(tx) => Ok(
serde_json::to_value(GetTransactionResult {
info: WalletTxInfo {
txid,
confirmations: 0,
time: 0,
timereceived: 0,
blockhash: None,
blockindex: None,
blockheight: None,
blocktime: None,
wallet_conflicts: Vec::new(),
bip125_replaceable: Bip125Replaceable::Unknown,
},
amount: SignedAmount::from_sat(0),
fee: None,
details: Vec::new(),
hex: serialize(tx),
})
.unwrap(),
),
None => Err(jsonrpc_core::Error::new(
jsonrpc_core::types::error::ErrorCode::ServerError(-8),
)),
}
}

fn get_raw_transaction(
&self,
txid: Txid,
Expand Down
6 changes: 4 additions & 2 deletions tests/command_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,10 @@ impl CommandBuilder {
self.tempdir
}

pub(crate) fn run(self) -> TempDir {
pub(crate) fn run(self) -> (TempDir, String) {
raphjaph marked this conversation as resolved.
Show resolved Hide resolved
let output = self.command().output().unwrap();
self.check(output)
let str_output = String::from(str::from_utf8(&output.stdout).unwrap());

(self.check(output), str_output)
}
}
Loading