Skip to content

Commit

Permalink
Merge pull request #49 from buffrr/v0.0.5-updates
Browse files Browse the repository at this point in the history
v0.0.5 updates
  • Loading branch information
buffrr authored Dec 2, 2024
2 parents 3f39391 + 57da8af commit d3c1457
Show file tree
Hide file tree
Showing 18 changed files with 1,048 additions and 214 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.

8 changes: 7 additions & 1 deletion node/src/bin/space-cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ pub struct Args {
/// Force invalid transaction (for testing only)
#[arg(long, global = true, default_value = "false")]
force: bool,
/// Skip tx checker (not recommended)
#[arg(long, global = true, default_value = "false")]
skip_tx_check: bool,
#[command(subcommand)]
command: Commands,
}
Expand Down Expand Up @@ -241,6 +244,7 @@ struct SpaceCli {
wallet: String,
dust: Option<Amount>,
force: bool,
skip_tx_check: bool,
network: ExtendedNetwork,
rpc_url: String,
client: HttpClient,
Expand All @@ -259,6 +263,7 @@ impl SpaceCli {
wallet: args.wallet.clone(),
dust: args.dust.map(|d| Amount::from_sat(d)),
force: args.force,
skip_tx_check: args.skip_tx_check,
network: args.chain,
rpc_url: args.spaced_rpc_url.clone().unwrap(),
client,
Expand Down Expand Up @@ -289,6 +294,7 @@ impl SpaceCli {
dust: self.dust,
force: self.force,
confirmed_only,
skip_tx_check: self.skip_tx_check,
},
)
.await?;
Expand Down Expand Up @@ -587,7 +593,7 @@ async fn handle_commands(
let fee_rate = FeeRate::from_sat_per_vb(fee_rate).expect("valid fee rate");
let response = cli
.client
.wallet_bump_fee(&cli.wallet, txid, fee_rate)
.wallet_bump_fee(&cli.wallet, txid, fee_rate, cli.skip_tx_check)
.await?;
println!("{}", serde_json::to_string_pretty(&response)?);
}
Expand Down
133 changes: 133 additions & 0 deletions node/src/checker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use std::collections::{BTreeMap};
use anyhow::anyhow;
use protocol::bitcoin::{OutPoint, Transaction};
use protocol::hasher::{KeyHasher, SpaceKey};
use protocol::prepare::{DataSource, TxContext};
use protocol::{Covenant, RevokeReason, SpaceOut};
use protocol::validate::{TxChangeSet, UpdateKind, Validator};
use crate::store::{LiveSnapshot, Sha256};

pub struct TxChecker<'a> {
pub original: &'a mut LiveSnapshot,
pub spaces: BTreeMap<SpaceKey, Option<OutPoint>>,
pub spaceouts: BTreeMap<OutPoint, Option<SpaceOut>>,
}

impl<'a> TxChecker<'a> {
pub fn new(snap: &'a mut LiveSnapshot) -> Self {
Self {
original: snap,
spaces: Default::default(),
spaceouts: Default::default(),
}
}

pub fn apply_package(&mut self, height: u32, txs: Vec<Transaction>) -> anyhow::Result<Vec<Option<TxChangeSet>>> {
let mut sets = Vec::with_capacity(txs.len());
for tx in txs {
sets.push(self.apply_tx(height, &tx)?);
}
Ok(sets)
}

pub fn check_apply_tx(&mut self, height: u32, tx: &Transaction) -> anyhow::Result<Option<TxChangeSet>> {
let changeset = self.apply_tx(height, tx)?;
if let Some(changeset) = changeset.as_ref() {
Self::check(&changeset)?;
}
Ok(changeset)
}

pub fn apply_tx(&mut self, height: u32, tx: &Transaction) -> anyhow::Result<Option<TxChangeSet>> {
let ctx =
match { TxContext::from_tx::<Self, Sha256>(self, tx)? } {
None => return Ok(None),
Some(ctx) => ctx,
};
let validator = Validator::new();
let changeset = validator.process(height, tx, ctx);
let changeset2 = changeset.clone();

let txid = tx.compute_txid();
for spend in changeset.spends {
let outpoint = tx.input[spend.n].previous_output;
self.spaceouts.insert(outpoint, None);
}
for create in changeset.creates {
let outpoint = OutPoint {
txid,
vout: create.n as _,
};
if create.space.is_some() {
let space = SpaceKey::from(Sha256::hash(
create.space.as_ref().expect("space").name.as_ref())
);
self.spaces.insert(space, Some(outpoint));
}
self.spaceouts.insert(outpoint, Some(create));
}
for update in changeset.updates {
let space = SpaceKey::from(
Sha256::hash(update.output.spaceout.space.as_ref()
.expect("space").name.as_ref()));
match update.kind {
UpdateKind::Revoke(_) => {
self.spaces.insert(space, None);
self.spaceouts.insert(update.output.outpoint(), None);
}
_ => {
let outpoint = update.output.outpoint();
self.spaces.insert(space, Some(outpoint));
self.spaceouts.insert(outpoint, Some(update.output.spaceout));
}
}
}
Ok(Some(changeset2))
}

pub fn check(changset: &TxChangeSet) -> anyhow::Result<()> {
if changset.spends.iter().any(|spend| spend.script_error.is_some()) {
return Err(anyhow!("tx-check: transaction not broadcasted as it may have an open that will be rejected"));
}
for create in changset.creates.iter() {
if let Some(space) = create.space.as_ref() {
match space.covenant {
Covenant::Reserved => {
return Err(anyhow!("tx-check: transaction not broadcasted as it may cause spaces to use a reserved covenant"))
}
_ => {}
}
}
}
for update in changset.updates.iter() {
match update.kind {
UpdateKind::Revoke(kind) => {
match kind {
RevokeReason::Expired => {}
_ => {
return Err(anyhow!("tx-check: transaction not broadcasted as it may cause a space to be revoked (code: {:?})", kind))
}
}
}
_ => {}
}
}
Ok(())
}
}

impl DataSource for TxChecker<'_> {
fn get_space_outpoint(&mut self, space_hash: &SpaceKey) -> protocol::errors::Result<Option<OutPoint>> {
match self.spaces.get(space_hash) {
None => self.original.get_space_outpoint(space_hash.into()),
Some(res) => Ok(res.clone())
}
}

fn get_spaceout(&mut self, outpoint: &OutPoint) -> protocol::errors::Result<Option<SpaceOut>> {
match self.spaceouts.get(outpoint) {
None => self.original.get_spaceout(outpoint),
Some(space_out) => Ok(space_out.clone())
}
}
}
1 change: 1 addition & 0 deletions node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ pub mod source;
pub mod store;
pub mod sync;
pub mod wallets;
mod checker;
67 changes: 53 additions & 14 deletions node/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,18 @@ use bdk::{
};
use jsonrpsee::{core::async_trait, proc_macros::rpc, server::Server, types::ErrorObjectOwned};
use log::info;
use protocol::{
bitcoin::{
bip32::Xpriv,
Network::{Regtest, Testnet},
OutPoint,
},
constants::ChainAnchor,
hasher::{BaseHash, KeyHasher, SpaceKey},
prepare::DataSource,
slabel::SLabel,
FullSpaceOut, SpaceOut,
};
use protocol::{bitcoin, bitcoin::{
bip32::Xpriv,
Network::{Regtest, Testnet},
OutPoint,
}, constants::ChainAnchor, hasher::{BaseHash, KeyHasher, SpaceKey}, prepare::DataSource, slabel::SLabel, FullSpaceOut, SpaceOut};
use serde::{Deserialize, Serialize};
use tokio::{
select,
sync::{broadcast, mpsc, oneshot, RwLock},
task::JoinSet,
};
use protocol::validate::TxChangeSet;
use wallet::{
bdk_wallet as bdk, bdk_wallet::template::Bip86, bitcoin::hashes::Hash, export::WalletExport,
DoubleUtxo, SpacesWallet, WalletConfig, WalletDescriptors, WalletInfo,
Expand All @@ -48,6 +42,7 @@ use crate::{
WalletResponse,
},
};
use crate::checker::TxChecker;

pub(crate) type Responder<T> = oneshot::Sender<T>;

Expand All @@ -58,6 +53,10 @@ pub struct ServerInfo {
}

pub enum ChainStateCommand {
CheckPackage {
txs: Vec<String>,
resp: Responder<anyhow::Result<Vec<Option<TxChangeSet>>>>,
},
GetTip {
resp: Responder<anyhow::Result<ChainAnchor>>,
},
Expand Down Expand Up @@ -117,6 +116,9 @@ pub trait Rpc {
#[method(name = "getspaceout")]
async fn get_spaceout(&self, outpoint: OutPoint) -> Result<Option<SpaceOut>, ErrorObjectOwned>;

#[method(name = "checkpackage")]
async fn check_package(&self, txs: Vec<String>) -> Result<Vec<Option<TxChangeSet>>, ErrorObjectOwned>;

#[method(name = "estimatebid")]
async fn estimate_bid(&self, target: usize) -> Result<u64, ErrorObjectOwned>;

Expand Down Expand Up @@ -167,6 +169,7 @@ pub trait Rpc {
wallet: &str,
txid: Txid,
fee_rate: FeeRate,
skip_tx_check: bool,
) -> Result<Vec<TxResponse>, ErrorObjectOwned>;

#[method(name = "walletlisttransactions")]
Expand All @@ -187,7 +190,7 @@ pub trait Rpc {

#[method(name = "walletlistspaces")]
async fn wallet_list_spaces(&self, wallet: &str)
-> Result<Vec<WalletOutput>, ErrorObjectOwned>;
-> Result<Vec<WalletOutput>, ErrorObjectOwned>;

#[method(name = "walletlistunspent")]
async fn wallet_list_unspent(
Expand All @@ -211,6 +214,7 @@ pub struct RpcWalletTxBuilder {
pub dust: Option<Amount>,
pub force: bool,
pub confirmed_only: bool,
pub skip_tx_check: bool,
}

#[derive(Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -617,6 +621,15 @@ impl RpcServer for RpcServerImpl {
Ok(spaceout)
}

async fn check_package(&self, txs: Vec<String>) -> Result<Vec<Option<TxChangeSet>>, ErrorObjectOwned> {
let spaceout = self
.store
.check_package(txs)
.await
.map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::<String>))?;
Ok(spaceout)
}

async fn estimate_bid(&self, target: usize) -> Result<u64, ErrorObjectOwned> {
let info = self
.store
Expand Down Expand Up @@ -731,10 +744,11 @@ impl RpcServer for RpcServerImpl {
wallet: &str,
txid: Txid,
fee_rate: FeeRate,
skip_tx_check: bool
) -> Result<Vec<TxResponse>, ErrorObjectOwned> {
self.wallet(&wallet)
.await?
.send_fee_bump(txid, fee_rate)
.send_fee_bump(txid, fee_rate, skip_tx_check)
.await
.map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::<String>))
}
Expand Down Expand Up @@ -836,6 +850,7 @@ impl AsyncChainState {
Ok(None)
}


async fn get_indexed_block(
index: &mut Option<LiveSnapshot>,
block_hash: &BlockHash,
Expand Down Expand Up @@ -884,6 +899,22 @@ impl AsyncChainState {
cmd: ChainStateCommand,
) {
match cmd {
ChainStateCommand::CheckPackage { txs : raw_txs, resp } => {
let mut txs = Vec::with_capacity(raw_txs.len());
for raw_tx in raw_txs {
let tx = bitcoin::consensus::encode::deserialize_hex(&raw_tx);
if tx.is_err() {
let _ = resp.send(Err(anyhow!("could not decode hex transaction")));
return;
}
txs.push(tx.unwrap());
}

let tip = chain_state.tip.read().expect("read meta").clone();
let mut emulator = TxChecker::new(chain_state);
let result = emulator.apply_package(tip.height+1, txs);
let _ = resp.send(result);
},
ChainStateCommand::GetTip { resp } => {
let tip = chain_state.tip.read().expect("read meta").clone();
_ = resp.send(Ok(tip))
Expand Down Expand Up @@ -979,6 +1010,14 @@ impl AsyncChainState {
resp_rx.await?
}

pub async fn check_package(&self, txs: Vec<String>) -> anyhow::Result<Vec<Option<TxChangeSet>>> {
let (resp, resp_rx) = oneshot::channel();
self.sender
.send(ChainStateCommand::CheckPackage { txs, resp })
.await?;
resp_rx.await?
}

pub async fn get_tip(&self) -> anyhow::Result<ChainAnchor> {
let (resp, resp_rx) = oneshot::channel();
self.sender.send(ChainStateCommand::GetTip { resp }).await?;
Expand Down
Loading

0 comments on commit d3c1457

Please sign in to comment.