Skip to content

Commit

Permalink
Add creating and accepting offers (#4156)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph authored Jan 11, 2025
1 parent f49b10c commit 9bc54f8
Show file tree
Hide file tree
Showing 30 changed files with 1,429 additions and 88 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ rust-version = "1.79.0"
base64 = "0.22.0"
bitcoin = { version = "0.32.5", features = ["rand"] }
colored = "3"
derive_more = { version = "1.0.0", features = ["display", "from_str"] }
hex = "0.4.3"
pretty_assertions = "1.2.1"
regex = "1.6.0"
Expand All @@ -56,6 +57,7 @@ ciborium = "0.2.1"
clap = { version = "4.4.2", features = ["derive", "env"] }
colored.workspace = true
ctrlc = { version = "3.2.1", features = ["termination"] }
derive_more.workspace = true
dirs = "5.0.0"
env_logger = "0.11.0"
futures = "0.3.21"
Expand Down
1 change: 1 addition & 0 deletions crates/mockcore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ hex.workspace = true
jsonrpc-core = "18.0.0"
jsonrpc-derive = "18.0.0"
jsonrpc-http-server = "18.0.0"
ord = { path = "../.."}
ord-bitcoincore-rpc = "0.19.0"
reqwest.workspace = true
serde.workspace = true
Expand Down
10 changes: 10 additions & 0 deletions crates/mockcore/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,14 @@ pub trait Api {
psbt: String,
extract: Option<bool>,
) -> Result<FinalizePsbtResult, jsonrpc_core::Error>;

#[rpc(name = "utxoupdatepsbt")]
fn utxo_update_psbt(&self, psbt: String) -> Result<String, jsonrpc_core::Error>;

#[rpc(name = "simulaterawtransaction")]
fn simulate_raw_transaction(
&self,
txs: Vec<String>,
options: Option<SimulateRawTransactionOptions>,
) -> Result<SimulateRawTransactionResult, jsonrpc_core::Error>;
}
28 changes: 25 additions & 3 deletions crates/mockcore/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@

use {
api::Api,
base64::Engine,
bitcoin::{
address::{Address, NetworkUnchecked},
amount::SignedAmount,
bip32::{ChildNumber, DerivationPath, Xpriv},
block::Header,
blockdata::{script, transaction::Version},
consensus::encode::{deserialize, serialize},
consensus::Decodable,
hash_types::{BlockHash, TxMerkleNode},
hashes::Hash,
key::{Keypair, Secp256k1, TapTweak, XOnlyPublicKey},
locktime::absolute::LockTime,
opcodes,
pow::CompactTarget,
psbt::Psbt,
script::Instruction,
secp256k1::{self, rand},
sighash::{self, SighashCache, TapSighashType},
Amount, Block, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid,
Expand All @@ -27,17 +32,18 @@ use {
GetTransactionResult, GetTransactionResultDetail, GetTransactionResultDetailCategory,
GetTxOutResult, GetWalletInfoResult, ImportDescriptors, ImportMultiResult,
ListTransactionResult, ListUnspentResultEntry, ListWalletDirItem, ListWalletDirResult,
LoadWalletResult, SignRawTransactionInput, SignRawTransactionResult, Timestamp,
WalletProcessPsbtResult, WalletTxInfo,
LoadWalletResult, SignRawTransactionInput, SignRawTransactionResult, StringOrStringArray,
Timestamp, WalletProcessPsbtResult, WalletTxInfo,
},
jsonrpc_core::{IoHandler, Value},
jsonrpc_http_server::{CloseHandle, ServerBuilder},
ord::{SimulateRawTransactionOptions, SimulateRawTransactionResult},
serde::{Deserialize, Serialize},
server::Server,
state::State,
std::{
collections::{BTreeMap, BTreeSet, HashMap},
fs,
fs, mem,
path::PathBuf,
sync::{Arc, Mutex, MutexGuard},
thread,
Expand All @@ -54,6 +60,22 @@ mod server;
mod state;
mod wallet;

fn parse_hex_tx(tx: String) -> Transaction {
let mut cursor = bitcoin::io::Cursor::new(hex::decode(tx).unwrap());

let version = Version(i32::consensus_decode_from_finite_reader(&mut cursor).unwrap());
let input = Vec::<TxIn>::consensus_decode_from_finite_reader(&mut cursor).unwrap();
let output = Decodable::consensus_decode_from_finite_reader(&mut cursor).unwrap();
let lock_time = Decodable::consensus_decode_from_finite_reader(&mut cursor).unwrap();

Transaction {
version,
input,
output,
lock_time,
}
}

#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct Descriptor {
pub desc: String,
Expand Down
77 changes: 56 additions & 21 deletions crates/mockcore/src/server.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
use {
super::*,
base64::Engine,
bitcoin::{consensus::Decodable, opcodes, psbt::Psbt, script::Instruction, Witness},
bitcoincore_rpc::json::StringOrStringArray,
};
use super::*;

pub(crate) struct Server {
pub(crate) state: Arc<Mutex<State>>,
Expand Down Expand Up @@ -360,19 +355,7 @@ impl Api for Server {
) -> Result<FundRawTransactionResult, jsonrpc_core::Error> {
let options = options.unwrap();

let mut cursor = bitcoin::io::Cursor::new(hex::decode(tx).unwrap());

let version = Version(i32::consensus_decode_from_finite_reader(&mut cursor).unwrap());
let input = Vec::<TxIn>::consensus_decode_from_finite_reader(&mut cursor).unwrap();
let output = Decodable::consensus_decode_from_finite_reader(&mut cursor).unwrap();
let lock_time = Decodable::consensus_decode_from_finite_reader(&mut cursor).unwrap();

let mut transaction = Transaction {
version,
input,
output,
lock_time,
};
let mut transaction = parse_hex_tx(tx);

assert_eq!(
options.change_position,
Expand Down Expand Up @@ -1014,7 +997,15 @@ impl Api for Server {
if let Some(sign) = sign {
if sign {
for input in psbt.inputs.iter_mut() {
input.final_script_witness = Some(Witness::from_slice(&[&[0; 64]]));
let address = Address::from_script(
&input.witness_utxo.as_ref().unwrap().script_pubkey,
self.network,
)
.unwrap();

if self.state().is_wallet_address(&address) {
input.final_script_witness = Some(Witness::from_slice(&[&[0; 64]]));
}
}
}
}
Expand All @@ -1028,8 +1019,10 @@ impl Api for Server {
fn finalize_psbt(
&self,
psbt: String,
_extract: Option<bool>,
extract: Option<bool>,
) -> Result<FinalizePsbtResult, jsonrpc_core::Error> {
assert!(extract.is_none());

let mut transaction = Psbt::deserialize(
&base64::engine::general_purpose::STANDARD
.decode(psbt)
Expand All @@ -1050,4 +1043,46 @@ impl Api for Server {
complete: true,
})
}

fn utxo_update_psbt(&self, psbt: String) -> Result<String, jsonrpc_core::Error> {
Ok(psbt)
}

fn simulate_raw_transaction(
&self,
txs: Vec<String>,
_options: Option<SimulateRawTransactionOptions>,
) -> Result<SimulateRawTransactionResult, jsonrpc_core::Error> {
let mut balance_change: i64 = 0;

for tx in txs.into_iter().map(parse_hex_tx) {
for input in tx.input {
let tx = self
.state()
.transactions
.get(&input.previous_output.txid)
.unwrap()
.clone();

let txout = &tx.output[usize::try_from(input.previous_output.vout).unwrap()];

let address = Address::from_script(&txout.script_pubkey, Network::Bitcoin).unwrap();

if self.state().is_wallet_address(&address) {
balance_change -= i64::try_from(txout.value.to_sat()).unwrap();
}
}

for output in tx.output {
let address = Address::from_script(&output.script_pubkey, Network::Bitcoin).unwrap();
if self.state().is_wallet_address(&address) {
balance_change += i64::try_from(output.value.to_sat()).unwrap();
}
}
}

Ok(SimulateRawTransactionResult {
balance_change: SignedAmount::from_sat(balance_change),
})
}
}
31 changes: 23 additions & 8 deletions crates/mockcore/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use super::*;
#[derive(Debug)]
pub struct State {
pub blocks: BTreeMap<BlockHash, Block>,
pub change_addresses: BTreeSet<Address>,
pub descriptors: Vec<(String, bitcoincore_rpc::json::Timestamp)>,
pub fail_lock_unspent: bool,
pub hashes: Vec<BlockHash>,
Expand All @@ -11,14 +12,13 @@ pub struct State {
pub mempool: Vec<Transaction>,
pub network: Network,
pub nonce: u32,
pub receive_addresses: BTreeSet<Address>,
pub transactions: BTreeMap<Txid, Transaction>,
pub txid_to_block_height: BTreeMap<Txid, u32>,
pub utxos: BTreeMap<OutPoint, Amount>,
pub version: usize,
pub receive_addresses: Vec<Address>,
pub change_addresses: Vec<Address>,
pub wallets: BTreeSet<String>,
pub wallet: Wallet,
pub wallets: BTreeSet<String>,
}

impl State {
Expand All @@ -33,7 +33,7 @@ impl State {

Self {
blocks,
change_addresses: Vec::new(),
change_addresses: BTreeSet::new(),
descriptors: Vec::new(),
fail_lock_unspent,
hashes,
Expand All @@ -42,25 +42,40 @@ impl State {
mempool: Vec::new(),
network,
nonce: 0,
receive_addresses: Vec::new(),
receive_addresses: BTreeSet::new(),
transactions: BTreeMap::new(),
txid_to_block_height: BTreeMap::new(),
utxos: BTreeMap::new(),
version,
wallets: BTreeSet::new(),
wallet: Wallet::new(network),
wallets: BTreeSet::new(),
}
}

pub(crate) fn new_address(&mut self, change: bool) -> Address {
pub fn clear_wallet_addresses(&mut self) -> BTreeSet<Address> {
mem::take(&mut self.receive_addresses)
.into_iter()
.chain(mem::take(&mut self.change_addresses))
.collect()
}

pub fn remove_wallet_address(&mut self, address: Address) {
assert!(self.receive_addresses.remove(&address) || self.change_addresses.remove(&address));
}

pub fn add_wallet_address(&mut self, address: Address) {
self.receive_addresses.insert(address);
}

pub fn new_address(&mut self, change: bool) -> Address {
let address = self.wallet.new_address();

if change {
&mut self.change_addresses
} else {
&mut self.receive_addresses
}
.push(address.clone());
.insert(address.clone());

address
}
Expand Down
2 changes: 1 addition & 1 deletion crates/ordinals/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ rust-version.workspace = true

[dependencies]
bitcoin.workspace = true
derive_more = { version = "1.0.0", features = ["display", "from_str"] }
derive_more.workspace = true
serde.workspace = true
serde_with.workspace = true
thiserror = "2.0.0"
Expand Down
4 changes: 3 additions & 1 deletion src/fee_rate.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use super::*;

#[derive(Debug, PartialEq, Clone, Copy)]
#[derive(
Debug, PartialEq, Clone, Copy, derive_more::Display, DeserializeFromStr, SerializeDisplay,
)]
pub struct FeeRate(f64);

impl FromStr for FeeRate {
Expand Down
6 changes: 1 addition & 5 deletions src/index/fetcher.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use {
super::*,
base64::Engine,
http_body_util::{BodyExt, Full},
hyper::{body::Bytes, Method, Request, Uri},
hyper_util::{
Expand Down Expand Up @@ -43,10 +42,7 @@ impl Fetcher {

let (user, password) = settings.bitcoin_credentials()?.get_user_pass()?;
let auth = format!("{}:{}", user.unwrap(), password.unwrap());
let auth = format!(
"Basic {}",
&base64::engine::general_purpose::STANDARD.encode(auth)
);
let auth = format!("Basic {}", &base64_encode(auth.as_bytes()));
Ok(Fetcher { client, url, auth })
}

Expand Down
27 changes: 25 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ use {
policy::MAX_STANDARD_TX_WEIGHT,
script,
transaction::Version,
Amount, Block, Network, OutPoint, Script, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid,
Witness,
Amount, Block, Network, OutPoint, Script, ScriptBuf, Sequence, SignedAmount, Transaction, TxIn,
TxOut, Txid, Witness,
},
bitcoincore_rpc::{Client, RpcApi},
chrono::{DateTime, TimeZone, Utc},
Expand Down Expand Up @@ -141,6 +141,19 @@ static SHUTTING_DOWN: AtomicBool = AtomicBool::new(false);
static LISTENERS: Mutex<Vec<axum_server::Handle>> = Mutex::new(Vec::new());
static INDEXER: Mutex<Option<thread::JoinHandle<()>>> = Mutex::new(None);

#[doc(hidden)]
#[derive(Deserialize, Serialize)]
pub struct SimulateRawTransactionResult {
#[serde(with = "bitcoin::amount::serde::as_btc")]
pub balance_change: SignedAmount,
}

#[doc(hidden)]
#[derive(Deserialize, Serialize)]
pub struct SimulateRawTransactionOptions {
include_watchonly: bool,
}

#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn fund_raw_transaction(
client: &Client,
Expand Down Expand Up @@ -209,6 +222,16 @@ fn uncheck(address: &Address) -> Address<NetworkUnchecked> {
address.to_string().parse().unwrap()
}

pub fn base64_encode(data: &[u8]) -> String {
use base64::Engine;
base64::engine::general_purpose::STANDARD.encode(data)
}

pub fn base64_decode(s: &str) -> Result<Vec<u8>> {
use base64::Engine;
Ok(base64::engine::general_purpose::STANDARD.decode(s)?)
}

fn default<T: Default>() -> T {
Default::default()
}
Expand Down
Loading

0 comments on commit 9bc54f8

Please sign in to comment.