diff --git a/.gitignore b/.gitignore index 26154489a5..81289eee8b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ /target /test-times.txt /tmp +inscriptions_snapshot.csv \ No newline at end of file diff --git a/README.md b/README.md index 03d6cb6b8c..f0d62d8c81 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,37 @@ `ord` ===== +First of all, install rust and build `ord` from source (see [building section](#building)) and then start your `bitcoind` daemon. +On the examples we are going to use the `signet` settings, starting by the `bitcoin.conf` file for the bitcoin node: +```bash +# ~/.bitcoin/bitcoin.conf +rpcallowip=127.0.0.1 +txindex=1 +chain=signet +server=1 + +[signet] +rpcport=38332 +``` + +**New features:** +* Added the endpoint `/inscription_by_number/:number` + To use this endpoint start the `ord` server + ``` + sudo ./target/release/ord -s --cookie-file ~/.bitcoin/signet/.cookie --rpc-url 127.0.0.1:38332 server + ``` + +* Added the endpoint `/block_json/:height` + +* Added the snapshot cli command `ord snapshot` + To use this cli run + ``` + sudo ./target/release/ord -s --cookie-file ~/.bitcoin/signet/.cookie --rpc-url 127.0.0.1:38332 snapshot > inscriptions_snapshot.csv + ``` +* Added a set of queries cli commands `ord query ` + +`ord` +===== `ord` is an index, block explorer, and command-line wallet. It is experimental software with no warranty. See [LICENSE](LICENSE) for more details. diff --git a/src/index.rs b/src/index.rs index 45980383f3..af650c2b74 100644 --- a/src/index.rs +++ b/src/index.rs @@ -244,6 +244,21 @@ impl Index { }) } + pub(crate) fn get_inscription_ids_by_number( + &self, + ) -> Result> { + let map: HashMap = HashMap::from_iter( + self + .database + .begin_read()? + .open_table(INSCRIPTION_NUMBER_TO_INSCRIPTION_ID)? + .range(0..)? + .map(|(n, id)| (Entry::load(*id.value()), n.value())) + ); + + Ok(map) + } + pub(crate) fn get_unspent_outputs(&self, _wallet: Wallet) -> Result> { let mut utxos = BTreeMap::new(); utxos.extend( diff --git a/src/subcommand.rs b/src/subcommand.rs index cc1848b252..a6b4de1ad8 100644 --- a/src/subcommand.rs +++ b/src/subcommand.rs @@ -12,6 +12,8 @@ pub mod subsidy; pub mod supply; pub mod traits; pub mod wallet; +pub mod snapshot; +pub mod query; fn print_json(output: impl Serialize) -> Result { serde_json::to_writer_pretty(io::stdout(), &output)?; @@ -45,6 +47,10 @@ pub(crate) enum Subcommand { Traits(traits::Traits), #[clap(subcommand, about = "Wallet commands")] Wallet(wallet::Wallet), + #[clap(about = "Take a snapshot of current indexed inscriptions")] + Snapshot, + #[clap(subcommand, about = "Database query commands")] + Query(query::Query), } impl Subcommand { @@ -67,6 +73,8 @@ impl Subcommand { Self::Supply => supply::run(), Self::Traits(traits) => traits.run(), Self::Wallet(wallet) => wallet.run(options), + Self::Snapshot => snapshot::run(options), + Self::Query(query) => query.run(options), } } } diff --git a/src/subcommand/query.rs b/src/subcommand/query.rs new file mode 100644 index 0000000000..afe76a5580 --- /dev/null +++ b/src/subcommand/query.rs @@ -0,0 +1,25 @@ +use super::*; + +pub(crate) mod inscription; +pub(crate) mod sat; +pub(crate) mod block; + +#[derive(Debug, Parser)] +pub(crate) enum Query { + #[clap(about = "Display information about an inscription")] + Inscription, + #[clap(about = "Display information about a satoshi")] + Sat, + #[clap(about = "Display information about a block")] + Block(block::Block), +} + +impl Query { + pub(crate) fn run(self, options: Options) -> Result { + match self { + Self::Inscription => inscription::run(options), + Self::Sat => sat::run(options), + Self::Block(block) => block.run(options), + } + } +} \ No newline at end of file diff --git a/src/subcommand/query/block.rs b/src/subcommand/query/block.rs new file mode 100644 index 0000000000..1883d7fb79 --- /dev/null +++ b/src/subcommand/query/block.rs @@ -0,0 +1,22 @@ +use super::*; + +#[derive(Debug, Parser)] +pub(crate) struct Block { + #[clap(help = "Block number")] + pub(crate) height: u64, +} + +impl Block { + pub(crate) fn run(self, options: Options) -> Result { + let index = Index::open(&options)?; + index.update()?; + + if let Some(block) = index.get_block_by_height(self.height)?{ + print_json(block)?; + } else { + print_json({})?; + } + + Ok(()) + } +} diff --git a/src/subcommand/query/inscription.rs b/src/subcommand/query/inscription.rs new file mode 100644 index 0000000000..92610433ce --- /dev/null +++ b/src/subcommand/query/inscription.rs @@ -0,0 +1,40 @@ +use super::*; + +pub(crate) fn run(_options: Options) -> Result { + println!("[subcommand::query::inscription]: Not implemented yet."); + /* + let index = Index::open(&options)?; + index.update()?; + + + let inscription_id = index.get_inscription_id_by_inscription_number(666)?.unwrap(); + let entry = index.get_inscription_entry(inscription_id)?.unwrap(); + let inscription = index.get_inscription_by_id(inscription_id)?.unwrap(); + let satpoint = index.get_inscription_satpoint_by_id(inscription_id)?.unwrap(); + + let output = index + .get_transaction(satpoint.outpoint.txid)?.unwrap() + .output + .into_iter() + .nth(satpoint.outpoint.vout.try_into().unwrap()).unwrap(); + + let previous = if let Some(previous) = entry.number.checked_sub(1) { + Some( + index + .get_inscription_id_by_inscription_number(previous)?.unwrap() + ) + } else { + None + }; + + let next = index.get_inscription_id_by_inscription_number(entry.number + 1)?.unwrap(); + + print_json(inscription_id); + print_json(satpoint); + print_json(output); + print_json(previous); + print_json(next); + */ + + Ok(()) +} diff --git a/src/subcommand/query/sat.rs b/src/subcommand/query/sat.rs new file mode 100644 index 0000000000..6cafd89275 --- /dev/null +++ b/src/subcommand/query/sat.rs @@ -0,0 +1,7 @@ +use super::*; + +pub(crate) fn run(_options: Options) -> Result { + println!("[subcommand::query::sat]: Not implemented yet."); + + Ok(()) +} diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 09abccee39..bbf44ce415 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -19,7 +19,7 @@ use { http::{header, HeaderMap, HeaderValue, StatusCode, Uri}, response::{IntoResponse, Redirect, Response}, routing::get, - Router, TypedHeader, + Router, TypedHeader, Json }, axum_server::Handle, rust_embed::RustEmbed, @@ -148,6 +148,7 @@ impl Server { .route("/", get(Self::home)) .route("/block-count", get(Self::block_count)) .route("/block/:query", get(Self::block)) + .route("/block_json/:query", get(Self::block_json)) .route("/bounties", get(Self::bounties)) .route("/clock", get(Self::clock)) .route("/content/:inscription_id", get(Self::content)) @@ -156,6 +157,7 @@ impl Server { .route("/feed.xml", get(Self::feed)) .route("/input/:block/:transaction/:input", get(Self::input)) .route("/inscription/:inscription_id", get(Self::inscription)) + .route("/inscription_by_number/:inscription_number", get(Self::inscription_by_number)) .route("/inscriptions", get(Self::inscriptions)) .route("/inscriptions/:from", get(Self::inscriptions_from)) .route("/install.sh", get(Self::install_script)) @@ -479,6 +481,17 @@ impl Server { Redirect::to("https://raw.githubusercontent.com/casey/ord/master/install.sh") } + async fn block_json( + Extension(index): Extension>, + Path(DeserializeFromStr(height)): Path> + ) -> ServerResult> { + let block = index + .get_block_by_height(height)? + .ok_or_not_found(|| format!("block {height}"))?; + + Ok(Json(block)) + } + async fn block( Extension(page_config): Extension>, Extension(index): Extension>, @@ -943,6 +956,19 @@ impl Server { ) } + async fn inscription_by_number( + Extension(page_config): Extension>, + Extension(index): Extension>, + Path(inscription_number): Path, + ) -> ServerResult>{ + + let inscription_id = index + .get_inscription_id_by_inscription_number(inscription_number)? + .ok_or_not_found(|| format!("inscription number {inscription_number}"))?; + + Self::inscription(Extension(page_config), Extension(index), Path(inscription_id)).await + } + async fn inscriptions( Extension(page_config): Extension>, Extension(index): Extension>, diff --git a/src/subcommand/snapshot.rs b/src/subcommand/snapshot.rs new file mode 100644 index 0000000000..217baa7358 --- /dev/null +++ b/src/subcommand/snapshot.rs @@ -0,0 +1,39 @@ +use super::*; + +struct SnapshotData { + inscription_number: u64, + inscription_id: InscriptionId, + transaction_id: Txid, + satoshi_location: SatPoint, +} + +pub(crate) fn run(options: Options) -> Result { + let index = Index::open(&options)?; + index.update()?; + + let inscriptions = index.get_inscriptions(None)?; + let inscription_numbers = index.get_inscription_ids_by_number()?; + + let mut snapshot_rows: Vec = vec![]; + + for (satpoint, id) in inscriptions { + let row = SnapshotData { + inscription_number: inscription_numbers[&id], + inscription_id: id, + transaction_id: id.txid, + satoshi_location: satpoint, + }; + + snapshot_rows.push(row); + } + + snapshot_rows.sort_by(|a, b| a.inscription_number.cmp(&b.inscription_number)); + + + println!("inscription_number, inscription_id, transaction_id, satoshi_location"); + for row in snapshot_rows { + println!("{}, {}, {}, {}", row.inscription_number, row.inscription_id, row.transaction_id, row.satoshi_location); + } + + Ok(()) +}