Skip to content

Commit d3c1457

Browse files
authored
Merge pull request #49 from buffrr/v0.0.5-updates
v0.0.5 updates
2 parents 3f39391 + 57da8af commit d3c1457

File tree

18 files changed

+1048
-214
lines changed

18 files changed

+1048
-214
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node/src/bin/space-cli.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ pub struct Args {
4242
/// Force invalid transaction (for testing only)
4343
#[arg(long, global = true, default_value = "false")]
4444
force: bool,
45+
/// Skip tx checker (not recommended)
46+
#[arg(long, global = true, default_value = "false")]
47+
skip_tx_check: bool,
4548
#[command(subcommand)]
4649
command: Commands,
4750
}
@@ -241,6 +244,7 @@ struct SpaceCli {
241244
wallet: String,
242245
dust: Option<Amount>,
243246
force: bool,
247+
skip_tx_check: bool,
244248
network: ExtendedNetwork,
245249
rpc_url: String,
246250
client: HttpClient,
@@ -259,6 +263,7 @@ impl SpaceCli {
259263
wallet: args.wallet.clone(),
260264
dust: args.dust.map(|d| Amount::from_sat(d)),
261265
force: args.force,
266+
skip_tx_check: args.skip_tx_check,
262267
network: args.chain,
263268
rpc_url: args.spaced_rpc_url.clone().unwrap(),
264269
client,
@@ -289,6 +294,7 @@ impl SpaceCli {
289294
dust: self.dust,
290295
force: self.force,
291296
confirmed_only,
297+
skip_tx_check: self.skip_tx_check,
292298
},
293299
)
294300
.await?;
@@ -587,7 +593,7 @@ async fn handle_commands(
587593
let fee_rate = FeeRate::from_sat_per_vb(fee_rate).expect("valid fee rate");
588594
let response = cli
589595
.client
590-
.wallet_bump_fee(&cli.wallet, txid, fee_rate)
596+
.wallet_bump_fee(&cli.wallet, txid, fee_rate, cli.skip_tx_check)
591597
.await?;
592598
println!("{}", serde_json::to_string_pretty(&response)?);
593599
}

node/src/checker.rs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
use std::collections::{BTreeMap};
2+
use anyhow::anyhow;
3+
use protocol::bitcoin::{OutPoint, Transaction};
4+
use protocol::hasher::{KeyHasher, SpaceKey};
5+
use protocol::prepare::{DataSource, TxContext};
6+
use protocol::{Covenant, RevokeReason, SpaceOut};
7+
use protocol::validate::{TxChangeSet, UpdateKind, Validator};
8+
use crate::store::{LiveSnapshot, Sha256};
9+
10+
pub struct TxChecker<'a> {
11+
pub original: &'a mut LiveSnapshot,
12+
pub spaces: BTreeMap<SpaceKey, Option<OutPoint>>,
13+
pub spaceouts: BTreeMap<OutPoint, Option<SpaceOut>>,
14+
}
15+
16+
impl<'a> TxChecker<'a> {
17+
pub fn new(snap: &'a mut LiveSnapshot) -> Self {
18+
Self {
19+
original: snap,
20+
spaces: Default::default(),
21+
spaceouts: Default::default(),
22+
}
23+
}
24+
25+
pub fn apply_package(&mut self, height: u32, txs: Vec<Transaction>) -> anyhow::Result<Vec<Option<TxChangeSet>>> {
26+
let mut sets = Vec::with_capacity(txs.len());
27+
for tx in txs {
28+
sets.push(self.apply_tx(height, &tx)?);
29+
}
30+
Ok(sets)
31+
}
32+
33+
pub fn check_apply_tx(&mut self, height: u32, tx: &Transaction) -> anyhow::Result<Option<TxChangeSet>> {
34+
let changeset = self.apply_tx(height, tx)?;
35+
if let Some(changeset) = changeset.as_ref() {
36+
Self::check(&changeset)?;
37+
}
38+
Ok(changeset)
39+
}
40+
41+
pub fn apply_tx(&mut self, height: u32, tx: &Transaction) -> anyhow::Result<Option<TxChangeSet>> {
42+
let ctx =
43+
match { TxContext::from_tx::<Self, Sha256>(self, tx)? } {
44+
None => return Ok(None),
45+
Some(ctx) => ctx,
46+
};
47+
let validator = Validator::new();
48+
let changeset = validator.process(height, tx, ctx);
49+
let changeset2 = changeset.clone();
50+
51+
let txid = tx.compute_txid();
52+
for spend in changeset.spends {
53+
let outpoint = tx.input[spend.n].previous_output;
54+
self.spaceouts.insert(outpoint, None);
55+
}
56+
for create in changeset.creates {
57+
let outpoint = OutPoint {
58+
txid,
59+
vout: create.n as _,
60+
};
61+
if create.space.is_some() {
62+
let space = SpaceKey::from(Sha256::hash(
63+
create.space.as_ref().expect("space").name.as_ref())
64+
);
65+
self.spaces.insert(space, Some(outpoint));
66+
}
67+
self.spaceouts.insert(outpoint, Some(create));
68+
}
69+
for update in changeset.updates {
70+
let space = SpaceKey::from(
71+
Sha256::hash(update.output.spaceout.space.as_ref()
72+
.expect("space").name.as_ref()));
73+
match update.kind {
74+
UpdateKind::Revoke(_) => {
75+
self.spaces.insert(space, None);
76+
self.spaceouts.insert(update.output.outpoint(), None);
77+
}
78+
_ => {
79+
let outpoint = update.output.outpoint();
80+
self.spaces.insert(space, Some(outpoint));
81+
self.spaceouts.insert(outpoint, Some(update.output.spaceout));
82+
}
83+
}
84+
}
85+
Ok(Some(changeset2))
86+
}
87+
88+
pub fn check(changset: &TxChangeSet) -> anyhow::Result<()> {
89+
if changset.spends.iter().any(|spend| spend.script_error.is_some()) {
90+
return Err(anyhow!("tx-check: transaction not broadcasted as it may have an open that will be rejected"));
91+
}
92+
for create in changset.creates.iter() {
93+
if let Some(space) = create.space.as_ref() {
94+
match space.covenant {
95+
Covenant::Reserved => {
96+
return Err(anyhow!("tx-check: transaction not broadcasted as it may cause spaces to use a reserved covenant"))
97+
}
98+
_ => {}
99+
}
100+
}
101+
}
102+
for update in changset.updates.iter() {
103+
match update.kind {
104+
UpdateKind::Revoke(kind) => {
105+
match kind {
106+
RevokeReason::Expired => {}
107+
_ => {
108+
return Err(anyhow!("tx-check: transaction not broadcasted as it may cause a space to be revoked (code: {:?})", kind))
109+
}
110+
}
111+
}
112+
_ => {}
113+
}
114+
}
115+
Ok(())
116+
}
117+
}
118+
119+
impl DataSource for TxChecker<'_> {
120+
fn get_space_outpoint(&mut self, space_hash: &SpaceKey) -> protocol::errors::Result<Option<OutPoint>> {
121+
match self.spaces.get(space_hash) {
122+
None => self.original.get_space_outpoint(space_hash.into()),
123+
Some(res) => Ok(res.clone())
124+
}
125+
}
126+
127+
fn get_spaceout(&mut self, outpoint: &OutPoint) -> protocol::errors::Result<Option<SpaceOut>> {
128+
match self.spaceouts.get(outpoint) {
129+
None => self.original.get_spaceout(outpoint),
130+
Some(space_out) => Ok(space_out.clone())
131+
}
132+
}
133+
}

node/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ pub mod source;
1111
pub mod store;
1212
pub mod sync;
1313
pub mod wallets;
14+
mod checker;

node/src/rpc.rs

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,18 @@ use bdk::{
1515
};
1616
use jsonrpsee::{core::async_trait, proc_macros::rpc, server::Server, types::ErrorObjectOwned};
1717
use log::info;
18-
use protocol::{
19-
bitcoin::{
20-
bip32::Xpriv,
21-
Network::{Regtest, Testnet},
22-
OutPoint,
23-
},
24-
constants::ChainAnchor,
25-
hasher::{BaseHash, KeyHasher, SpaceKey},
26-
prepare::DataSource,
27-
slabel::SLabel,
28-
FullSpaceOut, SpaceOut,
29-
};
18+
use protocol::{bitcoin, bitcoin::{
19+
bip32::Xpriv,
20+
Network::{Regtest, Testnet},
21+
OutPoint,
22+
}, constants::ChainAnchor, hasher::{BaseHash, KeyHasher, SpaceKey}, prepare::DataSource, slabel::SLabel, FullSpaceOut, SpaceOut};
3023
use serde::{Deserialize, Serialize};
3124
use tokio::{
3225
select,
3326
sync::{broadcast, mpsc, oneshot, RwLock},
3427
task::JoinSet,
3528
};
29+
use protocol::validate::TxChangeSet;
3630
use wallet::{
3731
bdk_wallet as bdk, bdk_wallet::template::Bip86, bitcoin::hashes::Hash, export::WalletExport,
3832
DoubleUtxo, SpacesWallet, WalletConfig, WalletDescriptors, WalletInfo,
@@ -48,6 +42,7 @@ use crate::{
4842
WalletResponse,
4943
},
5044
};
45+
use crate::checker::TxChecker;
5146

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

@@ -58,6 +53,10 @@ pub struct ServerInfo {
5853
}
5954

6055
pub enum ChainStateCommand {
56+
CheckPackage {
57+
txs: Vec<String>,
58+
resp: Responder<anyhow::Result<Vec<Option<TxChangeSet>>>>,
59+
},
6160
GetTip {
6261
resp: Responder<anyhow::Result<ChainAnchor>>,
6362
},
@@ -117,6 +116,9 @@ pub trait Rpc {
117116
#[method(name = "getspaceout")]
118117
async fn get_spaceout(&self, outpoint: OutPoint) -> Result<Option<SpaceOut>, ErrorObjectOwned>;
119118

119+
#[method(name = "checkpackage")]
120+
async fn check_package(&self, txs: Vec<String>) -> Result<Vec<Option<TxChangeSet>>, ErrorObjectOwned>;
121+
120122
#[method(name = "estimatebid")]
121123
async fn estimate_bid(&self, target: usize) -> Result<u64, ErrorObjectOwned>;
122124

@@ -167,6 +169,7 @@ pub trait Rpc {
167169
wallet: &str,
168170
txid: Txid,
169171
fee_rate: FeeRate,
172+
skip_tx_check: bool,
170173
) -> Result<Vec<TxResponse>, ErrorObjectOwned>;
171174

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

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

192195
#[method(name = "walletlistunspent")]
193196
async fn wallet_list_unspent(
@@ -211,6 +214,7 @@ pub struct RpcWalletTxBuilder {
211214
pub dust: Option<Amount>,
212215
pub force: bool,
213216
pub confirmed_only: bool,
217+
pub skip_tx_check: bool,
214218
}
215219

216220
#[derive(Clone, Serialize, Deserialize)]
@@ -617,6 +621,15 @@ impl RpcServer for RpcServerImpl {
617621
Ok(spaceout)
618622
}
619623

624+
async fn check_package(&self, txs: Vec<String>) -> Result<Vec<Option<TxChangeSet>>, ErrorObjectOwned> {
625+
let spaceout = self
626+
.store
627+
.check_package(txs)
628+
.await
629+
.map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::<String>))?;
630+
Ok(spaceout)
631+
}
632+
620633
async fn estimate_bid(&self, target: usize) -> Result<u64, ErrorObjectOwned> {
621634
let info = self
622635
.store
@@ -731,10 +744,11 @@ impl RpcServer for RpcServerImpl {
731744
wallet: &str,
732745
txid: Txid,
733746
fee_rate: FeeRate,
747+
skip_tx_check: bool
734748
) -> Result<Vec<TxResponse>, ErrorObjectOwned> {
735749
self.wallet(&wallet)
736750
.await?
737-
.send_fee_bump(txid, fee_rate)
751+
.send_fee_bump(txid, fee_rate, skip_tx_check)
738752
.await
739753
.map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::<String>))
740754
}
@@ -836,6 +850,7 @@ impl AsyncChainState {
836850
Ok(None)
837851
}
838852

853+
839854
async fn get_indexed_block(
840855
index: &mut Option<LiveSnapshot>,
841856
block_hash: &BlockHash,
@@ -884,6 +899,22 @@ impl AsyncChainState {
884899
cmd: ChainStateCommand,
885900
) {
886901
match cmd {
902+
ChainStateCommand::CheckPackage { txs : raw_txs, resp } => {
903+
let mut txs = Vec::with_capacity(raw_txs.len());
904+
for raw_tx in raw_txs {
905+
let tx = bitcoin::consensus::encode::deserialize_hex(&raw_tx);
906+
if tx.is_err() {
907+
let _ = resp.send(Err(anyhow!("could not decode hex transaction")));
908+
return;
909+
}
910+
txs.push(tx.unwrap());
911+
}
912+
913+
let tip = chain_state.tip.read().expect("read meta").clone();
914+
let mut emulator = TxChecker::new(chain_state);
915+
let result = emulator.apply_package(tip.height+1, txs);
916+
let _ = resp.send(result);
917+
},
887918
ChainStateCommand::GetTip { resp } => {
888919
let tip = chain_state.tip.read().expect("read meta").clone();
889920
_ = resp.send(Ok(tip))
@@ -979,6 +1010,14 @@ impl AsyncChainState {
9791010
resp_rx.await?
9801011
}
9811012

1013+
pub async fn check_package(&self, txs: Vec<String>) -> anyhow::Result<Vec<Option<TxChangeSet>>> {
1014+
let (resp, resp_rx) = oneshot::channel();
1015+
self.sender
1016+
.send(ChainStateCommand::CheckPackage { txs, resp })
1017+
.await?;
1018+
resp_rx.await?
1019+
}
1020+
9821021
pub async fn get_tip(&self) -> anyhow::Result<ChainAnchor> {
9831022
let (resp, resp_rx) = oneshot::channel();
9841023
self.sender.send(ChainStateCommand::GetTip { resp }).await?;

0 commit comments

Comments
 (0)