#![allow(
clippy::large_enum_variant,
clippy::result_large_err,
clippy::too_many_arguments,
clippy::type_complexity,
mismatched_lifetime_syntaxes
)]
#![deny(
clippy::cast_lossless,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss
)]
use {
self::{
arguments::Arguments,
blocktime::Blocktime,
decimal::Decimal,
deserialize_from_str::DeserializeFromStr,
fund_raw_transaction::fund_raw_transaction,
index::BitcoinCoreRpcResultExt,
inscriptions::{
inscription_id,
media::{self, ImageRendering, Media},
teleburn,
},
into_u64::IntoU64,
into_usize::IntoUsize,
option_ext::OptionExt,
outgoing::Outgoing,
representation::Representation,
satscard::Satscard,
settings::Settings,
signer::Signer,
subcommand::{OutputFormat, Subcommand, SubcommandResult},
tally::Tally,
},
anyhow::{Context, Error, anyhow, bail, ensure},
bip39::Mnemonic,
bitcoin::{
Amount, Block, KnownHrp, Network, OutPoint, Psbt, Script, ScriptBuf, Sequence, SignedAmount,
Transaction, TxIn, TxOut, Txid, Witness,
address::{Address, NetworkUnchecked},
blockdata::{
constants::{DIFFCHANGE_INTERVAL, MAX_SCRIPT_ELEMENT_SIZE, SUBSIDY_HALVING_INTERVAL},
locktime::absolute::LockTime,
},
consensus::{self, Decodable, Encodable},
hash_types::{BlockHash, TxMerkleNode},
hashes::Hash,
policy::MAX_STANDARD_TX_WEIGHT,
script,
secp256k1::{self, Secp256k1},
transaction::Version,
},
bitcoincore_rpc::{Client, RpcApi},
boilerplate::{Escape, Trusted},
chrono::{DateTime, TimeZone, Utc},
ciborium::Value,
clap::{ArgGroup, Parser},
error::{ResultExt, SnafuError},
ordinals::{
Artifact, Charm, Edict, Epoch, Etching, Height, Pile, Rarity, Rune, RuneId, Runestone, Sat,
SatPoint, SpacedRune, Terms, varint,
},
regex::Regex,
reqwest::{StatusCode, Url, header::HeaderMap},
serde::{Deserialize, Deserializer, Serialize},
serde_with::{DeserializeFromStr, SerializeDisplay},
snafu::{Backtrace, ErrorCompat, Snafu},
std::{
backtrace::BacktraceStatus,
borrow::Cow,
cmp,
collections::{BTreeMap, BTreeSet, HashSet},
env,
ffi::OsString,
fmt::{self, Display, Formatter},
fs::{self, File},
io::{self, BufReader, Cursor, Read},
mem,
net::{SocketAddr, ToSocketAddrs},
path::{Path, PathBuf},
process::{self, Command, Stdio},
str::FromStr,
sync::{
Arc, LazyLock, Mutex,
atomic::{self, AtomicBool},
},
thread,
time::{Duration, Instant, SystemTime},
},
sysinfo::System,
tokio::{runtime::Runtime, task},
};
pub use self::{
chain::Chain,
fee_rate::FeeRate,
index::{Index, RuneEntry},
inscriptions::{Envelope, Inscription, InscriptionId, ParsedEnvelope, RawEnvelope},
object::Object,
options::Options,
properties::{Attributes, Item, Properties, Trait, Traits},
wallet::transaction_builder::{Target, TransactionBuilder},
};
#[cfg(test)]
#[macro_use]
mod test;
#[cfg(test)]
use self::test::*;
pub mod api;
pub mod arguments;
mod blocktime;
pub mod chain;
pub mod decimal;
mod deserialize_from_str;
mod error;
mod fee_rate;
mod fund_raw_transaction;
pub mod index;
mod inscriptions;
mod into_u64;
mod into_usize;
mod macros;
mod object;
mod option_ext;
pub mod options;
pub mod outgoing;
mod properties;
mod re;
mod representation;
pub mod runes;
mod satscard;
pub mod settings;
mod signer;
pub mod subcommand;
mod tally;
pub mod templates;
pub mod wallet;
type Result<T = (), E = Error> = std::result::Result<T, E>;
type SnafuResult<T = (), E = SnafuError> = std::result::Result<T, E>;
const BROTLI: &str = "br";
const BROTLI_BUFFER_SIZE: usize = 4096;
const MAX_STANDARD_OP_RETURN_SIZE: usize = 83;
const TARGET_POSTAGE: Amount = Amount::from_sat(10_000);
static SHUTTING_DOWN: AtomicBool = AtomicBool::new(false);
static LISTENERS: Mutex<Vec<axum_server::Handle<SocketAddr>>> = Mutex::new(Vec::new());
static INDEXER: Mutex<Option<thread::JoinHandle<()>>> = Mutex::new(None);
#[doc(hidden)]
#[derive(Deserialize, Serialize)]
pub struct SimulateRawTransactionResult {
#[serde(with = "bitcoin::amount::serde::as_btc")]
pub balance_change: SignedAmount,
}
#[doc(hidden)]
#[derive(Deserialize, Serialize)]
pub struct SimulateRawTransactionOptions {
include_watchonly: bool,
}
pub fn timestamp(seconds: u64) -> DateTime<Utc> {
Utc
.timestamp_opt(seconds.try_into().unwrap_or(i64::MAX), 0)
.unwrap()
}
fn target_as_block_hash(target: bitcoin::Target) -> BlockHash {
BlockHash::from_raw_hash(Hash::from_byte_array(target.to_le_bytes()))
}
pub fn unbound_outpoint() -> OutPoint {
OutPoint {
txid: Hash::all_zeros(),
vout: 0,
}
}
fn uncheck(address: &Address) -> Address<NetworkUnchecked> {
address.to_string().parse().unwrap()
}
pub fn base64_encode(data: &[u8]) -> String {
use base64::Engine;
base64::engine::general_purpose::STANDARD.encode(data)
}
pub fn base64_decode(s: &str) -> Result<Vec<u8>> {
use base64::Engine;
Ok(base64::engine::general_purpose::STANDARD.decode(s)?)
}
fn default<T: Default>() -> T {
Default::default()
}
pub fn parse_ord_server_args(args: &str) -> (Settings, subcommand::server::Server) {
match Arguments::try_parse_from(args.split_whitespace()) {
Ok(arguments) => match arguments.subcommand {
Subcommand::Server(server) => (
Settings::merge(
arguments.options,
vec![("INTEGRATION_TEST".into(), "1".into())]
.into_iter()
.collect(),
)
.unwrap(),
server,
),
subcommand => panic!("unexpected subcommand: {subcommand:?}"),
},
Err(err) => panic!("error parsing arguments: {err}"),
}
}
pub fn cancel_shutdown() {
SHUTTING_DOWN.store(false, atomic::Ordering::Relaxed);
}
pub fn shut_down() {
SHUTTING_DOWN.store(true, atomic::Ordering::Relaxed);
}
fn gracefully_shut_down_indexer() {
if let Some(indexer) = INDEXER.lock().unwrap().take() {
shut_down();
log::info!("Waiting for index thread to finish...");
if indexer.join().is_err() {
log::warn!("Index thread panicked; join failed");
}
}
}
fn is_default<T: Default + PartialEq>(v: &T) -> bool {
*v == T::default()
}
fn unversioned_leaf_script_from_witness(witness: &Witness) -> Option<&Script> {
#[allow(deprecated)]
witness.tapscript()
}
pub fn main() {
env_logger::init();
ctrlc::set_handler(move || {
if SHUTTING_DOWN.fetch_or(true, atomic::Ordering::Relaxed) {
process::exit(1);
}
eprintln!("Shutting down gracefully. Press <CTRL-C> again to shutdown immediately.");
LISTENERS
.lock()
.unwrap()
.iter()
.for_each(|handle| handle.graceful_shutdown(Some(Duration::from_millis(100))));
gracefully_shut_down_indexer();
})
.expect("Error setting <CTRL-C> handler");
let args = Arguments::parse();
let format = args.options.format;
match args.run() {
Err(err) => {
eprintln!("error: {err}");
if let SnafuError::Anyhow { err } = err {
for (i, err) in err.chain().skip(1).enumerate() {
if i == 0 {
eprintln!();
eprintln!("because:");
}
eprintln!("- {err}");
}
if env::var_os("RUST_BACKTRACE")
.map(|val| val == "1")
.unwrap_or_default()
{
eprintln!("{}", err.backtrace());
}
} else {
for (i, err) in err.iter_chain().skip(1).enumerate() {
if i == 0 {
eprintln!();
eprintln!("because:");
}
eprintln!("- {err}");
}
if let Some(backtrace) = err.backtrace()
&& backtrace.status() == BacktraceStatus::Captured
{
eprintln!("backtrace:");
eprintln!("{backtrace}");
}
}
gracefully_shut_down_indexer();
process::exit(1);
}
Ok(output) => {
if let Some(output) = output {
output.print(format.unwrap_or_default());
}
gracefully_shut_down_indexer();
}
}
}