ord/
lib.rs

1#![allow(
2  clippy::large_enum_variant,
3  clippy::result_large_err,
4  clippy::too_many_arguments,
5  clippy::type_complexity,
6  mismatched_lifetime_syntaxes
7)]
8#![deny(
9  clippy::cast_lossless,
10  clippy::cast_possible_truncation,
11  clippy::cast_possible_wrap,
12  clippy::cast_sign_loss
13)]
14
15use {
16  self::{
17    arguments::Arguments,
18    blocktime::Blocktime,
19    decimal::Decimal,
20    deserialize_from_str::DeserializeFromStr,
21    fund_raw_transaction::fund_raw_transaction,
22    index::BitcoinCoreRpcResultExt,
23    inscriptions::{
24      inscription_id,
25      media::{self, ImageRendering, Media},
26      teleburn,
27    },
28    into_u64::IntoU64,
29    into_usize::IntoUsize,
30    option_ext::OptionExt,
31    outgoing::Outgoing,
32    representation::Representation,
33    satscard::Satscard,
34    settings::Settings,
35    signer::Signer,
36    subcommand::{OutputFormat, Subcommand, SubcommandResult},
37    tally::Tally,
38  },
39  anyhow::{Context, Error, anyhow, bail, ensure},
40  bip39::Mnemonic,
41  bitcoin::{
42    Amount, Block, KnownHrp, Network, OutPoint, Psbt, Script, ScriptBuf, Sequence, SignedAmount,
43    Transaction, TxIn, TxOut, Txid, Witness,
44    address::{Address, NetworkUnchecked},
45    blockdata::{
46      constants::{DIFFCHANGE_INTERVAL, MAX_SCRIPT_ELEMENT_SIZE, SUBSIDY_HALVING_INTERVAL},
47      locktime::absolute::LockTime,
48    },
49    consensus::{self, Decodable, Encodable},
50    hash_types::{BlockHash, TxMerkleNode},
51    hashes::Hash,
52    policy::MAX_STANDARD_TX_WEIGHT,
53    script,
54    secp256k1::{self, Secp256k1},
55    transaction::Version,
56  },
57  bitcoincore_rpc::{Client, RpcApi},
58  chrono::{DateTime, TimeZone, Utc},
59  ciborium::Value,
60  clap::{ArgGroup, Parser},
61  error::{ResultExt, SnafuError},
62  html_escaper::{Escape, Trusted},
63  ordinals::{
64    Artifact, Charm, Edict, Epoch, Etching, Height, Pile, Rarity, Rune, RuneId, Runestone, Sat,
65    SatPoint, SpacedRune, Terms, varint,
66  },
67  regex::Regex,
68  reqwest::{StatusCode, Url, header::HeaderMap},
69  serde::{Deserialize, Deserializer, Serialize},
70  serde_with::{DeserializeFromStr, SerializeDisplay},
71  snafu::{Backtrace, ErrorCompat, Snafu},
72  std::{
73    backtrace::BacktraceStatus,
74    cmp,
75    collections::{BTreeMap, BTreeSet, HashSet},
76    env,
77    ffi::OsString,
78    fmt::{self, Display, Formatter},
79    fs::{self, File},
80    io::{self, BufReader, Cursor, Read},
81    mem,
82    net::ToSocketAddrs,
83    path::{Path, PathBuf},
84    process::{self, Command, Stdio},
85    str::FromStr,
86    sync::{
87      Arc, LazyLock, Mutex,
88      atomic::{self, AtomicBool},
89    },
90    thread,
91    time::{Duration, Instant, SystemTime},
92  },
93  sysinfo::System,
94  tokio::{runtime::Runtime, task},
95};
96
97pub use self::{
98  chain::Chain,
99  fee_rate::FeeRate,
100  index::{Index, RuneEntry},
101  inscriptions::{Envelope, Inscription, InscriptionId, ParsedEnvelope, RawEnvelope},
102  object::Object,
103  options::Options,
104  properties::{Attributes, Item, Properties, Trait, Traits},
105  wallet::transaction_builder::{Target, TransactionBuilder},
106};
107
108#[cfg(test)]
109#[macro_use]
110mod test;
111
112#[cfg(test)]
113use self::test::*;
114
115pub mod api;
116pub mod arguments;
117mod blocktime;
118pub mod chain;
119pub mod decimal;
120mod deserialize_from_str;
121mod error;
122mod fee_rate;
123mod fund_raw_transaction;
124pub mod index;
125mod inscriptions;
126mod into_u64;
127mod into_usize;
128mod macros;
129mod object;
130mod option_ext;
131pub mod options;
132pub mod outgoing;
133mod properties;
134mod re;
135mod representation;
136pub mod runes;
137mod satscard;
138pub mod settings;
139mod signer;
140pub mod subcommand;
141mod tally;
142pub mod templates;
143pub mod wallet;
144
145type Result<T = (), E = Error> = std::result::Result<T, E>;
146type SnafuResult<T = (), E = SnafuError> = std::result::Result<T, E>;
147
148const MAX_STANDARD_OP_RETURN_SIZE: usize = 83;
149const TARGET_POSTAGE: Amount = Amount::from_sat(10_000);
150
151static SHUTTING_DOWN: AtomicBool = AtomicBool::new(false);
152static LISTENERS: Mutex<Vec<axum_server::Handle>> = Mutex::new(Vec::new());
153static INDEXER: Mutex<Option<thread::JoinHandle<()>>> = Mutex::new(None);
154
155#[doc(hidden)]
156#[derive(Deserialize, Serialize)]
157pub struct SimulateRawTransactionResult {
158  #[serde(with = "bitcoin::amount::serde::as_btc")]
159  pub balance_change: SignedAmount,
160}
161
162#[doc(hidden)]
163#[derive(Deserialize, Serialize)]
164pub struct SimulateRawTransactionOptions {
165  include_watchonly: bool,
166}
167
168pub fn timestamp(seconds: u64) -> DateTime<Utc> {
169  Utc
170    .timestamp_opt(seconds.try_into().unwrap_or(i64::MAX), 0)
171    .unwrap()
172}
173
174fn target_as_block_hash(target: bitcoin::Target) -> BlockHash {
175  BlockHash::from_raw_hash(Hash::from_byte_array(target.to_le_bytes()))
176}
177
178pub fn unbound_outpoint() -> OutPoint {
179  OutPoint {
180    txid: Hash::all_zeros(),
181    vout: 0,
182  }
183}
184
185fn uncheck(address: &Address) -> Address<NetworkUnchecked> {
186  address.to_string().parse().unwrap()
187}
188
189pub fn base64_encode(data: &[u8]) -> String {
190  use base64::Engine;
191  base64::engine::general_purpose::STANDARD.encode(data)
192}
193
194pub fn base64_decode(s: &str) -> Result<Vec<u8>> {
195  use base64::Engine;
196  Ok(base64::engine::general_purpose::STANDARD.decode(s)?)
197}
198
199fn default<T: Default>() -> T {
200  Default::default()
201}
202
203pub fn parse_ord_server_args(args: &str) -> (Settings, subcommand::server::Server) {
204  match Arguments::try_parse_from(args.split_whitespace()) {
205    Ok(arguments) => match arguments.subcommand {
206      Subcommand::Server(server) => (
207        Settings::merge(
208          arguments.options,
209          vec![("INTEGRATION_TEST".into(), "1".into())]
210            .into_iter()
211            .collect(),
212        )
213        .unwrap(),
214        server,
215      ),
216      subcommand => panic!("unexpected subcommand: {subcommand:?}"),
217    },
218    Err(err) => panic!("error parsing arguments: {err}"),
219  }
220}
221
222pub fn cancel_shutdown() {
223  SHUTTING_DOWN.store(false, atomic::Ordering::Relaxed);
224}
225
226pub fn shut_down() {
227  SHUTTING_DOWN.store(true, atomic::Ordering::Relaxed);
228}
229
230fn gracefully_shut_down_indexer() {
231  if let Some(indexer) = INDEXER.lock().unwrap().take() {
232    shut_down();
233    log::info!("Waiting for index thread to finish...");
234    if indexer.join().is_err() {
235      log::warn!("Index thread panicked; join failed");
236    }
237  }
238}
239
240/// Nota bene: This function extracts the leaf script from a witness if the
241/// witness could represent a taproot script path spend, respecting and
242/// ignoring the taproot script annex, if present. Note that the witness may
243/// not actually be for a P2TR output, and the leaf script version is ignored.
244/// This means that this function will return scripts for any witness program
245/// version, past and present, as well as for any leaf script version.
246fn unversioned_leaf_script_from_witness(witness: &Witness) -> Option<&Script> {
247  #[allow(deprecated)]
248  witness.tapscript()
249}
250
251pub fn main() {
252  env_logger::init();
253
254  ctrlc::set_handler(move || {
255    if SHUTTING_DOWN.fetch_or(true, atomic::Ordering::Relaxed) {
256      process::exit(1);
257    }
258
259    eprintln!("Shutting down gracefully. Press <CTRL-C> again to shutdown immediately.");
260
261    LISTENERS
262      .lock()
263      .unwrap()
264      .iter()
265      .for_each(|handle| handle.graceful_shutdown(Some(Duration::from_millis(100))));
266
267    gracefully_shut_down_indexer();
268  })
269  .expect("Error setting <CTRL-C> handler");
270
271  let args = Arguments::parse();
272
273  let format = args.options.format;
274
275  match args.run() {
276    Err(err) => {
277      eprintln!("error: {err}");
278
279      if let SnafuError::Anyhow { err } = err {
280        for (i, err) in err.chain().skip(1).enumerate() {
281          if i == 0 {
282            eprintln!();
283            eprintln!("because:");
284          }
285
286          eprintln!("- {err}");
287        }
288
289        if env::var_os("RUST_BACKTRACE")
290          .map(|val| val == "1")
291          .unwrap_or_default()
292        {
293          eprintln!("{}", err.backtrace());
294        }
295      } else {
296        for (i, err) in err.iter_chain().skip(1).enumerate() {
297          if i == 0 {
298            eprintln!();
299            eprintln!("because:");
300          }
301
302          eprintln!("- {err}");
303        }
304
305        if let Some(backtrace) = err.backtrace()
306          && backtrace.status() == BacktraceStatus::Captured
307        {
308          eprintln!("backtrace:");
309          eprintln!("{backtrace}");
310        }
311      }
312
313      gracefully_shut_down_indexer();
314
315      process::exit(1);
316    }
317    Ok(output) => {
318      if let Some(output) = output {
319        output.print(format.unwrap_or_default());
320      }
321      gracefully_shut_down_indexer();
322    }
323  }
324}