diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cfe7f97..d18db74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,20 @@ jobs: with: command: check args: --workspace + no-default: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - name: No features + uses: actions-rs/cargo@v1 + with: + command: check + args: --workspace --no-default-features features: runs-on: ubuntu-latest strategy: diff --git a/src/bin/rgb/command.rs b/src/bin/rgb/command.rs index d484786..65a8c8b 100644 --- a/src/bin/rgb/command.rs +++ b/src/bin/rgb/command.rs @@ -27,7 +27,7 @@ use amplify::confinement::U16; use bitcoin::bip32::ExtendedPubKey; use bitcoin::psbt::Psbt; use bp::seals::txout::{CloseMethod, ExplicitSeal, TxPtr}; -use rgb::{Runtime, RuntimeError}; +use rgb::{BlockchainResolver, Runtime, RuntimeError}; use rgbstd::containers::{Bindle, Transfer, UniversalBindle}; use rgbstd::contract::{ContractId, GenesisSeal, GraphSeal, StateType}; use rgbstd::interface::{ContractBuilder, SchemaIfaces, TypedState}; @@ -224,7 +224,11 @@ pub enum Command { } impl Command { - pub fn exec(self, runtime: &mut Runtime) -> Result<(), RuntimeError> { + pub fn exec( + self, + runtime: &mut Runtime, + resolver: &mut BlockchainResolver, + ) -> Result<(), RuntimeError> { match self { Command::Schemata => { for id in runtime.schema_ids()? { @@ -301,14 +305,10 @@ impl Command { } UniversalBindle::Contract(bindle) => { let id = bindle.id(); - let contract = - bindle - .unbindle() - .validate(runtime.resolver()) - .map_err(|c| { - c.validation_status().expect("just validated").to_string() - })?; - runtime.import_contract(contract)?; + let contract = bindle.unbindle().validate(resolver).map_err(|c| { + c.validation_status().expect("just validated").to_string() + })?; + runtime.import_contract(contract, resolver)?; eprintln!("Contract {id} imported to the stash"); } UniversalBindle::Transfer(_) => { @@ -341,7 +341,13 @@ impl Command { contract_id, iface, } => { - let wallet = wallet.map(|w| runtime.wallet(&w)).transpose()?; + let wallet = wallet + .map(|w| -> Result<_, RuntimeError> { + let mut wallet = runtime.wallet(&w)?; + wallet.update(resolver)?; + Ok(wallet) + }) + .transpose()?; let iface = runtime.iface_by_name(&tn!(iface))?.clone(); let contract = runtime.contract_iface(contract_id, iface.iface_id())?; @@ -516,10 +522,10 @@ impl Command { let contract = builder.issue_contract().expect("failure issuing contract"); let id = contract.contract_id(); let validated_contract = contract - .validate(runtime.resolver()) + .validate(resolver) .map_err(|_| RuntimeError::IncompleteContract)?; runtime - .import_contract(validated_contract) + .import_contract(validated_contract, resolver) .expect("failure importing issued contract"); eprintln!( "A new contract {id} is issued and added to the stash.\nUse `export` command \ @@ -681,7 +687,7 @@ impl Command { } Command::Validate { file } => { let bindle = Bindle::::load(file)?; - let status = match bindle.unbindle().validate(runtime.resolver()) { + let status = match bindle.unbindle().validate(resolver) { Ok(consignment) => consignment.into_validation_status(), Err(consignment) => consignment.into_validation_status(), } @@ -690,12 +696,9 @@ impl Command { } Command::Accept { force, file } => { let bindle = Bindle::::load(file)?; - let transfer = bindle - .unbindle() - .validate(runtime.resolver()) - .unwrap_or_else(|c| c); + let transfer = bindle.unbindle().validate(resolver).unwrap_or_else(|c| c); eprintln!("{}", transfer.validation_status().expect("just validated")); - runtime.accept_transfer(transfer, force)?; + runtime.accept_transfer(transfer, resolver, force)?; eprintln!("Transfer accepted into the stash"); } Command::SetHost { method, psbt_file } => { diff --git a/src/bin/rgb/main.rs b/src/bin/rgb/main.rs index 39cb96f..697987a 100644 --- a/src/bin/rgb/main.rs +++ b/src/bin/rgb/main.rs @@ -35,7 +35,7 @@ mod command; use std::process::ExitCode; use clap::Parser; -use rgb::{DefaultResolver, Runtime, RuntimeError}; +use rgb::{BlockchainResolver, DefaultResolver, Runtime, RuntimeError}; pub use crate::command::Command; pub use crate::loglevel::LogLevel; @@ -73,8 +73,9 @@ fn run() -> Result<(), RuntimeError> { .electrum .unwrap_or_else(|| opts.chain.default_resolver()); - let mut runtime = Runtime::load(opts.data_dir.clone(), opts.chain, &electrum)?; + let mut resolver = BlockchainResolver::with(&electrum)?; + let mut runtime = Runtime::load(opts.data_dir.clone(), opts.chain)?; debug!("Executing command: {}", opts.command); - opts.command.exec(&mut runtime)?; + opts.command.exec(&mut runtime, &mut resolver)?; Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index ccf3c5c..39a609f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ #[macro_use] extern crate amplify; +#[cfg(feature = "log")] #[macro_use] extern crate log; #[macro_use] @@ -36,7 +37,9 @@ pub mod prelude { pub use rgbstd::*; pub use rgbwallet::*; pub use runtime::{Runtime, RuntimeError}; - pub use wallet::{BlockchainResolver, DefaultResolver, RgbWallet}; + #[cfg(feature = "electrum")] + pub use wallet::BlockchainResolver; + pub use wallet::{DefaultResolver, RgbWallet}; pub use super::*; } diff --git a/src/runtime.rs b/src/runtime.rs index 394aa1f..9436395 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -32,11 +32,12 @@ use rgbfs::StockFs; use rgbstd::containers::{Contract, LoadError, Transfer}; use rgbstd::interface::BuilderError; use rgbstd::persistence::{Inventory, InventoryDataError, InventoryError, StashError, Stock}; +use rgbstd::resolvers::ResolveHeight; +use rgbstd::validation::ResolveTx; use rgbstd::{validation, Chain}; use strict_types::encoding::{DeserializeError, Ident, SerializeError}; use crate::descriptor::RgbDescr; -use crate::wallet::BlockchainResolver; use crate::{RgbWallet, Tapret}; #[derive(Debug, Display, Error, From)] @@ -104,8 +105,6 @@ pub struct Runtime { wallets: HashMap, #[getter(as_copy)] chain: Chain, - #[getter(skip)] - resolver: BlockchainResolver, } impl Deref for Runtime { @@ -118,15 +117,20 @@ impl DerefMut for Runtime { } impl Runtime { - pub fn load(mut data_dir: PathBuf, chain: Chain, electrum: &str) -> Result { + pub fn load(mut data_dir: PathBuf, chain: Chain) -> Result { data_dir.push(chain.to_string()); + #[cfg(feature = "log")] debug!("Using data directory '{}'", data_dir.display()); fs::create_dir_all(&data_dir)?; let mut stock_path = data_dir.clone(); stock_path.push("stock.dat"); + #[cfg(feature = "log")] debug!("Reading stock from '{}'", stock_path.display()); let stock = if !stock_path.exists() { + #[cfg(feature = "log")] + info!("Stock file not found, creating default stock"); + #[cfg(feature = "cli")] eprintln!("Stock file not found, creating default stock"); let stock = Stock::default(); stock.store(&stock_path)?; @@ -137,8 +141,12 @@ impl Runtime { let mut wallets_path = data_dir.clone(); wallets_path.push("wallets.yml"); + #[cfg(feature = "log")] debug!("Reading wallets from '{}'", wallets_path.display()); let wallets = if !wallets_path.exists() { + #[cfg(feature = "log")] + info!("Wallet file not found, creating new wallet list"); + #[cfg(feature = "cli")] eprintln!("Wallet file not found, creating new wallet list"); empty!() } else { @@ -146,22 +154,17 @@ impl Runtime { serde_yaml::from_reader(&wallets_fd)? }; - let resolver = BlockchainResolver::with(electrum)?; - Ok(Self { stock_path, wallets_path, stock, wallets, chain, - resolver, }) } pub fn unload(self) -> () {} - pub fn resolver(&mut self) -> &mut BlockchainResolver { &mut self.resolver } - pub fn create_wallet( &mut self, name: &Ident, @@ -183,35 +186,44 @@ impl Runtime { .wallets .get(name) .ok_or(RuntimeError::WalletUnknown(name.clone()))?; - RgbWallet::with(descr.clone(), &mut self.resolver).map_err(RuntimeError::from) + Ok(RgbWallet::new(descr.clone())) } - pub fn import_contract( + pub fn import_contract( &mut self, contract: Contract, - ) -> Result { + resolver: &mut R, + ) -> Result + where + R::Error: 'static, + { self.stock - .import_contract(contract, &mut self.resolver) + .import_contract(contract, resolver) .map_err(RuntimeError::from) } pub fn validate_transfer<'transfer>( &mut self, transfer: Transfer, + resolver: &mut impl ResolveTx, ) -> Result { transfer - .validate(&mut self.resolver) + .validate(resolver) .map_err(|invalid| invalid.validation_status().expect("just validated").clone()) .map_err(RuntimeError::from) } - pub fn accept_transfer( + pub fn accept_transfer( &mut self, transfer: Transfer, + resolver: &mut R, force: bool, - ) -> Result { + ) -> Result + where + R::Error: 'static, + { self.stock - .accept_transfer(transfer, &mut self.resolver, force) + .accept_transfer(transfer, resolver, force) .map_err(RuntimeError::from) } } diff --git a/src/wallet.rs b/src/wallet.rs index 8e7653d..2dcd051 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -21,10 +21,8 @@ use std::collections::{BTreeMap, BTreeSet}; -use amplify::RawArray; -use bitcoin::hashes::Hash; use bitcoin::ScriptBuf; -use bp::{Outpoint, Txid}; +use bp::Outpoint; use crate::descriptor::DeriveInfo; use crate::{RgbDescr, SpkDescriptor}; @@ -59,26 +57,33 @@ pub struct RgbWallet { } impl RgbWallet { - pub fn with(descr: RgbDescr, resolver: &mut impl Resolver) -> Result { - let mut utxos = BTreeSet::new(); + pub fn new(descr: RgbDescr) -> Self { + Self { + descr, + utxos: empty!(), + } + } + pub fn update(&mut self, resolver: &mut impl Resolver) -> Result<(), String> { const STEP: u32 = 20; - for app in [0, 1, 10, 20, 30, 40, 50, 60] { + for app in [0, 1, 9, 10] { let mut index = 0; loop { + #[cfg(feature = "log")] debug!("Requesting {STEP} scripts from the Electrum server"); - let scripts = descr.derive(app, index..(index + STEP)); + let scripts = self.descr.derive(app, index..(index + STEP)); let set = resolver.resolve_utxo(scripts)?; if set.is_empty() { break; } + #[cfg(feature = "log")] debug!("Electrum server returned {} UTXOs", set.len()); - utxos.extend(set); + self.utxos.extend(set); index += STEP; } } - Ok(Self { descr, utxos }) + Ok(()) } pub fn utxo(&self, outpoint: Outpoint) -> Option<&Utxo> { @@ -96,8 +101,8 @@ pub trait DefaultResolver { #[wrapper_mut(DerefMut)] pub struct BlockchainResolver(electrum_client::Client); +#[cfg(feature = "electrum")] impl BlockchainResolver { - #[cfg(feature = "electrum")] pub fn with(url: &str) -> Result { electrum_client::Client::new(url).map(Self) } @@ -105,8 +110,10 @@ impl BlockchainResolver { #[cfg(feature = "electrum")] mod _electrum { + use amplify::RawArray; + use bitcoin::hashes::Hash; use bitcoin::{Script, ScriptBuf}; - use bp::{Chain, LockTime, SeqNo, Tx, TxIn, TxOut, TxVer, VarIntArray, Witness}; + use bp::{Chain, LockTime, SeqNo, Tx, TxIn, TxOut, TxVer, Txid, VarIntArray, Witness}; use electrum_client::{ElectrumApi, Error, ListUnspentRes}; use rgbstd::contract::WitnessOrd; use rgbstd::resolvers::ResolveHeight;