Skip to content

Commit 4466a24

Browse files
committed
Protocol and wallet safety updates
- Make selection of dust outputs for auctions safer - Remove unused script errors - Add option to store raw tx in block index - Add Bytes type that can be serialized as hex - Use sensible names for space op codes - Add forcespend command for testing - Cargo fmt
1 parent af7a096 commit 4466a24

File tree

13 files changed

+360
-68
lines changed

13 files changed

+360
-68
lines changed

node/src/bin/space-cli.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,13 @@ enum Commands {
231231
/// The space name
232232
space: String,
233233
},
234+
/// Force spend an output owned by wallet (for testing only)
235+
#[command(name = "forcespend")]
236+
ForceSpend {
237+
outpoint: OutPoint,
238+
#[arg(long, short)]
239+
fee_rate: u64,
240+
},
234241
}
235242

236243
struct SpaceCli {
@@ -553,7 +560,7 @@ async fn handle_commands(
553560
}
554561
};
555562

556-
let space_script = protocol::script::SpaceScript::create_set(data.as_slice());
563+
let space_script = protocol::script::SpaceScript::create_set_fallback(data.as_slice());
557564

558565
cli.send_request(
559566
Some(RpcWalletRequest::Execute(ExecuteParams {
@@ -609,6 +616,17 @@ async fn handle_commands(
609616
space_hash(&space).map_err(|e| ClientError::Custom(e.to_string()))?
610617
);
611618
}
619+
Commands::ForceSpend { outpoint, fee_rate } => {
620+
let result = cli
621+
.client
622+
.wallet_force_spend(
623+
&cli.wallet,
624+
outpoint,
625+
FeeRate::from_sat_per_vb(fee_rate).unwrap(),
626+
)
627+
.await?;
628+
println!("{}", serde_json::to_string_pretty(&result).expect("result"));
629+
}
612630
}
613631

614632
Ok(())

node/src/config.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ pub struct Args {
7373
/// Listen for JSON-RPC connections on <port>
7474
#[arg(long, help_heading = Some(RPC_OPTIONS), env = "SPACED_RPC_PORT")]
7575
rpc_port: Option<u16>,
76+
/// Index blocks including the full transaction data
77+
#[arg(long, env = "SPACED_BLOCK_INDEX_FULL", default_value = "false")]
78+
block_index_full: bool,
7679
}
7780

7881
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, ValueEnum, Serialize, Deserialize)]
@@ -173,7 +176,8 @@ impl Args {
173176
store: chain_store,
174177
};
175178

176-
let block_index = if args.block_index {
179+
let block_index_enabled = args.block_index || args.block_index_full;
180+
let block_index = if block_index_enabled {
177181
let block_db_path = data_dir.join("block_index.sdb");
178182
if !initial_sync && !block_db_path.exists() {
179183
return Err(anyhow::anyhow!(
@@ -206,6 +210,7 @@ impl Args {
206210
bind: rpc_bind_addresses,
207211
chain,
208212
block_index,
213+
block_index_full: args.block_index_full,
209214
num_workers: args.jobs as usize,
210215
})
211216
}
@@ -262,7 +267,7 @@ pub fn safe_exit(code: i32) -> ! {
262267
std::process::exit(code)
263268
}
264269

265-
fn default_bitcoin_rpc_url(network: &ExtendedNetwork) -> &'static str {
270+
pub fn default_bitcoin_rpc_url(network: &ExtendedNetwork) -> &'static str {
266271
match network {
267272
ExtendedNetwork::Mainnet | ExtendedNetwork::MainnetAlpha => "http://127.0.0.1:8332",
268273
ExtendedNetwork::Testnet4 => "http://127.0.0.1:48332",

node/src/node.rs

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use protocol::{
1111
hasher::{BidKey, KeyHasher, OutpointKey, SpaceKey},
1212
prepare::TxContext,
1313
validate::{TxChangeSet, UpdateKind, Validator},
14-
Covenant, FullSpaceOut, RevokeReason, SpaceOut,
14+
Bytes, Covenant, FullSpaceOut, RevokeReason, SpaceOut,
1515
};
1616
use serde::{Deserialize, Serialize};
1717
use wallet::bitcoin::Transaction;
@@ -32,13 +32,29 @@ pub trait BlockSource {
3232
#[derive(Debug, Clone)]
3333
pub struct Node {
3434
validator: Validator,
35+
tx_data: bool,
3536
}
3637

3738
/// A block structure containing validated transaction metadata
3839
/// relevant to the Spaces protocol
3940
#[derive(Clone, Serialize, Deserialize, Encode, Decode)]
4041
pub struct BlockMeta {
41-
pub tx_meta: Vec<TxChangeSet>,
42+
pub height: u32,
43+
pub tx_meta: Vec<TxEntry>,
44+
}
45+
46+
#[derive(Clone, Serialize, Deserialize, Encode, Decode)]
47+
pub struct TxEntry {
48+
#[serde(flatten)]
49+
pub changeset: TxChangeSet,
50+
#[serde(skip_serializing_if = "Option::is_none", flatten)]
51+
pub tx: Option<TxData>,
52+
}
53+
54+
#[derive(Clone, Serialize, Deserialize, Encode, Decode)]
55+
pub struct TxData {
56+
pub position: u32,
57+
pub raw: Bytes,
4258
}
4359

4460
#[derive(Debug)]
@@ -60,9 +76,10 @@ impl fmt::Display for SyncError {
6076
impl Error for SyncError {}
6177

6278
impl Node {
63-
pub fn new() -> Self {
79+
pub fn new(tx_data: bool) -> Self {
6480
Self {
6581
validator: Validator::new(),
82+
tx_data,
6683
}
6784
}
6885

@@ -85,7 +102,10 @@ impl Node {
85102
}
86103
}
87104

88-
let mut block_data = BlockMeta { tx_meta: vec![] };
105+
let mut block_data = BlockMeta {
106+
height,
107+
tx_meta: vec![],
108+
};
89109

90110
if (height - 1) % ROLLOUT_BLOCK_INTERVAL == 0 {
91111
let batch = Self::get_rollout_batch(ROLLOUT_BATCH_SIZE, chain)?;
@@ -96,21 +116,44 @@ impl Node {
96116

97117
let validated = self.validator.rollout(height, &coinbase, batch);
98118
if get_block_data {
99-
block_data.tx_meta.push(validated.clone());
119+
block_data.tx_meta.push(TxEntry {
120+
changeset: validated.clone(),
121+
tx: if self.tx_data {
122+
Some(TxData {
123+
position: 0,
124+
raw: Bytes::new(protocol::bitcoin::consensus::encode::serialize(
125+
&coinbase,
126+
)),
127+
})
128+
} else {
129+
None
130+
},
131+
});
100132
}
101-
102133
self.apply_tx(&mut chain.state, &coinbase, validated);
103134
}
104135

105-
for tx in block.txdata {
136+
for (position, tx) in block.txdata.into_iter().enumerate() {
106137
let prepared_tx =
107138
{ TxContext::from_tx::<LiveSnapshot, Sha256>(&mut chain.state, &tx)? };
108139

109140
if let Some(prepared_tx) = prepared_tx {
110141
let validated_tx = self.validator.process(height, &tx, prepared_tx);
111142

112143
if get_block_data {
113-
block_data.tx_meta.push(validated_tx.clone());
144+
block_data.tx_meta.push(TxEntry {
145+
changeset: validated_tx.clone(),
146+
tx: if self.tx_data {
147+
Some(TxData {
148+
position: position as u32,
149+
raw: Bytes::new(protocol::bitcoin::consensus::encode::serialize(
150+
&tx,
151+
)),
152+
})
153+
} else {
154+
None
155+
},
156+
});
114157
}
115158
self.apply_tx(&mut chain.state, &tx, validated_tx);
116159
}

node/src/rpc.rs

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ use protocol::{
2424
constants::ChainAnchor,
2525
hasher::{BaseHash, SpaceKey},
2626
prepare::DataSource,
27-
validate::TxChangeSet,
2827
FullSpaceOut, SpaceOut,
2928
};
3029
use serde::{Deserialize, Serialize};
@@ -40,7 +39,7 @@ use wallet::{
4039

4140
use crate::{
4241
config::ExtendedNetwork,
43-
node::BlockMeta,
42+
node::{BlockMeta, TxEntry},
4443
source::BitcoinRpc,
4544
store::{ChainState, LiveSnapshot},
4645
wallets::{
@@ -75,7 +74,7 @@ pub enum ChainStateCommand {
7574
},
7675
GetTxMeta {
7776
txid: Txid,
78-
resp: Responder<anyhow::Result<Option<TxChangeSet>>>,
77+
resp: Responder<anyhow::Result<Option<TxEntry>>>,
7978
},
8079
GetBlockMeta {
8180
block_hash: BlockHash,
@@ -124,7 +123,7 @@ pub trait Rpc {
124123
) -> Result<Option<BlockMeta>, ErrorObjectOwned>;
125124

126125
#[method(name = "gettxmeta")]
127-
async fn get_tx_meta(&self, txid: Txid) -> Result<Option<TxChangeSet>, ErrorObjectOwned>;
126+
async fn get_tx_meta(&self, txid: Txid) -> Result<Option<TxEntry>, ErrorObjectOwned>;
128127

129128
#[method(name = "walletload")]
130129
async fn wallet_load(&self, name: &str) -> Result<(), ErrorObjectOwned>;
@@ -163,6 +162,14 @@ pub trait Rpc {
163162
fee_rate: FeeRate,
164163
) -> Result<Vec<TxResponse>, ErrorObjectOwned>;
165164

165+
#[method(name = "walletforcespend")]
166+
async fn wallet_force_spend(
167+
&self,
168+
wallet: &str,
169+
outpoint: OutPoint,
170+
fee_rate: FeeRate,
171+
) -> Result<TxResponse, ErrorObjectOwned>;
172+
166173
#[method(name = "walletlistspaces")]
167174
async fn wallet_list_spaces(&self, wallet: &str)
168175
-> Result<Vec<WalletOutput>, ErrorObjectOwned>;
@@ -624,7 +631,7 @@ impl RpcServer for RpcServerImpl {
624631
Ok(data)
625632
}
626633

627-
async fn get_tx_meta(&self, txid: Txid) -> Result<Option<TxChangeSet>, ErrorObjectOwned> {
634+
async fn get_tx_meta(&self, txid: Txid) -> Result<Option<TxEntry>, ErrorObjectOwned> {
628635
let data = self
629636
.store
630637
.get_tx_meta(txid)
@@ -715,6 +722,19 @@ impl RpcServer for RpcServerImpl {
715722
.map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::<String>))
716723
}
717724

725+
async fn wallet_force_spend(
726+
&self,
727+
wallet: &str,
728+
outpoint: OutPoint,
729+
fee_rate: FeeRate,
730+
) -> Result<TxResponse, ErrorObjectOwned> {
731+
self.wallet(&wallet)
732+
.await?
733+
.send_force_spend(outpoint, fee_rate)
734+
.await
735+
.map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::<String>))
736+
}
737+
718738
async fn wallet_list_spaces(
719739
&self,
720740
wallet: &str,
@@ -768,7 +788,7 @@ impl AsyncChainState {
768788
client: &reqwest::Client,
769789
rpc: &BitcoinRpc,
770790
chain_state: &mut LiveSnapshot,
771-
) -> Result<Option<TxChangeSet>, anyhow::Error> {
791+
) -> Result<Option<TxEntry>, anyhow::Error> {
772792
let info: serde_json::Value = rpc
773793
.send_json(client, &rpc.get_raw_transaction(&txid, true))
774794
.await
@@ -781,7 +801,10 @@ impl AsyncChainState {
781801
let block = Self::get_indexed_block(index, &block_hash, client, rpc, chain_state).await?;
782802

783803
if let Some(block) = block {
784-
return Ok(block.tx_meta.into_iter().find(|tx| &tx.txid == txid));
804+
return Ok(block
805+
.tx_meta
806+
.into_iter()
807+
.find(|tx| &tx.changeset.txid == txid));
785808
}
786809
Ok(None)
787810
}
@@ -951,7 +974,7 @@ impl AsyncChainState {
951974
resp_rx.await?
952975
}
953976

954-
pub async fn get_tx_meta(&self, txid: Txid) -> anyhow::Result<Option<TxChangeSet>> {
977+
pub async fn get_tx_meta(&self, txid: Txid) -> anyhow::Result<Option<TxEntry>> {
955978
let (resp, resp_rx) = oneshot::channel();
956979
self.sender
957980
.send(ChainStateCommand::GetTxMeta { txid, resp })

node/src/sync.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub struct Spaced {
3333
pub network: ExtendedNetwork,
3434
pub chain: LiveStore,
3535
pub block_index: Option<LiveStore>,
36+
pub block_index_full: bool,
3637
pub rpc: BitcoinRpc,
3738
pub data_dir: PathBuf,
3839
pub bind: Vec<SocketAddr>,
@@ -147,7 +148,7 @@ impl Spaced {
147148
shutdown: broadcast::Sender<()>,
148149
) -> anyhow::Result<()> {
149150
let start_block: ChainAnchor = { self.chain.state.tip.read().expect("read").clone() };
150-
let mut node = Node::new();
151+
let mut node = Node::new(self.block_index_full);
151152

152153
info!(
153154
"Start block={} height={}",

0 commit comments

Comments
 (0)