Skip to content

Commit

Permalink
Add special ordinal protection (ordinals#357)
Browse files Browse the repository at this point in the history
  • Loading branch information
terror committed Aug 23, 2022
1 parent 36de18e commit 3e3bdc5
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 27 deletions.
23 changes: 23 additions & 0 deletions src/purse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,29 @@ impl Purse {
bail!("No utxo contains {}˚.", ordinal);
}

pub(crate) fn special_ordinals(
&self,
options: &Options,
outpoint: OutPoint,
) -> Result<Vec<Ordinal>> {
let index = Index::index(options)?;

match index.list(outpoint)? {
Some(List::Unspent(ranges)) => Ok(
ranges
.into_iter()
.map(|(start, _end)| Ordinal(start))
.filter(|ordinal| ordinal.rarity() > Rarity::Common)
.collect(),
),
Some(List::Spent(txid)) => Err(anyhow!(
"UTXO {} unspent in wallet but spent in index by transaction {txid}",
outpoint
)),
None => Ok(Vec::new()),
}
}

fn blockchain(options: &Options, key: Mnemonic) -> Result<RpcBlockchain> {
Ok(RpcBlockchain::from_config(&RpcConfig {
url: options.rpc_url(),
Expand Down
23 changes: 8 additions & 15 deletions src/subcommand/wallet/identify.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
use super::*;

pub(crate) fn run(options: Options) -> Result {
let index = Index::index(&options)?;
let purse = Purse::load(&options)?;

let mut ordinals = Purse::load(&options)?
let mut ordinals = purse
.wallet
.list_unspent()?
.into_iter()
.map(|utxo| {
index.list(utxo.outpoint).and_then(|list| match list {
Some(List::Unspent(ranges)) => Ok(
ranges
purse
.special_ordinals(&options, utxo.outpoint)
.map(|ordinals| {
ordinals
.into_iter()
.map(|(start, _end)| Ordinal(start))
.filter(|ordinal| ordinal.rarity() > Rarity::Common)
.map(|ordinal| (ordinal, utxo.outpoint))
.collect(),
),
Some(List::Spent(txid)) => Err(anyhow!(
"UTXO {} unspent in wallet but spent in index by transaction {txid}",
utxo.outpoint
)),
None => Ok(Vec::new()),
})
.collect::<Vec<(Ordinal, OutPoint)>>()
})
})
.collect::<Result<Vec<Vec<(Ordinal, OutPoint)>>, _>>()?
.into_iter()
Expand Down
26 changes: 20 additions & 6 deletions src/subcommand/wallet/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,26 @@ pub(crate) struct Send {

impl Send {
pub(crate) fn run(self, options: Options) -> Result {
let wallet = Purse::load(&options)?;

let utxo = wallet.find(&options, self.ordinal)?;
let purse = Purse::load(&options)?;

let utxo = purse.find(&options, self.ordinal)?;

let ordinals = purse.special_ordinals(&options, utxo.outpoint)?;

if !ordinals.is_empty() && (ordinals.len() > 1 || ordinals[0] != self.ordinal) {
bail!(
"Trying to send ordinal {} but UTXO also contains ordinal(s) {}",
self.ordinal,
ordinals
.iter()
.map(|ordinal| format!("{ordinal} ({})", ordinal.rarity()))
.collect::<Vec<String>>()
.join(", ")
);
}

let (mut psbt, _details) = {
let mut builder = wallet.wallet.build_tx();
let mut builder = purse.wallet.build_tx();

builder
.manually_selected_only()
Expand All @@ -26,13 +40,13 @@ impl Send {
builder.finish()?
};

if !wallet.wallet.sign(&mut psbt, SignOptions::default())? {
if !purse.wallet.sign(&mut psbt, SignOptions::default())? {
bail!("Failed to sign transaction.");
}

let tx = psbt.extract_tx();

wallet.blockchain.broadcast(&tx)?;
purse.blockchain.broadcast(&tx)?;

println!(
"Sent ordinal {} to address {}: {}",
Expand Down
4 changes: 4 additions & 0 deletions tests/find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ fn first_satoshi_spent_in_second_block() {
slots: &[(1, 0, 0)],
output_count: 1,
fee: 0,
recipient: None,
})
.blocks(1)
.expected_stdout("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0:0\n")
Expand All @@ -83,6 +84,7 @@ fn first_satoshi_spent_in_second_block_slot() {
slots: &[(0, 0, 0)],
output_count: 1,
fee: 0,
recipient: None,
})
.run();
}
Expand All @@ -98,11 +100,13 @@ fn mining_and_spending_transaction_in_same_block() {
slots: &[(0, 0, 0)],
output_count: 1,
fee: 0,
recipient: None,
})
.transaction(TransactionOptions {
slots: &[(1, 1, 0)],
output_count: 1,
fee: 0,
recipient: None,
})
.expected_stdout("1x2x0x0\n")
.run();
Expand Down
4 changes: 3 additions & 1 deletion tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ use {
wallet::{signer::SignOptions, AddressIndex, SyncOptions, Wallet},
KeychainKind,
},
bitcoin::{hash_types::Txid, network::constants::Network, Address, Block, OutPoint, Transaction},
bitcoin::{
hash_types::Txid, network::constants::Network, Address, Block, OutPoint, Script, Transaction,
},
bitcoincore_rpc::{Client, RawTx, RpcApi},
executable_path::executable_path,
log::LevelFilter,
Expand Down
13 changes: 13 additions & 0 deletions tests/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ fn split_ranges_are_tracked_correctly() {
slots: &[(1, 0, 0)],
output_count: 2,
fee: 0,
recipient: None,
})
.blocks(1)
.expected_stdout("[5000000000,7500000000)\n")
Expand All @@ -38,6 +39,7 @@ fn split_ranges_are_tracked_correctly() {
slots: &[(1, 0, 0)],
output_count: 2,
fee: 0,
recipient: None,
})
.blocks(1)
.expected_stdout("[7500000000,10000000000)\n")
Expand All @@ -53,12 +55,14 @@ fn merge_ranges_are_tracked_correctly() {
slots: &[(1, 0, 0)],
output_count: 2,
fee: 0,
recipient: None,
})
.blocks(1)
.transaction(TransactionOptions {
slots: &[(102, 1, 0), (102, 1, 1)],
output_count: 1,
fee: 0,
recipient: None,
})
.blocks(1)
.expected_stdout("[5000000000,7500000000)\n[7500000000,10000000000)\n")
Expand All @@ -74,6 +78,7 @@ fn fee_paying_transaction_range() {
slots: &[(1, 0, 0)],
output_count: 2,
fee: 10,
recipient: None,
})
.blocks(1)
.expected_stdout("[5000000000,7499999995)\n")
Expand All @@ -86,6 +91,7 @@ fn fee_paying_transaction_range() {
slots: &[(1, 0, 0)],
output_count: 2,
fee: 10,
recipient: None,
})
.blocks(1)
.expected_stdout("[7499999995,9999999990)\n")
Expand All @@ -98,6 +104,7 @@ fn fee_paying_transaction_range() {
slots: &[(1, 0, 0)],
output_count: 2,
fee: 10,
recipient: None,
})
.blocks(1)
.expected_stdout("[510000000000,515000000000)\n[9999999990,10000000000)\n")
Expand All @@ -113,11 +120,13 @@ fn two_fee_paying_transaction_range() {
slots: &[(1, 0, 0)],
output_count: 1,
fee: 10,
recipient: None,
})
.transaction(TransactionOptions {
slots: &[(2, 0, 0)],
output_count: 1,
fee: 10,
recipient: None,
})
.blocks(1)
.expected_stdout(
Expand All @@ -135,6 +144,7 @@ fn null_output() {
slots: &[(1, 0, 0)],
output_count: 1,
fee: 50 * 100_000_000,
recipient: None,
})
.blocks(1)
.expected_stdout("")
Expand All @@ -150,12 +160,14 @@ fn null_input() {
slots: &[(1, 0, 0)],
output_count: 1,
fee: 50 * 100_000_000,
recipient: None,
})
.blocks(1)
.transaction(TransactionOptions {
slots: &[(102, 1, 0)],
output_count: 1,
fee: 0,
recipient: None,
})
.expected_stdout("")
.run()
Expand All @@ -170,6 +182,7 @@ fn old_transactions_are_pruned() {
slots: &[(1, 0, 0)],
output_count: 1,
fee: 50 * 100_000_000,
recipient: None
})
.blocks(1)
.expected_stderr("error: Output spent in transaction 3dbc87de25bf5a52ddfa8038bda36e09622f4dec7951d81ac43e4b0e8c54bc5b\n")
Expand Down
4 changes: 4 additions & 0 deletions tests/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ fn spent_output_returns_200() {
slots: &[(1, 0, 0)],
output_count: 1,
fee: 0,
recipient: None,
})
.txid();

Expand All @@ -164,6 +165,7 @@ fn spent_output_returns_200() {
slots: &[(102, 1, 0)],
output_count: 1,
fee: 0,
recipient: None,
});

state.blocks(1);
Expand Down Expand Up @@ -232,6 +234,7 @@ fn block() {
slots: &[(1, 0, 0)],
output_count: 1,
fee: 0,
recipient: None,
});

let blocks = state.blocks(1);
Expand Down Expand Up @@ -267,6 +270,7 @@ fn transaction() {
slots: &[(1, 0, 0)],
output_count: 1,
fee: 0,
recipient: None,
});

state.blocks(1);
Expand Down
4 changes: 2 additions & 2 deletions tests/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,12 @@ impl State {
.unwrap()
.set_recipients(vec![
(
self
options.recipient.unwrap_or_else(|| self
.wallet
.get_address(AddressIndex::Peek(0))
.unwrap()
.address
.script_pubkey(),
.script_pubkey()),
output_value / options.output_count as u64
);
options.output_count
Expand Down
3 changes: 3 additions & 0 deletions tests/transaction_options.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use super::*;

pub(crate) struct TransactionOptions<'a> {
pub(crate) slots: &'a [(usize, usize, usize)],
pub(crate) output_count: usize,
pub(crate) fee: u64,
pub(crate) recipient: Option<Script>,
}
Loading

0 comments on commit 3e3bdc5

Please sign in to comment.