Skip to content

Commit

Permalink
Add ord wallet list command (ordinals#601)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph authored Oct 3, 2022
1 parent 51c209c commit 0ffc62b
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 44 deletions.
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![allow(clippy::too_many_arguments)]
#![allow(clippy::too_many_arguments, clippy::type_complexity)]

use {
self::{
Expand Down
18 changes: 17 additions & 1 deletion src/options.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use {super::*, clap::ValueEnum};
use {
super::*,
bitcoincore_rpc::{Auth, Client},
clap::ValueEnum,
};

#[derive(Debug, Parser)]
pub(crate) struct Options {
Expand Down Expand Up @@ -112,6 +116,18 @@ impl Options {

Ok(path)
}

pub(crate) fn bitcoin_rpc_client(&self) -> Result<Client> {
let cookie_file = self.cookie_file()?;
let rpc_url = self.rpc_url();
log::info!(
"Connecting to Bitcoin Core RPC server at {rpc_url} using credentials from `{}`",
cookie_file.display()
);

Client::new(&rpc_url, Auth::CookieFile(cookie_file))
.context("Failed to connect to Bitcoin Core RPC at {rpc_url}")
}
}

#[cfg(test)]
Expand Down
25 changes: 24 additions & 1 deletion src/subcommand/wallet.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,39 @@
use super::*;
use {super::*, bitcoincore_rpc::RpcApi};

mod identify;
mod list;

fn list_unspent(options: Options) -> Result<Vec<(OutPoint, Vec<(u64, u64)>)>> {
let index = Index::open(&options)?;
index.index()?;

let client = options.bitcoin_rpc_client()?;

client
.list_unspent(None, None, None, None, None)?
.iter()
.map(|utxo| {
let outpoint = OutPoint::new(utxo.txid, utxo.vout);
match index.list(outpoint)? {
Some(List::Unspent(ordinal_ranges)) => Ok((outpoint, ordinal_ranges)),
Some(List::Spent) => bail!("Output {outpoint} in wallet but is spent according to index"),
None => bail!("Ordinals index has not seen {outpoint}"),
}
})
.collect()
}

#[derive(Debug, Parser)]
pub(crate) enum Wallet {
Identify,
List,
}

impl Wallet {
pub(crate) fn run(self, options: Options) -> Result<()> {
match self {
Self::Identify => identify::run(options),
Self::List => list::run(options),
}
}
}
Expand Down
51 changes: 11 additions & 40 deletions src/subcommand/wallet/identify.rs
Original file line number Diff line number Diff line change
@@ -1,45 +1,16 @@
use {
super::*,
bitcoincore_rpc::{Auth, Client, RpcApi},
};
use super::*;

pub(crate) fn run(options: Options) -> Result {
let index = Index::open(&options)?;
index.index()?;
let utxos = list_unspent(options)?;

let cookie_file = options.cookie_file()?;
let rpc_url = options.rpc_url();
log::info!(
"Connecting to Bitcoin Core RPC server at {rpc_url} using credentials from `{}`",
cookie_file.display()
);
let client = Client::new(&rpc_url, Auth::CookieFile(cookie_file))
.context("Failed to connect to Bitcoin Core RPC at {rpc_url}")?;

let unspent = client.list_unspent(None, None, None, None, None)?;

let mut utxos = Vec::new();
for utxo in unspent {
let output = OutPoint::new(utxo.txid, utxo.vout);
match index.list(output)? {
Some(List::Unspent(ordinal_ranges)) => {
utxos.push((output, ordinal_ranges));
}
Some(List::Spent) => {
bail!("Output {output} in wallet but is spent according to index")
}
None => bail!("Ordinals index has not seen {output}"),
}
}

for (ordinal, output, offset, rarity) in identify(utxos) {
println!("{ordinal}\t{output}\t{offset}\t{rarity}");
for (output, ordinal, offset, rarity) in identify(utxos) {
println!("{output}\t{ordinal}\t{offset}\t{rarity}");
}

Ok(())
}

fn identify(utxos: Vec<(OutPoint, Vec<(u64, u64)>)>) -> Vec<(Ordinal, OutPoint, u64, Rarity)> {
fn identify(utxos: Vec<(OutPoint, Vec<(u64, u64)>)>) -> Vec<(OutPoint, Ordinal, u64, Rarity)> {
utxos
.into_iter()
.flat_map(|(outpoint, ordinal_ranges)| {
Expand All @@ -50,7 +21,7 @@ fn identify(utxos: Vec<(OutPoint, Vec<(u64, u64)>)>) -> Vec<(Ordinal, OutPoint,
let start_offset = offset;
offset += end - start;
if rarity > Rarity::Common {
Some((ordinal, outpoint, start_offset, rarity))
Some((outpoint, ordinal, start_offset, rarity))
} else {
None
}
Expand Down Expand Up @@ -81,8 +52,8 @@ mod tests {
assert_eq!(
identify(utxos),
vec![(
Ordinal(50 * COIN_VALUE),
OutPoint::null(),
Ordinal(50 * COIN_VALUE),
70,
Rarity::Uncommon
)]
Expand All @@ -98,10 +69,10 @@ mod tests {
assert_eq!(
identify(utxos),
vec![
(Ordinal(0), OutPoint::null(), 0, Rarity::Mythic),
(OutPoint::null(), Ordinal(0), 0, Rarity::Mythic),
(
Ordinal(1050000000000000),
OutPoint::null(),
Ordinal(1050000000000000),
100,
Rarity::Epic
)
Expand All @@ -123,15 +94,15 @@ mod tests {
identify(utxos),
vec![
(
Ordinal(50 * COIN_VALUE),
OutPoint::null(),
Ordinal(50 * COIN_VALUE),
0,
Rarity::Uncommon
),
(
Ordinal(100 * COIN_VALUE),
OutPoint::from_str("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:5")
.unwrap(),
Ordinal(100 * COIN_VALUE),
0,
Rarity::Uncommon
)
Expand Down
73 changes: 73 additions & 0 deletions src/subcommand/wallet/list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use super::*;

pub(crate) fn run(options: Options) -> Result {
let utxos = list_unspent(options)?;

for (output, start, size, rarity, name) in list(utxos) {
println!("{output}\t{start}\t{size}\t{rarity}\t{name}");
}

Ok(())
}

fn list(utxos: Vec<(OutPoint, Vec<(u64, u64)>)>) -> Vec<(OutPoint, u64, u64, Rarity, String)> {
utxos
.into_iter()
.flat_map(|(output, ordinal_ranges)| {
ordinal_ranges.into_iter().map(move |(start, end)| {
let ordinal = Ordinal(start);
let rarity = ordinal.rarity();
let name = ordinal.name();
let size = end - start;
(output, start, size, rarity, name)
})
})
.collect()
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn list_ranges() {
let utxos = vec![
(
OutPoint::from_str("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:5")
.unwrap(),
vec![(50 * COIN_VALUE, 55 * COIN_VALUE)],
),
(
OutPoint::null(),
vec![(10, 100), (1050000000000000, 1150000000000000)],
),
];
assert_eq!(
list(utxos),
vec![
(
OutPoint::from_str("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:5")
.unwrap(),
50 * COIN_VALUE,
5 * COIN_VALUE,
Rarity::Uncommon,
"nvtcsezkbth".to_string()
),
(
OutPoint::null(),
10,
90,
Rarity::Common,
"nvtdijuwxlf".to_string()
),
(
OutPoint::null(),
1050000000000000,
100000000000000,
Rarity::Epic,
"gkjbdrhkfqf".to_string()
)
]
)
}
}
18 changes: 17 additions & 1 deletion tests/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,24 @@ fn identify() {
.rpc_server(&rpc_server)
.expected_stdout(format!(
"{}\t{}\t0\tuncommon\n",
OutPoint::new(second_coinbase, 0),
50 * COIN_VALUE,
))
.run();
}

#[test]
fn list() {
let rpc_server = test_bitcoincore_rpc::spawn();
let second_coinbase = rpc_server.mine_blocks(1)[0].txdata[0].txid();

CommandBuilder::new("wallet list")
.rpc_server(&rpc_server)
.expected_stdout(format!(
"{}\t{}\t{}\tuncommon\tnvtcsezkbth\n",
OutPoint::new(second_coinbase, 0),
50 * COIN_VALUE,
50 * COIN_VALUE,
OutPoint::new(second_coinbase, 0)
))
.run();
}

0 comments on commit 0ffc62b

Please sign in to comment.