diff --git a/.gitignore b/.gitignore index 26154489a5..b80534e689 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ /target /test-times.txt /tmp +/inscription_satpoint.txt \ No newline at end of file diff --git a/src/index.rs b/src/index.rs index 9572560be1..39c75c5a37 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1,3 +1,4 @@ +use dirs::home_dir; use { self::{ entry::{ @@ -448,6 +449,23 @@ impl Index { } pub(crate) fn update(&self) -> Result { + // verify cwd is ~ + if let Ok(current_dir) = env::current_dir() { + if let Some(home) = home_dir() { + if current_dir != home { + println!( + "Current working directory is not home ({:?}), cannot update the index!", + home + ); + panic!("Current working directory must be the home directory") + } + } else { + panic!("Failed to get the home directory") + } + } else { + panic!("Failed to get the current working directory"); + } + let mut updater = Updater::new(self)?; loop { diff --git a/src/index/updater.rs b/src/index/updater.rs index 8a3c3550f0..e99cfb190b 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -94,10 +94,21 @@ impl<'index> Updater<'_> { &mut outpoint_sender, &mut value_receiver, &mut wtx, - block, + &block, &mut value_cache, )?; + if self.height.checked_sub(1).is_some() { + log::info!( + target: "new_inscription_satpoint", + "{{\"height\":{},\"block_hash\":\"{}\",\"prev_block_hash\":\"{}\",\"tx_count\":{}}}", + &self.height - 1, + &block.header.block_hash(), + &block.header.prev_blockhash, + &block.txdata.len(), + ); + } + if let Some(progress_bar) = &mut progress_bar { progress_bar.inc(1); @@ -316,7 +327,7 @@ impl<'index> Updater<'_> { outpoint_sender: &mut Sender, value_receiver: &mut Receiver, wtx: &mut WriteTransaction, - block: BlockData, + block: &BlockData, value_cache: &mut HashMap, ) -> Result<()> { Reorg::detect_reorg(&block, self.height, self.index)?; diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 45deef074c..4eaf2639b8 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -1,3 +1,4 @@ +use serde_json::Value; use {super::*, inscription::Curse}; #[derive(Debug, Clone)] @@ -5,6 +6,8 @@ pub(super) struct Flotsam { inscription_id: InscriptionId, offset: u64, origin: Origin, + // populated if new inscription, None if transfer of existing inscription + inscription_data: Option, } #[derive(Debug, Clone)] @@ -134,6 +137,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { offset, inscription_id, origin: Origin::Old { old_satpoint }, + inscription_data: None, }); inscribed_offsets @@ -252,6 +256,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { pointer: inscription.payload.pointer(), unbound, }, + inscription_data: Some(inscription.payload.clone()), }); envelopes.next(); @@ -294,6 +299,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { pointer, unbound, }, + inscription_data, } = flotsam { Flotsam { @@ -306,6 +312,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { pointer, unbound, }, + inscription_data, } } else { flotsam @@ -433,6 +440,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { new_satpoint: SatPoint, ) -> Result { let inscription_id = flotsam.inscription_id.store(); + let new_inscription_number: i64 = 0; let unbound = match flotsam.origin { Origin::Old { old_satpoint } => { self.satpoint_to_id.remove_all(&old_satpoint.store())?; @@ -518,6 +526,72 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { self.satpoint_to_id.insert(&satpoint, &inscription_id)?; self.id_to_satpoint.insert(&inscription_id, &satpoint)?; + let inscription_id = InscriptionId::load(inscription_id); + let satpoint = SatPoint::load(satpoint); + // let inscription_entry = self.id_to_entry.get(&inscription_id.store())?.unwrap(); + // let inscription_number = InscriptionEntry::load(inscription_entry.value()).number; + let inscription_number = if new_inscription_number != 0 { + new_inscription_number + } else { + let inscription_entry = self.id_to_entry.get(&inscription_id.store())?.unwrap(); + InscriptionEntry::load(inscription_entry.value()).inscription_number + }; + + if let Some(inscription) = flotsam.inscription_data { + let is_brc_20 = Self::is_brc_20(&self, &inscription); + let content_type = inscription.content_type().unwrap_or(""); + let content_len = inscription.body().map_or(0, |body| body.len()); + + log::info!( + target: "new_inscription_satpoint", + "{},{},{},{},{},{},{}", + self.height, + satpoint, + inscription_id, + inscription_number, + content_type, + content_len, + is_brc_20, + ); + } else { + log::info!( + target: "new_inscription_satpoint", + "{},{},{},{}", + self.height, + satpoint, + inscription_id, + inscription_number, + ); + } + Ok(()) } + + fn valid_json(data: Option<&[u8]>) -> bool { + match data { + Some(bytes) => serde_json::from_slice::(bytes).is_ok(), + None => false, + } + } + + fn is_brc_20(&self, inscription: &Inscription) -> bool { + let valid_json = Self::valid_json(inscription.body()); + if valid_json { + let json_result: Result = + serde_json::from_slice(&inscription.body().unwrap()); + let json: Value = json_result.unwrap(); + let empty_json = serde_json::Map::new(); + let json_obj = json.as_object().unwrap_or(&empty_json); + if json_obj.contains_key("p") { + let p = json_obj.get("p").unwrap(); + if p.is_string() { + let p_str = p.as_str().unwrap(); + if p_str.to_lowercase() == "brc-20" { + return true; + } + } + } + } + false + } } diff --git a/src/lib.rs b/src/lib.rs index c1aca40a89..f6671fec46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,8 +59,8 @@ use { env, ffi::OsString, fmt::{self, Display, Formatter}, - fs::{self, File}, - io::{self, Cursor}, + fs::{self, File, OpenOptions}, + io::{self, Cursor, Write}, net::{TcpListener, ToSocketAddrs}, ops::{Add, AddAssign, Sub}, path::{Path, PathBuf}, @@ -175,7 +175,20 @@ fn gracefully_shutdown_indexer() { } pub fn main() { - env_logger::init(); + let inscription_satpoint_logs_file = OpenOptions::new() + .write(true) + .append(true) + .create(true) + .open("inscription_satpoint.txt") + .unwrap(); + + env_logger::builder() + .filter(Some("new_inscription_satpoint"), log::LevelFilter::Info) + .target(env_logger::Target::Pipe(Box::new( + inscription_satpoint_logs_file, + ))) + .format(|buf, record| writeln!(buf, "{}", record.args())) + .init(); ctrlc::set_handler(move || { if SHUTTING_DOWN.fetch_or(true, atomic::Ordering::Relaxed) { diff --git a/src/subcommand.rs b/src/subcommand.rs index 93e393dcea..8bd3a63fc7 100644 --- a/src/subcommand.rs +++ b/src/subcommand.rs @@ -2,6 +2,7 @@ use super::*; pub mod decode; pub mod epochs; +pub mod file; pub mod find; mod index; pub mod info; @@ -45,6 +46,8 @@ pub(crate) enum Subcommand { Traits(traits::Traits), #[command(subcommand, about = "Wallet commands")] Wallet(wallet::Wallet), + #[clap(about = "Create a file with the inscription's content")] + File(file::File), } impl Subcommand { @@ -69,6 +72,7 @@ impl Subcommand { Self::Teleburn(teleburn) => teleburn.run(), Self::Traits(traits) => traits.run(), Self::Wallet(wallet) => wallet.run(options), + Self::File(file) => file.run(options), } } } diff --git a/src/subcommand/file.rs b/src/subcommand/file.rs new file mode 100644 index 0000000000..1c0d58e270 --- /dev/null +++ b/src/subcommand/file.rs @@ -0,0 +1,37 @@ +use super::*; + +#[derive(Debug, Parser)] +pub(crate) struct File { + #[clap(long)] + pub(crate) inscription: InscriptionId, + #[clap()] + pub(crate) filename: PathBuf, +} + +impl File { + pub(crate) fn run(&self, options: Options) -> SubcommandResult { + let client = options.bitcoin_rpc_client()?; + let tx = client.get_raw_transaction(&self.inscription.txid, None)?; + let inscriptions = ParsedEnvelope::from_transaction(&tx); + + let mut filename = self.filename.clone(); + let mut file_number = 2; + + for inscription in inscriptions { + println!("Saving inscription to file {:?}", filename); + let content_bytes = inscription.payload.body().unwrap(); + let mut file = fs::File::create(self.filename.clone())?; + file.write_all(content_bytes)?; + + filename.set_file_name(format!( + "{}-{}{}", + filename.file_stem().unwrap().to_str().unwrap(), + file_number, + filename.extension().unwrap().to_str().unwrap() + )); + file_number += 1; + } + + Ok(Box::new(())) + } +} diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 9f69a6d9b8..3de54376a3 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -946,11 +946,28 @@ impl Server { ); headers.insert( header::CONTENT_SECURITY_POLICY, - HeaderValue::from_static("default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob:"), + HeaderValue::from_static("default-src 'self' https://ord.osura.com/content/ 'unsafe-eval' 'unsafe-inline' data: blob:"), ); headers.append( header::CONTENT_SECURITY_POLICY, - HeaderValue::from_static("default-src *:*/content/ *:*/blockheight *:*/blockhash *:*/blockhash/ *:*/blocktime 'unsafe-eval' 'unsafe-inline' data: blob:"), + // HeaderValue::from_static("default-src *:*/content/ *:*/blockheight *:*/blockhash *:*/blockhash/ *:*/blocktime 'unsafe-eval' 'unsafe-inline' data: blob:"), + HeaderValue::from_static("default-src https://ord.osura.com/content/ https://ord.osura.com/blockheight https://ord.osura.com/blockhash https://ord.osura.com/blockhash/ https://ord.osura.com/blocktime 'unsafe-eval' 'unsafe-inline' data: blob:"), + ); + headers.append( + header::CONTENT_SECURITY_POLICY, + HeaderValue::from_static("script-src-elem 'self' 'unsafe-eval' 'unsafe-inline' https://ord.osura.com/content/ blob:"), + ); + headers.append( + header::CONTENT_SECURITY_POLICY, + HeaderValue::from_static("style-src 'self' 'unsafe-eval' 'unsafe-hashes' 'unsafe-inline' https://ord.osura.com/content/"), + ); + headers.append( + header::CONTENT_SECURITY_POLICY, + HeaderValue::from_static("style-src-elem 'self' 'unsafe-eval' 'unsafe-hashes' 'unsafe-inline' https://ord.osura.com/content/"), + ); + headers.append( + header::CONTENT_SECURITY_POLICY, + HeaderValue::from_static("script-src 'self' 'unsafe-eval' 'unsafe-inline' https://ord.osura.com/content/ data: blob:"), ); headers.insert( header::CACHE_CONTROL, diff --git a/src/subcommand/wallet/create.rs b/src/subcommand/wallet/create.rs index 34cd762450..e230a5b4d5 100644 --- a/src/subcommand/wallet/create.rs +++ b/src/subcommand/wallet/create.rs @@ -1,8 +1,13 @@ +use bitcoin::bip32::{self, ExtendedPubKey}; +use bitcoin::secp256k1::PublicKey; + use super::*; #[derive(Serialize, Deserialize)] pub struct Output { pub mnemonic: Mnemonic, + pub address: bitcoin::Address, + pub public_key: PublicKey, pub passphrase: Option, } @@ -22,11 +27,32 @@ impl Create { rand::thread_rng().fill_bytes(&mut entropy); let mnemonic = Mnemonic::from_entropy(&entropy)?; + let seed = mnemonic.to_seed(self.passphrase.clone()); + let secp = Secp256k1::new(); + let root = bip32::ExtendedPrivKey::new_master(options.chain().network(), &seed)?; + + let coin_type = match options.chain().network() { + Network::Bitcoin => 0, + _ => 1, + }; + + let derivation_path = &DerivationPath::from_str(format!("m/86'/{}'/0'", coin_type).as_str())?; + let xprv = root.derive_priv(&secp, derivation_path)?; + let xpub = ExtendedPubKey::from_priv(&secp, &xprv); + let public_key = xpub + .derive_pub(&secp, &DerivationPath::from_str("m/0/0")?)? + .public_key; initialize_wallet(&options, mnemonic.to_seed(self.passphrase.clone()))?; + let address = options + .bitcoin_rpc_client_for_wallet_command(false)? + .get_new_address(None, Some(bitcoincore_rpc::json::AddressType::Bech32m))?; + Ok(Box::new(Output { mnemonic, + address, + public_key, passphrase: Some(self.passphrase), })) }