diff --git a/zebra-crosslink/wallet/Cargo.toml b/zebra-crosslink/wallet/Cargo.toml index 0a747d88..1e9b7587 100644 --- a/zebra-crosslink/wallet/Cargo.toml +++ b/zebra-crosslink/wallet/Cargo.toml @@ -8,11 +8,21 @@ secp256k1 = { workspace = true } bip39 = "2.2.0" zcash_primitives = { workspace = true } # version = "0.26.1" -zcash_client_backend = { path = "../../librustzcash/zcash_client_backend", features = ["lightwalletd-tonic"] } # version = "0.21.0" -zcash_client_memory = { path = "../../librustzcash/zcash_client_memory", features = ["orchard", "transparent-inputs"] } -zcash_client_sqlite = { path = "../../librustzcash/zcash_client_sqlite", features = ["orchard", "transparent-inputs"] } # version = "0.19.0" -zcash_keys = { workspace = true, features=["transparent-inputs"] } -zcash_protocol = { workspace = true } # version = "0.7.1" +zcash_client_backend = { path = "../../librustzcash/zcash_client_backend", features = [ + "lightwalletd-tonic", +] } # version = "0.21.0" +zcash_client_memory = { path = "../../librustzcash/zcash_client_memory", features = [ + "orchard", + "transparent-inputs", +] } +zcash_client_sqlite = { path = "../../librustzcash/zcash_client_sqlite", features = [ + "orchard", + "transparent-inputs", +] } # version = "0.19.0" +zcash_keys = { workspace = true, features = ["transparent-inputs"] } +zcash_protocol = { workspace = true, features = [ + "local-consensus", +] } # version = "0.7.1" zcash_transparent = { workspace = true } # version = "0.6.1" zcash_note_encryption = { workspace = true } # version = "0.4.1" zcash_proofs = { workspace = true, features = ["bundled-prover"] } @@ -29,13 +39,27 @@ rand_chacha = "0.3.1" rusqlite = { version = "0.37", features = ["bundled"] } prost = "0.14.1" -tonic = { workspace = true, features = ["transport", "channel", "tls-ring", "tls-webpki-roots"] } +tonic = { workspace = true, features = [ + "transport", + "channel", + "tls-ring", + "tls-webpki-roots", +] } tonic-build = { workspace = true, default-features = false } rustls = { version = "0.23.31", default-features = false, features = ["ring"] } -tokio = { workspace = true, features = ["time", "rt-multi-thread", "macros", "tracing", "signal"] } +tokio = { workspace = true, features = [ + "time", + "rt-multi-thread", + "macros", + "tracing", + "signal", +] } tokio-rustls = { version = "0.26", default-features = false } tokio-stream = { workspace = true, features = ["time"] } -reqwest = { version = "0.12", default-features = false, features = ["cookies", "rustls-tls"] } +reqwest = { version = "0.12", default-features = false, features = [ + "cookies", + "rustls-tls", +] } secrecy = "0.8.0" sapling-crypto = "0.5.0" diff --git a/zebra-crosslink/wallet/src/lib.rs b/zebra-crosslink/wallet/src/lib.rs index 9441d199..9af6ae41 100644 --- a/zebra-crosslink/wallet/src/lib.rs +++ b/zebra-crosslink/wallet/src/lib.rs @@ -1,13 +1,12 @@ //! Internal wallet #![allow(warnings)] +use orchard::note_encryption::{CompactAction as OrchardCompactAction, OrchardDomain}; use rand::seq::SliceRandom; -use zcash_client_backend::data_api::WalletCommitmentTrees; -use orchard::note_encryption::{ CompactAction as OrchardCompactAction, OrchardDomain }; +use rand::{thread_rng, Rng}; use rand_chacha::rand_core::SeedableRng; use rand_core::OsRng; -use rand::{Rng, thread_rng}; -use secrecy::{ExposeSecret,SecretVec,Secret}; +use secrecy::{ExposeSecret, Secret, SecretVec}; use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; use std::convert::{identity, Infallible}; use std::future::Future; @@ -19,26 +18,34 @@ use tonic::client::GrpcService; use tonic::transport::{Certificate, Channel, ClientTlsConfig, Endpoint}; use tonic::IntoRequest; use zcash_client_backend::data_api::chain::{BlockCache, CommitmentTreeRoot}; -use zcash_client_backend::data_api::wallet::{ConfirmationsPolicy, TargetHeight, create_proposed_transactions, propose_shielding, shield_transparent_funds}; -use zcash_client_backend::proto::service::{GetSubtreeRootsArg, RawTransaction, TreeState, TxFilter}; +use zcash_client_backend::data_api::wallet::{ + create_proposed_transactions, propose_shielding, shield_transparent_funds, ConfirmationsPolicy, + TargetHeight, +}; +use zcash_client_backend::data_api::WalletCommitmentTrees; +use zcash_client_backend::proto::service::{ + GetSubtreeRootsArg, RawTransaction, TreeState, TxFilter, +}; use zcash_client_backend::wallet::WalletTransparentOutput; use zcash_client_sqlite::error::SqliteClientError; use zcash_client_sqlite::util::SystemClock; use zcash_client_sqlite::{AccountUuid, WalletDb}; -use zcash_note_encryption::{try_compact_note_decryption, try_note_decryption, try_output_recovery_with_ovk, ShieldedOutput}; +use zcash_note_encryption::{ + try_compact_note_decryption, try_note_decryption, try_output_recovery_with_ovk, ShieldedOutput, +}; use zcash_primitives::transaction::builder::{BuildConfig, Builder as TxBuilder}; use zcash_primitives::transaction::components::TxOut; -use zcash_primitives::transaction::fees::{ - self, - FeeRule, - zip317, -}; +use zcash_primitives::transaction::fees::{self, zip317, FeeRule}; use zcash_primitives::transaction::sighash::{signature_hash, SignableInput}; use zcash_primitives::transaction::txid::TxIdDigester; -use zcash_primitives::transaction::{Authorized, StakingAction_CreateNewDelegationBond, Transaction, TransactionData, TxVersion, Unauthorized}; -use zcash_primitives::transaction::{RosterMember, StakingAction, StakingActionKind, StakeTxId}; +use zcash_primitives::transaction::{ + Authorized, StakingAction_CreateNewDelegationBond, Transaction, TransactionData, TxVersion, + Unauthorized, +}; +use zcash_primitives::transaction::{RosterMember, StakeTxId, StakingAction, StakingActionKind}; use zcash_proofs::prover::LocalTxProver; use zcash_protocol::consensus::{BlockHeight as LRZBlockHeight, BranchId}; +use zcash_protocol::local_consensus::LocalNetwork; use zcash_protocol::memo::MemoBytes; use zcash_protocol::value::{ZatBalance, Zatoshis}; use zcash_protocol::{PoolType, ShieldedProtocol, TxId}; @@ -46,7 +53,7 @@ use zcash_transparent::{ address::TransparentAddress, builder::{TransparentBuilder, TransparentSigningSet}, bundle::OutPoint, - keys::{IncomingViewingKey, TransparentKeyScope, NonHardenedChildIndex}, + keys::{IncomingViewingKey, NonHardenedChildIndex, TransparentKeyScope}, }; use zebra_chain::block::{Hash as BlockHash, Height, ZCASH_BLOCK_VERSION}; use zebra_chain::parameters::NetworkUpgrade; @@ -71,16 +78,18 @@ use zcash_client_backend::{ self, chain::{error::Error as ChainError, scan_cached_blocks, ChainState}, scanning::{ScanPriority, ScanRange}, - Balance, - wallet, Account as APIAccount, AccountBirthday, AccountPurpose, WalletRead, WalletWrite, - Zip32Derivation, + wallet, Account as APIAccount, AccountBirthday, AccountPurpose, Balance, WalletRead, + WalletWrite, Zip32Derivation, }, encoding::AddressCodec, keys::{ UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedIncomingViewingKey, UnifiedSpendingKey, }, proto::{ - compact_formats::{CompactBlock, CompactTx, CompactSaplingSpend, CompactSaplingOutput, CompactOrchardAction}, + compact_formats::{ + CompactBlock, CompactOrchardAction, CompactSaplingOutput, CompactSaplingSpend, + CompactTx, + }, service::{ compact_tx_streamer_client::CompactTxStreamerClient, BlockId, BlockRange, ChainSpec, Empty, GetAddressUtxosArg, LightdInfo, TransparentAddressBlockFilter, @@ -98,10 +107,10 @@ impl BlockHeight { // NOTE: these constants corresponds to the semantic that the lower-down it is, // the more sure we are about its continued existence pub const INVALID: Self = Self(u32::MAX); // NOTE: here for headroom for +1 to fake <= using < - // ALT: maybe better to go the other way round: use <= and saturating_sub(1) - // TODO: creating (slow), sending, sent, mempool - pub const INTERNAL: Self = Self(u32::MAX-1); - pub const MEMPOOL: Self = Self(u32::MAX-2); + // ALT: maybe better to go the other way round: use <= and saturating_sub(1) + // TODO: creating (slow), sending, sent, mempool + pub const INTERNAL: Self = Self(u32::MAX - 1); + pub const MEMPOOL: Self = Self(u32::MAX - 2); pub fn is_in_block(&self) -> bool { self.0 < Self::MEMPOOL.0 @@ -134,7 +143,7 @@ impl std::fmt::Display for BlockHeight { Self::INVALID => write!(f, ""), Self::INTERNAL => write!(f, ""), Self::MEMPOOL => write!(f, ""), - _ => self.0.fmt(f) + _ => self.0.fmt(f), } } } @@ -145,7 +154,7 @@ impl std::fmt::Display for LESlice<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let n = usize::min(self.0.len(), f.precision().unwrap_or(self.0.len())); for i in 0..n { - write!(f, "{:02x}", self.0[31-i])?; + write!(f, "{:02x}", self.0[31 - i])?; } Ok(()) } @@ -194,7 +203,9 @@ impl std::fmt::Debug for NL<'_, T> { // NOTE: only a function because from() isn't const // This is u64::MAX in large part to operate correctly on anchor comparison // ALT: Option -fn unknown_tree_position() -> incrementalmerkletree::Position { incrementalmerkletree::Position::from(u64::MAX) } +fn unknown_tree_position() -> incrementalmerkletree::Position { + incrementalmerkletree::Position::from(u64::MAX) +} // ALT: collapse into Txo with internal enum(s) #[derive(Clone, Copy, PartialEq)] @@ -212,21 +223,27 @@ struct OrchardNote { } impl std::fmt::Debug for OrchardNote { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "OrchardNote{{ recv:{:?}, spent:{:?}, txid:{}, nf:{:?}, value:{}, pos:{}}}", - self.recv_h, self.spent_h, self.txid, self.nf, self.note.value().inner(), - u64::from(self.position) - // u64::from(self.witness.witnessed_position()), self.witness.root() - ) + write!( + f, + "OrchardNote{{ recv:{:?}, spent:{:?}, txid:{}, nf:{:?}, value:{}, pos:{}}}", + self.recv_h, + self.spent_h, + self.txid, + self.nf, + self.note.value().inner(), + u64::from(self.position) // u64::from(self.witness.witnessed_position()), self.witness.root() + ) } } impl OrchardNote { fn monotonically_update(&mut self, mut new_note: OrchardNote) { if new_note.position < unknown_tree_position() { - if (self.position < unknown_tree_position() && - self.position != new_note.position) - { - println!("ERROR: orchard note has 2 different valid positions: {:?} vs {:?}", self.position, new_note.position); + if (self.position < unknown_tree_position() && self.position != new_note.position) { + println!( + "ERROR: orchard note has 2 different valid positions: {:?} vs {:?}", + self.position, new_note.position + ); } self.position = new_note.position; } else { @@ -239,7 +256,6 @@ impl OrchardNote { } } - const CHEAT_UNSTAKING: bool = false; pub static AM_I_THE_UNSTAKER: Mutex = Mutex::new(false); @@ -271,17 +287,20 @@ async fn wait_for_zainod() { } } - interval.tick().await; } } -struct Timer<'a> { t_bgn: std::time::Instant, name: &'a str } +struct Timer<'a> { + t_bgn: std::time::Instant, + name: &'a str, +} impl<'a> Timer<'a> { pub fn scope(name: &'a str) -> Self { println!("started {}", name); Self { - name, t_bgn: std::time::Instant::now() + name, + t_bgn: std::time::Instant::now(), } } } @@ -291,8 +310,14 @@ impl Drop for Timer<'_> { } } - -fn block_policy_10() -> ConfirmationsPolicy { ConfirmationsPolicy::new(std::num::NonZeroU32::new(5).unwrap(), std::num::NonZeroU32::new(5).unwrap(), false).unwrap() } +fn block_policy_10() -> ConfirmationsPolicy { + ConfirmationsPolicy::new( + std::num::NonZeroU32::new(5).unwrap(), + std::num::NonZeroU32::new(5).unwrap(), + false, + ) + .unwrap() +} #[derive(Debug, Clone, PartialEq)] enum WalletAction { @@ -349,22 +374,22 @@ impl WalletTxPart { pub fn checked_add(&self, rhs: &WalletTxPart) -> Option { Some(WalletTxPart { spent_note_count: (self.spent_note_count + rhs.spent_note_count), - sent_note_count: (self.sent_note_count + rhs.sent_note_count), - recv_note_count: (self.recv_note_count + rhs.recv_note_count), - spent_zats: (self.spent_zats + rhs.spent_zats)?, - sent_zats: (self.sent_zats + rhs.sent_zats)?, - recv_zats: (self.recv_zats + rhs.recv_zats)?, + sent_note_count: (self.sent_note_count + rhs.sent_note_count), + recv_note_count: (self.recv_note_count + rhs.recv_note_count), + spent_zats: (self.spent_zats + rhs.spent_zats)?, + sent_zats: (self.sent_zats + rhs.sent_zats)?, + recv_zats: (self.recv_zats + rhs.recv_zats)?, }) } pub fn unchecked_add(&self, rhs: &WalletTxPart) -> WalletTxPart { WalletTxPart { spent_note_count: (self.spent_note_count + rhs.spent_note_count), - sent_note_count: (self.sent_note_count + rhs.sent_note_count), - recv_note_count: (self.recv_note_count + rhs.recv_note_count), - spent_zats: (self.spent_zats + rhs.spent_zats).expect("already checked"), - sent_zats: (self.sent_zats + rhs.sent_zats).expect("already checked"), - recv_zats: (self.recv_zats + rhs.recv_zats).expect("already checked"), + sent_note_count: (self.sent_note_count + rhs.sent_note_count), + recv_note_count: (self.recv_note_count + rhs.recv_note_count), + spent_zats: (self.spent_zats + rhs.spent_zats).expect("already checked"), + sent_zats: (self.sent_zats + rhs.sent_zats).expect("already checked"), + recv_zats: (self.recv_zats + rhs.recv_zats).expect("already checked"), } } } @@ -372,16 +397,18 @@ impl WalletTxPart { type TxPartFlags = u8; struct TxParts(pub TxPartFlags); impl TxParts { - const NONE: TxPartFlags = 0; - const TRANSPARENT: TxPartFlags = 1 << WalletTxPart::TRANSPARENT; + const NONE: TxPartFlags = 0; + const TRANSPARENT: TxPartFlags = 1 << WalletTxPart::TRANSPARENT; const SHIELDED_RECV: TxPartFlags = 1 << WalletTxPart::SHIELDED; const SHIELDED_SENT: TxPartFlags = 1 << 2; - const MEMO: TxPartFlags = 1 << 3; + const MEMO: TxPartFlags = 1 << 3; const STAKING_ACTION: TxPartFlags = 1 << 4; - const FULL_TX: TxPartFlags = ( - Self::TRANSPARENT | Self::SHIELDED_RECV | Self::SHIELDED_SENT | Self::MEMO | Self::STAKING_ACTION - ); + const FULL_TX: TxPartFlags = (Self::TRANSPARENT + | Self::SHIELDED_RECV + | Self::SHIELDED_SENT + | Self::MEMO + | Self::STAKING_ACTION); } // NOTE: trying to not store data that can be computed directly @@ -408,41 +435,54 @@ pub struct WalletTx { } impl WalletTx { - pub fn with_fake_data(kind: WalletTxKind, sent: u64, recv: u64, shielding: bool, memo: &str, mined_h: u32) -> Self { + pub fn with_fake_data( + kind: WalletTxKind, + sent: u64, + recv: u64, + shielding: bool, + memo: &str, + mined_h: u32, + ) -> Self { let mut memo_as_bytes = [0u8; 512]; &memo_as_bytes[0..memo.len()].copy_from_slice(memo.as_bytes()); Self { - account_id: 0,//AccountUuid::default(), - txid: TxId::from_bytes([0; 32]), + account_id: 0, //AccountUuid::default(), + txid: TxId::from_bytes([0; 32]), expiry_h: None, - mined_h: if mined_h != 0 { (BlockHeight(mined_h)) } else { BlockHeight::MEMPOOL }, + mined_h: if mined_h != 0 { + (BlockHeight(mined_h)) + } else { + BlockHeight::MEMPOOL + }, part_flags: TxParts::FULL_TX, parts: [ - WalletTxPart { // Transparent + WalletTxPart { + // Transparent spent_note_count: (sent > 0 && shielding) as usize, - sent_note_count: (sent > 0 && shielding) as usize, - recv_note_count: 0, + sent_note_count: (sent > 0 && shielding) as usize, + recv_note_count: 0, spent_zats: Zatoshis::from_u64(sent * shielding as u64).unwrap(), - sent_zats: Zatoshis::from_u64(sent * shielding as u64).unwrap(), - recv_zats: Zatoshis::ZERO, + sent_zats: Zatoshis::from_u64(sent * shielding as u64).unwrap(), + recv_zats: Zatoshis::ZERO, }, - WalletTxPart { // Shielded + WalletTxPart { + // Shielded spent_note_count: (sent > 0 && !shielding) as usize, - sent_note_count: (sent > 0 && !shielding) as usize, - recv_note_count: (recv > 0) as usize, + sent_note_count: (sent > 0 && !shielding) as usize, + recv_note_count: (recv > 0) as usize, spent_zats: Zatoshis::from_u64(sent * !shielding as u64).unwrap(), - sent_zats: Zatoshis::from_u64(sent * !shielding as u64).unwrap(), + sent_zats: Zatoshis::from_u64(sent * !shielding as u64).unwrap(), recv_zats: Zatoshis::from_u64(if shielding { sent } else { recv }).unwrap(), }, ], memo_count: if memo.len() != 0 { 1 } else { 0 }, - memo: memo_as_bytes, + memo: memo_as_bytes, is_coinbase: false, is_outside_bc: false, staking_action: None, + } } -} pub fn totals(&self) -> WalletTxPart { self.parts[0].unchecked_add(&self.parts[1]) @@ -451,7 +491,8 @@ impl WalletTx { pub fn account_value_delta(&self) -> ZatBalance { let all = self.totals(); // NOTE: into_i64 isn't pub... - ZatBalance::from_i64(all.recv_zats.into_u64() as i64 - all.spent_zats.into_u64() as i64).expect("checked before") + ZatBalance::from_i64(all.recv_zats.into_u64() as i64 - all.spent_zats.into_u64() as i64) + .expect("checked before") } // TODO: @@ -474,7 +515,7 @@ impl WalletTx { // ALT: only consider it change if a single note is received (per pool?) let is_self_send = all.sent_zats == all.recv_zats; if is_self_send { - let all_spent_is_t = self.parts[0].spent_zats == all.spent_zats; + let all_spent_is_t = self.parts[0].spent_zats == all.spent_zats; let all_recv_is_shield = self.parts[1].recv_zats == all.recv_zats; if all_spent_is_t && all_recv_is_shield && all.recv_zats > Zatoshis::ZERO { WalletTxKind::Shield @@ -488,24 +529,24 @@ impl WalletTx { } } -// pub fn loc(&self, finalized_h: BlockHeight, bc_tip_h: BlockHeight) -> (WalletTxLoc, u32, bool/*finalized*/, bool/*outside_bc*/) { -// match self.mined_h { -// BlockHeight::MEMPOOL => (WalletTxLoc::Mempool, falsself.is_outside_bc), -// BlockHeight::INTERNAL => (WalletTxLoc::Internal, falsself.is_outside_bc), -// _ => { -// if self.is_outside_bc { -// (WalletTxLoc::Block(0), self.is_outside_bc) -// } else if self.mined_h > bc_tip_h { -// println!("ERROR: mined h on best chain ({}) higher than tip ({})", self.mined_h, bc_tip_h); -// return (WalletTxLoc::Block(0), true); -// } else if self.mined_h <= finalized_h { -// (WalletTxLoc::Finalized, self.is_outside_bc) -// } else { -// (WalletTxLoc::Block(bc_tip_h - self.mined_h), self.is_outside_bc) -// } -// } -// } -// } + // pub fn loc(&self, finalized_h: BlockHeight, bc_tip_h: BlockHeight) -> (WalletTxLoc, u32, bool/*finalized*/, bool/*outside_bc*/) { + // match self.mined_h { + // BlockHeight::MEMPOOL => (WalletTxLoc::Mempool, falsself.is_outside_bc), + // BlockHeight::INTERNAL => (WalletTxLoc::Internal, falsself.is_outside_bc), + // _ => { + // if self.is_outside_bc { + // (WalletTxLoc::Block(0), self.is_outside_bc) + // } else if self.mined_h > bc_tip_h { + // println!("ERROR: mined h on best chain ({}) higher than tip ({})", self.mined_h, bc_tip_h); + // return (WalletTxLoc::Block(0), true); + // } else if self.mined_h <= finalized_h { + // (WalletTxLoc::Finalized, self.is_outside_bc) + // } else { + // (WalletTxLoc::Block(bc_tip_h - self.mined_h), self.is_outside_bc) + // } + // } + // } + // } } // @note(judah): needed so the visualizer doesn't take a dependency on zcash_primitives @@ -527,14 +568,19 @@ fn w_flip(use_i: &mut usize, update_i: &mut usize) { #[derive(Default, Debug, Clone)] pub struct WalletState { - pub balance: i64, // in zats + pub balance: i64, // in zats pub pending_balance: i64, // in zats - pub staked_balance: i64, // in zats + pub staked_balance: i64, // in zats pub show_staked_balance: bool, - pub txs: Vec, - pub roster: Vec, - pub staked_roster: Vec<([u8; 32] /* pub key */, [u8; 32] /* txid */, u64 /* initial */, u64 /* accumulated */)>, + pub txs: Vec, + pub roster: Vec, + pub staked_roster: Vec<( + [u8; 32], /* pub key */ + [u8; 32], /* txid */ + u64, /* initial */ + u64, /* accumulated */ + )>, pub waiting_for_faucet: bool, pub waiting_for_stake_to_finalizer: bool, @@ -561,45 +607,108 @@ impl WalletState { pub fn request_from_faucet(&mut self) { self.waiting_for_faucet = true; - if self.actions_in_flight.iter().filter(|a| match a { WalletAction::RequestFromFaucet => true, _ => false }).count() != 0 { + if self + .actions_in_flight + .iter() + .filter(|a| match a { + WalletAction::RequestFromFaucet => true, + _ => false, + }) + .count() + != 0 + { return; } - self.actions_in_flight.push_back(WalletAction::RequestFromFaucet); + self.actions_in_flight + .push_back(WalletAction::RequestFromFaucet); } pub fn stake_to_finalizer(&mut self, amount: u64, target_finalizer: [u8; 32]) { - if self.actions_in_flight.iter().filter(|a| match a { WalletAction::StakeToFinalizer(_,_) => true, _ => false }).count() != 0 { + if self + .actions_in_flight + .iter() + .filter(|a| match a { + WalletAction::StakeToFinalizer(_, _) => true, + _ => false, + }) + .count() + != 0 + { return; } self.waiting_for_stake_to_finalizer = true; - self.actions_in_flight.push_back(WalletAction::StakeToFinalizer(Zatoshis::from_u64(amount).expect("Invalid amount given to stake_to_finalizer"), target_finalizer)); + self.actions_in_flight + .push_back(WalletAction::StakeToFinalizer( + Zatoshis::from_u64(amount).expect("Invalid amount given to stake_to_finalizer"), + target_finalizer, + )); } pub fn unstake_from_finalizer(&mut self, txid: [u8; 32]) { let txid = TxId::from_bytes(txid); - if self.actions_in_flight.iter().filter(|a| match a { WalletAction::UnstakeFromFinalizer(id) if id.eq(&txid) => true, _ => false }).count() != 0 { + println!( + "===== UNSTAKE_FROM_FINALIZER with txid {}", + txid.to_string() + ); + if self + .actions_in_flight + .iter() + .filter(|a| match a { + WalletAction::UnstakeFromFinalizer(id) if id.eq(&txid) => true, + _ => false, + }) + .count() + != 0 + { return; } - self.actions_in_flight.push_back(WalletAction::UnstakeFromFinalizer(txid)); + self.actions_in_flight + .push_back(WalletAction::UnstakeFromFinalizer(txid)); } pub fn send_to_address(&mut self, address: String, amount: u64) { - let Ok(address) = UnifiedAddress::decode(&TEST_NETWORK /* @todo */, &address) else { + let Ok(address) = UnifiedAddress::decode( + &LocalNetwork { + overwinter: Some(zcash_protocol::consensus::BlockHeight::from_u32(1)), + sapling: Some(zcash_protocol::consensus::BlockHeight::from_u32(1)), + blossom: Some(zcash_protocol::consensus::BlockHeight::from_u32(1)), + heartwood: Some(zcash_protocol::consensus::BlockHeight::from_u32(1)), + canopy: Some(zcash_protocol::consensus::BlockHeight::from_u32(1)), + nu5: Some(zcash_protocol::consensus::BlockHeight::from_u32(1)), + nu6: Some(zcash_protocol::consensus::BlockHeight::from_u32(1)), + nu6_1: None, + }, /* @todo */ + &address, + ) else { println!("Invalid address for send: {}", address); return; }; - if self.actions_in_flight.iter().filter(|a| match a { - WalletAction::SendToAddress(addr, amt) if amt.into_u64() == amount && addr.eq(&address) => true, - _ => false - }).count() != 0 { + if self + .actions_in_flight + .iter() + .filter(|a| match a { + WalletAction::SendToAddress(addr, amt) + if amt.into_u64() == amount && addr.eq(&address) => + { + true + } + _ => false, + }) + .count() + != 0 + { return; } self.waiting_for_send = true; - self.actions_in_flight.push_back(WalletAction::SendToAddress(address, Zatoshis::from_u64(amount).expect("Invalid amount given to stake_to_finalizer"))); + self.actions_in_flight + .push_back(WalletAction::SendToAddress( + address, + Zatoshis::from_u64(amount).expect("Invalid amount given to stake_to_finalizer"), + )); } } @@ -622,7 +731,7 @@ enum TxOutput { dst: orchard::Address, zats: u64, memo: MemoBytes, - } + }, } struct TxOptions<'a> { @@ -644,7 +753,7 @@ impl<'a> Default for TxOptions<'a> { } } -pub static wallet_main_zaino_port : Mutex = Mutex::new(0); +pub static wallet_main_zaino_port: Mutex = Mutex::new(0); pub enum TxPool<'a> { Transparent, @@ -654,24 +763,39 @@ pub enum TxPool<'a> { Orchard(&'a OrchardShardTree), } -fn transparent_keys_from_usk(usk: &UnifiedSpendingKey, index: u32) -> Option<(secp256k1::PublicKey, secp256k1::SecretKey)> { +fn transparent_keys_from_usk( + usk: &UnifiedSpendingKey, + index: u32, +) -> Option<(secp256k1::PublicKey, secp256k1::SecretKey)> { let transparent = usk.transparent(); let account_pubkey = transparent.to_account_pubkey(); let child_index = NonHardenedChildIndex::const_from_index(index); - let address_pubkey = account_pubkey.derive_address_pubkey(TransparentKeyScope::EXTERNAL, child_index).ok()?; + let address_pubkey = account_pubkey + .derive_address_pubkey(TransparentKeyScope::EXTERNAL, child_index) + .ok()?; let address_privkey = transparent.derive_external_secret_key(child_index).ok()?; Some((address_pubkey, address_privkey)) } -fn addrs_from_account(account: &ManualAccount, index: u32) -> Option<(TransparentAddress, UnifiedAddress)> { +fn addrs_from_account( + account: &ManualAccount, + index: u32, +) -> Option<(TransparentAddress, UnifiedAddress)> { // NOTE: the wallet auto-increments the child index so this isn't recognized let ufvk = &account.ufvk; - let (ua, di_) = ufvk.find_address(orchard::keys::DiversifierIndex::new(), UnifiedAddressRequest::ORCHARD).ok()?; + let (ua, di_) = ufvk + .find_address( + orchard::keys::DiversifierIndex::new(), + UnifiedAddressRequest::ORCHARD, + ) + .ok()?; let account_pubkey = ufvk.transparent()?; let child_index = NonHardenedChildIndex::const_from_index(index); - let address_pubkey = account_pubkey.derive_address_pubkey(TransparentKeyScope::EXTERNAL, child_index).ok()?; + let address_pubkey = account_pubkey + .derive_address_pubkey(TransparentKeyScope::EXTERNAL, child_index) + .ok()?; Some((TransparentAddress::from_pubkey(&address_pubkey), ua)) - // Some(account.default_address().ok()??.0) + // Some(account.default_address().ok()??.0) } fn update_insert_i(txs: &[WalletTx], insert_i: &mut usize, block_h: BlockHeight) { @@ -680,12 +804,17 @@ fn update_insert_i(txs: &[WalletTx], insert_i: &mut usize, block_h: BlockHeight) *insert_i += txs[*insert_i..].partition_point(|tx| tx.mined_h <= block_h); } -fn update_with_tx(wallet: &mut ManualWallet, txid: TxId, mut new_tx: WalletTx, insert_i: &mut usize) { +fn update_with_tx( + wallet: &mut ManualWallet, + txid: TxId, + mut new_tx: WalletTx, + insert_i: &mut usize, +) { // find if there's an existing height/transaction for this txid let new_totals = new_tx.totals(); - if (new_totals.spent_note_count == 0 && - new_totals.recv_note_count == 0 && - new_totals.sent_note_count == 0) + if (new_totals.spent_note_count == 0 + && new_totals.recv_note_count == 0 + && new_totals.sent_note_count == 0) { // not our transaction; ignore return; @@ -695,32 +824,45 @@ fn update_with_tx(wallet: &mut ManualWallet, txid: TxId, mut new_tx: WalletTx, i if let Some(tx_i) = tx_mined_h_position(&wallet.txs, *tx_h, &txid) { let old_tx = &wallet.txs[tx_i]; if old_tx != &new_tx { - if new_tx.mined_h == BlockHeight::MEMPOOL && old_tx.mined_h.is_in_block() && !old_tx.is_outside_bc { + if new_tx.mined_h == BlockHeight::MEMPOOL + && old_tx.mined_h.is_in_block() + && !old_tx.is_outside_bc + { // NOTE: mempool fetch is not synced to chain reading println!("mempool tx already in best chain; skipping"); return; } - println!("{} wallet updated existing transaction {txid} {:?} => {:?}", wallet.name, old_tx.mined_h, new_tx.mined_h); + println!( + "{} wallet updated existing transaction {txid} {:?} => {:?}", + wallet.name, old_tx.mined_h, new_tx.mined_h + ); // println!("{} wallet updated existing transaction {txid} {old_tx:?} => {new_tx:?}", wallet.name); // leave the tx-parts from the components not provided here if (new_tx.part_flags & TxParts::TRANSPARENT) == 0 { - new_tx.parts[WalletTxPart::TRANSPARENT] = old_tx.parts[WalletTxPart::TRANSPARENT]; + new_tx.parts[WalletTxPart::TRANSPARENT] = + old_tx.parts[WalletTxPart::TRANSPARENT]; } if (new_tx.part_flags & TxParts::SHIELDED_RECV) == 0 { - new_tx.parts[WalletTxPart::SHIELDED].recv_note_count = old_tx.parts[WalletTxPart::SHIELDED].recv_note_count; - new_tx.parts[WalletTxPart::SHIELDED].recv_zats = old_tx.parts[WalletTxPart::SHIELDED].recv_zats; + new_tx.parts[WalletTxPart::SHIELDED].recv_note_count = + old_tx.parts[WalletTxPart::SHIELDED].recv_note_count; + new_tx.parts[WalletTxPart::SHIELDED].recv_zats = + old_tx.parts[WalletTxPart::SHIELDED].recv_zats; } if (new_tx.part_flags & TxParts::SHIELDED_SENT) == 0 { - new_tx.parts[WalletTxPart::SHIELDED].spent_note_count = old_tx.parts[WalletTxPart::SHIELDED].spent_note_count; - new_tx.parts[WalletTxPart::SHIELDED].spent_zats = old_tx.parts[WalletTxPart::SHIELDED].spent_zats; - new_tx.parts[WalletTxPart::SHIELDED].sent_note_count = old_tx.parts[WalletTxPart::SHIELDED].sent_note_count; - new_tx.parts[WalletTxPart::SHIELDED].sent_zats = old_tx.parts[WalletTxPart::SHIELDED].sent_zats; + new_tx.parts[WalletTxPart::SHIELDED].spent_note_count = + old_tx.parts[WalletTxPart::SHIELDED].spent_note_count; + new_tx.parts[WalletTxPart::SHIELDED].spent_zats = + old_tx.parts[WalletTxPart::SHIELDED].spent_zats; + new_tx.parts[WalletTxPart::SHIELDED].sent_note_count = + old_tx.parts[WalletTxPart::SHIELDED].sent_note_count; + new_tx.parts[WalletTxPart::SHIELDED].sent_zats = + old_tx.parts[WalletTxPart::SHIELDED].sent_zats; } if (new_tx.part_flags & TxParts::MEMO) == 0 { new_tx.memo_count = old_tx.memo_count; - new_tx.memo = old_tx.memo; + new_tx.memo = old_tx.memo; } if (new_tx.part_flags & TxParts::STAKING_ACTION) == 0 { new_tx.staking_action = old_tx.staking_action; @@ -748,7 +890,10 @@ fn update_with_tx(wallet: &mut ManualWallet, txid: TxId, mut new_tx: WalletTx, i *tx_h = new_tx.mined_h; } else { wallet.tx_h_map.insert(txid, new_tx.mined_h); - println!("{} wallet inserted new transaction {txid} at {:?}", wallet.name, new_tx.mined_h); + println!( + "{} wallet inserted new transaction {txid} at {:?}", + wallet.name, new_tx.mined_h + ); } wallet.txs.insert(*insert_i, new_tx); *insert_i += 1; @@ -812,23 +957,25 @@ pub struct ManualWallet { /// sorted by (mined_h, discovery_time) pub txs: Vec, pub tx_h_map: HashMap, // NOTE: not a direct index because txs get inserted - // data_api has max_scanned in case they're scanned out of order - // pub next_sapling_subtree_index: u64, - // pub next_orchard_subtree_index: u64, - - // TODO: have a finalized balance etc and everything above that be a single volatile system - - // TODO: tiered tx definitiveness: - // - local-only - // - sent to lightwalletd - // - seen in mempool - // - any best-chain block - // - best-chain block confirmed by N - // - finalized block + // data_api has max_scanned in case they're scanned out of order + // pub next_sapling_subtree_index: u64, + // pub next_orchard_subtree_index: u64, + + // TODO: have a finalized balance etc and everything above that be a single volatile system + + // TODO: tiered tx definitiveness: + // - local-only + // - sent to lightwalletd + // - seen in mempool + // - any best-chain block + // - best-chain block confirmed by N + // - finalized block } // N.B. using some of the same API as WalletDb to allow smooth transition/comparison impl ManualWallet { - pub fn chain_height(&self) -> BlockHeight { self.chain_tip_h } + pub fn chain_height(&self) -> BlockHeight { + self.chain_tip_h + } pub fn fully_detected_height(&self) -> BlockHeight { let mut h = BlockHeight(0); for account in &self.accounts { @@ -846,10 +993,16 @@ impl ManualWallet { } // TODO: confirmations_policy should be overridden by finalized height - pub fn get_wallet_summary(&self, confirmations_policy: ConfirmationsPolicy) -> Result>, Infallible> { + pub fn get_wallet_summary( + &self, + confirmations_policy: ConfirmationsPolicy, + ) -> Result>, Infallible> { let mut account_balances = HashMap::with_capacity(self.accounts.len()); for account_i in 0..self.accounts.len() { - account_balances.insert(account_i, self.accounts[account_i].balance_changes.last().unwrap().1); + account_balances.insert( + account_i, + self.accounts[account_i].balance_changes.last().unwrap().1, + ); } Ok(Some(data_api::WalletSummary::new( @@ -857,9 +1010,9 @@ impl ManualWallet { LRZBlockHeight::from_u32(self.chain_tip_h.0), LRZBlockHeight::from_u32(self.fully_decoded_height().0), // TODO: fully_detected_height? // ignored: - data_api::Progress::new(data_api::Ratio::new(0,0), None), - 0,// sapling subtree - 0,// orchard subtree + data_api::Progress::new(data_api::Ratio::new(0, 0), None), + 0, // sapling subtree + 0, // orchard subtree ))) } @@ -867,10 +1020,15 @@ impl ManualWallet { // TODO: allow one destination to have an empty value for "send the rest here" (this could be // change or normal recipient) pub async fn send_zats( - &mut self, network: P, client: &mut CompactTxStreamerClient, outputs: &[TxOutput], - src_usk: &UnifiedSpendingKey, zats: Zatoshis, fee_zats: Zatoshis, opts: &TxOptions<'_> - ) -> Option - { + &mut self, + network: P, + client: &mut CompactTxStreamerClient, + outputs: &[TxOutput], + src_usk: &UnifiedSpendingKey, + zats: Zatoshis, + fee_zats: Zatoshis, + opts: &TxOptions<'_>, + ) -> Option { let tz = Timer::scope("send_zats"); let block_h = self.chain_tip_h.0 + 1; @@ -908,7 +1066,10 @@ impl ManualWallet { // - spendable note heights (must be higher than all needed) // - more notes needed if fees change -> change in anchor orchard_anchor_h = self.chain_tip_h.sat_sub(1); - orchard_anchor = match shardtree.root_at_checkpoint_id(&orchard_anchor_h).expect("Infallible MemoryShardStore") { + orchard_anchor = match shardtree + .root_at_checkpoint_id(&orchard_anchor_h) + .expect("Infallible MemoryShardStore") + { Some(root) => orchard::Anchor::from(root), None => { println!("tx build: couldn't get anchor at {orchard_anchor_h:?}"); @@ -924,7 +1085,9 @@ impl ManualWallet { let mut t_pubkey = None; if has_transparent_src { let Some((pubkey, privkey)) = transparent_keys_from_usk(&src_usk, 0) else { - println!("tried to send from transparent source without available transparent keys"); + println!( + "tried to send from transparent source without available transparent keys" + ); return None; }; signing_set.add_key(privkey); @@ -933,14 +1096,15 @@ impl ManualWallet { let mut sapling_esk: &[sapling_crypto::zip32::ExtendedSpendingKey] = &[]; let mut orchard_sak = &[orchard::keys::SpendAuthorizingKey::from(src_usk.orchard())]; - let mut txb = TxBuilder::new( network, LRZBlockHeight::from_u32(block_h), - BuildConfig::Standard { sapling_anchor, orchard_anchor: Some(orchard_anchor), }, + BuildConfig::Standard { + sapling_anchor, + orchard_anchor: Some(orchard_anchor), + }, ); - //- OUTPUTS/SENDS let mut memo_count = 0; let mut memo = EMPTY_MEMO_BYTES; @@ -948,7 +1112,7 @@ impl ManualWallet { let (mut t_send_c, mut t_recv_c, mut s_send_c, mut s_recv_c) = (0, 0, 0, 0); for output in outputs { match output { - &TxOutput::Transparent{ dst, zats } => { + &TxOutput::Transparent { dst, zats } => { t_send_c += 1; t_send_z += zats.into_u64(); // TODO: more comprehensive address matching @@ -962,7 +1126,12 @@ impl ManualWallet { } println!(" added transparent output: {}", zats.into_u64()); } - TxOutput::Orchard{ ovk, dst, zats, memo: note_memo } => { + TxOutput::Orchard { + ovk, + dst, + zats, + memo: note_memo, + } => { s_send_c += 1; s_send_z += zats; // TODO: more comprehensive address matching @@ -973,7 +1142,12 @@ impl ManualWallet { memo_count += !memo_is_empty(note_memo.as_array()) as usize; memo = *note_memo.as_array(); // TODO: handle multiple memos - if let Err(err) = txb.add_orchard_output::(ovk.clone(), dst.clone(), *zats, note_memo.clone()) { + if let Err(err) = txb.add_orchard_output::( + ovk.clone(), + dst.clone(), + *zats, + note_memo.clone(), + ) { println!("tx build error: {err:?}"); return None; } @@ -982,8 +1156,6 @@ impl ManualWallet { } } - - //- SPENDS let min_spend = t_send_z + s_send_z + fee_zats.into_u64(); let (mut t_spend_z, mut s_spend_z) = (0, 0); @@ -993,12 +1165,13 @@ impl ManualWallet { // TODO: account for notes that shouldn't be spent yet // - not enough confirmations // - used in another transaction that we've built - TxPool::Transparent => { let t_pubkey = t_pubkey.expect("checked above"); // "greedy strategy" for utxo in &account.utxos { - if let Err(err) = txb.add_transparent_input(t_pubkey, utxo.id.clone(), utxo.txout()) { + if let Err(err) = + txb.add_transparent_input(t_pubkey, utxo.id.clone(), utxo.txout()) + { println!("tx build: transparent/UTXO spend failed: {err:?}"); continue; } @@ -1029,9 +1202,13 @@ impl ManualWallet { shuffled_notes.shuffle(&mut OsRng); for note in &shuffled_notes { - if note.recv_h > orchard_anchor_h { continue; } + if note.recv_h > orchard_anchor_h { + continue; + } - let witness = match tree.witness_at_checkpoint_id(note.position, &orchard_anchor_h) { + let witness = match tree + .witness_at_checkpoint_id(note.position, &orchard_anchor_h) + { // NOTE: presumably can fail from too-recent note Ok(Some(witness)) => witness, Ok(None) => { @@ -1044,7 +1221,11 @@ impl ManualWallet { } }; let merkle_path = orchard::tree::MerklePath::from(witness); - if let Err(err) = txb.add_orchard_spend::(fvk.clone(), note.note, merkle_path) { + if let Err(err) = txb.add_orchard_spend::( + fvk.clone(), + note.note, + merkle_path, + ) { println!("tx build: orchard note spend failed: {err:?}"); continue; } @@ -1064,15 +1245,17 @@ impl ManualWallet { let change = match spend_z.cmp(&min_spend) { std::cmp::Ordering::Less => { println!("tx build error: can't afford {min_spend}; only {spend_z} available from given sources"); - return None - }, // can't afford + return None; + } // can't afford std::cmp::Ordering::Equal => 0, std::cmp::Ordering::Greater => { // TODO: prefer shielded output let change = spend_z - min_spend; t_send_z += change; t_recv_z += change; - if let Err(err) = txb.add_transparent_output(&t_addr, Zatoshis::from_u64(change).unwrap()) { + if let Err(err) = + txb.add_transparent_output(&t_addr, Zatoshis::from_u64(change).unwrap()) + { println!("tx build: failed to add change: {err:?}"); return None; }; @@ -1085,21 +1268,23 @@ impl ManualWallet { //- TOTALS GATHERED; CHECK VALUES let mut parts = [ - WalletTxPart { // Transparent + WalletTxPart { + // Transparent spent_note_count: t_spend_c, - sent_note_count: t_send_c, - recv_note_count: t_recv_c, - spent_zats: to_zats_or_dump_err("tx build", t_spend_z)?, - sent_zats: to_zats_or_dump_err("tx build", t_send_z)?, - recv_zats: to_zats_or_dump_err("tx build", t_recv_z)?, + sent_note_count: t_send_c, + recv_note_count: t_recv_c, + spent_zats: to_zats_or_dump_err("tx build", t_spend_z)?, + sent_zats: to_zats_or_dump_err("tx build", t_send_z)?, + recv_zats: to_zats_or_dump_err("tx build", t_recv_z)?, }, - WalletTxPart { // Shielded + WalletTxPart { + // Shielded spent_note_count: s_spend_c, - sent_note_count: s_send_c, - recv_note_count: s_recv_c, - spent_zats: to_zats_or_dump_err("tx build", s_spend_z)?, - sent_zats: to_zats_or_dump_err("tx build", s_send_z)?, - recv_zats: to_zats_or_dump_err("tx build", s_recv_z)?, + sent_note_count: s_send_c, + recv_note_count: s_recv_c, + spent_zats: to_zats_or_dump_err("tx build", s_spend_z)?, + sent_zats: to_zats_or_dump_err("tx build", s_send_z)?, + recv_zats: to_zats_or_dump_err("tx build", s_recv_z)?, }, ]; @@ -1108,7 +1293,6 @@ impl ManualWallet { return None; }; - //-- VERY EXPENSIVE TX CREATION (PARTICULARLY IF SHIELDED OUTPUT) use rand_chacha::ChaCha20Rng; let prover = LocalTxProver::bundled(); @@ -1135,13 +1319,18 @@ impl ManualWallet { //-- EXPENSIVE NETWORK SEND // TODO: don't block, maybe return a future? - let res = client.send_transaction(RawTransaction{ data: tx_bytes, height: 0 }).await; + let res = client + .send_transaction(RawTransaction { + data: tx_bytes, + height: 0, + }) + .await; println!("******* res for {:?}: {:?}", tx.txid(), res); //-- COMPLETION if res.is_ok() { // TODO: complete - let new_tx = WalletTx{ + let new_tx = WalletTx { account_id: 0, txid: tx.txid(), expiry_h: None, @@ -1163,12 +1352,14 @@ impl ManualWallet { } } - pub async fn shield_transparent_zats( - &mut self, network: P, client: &mut CompactTxStreamerClient, - src_usk: &UnifiedSpendingKey, min_zats_to_shield: u64, orchard_tree: &OrchardShardTree, - ) -> Option - { + &mut self, + network: P, + client: &mut CompactTxStreamerClient, + src_usk: &UnifiedSpendingKey, + min_zats_to_shield: u64, + orchard_tree: &OrchardShardTree, + ) -> Option { let tz = Timer::scope("shield_transparent_zats"); let block_h = self.chain_tip_h.0 + 1; @@ -1178,7 +1369,10 @@ impl ManualWallet { let orchard_addr = ua.orchard().unwrap(); let orchard_anchor_h = self.chain_tip_h.sat_sub(5); - let orchard_anchor = match orchard_tree.root_at_checkpoint_id(&orchard_anchor_h).expect("Infallible MemoryShardStore") { + let orchard_anchor = match orchard_tree + .root_at_checkpoint_id(&orchard_anchor_h) + .expect("Infallible MemoryShardStore") + { Some(root) => orchard::Anchor::from(root), None => { println!("tx build: couldn't get anchor at {orchard_anchor_h:?}"); @@ -1199,7 +1393,10 @@ impl ManualWallet { let mut txb = TxBuilder::new( network, LRZBlockHeight::from_u32(block_h), - BuildConfig::Standard { sapling_anchor: None, orchard_anchor: Some(orchard_anchor), }, + BuildConfig::Standard { + sapling_anchor: None, + orchard_anchor: Some(orchard_anchor), + }, ); //- SPENDS @@ -1239,11 +1436,22 @@ impl ManualWallet { s_send_c = 1; s_recv_c = 1; - let memo_bytes = MemoBytes::from_bytes("shielding notes (fn sheild_transparent_zats)".as_bytes()).unwrap(); + let memo_bytes = + MemoBytes::from_bytes("shielding notes (fn sheild_transparent_zats)".as_bytes()) + .unwrap(); memo_count = 1; memo = *memo_bytes.as_array(); - if let Err(err) = txb.add_orchard_output::(account.ufvk.orchard().cloned().map(|fvk| fvk.to_ovk(orchard::keys::Scope::External)), orchard_addr.clone(), s_send_z, memo_bytes) { + if let Err(err) = txb.add_orchard_output::( + account + .ufvk + .orchard() + .cloned() + .map(|fvk| fvk.to_ovk(orchard::keys::Scope::External)), + orchard_addr.clone(), + s_send_z, + memo_bytes, + ) { println!("tx build error: {err:?}"); return None; } @@ -1251,21 +1459,23 @@ impl ManualWallet { //- TOTALS GATHERED; CHECK VALUES let mut parts = [ - WalletTxPart { // Transparent + WalletTxPart { + // Transparent spent_note_count: t_spend_c, - sent_note_count: t_send_c, - recv_note_count: t_recv_c, - spent_zats: to_zats_or_dump_err("tx build", t_spend_z)?, - sent_zats: to_zats_or_dump_err("tx build", t_send_z)?, - recv_zats: to_zats_or_dump_err("tx build", t_recv_z)?, + sent_note_count: t_send_c, + recv_note_count: t_recv_c, + spent_zats: to_zats_or_dump_err("tx build", t_spend_z)?, + sent_zats: to_zats_or_dump_err("tx build", t_send_z)?, + recv_zats: to_zats_or_dump_err("tx build", t_recv_z)?, }, - WalletTxPart { // Shielded + WalletTxPart { + // Shielded spent_note_count: s_spend_c, - sent_note_count: s_send_c, - recv_note_count: s_recv_c, - spent_zats: to_zats_or_dump_err("tx build", s_spend_z)?, - sent_zats: to_zats_or_dump_err("tx build", s_send_z)?, - recv_zats: to_zats_or_dump_err("tx build", s_recv_z)?, + sent_note_count: s_send_c, + recv_note_count: s_recv_c, + spent_zats: to_zats_or_dump_err("tx build", s_spend_z)?, + sent_zats: to_zats_or_dump_err("tx build", s_send_z)?, + recv_zats: to_zats_or_dump_err("tx build", s_recv_z)?, }, ]; @@ -1300,13 +1510,18 @@ impl ManualWallet { //-- EXPENSIVE NETWORK SEND // TODO: don't block, maybe return a future? - let res = client.send_transaction(RawTransaction{ data: tx_bytes, height: 0 }).await; + let res = client + .send_transaction(RawTransaction { + data: tx_bytes, + height: 0, + }) + .await; println!("******* res for {:?}: {:?}", tx.txid(), res); //-- COMPLETION if res.is_ok() { // TODO: complete - let new_tx = WalletTx{ + let new_tx = WalletTx { account_id: 0, txid: tx.txid(), expiry_h: None, @@ -1328,12 +1543,15 @@ impl ManualWallet { } } - pub async fn send_orchard_to_orchard_zats( - &mut self, network: P, client: &mut CompactTxStreamerClient, - src_usk: &UnifiedSpendingKey, exact_amount_to_send: u64, orchard_tree: &OrchardShardTree, orchard_addr: &orchard::Address - ) -> Option - { + &mut self, + network: P, + client: &mut CompactTxStreamerClient, + src_usk: &UnifiedSpendingKey, + exact_amount_to_send: u64, + orchard_tree: &OrchardShardTree, + orchard_addr: &orchard::Address, + ) -> Option { let tz = Timer::scope("send_orchard_to_orchard_zats"); let block_h = self.chain_tip_h.0 + 1; @@ -1341,7 +1559,10 @@ impl ManualWallet { let keys = PreparedKeys::from_ufvk_all(&account.ufvk); let orchard_anchor_h = self.chain_tip_h.sat_sub(5); - let orchard_anchor = match orchard_tree.root_at_checkpoint_id(&orchard_anchor_h).expect("Infallible MemoryShardStore") { + let orchard_anchor = match orchard_tree + .root_at_checkpoint_id(&orchard_anchor_h) + .expect("Infallible MemoryShardStore") + { Some(root) => orchard::Anchor::from(root), None => { println!("tx build: couldn't get anchor at {orchard_anchor_h:?}"); @@ -1362,7 +1583,10 @@ impl ManualWallet { let mut txb = TxBuilder::new( network, LRZBlockHeight::from_u32(block_h), - BuildConfig::Standard { sapling_anchor: None, orchard_anchor: Some(orchard_anchor), }, + BuildConfig::Standard { + sapling_anchor: None, + orchard_anchor: Some(orchard_anchor), + }, ); //- SPENDS @@ -1377,21 +1601,28 @@ impl ManualWallet { let mut shuffled_notes = account.unspent_orchard_notes.clone(); shuffled_notes.shuffle(&mut OsRng); for note in &shuffled_notes { - if note.recv_h > orchard_anchor_h { continue; } - let witness = match orchard_tree.witness_at_checkpoint_id(note.position, &orchard_anchor_h) { - // NOTE: presumably can fail from too-recent note - Ok(Some(witness)) => witness, - Ok(None) => { - println!("tx build: no orchard checkpoint exists at {orchard_anchor_h:?}"); - return None; - } - Err(err) => { - println!("tx build: orchard witness error: {err:?}"); - return None; - } - }; + if note.recv_h > orchard_anchor_h { + continue; + } + let witness = + match orchard_tree.witness_at_checkpoint_id(note.position, &orchard_anchor_h) { + // NOTE: presumably can fail from too-recent note + Ok(Some(witness)) => witness, + Ok(None) => { + println!("tx build: no orchard checkpoint exists at {orchard_anchor_h:?}"); + return None; + } + Err(err) => { + println!("tx build: orchard witness error: {err:?}"); + return None; + } + }; let merkle_path = orchard::tree::MerklePath::from(witness); - if let Err(err) = txb.add_orchard_spend::(keys.orchard_fvk.clone().unwrap(), note.note, merkle_path) { + if let Err(err) = txb.add_orchard_spend::( + keys.orchard_fvk.clone().unwrap(), + note.note, + merkle_path, + ) { println!("tx build: orchard note spend failed: {err:?}"); continue; } @@ -1407,7 +1638,11 @@ impl ManualWallet { } if s_spend_z < exact_amount_to_send + rolling_fee_estimate { - println!("tx build error: not enough unspent orchard notes, got {} zats needed {}", s_spend_z, exact_amount_to_send + rolling_fee_estimate); + println!( + "tx build error: not enough unspent orchard notes, got {} zats needed {}", + s_spend_z, + exact_amount_to_send + rolling_fee_estimate + ); return None; } @@ -1420,11 +1655,22 @@ impl ManualWallet { s_send_z = s_spend_z - rolling_fee_estimate; s_send_c = 2; - let memo_bytes = MemoBytes::from_bytes("orchard send (fn send_orchard_to_orchard_zats)".as_bytes()).unwrap(); + let memo_bytes = + MemoBytes::from_bytes("orchard send (fn send_orchard_to_orchard_zats)".as_bytes()) + .unwrap(); memo_count = 1; memo = *memo_bytes.as_array(); - if let Err(err) = txb.add_orchard_output::(account.ufvk.orchard().cloned().map(|fvk| fvk.to_ovk(orchard::keys::Scope::External)), orchard_addr.clone(), exact_amount_to_send, memo_bytes) { + if let Err(err) = txb.add_orchard_output::( + account + .ufvk + .orchard() + .cloned() + .map(|fvk| fvk.to_ovk(orchard::keys::Scope::External)), + orchard_addr.clone(), + exact_amount_to_send, + memo_bytes, + ) { println!("tx build error: {err:?}"); return None; } @@ -1435,29 +1681,43 @@ impl ManualWallet { // Note(Sam): Omg wow this is actually kind of gnarly. We get two grace outputs always. s_recv_z = s_send_z - exact_amount_to_send; s_recv_c = 1; - if let Err(err) = txb.add_orchard_output::(account.ufvk.orchard().cloned().map(|fvk| fvk.to_ovk(orchard::keys::Scope::External)), my_ua.orchard().unwrap().clone(), s_recv_z, MemoBytes::from_bytes(&EMPTY_MEMO_BYTES).unwrap()) { + if let Err(err) = txb.add_orchard_output::( + account + .ufvk + .orchard() + .cloned() + .map(|fvk| fvk.to_ovk(orchard::keys::Scope::External)), + my_ua.orchard().unwrap().clone(), + s_recv_z, + MemoBytes::from_bytes(&EMPTY_MEMO_BYTES).unwrap(), + ) { println!("tx build error: {err:?}"); return None; } - println!(" added orchard change: {}", s_send_z - exact_amount_to_send); + println!( + " added orchard change: {}", + s_send_z - exact_amount_to_send + ); //- TOTALS GATHERED; CHECK VALUES let mut parts = [ - WalletTxPart { // Transparent + WalletTxPart { + // Transparent spent_note_count: t_spend_c, - sent_note_count: t_send_c, - recv_note_count: t_recv_c, - spent_zats: to_zats_or_dump_err("tx build", t_spend_z)?, - sent_zats: to_zats_or_dump_err("tx build", t_send_z)?, - recv_zats: to_zats_or_dump_err("tx build", t_recv_z)?, + sent_note_count: t_send_c, + recv_note_count: t_recv_c, + spent_zats: to_zats_or_dump_err("tx build", t_spend_z)?, + sent_zats: to_zats_or_dump_err("tx build", t_send_z)?, + recv_zats: to_zats_or_dump_err("tx build", t_recv_z)?, }, - WalletTxPart { // Shielded + WalletTxPart { + // Shielded spent_note_count: s_spend_c, - sent_note_count: s_send_c, - recv_note_count: s_recv_c, - spent_zats: to_zats_or_dump_err("tx build", s_spend_z)?, - sent_zats: to_zats_or_dump_err("tx build", s_send_z)?, - recv_zats: to_zats_or_dump_err("tx build", s_recv_z)?, + sent_note_count: s_send_c, + recv_note_count: s_recv_c, + spent_zats: to_zats_or_dump_err("tx build", s_spend_z)?, + sent_zats: to_zats_or_dump_err("tx build", s_send_z)?, + recv_zats: to_zats_or_dump_err("tx build", s_recv_z)?, }, ]; @@ -1492,13 +1752,18 @@ impl ManualWallet { //-- EXPENSIVE NETWORK SEND // TODO: don't block, maybe return a future? - let res = client.send_transaction(RawTransaction{ data: tx_bytes, height: 0 }).await; + let res = client + .send_transaction(RawTransaction { + data: tx_bytes, + height: 0, + }) + .await; println!("******* res for {:?}: {:?}", tx.txid(), res); //-- COMPLETION if res.is_ok() { // TODO: complete - let new_tx = WalletTx{ + let new_tx = WalletTx { account_id: 0, txid: tx.txid(), expiry_h: None, @@ -1520,12 +1785,15 @@ impl ManualWallet { } } - pub async fn stake_orchard_to_finalizer( - &mut self, network: P, client: &mut CompactTxStreamerClient, - src_usk: &UnifiedSpendingKey, exact_amount_to_send: u64, orchard_tree: &OrchardShardTree, target_finalizer: &[u8; 32], - ) -> Option - { + &mut self, + network: P, + client: &mut CompactTxStreamerClient, + src_usk: &UnifiedSpendingKey, + exact_amount_to_send: u64, + orchard_tree: &OrchardShardTree, + target_finalizer: &[u8; 32], + ) -> Option { let tz = Timer::scope("stake_orchard_to_finalizer"); let block_h = self.chain_tip_h.0 + 1; @@ -1533,7 +1801,10 @@ impl ManualWallet { let keys = PreparedKeys::from_ufvk_all(&account.ufvk); let orchard_anchor_h = self.chain_tip_h.sat_sub(5); - let orchard_anchor = match orchard_tree.root_at_checkpoint_id(&orchard_anchor_h).expect("Infallible MemoryShardStore") { + let orchard_anchor = match orchard_tree + .root_at_checkpoint_id(&orchard_anchor_h) + .expect("Infallible MemoryShardStore") + { Some(root) => orchard::Anchor::from(root), None => { println!("tx build: couldn't get anchor at {orchard_anchor_h:?}"); @@ -1554,7 +1825,10 @@ impl ManualWallet { let mut txb = TxBuilder::new( network, LRZBlockHeight::from_u32(block_h), - BuildConfig::Standard { sapling_anchor: None, orchard_anchor: Some(orchard_anchor), }, + BuildConfig::Standard { + sapling_anchor: None, + orchard_anchor: Some(orchard_anchor), + }, ); //- SPENDS @@ -1569,21 +1843,28 @@ impl ManualWallet { let mut shuffled_notes = account.unspent_orchard_notes.clone(); shuffled_notes.shuffle(&mut OsRng); for note in &shuffled_notes { - if note.recv_h > orchard_anchor_h { continue; } - let witness = match orchard_tree.witness_at_checkpoint_id(note.position, &orchard_anchor_h) { - // NOTE: presumably can fail from too-recent note - Ok(Some(witness)) => witness, - Ok(None) => { - println!("tx build: no orchard checkpoint exists at {orchard_anchor_h:?}"); - return None; - } - Err(err) => { - println!("tx build: orchard witness error: {err:?}"); - return None; - } - }; + if note.recv_h > orchard_anchor_h { + continue; + } + let witness = + match orchard_tree.witness_at_checkpoint_id(note.position, &orchard_anchor_h) { + // NOTE: presumably can fail from too-recent note + Ok(Some(witness)) => witness, + Ok(None) => { + println!("tx build: no orchard checkpoint exists at {orchard_anchor_h:?}"); + return None; + } + Err(err) => { + println!("tx build: orchard witness error: {err:?}"); + return None; + } + }; let merkle_path = orchard::tree::MerklePath::from(witness); - if let Err(err) = txb.add_orchard_spend::(keys.orchard_fvk.clone().unwrap(), note.note, merkle_path) { + if let Err(err) = txb.add_orchard_spend::( + keys.orchard_fvk.clone().unwrap(), + note.note, + merkle_path, + ) { println!("tx build: orchard note spend failed: {err:?}"); continue; } @@ -1599,7 +1880,11 @@ impl ManualWallet { } if s_spend_z < exact_amount_to_send + rolling_fee_estimate { - println!("tx build error: not enough unspent orchard notes, got {} zats needed {}", s_spend_z, exact_amount_to_send + rolling_fee_estimate); + println!( + "tx build error: not enough unspent orchard notes, got {} zats needed {}", + s_spend_z, + exact_amount_to_send + rolling_fee_estimate + ); return None; } @@ -1612,12 +1897,20 @@ impl ManualWallet { s_send_z = s_spend_z - rolling_fee_estimate; s_send_c = 2; - use rand::RngCore; let mut pretend_pub_key = [0u8; 32]; rand::rngs::OsRng.fill_bytes(&mut pretend_pub_key); - if let Err(err) = txb.put_staking_action(StakingAction_CreateNewDelegationBond { amount_zats: exact_amount_to_send, unique_pubkey: pretend_pub_key, challenge: [0u8; 32], target_finalizer: *target_finalizer, signature: [0u8; 64] }.to_union()) { + if let Err(err) = txb.put_staking_action( + StakingAction_CreateNewDelegationBond { + amount_zats: exact_amount_to_send, + unique_pubkey: pretend_pub_key, + challenge: [0u8; 32], + target_finalizer: *target_finalizer, + signature: [0u8; 64], + } + .to_union(), + ) { println!("tx build error: {err:?}"); return None; } @@ -1628,7 +1921,16 @@ impl ManualWallet { // Change is free. s_recv_z = s_send_z - exact_amount_to_send; s_recv_c = 1; - if let Err(err) = txb.add_orchard_output::(account.ufvk.orchard().cloned().map(|fvk| fvk.to_ovk(orchard::keys::Scope::External)), my_ua.orchard().unwrap().clone(), s_recv_z, MemoBytes::from_bytes(&EMPTY_MEMO_BYTES).unwrap()) { + if let Err(err) = txb.add_orchard_output::( + account + .ufvk + .orchard() + .cloned() + .map(|fvk| fvk.to_ovk(orchard::keys::Scope::External)), + my_ua.orchard().unwrap().clone(), + s_recv_z, + MemoBytes::from_bytes(&EMPTY_MEMO_BYTES).unwrap(), + ) { println!("tx build error: {err:?}"); return None; } @@ -1636,21 +1938,23 @@ impl ManualWallet { //- TOTALS GATHERED; CHECK VALUES let mut parts = [ - WalletTxPart { // Transparent + WalletTxPart { + // Transparent spent_note_count: t_spend_c, - sent_note_count: t_send_c, - recv_note_count: t_recv_c, - spent_zats: to_zats_or_dump_err("tx build", t_spend_z)?, - sent_zats: to_zats_or_dump_err("tx build", t_send_z)?, - recv_zats: to_zats_or_dump_err("tx build", t_recv_z)?, + sent_note_count: t_send_c, + recv_note_count: t_recv_c, + spent_zats: to_zats_or_dump_err("tx build", t_spend_z)?, + sent_zats: to_zats_or_dump_err("tx build", t_send_z)?, + recv_zats: to_zats_or_dump_err("tx build", t_recv_z)?, }, - WalletTxPart { // Shielded + WalletTxPart { + // Shielded spent_note_count: s_spend_c, - sent_note_count: s_send_c, - recv_note_count: s_recv_c, - spent_zats: to_zats_or_dump_err("tx build", s_spend_z)?, - sent_zats: to_zats_or_dump_err("tx build", s_send_z)?, - recv_zats: to_zats_or_dump_err("tx build", s_recv_z)?, + sent_note_count: s_send_c, + recv_note_count: s_recv_c, + spent_zats: to_zats_or_dump_err("tx build", s_spend_z)?, + sent_zats: to_zats_or_dump_err("tx build", s_send_z)?, + recv_zats: to_zats_or_dump_err("tx build", s_recv_z)?, }, ]; @@ -1685,13 +1989,18 @@ impl ManualWallet { //-- EXPENSIVE NETWORK SEND // TODO: don't block, maybe return a future? - let res = client.send_transaction(RawTransaction{ data: tx_bytes, height: 0 }).await; + let res = client + .send_transaction(RawTransaction { + data: tx_bytes, + height: 0, + }) + .await; println!("******* res for {:?}: {:?}", tx.txid(), res); //-- COMPLETION if res.is_ok() { // TODO: complete - let new_tx = WalletTx{ + let new_tx = WalletTx { account_id: 0, txid: tx.txid(), expiry_h: None, @@ -1713,7 +2022,6 @@ impl ManualWallet { } } - pub fn audit_txs(&self) { for tx in &self.txs { // if tx.parts[0].spent_note_count > 0 && @@ -1753,14 +2061,14 @@ impl PoWCache { } self.next_tip_h = h + 1; } - pub fn hash_at_h(&self, h: u64) -> Option<[u8;32]> { + pub fn hash_at_h(&self, h: u64) -> Option<[u8; 32]> { if h < self.next_tip_h { Some(self.hashes[h as usize]) } else { None } } - pub fn tip_hash(&self) -> [u8;32] { + pub fn tip_hash(&self) -> [u8; 32] { self.hashes[self.next_tip_h as usize - 1] } } @@ -1780,7 +2088,12 @@ fn orchard_recv_h_insert(notes: &mut Vec, note: OrchardNote) { i = notes.partition_point(|n| n.recv_h <= note.recv_h); } } - debug_assert!(i == 0 || notes[i-1].recv_h <= note.recv_h, "{} <= {}", notes[i-1].recv_h, note.recv_h); + debug_assert!( + i == 0 || notes[i - 1].recv_h <= note.recv_h, + "{} <= {}", + notes[i - 1].recv_h, + note.recv_h + ); notes.insert(i, note); } fn orchard_spent_h_insert(notes: &mut Vec, note: OrchardNote) { @@ -1790,7 +2103,12 @@ fn orchard_spent_h_insert(notes: &mut Vec, note: OrchardNote) { i = notes.partition_point(|n| n.spent_h <= note.spent_h); } } - debug_assert!(i == 0 || notes[i-1].spent_h <= note.spent_h, "{} <= {}", notes[i-1].spent_h, note.spent_h); + debug_assert!( + i == 0 || notes[i - 1].spent_h <= note.spent_h, + "{} <= {}", + notes[i - 1].spent_h, + note.spent_h + ); notes.insert(i, note); } fn txo_recv_h_insert(notes: &mut Vec, note: Txo) { @@ -1800,7 +2118,12 @@ fn txo_recv_h_insert(notes: &mut Vec, note: Txo) { i = notes.partition_point(|n| n.recv_h <= note.recv_h); } } - debug_assert!(i == 0 || notes[i-1].recv_h <= note.recv_h, "{} <= {}", notes[i-1].recv_h, note.recv_h); + debug_assert!( + i == 0 || notes[i - 1].recv_h <= note.recv_h, + "{} <= {}", + notes[i - 1].recv_h, + note.recv_h + ); notes.insert(i, note); } fn txo_spent_h_insert(notes: &mut Vec, note: Txo) { @@ -1810,12 +2133,21 @@ fn txo_spent_h_insert(notes: &mut Vec, note: Txo) { i = notes.partition_point(|n| n.spent_h <= note.spent_h); } } - debug_assert!(i == 0 || notes[i-1].spent_h <= note.spent_h, "{} <= {}", notes[i-1].spent_h, note.spent_h); + debug_assert!( + i == 0 || notes[i - 1].spent_h <= note.spent_h, + "{} <= {}", + notes[i - 1].spent_h, + note.spent_h + ); notes.insert(i, note); } /// GET NOTE/TX INDEXES WITH KNOWN HEIGHTS IN SORTED SLICES -fn orchard_recv_h_position(notes: &[OrchardNote], block_h: BlockHeight, nf: &orchard::note::Nullifier) -> Option { +fn orchard_recv_h_position( + notes: &[OrchardNote], + block_h: BlockHeight, + nf: &orchard::note::Nullifier, +) -> Option { let mut i = notes.partition_point(|txo| txo.recv_h < block_h); while i < notes.len() && notes[i].recv_h == block_h { if ¬es[i].nf == nf { @@ -1825,7 +2157,11 @@ fn orchard_recv_h_position(notes: &[OrchardNote], block_h: BlockHeight, nf: &orc } None } -fn orchard_spent_h_position(notes: &[OrchardNote], block_h: BlockHeight, nf: &orchard::note::Nullifier) -> Option { +fn orchard_spent_h_position( + notes: &[OrchardNote], + block_h: BlockHeight, + nf: &orchard::note::Nullifier, +) -> Option { let mut i = notes.partition_point(|txo| txo.spent_h < block_h); while i < notes.len() && notes[i].spent_h == block_h { if ¬es[i].nf == nf { @@ -1925,7 +2261,10 @@ fn bc_h_from_raw_tx_h(height: u64) -> Option> { return Some(None); } let Ok(height) = ::try_from(height) else { - println!("transparent tx's height can't be represented in 32 bits: {}", height); + println!( + "transparent tx's height can't be represented in 32 bits: {}", + height + ); return None; }; @@ -1938,7 +2277,17 @@ fn bc_h_from_raw_tx_h(height: u64) -> Option> { // TODO: handle memo here instead of caller? // TODO: replace orchard_action_tree_position_by_cmx with position -fn handle_orchard_action(wallet: &mut ManualWallet, account_i: usize, keys: &PreparedKeys, position: incrementalmerkletree::Position, block_h: BlockHeight, txid: &TxId, nf: &orchard::note::Nullifier, recv_note_addr: Option<(orchard::note::Note, orchard::Address)>, send_note_addr_memo: Option<(orchard::note::Note, orchard::Address, [u8; 512])>) -> Option { +fn handle_orchard_action( + wallet: &mut ManualWallet, + account_i: usize, + keys: &PreparedKeys, + position: incrementalmerkletree::Position, + block_h: BlockHeight, + txid: &TxId, + nf: &orchard::note::Nullifier, + recv_note_addr: Option<(orchard::note::Note, orchard::Address)>, + send_note_addr_memo: Option<(orchard::note::Note, orchard::Address, [u8; 512])>, +) -> Option { let account = &mut wallet.accounts[account_i]; //- HANDLE SPENT NOTES @@ -1952,7 +2301,13 @@ fn handle_orchard_action(wallet: &mut ManualWallet, account_i: usize, keys: &Pre let spent_note = account.unspent_orchard_notes.remove(note_i); s_spend_c = 1; s_spend_z = spent_note.note.value().inner(); - orchard_spent_h_insert(&mut account.spent_orchard_notes, OrchardNote { spent_h: block_h, ..spent_note }); + orchard_spent_h_insert( + &mut account.spent_orchard_notes, + OrchardNote { + spent_h: block_h, + ..spent_note + }, + ); break; } } @@ -1982,7 +2337,7 @@ fn handle_orchard_action(wallet: &mut ManualWallet, account_i: usize, keys: &Pre // } // NOTE: s_send_c/s_send_z equivalent handled inside update_with_tx - let orchard_note = OrchardNote{ + let orchard_note = OrchardNote { recv_h: block_h, spent_h: BlockHeight(0), txid: *txid, @@ -2008,7 +2363,9 @@ fn handle_orchard_action(wallet: &mut ManualWallet, account_i: usize, keys: &Pre }; // TODO: can we just check if we've seen the tx && tx.is_outside_bc == false - let have_seen = if let Some(i) = orchard_recv_h_position(&account.recv_orchard_notes, txid_h, &orchard_note.nf) { + let have_seen = if let Some(i) = + orchard_recv_h_position(&account.recv_orchard_notes, txid_h, &orchard_note.nf) + { account.recv_orchard_notes[i].monotonically_update(orchard_note); true } else { @@ -2016,14 +2373,20 @@ fn handle_orchard_action(wallet: &mut ManualWallet, account_i: usize, keys: &Pre false }; - if let Some(i) = orchard_recv_h_position(&account.unspent_orchard_notes, txid_h, &orchard_note.nf) { + if let Some(i) = + orchard_recv_h_position(&account.unspent_orchard_notes, txid_h, &orchard_note.nf) + { account.unspent_orchard_notes[i].monotonically_update(orchard_note); } else if !have_seen { orchard_recv_h_insert(&mut account.unspent_orchard_notes, orchard_note); } } - match (Zatoshis::from_u64(s_spend_z), Zatoshis::from_u64(s_send_z), Zatoshis::from_u64(s_recv_z)) { + match ( + Zatoshis::from_u64(s_spend_z), + Zatoshis::from_u64(s_send_z), + Zatoshis::from_u64(s_recv_z), + ) { (Ok(spent_zats), Ok(sent_zats), Ok(recv_zats)) => Some(WalletTxPart { spent_zats, sent_zats, @@ -2040,7 +2403,15 @@ fn handle_orchard_action(wallet: &mut ManualWallet, account_i: usize, keys: &Pre } } -fn read_full_tx(wallet: &mut ManualWallet, account_i: usize, keys: &PreparedKeys, block_h: BlockHeight, tx: &Transaction, insert_i: &mut usize, is_outside_bc: bool) -> Option<()> { +fn read_full_tx( + wallet: &mut ManualWallet, + account_i: usize, + keys: &PreparedKeys, + block_h: BlockHeight, + tx: &Transaction, + insert_i: &mut usize, + is_outside_bc: bool, +) -> Option<()> { // TODO: we probably want to early-out if our existing tx data is complete // (after checking that this doesn't get modified) update_insert_i(&wallet.txs, insert_i, block_h); @@ -2077,22 +2448,37 @@ fn read_full_tx(wallet: &mut ManualWallet, account_i: usize, keys: &PreparedKeys input_i += 1; if let Some(&prevout_txid_h) = wallet.tx_h_map.get(input.prevout.txid()) { - if let Some(utxo_i) = txo_recv_h_position(&account.utxos, prevout_txid_h, &input.prevout) { + if let Some(utxo_i) = + txo_recv_h_position(&account.utxos, prevout_txid_h, &input.prevout) + { let utxo = account.utxos.remove(utxo_i); - let stxo = Txo { spent_h: block_h, ..utxo }; + let stxo = Txo { + spent_h: block_h, + ..utxo + }; if let Some(last_stxo) = account.stxos.last() { if last_stxo.spent_h > stxo.spent_h { - println!("ERROR: out of sequence spent UTXO: {} > {}", last_stxo.spent_h, stxo.spent_h); + println!( + "ERROR: out of sequence spent UTXO: {} > {}", + last_stxo.spent_h, stxo.spent_h + ); } } t_spend_c += 1; t_spend_z += stxo.value.into_u64(); if let Some(last_stxo) = account.stxos.last() { - debug_assert!(last_stxo.spent_h <= stxo.spent_h, "{} <= {}", last_stxo.spent_h, stxo.spent_h); + debug_assert!( + last_stxo.spent_h <= stxo.spent_h, + "{} <= {}", + last_stxo.spent_h, + stxo.spent_h + ); } account.stxos.push(stxo); - } else if let Some(txo_i) = txo_recv_h_position(&account.recv_txos, prevout_txid_h, &input.prevout) { + } else if let Some(txo_i) = + txo_recv_h_position(&account.recv_txos, prevout_txid_h, &input.prevout) + { // NOTE: we need to use our own tracking of the TXO as otherwise we don't know the value t_spend_c += 1; t_spend_z += account.recv_txos[txo_i].value.into_u64(); @@ -2133,17 +2519,30 @@ fn read_full_tx(wallet: &mut ManualWallet, account_i: usize, keys: &PreparedKeys }; if let Some(utxo_i) = txo_recv_h_position(&account.utxos, txid_h, &utxo.id) { if account.utxos[utxo_i] != utxo { - println!("ERROR: UTXO mismatch: {:?} vs {:?}", account.utxos[utxo_i], &utxo); + println!( + "ERROR: UTXO mismatch: {:?} vs {:?}", + account.utxos[utxo_i], &utxo + ); } } else if txo_recv_h_position(&account.recv_txos, txid_h, &utxo.id).is_none() { // TODO: can we just check if we've seen the tx && tx.2 == false if let Some(last_txo) = account.recv_txos.last() { - debug_assert!(last_txo.recv_h <= utxo.recv_h, "{} <= {}", last_txo.recv_h, utxo.recv_h); + debug_assert!( + last_txo.recv_h <= utxo.recv_h, + "{} <= {}", + last_txo.recv_h, + utxo.recv_h + ); } account.recv_txos.push(utxo.clone()); if let Some(last_utxo) = account.utxos.last() { - debug_assert!(last_utxo.recv_h <= utxo.recv_h, "{} <= {}", last_utxo.recv_h, utxo.recv_h); + debug_assert!( + last_utxo.recv_h <= utxo.recv_h, + "{} <= {}", + last_utxo.recv_h, + utxo.recv_h + ); } account.utxos.push(utxo); } @@ -2172,7 +2571,13 @@ fn read_full_tx(wallet: &mut ManualWallet, account_i: usize, keys: &PreparedKeys // TODO: do we get more useful info if we do both? // we use the nullifier to detect spends anyway, // and we've already got any memos from the above - try_output_recovery_with_ovk(&domain, ovk, action, action.cv_net(), &action.encrypted_note().out_ciphertext) + try_output_recovery_with_ovk( + &domain, + ovk, + action, + action.cv_net(), + &action.encrypted_note().out_ciphertext, + ) } else { None }; @@ -2191,7 +2596,17 @@ fn read_full_tx(wallet: &mut ManualWallet, account_i: usize, keys: &PreparedKeys } } - let Some(part) = handle_orchard_action(wallet, account_i, keys, unknown_tree_position(), block_h, &txid, action.nullifier(), recv_note_addr, send_res) else { + let Some(part) = handle_orchard_action( + wallet, + account_i, + keys, + unknown_tree_position(), + block_h, + &txid, + action.nullifier(), + recv_note_addr, + send_res, + ) else { // error creating zats (already printed) // TODO: can we validly continue at all? continue; @@ -2237,24 +2652,33 @@ fn read_full_tx(wallet: &mut ManualWallet, account_i: usize, keys: &PreparedKeys Some(()) } -type OrchardTree = incrementalmerkletree::frontier::CommitmentTree; -type OrchardFrontier = incrementalmerkletree::frontier::Frontier; -type OrchardWitness = incrementalmerkletree::witness::IncrementalWitness; +type OrchardTree = incrementalmerkletree::frontier::CommitmentTree< + orchard::tree::MerkleHashOrchard, + { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }, +>; +type OrchardFrontier = incrementalmerkletree::frontier::Frontier< + orchard::tree::MerkleHashOrchard, + { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }, +>; +type OrchardWitness = incrementalmerkletree::witness::IncrementalWitness< + orchard::tree::MerkleHashOrchard, + { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }, +>; const SHARD_HEIGHT: u8 = 16; // default => 65536 leaves per shard -type OrchardShardTree = shardtree::ShardTree::< - shardtree::store::memory::MemoryShardStore::< +type OrchardShardTree = shardtree::ShardTree< + shardtree::store::memory::MemoryShardStore< orchard::tree::MerkleHashOrchard, // shardtree::Node, - BlockHeight + BlockHeight, >, { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }, - SHARD_HEIGHT + SHARD_HEIGHT, >; fn shard_tree_size(tree: &OrchardShardTree) -> u64 { tree.max_leaf_position(None) .expect("Infallible Memory Store") - .map_or(0, |pos| u64::from(pos)+1) + .map_or(0, |pos| u64::from(pos) + 1) } fn shard_tree_root(tree: &OrchardShardTree) -> orchard::tree::MerkleHashOrchard { @@ -2264,8 +2688,17 @@ fn shard_tree_root(tree: &OrchardShardTree) -> orchard::tree::MerkleHashOrchard } /// NOTE: this *must* only be called in sequential order without gaps (including after reorg/truncate) -fn read_compact_tx(wallet: &mut ManualWallet, account_i: usize, keys: &PreparedKeys, block_h: BlockHeight, tx: &CompactTx, insert_i: &mut usize, orchard_tree: &mut OrchardShardTree) -> (TxId, bool/*ours*/, bool/*ok*/) { - let txid = TxId::from_bytes(<[u8;32]>::try_from(&tx.hash[..]).expect("successfully converted above")); +fn read_compact_tx( + wallet: &mut ManualWallet, + account_i: usize, + keys: &PreparedKeys, + block_h: BlockHeight, + tx: &CompactTx, + insert_i: &mut usize, + orchard_tree: &mut OrchardShardTree, +) -> (TxId, bool /*ours*/, bool /*ok*/) { + let txid = + TxId::from_bytes(<[u8; 32]>::try_from(&tx.hash[..]).expect("successfully converted above")); let mut shielded_part = WalletTxPart::ZERO; @@ -2275,17 +2708,20 @@ fn read_compact_tx(wallet: &mut ManualWallet, account_i: usize, keys: &PreparedK Err(err) => { // TODO: we can't keep position updated if we fail here // TODO: should we fail validation for the entire block above if we can't do this? - println!("couldn't convert CompactOrchardAction to orchard::CompactAction: {err:?}"); + println!( + "couldn't convert CompactOrchardAction to orchard::CompactAction: {err:?}" + ); continue; } }; let domain = OrchardDomain::for_compact_action(&action); - let note_addr: Option<(orchard::note::Note, orchard::Address)> = if let Some(ivk) = &keys.orchard_ivk { - try_compact_note_decryption(&domain, ivk, &action) - } else { - None - }; + let note_addr: Option<(orchard::note::Note, orchard::Address)> = + if let Some(ivk) = &keys.orchard_ivk { + try_compact_note_decryption(&domain, ivk, &action) + } else { + None + }; let nf: orchard::note::Nullifier = action.nullifier(); let cmx: orchard::note::ExtractedNoteCommitment = action.cmx(); @@ -2301,12 +2737,24 @@ fn read_compact_tx(wallet: &mut ManualWallet, account_i: usize, keys: &PreparedK // let position = orchard_tree.max_leaf_position(None).unwrap().unwrap_or(incrementalmerkletree::Position::from(0)); // let res = orchard_tree.batch_insert(position, append_iter).expect("Infallible Memory Store"); // println!("****** orchard_tree.batch_insert result {:?}", res); - orchard_tree.append(orchard::tree::MerkleHashOrchard::from_cmx(&cmx), retention).expect("Infallible Memory Store"); - println!("orchard root at {:?} tree size={:02} {:?}", block_h, shard_tree_size(orchard_tree), shard_tree_root(orchard_tree)); + orchard_tree + .append(orchard::tree::MerkleHashOrchard::from_cmx(&cmx), retention) + .expect("Infallible Memory Store"); + println!( + "orchard root at {:?} tree size={:02} {:?}", + block_h, + shard_tree_size(orchard_tree), + shard_tree_root(orchard_tree) + ); - let position = orchard_tree.max_leaf_position(None).expect("Infallible Memory Store").expect("just appended"); + let position = orchard_tree + .max_leaf_position(None) + .expect("Infallible Memory Store") + .expect("just appended"); - let Some(part) = handle_orchard_action(wallet, account_i, keys, position, block_h, &txid, &nf, note_addr, None) else { + let Some(part) = handle_orchard_action( + wallet, account_i, keys, position, block_h, &txid, &nf, note_addr, None, + ) else { // error creating zats (already printed) // TODO: can we validly continue at all? continue; @@ -2329,14 +2777,18 @@ fn read_compact_tx(wallet: &mut ManualWallet, account_i: usize, keys: &PreparedK // TODO: decrypt our transactions & fill in actual data here // TODO: get full info with memos - if (shielded_part.spent_note_count | shielded_part.sent_note_count | shielded_part.recv_note_count) != 0 { + if (shielded_part.spent_note_count + | shielded_part.sent_note_count + | shielded_part.recv_note_count) + != 0 + { let new_tx = WalletTx { account_id: account_i, txid, expiry_h: None, // TODO mined_h: block_h, part_flags: TxParts::SHIELDED_RECV, - parts: [ WalletTxPart::ZERO, shielded_part ], + parts: [WalletTxPart::ZERO, shielded_part], memo_count: 0, memo: EMPTY_MEMO_BYTES, is_coinbase: false, @@ -2351,12 +2803,11 @@ fn read_compact_tx(wallet: &mut ManualWallet, account_i: usize, keys: &PreparedK } } - pub async fn wallet_main(wallet_state: Arc>) { - fn stuff_from_seed_phrase(params:P, phrase: &str) -> ( - SecretVec, - UnifiedSpendingKey, - ) { + fn stuff_from_seed_phrase( + params: P, + phrase: &str, + ) -> (SecretVec, UnifiedSpendingKey) { use secrecy::ExposeSecret; let mnemonic = bip39::Mnemonic::parse(phrase).unwrap(); @@ -2368,14 +2819,21 @@ pub async fn wallet_main(wallet_state: Arc>) { let usk = UnifiedSpendingKey::from_seed(¶ms, seed.expose_secret(), account_id).unwrap(); let birthday = &AccountBirthday::from_parts( - ChainState::empty(LRZBlockHeight::from_u32(0), zcash_primitives::block::BlockHash([0; 32])), + ChainState::empty( + LRZBlockHeight::from_u32(0), + zcash_primitives::block::BlockHash([0; 32]), + ), None, ); (seed, usk) } - fn wallet_from_stuff(params: P, name: &'static str, seed: SecretVec) -> (ManualWallet, ManualAccount) { + fn wallet_from_stuff( + params: P, + name: &'static str, + seed: SecretVec, + ) -> (ManualWallet, ManualAccount) { // TODO: skip this by changing API slightly let account_id = zip32::AccountId::try_from(0).unwrap(); let usk = UnifiedSpendingKey::from_seed(¶ms, seed.expose_secret(), account_id).unwrap(); @@ -2405,20 +2863,35 @@ pub async fn wallet_main(wallet_state: Arc>) { (wallet, account) } - let addrs_from_wallet = |wallet: &ManualWallet| -> Option<(TransparentAddress, UnifiedAddress)> { - let Some(account) = wallet.accounts.first() else { return None; }; - addrs_from_account(account, 0) - }; + let addrs_from_wallet = + |wallet: &ManualWallet| -> Option<(TransparentAddress, UnifiedAddress)> { + let Some(account) = wallet.accounts.first() else { + return None; + }; + addrs_from_account(account, 0) + }; fn get_transaction_history(wallet: &ManualWallet) -> Result, Infallible> { Ok(wallet.txs.clone()) } - async fn get_received_memos_and_actions(client: &mut CompactTxStreamerClient, wallet: &ManualWallet, params: P, history: &[WalletTx]) - -> Option<(HashMap, Vec)>, HashMap, Vec)>)> { - fn try_get_orchard_memos(tx: &TransactionData, ivk: &orchard::keys::PreparedIncomingViewingKey) -> Vec { + async fn get_received_memos_and_actions( + client: &mut CompactTxStreamerClient, + wallet: &ManualWallet, + params: P, + history: &[WalletTx], + ) -> Option<( + HashMap, Vec)>, + HashMap, Vec)>, + )> { + fn try_get_orchard_memos( + tx: &TransactionData, + ivk: &orchard::keys::PreparedIncomingViewingKey, + ) -> Vec { let mut memos = Vec::new(); - let Some(bundle) = tx.orchard_bundle() else { return memos; }; + let Some(bundle) = tx.orchard_bundle() else { + return memos; + }; for action in bundle.actions() { let domain = orchard::note_encryption::OrchardDomain::for_action(action); @@ -2434,15 +2907,26 @@ pub async fn wallet_main(wallet_state: Arc>) { return memos; } - fn try_get_orchard_sent_memos(tx: &TransactionData, ovk: &orchard::keys::OutgoingViewingKey) -> Vec { + fn try_get_orchard_sent_memos( + tx: &TransactionData, + ovk: &orchard::keys::OutgoingViewingKey, + ) -> Vec { // TODO: this is primarily for syncing txs that our wallet didn't observe sending; we // can optimize ones we sent directly let mut memos = Vec::new(); - let Some(bundle) = tx.orchard_bundle() else { return memos; }; + let Some(bundle) = tx.orchard_bundle() else { + return memos; + }; for action in bundle.actions() { let domain = orchard::note_encryption::OrchardDomain::for_action(action); - if let Some((_, _, memo)) = try_output_recovery_with_ovk(&domain, ovk, action, action.cv_net(), &action.encrypted_note().out_ciphertext) { + if let Some((_, _, memo)) = try_output_recovery_with_ovk( + &domain, + ovk, + action, + action.cv_net(), + &action.encrypted_note().out_ciphertext, + ) { let memo = String::from_utf8_lossy(&memo[..]); if memo.len() == 0 { continue; @@ -2466,18 +2950,22 @@ pub async fn wallet_main(wallet_state: Arc>) { // .filter_map(|acc| acc.ok()) // .filter_map(|acc| acc) // .collect(); - let ufvks: Vec = wallet.accounts - .iter() - .map(|acc| acc.ufvk.clone()) - .collect(); + let ufvks: Vec = + wallet.accounts.iter().map(|acc| acc.ufvk.clone()).collect(); for txid in &txids { - let filter = TxFilter{ hash: txid.as_ref().to_vec(), ..Default::default() }; - let Ok(rawtx) = client.get_transaction(filter).await else { continue; }; + let filter = TxFilter { + hash: txid.as_ref().to_vec(), + ..Default::default() + }; + let Ok(rawtx) = client.get_transaction(filter).await else { + continue; + }; let rawtx = rawtx.into_inner(); let block_h = LRZBlockHeight::from_u32(rawtx.height as u32); - let Ok(tx) = Transaction::read(&*rawtx.data, BranchId::for_height(¶ms, block_h)) else { + let Ok(tx) = Transaction::read(&*rawtx.data, BranchId::for_height(¶ms, block_h)) + else { continue; }; @@ -2489,7 +2977,11 @@ pub async fn wallet_main(wallet_state: Arc>) { let txdata = &tx.into_data(); for ufvk in &ufvks { let uivk = ufvk.to_unified_incoming_viewing_key(); - let possible_orchard_ivk = if let Some(orchard_ivk) = uivk.orchard() { Some(orchard_ivk.prepare()) } else { None }; + let possible_orchard_ivk = if let Some(orchard_ivk) = uivk.orchard() { + Some(orchard_ivk.prepare()) + } else { + None + }; if let Some(orchard_ivk) = possible_orchard_ivk { let m: Vec = try_get_orchard_memos(txdata, &orchard_ivk) .iter() @@ -2503,10 +2995,11 @@ pub async fn wallet_main(wallet_state: Arc>) { } txid_map.insert(*txid, (action.clone(), memos)); - let mut memos = Vec::new(); for ufvk in &ufvks { - let Some(ufvk_orchard) = ufvk.orchard() else { continue; }; + let Some(ufvk_orchard) = ufvk.orchard() else { + continue; + }; let ovk = ufvk_orchard.to_ovk(orchard::keys::Scope::External); let m: Vec = try_get_orchard_sent_memos(txdata, &ovk) .iter() @@ -2694,12 +3187,24 @@ pub async fn wallet_main(wallet_state: Arc>) { let (seed, miner_usk) = stuff_from_seed_phrase(network, "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" ); - let (miner_wallet, miner_account) = wallet_from_stuff(network, "miner", Secret::new(seed.expose_secret().clone())); + let (miner_wallet, miner_account) = + wallet_from_stuff(network, "miner", Secret::new(seed.expose_secret().clone())); let (miner_t_addr, miner_ua) = addrs_from_account(&miner_account, 0).unwrap(); let miner_t_addr_str = miner_t_addr.encode(network); let (miner_pubkey, miner_privkey) = transparent_keys_from_usk(&miner_usk, 0).unwrap(); - (miner_wallet, miner_account, seed, miner_usk, miner_pubkey, miner_privkey, miner_t_addr, miner_ua, HashMap::, Vec)>::new(), HashMap::, Vec)>::new()) + ( + miner_wallet, + miner_account, + seed, + miner_usk, + miner_pubkey, + miner_privkey, + miner_t_addr, + miner_ua, + HashMap::, Vec)>::new(), + HashMap::, Vec)>::new(), + ) }; let ( @@ -2714,11 +3219,17 @@ pub async fn wallet_main(wallet_state: Arc>) { mut user_txid_map, ) = { // roundtrip seed through mnemonic phrase - let mnemonic = bip39::Mnemonic::from_entropy_in(bip39::Language::English, &global_seed).unwrap(); - let phrase = mnemonic.words().map(|s| s.to_string()).collect::>().join(" "); + let mnemonic = + bip39::Mnemonic::from_entropy_in(bip39::Language::English, &global_seed).unwrap(); + let phrase = mnemonic + .words() + .map(|s| s.to_string()) + .collect::>() + .join(" "); let (seed, user_usk) = stuff_from_seed_phrase(network, &phrase); - let (user_wallet, user_account) = wallet_from_stuff(network, "user", Secret::new(seed.expose_secret().clone())); + let (user_wallet, user_account) = + wallet_from_stuff(network, "user", Secret::new(seed.expose_secret().clone())); let (user_t_addr, user_ua) = addrs_from_account(&user_account, 0).unwrap(); let user_t_addr_str = user_t_addr.encode(network); let (user_pubkey, user_privkey) = transparent_keys_from_usk(&user_usk, 0).unwrap(); @@ -2727,20 +3238,44 @@ pub async fn wallet_main(wallet_state: Arc>) { // NOTE: the default isn't the same as below, but I think this is because it forces a diversifier index // println!("User wallet: {}/{:?}", user_t_addr_str, user_t_addr1.encode(network)); - (user_wallet, user_account, seed, user_usk, user_pubkey, user_privkey, user_t_addr, user_ua, HashMap::, Vec)>::new()) + ( + user_wallet, + user_account, + seed, + user_usk, + user_pubkey, + user_privkey, + user_t_addr, + user_ua, + HashMap::, Vec)>::new(), + ) }; + let miner_ua_regtest = miner_ua.encode(&LocalNetwork { + overwinter: Some(zcash_protocol::consensus::BlockHeight::from_u32(1)), + sapling: Some(zcash_protocol::consensus::BlockHeight::from_u32(1)), + blossom: Some(zcash_protocol::consensus::BlockHeight::from_u32(1)), + heartwood: Some(zcash_protocol::consensus::BlockHeight::from_u32(1)), + canopy: Some(zcash_protocol::consensus::BlockHeight::from_u32(1)), + nu5: Some(zcash_protocol::consensus::BlockHeight::from_u32(1)), + nu6: Some(zcash_protocol::consensus::BlockHeight::from_u32(1)), + nu6_1: None, + }); + let user_ua_str = user_ua.encode(network); println!("*************************"); println!("MINER WALLET T-ADDRESS: {}", miner_ua.encode(network)); - println!("MINER WALLET ADDRESS: {}", miner_t_address.encode(network)); + println!( + "MINER WALLET ADDRESS: {}", + miner_t_address.encode(network) + ); + println!("MINER WALLET ADDRESS (REGTEST): {}", miner_ua_regtest); println!("USER WALLET T-ADDRESS: {}", user_t_address.encode(network)); println!("USER WALLET ADDRESS: {}", user_ua_str); println!("*************************"); wallet_state.lock().unwrap().user_recv_ua = user_ua_str; - println!("waiting for zaino to be ready..."); wait_for_zainod().await; ////////////////////////////////////////////////////////////////////////////////// @@ -2749,14 +3284,20 @@ pub async fn wallet_main(wallet_state: Arc>) { let mut zaino_port = 0; loop { zaino_port = *wallet_main_zaino_port.lock().unwrap(); - if zaino_port != 0 { break; } + if zaino_port != 0 { + break; + } tokio::time::sleep(std::time::Duration::from_millis(25)).await; } // @todo(judah): investigate why requests get randomly dropped in a strange way: // transport error, service not ready, etc. let mut client = loop { - if let Ok(channel) = Channel::from_shared(format!("http://localhost:{}", zaino_port)).unwrap().connect().await { + if let Ok(channel) = Channel::from_shared(format!("http://localhost:{}", zaino_port)) + .unwrap() + .connect() + .await + { break CompactTxStreamerClient::new(channel); } @@ -2766,21 +3307,31 @@ pub async fn wallet_main(wallet_state: Arc>) { // NOTE: current model is to reorg this many blocks back // ALT: have checkpoints every 16/32 blocks and always sync from the start of one of these const MAX_BLOCKS_TO_DOWNLOAD_AT_TIME: u64 = 64; - let mut time_since_last_transparent_shielded = std::time::Instant::now() - std::time::Duration::from_secs(1000); + let mut time_since_last_transparent_shielded = + std::time::Instant::now() - std::time::Duration::from_secs(1000); - let (mut user_use_i, mut user_update_i) = (0,0); - let (mut miner_use_i, mut miner_update_i) = (0,0); + let (mut user_use_i, mut user_update_i) = (0, 0); + let (mut miner_use_i, mut miner_update_i) = (0, 0); // let mut user_wallets = [user_wallet_init, WalletDb::for_path(":memory:", network, SystemClock, OsRng).unwrap()]; // let mut miner_wallets = [miner_wallet_init, WalletDb::for_path(":memory:", network, SystemClock, OsRng).unwrap()]; - let mut user_wallets = [user_wallet_init]; + let mut user_wallets = [user_wallet_init]; let mut miner_wallets = [miner_wallet_init]; - let mut stupid_thing_because_judah_is_tired_and_wants_this_to_work_properly = Vec::::new(); + let mut stupid_thing_because_judah_is_tired_and_wants_this_to_work_properly = + Vec::::new(); let genesis_hash = loop { - match client.get_block(BlockId { height: 0, hash: Vec::new() }).await { - Ok(block) => { break <[u8;32]>::try_from(&block.into_inner().hash[..]).unwrap() }, - Err(err) => { print!("failed to get genesis block") }, + match client + .get_block(BlockId { + height: 0, + hash: Vec::new(), + }) + .await + { + Ok(block) => break <[u8; 32]>::try_from(&block.into_inner().hash[..]).unwrap(), + Err(err) => { + print!("failed to get genesis block") + } } tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; }; @@ -2789,7 +3340,10 @@ pub async fn wallet_main(wallet_state: Arc>) { let mut pow_cache = PoWCache::new(0, genesis_hash); // NOTE: checkpoints allow us to reset the tree after a reorg & also create spend anchors const CHECKPOINTS_N: usize = 100; - let mut orchard_tree = OrchardShardTree::new(shardtree::store::memory::MemoryShardStore::empty(), CHECKPOINTS_N); + let mut orchard_tree = OrchardShardTree::new( + shardtree::store::memory::MemoryShardStore::empty(), + CHECKPOINTS_N, + ); orchard_tree.checkpoint(BlockHeight(0)).unwrap(); const MAX_TXS_TO_DOWNLOAD_AT_TIME: u64 = 64; @@ -2815,7 +3369,9 @@ pub async fn wallet_main(wallet_state: Arc>) { Ok(Some(tx)) => { // println!("MEMPOOL: got new message"); if let Err(err) = mempool_send.send(tx).await { - println!("MEMPOOL ERROR: can't send message to channel: {err:?}"); + println!( + "MEMPOOL ERROR: can't send message to channel: {err:?}" + ); break; } } @@ -2851,29 +3407,42 @@ pub async fn wallet_main(wallet_state: Arc>) { // NOTE: this is desynced from local_tip because we need to speculatively request blocks // further back than the chain divergence on reorg to find out where it occurred - let mut req_start_h = pow_cache.next_tip_h-1; + let mut req_start_h = pow_cache.next_tip_h - 1; // NOTE: if you're dealing with multiple wallets, you don't want to resync all blocks for each // of them. They can all sync from the same blocks. // TODO: this needs to be a bit more complicated to handle arbitrarily many transparent addresses - let (new_blocks, miner_t_txs, mut sync_from_i, req_rng, prev_tip_chain_state): - (Vec, Vec<(BlockHeight, Transaction)>, Option, (u64, u64), ChainState) = - 'sync_find_continuation_point: loop - { + let (new_blocks, miner_t_txs, mut sync_from_i, req_rng, prev_tip_chain_state): ( + Vec, + Vec<(BlockHeight, Transaction)>, + Option, + (u64, u64), + ChainState, + ) = 'sync_find_continuation_point: loop { // GET THE CURRENT STATE OF THE WORLD //////////////////// // BATCH NETWORK REQUESTS let (tree_state_res, lightd_res, block_range_res, t_txs_res, req_rng, t_req_rng) = { use std::future::Ready; // NOTE: clients are cheap to clone, and this is recommended in docs: // REF: https://docs.rs/tonic/0.14.2/tonic/client/index.html - let (mut client0, mut client1, mut client2) = (client.clone(), client.clone(), client.clone()); + let (mut client0, mut client1, mut client2) = + (client.clone(), client.clone(), client.clone()); fn block_rng_from_heights(heights: (u64, u64)) -> BlockRange { BlockRange { - start: Some(BlockId { height: heights.0, hash: Vec::new() }), - end: Some(BlockId { height: heights.1, hash: Vec::new() }), + start: Some(BlockId { + height: heights.0, + hash: Vec::new(), + }), + end: Some(BlockId { + height: heights.1, + hash: Vec::new(), + }), } } - let req_rng = (req_start_h + 1, req_start_h + MAX_BLOCKS_TO_DOWNLOAD_AT_TIME); + let req_rng = ( + req_start_h + 1, + req_start_h + MAX_BLOCKS_TO_DOWNLOAD_AT_TIME, + ); // ******************************************************************************** // TODO IMPORTANT: the indexer can "succeed" without actually giving us all the txs @@ -2882,12 +3451,21 @@ pub async fn wallet_main(wallet_state: Arc>) { // LRZ/Zaino/Zebra *should* return an error when iterating through the t_txs // they also *shouldn't* return out-of-range responses // ******************************************************************************** - let t_req_rng = (req_rng.1.saturating_sub(2*MAX_BLOCKS_TO_DOWNLOAD_AT_TIME).max(1), req_rng.1); + let t_req_rng = ( + req_rng + .1 + .saturating_sub(2 * MAX_BLOCKS_TO_DOWNLOAD_AT_TIME) + .max(1), + req_rng.1, + ); // println!("cache at {}, downloading blocks: {}-{}", // pow_cache.next_tip_h-1, block_range.start.clone().unwrap().height, block_range.end.clone().unwrap().height); let (tree_state_res, lightd_res, block_range_res, t_txs_res) = tokio::join!( - client.get_tree_state(BlockId {height: req_start_h, hash: Vec::new()}), + client.get_tree_state(BlockId { + height: req_start_h, + hash: Vec::new() + }), client0.get_lightd_info(Empty {}), client1.get_block_range(block_rng_from_heights(req_rng)), client2.get_taddress_txids(TransparentAddressBlockFilter { @@ -2895,15 +3473,22 @@ pub async fn wallet_main(wallet_state: Arc>) { range: Some(block_rng_from_heights(t_req_rng)), }) ); - (tree_state_res, lightd_res, block_range_res, t_txs_res, req_rng, t_req_rng) + ( + tree_state_res, + lightd_res, + block_range_res, + t_txs_res, + req_rng, + t_req_rng, + ) }; //- ROSTER // TODO: batch - match client.get_roster(Empty{}).await { + match client.get_roster(Empty {}).await { Err(err) => println!("Get roster error: {err:?}"), Ok(res) => { - use std::io::{ Cursor,Read }; + use std::io::{Cursor, Read}; let roster_bytes = res.into_inner().data; let mut ok = roster_bytes.len() > 0; @@ -2912,7 +3497,11 @@ pub async fn wallet_main(wallet_state: Arc>) { let mut new_roster = Vec::new(); let mut num_buf = [0u8; 8]; 'read: while cur.position() < roster_bytes.len() as u64 { - let mut m = RosterMember{ pub_key: [0;32], voting_power:0, txids: Vec::new() }; + let mut m = RosterMember { + pub_key: [0; 32], + voting_power: 0, + txids: Vec::new(), + }; if let Err(err) = cur.read_exact(&mut m.pub_key) { println!("******* ROSTER DESERIALIZE ERROR: {err:?}"); ok = false; @@ -2934,7 +3523,10 @@ pub async fn wallet_main(wallet_state: Arc>) { let mut voting_power_check = 0; let txids_n = u64::from_le_bytes(num_buf); for _ in 0..txids_n { - let mut stake_txid = StakeTxId{ txid:[0;32], zats:0 }; + let mut stake_txid = StakeTxId { + txid: [0; 32], + zats: 0, + }; if let Err(err) = cur.read_exact(&mut stake_txid.txid) { println!("******* ROSTER DESERIALIZE ERROR: {err:?}"); ok = false; @@ -2952,7 +3544,10 @@ pub async fn wallet_main(wallet_state: Arc>) { if m.voting_power != voting_power_check { // TODO: use manually-found one? - println!("******* RECEIVED ROSTER VOTING POWER INACCURATE: {} vs {}", m.voting_power, voting_power_check); + println!( + "******* RECEIVED ROSTER VOTING POWER INACCURATE: {} vs {}", + m.voting_power, voting_power_check + ); // ok = false; // break; } @@ -2966,12 +3561,12 @@ pub async fn wallet_main(wallet_state: Arc>) { let wallet_roster = roster .iter() - .map(|member| WalletRosterMember{ + .map(|member| WalletRosterMember { pub_key: member.pub_key, voting_power: member.voting_power, - txids: member.txids.clone() + txids: member.txids.clone(), }) - .collect::>() + .collect::>() .clone(); println!("*********** WALLET ROSTER: {wallet_roster:?}"); wallet_state.lock().unwrap().roster = wallet_roster; @@ -2979,7 +3574,6 @@ pub async fn wallet_main(wallet_state: Arc>) { } println!("*********** ROSTER: {roster:?}"); - // NETWORK TIP HEIGHT // NOTE: I think this is only needed for telling the user how sync'd we are? match lightd_res { @@ -2992,17 +3586,19 @@ pub async fn wallet_main(wallet_state: Arc>) { // AFAICT there's no downside to updating these as frequently as possible, even if the // rest of sync is lagging behind - for wallet in [&mut miner_wallets[miner_use_i], &mut user_wallets[user_use_i]] { + for wallet in [ + &mut miner_wallets[miner_use_i], + &mut user_wallets[user_use_i], + ] { wallet.chain_tip_h = BlockHeight(network_tip_h); } - }, + } Err(err) => { println!("Failed to get lightd info: {err:?}"); continue 'outer_sync; // TODO: don't continue if it's not actually critical } } - // PREV CHAIN STATE // TODO: do we need to redownload this? surely we have it locally (and catch reorgs without it) // maybe we don't get enough info to compute it in CompactBlocks... @@ -3026,7 +3622,13 @@ pub async fn wallet_main(wallet_state: Arc>) { // START DOWNLOADS FOR FULL TRANSACTIONS // TODO: fairer requests - for (wallet_i, wallet) in [&mut miner_wallets[miner_use_i], &mut user_wallets[user_use_i]].into_iter().enumerate() { + for (wallet_i, wallet) in [ + &mut miner_wallets[miner_use_i], + &mut user_wallets[user_use_i], + ] + .into_iter() + .enumerate() + { for tx in &wallet.txs { // TODO: trigger whenever we see a tx we want more info on if in_flight_tx_requests.len() >= MAX_TXS_TO_DOWNLOAD_AT_TIME as usize { @@ -3042,7 +3644,10 @@ pub async fn wallet_main(wallet_state: Arc>) { let mut client = client.clone(); let txid = tx.txid; - let filter = TxFilter{ hash: txid.as_ref().to_vec(), ..Default::default() }; + let filter = TxFilter { + hash: txid.as_ref().to_vec(), + ..Default::default() + }; let _abort_handle = in_flight_tx_join_set.spawn(async move { let str = format!("download {txid:?}"); let tz = Timer::scope(&str); @@ -3054,8 +3659,8 @@ pub async fn wallet_main(wallet_state: Arc>) { println!("get transaction: {err:?}"); None } - Ok(v) => Some(v.into_inner()) - } + Ok(v) => Some(v.into_inner()), + }, ) }); } @@ -3068,10 +3673,9 @@ pub async fn wallet_main(wallet_state: Arc>) { Ok(blocks) => { let mut block_stream = blocks.into_inner(); loop { - match block_stream.message().await { // TODO: bulk await these? - Ok(Some(block)) => { - new_blocks.push(block) - } + match block_stream.message().await { + // TODO: bulk await these? + Ok(Some(block)) => new_blocks.push(block), Ok(None) => break, Err(err) => { if err.code() != tonic::Code::OutOfRange { @@ -3101,22 +3705,28 @@ pub async fn wallet_main(wallet_state: Arc>) { // and that also provides another race condition for out-of-sync data. // TODO: determine whether we actually need tree/chain state (commitment trees etc) for syncing // for i in 0..new_blocks.len() - let Ok(prev_hash) = <[u8;32]>::try_from(&new_blocks[i].prev_hash[..]) else { - println!("invalid prev_hash for compact block at height {}: {}", new_blocks[i].height, LESlice(&new_blocks[i].prev_hash)); + let Ok(prev_hash) = <[u8; 32]>::try_from(&new_blocks[i].prev_hash[..]) else { + println!( + "invalid prev_hash for compact block at height {}: {}", + new_blocks[i].height, + LESlice(&new_blocks[i].prev_hash) + ); continue 'outer_sync; }; - let expected_prev_hash = pow_cache.hash_at_h(new_blocks[i].height-1); + let expected_prev_hash = pow_cache.hash_at_h(new_blocks[i].height - 1); if i == 0 { let mut needs_resync = false; if prev_hash != prev_tip_chain_state.block_hash().0 { println!("non-atomic API meant block range & chain-state are torn reads: {} vs {}", LEHash(prev_hash), LEHash(prev_tip_chain_state.block_hash().0)); - req_start_h = req_start_h.saturating_sub(MAX_BLOCKS_TO_DOWNLOAD_AT_TIME / 2); + req_start_h = + req_start_h.saturating_sub(MAX_BLOCKS_TO_DOWNLOAD_AT_TIME / 2); needs_resync = true; } if Some(prev_hash) != expected_prev_hash { println!("reorg occurred before height {}; hash mismatch {prev_hash:?} vs {expected_prev_hash:?}", new_blocks[0].height); - req_start_h = req_start_h.saturating_sub(MAX_BLOCKS_TO_DOWNLOAD_AT_TIME); + req_start_h = + req_start_h.saturating_sub(MAX_BLOCKS_TO_DOWNLOAD_AT_TIME); needs_resync = true; } if needs_resync { @@ -3133,32 +3743,43 @@ pub async fn wallet_main(wallet_state: Arc>) { // } } - for block_i in first_new_block_i..new_blocks.len() { // TODO: more data validation? let mut data_is_invalid = false; - let hash = if let Ok(hash) = <[u8;32]>::try_from(&new_blocks[block_i].hash[..]) { + let hash = if let Ok(hash) = <[u8; 32]>::try_from(&new_blocks[block_i].hash[..]) + { hash } else { - println!("invalid hash for compact block at height {}: {}", new_blocks[block_i].height, LESlice(&new_blocks[block_i].hash)); + println!( + "invalid hash for compact block at height {}: {}", + new_blocks[block_i].height, + LESlice(&new_blocks[block_i].hash) + ); data_is_invalid = true; - [0;32] + [0; 32] }; if ::try_from(new_blocks[block_i].height).is_err() { - println!("block height cannot be stored in 32 bits: {}", new_blocks[block_i].height); + println!( + "block height cannot be stored in 32 bits: {}", + new_blocks[block_i].height + ); data_is_invalid = true; } for tx in &new_blocks[block_i].vtx { - if <[u8;32]>::try_from(&new_blocks[block_i].hash[..]).is_err() { + if <[u8; 32]>::try_from(&new_blocks[block_i].hash[..]).is_err() { // TODO: are TxIds LE or BE? - println!("invalid hash for compact tx at height {}: {:?}", new_blocks[block_i].height, tx.hash); + println!( + "invalid hash for compact tx at height {}: {:?}", + new_blocks[block_i].height, tx.hash + ); data_is_invalid = true; break; } } let new_tip_h = new_blocks[block_i].height; - let cached_prev_hash = pow_cache.hash_at_h(new_tip_h-1); - let pre_new_tip_hash = <[u8;32]>::try_from(&new_blocks[block_i].prev_hash[..]).unwrap(); + let cached_prev_hash = pow_cache.hash_at_h(new_tip_h - 1); + let pre_new_tip_hash = + <[u8; 32]>::try_from(&new_blocks[block_i].prev_hash[..]).unwrap(); // println!("pushing {} at {new_tip_h}, prev hash {pre_new_tip_hash:?} vs cached prev {cached_prev_hash:?}", LEHash(hash)); if let Some(cached_prev_hash) = cached_prev_hash { if (cached_prev_hash != pre_new_tip_hash) { @@ -3188,9 +3809,22 @@ pub async fn wallet_main(wallet_state: Arc>) { if sync_from_i.is_none() { // println!("nothing to sync"); - break (Vec::new(), Vec::new(), None, req_rng, ChainState::empty(LRZBlockHeight::from_u32(0), zcash_primitives::block::BlockHash([0; 32]))); + break ( + Vec::new(), + Vec::new(), + None, + req_rng, + ChainState::empty( + LRZBlockHeight::from_u32(0), + zcash_primitives::block::BlockHash([0; 32]), + ), + ); } - println!("downloaded compact blocks {}-{}", new_blocks.first().unwrap().height, new_blocks.last().unwrap().height); + println!( + "downloaded compact blocks {}-{}", + new_blocks.first().unwrap().height, + new_blocks.last().unwrap().height + ); let compact_block_max_h = new_blocks.last().expect("non-empty vector").height; @@ -3202,7 +3836,8 @@ pub async fn wallet_main(wallet_state: Arc>) { Ok(t_txs) => { let mut tx_stream = t_txs.into_inner(); loop { - match tx_stream.message().await { // TODO: bulk await these? + match tx_stream.message().await { + // TODO: bulk await these? Ok(Some(tx)) => { min_t_h = min_t_h.min(tx.height); max_t_h = max_t_h.max(tx.height); @@ -3213,7 +3848,8 @@ pub async fn wallet_main(wallet_state: Arc>) { if err.code() != tonic::Code::OutOfRange { println!("failed to get transparent tx: {err:?}"); // NOTE: this is overly conservative - t_failed_at_h = Some(new_raw_t_txs.last().map_or(0, |tx| tx.height+1)); + t_failed_at_h = + Some(new_raw_t_txs.last().map_or(0, |tx| tx.height + 1)); } break; // we can still update with the txs we got, we don't need to fully reset } @@ -3226,10 +3862,14 @@ pub async fn wallet_main(wallet_state: Arc>) { } }; if new_raw_t_txs.len() > 0 { - println!("downloaded transparent txs at heights {}-{}", min_t_h, max_t_h); + println!( + "downloaded transparent txs at heights {}-{}", + min_t_h, max_t_h + ); } - let mut new_t_txs = Vec::<(BlockHeight, Transaction)>::with_capacity(new_raw_t_txs.len()); + let mut new_t_txs = + Vec::<(BlockHeight, Transaction)>::with_capacity(new_raw_t_txs.len()); for tx_i in 0..new_raw_t_txs.len() { // ******************************************************************************** // TODO IMPORTANT: we can get the mined block hash for txs *individually* if we @@ -3251,7 +3891,7 @@ pub async fn wallet_main(wallet_state: Arc>) { None => break, // read error }; - if ! h.is_in_block() { + if !h.is_in_block() { break; } if u64::from(h.0) > compact_block_max_h { @@ -3259,7 +3899,10 @@ pub async fn wallet_main(wallet_state: Arc>) { break; } - let tx = match Transaction::read(&raw_tx.data[..], BranchId::for_height(network, LRZBlockHeight::from_u32(h.0))) { + let tx = match Transaction::read( + &raw_tx.data[..], + BranchId::for_height(network, LRZBlockHeight::from_u32(h.0)), + ) { Ok(tx) => tx, Err(err) => { println!("failed to read transparent tx at height {h}"); @@ -3298,7 +3941,13 @@ pub async fn wallet_main(wallet_state: Arc>) { } } - break (new_blocks, new_t_txs, sync_from_i, req_rng, prev_tip_chain_state); + break ( + new_blocks, + new_t_txs, + sync_from_i, + req_rng, + prev_tip_chain_state, + ); }; let network_tip_h = user_wallets[user_use_i].chain_tip_h; @@ -3307,18 +3956,21 @@ pub async fn wallet_main(wallet_state: Arc>) { // let mut orchard_tree = incrementalmerkletree::frontier::CommitmentTree::from_frontier(&orchard_frontier); // println!("orchard root at {:?} tree: size={} {:?}", prev_tip_chain_state.block_height(), shard_tree_size(orchard_tree), shard_tree_root(orchard_tree)); - //-- REORG if let Some(start_block_i) = sync_from_i { // the regime is basically "always reorg", but that's often a no-op // truncate wallet for everything below height - let sync_start_h = ::try_from(new_blocks[start_block_i].height).expect("successfully converted above"); + let sync_start_h = ::try_from(new_blocks[start_block_i].height) + .expect("successfully converted above"); let block_h = BlockHeight(sync_start_h); let last_block_h = block_h.sat_sub(1); orchard_tree.truncate_to_checkpoint(&last_block_h); // N.B. checkpoints are at the *end* of their block - let (user_wallet, miner_wallet) = (&mut user_wallets[user_use_i], &mut miner_wallets[miner_use_i]); + let (user_wallet, miner_wallet) = ( + &mut user_wallets[user_use_i], + &mut miner_wallets[miner_use_i], + ); for (wallet_i, wallet) in [miner_wallet, user_wallet].into_iter().enumerate() { //-- INVALIDATE TXS >= NEW BLOCKS HEIGHT for account in &mut wallet.accounts { @@ -3326,37 +3978,69 @@ pub async fn wallet_main(wallet_state: Arc>) { account.fully_decoded_h = account.fully_decoded_h.min(last_block_h); // TODO: do we want to track balance changes or keep balances updated as chain changes occur? - let truncate_to_i = account.balance_changes.partition_point(|(b,_)| *b < block_h); + let truncate_to_i = account + .balance_changes + .partition_point(|(b, _)| *b < block_h); account.balance_changes.truncate(truncate_to_i); //- UNRECEIVE NOTES - let utxos_at_h_start = account.utxos.partition_point(|txo| txo.recv_h < block_h); + let utxos_at_h_start = + account.utxos.partition_point(|txo| txo.recv_h < block_h); account.utxos.truncate(utxos_at_h_start); - let recv_txos_at_h_start = account.recv_txos.partition_point(|txo| txo.recv_h < block_h); + let recv_txos_at_h_start = account + .recv_txos + .partition_point(|txo| txo.recv_h < block_h); account.recv_txos.truncate(recv_txos_at_h_start); - let unspent_orchard_notes_at_h_start = account.unspent_orchard_notes.partition_point(|txo| txo.recv_h < block_h); - account.unspent_orchard_notes.truncate(unspent_orchard_notes_at_h_start); - let recv_orchard_notes_at_h_start = account.recv_orchard_notes.partition_point(|txo| txo.recv_h < block_h); - account.recv_orchard_notes.truncate(recv_orchard_notes_at_h_start); + let unspent_orchard_notes_at_h_start = account + .unspent_orchard_notes + .partition_point(|txo| txo.recv_h < block_h); + account + .unspent_orchard_notes + .truncate(unspent_orchard_notes_at_h_start); + let recv_orchard_notes_at_h_start = account + .recv_orchard_notes + .partition_point(|txo| txo.recv_h < block_h); + account + .recv_orchard_notes + .truncate(recv_orchard_notes_at_h_start); //- UNSPEND NOTES // NOTE: spent notes are in spend_h order, NOT recv_h order - let stxos_at_h_start = account.stxos.partition_point(|txo| txo.spent_h < block_h); + let stxos_at_h_start = + account.stxos.partition_point(|txo| txo.spent_h < block_h); for stxo in &account.stxos[stxos_at_h_start..] { if stxo.recv_h < block_h { - txo_recv_h_insert(&mut account.utxos, Txo{ spent_h: BlockHeight(0), ..stxo.clone() }); + txo_recv_h_insert( + &mut account.utxos, + Txo { + spent_h: BlockHeight(0), + ..stxo.clone() + }, + ); } } account.stxos.truncate(stxos_at_h_start); - let spent_orchard_notes_at_h_start = account.spent_orchard_notes.partition_point(|note| note.spent_h < block_h); - for spent_orchard_note in &account.spent_orchard_notes[spent_orchard_notes_at_h_start..] { + let spent_orchard_notes_at_h_start = account + .spent_orchard_notes + .partition_point(|note| note.spent_h < block_h); + for spent_orchard_note in + &account.spent_orchard_notes[spent_orchard_notes_at_h_start..] + { if spent_orchard_note.recv_h < block_h { - orchard_recv_h_insert(&mut account.unspent_orchard_notes, OrchardNote{ spent_h: BlockHeight(0), ..spent_orchard_note.clone() }); + orchard_recv_h_insert( + &mut account.unspent_orchard_notes, + OrchardNote { + spent_h: BlockHeight(0), + ..spent_orchard_note.clone() + }, + ); } } - account.spent_orchard_notes.truncate(spent_orchard_notes_at_h_start); + account + .spent_orchard_notes + .truncate(spent_orchard_notes_at_h_start); } // higher blocks & mempool @@ -3368,7 +4052,6 @@ pub async fn wallet_main(wallet_state: Arc>) { } } - //-- ADD/REVALIDATE TRANSPARENT TXS (and attached shielded data) { // see note above on transparent syncing @@ -3377,7 +4060,10 @@ pub async fn wallet_main(wallet_state: Arc>) { // } else { // req_rng.0.try_into.expect("fits in u32") // }; - let (user_wallet, miner_wallet) = (&mut user_wallets[user_use_i], &mut miner_wallets[miner_use_i]); + let (user_wallet, miner_wallet) = ( + &mut user_wallets[user_use_i], + &mut miner_wallets[miner_use_i], + ); let keys = PreparedKeys::from_ufvk_all(&miner_wallet.accounts[0].ufvk); let mut insert_i = 0; for t_tx_i in 0..miner_t_txs.len() { @@ -3392,7 +4078,10 @@ pub async fn wallet_main(wallet_state: Arc>) { { // TODO: maybe wait until we're ~block-synced before doing this // NOTE: assumes we can keep up... maybe dropping with some feedback about that is better? - let wallets = [&mut user_wallets[user_use_i], &mut miner_wallets[miner_use_i]]; + let wallets = [ + &mut user_wallets[user_use_i], + &mut miner_wallets[miner_use_i], + ]; let keys = [ PreparedKeys::from_ufvk_all(&wallets[0].accounts[0].ufvk), PreparedKeys::from_ufvk_all(&wallets[1].accounts[0].ufvk), @@ -3401,7 +4090,10 @@ pub async fn wallet_main(wallet_state: Arc>) { while let Ok(raw_tx) = mempool_recv.try_recv() { // NOTE: expected LRZ height different from abstract mempool height - match Transaction::read(&raw_tx.data[..], BranchId::for_height(network, LRZBlockHeight::from_u32(network_tip_h.0 + 1))) { + match Transaction::read( + &raw_tx.data[..], + BranchId::for_height(network, LRZBlockHeight::from_u32(network_tip_h.0 + 1)), + ) { Err(err) => { println!("invalid mempool tx: {err:?}"); // NOTE: as mempool txs are not sequenced, it seems reasonable to just ignore @@ -3409,21 +4101,35 @@ pub async fn wallet_main(wallet_state: Arc>) { } Ok(tx) => { for i in 0..2 { - read_full_tx(wallets[i], 0, &keys[i], BlockHeight::MEMPOOL, &tx, insert_idxs[i], false); + read_full_tx( + wallets[i], + 0, + &keys[i], + BlockHeight::MEMPOOL, + &tx, + insert_idxs[i], + false, + ); } } } } } - //-- ADD/REVALIDATE SHIELDED TXS FROM NEW BLOCKS if let Some(start_block_i) = sync_from_i { - let (user_wallet, miner_wallet) = (&mut user_wallets[user_use_i], &mut miner_wallets[miner_use_i]); - let sync_start_h = ::try_from(new_blocks[start_block_i].height).expect("successfully converted above"); - println!("cache at {}, new blocks: {}-{}; updating wallets...", - pow_cache.next_tip_h-1, new_blocks.first().unwrap().height, new_blocks.last().unwrap().height); - + let (user_wallet, miner_wallet) = ( + &mut user_wallets[user_use_i], + &mut miner_wallets[miner_use_i], + ); + let sync_start_h = ::try_from(new_blocks[start_block_i].height) + .expect("successfully converted above"); + println!( + "cache at {}, new blocks: {}-{}; updating wallets...", + pow_cache.next_tip_h - 1, + new_blocks.first().unwrap().height, + new_blocks.last().unwrap().height + ); for (wallet_i, wallet) in [miner_wallet, user_wallet].into_iter().enumerate() { let keys = PreparedKeys::from_ufvk_ivks(&wallet.accounts[0].ufvk); // NOTE: can't use ovk for CompactTx @@ -3432,10 +4138,17 @@ pub async fn wallet_main(wallet_state: Arc>) { let block_h = BlockHeight(block.height.try_into().unwrap()); update_insert_i(&wallet.txs, &mut insert_i, block_h); - //-- INCORPORATE SHIELDED TRANSACTIONS FROM COMPACT BLOCK 'tx_iter: for tx in &block.vtx { - if let (txid, true, true) = read_compact_tx(wallet, 0, &keys, block_h, tx, &mut insert_i, &mut orchard_tree) { + if let (txid, true, true) = read_compact_tx( + wallet, + 0, + &keys, + block_h, + tx, + &mut insert_i, + &mut orchard_tree, + ) { println!("found our compact tx: {txid:?}"); } } @@ -3448,20 +4161,26 @@ pub async fn wallet_main(wallet_state: Arc>) { } } - //-- READ ANY DOWNLOADED FULL TXS if in_flight_tx_requests.len() > 0 { - println!("before reading, there are {} in flight tx downloads", in_flight_tx_requests.len()); - let (user_wallet, miner_wallet) = (&mut user_wallets[user_use_i], &mut miner_wallets[miner_use_i]); + println!( + "before reading, there are {} in flight tx downloads", + in_flight_tx_requests.len() + ); + let (user_wallet, miner_wallet) = ( + &mut user_wallets[user_use_i], + &mut miner_wallets[miner_use_i], + ); let wallets = [miner_wallet, user_wallet]; while let Some(tx_completion) = in_flight_tx_join_set.try_join_next() { - let (txid, wallet_i, dl_result): (TxId, usize, Option) = match tx_completion { - Ok(v) => v, - Err(err) => { - println!("tx completion join error: {err:?}"); - continue - } - }; + let (txid, wallet_i, dl_result): (TxId, usize, Option) = + match tx_completion { + Ok(v) => v, + Err(err) => { + println!("tx completion join error: {err:?}"); + continue; + } + }; // ALT: do (most of) this work in the task // ALT: we have the option for best-chain/height info of: @@ -3473,27 +4192,42 @@ pub async fn wallet_main(wallet_state: Arc>) { // gets fixed println!("download finished for {txid:?}"); in_flight_tx_requests.remove(&txid); - if let (Some(raw_tx), Some(existing_tx_i)) = (dl_result, tx_position(&wallets[wallet_i], &txid)) { + if let (Some(raw_tx), Some(existing_tx_i)) = + (dl_result, tx_position(&wallets[wallet_i], &txid)) + { let existing_tx = &wallets[wallet_i].txs[existing_tx_i]; let found_h = match bc_h_from_raw_tx_h(raw_tx.height) { Some(None) => existing_tx.mined_h, // on sidechain: use previously-spec'd height Some(Some(h)) => { if h != existing_tx.mined_h { - println!("requested tx {txid:?} has moved from {:?} to {h:?}", existing_tx.mined_h); + println!( + "requested tx {txid:?} has moved from {:?} to {h:?}", + existing_tx.mined_h + ); } h - }, + } None => continue, // read error }; // NOTE: there's a potential inconsistency here around branch id changes if we // see it both before and after the change - let lrz_h = LRZBlockHeight::from_u32(if found_h.is_in_block() { found_h.0 } else { network_tip_h.0 }); - let tx = match Transaction::read(&raw_tx.data[..], BranchId::for_height(network, lrz_h)) { + let lrz_h = LRZBlockHeight::from_u32(if found_h.is_in_block() { + found_h.0 + } else { + network_tip_h.0 + }); + let tx = match Transaction::read( + &raw_tx.data[..], + BranchId::for_height(network, lrz_h), + ) { Ok(tx) => tx, Err(err) => { - println!("failed to read tx at height {:?}/{found_h:?}/{lrz_h:?}", existing_tx.mined_h); + println!( + "failed to read tx at height {:?}/{found_h:?}/{lrz_h:?}", + existing_tx.mined_h + ); continue; } }; @@ -3501,19 +4235,45 @@ pub async fn wallet_main(wallet_state: Arc>) { println!("reading downloaded full tx for {txid:?}"); let keys = PreparedKeys::from_ufvk_all(&wallets[wallet_i].accounts[0].ufvk); - read_full_tx(wallets[wallet_i], 0, &keys, existing_tx.mined_h, &tx, &mut 0, existing_tx.is_outside_bc); + read_full_tx( + wallets[wallet_i], + 0, + &keys, + existing_tx.mined_h, + &tx, + &mut 0, + existing_tx.is_outside_bc, + ); } } - println!("after reading, there are {} in flight tx downloads", in_flight_tx_requests.len()); + println!( + "after reading, there are {} in flight tx downloads", + in_flight_tx_requests.len() + ); } //-- SEND DATA TO UI { - let (user_wallet, miner_wallet) = (&mut user_wallets[user_use_i], &mut miner_wallets[miner_use_i]); - println!("miner unspent UTXOs {:#?}", NL(&*miner_wallet.accounts[0].utxos)); - println!("miner spent UTXOs {:#?}", NL(&*miner_wallet.accounts[0].stxos)); - println!("miner unspent notes {:#?}", NL(&*miner_wallet.accounts[0].unspent_orchard_notes)); - println!("miner spent notes {:#?}", NL(&*miner_wallet.accounts[0].spent_orchard_notes)); + let (user_wallet, miner_wallet) = ( + &mut user_wallets[user_use_i], + &mut miner_wallets[miner_use_i], + ); + println!( + "miner unspent UTXOs {:#?}", + NL(&*miner_wallet.accounts[0].utxos) + ); + println!( + "miner spent UTXOs {:#?}", + NL(&*miner_wallet.accounts[0].stxos) + ); + println!( + "miner unspent notes {:#?}", + NL(&*miner_wallet.accounts[0].unspent_orchard_notes) + ); + println!( + "miner spent notes {:#?}", + NL(&*miner_wallet.accounts[0].spent_orchard_notes) + ); let mut miner_unshielded_funds = 0; let mut miner_shielded_pending_funds = 0; @@ -3558,7 +4318,6 @@ pub async fn wallet_main(wallet_state: Arc>) { lock.pending_balance = user_pending_balance as i64; } - // Anchor debugging // { // let (user_wallet, miner_wallet) = (&mut user_wallets[user_use_i], &mut miner_wallets[miner_use_i]); @@ -3577,9 +4336,20 @@ pub async fn wallet_main(wallet_state: Arc>) { // } if faucet_shield_cooldown_instant.elapsed().as_secs() > 5 { - let (user_wallet, miner_wallet) = (&mut user_wallets[user_use_i], &mut miner_wallets[miner_use_i]); - - let maybe_shield_txid = miner_wallet.shield_transparent_zats(network, &mut client, &miner_usk, 1000000000, &orchard_tree).await; + let (user_wallet, miner_wallet) = ( + &mut user_wallets[user_use_i], + &mut miner_wallets[miner_use_i], + ); + + let maybe_shield_txid = miner_wallet + .shield_transparent_zats( + network, + &mut client, + &miner_usk, + 1000000000, + &orchard_tree, + ) + .await; println!("Try miner shield txid: {:?}", maybe_shield_txid); faucet_shield_cooldown_instant = Instant::now(); } @@ -3663,7 +4433,6 @@ pub async fn wallet_main(wallet_state: Arc>) { // continue; // } - // match client.get_roster(Empty{}).await { // Err(err) => println!("Get roster error: {err:?}"), // Ok(res) => { @@ -3854,7 +4623,6 @@ pub async fn wallet_main(wallet_state: Arc>) { // _ => None // }; - // println!("WALLET HAS {} ({})) cTAZ", spendable_balance, str_from_ctaz(spendable_balance)); // let txs = if let Ok(mut history) = get_transaction_history(user_wallet) { @@ -4086,16 +4854,33 @@ pub async fn wallet_main(wallet_state: Arc>) { } }; - println!("*** wallet has {:?} actions in flight", wallet_state.actions_in_flight.len()); - let Some(action) = wallet_state.actions_in_flight.front() else { break; }; + println!( + "*** wallet has {:?} actions in flight", + wallet_state.actions_in_flight.len() + ); + let Some(action) = wallet_state.actions_in_flight.front() else { + break; + }; action.clone() }; let ok: bool = 'process_action: { match &action { WalletAction::RequestFromFaucet => { - let (user_wallet, miner_wallet) = (&mut user_wallets[user_use_i], &mut miner_wallets[miner_use_i]); - let maybe_send_txid = miner_wallet.send_orchard_to_orchard_zats(network, &mut client, &miner_usk, 500_000_000, &orchard_tree, user_ua.orchard().unwrap()).await; + let (user_wallet, miner_wallet) = ( + &mut user_wallets[user_use_i], + &mut miner_wallets[miner_use_i], + ); + let maybe_send_txid = miner_wallet + .send_orchard_to_orchard_zats( + network, + &mut client, + &miner_usk, + 500_000_000, + &orchard_tree, + user_ua.orchard().unwrap(), + ) + .await; println!("Try miner send txid: {:?}", maybe_send_txid); match maybe_send_txid { None => { @@ -4105,13 +4890,25 @@ pub async fn wallet_main(wallet_state: Arc>) { Some(_) => { wallet_state.lock().unwrap().waiting_for_faucet = false; true - }, + } } } WalletAction::StakeToFinalizer(amount, target_finalizer) => { - let (user_wallet, miner_wallet) = (&mut user_wallets[user_use_i], &mut miner_wallets[miner_use_i]); - let maybe_send_txid = user_wallet.stake_orchard_to_finalizer(network, &mut client, &user_usk, amount.into_u64(), &orchard_tree, target_finalizer).await; + let (user_wallet, miner_wallet) = ( + &mut user_wallets[user_use_i], + &mut miner_wallets[miner_use_i], + ); + let maybe_send_txid = user_wallet + .stake_orchard_to_finalizer( + network, + &mut client, + &user_usk, + amount.into_u64(), + &orchard_tree, + target_finalizer, + ) + .await; println!("Try miner send txid: {:?}", maybe_send_txid); match maybe_send_txid { None => { @@ -4121,11 +4918,12 @@ pub async fn wallet_main(wallet_state: Arc>) { Some(_) => { wallet_state.lock().unwrap().waiting_for_stake_to_finalizer = false; true - }, + } } } - WalletAction::SendToAddress(address, amount) => { true + WalletAction::SendToAddress(address, amount) => { + true // let Ok(Some(wallet_summary)) = user_wallet.get_wallet_summary(ConfirmationsPolicy::MIN) else { // println!("Failed to get wallet summary"); // break 'process_action false; @@ -4158,7 +4956,8 @@ pub async fn wallet_main(wallet_state: Arc>) { // } } - WalletAction::UnstakeFromFinalizer(txid) => { true + WalletAction::UnstakeFromFinalizer(txid) => { + true // let mut ok = { // User sends unstaking action // let Some((member_pub_key, staked_txid)) = ('find_txid: { // for mem in &roster { @@ -4218,7 +5017,7 @@ pub async fn wallet_main(wallet_state: Arc>) { // ok } - _ => { true } + _ => true, } }; diff --git a/zebra-crosslink/zebra-crosslink/src/lib.rs b/zebra-crosslink/zebra-crosslink/src/lib.rs index 3db19b7e..bdf9a318 100644 --- a/zebra-crosslink/zebra-crosslink/src/lib.rs +++ b/zebra-crosslink/zebra-crosslink/src/lib.rs @@ -12,9 +12,9 @@ use async_trait::async_trait; use strum::{EnumCount, IntoEnumIterator}; use strum_macros::{EnumCount, EnumIter}; -use tenderlink::SortedRosterMember; -use zcash_primitives::transaction::{RosterMember, StakingAction, StakingActionKind, StakeTxId}; use ed25519_zebra::VerificationKeyBytes; +use tenderlink::SortedRosterMember; +use zcash_primitives::transaction::{RosterMember, StakeTxId, StakingAction, StakingActionKind}; use zebra_chain::serialization::{ SerializationError, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize, }; @@ -147,7 +147,10 @@ use zebra_chain::block::{ Block, CountedHeader, Hash as BlockHash, Header as BlockHeader, Height as BlockHeight, }; use zebra_node_services::mempool::{Request as MempoolRequest, Response as MempoolResponse}; -use zebra_state::{crosslink::*, Request as StateRequest, Response as StateResponse, ReadRequest as StateReadRequest, ReadResponse as StateReadResponse}; +use zebra_state::{ + crosslink::*, ReadRequest as StateReadRequest, ReadResponse as StateReadResponse, + Request as StateRequest, Response as StateResponse, +}; /// Placeholder activation height for Crosslink functionality pub const TFL_ACTIVATION_HEIGHT: BlockHeight = BlockHeight(2000); @@ -192,12 +195,20 @@ pub(crate) struct TFLServiceInternal { path_to_pos_store_file: PathBuf, } -fn call_from_state_to_crosslink_to_ask_about_fat_pointers(internal_handle: &TFLServiceHandle, parent_fat_pointer: zebra_chain::block::FatPointerToBftBlock, child_fat_pointer: zebra_chain::block::FatPointerToBftBlock) -> bool { +fn call_from_state_to_crosslink_to_ask_about_fat_pointers( + internal_handle: &TFLServiceHandle, + parent_fat_pointer: zebra_chain::block::FatPointerToBftBlock, + child_fat_pointer: zebra_chain::block::FatPointerToBftBlock, +) -> bool { let mut internal = internal_handle.internal.blocking_lock(); let parent_height = if parent_fat_pointer == zebra_chain::block::FatPointerToBftBlock::null() { 0 } else { - if let Some(h) = internal.bft_blocks.iter().position(|b| b.blake3_hash().0 == parent_fat_pointer.points_at_block_hash()) { + if let Some(h) = internal + .bft_blocks + .iter() + .position(|b| b.blake3_hash().0 == parent_fat_pointer.points_at_block_hash()) + { h } else { return false; @@ -206,7 +217,11 @@ fn call_from_state_to_crosslink_to_ask_about_fat_pointers(internal_handle: &TFLS let child_height = if child_fat_pointer == zebra_chain::block::FatPointerToBftBlock::null() { 0 } else { - if let Some(h) = internal.bft_blocks.iter().position(|b| b.blake3_hash().0 == child_fat_pointer.points_at_block_hash()) { + if let Some(h) = internal + .bft_blocks + .iter() + .position(|b| b.blake3_hash().0 == child_fat_pointer.points_at_block_hash()) + { h } else { return false; @@ -243,11 +258,16 @@ async fn block_height_hash_from_hash( } } -async fn block_from_hash( // @Phillip +async fn block_from_hash( + // @Phillip call: &TFLServiceCalls, hash: BlockHash, ) -> Option> { - if let Ok(StateResponse::Block(Some(block))) = (call.state)(StateRequest::Block(zebra_state::HashOrHeight::Hash(hash.into()))).await { + if let Ok(StateResponse::Block(Some(block))) = (call.state)(StateRequest::Block( + zebra_state::HashOrHeight::Hash(hash.into()), + )) + .await + { let check_hash = block.as_ref().hash(); assert_eq!(hash, check_hash); Some(block) @@ -256,12 +276,16 @@ async fn block_from_hash( // @Phillip } } -async fn is_block_known( // @Phillip +async fn is_block_known( + // @Phillip call: &TFLServiceCalls, hash: BlockHash, ) -> bool { - if let Ok(StateResponse::KnownBlock(Some(known_block))) = (call.state)(StateRequest::KnownBlock(hash.into())).await { - known_block.location == zebra_state::KnownBlockLocation::BestChain || known_block.location == zebra_state::KnownBlockLocation::SideChain + if let Ok(StateResponse::KnownBlock(Some(known_block))) = + (call.state)(StateRequest::KnownBlock(hash.into())).await + { + known_block.location == zebra_state::KnownBlockLocation::BestChain + || known_block.location == zebra_state::KnownBlockLocation::SideChain } else { false } @@ -469,7 +493,13 @@ async fn propose_new_bft_block(tfl_handle: &TFLServiceHandle) -> Option = Vec::new(); new_block.zcash_serialize(&mut append_bytes).unwrap(); fat_pointer.zcash_serialize(&mut append_bytes).unwrap(); - append_bytes.extend_from_slice(&(internal.validators_at_current_height.len() as u64).to_le_bytes()); + append_bytes + .extend_from_slice(&(internal.validators_at_current_height.len() as u64).to_le_bytes()); for v in &internal.validators_at_current_height { v.write_to(&mut append_bytes).unwrap(); } @@ -781,7 +815,10 @@ async fn new_decided_bft_block_from_malachite( for sig in tender_proposal_sigs { append_bytes.extend_from_slice(&sig.0); } - let mut file = OpenOptions::new().append(true).open(&internal.path_to_pos_store_file).unwrap(); + let mut file = OpenOptions::new() + .append(true) + .open(&internal.path_to_pos_store_file) + .unwrap(); file.write_all(&append_bytes).unwrap(); file.flush().unwrap(); } @@ -851,7 +888,12 @@ async fn validate_bft_block_from_malachite( "Didn't have hash available for confirmation: {}", new_final_hash ); - return (tenderlink::TMStatus::Indeterminate, tenderlink::TMStatusReason::NeedsBlock { hash: new_final_hash.0 }); + return ( + tenderlink::TMStatus::Indeterminate, + tenderlink::TMStatusReason::NeedsBlock { + hash: new_final_hash.0, + }, + ); }; return (tenderlink::TMStatus::Pass, tenderlink::TMStatusReason::None); } @@ -993,7 +1035,10 @@ fn update_roster_for_block(internal: &mut TFLServiceInternal, block: &Block) -> { if let Some(staking_action) = staking_action { let txid = tx.unmined_id().mined_id(); - info!("got staking action in txid: {}", StakingAction::str_from_addr(txid.0)); + info!( + "got staking action in txid: {}", + StakingAction::str_from_addr(txid.0) + ); // TODO(Sam) //cmd_c += update_roster_for_cmd(roster, txid.0, validators_keys_to_names, &staking_action); } @@ -1004,7 +1049,11 @@ fn update_roster_for_block(internal: &mut TFLServiceInternal, block: &Block) -> cmd_c } -async fn tfl_service_main_loop(internal_handle: TFLServiceHandle, global_seed: [u8; 32], path_to_pos_store_file: PathBuf) -> Result<(), String> { +async fn tfl_service_main_loop( + internal_handle: TFLServiceHandle, + global_seed: [u8; 32], + path_to_pos_store_file: PathBuf, +) -> Result<(), String> { let call = internal_handle.call.clone(); let config = internal_handle.config.clone(); let params = &PROTOTYPE_PARAMETERS; @@ -1142,7 +1191,12 @@ async fn tfl_service_main_loop(internal_handle: TFLServiceHandle, global_seed: [ .collect(); if path_to_pos_store_file.to_str() != Some("") { - let mut pos_file = OpenOptions::new().read(true).write(true).create(true).open(&path_to_pos_store_file).unwrap(); + let mut pos_file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(&path_to_pos_store_file) + .unwrap(); let mut pos_file_bytes = Vec::new(); pos_file.read_to_end(&mut pos_file_bytes).unwrap(); @@ -1150,43 +1204,86 @@ async fn tfl_service_main_loop(internal_handle: TFLServiceHandle, global_seed: [ let mut valid_byte_count = 0; 'big_loop: loop { valid_byte_count = cursor.position(); - let block = if let Ok(block) = BftBlock::zcash_deserialize(&mut cursor) { block } else { break; }; - let fat_pointer = if let Ok(fat_pointer) = FatPointerToBftBlock2::zcash_deserialize(&mut cursor) { fat_pointer } else { break; }; + let block = if let Ok(block) = BftBlock::zcash_deserialize(&mut cursor) { + block + } else { + break; + }; + let fat_pointer = if let Ok(fat_pointer) = + FatPointerToBftBlock2::zcash_deserialize(&mut cursor) + { + fat_pointer + } else { + break; + }; let mut buf = [0u8; 8]; - if cursor.read_exact(&mut buf).is_err() { break; } + if cursor.read_exact(&mut buf).is_err() { + break; + } let new_roster_count = u64::from_le_bytes(buf); let mut new_roster = Vec::new(); for _ in 0..new_roster_count { if let Ok(v) = MalValidator::read_from(&mut cursor) { new_roster.push(v); - } else { break; } + } else { + break; + } } let mut buf = [0u8; 8]; - if cursor.read_exact(&mut buf).is_err() { break; } + if cursor.read_exact(&mut buf).is_err() { + break; + } let proposal_sigs_n = u64::from_le_bytes(buf); let mut proposal_sigs = Vec::new(); for _ in 0..proposal_sigs_n { let mut sig = tenderlink::TMSig::NIL; - if cursor.read_exact(&mut sig.0).is_err() { break 'big_loop; } + if cursor.read_exact(&mut sig.0).is_err() { + break 'big_loop; + } proposal_sigs.push(sig); } - if block.previous_block_fat_ptr.points_at_block_hash() != fat_pointer_to_tip.points_at_block_hash() { break; } - + if block.previous_block_fat_ptr.points_at_block_hash() + != fat_pointer_to_tip.points_at_block_hash() + { + break; + } + let mut round_data = tenderlink::RoundData::EMPTY; round_data.roster = tenderlink_roster_from_internal(&unsorted_roster); - round_data.msg_val_sigs = round_data.roster.iter().map(|v| fat_pointer.signatures.iter().find(|s| s.public_key == v.pub_key.0).map(|s| s.vote_signature).unwrap_or([0u8; 64])).map(|s| [(tenderlink::ValueId::NIL, tenderlink::TMSig::NIL), (tenderlink::ValueId(fat_pointer.points_at_block_hash().0), tenderlink::TMSig(s))]).collect(); + round_data.msg_val_sigs = round_data + .roster + .iter() + .map(|v| { + fat_pointer + .signatures + .iter() + .find(|s| s.public_key == v.pub_key.0) + .map(|s| s.vote_signature) + .unwrap_or([0u8; 64]) + }) + .map(|s| { + [ + (tenderlink::ValueId::NIL, tenderlink::TMSig::NIL), + ( + tenderlink::ValueId(fat_pointer.points_at_block_hash().0), + tenderlink::TMSig(s), + ), + ] + }) + .collect(); round_data.counts.precommits = fat_pointer.signatures.len() as u64; round_data.counts.yes_precommits = fat_pointer.signatures.len() as u64; round_data.proposal_sigs_n = proposal_sigs_n as usize; round_data.proposal_sigs = proposal_sigs; - round_data.proposal = tenderlink::BlockValue(block.zcash_serialize_to_vec().unwrap()); + round_data.proposal = + tenderlink::BlockValue(block.zcash_serialize_to_vec().unwrap()); round_data.proposal_id = tenderlink::ValueId(fat_pointer.points_at_block_hash().0); round_data.height = ingest_data_for_tenderlink.len() as u64; round_data.round = fat_pointer.get_vote_template().round as u32; - + ingest_data_for_tenderlink.push(round_data); i_bft_blocks.push(block); fat_pointer_to_tip = fat_pointer; @@ -1201,7 +1298,7 @@ async fn tfl_service_main_loop(internal_handle: TFLServiceHandle, global_seed: [ if let Some(new_block) = i_bft_blocks.last() { new_final_hash = new_block.headers.first().expect("at least 1 header").hash(); new_final_height = block_height_from_hash(&call, new_final_hash).await.unwrap(); -//println!("Loaded at pow ({:?}, {:?}) with roster: {:?}", new_final_height, new_final_hash, unsorted_roster); + //println!("Loaded at pow ({:?}, {:?}) with roster: {:?}", new_final_height, new_final_hash, unsorted_roster); } let roster = { @@ -1247,27 +1344,30 @@ async fn tfl_service_main_loop(internal_handle: TFLServiceHandle, global_seed: [ } }) })), - tenderlink::ClosureToPushDecidedBlock(Arc::new(move |block, fat_pointer, tender_proposal_sigs| { - let tfl_handle3 = tfl_handle3.clone(); - Box::pin(async move { - use bytes::Buf; - use zebra_chain::serialization::ZcashDeserialize; - - new_decided_bft_block_from_malachite( - &tfl_handle3, - &BftBlock::zcash_deserialize(block.0.reader()).unwrap(), - &fat_pointer.into(), - tender_proposal_sigs, - ) - .await - }) - })), + tenderlink::ClosureToPushDecidedBlock(Arc::new( + move |block, fat_pointer, tender_proposal_sigs| { + let tfl_handle3 = tfl_handle3.clone(); + Box::pin(async move { + use bytes::Buf; + use zebra_chain::serialization::ZcashDeserialize; + + new_decided_bft_block_from_malachite( + &tfl_handle3, + &BftBlock::zcash_deserialize(block.0.reader()).unwrap(), + &fat_pointer.into(), + tender_proposal_sigs, + ) + .await + }) + }, + )), tenderlink::ClosureToGetHistoricalBlock(Arc::new(move |height| { Box::pin(async move { panic!(); }) })), - tenderlink::ClosureToGetPow(Arc::new(move |hash| { // @Phillip + tenderlink::ClosureToGetPow(Arc::new(move |hash| { + // @Phillip let tfl_handle5 = tfl_handle5.clone(); Box::pin(async move { if let Some(block) = block_from_hash(&tfl_handle5.call, hash.into()).await { @@ -1280,12 +1380,16 @@ async fn tfl_service_main_loop(internal_handle: TFLServiceHandle, global_seed: [ None } } else { - eprintln!("PowLink: \x1b[93mCouldn't find PoW block\x1b[0m for hash {:?}.", hash); + eprintln!( + "PowLink: \x1b[93mCouldn't find PoW block\x1b[0m for hash {:?}.", + hash + ); None } }) })), - tenderlink::ClosureToParsePow(Arc::new(move |hash, bytes| { // @Phillip + tenderlink::ClosureToParsePow(Arc::new(move |hash, bytes| { + // @Phillip Box::pin(async move { let mut slice = &bytes[..]; // let slice_ref = &mut slice; @@ -1303,13 +1407,13 @@ async fn tfl_service_main_loop(internal_handle: TFLServiceHandle, global_seed: [ } }) })), - tenderlink::ClosureIsPoWInChain(Arc::new(move |hash| { // @Phillip + tenderlink::ClosureIsPoWInChain(Arc::new(move |hash| { + // @Phillip let tfl_handle6 = tfl_handle6.clone(); - Box::pin(async move { - is_block_known(&tfl_handle6.call, BlockHash(hash)).await - }) + Box::pin(async move { is_block_known(&tfl_handle6.call, BlockHash(hash)).await }) })), - tenderlink::ClosureToPushPow(Arc::new(move |block| { // @Phillip + tenderlink::ClosureToPushPow(Arc::new(move |block| { + // @Phillip let tfl_handle7 = tfl_handle7.clone(); Box::pin(async move { (tfl_handle7.call.force_feed_pow)(block.clone()).await; @@ -1505,7 +1609,11 @@ async fn tfl_service_incoming_request( internal .validators_at_current_height .iter() - .map(|v| RosterMember{ pub_key:<[u8; 32]>::from(v.public_key), voting_power: v.voting_power, txids:v.txids.clone() }) + .map(|v| RosterMember { + pub_key: <[u8; 32]>::from(v.public_key), + voting_power: v.voting_power, + txids: v.txids.clone(), + }) .collect() })), @@ -1834,7 +1942,12 @@ impl MalValidator { } pub fn push_txid(&mut self, txid: StakeTxId) { - if self.txids.iter().find(|cmp| cmp.txid == txid.txid).is_some() { + if self + .txids + .iter() + .find(|cmp| cmp.txid == txid.txid) + .is_some() + { return; } self.voting_power += txid.zats; diff --git a/zebra-gui/src/ui.rs b/zebra-gui/src/ui.rs index fe02c74b..8a84c56f 100644 --- a/zebra-gui/src/ui.rs +++ b/zebra-gui/src/ui.rs @@ -1,18 +1,18 @@ #![allow(warnings)] +use clay::layout::{Alignment, LayoutAlignmentX, LayoutAlignmentY}; +use clay::render_commands::RenderCommandConfig; +use clay::render_commands::RenderCommandConfig::{Rectangle, ScissorEnd, ScissorStart}; +use clay::{Clay, Declaration}; +use clay_layout as clay; +use std::collections::HashMap; +use std::hash::Hash; use std::net::Shutdown; use std::thread::current; -use std::{hash::Hash}; use winit::{event::MouseButton, keyboard::KeyCode}; -use clay_layout as clay; -use clay::{Clay, Declaration}; -use clay::render_commands::RenderCommandConfig; -use clay::render_commands::RenderCommandConfig::{Rectangle, ScissorStart, ScissorEnd}; -use clay::layout::{Alignment, LayoutAlignmentX, LayoutAlignmentY}; -use std::collections::HashMap; //use clay::*; // @Temporary -use wallet::{ BlockHeight, WalletState, WalletTxKind, str_from_ctaz }; +use wallet::{BlockHeight, WalletState, WalletTxKind, str_from_ctaz}; use super::*; @@ -25,14 +25,13 @@ pub fn magic<'a, 'b, T>(mut_ref: &'a mut T) -> &'b mut T { pub struct UiData { pub per_frame_strs: Vec, - pub send_address: String, + pub send_address: String, pub stake_address: String, - pub recv_address: String, + pub recv_address: String, pub textboxes: HashMap, } - #[macro_export] macro_rules! frame_strf { ($data:expr, $($arg:tt)*) => { @@ -65,7 +64,10 @@ pub fn dbg_ui(ui: &mut Context, is_rendering: bool) -> bool { if ui.pixel_inspector_primed { if ui.input().mouse_pressed(MouseButton::Left) { unsafe { - *ui.draw().debug_pixel_inspector = Some((ui.input().mouse_pos().0.clamp(0, ui.draw().window_width) as usize, ui.input().mouse_pos().1.clamp(0, ui.draw().window_height) as usize)); + *ui.draw().debug_pixel_inspector = Some(( + ui.input().mouse_pos().0.clamp(0, ui.draw().window_width) as usize, + ui.input().mouse_pos().1.clamp(0, ui.draw().window_height) as usize, + )); } ui.pixel_inspector_primed = false; } @@ -73,66 +75,188 @@ pub fn dbg_ui(ui: &mut Context, is_rendering: bool) -> bool { if is_rendering { if ui.pixel_inspector_primed { - ui.draw().text_line(FontKind::Mono, 0.0, 0.0, 16.0, "Pixel Inspector is Primed! Click to select pixel.", 0xff_00ff00); + ui.draw().text_line( + FontKind::Mono, + 0.0, + 0.0, + 16.0, + "Pixel Inspector is Primed! Click to select pixel.", + 0xff_00ff00, + ); } if let Some((x, y)) = unsafe { *ui.draw().debug_pixel_inspector } { - let x = x as isize; let y = y as isize; + let x = x as isize; + let y = y as isize; let mut draw_x = 0; let mut draw_y = 0; - if x < ui.draw().window_width/2 { draw_x = ui.draw().window_width - 256 }; - if y < ui.draw().window_height/2 { draw_y = ui.draw().window_height - 256 }; + if x < ui.draw().window_width / 2 { + draw_x = ui.draw().window_width - 256 + }; + if y < ui.draw().window_height / 2 { + draw_y = ui.draw().window_height - 256 + }; let color = unsafe { *ui.draw().debug_pixel_inspector_last_color }; - ui.draw().rectangle(draw_x as f32, draw_y as f32, draw_x as f32 + 256.0, draw_y as f32 + 256.0, 0xff_000000 | color); - ui.draw().text_line(FontKind::Mono, draw_x as f32, draw_y as f32, 12.0, &format!("({},{}) = {:X}", x, y, color), 0xff_000000 | (color ^ u32::MAX)); + ui.draw().rectangle( + draw_x as f32, + draw_y as f32, + draw_x as f32 + 256.0, + draw_y as f32 + 256.0, + 0xff_000000 | color, + ); + ui.draw().text_line( + FontKind::Mono, + draw_x as f32, + draw_y as f32, + 12.0, + &format!("({},{}) = {:X}", x, y, color), + 0xff_000000 | (color ^ u32::MAX), + ); } } return false; } -#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default, Hash, Ord, Eq)] pub enum Direction { #[default] LeftToRight, TopToBottom } -#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default)] pub enum Floating { #[default] None, Parent, Root(f32, f32) } -#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default)] pub enum ClipMode { #[default] None, Clip, Scroll(f32, f32) } -#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default, Hash, Ord, Eq)] pub enum AlignX { #[default] Left, Right, Center } -#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default, Hash, Ord, Eq)] pub enum AlignY { #[default] Top, Bottom, Center } -#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default, Hash, Ord, Eq)] pub struct Align { x: AlignX, y: AlignY } -#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] pub enum Sizing { Fit(f32, f32), Grow(f32, f32), Fixed(f32), Percent(f32) } -#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default, Hash, Ord, Eq)] pub struct Id { id: u32, offset: u32, base_id: u32, len: usize, chars: *const u8 } -impl Default for Sizing { fn default() -> Self { Self::Fit(0.0, f32::MAX) } } +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default, Hash, Ord, Eq)] +pub enum Direction { + #[default] + LeftToRight, + TopToBottom, +} +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default)] +pub enum Floating { + #[default] + None, + Parent, + Root(f32, f32), +} +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default)] +pub enum ClipMode { + #[default] + None, + Clip, + Scroll(f32, f32), +} +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default, Hash, Ord, Eq)] +pub enum AlignX { + #[default] + Left, + Right, + Center, +} +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default, Hash, Ord, Eq)] +pub enum AlignY { + #[default] + Top, + Bottom, + Center, +} +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default, Hash, Ord, Eq)] +pub struct Align { + x: AlignX, + y: AlignY, +} +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] +pub enum Sizing { + Fit(f32, f32), + Grow(f32, f32), + Fixed(f32), + Percent(f32), +} +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default, Hash, Ord, Eq)] +pub struct Id { + id: u32, + offset: u32, + base_id: u32, + len: usize, + chars: *const u8, +} +impl Default for Sizing { + fn default() -> Self { + Self::Fit(0.0, f32::MAX) + } +} impl Align { - pub const TopLeft: Self = Self { y: AlignY::Top, x: AlignX::Left }; - pub const Top: Self = Self { y: AlignY::Top, x: AlignX::Center }; - pub const TopRight: Self = Self { y: AlignY::Top, x: AlignX::Right }; - pub const Left: Self = Self { y: AlignY::Center, x: AlignX::Left }; - pub const Center: Self = Self { y: AlignY::Center, x: AlignX::Center }; - pub const Right: Self = Self { y: AlignY::Center, x: AlignX::Right }; - pub const BottomLeft: Self = Self { y: AlignY::Bottom, x: AlignX::Left }; - pub const Bottom: Self = Self { y: AlignY::Bottom, x: AlignX::Center }; - pub const BottomRight: Self = Self { y: AlignY::Bottom, x: AlignX::Right }; + pub const TopLeft: Self = Self { + y: AlignY::Top, + x: AlignX::Left, + }; + pub const Top: Self = Self { + y: AlignY::Top, + x: AlignX::Center, + }; + pub const TopRight: Self = Self { + y: AlignY::Top, + x: AlignX::Right, + }; + pub const Left: Self = Self { + y: AlignY::Center, + x: AlignX::Left, + }; + pub const Center: Self = Self { + y: AlignY::Center, + x: AlignX::Center, + }; + pub const Right: Self = Self { + y: AlignY::Center, + x: AlignX::Right, + }; + pub const BottomLeft: Self = Self { + y: AlignY::Bottom, + x: AlignX::Left, + }; + pub const Bottom: Self = Self { + y: AlignY::Bottom, + x: AlignX::Center, + }; + pub const BottomRight: Self = Self { + y: AlignY::Bottom, + x: AlignX::Right, + }; } // why can we not `use` these? namaste -pub const TopLeft: Align = Align::TopLeft; -pub const Top: Align = Align::Top; -pub const TopRight: Align = Align::TopRight; -pub const Left: Align = Align::Left; -pub const Center: Align = Align::Center; -pub const Right: Align = Align::Right; -pub const BottomLeft: Align = Align::BottomLeft; -pub const Bottom: Align = Align::Bottom; +pub const TopLeft: Align = Align::TopLeft; +pub const Top: Align = Align::Top; +pub const TopRight: Align = Align::TopRight; +pub const Left: Align = Align::Left; +pub const Center: Align = Align::Center; +pub const Right: Align = Align::Right; +pub const BottomLeft: Align = Align::BottomLeft; +pub const Bottom: Align = Align::Bottom; pub const BottomRight: Align = Align::BottomRight; -#[macro_export] macro_rules! fit { - ($min:expr, $max:expr) => { Sizing::Fit($min, $max) }; - ($min:expr) => { fit!($min, f32::MAX) }; - () => { fit!(0.0) }; +#[macro_export] +macro_rules! fit { + ($min:expr, $max:expr) => { + Sizing::Fit($min, $max) + }; + ($min:expr) => { + fit!($min, f32::MAX) + }; + () => { + fit!(0.0) + }; +} +#[macro_export] +macro_rules! grow { + ($min:expr, $max:expr) => { + Sizing::Grow($min, $max) + }; + ($min:expr) => { + grow!($min, f32::MAX) + }; + () => { + grow!(0.0) + }; } -#[macro_export] macro_rules! grow { - ($min:expr, $max:expr) => { Sizing::Grow($min, $max) }; - ($min:expr) => { grow!($min, f32::MAX) }; - () => { grow!(0.0) }; +#[macro_export] +macro_rules! fixed { + ($val:expr) => { + Sizing::Fixed($val) + }; } -#[macro_export] macro_rules! fixed { ($val:expr) => { Sizing::Fixed($val) }; } -#[macro_export] macro_rules! percent { +#[macro_export] +macro_rules! percent { ($percent:expr) => {{ const _: () = assert!( $percent >= 0.0 && $percent <= 1.0, @@ -145,7 +269,13 @@ pub const BottomRight: Align = Align::BottomRight; use ClipMode::{Clip, Scroll}; use Direction::*; -pub const Id: Id = Id { id: 0, offset: 0, base_id: 0, len: 0, chars: std::ptr::null() }; +pub const Id: Id = Id { + id: 0, + offset: 0, + base_id: 0, + len: 0, + chars: std::ptr::null(), +}; #[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct Decl { @@ -158,23 +288,23 @@ pub struct Decl { clip: ClipMode, child_gap: f32, align: Align, - width: Sizing, + width: Sizing, height: Sizing, } // Ease-of-use constant for the builder pattern thing, so you can write Decl{..Decl} to get a default Decl. // I can't do #[derive_const(Default)] because that's only on Rust nightly. And it would probably be complex anyway. pub const Decl: Decl = Decl { - id: Id, + id: Id, direction: Direction::LeftToRight, - floating: Floating::None, - colour: (0, 0, 0, 0), - radius: (0.0, 0.0, 0.0, 0.0), - padding: (0.0, 0.0, 0.0, 0.0), - clip: ClipMode::None, + floating: Floating::None, + colour: (0, 0, 0, 0), + radius: (0.0, 0.0, 0.0, 0.0), + padding: (0.0, 0.0, 0.0, 0.0), + clip: ClipMode::None, child_gap: 0.0, - align: TopLeft, - width: Sizing::Fit(0.0, f32::MAX), - height: Sizing::Fit(0.0, f32::MAX), + align: TopLeft, + width: Sizing::Fit(0.0, f32::MAX), + height: Sizing::Fit(0.0, f32::MAX), }; #[derive(Debug, Default, Copy, Clone, PartialEq)] @@ -193,8 +323,6 @@ pub const TextDecl: TextDecl = TextDecl { wrap_chars: false, }; - - impl Id { pub const VIZ_GUI: Self = Self { id: 1, ..Id }; fn clay(&self) -> clay::id::Id { @@ -206,9 +334,9 @@ impl Id { stringId: clay::Clay_String { isStaticallyAllocated: false, length: self.len as i32, - chars: self.chars as *const i8 - } - } + chars: self.chars as *const i8, + }, + }, } } } @@ -219,34 +347,74 @@ pub fn id(label: &str) -> Id { offset: id.offset, base_id: id.baseId, len: id.stringId.length as usize, - chars: id.stringId.chars as *const u8 + chars: id.stringId.chars as *const u8, } } pub fn id_index(label: &str, index: u32) -> Id { - let id = unsafe { clay::Clay__HashString(label.into(), index, clay::Clay__GetParentElementId()) }; + let id = + unsafe { clay::Clay__HashString(label.into(), index, clay::Clay__GetParentElementId()) }; Id { id: id.id, offset: id.offset, base_id: id.baseId, len: id.stringId.length as usize, - chars: id.stringId.chars as *const u8 + chars: id.stringId.chars as *const u8, } } -#[derive(Default)] pub struct Element { decl: Decl } -pub fn elem() -> Element { unsafe { clay::Clay__OpenElement(); } Element::default() } -impl Drop for Element { fn drop(&mut self) { unsafe { clay::Clay__CloseElement(); } } } +#[derive(Default)] +pub struct Element { + decl: Decl, +} +pub fn elem() -> Element { + unsafe { + clay::Clay__OpenElement(); + } + Element::default() +} +impl Drop for Element { + fn drop(&mut self) { + unsafe { + clay::Clay__CloseElement(); + } + } +} -pub const Clay_ElementId_ZERO: clay::Clay_ElementId = clay::Clay_ElementId { id: 0, offset: 0, baseId: 0, stringId: clay::Clay_String { isStaticallyAllocated: false, length: 0, chars: std::ptr::null() } }; -pub const Clay_SizingMinMax_ZERO: clay::Clay_SizingMinMax = clay::Clay_SizingMinMax { min: 0f32, max: f32::MAX }; +pub const Clay_ElementId_ZERO: clay::Clay_ElementId = clay::Clay_ElementId { + id: 0, + offset: 0, + baseId: 0, + stringId: clay::Clay_String { + isStaticallyAllocated: false, + length: 0, + chars: std::ptr::null(), + }, +}; +pub const Clay_SizingMinMax_ZERO: clay::Clay_SizingMinMax = clay::Clay_SizingMinMax { + min: 0f32, + max: f32::MAX, +}; pub const Clay_SizingAxis_ZERO: clay::Clay_SizingAxis = clay::Clay_SizingAxis { - size: clay::Clay_SizingAxis__bindgen_ty_1 { minMax: Clay_SizingMinMax_ZERO }, - type_: clay::Clay__SizingType_CLAY__SIZING_TYPE_FIT + size: clay::Clay_SizingAxis__bindgen_ty_1 { + minMax: Clay_SizingMinMax_ZERO, + }, + type_: clay::Clay__SizingType_CLAY__SIZING_TYPE_FIT, +}; +pub const Clay_Sizing_ZERO: clay::Clay_Sizing = clay::Clay_Sizing { + width: Clay_SizingAxis_ZERO, + height: Clay_SizingAxis_ZERO, +}; +pub const Clay_Padding_ZERO: clay::Clay_Padding = clay::Clay_Padding { + left: 0, + right: 0, + top: 0, + bottom: 0, +}; +pub const Clay_ChildAlignment_ZERO: clay::Clay_ChildAlignment = clay::Clay_ChildAlignment { + x: 0 as _, + y: 0 as _, }; -pub const Clay_Sizing_ZERO: clay::Clay_Sizing = clay::Clay_Sizing { width: Clay_SizingAxis_ZERO, height: Clay_SizingAxis_ZERO }; -pub const Clay_Padding_ZERO: clay::Clay_Padding = clay::Clay_Padding { left: 0, right: 0, top: 0, bottom: 0 }; -pub const Clay_ChildAlignment_ZERO: clay::Clay_ChildAlignment = clay::Clay_ChildAlignment { x: 0 as _, y: 0 as _ }; pub const Clay_LayoutConfig_ZERO: clay::Clay_LayoutConfig = clay::Clay_LayoutConfig { sizing: Clay_Sizing_ZERO, padding: Clay_Padding_ZERO, @@ -254,45 +422,73 @@ pub const Clay_LayoutConfig_ZERO: clay::Clay_LayoutConfig = clay::Clay_LayoutCon childAlignment: Clay_ChildAlignment_ZERO, layoutDirection: clay::Clay_LayoutDirection_CLAY_LEFT_TO_RIGHT, }; -pub const Clay_Color_ZERO: clay::Clay_Color = clay::Clay_Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }; -pub const Clay_CornerRadius_ZERO: clay::Clay_CornerRadius = clay::Clay_CornerRadius { topLeft: 0f32, topRight: 0f32, bottomLeft: 0f32, bottomRight: 0f32 }; -pub const Clay_ElementDeclaration_ZERO: clay::Clay_ElementDeclaration = clay::Clay_ElementDeclaration { - id: Clay_ElementId_ZERO, - layout: Clay_LayoutConfig_ZERO, - backgroundColor: Clay_Color_ZERO, - cornerRadius: Clay_CornerRadius_ZERO, - aspectRatio: clay::Clay_AspectRatioElementConfig { aspectRatio: 0.0 }, - image: clay::Clay_ImageElementConfig { imageData: std::ptr::null_mut() }, - floating: clay::Clay_FloatingElementConfig { - offset: clay::Clay_Vector2 { x: 0f32, y: 0f32 }, - expand: clay::Clay_Dimensions { width: 0f32, height: 0f32 }, - parentId: 0, - zIndex: 0, - attachPoints: clay::Clay_FloatingAttachPoints { - element: clay::Clay_FloatingAttachPointType_CLAY_ATTACH_POINT_LEFT_TOP, - parent: clay::Clay_FloatingAttachPointType_CLAY_ATTACH_POINT_LEFT_TOP, - }, - pointerCaptureMode: clay::Clay_PointerCaptureMode_CLAY_POINTER_CAPTURE_MODE_CAPTURE, - attachTo: clay::Clay_FloatingAttachToElement_CLAY_ATTACH_TO_NONE, - clipTo: clay::Clay_FloatingClipToElement_CLAY_CLIP_TO_NONE - }, - custom: clay::Clay_CustomElementConfig { customData: std::ptr::null_mut() }, - clip: clay::Clay_ClipElementConfig { horizontal: false, vertical: false, childOffset: clay::Clay_Vector2 { x: 0f32, y: 0f32 } }, - border: clay::Clay_BorderElementConfig { - color: Clay_Color_ZERO, - width: clay::Clay_BorderWidth { left: 0, right: 0, top: 0, bottom: 0, betweenChildren: 0 } - }, - userData: std::ptr::null_mut(), +pub const Clay_Color_ZERO: clay::Clay_Color = clay::Clay_Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.0, +}; +pub const Clay_CornerRadius_ZERO: clay::Clay_CornerRadius = clay::Clay_CornerRadius { + topLeft: 0f32, + topRight: 0f32, + bottomLeft: 0f32, + bottomRight: 0f32, }; +pub const Clay_ElementDeclaration_ZERO: clay::Clay_ElementDeclaration = + clay::Clay_ElementDeclaration { + id: Clay_ElementId_ZERO, + layout: Clay_LayoutConfig_ZERO, + backgroundColor: Clay_Color_ZERO, + cornerRadius: Clay_CornerRadius_ZERO, + aspectRatio: clay::Clay_AspectRatioElementConfig { aspectRatio: 0.0 }, + image: clay::Clay_ImageElementConfig { + imageData: std::ptr::null_mut(), + }, + floating: clay::Clay_FloatingElementConfig { + offset: clay::Clay_Vector2 { x: 0f32, y: 0f32 }, + expand: clay::Clay_Dimensions { + width: 0f32, + height: 0f32, + }, + parentId: 0, + zIndex: 0, + attachPoints: clay::Clay_FloatingAttachPoints { + element: clay::Clay_FloatingAttachPointType_CLAY_ATTACH_POINT_LEFT_TOP, + parent: clay::Clay_FloatingAttachPointType_CLAY_ATTACH_POINT_LEFT_TOP, + }, + pointerCaptureMode: clay::Clay_PointerCaptureMode_CLAY_POINTER_CAPTURE_MODE_CAPTURE, + attachTo: clay::Clay_FloatingAttachToElement_CLAY_ATTACH_TO_NONE, + clipTo: clay::Clay_FloatingClipToElement_CLAY_CLIP_TO_NONE, + }, + custom: clay::Clay_CustomElementConfig { + customData: std::ptr::null_mut(), + }, + clip: clay::Clay_ClipElementConfig { + horizontal: false, + vertical: false, + childOffset: clay::Clay_Vector2 { x: 0f32, y: 0f32 }, + }, + border: clay::Clay_BorderElementConfig { + color: Clay_Color_ZERO, + width: clay::Clay_BorderWidth { + left: 0, + right: 0, + top: 0, + bottom: 0, + betweenChildren: 0, + }, + }, + userData: std::ptr::null_mut(), + }; impl Element { fn decl(&mut self, item: Decl) -> &mut Self { fn sizing(sizing: Sizing) -> clay::layout::Sizing { match sizing { - Sizing::Fit(min, max) => { clay::layout::Sizing::Fit(min, max) } - Sizing::Grow(min, max) => { clay::layout::Sizing::Grow(min, max) } - Sizing::Fixed(x) => { clay::layout::Sizing::Fixed(x) } - Sizing::Percent(p) => { clay::layout::Sizing::Percent(p) } + Sizing::Fit(min, max) => clay::layout::Sizing::Fit(min, max), + Sizing::Grow(min, max) => clay::layout::Sizing::Grow(min, max), + Sizing::Fixed(x) => clay::layout::Sizing::Fixed(x), + Sizing::Percent(p) => clay::layout::Sizing::Percent(p), } } @@ -301,10 +497,13 @@ impl Element { r: item.colour.0 as f32, g: item.colour.1 as f32, b: item.colour.2 as f32, - a: (item.colour.3 | 0x01) as f32 + a: (item.colour.3 | 0x01) as f32, }; decl.id = item.id.clay().id; - let clipping = match item.clip { ClipMode::None => false, _ => true }; + let clipping = match item.clip { + ClipMode::None => false, + _ => true, + }; decl.clip = clay::Clay_ClipElementConfig { horizontal: clipping, vertical: clipping, @@ -312,47 +511,53 @@ impl Element { ClipMode::None => clay::Clay_Vector2 { x: 0.0, y: 0.0 }, ClipMode::Clip => clay::Clay_Vector2 { x: 0.0, y: 0.0 }, ClipMode::Scroll(x, y) => clay::Clay_Vector2 { x, y }, - } + }, }; match item.floating { Floating::Parent => { decl.floating.attachTo = clay::Clay_FloatingAttachToElement_CLAY_ATTACH_TO_PARENT; - decl.floating.clipTo = clay::Clay_FloatingClipToElement_CLAY_CLIP_TO_ATTACHED_PARENT; + decl.floating.clipTo = + clay::Clay_FloatingClipToElement_CLAY_CLIP_TO_ATTACHED_PARENT; decl.floating.attachPoints = clay::Clay_FloatingAttachPoints { element: clay::Clay_FloatingAttachPointType_CLAY_ATTACH_POINT_CENTER_CENTER, - parent: clay::Clay_FloatingAttachPointType_CLAY_ATTACH_POINT_CENTER_CENTER, + parent: clay::Clay_FloatingAttachPointType_CLAY_ATTACH_POINT_CENTER_CENTER, }; - }, + } Floating::Root(x, y) => { decl.floating.attachTo = clay::Clay_FloatingAttachToElement_CLAY_ATTACH_TO_ROOT; decl.floating.offset.x = x; decl.floating.offset.y = y; decl.floating.attachPoints = clay::Clay_FloatingAttachPoints { element: clay::Clay_FloatingAttachPointType_CLAY_ATTACH_POINT_LEFT_TOP, - parent: clay::Clay_FloatingAttachPointType_CLAY_ATTACH_POINT_LEFT_TOP, + parent: clay::Clay_FloatingAttachPointType_CLAY_ATTACH_POINT_LEFT_TOP, }; - }, - _ => {}, + } + _ => {} } - decl.layout.sizing.width = clay::Clay_SizingAxis::from(sizing(item.width)); + decl.layout.sizing.width = clay::Clay_SizingAxis::from(sizing(item.width)); decl.layout.sizing.height = clay::Clay_SizingAxis::from(sizing(item.height)); decl.layout.padding = clay::Clay_Padding::from(clay::Clay_Padding { - left: item.padding.0 as u16, - right: item.padding.1 as u16, - top: item.padding.2 as u16, + left: item.padding.0 as u16, + right: item.padding.1 as u16, + top: item.padding.2 as u16, bottom: item.padding.3 as u16, }); decl.layout.childGap = item.child_gap as u16; - decl.layout.childAlignment = clay::Clay_ChildAlignment { x: item.align.x as _, y: item.align.y as _ }; + decl.layout.childAlignment = clay::Clay_ChildAlignment { + x: item.align.x as _, + y: item.align.y as _, + }; decl.layout.layoutDirection = item.direction as _; decl.cornerRadius = clay::Clay_CornerRadius { - topLeft: item.radius.0, - topRight: item.radius.1, - bottomLeft: item.radius.2, - bottomRight: item.radius.3 + topLeft: item.radius.0, + topRight: item.radius.1, + bottomLeft: item.radius.2, + bottomRight: item.radius.3, }; - unsafe { clay::Clay__ConfigureOpenElement(decl); } + unsafe { + clay::Clay__ConfigureOpenElement(decl); + } self.decl = item; self @@ -361,25 +566,33 @@ impl Element { pub const PANE_PERCENT: f32 = 0.27; // @PreventPanesColliding -pub const WHITE: (u8, u8, u8, u8) = (0xff, 0xff, 0xff, 0xff); +pub const WHITE: (u8, u8, u8, u8) = (0xff, 0xff, 0xff, 0xff); // pub const PANE_COL: (u8, u8, u8, u8) = (0x12, 0x12, 0x12, 0xff); // @FigmaScreenshot -pub const PANE_COL: (u8, u8, u8, u8) = (0x13, 0x13, 0x13, 0xff); // @FigmaScreenshot +pub const PANE_COL: (u8, u8, u8, u8) = (0x13, 0x13, 0x13, 0xff); // @FigmaScreenshot pub const INACTIVE_TAB_COL: (u8, u8, u8, u8) = (0x0f, 0x0f, 0x0f, 0xff); -pub const ACTIVE_TAB_COL: (u8, u8, u8, u8) = PANE_COL; +pub const ACTIVE_TAB_COL: (u8, u8, u8, u8) = PANE_COL; -pub const BUTTON_GREY: (u8, u8, u8, u8) = (0x24, 0x24, 0x24, 0xff); // @FigmaScreenshot -pub const BUTTON_BLUE: (u8, u8, u8, u8) = (0x1a, 0x36, 0x51, 0xff); // @FigmaScreenshot -pub const BUTTON_ORANGE: (u8, u8, u8, u8) = (0x59, 0x41, 0x11, 0xff); // @FigmaScreenshot +pub const BUTTON_GREY: (u8, u8, u8, u8) = (0x24, 0x24, 0x24, 0xff); // @FigmaScreenshot +pub const BUTTON_BLUE: (u8, u8, u8, u8) = (0x1a, 0x36, 0x51, 0xff); // @FigmaScreenshot +pub const BUTTON_ORANGE: (u8, u8, u8, u8) = (0x59, 0x41, 0x11, 0xff); // @FigmaScreenshot pub const MODAL_COL: (u8, u8, u8, u8) = (0x1e, 0x1e, 0x1e, 0xff); // @FigmaScreenshot pub const TRANSACTION_HISTORY_CONTAINER_COL: (u8, u8, u8, u8) = (0x22, 0x22, 0x24, 0xff); // @FigmaScreenshot -pub const fn clay_colour(colour: (u8, u8, u8, u8)) -> clay::Color { clay::Color::rgba(colour.0 as f32, colour.1 as f32, colour.2 as f32, colour.3 as f32) } +pub const fn clay_colour(colour: (u8, u8, u8, u8)) -> clay::Color { + clay::Color::rgba( + colour.0 as f32, + colour.1 as f32, + colour.2 as f32, + colour.3 as f32, + ) +} #[derive(Debug, Default, Copy, Clone, PartialEq)] pub enum Modal { - #[default] None, + #[default] + None, Send, Receive, Stake, @@ -419,9 +632,9 @@ pub fn rgba_to_hsva(r: u8, g: u8, b: u8, a: u8) -> (u8, u8, u8, u8) { } pub fn hsva_to_rgba(h: u8, s: u8, v: u8, a: u8) -> (u8, u8, u8, u8) { - let hf = (h as f32 / 255.0) * 6.0; // 0..6 - let sf = s as f32 / 255.0; // 0..1 - let vf = v as f32 / 255.0; // 0..1 + let hf = (h as f32 / 255.0) * 6.0; // 0..6 + let sf = s as f32 / 255.0; // 0..1 + let vf = v as f32 / 255.0; // 0..1 let c = vf * sf; let x = c * (1.0 - ((hf % 2.0) - 1.0).abs()); @@ -444,52 +657,105 @@ pub fn hsva_to_rgba(h: u8, s: u8, v: u8, a: u8) -> (u8, u8, u8, u8) { } pub trait HSVA_RGBA { - #[inline(always)] fn hsva(self) -> Self; - #[inline(always)] fn rgba(self) -> Self; + #[inline(always)] + fn hsva(self) -> Self; + #[inline(always)] + fn rgba(self) -> Self; } impl HSVA_RGBA for (u8, u8, u8, u8) { - #[inline(always)] fn hsva(self) -> Self { rgba_to_hsva(self.0, self.1, self.2, self.3) } - #[inline(always)] fn rgba(self) -> Self { hsva_to_rgba(self.0, self.1, self.2, self.3) } + #[inline(always)] + fn hsva(self) -> Self { + rgba_to_hsva(self.0, self.1, self.2, self.3) + } + #[inline(always)] + fn rgba(self) -> Self { + hsva_to_rgba(self.0, self.1, self.2, self.3) + } } impl Context { - pub fn new() -> Context { Context { scale: 1f32, zoom: 1f32, dpi_scale: 1f32, ..Default::default() } } - pub fn draw(&self) -> &DrawCtx { unsafe { &*self.draw } } - pub fn input(&self) -> &InputCtx { unsafe { &*self.input } } - pub fn clay(&self) -> &mut Clay { unsafe { &mut *self.clay } } - - pub fn scale(&self, size: f32) -> f32 { (size * self.scale).floor() } - pub fn scale32(&self, size: f32) -> u32 { self.scale(size) as u32 } - pub fn scale16(&self, size: f32) -> u16 { self.scale(size) as u16 } + pub fn new() -> Context { + Context { + scale: 1f32, + zoom: 1f32, + dpi_scale: 1f32, + ..Default::default() + } + } + pub fn draw(&self) -> &DrawCtx { + unsafe { &*self.draw } + } + pub fn input(&self) -> &InputCtx { + unsafe { &*self.input } + } + pub fn clay(&self) -> &mut Clay { + unsafe { &mut *self.clay } + } - pub fn hovered(&self, id: Id) -> bool { unsafe { clay::Clay_PointerOver(id.clay().id) } } + pub fn scale(&self, size: f32) -> f32 { + (size * self.scale).floor() + } + pub fn scale32(&self, size: f32) -> u32 { + self.scale(size) as u32 + } + pub fn scale16(&self, size: f32) -> u16 { + self.scale(size) as u16 + } - pub fn button_ex(&mut self, act_on_press: bool, colour: (u8, u8, u8, u8), id: Id, enabled: bool, pointer_on_hover: winit::window::CursorIcon) -> (bool, (u8, u8, u8, u8), (u8, u8, u8, u8)) { + pub fn hovered(&self, id: Id) -> bool { + unsafe { clay::Clay_PointerOver(id.clay().id) } + } + pub fn button_ex( + &mut self, + act_on_press: bool, + colour: (u8, u8, u8, u8), + id: Id, + enabled: bool, + pointer_on_hover: winit::window::CursorIcon, + ) -> (bool, (u8, u8, u8, u8), (u8, u8, u8, u8)) { let mouse_hover = self.hovered(id); - let key_hover = self.nav_enable && self.nav_id == id.id; + let key_hover = self.nav_enable && self.nav_id == id.id; - let mouse_held = mouse_hover && self.input().mouse_held(winit::event::MouseButton::Left); - let mouse_pressed = mouse_hover && self.input().mouse_pressed(winit::event::MouseButton::Left); - let mouse_released = mouse_hover && self.input().mouse_released(winit::event::MouseButton::Left); + let mouse_held = mouse_hover && self.input().mouse_held(winit::event::MouseButton::Left); + let mouse_pressed = + mouse_hover && self.input().mouse_pressed(winit::event::MouseButton::Left); + let mouse_released = + mouse_hover && self.input().mouse_released(winit::event::MouseButton::Left); - let key_held = key_hover && self.input().key_held(winit::keyboard::KeyCode::Enter); - let key_pressed = key_hover && self.input().key_pressed(winit::keyboard::KeyCode::Enter); + let key_held = key_hover && self.input().key_held(winit::keyboard::KeyCode::Enter); + let key_pressed = key_hover && self.input().key_pressed(winit::keyboard::KeyCode::Enter); let key_released = key_hover && self.input().key_released(winit::keyboard::KeyCode::Enter); - if mouse_pressed { self.mouse_pressed_id = id; } - if key_pressed { self.key_pressed_id = id; } - let mouse_activated = enabled && self.mouse_pressed_id == id && if act_on_press { mouse_pressed } else { mouse_released }; - let key_activated = enabled && self.key_pressed_id == id && if act_on_press { key_pressed } else { key_released }; + if mouse_pressed { + self.mouse_pressed_id = id; + } + if key_pressed { + self.key_pressed_id = id; + } + let mouse_activated = enabled + && self.mouse_pressed_id == id + && if act_on_press { + mouse_pressed + } else { + mouse_released + }; + let key_activated = enabled + && self.key_pressed_id == id + && if act_on_press { + key_pressed + } else { + key_released + }; if mouse_hover && pointer_on_hover != winit::window::CursorIcon::Default { self.cursor = winit::window::Cursor::Icon(pointer_on_hover); } - let held = mouse_held || key_held; - let hover = mouse_hover || key_hover; - let pressed = mouse_pressed || key_pressed; - let released = mouse_released || key_released; + let held = mouse_held || key_held; + let hover = mouse_hover || key_hover; + let pressed = mouse_pressed || key_pressed; + let released = mouse_released || key_released; let activated = mouse_activated || key_activated; let mut hsva = colour.hsva(); @@ -528,7 +794,15 @@ impl Context { (activated, colour, text_colour) } - pub fn button(&mut self, id: Id) -> (bool, (u8, u8, u8, u8), (u8, u8, u8, u8)) { return self.button_ex(true, BUTTON_GREY, id, true, winit::window::CursorIcon::Default); } + pub fn button(&mut self, id: Id) -> (bool, (u8, u8, u8, u8), (u8, u8, u8, u8)) { + return self.button_ex( + true, + BUTTON_GREY, + id, + true, + winit::window::CursorIcon::Default, + ); + } pub fn text(&self, label: &str, decl: TextDecl) { let config = clay::text::TextConfig::new() @@ -536,8 +810,8 @@ impl Context { .font_size(decl.h as u16) .color(clay_colour(decl.colour)) .alignment(match decl.align { - AlignX::Left => clay::text::TextAlignment::Left, - AlignX::Right => clay::text::TextAlignment::Right, + AlignX::Left => clay::text::TextAlignment::Left, + AlignX::Right => clay::text::TextAlignment::Right, AlignX::Center => clay::text::TextAlignment::Center, }) .wrap_mode(if decl.wrap_chars { @@ -549,12 +823,14 @@ impl Context { unsafe { clay::Clay__OpenTextElement(label.into(), config.into()) }; } - pub fn tab_ex(&mut self, - radius: (f32, f32, f32, f32), - padding: (f32, f32, f32, f32), - tab_id: &mut Id, - id: Id, - label: &str) -> Id { + pub fn tab_ex( + &mut self, + radius: (f32, f32, f32, f32), + padding: (f32, f32, f32, f32), + tab_id: &mut Id, + id: Id, + label: &str, + ) -> Id { let tab_text_h = self.scale(18.0); let radius = (radius.0, radius.1, 0.0, 0.0); @@ -566,34 +842,49 @@ impl Context { if let _ = elem().decl(Decl { id, - radius, padding, - colour: if *tab_id == id { ACTIVE_TAB_COL } else { INACTIVE_TAB_COL }, + radius, + padding, + colour: if *tab_id == id { + ACTIVE_TAB_COL + } else { + INACTIVE_TAB_COL + }, width: grow!(), height: grow!(), align: Center, ..Decl }) { - self.text(label, TextDecl { h: tab_text_h, align: AlignX::Center, ..TextDecl }); + self.text( + label, + TextDecl { + h: tab_text_h, + align: AlignX::Center, + ..TextDecl + }, + ); } id } - pub fn tab(&mut self, - radius: (f32, f32, f32, f32), - padding: (f32, f32, f32, f32), - tab_id: &mut Id, - label: &str) -> Id { + pub fn tab( + &mut self, + radius: (f32, f32, f32, f32), + padding: (f32, f32, f32, f32), + tab_id: &mut Id, + label: &str, + ) -> Id { let id = id(label); self.tab_ex(radius, padding, tab_id, id, label) } pub fn textbox(&mut self, data: &mut UiData, id: Id) -> String { let child_gap = self.scale(12.0); // @Duplicate :TextBox - let padding = child_gap.dup4(); // @Duplicate :TextBox - let radius = child_gap.dup4(); // @Duplicate :TextBox + let padding = child_gap.dup4(); // @Duplicate :TextBox + let radius = child_gap.dup4(); // @Duplicate :TextBox - let (activated, colour, text_colour) = self.button_ex(true, BUTTON_GREY, id, true, winit::window::CursorIcon::Text); + let (activated, colour, text_colour) = + self.button_ex(true, BUTTON_GREY, id, true, winit::window::CursorIcon::Text); if activated { self.nav_id = id.id; @@ -610,27 +901,63 @@ impl Context { if self.nav_id == id.id { let mut moved = false; - let shift = (self.input().key_held(KeyCode::ShiftLeft) || self.input().key_held(KeyCode::ShiftRight)); - let ctrl = (self.input().key_held(KeyCode::ControlLeft) || self.input().key_held(KeyCode::ControlRight)); + let shift = (self.input().key_held(KeyCode::ShiftLeft) + || self.input().key_held(KeyCode::ShiftRight)); + let ctrl = (self.input().key_held(KeyCode::ControlLeft) + || self.input().key_held(KeyCode::ControlRight)); if !shift && textbox_state.selection.1 != textbox_state.selection.0 { - let (min, max) = (textbox_state.selection.0.min(textbox_state.selection.1), - textbox_state.selection.0.max(textbox_state.selection.1)); - if self.input().key_pressed(KeyCode::ArrowRight) { moved = true; textbox_state.selection.0 = max; } - if self.input().key_pressed(KeyCode::ArrowLeft) { moved = true; textbox_state.selection.0 = min; } + let (min, max) = ( + textbox_state.selection.0.min(textbox_state.selection.1), + textbox_state.selection.0.max(textbox_state.selection.1), + ); + if self.input().key_pressed(KeyCode::ArrowRight) { + moved = true; + textbox_state.selection.0 = max; + } + if self.input().key_pressed(KeyCode::ArrowLeft) { + moved = true; + textbox_state.selection.0 = min; + } } else { - if self.input().key_pressed(KeyCode::ArrowRight) { moved = true; { textbox_state.selection.0 += 1; } } - if self.input().key_pressed(KeyCode::ArrowLeft) { moved = true; { if (textbox_state.selection.0 > 0) { textbox_state.selection.0 -= 1; } } } + if self.input().key_pressed(KeyCode::ArrowRight) { + moved = true; + { + textbox_state.selection.0 += 1; + } + } + if self.input().key_pressed(KeyCode::ArrowLeft) { + moved = true; + { + if (textbox_state.selection.0 > 0) { + textbox_state.selection.0 -= 1; + } + } + } } - if self.input().key_pressed(KeyCode::Home) { moved = true; textbox_state.selection.0 = 0; } - if self.input().key_pressed(KeyCode::ArrowUp) { moved = true; textbox_state.selection.0 = 0; } + if self.input().key_pressed(KeyCode::Home) { + moved = true; + textbox_state.selection.0 = 0; + } + if self.input().key_pressed(KeyCode::ArrowUp) { + moved = true; + textbox_state.selection.0 = 0; + } - if self.input().key_pressed(KeyCode::End) { moved = true; textbox_state.selection.0 = textbox_state.text_buf.len(); } - if self.input().key_pressed(KeyCode::ArrowDown) { moved = true; textbox_state.selection.0 = textbox_state.text_buf.len(); } + if self.input().key_pressed(KeyCode::End) { + moved = true; + textbox_state.selection.0 = textbox_state.text_buf.len(); + } + if self.input().key_pressed(KeyCode::ArrowDown) { + moved = true; + textbox_state.selection.0 = textbox_state.text_buf.len(); + } - textbox_state.selection.0 = textbox_state.selection.0.min(textbox_state.text_buf.len()); - textbox_state.selection.1 = textbox_state.selection.1.min(textbox_state.text_buf.len()); + textbox_state.selection.0 = + textbox_state.selection.0.min(textbox_state.text_buf.len()); + textbox_state.selection.1 = + textbox_state.selection.1.min(textbox_state.text_buf.len()); if moved && !shift { textbox_state.selection.1 = textbox_state.selection.0; @@ -639,15 +966,16 @@ impl Context { let has_selection = (textbox_state.selection.1 != textbox_state.selection.0); let select_all = (ctrl && self.input().key_pressed(KeyCode::KeyA)) || activated; - let copy = (ctrl && self.input().key_pressed(KeyCode::KeyC)); - let cut = (ctrl && self.input().key_pressed(KeyCode::KeyX)) || (shift && self.input().key_pressed(KeyCode::Delete)); + let copy = (ctrl && self.input().key_pressed(KeyCode::KeyC)); + let cut = (ctrl && self.input().key_pressed(KeyCode::KeyX)) + || (shift && self.input().key_pressed(KeyCode::Delete)); let backspace = self.input().key_pressed(KeyCode::Backspace); - let delete = self.input().key_pressed(KeyCode::Delete); + let delete = self.input().key_pressed(KeyCode::Delete); let has_input = self.input().text_input.is_some(); let should_copy = has_selection && (copy || cut); - let should_cut = has_selection && cut; + let should_cut = has_selection && cut; if select_all { textbox_state.selection.1 = 0; @@ -655,12 +983,17 @@ impl Context { } // Sort selection - let (mut min, mut max) = (textbox_state.selection.0.min(textbox_state.selection.1), - textbox_state.selection.0.max(textbox_state.selection.1)); + let (mut min, mut max) = ( + textbox_state.selection.0.min(textbox_state.selection.1), + textbox_state.selection.0.max(textbox_state.selection.1), + ); if min == max { - if backspace { min = min.saturating_sub(1); } - else if delete { max = max.saturating_add(1); } + if backspace { + min = min.saturating_sub(1); + } else if delete { + max = max.saturating_add(1); + } } min = min.min(textbox_state.text_buf.len()); @@ -680,9 +1013,14 @@ impl Context { let pre_slice = &textbox_state.text_buf[..min]; let pst_slice = &textbox_state.text_buf[max..]; - let input_len = if let Some(input) = &self.input().text_input { input.len() } else { 0 }; + let input_len = if let Some(input) = &self.input().text_input { + input.len() + } else { + 0 + }; - let mut new_buf = Vec::with_capacity(pre_slice.len() + input_len + pst_slice.len()); + let mut new_buf = + Vec::with_capacity(pre_slice.len() + input_len + pst_slice.len()); new_buf.extend_from_slice(pre_slice); if let Some(input) = &self.input().text_input { new_buf.extend_from_slice(input); @@ -706,51 +1044,191 @@ impl Context { if let _ = elem().decl(Decl { id, - padding, child_gap, radius, + padding, + child_gap, + radius, width: grow!(), height: fit!(), colour, ..Decl }) { - self.text(&str, TextDecl { - colour: text_colour, - h, - align: AlignX::Center, - ..TextDecl - }); + self.text( + &str, + TextDecl { + colour: text_colour, + h, + align: AlignX::Center, + ..TextDecl + }, + ); } text } - } -pub trait Dup2: Copy { fn dup2(self) -> (Self, Self); } -impl Dup2 for T { fn dup2(self) -> (Self, Self) { (self, self) } } -pub trait Dup3: Copy { fn dup3(self) -> (Self, Self, Self); } -impl Dup3 for T { fn dup3(self) -> (Self, Self, Self) { (self, self, self) } } -pub trait Dup4: Copy { fn dup4(self) -> (Self, Self, Self, Self); } -impl Dup4 for T { fn dup4(self) -> (Self, Self, Self, Self) { (self, self, self, self) } } +pub trait Dup2: Copy { + fn dup2(self) -> (Self, Self); +} +impl Dup2 for T { + fn dup2(self) -> (Self, Self) { + (self, self) + } +} +pub trait Dup3: Copy { + fn dup3(self) -> (Self, Self, Self); +} +impl Dup3 for T { + fn dup3(self) -> (Self, Self, Self) { + (self, self, self) + } +} +pub trait Dup4: Copy { + fn dup4(self) -> (Self, Self, Self, Self); +} +impl Dup4 for T { + fn dup4(self) -> (Self, Self, Self, Self) { + (self, self, self, self) + } +} // Implementation of `tuple.mul(scalar)`. Helper "AsF32" trait to get it working. // This would literally be a two-liner, if Rust generics had C++-style SFINAE. -pub trait AsF32 { #[inline(always)] fn to_f32(self) -> f32; #[inline(always)] fn from_f32(x: f32) -> Self; } -impl AsF32 for u8 { #[inline(always)] fn to_f32(self) -> f32 { self as f32 } #[inline(always)] fn from_f32(x: f32) -> Self { x as Self } } -impl AsF32 for u16 { #[inline(always)] fn to_f32(self) -> f32 { self as f32 } #[inline(always)] fn from_f32(x: f32) -> Self { x as Self } } -impl AsF32 for u32 { #[inline(always)] fn to_f32(self) -> f32 { self as f32 } #[inline(always)] fn from_f32(x: f32) -> Self { x as Self } } -impl AsF32 for u64 { #[inline(always)] fn to_f32(self) -> f32 { self as f32 } #[inline(always)] fn from_f32(x: f32) -> Self { x as Self } } -impl AsF32 for i8 { #[inline(always)] fn to_f32(self) -> f32 { self as f32 } #[inline(always)] fn from_f32(x: f32) -> Self { x as Self } } -impl AsF32 for i16 { #[inline(always)] fn to_f32(self) -> f32 { self as f32 } #[inline(always)] fn from_f32(x: f32) -> Self { x as Self } } -impl AsF32 for i32 { #[inline(always)] fn to_f32(self) -> f32 { self as f32 } #[inline(always)] fn from_f32(x: f32) -> Self { x as Self } } -impl AsF32 for i64 { #[inline(always)] fn to_f32(self) -> f32 { self as f32 } #[inline(always)] fn from_f32(x: f32) -> Self { x as Self } } -impl AsF32 for f32 { #[inline(always)] fn to_f32(self) -> f32 { self as f32 } #[inline(always)] fn from_f32(x: f32) -> Self { x as Self } } -impl AsF32 for f64 { #[inline(always)] fn to_f32(self) -> f32 { self as f32 } #[inline(always)] fn from_f32(x: f32) -> Self { x as Self } } - -pub trait Mul { #[inline(always)] fn mul(self, f: f32) -> Self; } -impl Mul for T { #[inline(always)] fn mul(self, f: f32) -> Self { T::from_f32(self.to_f32() * f) } } -impl Mul for (A, B) { #[inline(always)] fn mul(self, f: f32) -> Self { (self.0.mul(f), self.1.mul(f)) } } -impl Mul for (A, B, C) { #[inline(always)] fn mul(self, f: f32) -> Self { (self.0.mul(f), self.1.mul(f), self.2.mul(f)) } } -impl Mul for (A, B, C, D) { #[inline(always)] fn mul(self, f: f32) -> Self { (self.0.mul(f), self.1.mul(f), self.2.mul(f), self.3.mul(f)) } } +pub trait AsF32 { + #[inline(always)] + fn to_f32(self) -> f32; + #[inline(always)] + fn from_f32(x: f32) -> Self; +} +impl AsF32 for u8 { + #[inline(always)] + fn to_f32(self) -> f32 { + self as f32 + } + #[inline(always)] + fn from_f32(x: f32) -> Self { + x as Self + } +} +impl AsF32 for u16 { + #[inline(always)] + fn to_f32(self) -> f32 { + self as f32 + } + #[inline(always)] + fn from_f32(x: f32) -> Self { + x as Self + } +} +impl AsF32 for u32 { + #[inline(always)] + fn to_f32(self) -> f32 { + self as f32 + } + #[inline(always)] + fn from_f32(x: f32) -> Self { + x as Self + } +} +impl AsF32 for u64 { + #[inline(always)] + fn to_f32(self) -> f32 { + self as f32 + } + #[inline(always)] + fn from_f32(x: f32) -> Self { + x as Self + } +} +impl AsF32 for i8 { + #[inline(always)] + fn to_f32(self) -> f32 { + self as f32 + } + #[inline(always)] + fn from_f32(x: f32) -> Self { + x as Self + } +} +impl AsF32 for i16 { + #[inline(always)] + fn to_f32(self) -> f32 { + self as f32 + } + #[inline(always)] + fn from_f32(x: f32) -> Self { + x as Self + } +} +impl AsF32 for i32 { + #[inline(always)] + fn to_f32(self) -> f32 { + self as f32 + } + #[inline(always)] + fn from_f32(x: f32) -> Self { + x as Self + } +} +impl AsF32 for i64 { + #[inline(always)] + fn to_f32(self) -> f32 { + self as f32 + } + #[inline(always)] + fn from_f32(x: f32) -> Self { + x as Self + } +} +impl AsF32 for f32 { + #[inline(always)] + fn to_f32(self) -> f32 { + self as f32 + } + #[inline(always)] + fn from_f32(x: f32) -> Self { + x as Self + } +} +impl AsF32 for f64 { + #[inline(always)] + fn to_f32(self) -> f32 { + self as f32 + } + #[inline(always)] + fn from_f32(x: f32) -> Self { + x as Self + } +} + +pub trait Mul { + #[inline(always)] + fn mul(self, f: f32) -> Self; +} +impl Mul for T { + #[inline(always)] + fn mul(self, f: f32) -> Self { + T::from_f32(self.to_f32() * f) + } +} +impl Mul for (A, B) { + #[inline(always)] + fn mul(self, f: f32) -> Self { + (self.0.mul(f), self.1.mul(f)) + } +} +impl Mul for (A, B, C) { + #[inline(always)] + fn mul(self, f: f32) -> Self { + (self.0.mul(f), self.1.mul(f), self.2.mul(f)) + } +} +impl Mul for (A, B, C, D) { + #[inline(always)] + fn mul(self, f: f32) -> Self { + (self.0.mul(f), self.1.mul(f), self.2.mul(f), self.3.mul(f)) + } +} #[derive(Debug, Clone, PartialEq, Default)] pub struct TextboxState { @@ -760,32 +1238,36 @@ pub struct TextboxState { pub h: f32, } -pub fn ui_left_pane(ui: &mut Context, - wallet_state: Arc>, - data: &mut UiData, - viz: &mut VizState, - child_gap: f32, - padding: (f32, f32, f32, f32), - radius: (f32, f32, f32, f32), - tab_id: &mut Id) { - - if ui.modal != Modal::None && let _elem = elem().decl(Decl { - child_gap, - id: id("Modal Container"), - padding: padding.mul(2.0), - radius: (radius.0, 0.0, radius.2, 0.0), - floating: Floating::Parent, - colour: (0, 0, 0, 0xC0), - align: Center, - width: grow!(), - height: grow!(), - ..Decl - }) { - +pub fn ui_left_pane( + ui: &mut Context, + wallet_state: Arc>, + data: &mut UiData, + viz: &mut VizState, + child_gap: f32, + padding: (f32, f32, f32, f32), + radius: (f32, f32, f32, f32), + tab_id: &mut Id, +) { + if ui.modal != Modal::None + && let _elem = elem().decl(Decl { + child_gap, + id: id("Modal Container"), + padding: padding.mul(2.0), + radius: (radius.0, 0.0, radius.2, 0.0), + floating: Floating::Parent, + colour: (0, 0, 0, 0xC0), + align: Center, + width: grow!(), + height: grow!(), + ..Decl + }) + { let container_id = _elem.decl.id; let container_hovered = ui.hovered(container_id); - if container_hovered { ui.capture = true; } + if container_hovered { + ui.capture = true; + } let mut height = grow!(ui.scale(192.0), ui.scale(384.0)); if ui.modal == Modal::Stake { @@ -793,47 +1275,79 @@ pub fn ui_left_pane(ui: &mut Context, } if let _elem = elem().decl(Decl { - child_gap, radius, + child_gap, + radius, id: id("Modal Contents"), padding: padding.mul(2.0), colour: MODAL_COL, - width: grow!(ui.scale(192.0), ui.scale(384.0)), + width: grow!(ui.scale(192.0), ui.scale(384.0)), height: height, align: Top, direction: TopToBottom, ..Decl }) { - let contents_id = _elem.decl.id; let contents_hovered = ui.hovered(contents_id); - if container_hovered { ui.capture = true; } + if container_hovered { + ui.capture = true; + } let text_h = ui.scale(24.0); let title_bar = |ui: &mut Context, closeable, title, title_bar_id| { if let _ = elem().decl(Decl { id: title_bar_id, child_gap, - width: grow!(), + width: grow!(), height: fit!(), align: Center, direction: LeftToRight, ..Decl }) { - if let _ = elem().decl(Decl { width: grow!(), align: Left, ..Decl }) {} - if let _ = elem().decl(Decl { width: grow!(), align: Center, ..Decl }) { - ui.text(title, TextDecl { h: text_h, align: AlignX::Center, ..TextDecl }); + if let _ = elem().decl(Decl { + width: grow!(), + align: Left, + ..Decl + }) {} + if let _ = elem().decl(Decl { + width: grow!(), + align: Center, + ..Decl + }) { + ui.text( + title, + TextDecl { + h: text_h, + align: AlignX::Center, + ..TextDecl + }, + ); } - if let _ = elem().decl(Decl { id: id("Title Bar Right Side"), width: grow!(), align: Right, ..Decl }) && closeable { + if let _ = elem().decl(Decl { + id: id("Title Bar Right Side"), + width: grow!(), + align: Right, + ..Decl + }) && closeable + { let id = id("Close This Modal"); - let (clicked, colour, _) = ui.button_ex(false, BUTTON_GREY, id, true, winit::window::CursorIcon::Default); + let (clicked, colour, _) = ui.button_ex( + false, + BUTTON_GREY, + id, + true, + winit::window::CursorIcon::Default, + ); if clicked || (ui.input().key_pressed(KeyCode::Escape) && !ui.nav_enable) { ui.modal = Modal::None; } // Click background to exit -- the code could be placed farther outside but it is here so it can be gated by `closeable` - if ui.hovered(container_id) && !ui.hovered(contents_id) && ui.input().mouse_pressed(winit::event::MouseButton::Left) { + if ui.hovered(container_id) + && !ui.hovered(contents_id) + && ui.input().mouse_pressed(winit::event::MouseButton::Left) + { ui.modal = Modal::None; ui.mouse_pressed_id = id; } @@ -842,13 +1356,26 @@ pub fn ui_left_pane(ui: &mut Context, // Button circle if let _ = elem().decl(Decl { - id, colour, radius: radius.dup4(), padding, child_gap, align: Center, - width: fixed!(radius * 2.0), + id, + colour, + radius: radius.dup4(), + padding, + child_gap, + align: Center, + width: fixed!(radius * 2.0), height: fixed!(radius * 2.0), ..Decl }) { let temp_letter_symbol_h = ui.scale(24.0); - ui.text(ICON_CANCEL, TextDecl { font: Icons, h: temp_letter_symbol_h, align: AlignX::Center, ..TextDecl }); + ui.text( + ICON_CANCEL, + TextDecl { + font: Icons, + h: temp_letter_symbol_h, + align: AlignX::Center, + ..TextDecl + }, + ); } } } @@ -861,7 +1388,13 @@ pub fn ui_left_pane(ui: &mut Context, hsva.2 = ((hsva.2 as f32) * 1.25).min(255.0) as u8; hsva.rgba() }; - let (clicked, colour, text_colour) = ui.button_ex(false, colour, id, enabled, winit::window::CursorIcon::Default); + let (clicked, colour, text_colour) = ui.button_ex( + false, + colour, + id, + enabled, + winit::window::CursorIcon::Default, + ); let radius = ui.scale(24.0); if let _ = elem().decl(Decl { id, @@ -870,12 +1403,20 @@ pub fn ui_left_pane(ui: &mut Context, radius: radius.dup4(), align: Align::Center, direction: TopToBottom, - width: fit!(ui.scale(192.0)), + width: fit!(ui.scale(192.0)), height: fit!(radius * 2.0), ..Decl }) { let h = ui.scale(20.0); - ui.text(label, TextDecl { h, colour: text_colour, align: AlignX::Center, ..TextDecl }); + ui.text( + label, + TextDecl { + h, + colour: text_colour, + align: AlignX::Center, + ..TextDecl + }, + ); } clicked @@ -884,57 +1425,79 @@ pub fn ui_left_pane(ui: &mut Context, match ui.modal { Modal::None => {} Modal::Send => { - title_bar(ui, true, "Send", id("Send Title Bar")); + title_bar(ui, true, "Send", id("Send Title Bar")); if let _ = elem().decl(Decl { - child_gap, radius, + child_gap, + radius, id: id("Send Container"), colour: MODAL_COL, - width: grow!(), + width: grow!(), height: grow!(), align: Center, direction: TopToBottom, ..Decl }) { // spacer - if let _ = elem().decl(Decl { width: grow!(), height: fixed!(ui.scale(4.0)), ..Default::default() }) {} + if let _ = elem().decl(Decl { + width: grow!(), + height: fixed!(ui.scale(4.0)), + ..Default::default() + }) {} data.send_address = ui.textbox(data, id("Send Address Textbox")); // spacer - if let _ = elem().decl(Decl { width: grow!(), height: fixed!(ui.scale(16.0)), ..Default::default() }) {} + if let _ = elem().decl(Decl { + width: grow!(), + height: fixed!(ui.scale(16.0)), + ..Default::default() + }) {} if (wallet_state.lock().unwrap().balance as u64) < ONE_cTAZ / 100 { let colour = (0xff, 0xaf, 0x0e, 0xff); - ui.text("Insufficient funds. Try the faucet!", TextDecl { h: ui.scale(20.0), colour, align: AlignX::Center, ..TextDecl }); + ui.text( + "Insufficient funds. Try the faucet!", + TextDecl { + h: ui.scale(20.0), + colour, + align: AlignX::Center, + ..TextDecl + }, + ); } const ONE_cTAZ: u64 = 100_000_000; if let _ = elem().decl(Decl { - child_gap, radius, + child_gap, + radius, id: id("Send Buttons"), colour: MODAL_COL, - width: grow!(), + width: grow!(), height: grow!(), align: Center, direction: TopToBottom, ..Decl }) { - let ( - balance, - waiting_for_send - ) = { + let (balance, waiting_for_send) = { let wallet_state = wallet_state.lock().unwrap(); - ( - wallet_state.balance, - wallet_state.waiting_for_send, - ) + (wallet_state.balance, wallet_state.waiting_for_send) }; let can = !waiting_for_send && data.send_address.len() != 0; - if button_ex(ui, "1 cTAZ", can && (balance as u64) >= ONE_cTAZ) { wallet_state.lock().unwrap().send_to_address(data.send_address.clone(), ONE_cTAZ); } - if button_ex(ui, "10 cTAZ", can && (balance as u64) >= ONE_cTAZ * 10) { wallet_state.lock().unwrap().send_to_address(data.send_address.clone(), ONE_cTAZ * 10); } + if button_ex(ui, "1 cTAZ", can && (balance as u64) >= ONE_cTAZ) { + wallet_state + .lock() + .unwrap() + .send_to_address(data.send_address.clone(), ONE_cTAZ); + } + if button_ex(ui, "10 cTAZ", can && (balance as u64) >= ONE_cTAZ * 10) { + wallet_state + .lock() + .unwrap() + .send_to_address(data.send_address.clone(), ONE_cTAZ * 10); + } } } } @@ -942,10 +1505,11 @@ pub fn ui_left_pane(ui: &mut Context, title_bar(ui, true, "Receive", id("Receive Title Bar")); if let _ = elem().decl(Decl { - child_gap, radius, + child_gap, + radius, id: id("Receive Container"), colour: MODAL_COL, - width: grow!(), + width: grow!(), height: grow!(), align: Center, direction: TopToBottom, @@ -953,13 +1517,31 @@ pub fn ui_left_pane(ui: &mut Context, }) { let ua = &wallet_state.lock().unwrap().user_recv_ua; if ua.len() != 0 { - ui.text(frame_strf!(data, "[{}..{}]", &ua[..8], &ua[ua.len() - 8..]), TextDecl { font: Mono, h: ui.scale(20.0), colour: WHITE, align: AlignX::Center, ..TextDecl }); + ui.text( + frame_strf!(data, "[{}..{}]", &ua[..8], &ua[ua.len() - 8..]), + TextDecl { + font: Mono, + h: ui.scale(20.0), + colour: WHITE, + align: AlignX::Center, + ..TextDecl + }, + ); if button_ex(ui, "Copy Address", true) { ui.input().send_to_clipboard(&ua); } } else { - ui.text("Loading...", TextDecl { font: Mono, h: ui.scale(20.0), colour: WHITE, align: AlignX::Center, ..TextDecl }); + ui.text( + "Loading...", + TextDecl { + font: Mono, + h: ui.scale(20.0), + colour: WHITE, + align: AlignX::Center, + ..TextDecl + }, + ); } } } @@ -971,21 +1553,48 @@ pub fn ui_left_pane(ui: &mut Context, stake_address = &data.stake_address; } - ui.text(frame_strf!(data, "[{}..{}]", &stake_address[0..8], &stake_address[stake_address.len()-8..]), TextDecl { font: Mono, h: ui.scale(20.0), colour: WHITE, align: AlignX::Center, ..TextDecl }); + ui.text( + frame_strf!( + data, + "[{}..{}]", + &stake_address[0..8], + &stake_address[stake_address.len() - 8..] + ), + TextDecl { + font: Mono, + h: ui.scale(20.0), + colour: WHITE, + align: AlignX::Center, + ..TextDecl + }, + ); if button_ex(ui, "Paste Address", true) { data.stake_address = ui.input().get_from_clipboard().trim().to_string(); } // spacer - if let _ = elem().decl(Decl { width: grow!(), height: fixed!(ui.scale(16.0)), ..Default::default() }) {} + if let _ = elem().decl(Decl { + width: grow!(), + height: fixed!(ui.scale(16.0)), + ..Default::default() + }) {} if (wallet_state.lock().unwrap().balance as u64) < ONE_cTAZ / 100 { let colour = (0xff, 0xaf, 0x0e, 0xff); - ui.text("Insufficient funds. Try the faucet!", TextDecl { h: ui.scale(20.0), colour, align: AlignX::Center, ..TextDecl }); + ui.text( + "Insufficient funds. Try the faucet!", + TextDecl { + h: ui.scale(20.0), + colour, + align: AlignX::Center, + ..TextDecl + }, + ); } const ONE_cTAZ: u64 = 100_000_000; - let waiting_for_stake_to_finalizer = wallet_state.lock().unwrap().waiting_for_stake_to_finalizer; + let waiting_for_stake_to_finalizer = + wallet_state.lock().unwrap().waiting_for_stake_to_finalizer; { let balance = wallet_state.lock().unwrap().balance; @@ -1018,8 +1627,8 @@ pub fn ui_left_pane(ui: &mut Context, }; let mut buf = [0u8; 32]; for i in 0..32 { - let a = data.get(2*i)?; - let b = data.get(2*i + 1)?; + let a = data.get(2 * i)?; + let b = data.get(2 * i + 1)?; let a = VALS[*a as usize]; if a == 0xff { return None; @@ -1028,17 +1637,37 @@ pub fn ui_left_pane(ui: &mut Context, if b == 0xff { return None; } - buf[31-i] = (a << 4) | b + buf[31 - i] = (a << 4) | b } Some(buf) } let hex_dest = addr_from_str_bytes(data.stake_address.as_bytes()); let can = !waiting_for_stake_to_finalizer && hex_dest.is_some(); - if button_ex(ui, "+0.01 cTAZ", can && (balance as u64) >= ONE_cTAZ / 100) { wallet_state.lock().unwrap().stake_to_finalizer(ONE_cTAZ / 100, hex_dest.unwrap()); } - if button_ex(ui, "+0.1 cTAZ", can && (balance as u64) >= ONE_cTAZ / 10) { wallet_state.lock().unwrap().stake_to_finalizer(ONE_cTAZ / 10, hex_dest.unwrap()); } - if button_ex(ui, "+1 cTAZ", can && (balance as u64) >= ONE_cTAZ) { wallet_state.lock().unwrap().stake_to_finalizer(ONE_cTAZ, hex_dest.unwrap()); } - if button_ex(ui, "+10 cTAZ", can && (balance as u64) >= ONE_cTAZ * 10) { wallet_state.lock().unwrap().stake_to_finalizer(ONE_cTAZ * 10, hex_dest.unwrap()); } + if button_ex(ui, "+0.01 cTAZ", can && (balance as u64) >= ONE_cTAZ / 100) { + wallet_state + .lock() + .unwrap() + .stake_to_finalizer(ONE_cTAZ / 100, hex_dest.unwrap()); + } + if button_ex(ui, "+0.1 cTAZ", can && (balance as u64) >= ONE_cTAZ / 10) { + wallet_state + .lock() + .unwrap() + .stake_to_finalizer(ONE_cTAZ / 10, hex_dest.unwrap()); + } + if button_ex(ui, "+1 cTAZ", can && (balance as u64) >= ONE_cTAZ) { + wallet_state + .lock() + .unwrap() + .stake_to_finalizer(ONE_cTAZ, hex_dest.unwrap()); + } + if button_ex(ui, "+10 cTAZ", can && (balance as u64) >= ONE_cTAZ * 10) { + wallet_state + .lock() + .unwrap() + .stake_to_finalizer(ONE_cTAZ * 10, hex_dest.unwrap()); + } } } Modal::Unstake => { @@ -1072,7 +1701,13 @@ pub fn ui_left_pane(ui: &mut Context, let mut button_ex = |ui: &mut Context, label, act_on_press, enabled: bool| { let id = id(label); - let (clicked, colour, text_colour) = ui.button_ex(act_on_press, BUTTON_GREY, id, enabled, winit::window::CursorIcon::Default); + let (clicked, colour, text_colour) = ui.button_ex( + act_on_press, + BUTTON_GREY, + id, + enabled, + winit::window::CursorIcon::Default, + ); if let _ = elem().decl(Decl { id, child_gap, @@ -1091,23 +1726,37 @@ pub fn ui_left_pane(ui: &mut Context, child_gap, radius: radius.dup4(), align: Center, - width: fit!(ui.scale(192.0)), + width: fit!(ui.scale(192.0)), height: fit!(radius * 2.0), ..Decl }) { let h = ui.scale(20.0); - ui.text(label, TextDecl { h, colour: text_colour, align: AlignX::Center, ..TextDecl }); + ui.text( + label, + TextDecl { + h, + colour: text_colour, + align: AlignX::Center, + ..TextDecl + }, + ); } } clicked }; - let mut clickable_icon = |ui: &mut Context, id, icon, icon_hovered, enabled | { - let (clicked, colour, _) = ui.button_ex(true, (0xcc, 0xcc, 0xcc, 0xff) /* @todo colors */, id, enabled, winit::window::CursorIcon::Pointer); + let mut clickable_icon = |ui: &mut Context, id, icon, icon_hovered, enabled| { + let (clicked, colour, _) = ui.button_ex( + true, + (0xcc, 0xcc, 0xcc, 0xff), /* @todo colors */ + id, + enabled, + winit::window::CursorIcon::Pointer, + ); let icon = if ui.hovered(id) { icon_hovered } else { icon }; - if let _ = elem().decl(Decl{ + if let _ = elem().decl(Decl { id, child_gap, align: Center, @@ -1116,13 +1765,21 @@ pub fn ui_left_pane(ui: &mut Context, height: fit!(), ..Decl }) { - ui.text(icon, TextDecl { font: Icons, colour, h: ui.scale(24.0), align: AlignX::Center, ..TextDecl }); + ui.text( + icon, + TextDecl { + font: Icons, + colour, + h: ui.scale(24.0), + align: AlignX::Center, + ..TextDecl + }, + ); } clicked }; - let staked_roster = wallet_state.lock().unwrap().staked_roster.clone(); if staked_roster.len() == 0 { ui.unstake_scroll = 0.0; @@ -1130,13 +1787,14 @@ pub fn ui_left_pane(ui: &mut Context, let id = id("Unstake Scroll Container"); if ui.hovered(id) { - ui.unstake_scroll -= ui.input().zoom_delta as f32 * 32.0; + ui.unstake_scroll -= ui.input().zoom_delta as f32 * 32.0; ui.unstake_scroll -= ui.input().scroll_delta.1 as f32 * 32.0; } if ui.unstake_scroll < 0.0 { ui.unstake_scroll = 0.0; } - let scroll_container_data: clay::Clay_ScrollContainerData = unsafe { clay::Clay_GetScrollContainerData(id.clay().id) }; + let scroll_container_data: clay::Clay_ScrollContainerData = + unsafe { clay::Clay_GetScrollContainerData(id.clay().id) }; if scroll_container_data.found { let max = scroll_container_data.contentDimensions.height / ui.scale - 96.0; if ui.unstake_scroll > max { @@ -1146,9 +1804,10 @@ pub fn ui_left_pane(ui: &mut Context, if let _ = elem().decl(Decl { id, colour: TRANSACTION_HISTORY_CONTAINER_COL, - child_gap: child_gap * 0.5, padding, + child_gap: child_gap * 0.5, + padding, radius: padding.0.dup4(), - width: percent!(1.0), + width: percent!(1.0), height: grow!(), // height: percent!(1.0), direction: TopToBottom, @@ -1160,22 +1819,39 @@ pub fn ui_left_pane(ui: &mut Context, let h = ui.scale(24.0); if let _ = elem().decl(Decl { direction: TopToBottom, - width: percent!(1.0), + width: percent!(1.0), height: percent!(1.0), child_gap, - align: Center, + align: Center, ..Decl }) { - ui.text(ICON_EYE_OFF, TextDecl { font: Icons, colour: WHITE.mul(0.6), h: ui.scale(64.0), align: AlignX::Center, ..TextDecl }); - ui.text("You have not staked to any finalizers yet.", TextDecl { colour: WHITE.mul(0.6), h, align: AlignX::Center, ..TextDecl }); + ui.text( + ICON_EYE_OFF, + TextDecl { + font: Icons, + colour: WHITE.mul(0.6), + h: ui.scale(64.0), + align: AlignX::Center, + ..TextDecl + }, + ); + ui.text( + "You have not staked to any finalizers yet.", + TextDecl { + colour: WHITE.mul(0.6), + h, + align: AlignX::Center, + ..TextDecl + }, + ); } - } - else { + } else { let kind_text_h = ui.scale(18.0); let transaction_text_h = ui.scale(16.0); for (index, member) in staked_roster.iter().enumerate() { - if index > 0 { // separator + if index > 0 { + // separator let colour = { let mut col = TRANSACTION_HISTORY_CONTAINER_COL; col = col.hsva(); @@ -1183,9 +1859,14 @@ pub fn ui_left_pane(ui: &mut Context, col.rgba() }; - let _ = elem().decl(Decl { colour, height: fixed!(ui.scale(2.0)), width: percent!(1.0), ..Decl }); + let _ = elem().decl(Decl { + colour, + height: fixed!(ui.scale(2.0)), + width: percent!(1.0), + ..Decl + }); } - if let _ = elem().decl(Decl{ + if let _ = elem().decl(Decl { id: id_index("Unstake Roster Member", index as u32), padding, child_gap, @@ -1196,21 +1877,33 @@ pub fn ui_left_pane(ui: &mut Context, ..Decl }) { // left icon - if let _ = elem().decl(Decl{ - id: id_index("Unstake Roster Member Left Icon", index as u32), + if let _ = elem().decl(Decl { + id: id_index( + "Unstake Roster Member Left Icon", + index as u32, + ), height: fit!(), width: fixed!(ui.scale(32.0)), direction: TopToBottom, align: Center, ..Decl }) { - if clickable_icon(ui, id_index("Unstake Button", index as u32), ICON_LINK_1, ICON_UNLINK, true) { - wallet_state.lock().unwrap().unstake_from_finalizer(member.1); + if clickable_icon( + ui, + id_index("Unstake Button", index as u32), + ICON_LINK_1, + ICON_UNLINK, + true, + ) { + wallet_state + .lock() + .unwrap() + .unstake_from_finalizer(member.1); } } // info - if let _ = elem().decl(Decl{ + if let _ = elem().decl(Decl { id: id_index("Unstake Roster Member Info", index as u32), height: fit!(), width: grow!(), @@ -1232,11 +1925,19 @@ pub fn ui_left_pane(ui: &mut Context, chunks }; - ui.text(frame_strf!(data, "{}", display_str(&chunks)), TextDecl { font: Mono, h: ui.scale(14.0), align: AlignX::Left, ..TextDecl }); + ui.text( + frame_strf!(data, "{}", display_str(&chunks)), + TextDecl { + font: Mono, + h: ui.scale(14.0), + align: AlignX::Left, + ..TextDecl + }, + ); } // right info - if let _ = elem().decl(Decl{ + if let _ = elem().decl(Decl { id: id_index("Unstake Roster Member Amounts", index as u32), height: fit!(), width: fit!(), @@ -1252,7 +1953,12 @@ pub fn ui_left_pane(ui: &mut Context, let id = id_index("Unstake Clickable Text", index as u32); let mut colour = (0xff, 0xaf, 0x0e, 0xff); // @todo color - let mut str = frame_strf!(data, "{}.{} cTAZ", full, &part_str[..trim_part.len().max(3)]); + let mut str = frame_strf!( + data, + "{}.{} cTAZ", + full, + &part_str[..trim_part.len().max(3)] + ); if ui.hovered(id) { let stake_amount: i64 = member.2 as i64; let full = stake_amount / 100_000_000; @@ -1261,16 +1967,30 @@ pub fn ui_left_pane(ui: &mut Context, let trim_part = part_str.trim_end_matches("0"); colour = WHITE; - str = frame_strf!(data, "{}.{} cTAZ", full, &part_str[..trim_part.len().max(3)]); + str = frame_strf!( + data, + "{}.{} cTAZ", + full, + &part_str[..trim_part.len().max(3)] + ); } - if let _ = elem().decl(Decl{ + if let _ = elem().decl(Decl { id, width: fit!(), height: fit!(), ..Decl }) { - ui.text(str, TextDecl { font: Mono, h: ui.scale(14.0), colour, align: AlignX::Right, ..TextDecl }); + ui.text( + str, + TextDecl { + font: Mono, + h: ui.scale(14.0), + colour, + align: AlignX::Right, + ..TextDecl + }, + ); } } } @@ -1286,7 +2006,6 @@ pub fn ui_left_pane(ui: &mut Context, let mut tab_id_wallet = Id::default(); - // @Todo: How to avoid doing this? Clearing the nav array when there is a modal? ui.nav_skip = (ui.modal != Modal::None); @@ -1298,14 +2017,20 @@ pub fn ui_left_pane(ui: &mut Context, align: Center, ..Decl }) { - tab_id_wallet = ui.tab((radius.0, 0.0, radius.2, radius.3), padding, tab_id, "Wallet"); + tab_id_wallet = ui.tab( + (radius.0, 0.0, radius.2, radius.3), + padding, + tab_id, + "Wallet", + ); } // Main contents if let _ = elem().decl(Decl { id: id("Main Contents"), colour: PANE_COL, - padding, child_gap, + padding, + child_gap, radius: (0.0, 0.0, radius.2, 0.0), direction: TopToBottom, align: Top, @@ -1315,17 +2040,12 @@ pub fn ui_left_pane(ui: &mut Context, ..Decl }) { let balance_text_h = ui.scale(48.0); - let accent_text_h = ui.scale(16.0); + let accent_text_h = ui.scale(16.0); // spacer // if let _ = elem().decl(Decl { width: grow!(), height: fixed!(ui.scale(16.0)), ..Default::default() }) {} - let ( - balance, - pending_balance, - staked_balance, - show_staked_balance, - ) = { + let (balance, pending_balance, staked_balance, show_staked_balance) = { let wallet_state = wallet_state.lock().unwrap(); ( wallet_state.balance, @@ -1337,14 +2057,20 @@ pub fn ui_left_pane(ui: &mut Context, // balance container let mut balance = balance; - let mut colour = WHITE; + let mut colour = WHITE; if show_staked_balance { balance = staked_balance; - colour = (0xff, 0xaf, 0x0e, 0xff); + colour = (0xff, 0xaf, 0x0e, 0xff); } let balance_id = id("Balance Text"); - let (clicked, colour, _) = ui.button_ex(true, colour, balance_id, true, winit::window::CursorIcon::Pointer); + let (clicked, colour, _) = ui.button_ex( + true, + colour, + balance_id, + true, + winit::window::CursorIcon::Pointer, + ); if clicked { wallet_state.lock().unwrap().show_staked_balance = !show_staked_balance; } @@ -1357,9 +2083,26 @@ pub fn ui_left_pane(ui: &mut Context, align: Center, ..Decl }) { - let suffix = if !show_staked_balance && staked_balance > 0 { "*" } else { "" }; - let balance_str = frame_strf!(data, "{} cTAZ{}", str_from_ctaz(balance.try_into().unwrap()), suffix); - ui.text(&balance_str, TextDecl { colour, h: balance_text_h, align: AlignX::Center, ..TextDecl }); + let suffix = if !show_staked_balance && staked_balance > 0 { + "*" + } else { + "" + }; + let balance_str = frame_strf!( + data, + "{} cTAZ{}", + str_from_ctaz(balance.try_into().unwrap()), + suffix + ); + ui.text( + &balance_str, + TextDecl { + colour, + h: balance_text_h, + align: AlignX::Center, + ..TextDecl + }, + ); } // pending container @@ -1372,8 +2115,20 @@ pub fn ui_left_pane(ui: &mut Context, // let staked_str = frame_strf!(data, "{} cTAZ Staked", str_from_ctaz(staked_balance.try_into().unwrap())); // ui.text(&staked_str, TextDecl { h: accent_text_h, align: AlignX::Center, colour: (0x90, 0x90, 0x90, 0xff) /* @todo colors */, ..TextDecl }); - let balance_str = frame_strf!(data, "{} cTAZ Pending", str_from_ctaz(pending_balance.try_into().unwrap())); - ui.text(&balance_str, TextDecl { h: accent_text_h, align: AlignX::Center, colour: (0x90, 0x90, 0x90, 0xff) /* @todo colors */, ..TextDecl }); + let balance_str = frame_strf!( + data, + "{} cTAZ Pending", + str_from_ctaz(pending_balance.try_into().unwrap()) + ); + ui.text( + &balance_str, + TextDecl { + h: accent_text_h, + align: AlignX::Center, + colour: (0x90, 0x90, 0x90, 0xff), /* @todo colors */ + ..TextDecl + }, + ); } // spacer @@ -1385,46 +2140,82 @@ pub fn ui_left_pane(ui: &mut Context, // buttons container if let _ = elem().decl(Decl { id: id("Buttons Container"), - padding, child_gap, align: Center, + padding, + child_gap, + align: Center, width: grow!(), height: fit!(), ..Decl }) { - let mut button = |ui: &mut Context, icon: &'static str, label: &'static str| { let id = id(label); - let (clicked, colour, text_colour) = ui.button_ex(true, BUTTON_BLUE, id, true, winit::window::CursorIcon::Default); + let (clicked, colour, text_colour) = ui.button_ex( + true, + BUTTON_BLUE, + id, + true, + winit::window::CursorIcon::Default, + ); if let _ = elem().decl(Decl { - id, child_gap, align: Center, + id, + child_gap, + align: Center, direction: TopToBottom, width: fit!(), height: fit!(), ..Decl }) { - let radius = ui.scale(24.0); // Button circle if let _ = elem().decl(Decl { - colour, radius: radius.dup4(), padding, child_gap, align: Center, - width: fixed!(radius * 2.0), + colour, + radius: radius.dup4(), + padding, + child_gap, + align: Center, + width: fixed!(radius * 2.0), height: fixed!(radius * 2.0), ..Decl }) { let temp_letter_symbol_h = ui.scale(28.0); - ui.text(icon, TextDecl { colour: text_colour, font: Icons, h: temp_letter_symbol_h, align: AlignX::Center, ..TextDecl }); + ui.text( + icon, + TextDecl { + colour: text_colour, + font: Icons, + h: temp_letter_symbol_h, + align: AlignX::Center, + ..TextDecl + }, + ); } let button_text_h = ui.scale(16.0); - ui.text(label, TextDecl { h: button_text_h, align: AlignX::Center, ..TextDecl }); + ui.text( + label, + TextDecl { + h: button_text_h, + align: AlignX::Center, + ..TextDecl + }, + ); } clicked }; - if button(ui, ICON_UP_BIG, "Send") { ui.modal = Modal::Send; } - if button(ui, ICON_QRCODE, "Receive") { ui.modal = Modal::Receive; } - if button(ui, ICON_LINK_1, "Stake") { ui.modal = Modal::Stake; } - if button(ui, ICON_UNLINK, "Unstake") { ui.modal = Modal::Unstake; } + if button(ui, ICON_UP_BIG, "Send") { + ui.modal = Modal::Send; + } + if button(ui, ICON_QRCODE, "Receive") { + ui.modal = Modal::Receive; + } + if button(ui, ICON_LINK_1, "Stake") { + ui.modal = Modal::Stake; + } + if button(ui, ICON_UNLINK, "Unstake") { + ui.modal = Modal::Unstake; + } } // if let _ = elem().decl(Decl { width: grow!(), height: fixed!(ui.scale(32.0)), ..Default::default() }) {} @@ -1438,13 +2229,14 @@ pub fn ui_left_pane(ui: &mut Context, let id = id("History Scroll Container"); if ui.hovered(id) { - ui.history_scroll -= ui.input().zoom_delta as f32 * 32.0; + ui.history_scroll -= ui.input().zoom_delta as f32 * 32.0; ui.history_scroll -= ui.input().scroll_delta.1 as f32 * 32.0; } if ui.history_scroll < 0.0 { ui.history_scroll = 0.0; } - let scroll_container_data: clay::Clay_ScrollContainerData = unsafe { clay::Clay_GetScrollContainerData(id.clay().id) }; + let scroll_container_data: clay::Clay_ScrollContainerData = + unsafe { clay::Clay_GetScrollContainerData(id.clay().id) }; if scroll_container_data.found { let max = scroll_container_data.contentDimensions.height / ui.scale - 96.0; if ui.history_scroll > max { @@ -1454,9 +2246,10 @@ pub fn ui_left_pane(ui: &mut Context, if let _ = elem().decl(Decl { id, colour: TRANSACTION_HISTORY_CONTAINER_COL, - child_gap: child_gap * 0.5, padding, + child_gap: child_gap * 0.5, + padding, radius: padding.0.dup4(), - width: percent!(1.0), + width: percent!(1.0), height: grow!(), // height: percent!(1.0), direction: TopToBottom, @@ -1468,22 +2261,39 @@ pub fn ui_left_pane(ui: &mut Context, let h = ui.scale(24.0); if let _ = elem().decl(Decl { direction: TopToBottom, - width: percent!(1.0), + width: percent!(1.0), height: percent!(1.0), child_gap, - align: Center, + align: Center, ..Decl }) { - ui.text(ICON_DROPBOX_1, TextDecl { font: Icons, colour: WHITE.mul(0.6), h: ui.scale(64.0), align: AlignX::Center, ..TextDecl }); - ui.text("There are no transactions yet.", TextDecl { colour: WHITE.mul(0.6), h, align: AlignX::Center, ..TextDecl }); + ui.text( + ICON_DROPBOX_1, + TextDecl { + font: Icons, + colour: WHITE.mul(0.6), + h: ui.scale(64.0), + align: AlignX::Center, + ..TextDecl + }, + ); + ui.text( + "There are no transactions yet.", + TextDecl { + colour: WHITE.mul(0.6), + h, + align: AlignX::Center, + ..TextDecl + }, + ); } - } - else { + } else { let kind_text_h = ui.scale(18.0); let transaction_text_h = ui.scale(16.0); for (index, tx) in txs.iter().enumerate() { - if index > 0 { // separator + if index > 0 { + // separator let colour = { let mut col = TRANSACTION_HISTORY_CONTAINER_COL; col = col.hsva(); @@ -1491,9 +2301,14 @@ pub fn ui_left_pane(ui: &mut Context, col.rgba() }; - let _ = elem().decl(Decl { colour, height: fixed!(ui.scale(2.0)), width: percent!(1.0), ..Decl }); + let _ = elem().decl(Decl { + colour, + height: fixed!(ui.scale(2.0)), + width: percent!(1.0), + ..Decl + }); } - if let _ = elem().decl(Decl{ + if let _ = elem().decl(Decl { id: id_index("Transaction", index as u32), padding, child_gap, @@ -1504,7 +2319,7 @@ pub fn ui_left_pane(ui: &mut Context, ..Decl }) { // left icon - if let _ = elem().decl(Decl{ + if let _ = elem().decl(Decl { id: id_index("Left Icon", index as u32), height: fit!(), width: fixed!(ui.scale(32.0)), @@ -1514,12 +2329,12 @@ pub fn ui_left_pane(ui: &mut Context, }) { // TODO: account for mempool let icon = match (tx.kind(), tx.mined_h.is_in_block()) { - (WalletTxKind::Send, true) => ICON_UP_SMALL, + (WalletTxKind::Send, true) => ICON_UP_SMALL, (WalletTxKind::SelfSend, true) => ICON_DOWN_SMALL, - (WalletTxKind::Receive, true) => ICON_DOWN_SMALL, - (WalletTxKind::Shield, true) => ICON_SHIELD, - (WalletTxKind::Stake, true) => ICON_LINK_1, - (WalletTxKind::Unstake, true) => ICON_UNLINK, + (WalletTxKind::Receive, true) => ICON_DOWN_SMALL, + (WalletTxKind::Shield, true) => ICON_SHIELD, + (WalletTxKind::Stake, true) => ICON_LINK_1, + (WalletTxKind::Unstake, true) => ICON_UNLINK, _ => { let timer = (ui.tx_loading_animation_timer * 3.0) as u64; if timer % 3 == 0 { @@ -1539,11 +2354,20 @@ pub fn ui_left_pane(ui: &mut Context, (0x60, 0x60, 0x60, 0xff) /* @todo colors */ }; - ui.text(icon, TextDecl { font: Icons, colour, h: ui.scale(24.0), align: AlignX::Center, ..TextDecl }); + ui.text( + icon, + TextDecl { + font: Icons, + colour, + h: ui.scale(24.0), + align: AlignX::Center, + ..TextDecl + }, + ); } // info - if let _ = elem().decl(Decl{ + if let _ = elem().decl(Decl { id: id_index("Centre Info", index as u32), height: fit!(), width: grow!(), @@ -1552,12 +2376,48 @@ pub fn ui_left_pane(ui: &mut Context, ..Decl }) { let label = match tx.kind() { - WalletTxKind::Send => if tx.mined_h.is_in_block() { "Sent" } else { "Sending" }, - WalletTxKind::Receive => if tx.mined_h.is_in_block() { "Received" } else { "Receiving" }, - WalletTxKind::SelfSend => if tx.mined_h.is_in_block() { "Returned" } else { "Returning" }, - WalletTxKind::Shield => if tx.mined_h.is_in_block() { "Shielded" } else { "Shielding" }, - WalletTxKind::Stake => if tx.mined_h.is_in_block() { "Staked" } else { "Staking" }, - WalletTxKind::Unstake => if tx.mined_h.is_in_block() { "Unstaked" } else { "Unstaking" }, + WalletTxKind::Send => { + if tx.mined_h.is_in_block() { + "Sent" + } else { + "Sending" + } + } + WalletTxKind::Receive => { + if tx.mined_h.is_in_block() { + "Received" + } else { + "Receiving" + } + } + WalletTxKind::SelfSend => { + if tx.mined_h.is_in_block() { + "Returned" + } else { + "Returning" + } + } + WalletTxKind::Shield => { + if tx.mined_h.is_in_block() { + "Shielded" + } else { + "Shielding" + } + } + WalletTxKind::Stake => { + if tx.mined_h.is_in_block() { + "Staked" + } else { + "Staking" + } + } + WalletTxKind::Unstake => { + if tx.mined_h.is_in_block() { + "Unstaked" + } else { + "Unstaking" + } + } }; let label_str = if tx.mined_h.is_in_block() { @@ -1566,19 +2426,54 @@ pub fn ui_left_pane(ui: &mut Context, frame_strf!(data, "{}", label) }; - ui.text(label_str, TextDecl { h: kind_text_h, align: AlignX::Left, ..TextDecl }); + ui.text( + label_str, + TextDecl { + h: kind_text_h, + align: AlignX::Left, + ..TextDecl + }, + ); // spacer - if let _ = elem().decl(Decl { width: grow!(), height: fixed!(ui.scale(4.0)), ..Default::default() }) {} + if let _ = elem().decl(Decl { + width: grow!(), + height: fixed!(ui.scale(4.0)), + ..Default::default() + }) {} let txid = tx.txid.to_string(); - ui.text(frame_strf!(data, "{}..{}", &txid[0..8], &txid[txid.len() - 8..]), TextDecl { font: Mono, h: transaction_text_h, colour: (0x90, 0x90, 0x90, 0xff) /* @todo colors */, align: AlignX::Left, ..TextDecl }); + ui.text( + frame_strf!( + data, + "{}..{}", + &txid[0..8], + &txid[txid.len() - 8..] + ), + TextDecl { + font: Mono, + h: transaction_text_h, + colour: (0x90, 0x90, 0x90, 0xff), /* @todo colors */ + align: AlignX::Left, + ..TextDecl + }, + ); // spacer - if let _ = elem().decl(Decl { width: grow!(), height: fixed!(ui.scale(4.0)), ..Default::default() }) {} - - if let Ok(memo_str) = String::from_utf8(tx.memo.as_slice().to_vec()) { - let mut memo_str = memo_str.chars().filter(|c| c.is_ascii()).collect::().trim_end_matches(|c| c == '\0').to_string(); + if let _ = elem().decl(Decl { + width: grow!(), + height: fixed!(ui.scale(4.0)), + ..Default::default() + }) {} + + if let Ok(memo_str) = String::from_utf8(tx.memo.as_slice().to_vec()) + { + let mut memo_str = memo_str + .chars() + .filter(|c| c.is_ascii()) + .collect::() + .trim_end_matches(|c| c == '\0') + .to_string(); if memo_str.len() != 0 { if memo_str.starts_with("@") { if let Some(end) = memo_str.find(":") { @@ -1586,13 +2481,21 @@ pub fn ui_left_pane(ui: &mut Context, } } - ui.text(frame_strf!(data, "{}", memo_str), TextDecl { h: transaction_text_h, align: AlignX::Left, colour: (0x90, 0x90, 0x90, 0xff) /* @todo colors */, ..TextDecl }); + ui.text( + frame_strf!(data, "{}", memo_str), + TextDecl { + h: transaction_text_h, + align: AlignX::Left, + colour: (0x90, 0x90, 0x90, 0xff), /* @todo colors */ + ..TextDecl + }, + ); } } } // right info - if let _ = elem().decl(Decl{ + if let _ = elem().decl(Decl { id: id_index("Right Info", index as u32), height: fit!(), width: fit!(), @@ -1602,39 +2505,110 @@ pub fn ui_left_pane(ui: &mut Context, }) { // @todo colors let color = match tx.kind() { - WalletTxKind::Send => (0xec, 0x27, 0x3f, 0xff), - WalletTxKind::Stake => (0xff, 0xaf, 0x0e, 0xff), - WalletTxKind::Receive | WalletTxKind::Unstake => (0x5a, 0xb5, 0x52, 0xff), - WalletTxKind::Shield => (0x33, 0x88, 0xde, 0xff), + WalletTxKind::Send => (0xec, 0x27, 0x3f, 0xff), + WalletTxKind::Stake => (0xff, 0xaf, 0x0e, 0xff), + WalletTxKind::Receive | WalletTxKind::Unstake => { + (0x5a, 0xb5, 0x52, 0xff) + } + WalletTxKind::Shield => (0x33, 0x88, 0xde, 0xff), _ => WHITE, }; let totals = tx.totals(); match tx.kind() { - WalletTxKind::Send | WalletTxKind::SelfSend | WalletTxKind::Stake => { + WalletTxKind::Send + | WalletTxKind::SelfSend + | WalletTxKind::Stake => { let send_amount: i64 = tx.account_value_delta().into(); let send_amount: u64 = send_amount.abs() as u64; - let prefix = if tx.kind() == WalletTxKind::Send { "-" } else { "" }; - ui.text(frame_strf!(data, "{}{} cTAZ", prefix, str_from_ctaz(send_amount)), TextDecl { h: transaction_text_h, align: AlignX::Right, colour: color, ..TextDecl }); - }, + let prefix = if tx.kind() == WalletTxKind::Send { + "-" + } else { + "" + }; + ui.text( + frame_strf!( + data, + "{}{} cTAZ", + prefix, + str_from_ctaz(send_amount) + ), + TextDecl { + h: transaction_text_h, + align: AlignX::Right, + colour: color, + ..TextDecl + }, + ); + } WalletTxKind::Receive | WalletTxKind::Unstake => { - if true { // total - ui.text(frame_strf!(data, "+{} cTAZ", str_from_ctaz(tx.totals().recv_zats.into_u64())), TextDecl { h: transaction_text_h, align: AlignX::Right, colour: color, ..TextDecl }); - } else { // transparent, shielded - let (t_z, s_z) = (tx.parts[0].recv_zats.into_u64(), tx.parts[1].recv_zats.into_u64()); - ui.text(frame_strf!(data, "+{} | {} cTAZ", str_from_ctaz(t_z), str_from_ctaz(s_z)), TextDecl { h: transaction_text_h, align: AlignX::Right, colour: color, ..TextDecl }); + if true { + // total + ui.text( + frame_strf!( + data, + "+{} cTAZ", + str_from_ctaz(tx.totals().recv_zats.into_u64()) + ), + TextDecl { + h: transaction_text_h, + align: AlignX::Right, + colour: color, + ..TextDecl + }, + ); + } else { + // transparent, shielded + let (t_z, s_z) = ( + tx.parts[0].recv_zats.into_u64(), + tx.parts[1].recv_zats.into_u64(), + ); + ui.text( + frame_strf!( + data, + "+{} | {} cTAZ", + str_from_ctaz(t_z), + str_from_ctaz(s_z) + ), + TextDecl { + h: transaction_text_h, + align: AlignX::Right, + colour: color, + ..TextDecl + }, + ); } - }, + } WalletTxKind::Shield => { // TODO: (how) do we want to show the fee? let shield_amount = totals.recv_zats.into_u64(); - ui.text(frame_strf!(data, "{} cTAZ", str_from_ctaz(shield_amount)), TextDecl { h: transaction_text_h, align: AlignX::Right, colour: color, ..TextDecl }); - }, + ui.text( + frame_strf!( + data, + "{} cTAZ", + str_from_ctaz(shield_amount) + ), + TextDecl { + h: transaction_text_h, + align: AlignX::Right, + colour: color, + ..TextDecl + }, + ); + } _ => { - ui.text("TODO cTAZ", TextDecl { h: transaction_text_h, align: AlignX::Right, colour: color, ..TextDecl }); - }, + ui.text( + "TODO cTAZ", + TextDecl { + h: transaction_text_h, + align: AlignX::Right, + colour: color, + ..TextDecl + }, + ); + } } } } @@ -1647,14 +2621,16 @@ pub fn ui_left_pane(ui: &mut Context, ui.nav_skip = false; } -pub fn ui_right_pane(ui: &mut Context, - wallet_state: Arc>, - viz: &mut VizState, - data: &mut UiData, - child_gap: f32, - padding: (f32, f32, f32, f32), - radius: (f32, f32, f32, f32), - tab_id: &mut Id) { +pub fn ui_right_pane( + ui: &mut Context, + wallet_state: Arc>, + viz: &mut VizState, + data: &mut UiData, + child_gap: f32, + padding: (f32, f32, f32, f32), + radius: (f32, f32, f32, f32), + tab_id: &mut Id, +) { // @TODO: MAKE THESE NOT USE TABS, JUST USE HEADERS let mut tab_id_faucet = Id::default(); let mut tab_id_roster = Id::default(); @@ -1669,11 +2645,18 @@ pub fn ui_right_pane(ui: &mut Context, align: Center, ..Decl }) { - tab_id_roster = ui.tab_ex((0.0, radius.1, radius.2, radius.3), padding, tab_id, id("Finalizers"), frame_strf!(data, "Finalizers ({})", roster.len())); + tab_id_roster = ui.tab_ex( + (0.0, radius.1, radius.2, radius.3), + padding, + tab_id, + id("Finalizers"), + frame_strf!(data, "Finalizers ({})", roster.len()), + ); } if let _ = elem().decl(Decl { id: id("Finalizers Contents"), - padding, child_gap, + padding, + child_gap, radius: (0.0, 0.0, 0.0, radius.3), colour: PANE_COL, direction: TopToBottom, @@ -1709,7 +2692,13 @@ pub fn ui_right_pane(ui: &mut Context, let mut button_ex = |ui: &mut Context, label, act_on_press, enabled: bool| { let id = id(label); - let (clicked, colour, text_colour) = ui.button_ex(act_on_press, BUTTON_GREY, id, enabled, winit::window::CursorIcon::Default); + let (clicked, colour, text_colour) = ui.button_ex( + act_on_press, + BUTTON_GREY, + id, + enabled, + winit::window::CursorIcon::Default, + ); if let _ = elem().decl(Decl { id, child_gap, @@ -1728,21 +2717,35 @@ pub fn ui_right_pane(ui: &mut Context, child_gap, radius: radius.dup4(), align: Center, - width: fit!(ui.scale(192.0)), + width: fit!(ui.scale(192.0)), height: fit!(radius * 2.0), ..Decl }) { let h = ui.scale(20.0); - ui.text(label, TextDecl { h, colour: text_colour, align: AlignX::Center, ..TextDecl }); + ui.text( + label, + TextDecl { + h, + colour: text_colour, + align: AlignX::Center, + ..TextDecl + }, + ); } } clicked }; - let mut clickable_icon = |ui: &mut Context, id, icon, enabled | { - let (clicked, colour, _) = ui.button_ex(false, (0xcc, 0xcc, 0xcc, 0xff) /* @todo colors */, id, enabled, winit::window::CursorIcon::Pointer); - if let _ = elem().decl(Decl{ + let mut clickable_icon = |ui: &mut Context, id, icon, enabled| { + let (clicked, colour, _) = ui.button_ex( + false, + (0xcc, 0xcc, 0xcc, 0xff), /* @todo colors */ + id, + enabled, + winit::window::CursorIcon::Pointer, + ); + if let _ = elem().decl(Decl { id, child_gap, align: Center, @@ -1751,7 +2754,16 @@ pub fn ui_right_pane(ui: &mut Context, height: fit!(), ..Decl }) { - ui.text(icon, TextDecl { font: Icons, colour, h: ui.scale(24.0), align: AlignX::Center, ..TextDecl }); + ui.text( + icon, + TextDecl { + font: Icons, + colour, + h: ui.scale(24.0), + align: AlignX::Center, + ..TextDecl + }, + ); } clicked @@ -1760,7 +2772,9 @@ pub fn ui_right_pane(ui: &mut Context, // @todo(judah): incorporate into list below? (i.e. always at the top of the list (and doesn't scroll with the rest), styled different from the others) if let _ = elem().decl(Decl { id: id("Finalizers Buttons"), - padding, child_gap, align: Center, + padding, + child_gap, + align: Center, direction: TopToBottom, width: percent!(1.0), height: fit!(), @@ -1771,8 +2785,30 @@ pub fn ui_right_pane(ui: &mut Context, recv_address.push_str(&format!("{:02x}", b)); } - ui.text("Your Finalizer Address", TextDecl { h: ui.scale(20.0), colour: WHITE, align: AlignX::Center, ..TextDecl }); - ui.text(frame_strf!(data, "[{}..{}]", &recv_address[0..8], &recv_address[recv_address.len()-8..]), TextDecl { font: Mono, h: ui.scale(20.0), colour: WHITE, align: AlignX::Center, ..TextDecl }); + ui.text( + "Your Finalizer Address", + TextDecl { + h: ui.scale(20.0), + colour: WHITE, + align: AlignX::Center, + ..TextDecl + }, + ); + ui.text( + frame_strf!( + data, + "[{}..{}]", + &recv_address[0..8], + &recv_address[recv_address.len() - 8..] + ), + TextDecl { + font: Mono, + h: ui.scale(20.0), + colour: WHITE, + align: AlignX::Center, + ..TextDecl + }, + ); if button_ex(ui, "Copy Address", true, true) { ui.input().send_to_clipboard(&recv_address); @@ -1785,13 +2821,14 @@ pub fn ui_right_pane(ui: &mut Context, let id = id("History Scroll Container"); if ui.hovered(id) { - ui.finalizers_scroll -= ui.input().zoom_delta as f32 * 32.0; + ui.finalizers_scroll -= ui.input().zoom_delta as f32 * 32.0; ui.finalizers_scroll -= ui.input().scroll_delta.1 as f32 * 32.0; } if ui.finalizers_scroll < 0.0 { ui.finalizers_scroll = 0.0; } - let scroll_container_data: clay::Clay_ScrollContainerData = unsafe { clay::Clay_GetScrollContainerData(id.clay().id) }; + let scroll_container_data: clay::Clay_ScrollContainerData = + unsafe { clay::Clay_GetScrollContainerData(id.clay().id) }; if scroll_container_data.found { let max = scroll_container_data.contentDimensions.height / ui.scale - 96.0; if ui.finalizers_scroll > max { @@ -1801,9 +2838,10 @@ pub fn ui_right_pane(ui: &mut Context, if let _ = elem().decl(Decl { id, colour: TRANSACTION_HISTORY_CONTAINER_COL, - child_gap: child_gap * 0.5, padding, + child_gap: child_gap * 0.5, + padding, radius: padding.0.dup4(), - width: percent!(1.0), + width: percent!(1.0), height: grow!(), // height: percent!(1.0), direction: TopToBottom, @@ -1815,22 +2853,39 @@ pub fn ui_right_pane(ui: &mut Context, let h = ui.scale(24.0); if let _ = elem().decl(Decl { direction: TopToBottom, - width: percent!(1.0), + width: percent!(1.0), height: percent!(1.0), child_gap, - align: Center, + align: Center, ..Decl }) { - ui.text(ICON_EYE_OFF, TextDecl { font: Icons, colour: WHITE.mul(0.6), h: ui.scale(64.0), align: AlignX::Center, ..TextDecl }); - ui.text("There are no finalizers yet.", TextDecl { colour: WHITE.mul(0.6), h, align: AlignX::Center, ..TextDecl }); + ui.text( + ICON_EYE_OFF, + TextDecl { + font: Icons, + colour: WHITE.mul(0.6), + h: ui.scale(64.0), + align: AlignX::Center, + ..TextDecl + }, + ); + ui.text( + "There are no finalizers yet.", + TextDecl { + colour: WHITE.mul(0.6), + h, + align: AlignX::Center, + ..TextDecl + }, + ); } - } - else { + } else { let kind_text_h = ui.scale(18.0); let transaction_text_h = ui.scale(16.0); for (index, member) in roster.iter().enumerate() { - if index > 0 { // separator + if index > 0 { + // separator let colour = { let mut col = TRANSACTION_HISTORY_CONTAINER_COL; col = col.hsva(); @@ -1838,9 +2893,14 @@ pub fn ui_right_pane(ui: &mut Context, col.rgba() }; - let _ = elem().decl(Decl { colour, height: fixed!(ui.scale(2.0)), width: percent!(1.0), ..Decl }); + let _ = elem().decl(Decl { + colour, + height: fixed!(ui.scale(2.0)), + width: percent!(1.0), + ..Decl + }); } - if let _ = elem().decl(Decl{ + if let _ = elem().decl(Decl { id: id_index("Roster Member", index as u32), padding, child_gap, @@ -1851,7 +2911,7 @@ pub fn ui_right_pane(ui: &mut Context, ..Decl }) { // left icon - if let _ = elem().decl(Decl{ + if let _ = elem().decl(Decl { id: id_index("Roster Member Left Icon", index as u32), height: fit!(), width: fixed!(ui.scale(32.0)), @@ -1859,7 +2919,12 @@ pub fn ui_right_pane(ui: &mut Context, align: Center, ..Decl }) { - if clickable_icon(ui, id_index("Copy Button", index as u32), ICON_DOCS_1, true) { + if clickable_icon( + ui, + id_index("Copy Button", index as u32), + ICON_DOCS_1, + true, + ) { let mut address_str = String::new(); for b in member.pub_key.iter().rev() { address_str.push_str(&format!("{:02x}", b)); @@ -1869,7 +2934,7 @@ pub fn ui_right_pane(ui: &mut Context, } // info - if let _ = elem().decl(Decl{ + if let _ = elem().decl(Decl { id: id_index("Roster Member Info", index as u32), height: fit!(), width: grow!(), @@ -1891,11 +2956,19 @@ pub fn ui_right_pane(ui: &mut Context, chunks }; - ui.text(frame_strf!(data, "{}", display_str(&chunks)), TextDecl { font: Mono, h: ui.scale(18.0), align: AlignX::Left, ..TextDecl }); + ui.text( + frame_strf!(data, "{}", display_str(&chunks)), + TextDecl { + font: Mono, + h: ui.scale(18.0), + align: AlignX::Left, + ..TextDecl + }, + ); } // right info - if let _ = elem().decl(Decl{ + if let _ = elem().decl(Decl { id: id_index("Roster Member Amounts", index as u32), height: fit!(), width: fit!(), @@ -1909,7 +2982,21 @@ pub fn ui_right_pane(ui: &mut Context, let part_str = format!("{part}00"); let trim_part = part_str.trim_end_matches("0"); let colour = (0xff, 0xaf, 0x0e, 0xff); - ui.text(frame_strf!(data, "{}.{} cTAZ", full, &part_str[..trim_part.len().max(3)]), TextDecl { font: Mono, h: ui.scale(16.0), colour, align: AlignX::Right, ..TextDecl }); + ui.text( + frame_strf!( + data, + "{}.{} cTAZ", + full, + &part_str[..trim_part.len().max(3)] + ), + TextDecl { + font: Mono, + h: ui.scale(16.0), + colour, + align: AlignX::Right, + ..TextDecl + }, + ); } } } @@ -1921,7 +3008,11 @@ pub fn ui_right_pane(ui: &mut Context, } // spacer - if let _ = elem().decl(Decl { width: grow!(), height: fixed!(ui.scale(16.0)), ..Default::default() }) {} + if let _ = elem().decl(Decl { + width: grow!(), + height: fixed!(ui.scale(16.0)), + ..Default::default() + }) {} if let _ = elem().decl(Decl { id: id("Faucet Tab Bar"), @@ -1931,11 +3022,22 @@ pub fn ui_right_pane(ui: &mut Context, align: Center, ..Decl }) { - tab_id_faucet = ui.tab_ex(radius, padding, tab_id, id("Faucet"), frame_strf!(data, "Faucet (Height {})", &wallet_state.lock().unwrap().miner_seen_h)); + tab_id_faucet = ui.tab_ex( + radius, + padding, + tab_id, + id("Faucet"), + frame_strf!( + data, + "Faucet (Height {})", + &wallet_state.lock().unwrap().miner_seen_h + ), + ); } if let _ = elem().decl(Decl { id: id("Faucet Contents"), - padding, child_gap, + padding, + child_gap, radius: (0.0, 0.0, 0.0, radius.3), colour: PANE_COL, direction: TopToBottom, @@ -1945,7 +3047,11 @@ pub fn ui_right_pane(ui: &mut Context, ..Decl }) { // spacer - if let _ = elem().decl(Decl { width: grow!(), height: fixed!(ui.scale(16.0)), ..Default::default() }) {} + if let _ = elem().decl(Decl { + width: grow!(), + height: fixed!(ui.scale(16.0)), + ..Default::default() + }) {} // big text container // if let _ = elem().decl(Decl { @@ -1964,8 +3070,11 @@ pub fn ui_right_pane(ui: &mut Context, // info container if let _ = elem().decl(Decl { - padding: ui.scale(32.0).dup4(), child_gap, align: TopLeft, - width: grow!(), height: fit!(), + padding: ui.scale(32.0).dup4(), + child_gap, + align: TopLeft, + width: grow!(), + height: fit!(), direction: TopToBottom, ..Decl }) { @@ -1980,25 +3089,66 @@ pub fn ui_right_pane(ui: &mut Context, ) }; - let row = Decl { width: percent!(1.0), child_gap, height: fit!(), ..Decl }; + let row = Decl { + width: percent!(1.0), + child_gap, + height: fit!(), + ..Decl + }; - let left = Decl { width: grow!(), height: fit!(), align: Left, ..Decl }; - let right = Decl { width: grow!(), height: fit!(), align: Right, ..Decl }; + let left = Decl { + width: grow!(), + height: fit!(), + align: Left, + ..Decl + }; + let right = Decl { + width: grow!(), + height: fit!(), + align: Right, + ..Decl + }; - let left_text = TextDecl { h: text_h, align: AlignX::Left, ..TextDecl }; - let right_text = TextDecl { font: Mono, align: AlignX::Right, ..left_text }; + let left_text = TextDecl { + h: text_h, + align: AlignX::Left, + ..TextDecl + }; + let right_text = TextDecl { + font: Mono, + align: AlignX::Right, + ..left_text + }; if let _ = elem().decl(row) { - if let _ = elem().decl(left) { ui.text("Unshielded:", left_text); } - if let _ = elem().decl(right) { ui.text(frame_strf!(data, "{} cTAZ", str_from_ctaz(un)), right_text); } + if let _ = elem().decl(left) { + ui.text("Unshielded:", left_text); + } + if let _ = elem().decl(right) { + ui.text(frame_strf!(data, "{} cTAZ", str_from_ctaz(un)), right_text); + } } if let _ = elem().decl(row) { - if let _ = elem().decl(left) { ui.text("Shielded (Pending):", left_text); } - if let _ = elem().decl(right) { ui.text(frame_strf!(data, "{} cTAZ", str_from_ctaz(sh_p)), right_text); } + if let _ = elem().decl(left) { + ui.text("Shielded (Pending):", left_text); + } + if let _ = elem().decl(right) { + ui.text( + frame_strf!(data, "{} cTAZ", str_from_ctaz(sh_p)), + right_text, + ); + } } if let _ = elem().decl(row) { - if let _ = elem().decl(left) { ui.text("Shielded (Spendable):", left_text); } - if let _ = elem().decl(right) { ui.text(frame_strf!(data, "{} cTAZ", str_from_ctaz(sh_s)), right_text); } + if let _ = elem().decl(left) { + ui.text("Shielded (Spendable):", left_text); + } + if let _ = elem().decl(right) { + ui.text( + frame_strf!(data, "{} cTAZ", str_from_ctaz(sh_s)), + right_text, + ); + } } // if let _ = elem().decl(Decl { width: grow!(), height: fixed!(ui.scale(32.0)), ..Default::default() }) {} }; @@ -2006,15 +3156,22 @@ pub fn ui_right_pane(ui: &mut Context, // buttons container if let _ = elem().decl(Decl { id: id("Buttons Container"), - padding, child_gap, align: Center, + padding, + child_gap, + align: Center, width: percent!(1.0), height: fit!(), ..Decl }) { - let mut button_ex = |label, act_on_press, enabled: bool| { let id = id(label); - let (clicked, colour, text_colour) = ui.button_ex(act_on_press, BUTTON_GREY, id, enabled, winit::window::CursorIcon::Default); + let (clicked, colour, text_colour) = ui.button_ex( + act_on_press, + BUTTON_GREY, + id, + enabled, + winit::window::CursorIcon::Default, + ); if let _ = elem().decl(Decl { id, child_gap, @@ -2033,27 +3190,44 @@ pub fn ui_right_pane(ui: &mut Context, child_gap, radius: radius.dup4(), align: Center, - width: fit!(ui.scale(192.0)), + width: fit!(ui.scale(192.0)), height: fit!(radius * 2.0), ..Decl }) { let h = ui.scale(20.0); - ui.text(label, TextDecl { h, colour: text_colour, align: AlignX::Center, ..TextDecl }); + ui.text( + label, + TextDecl { + h, + colour: text_colour, + align: AlignX::Center, + ..TextDecl + }, + ); } } clicked }; - if button_ex("Receive cTAZ", false, !wallet_state.lock().unwrap().waiting_for_faucet) { + if button_ex( + "Receive cTAZ", + false, + !wallet_state.lock().unwrap().waiting_for_faucet, + ) { wallet_state.lock().unwrap().request_from_faucet(); } } } } - -pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mut UiData, viz: &mut VizState, is_rendering: bool) -> bool { +pub fn run_ui( + ui: &mut Context, + wallet_state: Arc>, + data: &mut UiData, + viz: &mut VizState, + is_rendering: bool, +) -> bool { data.per_frame_strs.clear(); let mut result = false; @@ -2093,13 +3267,19 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu ui.nav_id_to_idx.clear(); ui.nav_idx_to_id.clear(); - let (window_w, window_h) = (ui.draw().window_width as f32, ui.draw().window_height as f32); - let mouse_pos = (ui.input().mouse_pos().0 as f32, ui.input().mouse_pos().1 as f32); + let (window_w, window_h) = ( + ui.draw().window_width as f32, + ui.draw().window_height as f32, + ); + let mouse_pos = ( + ui.input().mouse_pos().0 as f32, + ui.input().mouse_pos().1 as f32, + ); let child_gap = ui.scale(12.0); let padding = child_gap.dup4(); - let mouse_held = ui.input().mouse_held(winit::event::MouseButton::Left); + let mouse_held = ui.input().mouse_held(winit::event::MouseButton::Left); let mouse_clicked = ui.input().mouse_pressed(winit::event::MouseButton::Left); let radius = ui.scale(12.0).dup4(); @@ -2109,7 +3289,12 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu clay.set_layout_dimensions((window_w as f32, window_h as f32).into()); clay.pointer_state(mouse_pos.into(), mouse_held); clay.set_measure_text_function_user_data(ui.draw(), |string, text_config, draw| { - let font_kind = match text_config.font_id { 0 => FontKind::Normal, 1 => FontKind::Mono, 2 => FontKind::Icons, _ => todo!() }; + let font_kind = match text_config.font_id { + 0 => FontKind::Normal, + 1 => FontKind::Mono, + 2 => FontKind::Icons, + _ => todo!(), + }; let h = text_config.font_size as f32; let w = draw.measure_text_line(font_kind, h, string); clay::math::Dimensions::new(w, h) @@ -2117,18 +3302,20 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu let mut c = clay.begin::<(), ()>(); - unsafe { clay::Clay_SetCurrentContext(c.clay.context); } + unsafe { + clay::Clay_SetCurrentContext(c.clay.context); + } // c.set_debug_mode(true); if let _ = elem().decl(Decl { id: id("Main"), - padding: (0.0, 0.0, padding.2, padding.3), child_gap, + padding: (0.0, 0.0, padding.2, padding.3), + child_gap, width: grow!(), height: grow!(), ..Decl }) { - let pane_pct = Sizing::Percent(ui.zoom * PANE_PERCENT); if let _elem = elem().decl(Decl { @@ -2145,13 +3332,24 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu } let mut pane_tab_l = ui.pane_tab_l; - ui_left_pane(ui, wallet_state.clone(), data, viz, child_gap, padding, radius, &mut pane_tab_l); + ui_left_pane( + ui, + wallet_state.clone(), + data, + viz, + child_gap, + padding, + radius, + &mut pane_tab_l, + ); ui.pane_tab_l = pane_tab_l; } if let _elem = elem().decl(Decl { id: id("Central Gap"), - radius, padding, child_gap, + radius, + padding, + child_gap, direction: TopToBottom, width: grow!(), height: grow!(), @@ -2176,25 +3374,82 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu ..Decl }) { // @todo: two vertical panes with right aligned label and left aligned height - ui.text(frame_strf!(data, "PoS Height: {}", viz.bft_tip_height), TextDecl { h: ui.scale(16.0), align: AlignX::Center, ..TextDecl }); - ui.text(frame_strf!(data, "PoW Height: {}", viz.bc_tip_height), TextDecl { h: ui.scale(16.0), align: AlignX::Center, ..TextDecl }); - ui.text(frame_strf!(data, "Orchard Pool Zatoshis: {}", viz.orchard_pool_balance), TextDecl { h: ui.scale(16.0), align: AlignX::Center, ..TextDecl }); - ui.text(frame_strf!(data, "Staking (Bonded) Pool Zatoshis: {}", viz.staking_bonded_pool_balance), TextDecl { h: ui.scale(16.0), align: AlignX::Center, ..TextDecl }); - ui.text(frame_strf!(data, "Staking (Unbonded) Pool Zatoshis: {}", viz.staking_unbonded_pool_balance), TextDecl { h: ui.scale(16.0), align: AlignX::Center, ..TextDecl }); + ui.text( + frame_strf!(data, "PoS Height: {}", viz.bft_tip_height), + TextDecl { + h: ui.scale(16.0), + align: AlignX::Center, + ..TextDecl + }, + ); + ui.text( + frame_strf!(data, "PoW Height: {}", viz.bc_tip_height), + TextDecl { + h: ui.scale(16.0), + align: AlignX::Center, + ..TextDecl + }, + ); + ui.text( + frame_strf!(data, "Orchard Pool Zatoshis: {}", viz.orchard_pool_balance), + TextDecl { + h: ui.scale(16.0), + align: AlignX::Center, + ..TextDecl + }, + ); + ui.text( + frame_strf!( + data, + "Staking (Bonded) Pool Zatoshis: {}", + viz.staking_bonded_pool_balance + ), + TextDecl { + h: ui.scale(16.0), + align: AlignX::Center, + ..TextDecl + }, + ); + ui.text( + frame_strf!( + data, + "Staking (Unbonded) Pool Zatoshis: {}", + viz.staking_unbonded_pool_balance + ), + TextDecl { + h: ui.scale(16.0), + align: AlignX::Center, + ..TextDecl + }, + ); } } // spacer - if let _ = elem().decl(Decl { height: grow!(), ..Decl }) {} + if let _ = elem().decl(Decl { + height: grow!(), + ..Decl + }) {} // "Reset View" button - if let _ = elem().decl(Decl { align: Bottom, width: grow!(), ..Decl }) { + if let _ = elem().decl(Decl { + align: Bottom, + width: grow!(), + ..Decl + }) { let label = "Reset View"; - let enabled = viz.camera_x != 0.0 || viz.camera_y != viz.bc_tip_y || viz.zoom != 0.0; + let enabled = + viz.camera_x != 0.0 || viz.camera_y != viz.bc_tip_y || viz.zoom != 0.0; let id = id(label); - let (clicked, colour, text_colour) = ui.button_ex(true, BUTTON_GREY, id, enabled, winit::window::CursorIcon::Default); + let (clicked, colour, text_colour) = ui.button_ex( + true, + BUTTON_GREY, + id, + enabled, + winit::window::CursorIcon::Default, + ); let radius = ui.scale(20.0); if ui.hovered(id) { @@ -2209,12 +3464,20 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu child_gap, radius: radius.dup4(), align: Center, - width: fit!(ui.scale(128.0)), + width: fit!(ui.scale(128.0)), height: fit!(radius * 2.0), ..Decl }) { let button_text_h = ui.scale(16.0); - ui.text(label, TextDecl { h: button_text_h, colour: text_colour, align: AlignX::Center, ..TextDecl }); + ui.text( + label, + TextDecl { + h: button_text_h, + colour: text_colour, + align: AlignX::Center, + ..TextDecl + }, + ); } if clicked { @@ -2225,12 +3488,33 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu } } + // Lots of finalizers in the list pushes the faucet section outside the screen + // This seems to fix it + let right_id = id("Right Pane"); + + if ui.hovered(right_id) { + ui.finalizers_scroll -= ui.input().zoom_delta as f32 * 32.0; + ui.finalizers_scroll -= ui.input().scroll_delta.1 as f32 * 32.0; + } + if ui.finalizers_scroll < 0.0 { + ui.finalizers_scroll = 0.0; + } + + let scroll_data: clay::Clay_ScrollContainerData = + unsafe { clay::Clay_GetScrollContainerData(right_id.clay().id) }; + if scroll_data.found { + let max = scroll_data.contentDimensions.height / ui.scale - 96.0; + if ui.finalizers_scroll > max { + ui.finalizers_scroll = max; + } + } + if let _elem = elem().decl(Decl { - id: id("Right Pane"), + id: right_id, direction: TopToBottom, width: pane_pct, height: grow!(), - clip: Clip, + clip: Scroll(0.0, -ui.finalizers_scroll * ui.scale), ..Decl }) { let id = _elem.decl.id; @@ -2239,7 +3523,16 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu } let mut pane_tab_r = ui.pane_tab_r; - ui_right_pane(ui, wallet_state.clone(), viz, data, child_gap, padding, radius, &mut pane_tab_r); + ui_right_pane( + ui, + wallet_state.clone(), + viz, + data, + child_gap, + padding, + radius, + &mut pane_tab_r, + ); ui.pane_tab_r = pane_tab_r; } } @@ -2250,41 +3543,71 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu if ui.hovered(id) { ui.capture = true; } - let border_colour = { let mut col = PANE_COL.hsva(); col.2 = 0x18; col.rgba() }; + let border_colour = { + let mut col = PANE_COL.hsva(); + col.2 = 0x18; + col.rgba() + }; if let _ = elem().decl(Decl { id, colour: border_colour, - child_gap, padding, radius, - width: fit!(), + child_gap, + padding, + radius, + width: fit!(), height: fit!(), floating: Floating::Root(ctx_menu_pos.0 as f32, ctx_menu_pos.1 as f32), ..Decl }) { if let _ = elem().decl(Decl { colour: PANE_COL, - child_gap, padding, radius, - width: fit!(ui.scale(192.0), ui.draw().window_width as f32 * 0.5), + child_gap, + padding, + radius, + width: fit!(ui.scale(192.0), ui.draw().window_width as f32 * 0.5), height: fit!(ui.scale(128.0)), direction: TopToBottom, ..Decl }) { let text_h = ui.scale(10.0); // Block Inspector Contents - ui.text(frame_strf!(data, "Block: {}", viz.inspecting_block_hash), TextDecl { font: Mono, wrap_chars: true, h: text_h, align: AlignX::Left, ..TextDecl }); + ui.text( + frame_strf!(data, "Block: {}", viz.inspecting_block_hash), + TextDecl { + font: Mono, + wrap_chars: true, + h: text_h, + align: AlignX::Left, + ..TextDecl + }, + ); let text = { if let Some(text) = viz.inspect_block_json_text.as_ref() { text.to_string() } else { - frame_strf!(data, "Loading info for block {}...", viz.inspecting_block_hash).to_string() + frame_strf!( + data, + "Loading info for block {}...", + viz.inspecting_block_hash + ) + .to_string() } }; - ui.text(frame_strf!(data, "{}", text), TextDecl { font: Mono, wrap_chars: true, h: text_h, align: AlignX::Left, ..TextDecl }); + ui.text( + frame_strf!(data, "{}", text), + TextDecl { + font: Mono, + wrap_chars: true, + h: text_h, + align: AlignX::Left, + ..TextDecl + }, + ); } } } - if !ui.input().mouse_held(winit::event::MouseButton::Left) { ui.mouse_pressed_id = Id::default(); } @@ -2312,7 +3635,8 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu if ui.input().key_pressed(KeyCode::Tab) && ui.nav_idx_to_id.len() > 0 { let idx = if ui.nav_id != 0 { let old_idx = ui.nav_id_to_idx[&ui.nav_id] as isize; - if (ui.input().key_held(KeyCode::ShiftLeft) || ui.input().key_held(KeyCode::ShiftRight)) { + if (ui.input().key_held(KeyCode::ShiftLeft) || ui.input().key_held(KeyCode::ShiftRight)) + { (old_idx - 1).rem_euclid(ui.nav_idx_to_id.len() as isize) as usize } else { (old_idx + 1).rem_euclid(ui.nav_idx_to_id.len() as isize) as usize @@ -2345,9 +3669,9 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu color } - let x1 = (command.bounding_box.x) as isize; - let y1 = (command.bounding_box.y) as isize; - let x2 = (command.bounding_box.x + command.bounding_box.width) as isize; + let x1 = (command.bounding_box.x) as isize; + let y1 = (command.bounding_box.y) as isize; + let x2 = (command.bounding_box.x + command.bounding_box.width) as isize; let y2 = (command.bounding_box.y + command.bounding_box.height) as isize; let bbox = (x1, y1, x2, y2); @@ -2357,9 +3681,9 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu } let colour = match command.config { - Rectangle(ref config) => { clay_color_to_u32(config.color) } - RenderCommandConfig::Text(ref config) => { clay_color_to_u32(config.color) } - _ => { 0 } + Rectangle(ref config) => clay_color_to_u32(config.color), + RenderCommandConfig::Text(ref config) => clay_color_to_u32(config.color), + _ => 0, }; // @Hack for generating nav highlight rectangle via draw commands. @@ -2369,24 +3693,33 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu match command.config { Rectangle(config) => { - let radius_tl = config.corner_radii.top_left as isize; - let radius_tr = config.corner_radii.top_right as isize; - let radius_bl = config.corner_radii.bottom_left as isize; + let radius_tl = config.corner_radii.top_left as isize; + let radius_tr = config.corner_radii.top_right as isize; + let radius_bl = config.corner_radii.bottom_left as isize; let radius_br = config.corner_radii.bottom_right as isize; - ui.draw().rounded_rectangle(x1, y1, x2, y2, - radius_tl, - radius_tr, - radius_bl, - radius_br, - colour); + ui.draw().rounded_rectangle( + x1, y1, x2, y2, radius_tl, radius_tr, radius_bl, radius_br, colour, + ); } RenderCommandConfig::Text(config) => { - let font_kind = match config.font_id { 0 => FontKind::Normal, 1 => FontKind::Mono, 2 => FontKind::Icons, _ => todo!() }; + let font_kind = match config.font_id { + 0 => FontKind::Normal, + 1 => FontKind::Mono, + 2 => FontKind::Icons, + _ => todo!(), + }; // let text = frame_strf!(data, "{} ", command.id); // ui.draw().text_line(font_kind, x1 as f32, y1 as f32, config.font_size as f32, text, colour); - ui.draw().text_line(font_kind, x1 as f32, y1 as f32, config.font_size as f32, config.text, colour); + ui.draw().text_line( + font_kind, + x1 as f32, + y1 as f32, + config.font_size as f32, + config.text, + colour, + ); } ScissorStart() => { ui.draw().set_scissor(x1, y1, x2, y2); @@ -2394,10 +3727,14 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu ScissorEnd() => { ui.draw().clear_scissor(); } - misc => { todo!("Unsupported clay render command: {:?}", misc) } + misc => { + todo!("Unsupported clay render command: {:?}", misc) + } } - if let Some(textbox_state) = data.textboxes.get_mut(&command.id) && ui.nav_id == command.id { + if let Some(textbox_state) = data.textboxes.get_mut(&command.id) + && ui.nav_id == command.id + { textbox_state.bbox = bbox; let h = textbox_state.h; @@ -2408,7 +3745,9 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu let n = idx.min(textbox_state.text_buf.len()); let mut str = String::with_capacity(n * 4); str.extend(textbox_state.text_buf[..n].iter().copied()); - let xoff = ui.draw().measure_text_line(FontKind::Normal, textbox_state.h, &str); + let xoff = ui + .draw() + .measure_text_line(FontKind::Normal, textbox_state.h, &str); x1 + padding as isize + xoff as isize }; @@ -2420,36 +3759,69 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu let y2 = y_centre + (h * 0.5).ceil() as isize; if textbox_state.selection.1 == textbox_state.selection.0 { - let x1 = head_x; + let x1 = head_x; let color = 0xc0ffffff; - let t = (ui.scale * 1.0).ceil() as isize; - - ui.draw().rectangle((x1-t) as f32, (y1-t) as f32, (x1+t) as f32, (y2+t) as f32, color); + let t = (ui.scale * 1.0).ceil() as isize; + + ui.draw().rectangle( + (x1 - t) as f32, + (y1 - t) as f32, + (x1 + t) as f32, + (y2 + t) as f32, + color, + ); } else { - let (min, max) = (head_x.min(tail_x), - head_x.max(tail_x)); + let (min, max) = (head_x.min(tail_x), head_x.max(tail_x)); - let x1 = min; - let x2 = max; + let x1 = min; + let x2 = max; let color = 0xc00000ff; - ui.draw().rectangle(x1 as f32, y1 as f32, x2 as f32, y2 as f32, color); + ui.draw() + .rectangle(x1 as f32, y1 as f32, x2 as f32, y2 as f32, color); } } if ui.debug { let thickness = 2.0; let color = 0x80ff00ff; - let t = (thickness / 2.0) as isize; - - ui.draw().rectangle((x1-t) as f32, (y1-t) as f32, (x1+t) as f32, (y2+t) as f32, color); - ui.draw().rectangle((x2-t) as f32, (y1-t) as f32, (x2+t) as f32, (y2+t) as f32, color); - ui.draw().rectangle((x1-t) as f32, (y1-t) as f32, (x2-t) as f32, (y1+t) as f32, color); - ui.draw().rectangle((x1-t) as f32, (y2-t) as f32, (x2-t) as f32, (y2+t) as f32, color); + let t = (thickness / 2.0) as isize; + + ui.draw().rectangle( + (x1 - t) as f32, + (y1 - t) as f32, + (x1 + t) as f32, + (y2 + t) as f32, + color, + ); + ui.draw().rectangle( + (x2 - t) as f32, + (y1 - t) as f32, + (x2 + t) as f32, + (y2 + t) as f32, + color, + ); + ui.draw().rectangle( + (x1 - t) as f32, + (y1 - t) as f32, + (x2 - t) as f32, + (y1 + t) as f32, + color, + ); + ui.draw().rectangle( + (x1 - t) as f32, + (y2 - t) as f32, + (x2 - t) as f32, + (y2 + t) as f32, + color, + ); } } - if ui.nav_enable && let Some((x1, y1, x2, y2)) = nav_bbox && !data.textboxes.contains_key(&ui.nav_id) { + if ui.nav_enable + && let Some((x1, y1, x2, y2)) = nav_bbox + && !data.textboxes.contains_key(&ui.nav_id) + { let gap = ui.scale(1.0) as isize; let x1 = x1 - gap; @@ -2463,18 +3835,66 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu { let color = 0xffffffff; let t = white_t; - ui.draw().rectangle((x1-t) as f32, (y1-t) as f32, (x1+0) as f32, (y2+t) as f32, color); - ui.draw().rectangle((x2-0) as f32, (y1-t) as f32, (x2+t) as f32, (y2+t) as f32, color); - ui.draw().rectangle((x1-t) as f32, (y1-t) as f32, (x2-0) as f32, (y1+0) as f32, color); - ui.draw().rectangle((x1-t) as f32, (y2-0) as f32, (x2-0) as f32, (y2+t) as f32, color); + ui.draw().rectangle( + (x1 - t) as f32, + (y1 - t) as f32, + (x1 + 0) as f32, + (y2 + t) as f32, + color, + ); + ui.draw().rectangle( + (x2 - 0) as f32, + (y1 - t) as f32, + (x2 + t) as f32, + (y2 + t) as f32, + color, + ); + ui.draw().rectangle( + (x1 - t) as f32, + (y1 - t) as f32, + (x2 - 0) as f32, + (y1 + 0) as f32, + color, + ); + ui.draw().rectangle( + (x1 - t) as f32, + (y2 - 0) as f32, + (x2 - 0) as f32, + (y2 + t) as f32, + color, + ); } { let color = 0xff000000; let t = black_t; - ui.draw().rectangle((x1-t) as f32, (y1-t) as f32, (x1+0) as f32, (y2+t) as f32, color); - ui.draw().rectangle((x2-0) as f32, (y1-t) as f32, (x2+t) as f32, (y2+t) as f32, color); - ui.draw().rectangle((x1-t) as f32, (y1-t) as f32, (x2-0) as f32, (y1+0) as f32, color); - ui.draw().rectangle((x1-t) as f32, (y2-0) as f32, (x2-0) as f32, (y2+t) as f32, color); + ui.draw().rectangle( + (x1 - t) as f32, + (y1 - t) as f32, + (x1 + 0) as f32, + (y2 + t) as f32, + color, + ); + ui.draw().rectangle( + (x2 - 0) as f32, + (y1 - t) as f32, + (x2 + t) as f32, + (y2 + t) as f32, + color, + ); + ui.draw().rectangle( + (x1 - t) as f32, + (y1 - t) as f32, + (x2 - 0) as f32, + (y1 + 0) as f32, + color, + ); + ui.draw().rectangle( + (x1 - t) as f32, + (y2 - 0) as f32, + (x2 - 0) as f32, + (y2 + t) as f32, + color, + ); } } } @@ -2484,7 +3904,12 @@ pub fn run_ui(ui: &mut Context, wallet_state: Arc>, data: &mu result } -pub fn ui_update(ui: &mut Context, data: &mut UiData, viz: &mut VizState, wallet_state: Arc>) -> bool { +pub fn ui_update( + ui: &mut Context, + data: &mut UiData, + viz: &mut VizState, + wallet_state: Arc>, +) -> bool { ui.tx_loading_animation_timer += ui.delta; if ui.tx_loading_animation_timer >= 1.0 { ui.tx_loading_animation_timer = 0.0; @@ -2500,17 +3925,19 @@ pub fn ui_update(ui: &mut Context, data: &mut UiData, viz: &mut VizState, wallet ..Default::default() }; - let real_input = ui.input; let result = run_ui(ui, wallet_state.clone(), data, viz, false); - ui.input = &dummy_input; let result = result || run_ui(ui, wallet_state.clone(), data, viz, true); - ui.input = real_input; + let real_input = ui.input; + let result = run_ui(ui, wallet_state.clone(), data, viz, false); + ui.input = &dummy_input; + let result = result || run_ui(ui, wallet_state.clone(), data, viz, true); + ui.input = real_input; return result; } #[derive(Debug, Clone, PartialEq, Default)] pub struct Context { pub input: *const InputCtx, - pub draw: *const DrawCtx, - pub clay: *mut Clay, + pub draw: *const DrawCtx, + pub clay: *mut Clay, pub cursor: winit::window::Cursor, pub prev_cursor: winit::window::Cursor, @@ -2520,17 +3947,16 @@ pub struct Context { pub draw_commands: Vec, - pub scale: f32, - pub zoom: f32, + pub scale: f32, + pub zoom: f32, pub dpi_scale: f32, - pub delta: f32, + pub delta: f32, pub capture: bool, - pub mouse_pressed_id: Id, - pub key_pressed_id: Id, + pub mouse_pressed_id: Id, + pub key_pressed_id: Id, // pub most_recent_mouse_pressed_id: Id, - pub nav_id_to_idx: HashMap, pub nav_idx_to_id: Vec, pub nav_id: u32, @@ -2542,9 +3968,9 @@ pub struct Context { pub modal: Modal, - pub history_scroll: f32, + pub history_scroll: f32, pub finalizers_scroll: f32, - pub unstake_scroll: f32, + pub unstake_scroll: f32, pub tx_loading_animation_timer: f32, }