diff --git a/applications/tari_indexer/src/config.rs b/applications/tari_indexer/src/config.rs index c2e3bac554..15b590e2cd 100644 --- a/applications/tari_indexer/src/config.rs +++ b/applications/tari_indexer/src/config.rs @@ -80,6 +80,10 @@ impl ApplicationConfig { pub fn state_db_path(&self) -> PathBuf { self.to_data_dir().join("state.db") } + + pub fn global_db_path(&self) -> PathBuf { + self.to_data_dir().join("global_storage.sqlite") + } } #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/applications/tari_indexer/src/lib.rs b/applications/tari_indexer/src/lib.rs index 354a99f8c7..cfe4b65700 100644 --- a/applications/tari_indexer/src/lib.rs +++ b/applications/tari_indexer/src/lib.rs @@ -79,7 +79,7 @@ pub async fn run_indexer(config: ApplicationConfig, mut shutdown_signal: Shutdow info!(target: LOG_TARGET, "Starting indexer node on network {}", config.network); let keypair = setup_keypair_prompt(config.to_identity_file_path(), true)?; - let db_factory = SqliteDbFactory::new(config.indexer.data_dir.clone()); + let db_factory = SqliteDbFactory::new(config.global_db_path()); db_factory .migrate() .map_err(|e| ExitError::new(ExitCode::DatabaseError, e))?; diff --git a/applications/tari_indexer/src/network_state_sync/worker.rs b/applications/tari_indexer/src/network_state_sync/worker.rs index 121301932c..a1b01e778e 100644 --- a/applications/tari_indexer/src/network_state_sync/worker.rs +++ b/applications/tari_indexer/src/network_state_sync/worker.rs @@ -412,7 +412,7 @@ impl NetworkWideStateSync { // TODO: this is not currently used. Consider removing. tx.batch_insert_substate_transitions(shard, state_version, update_buf.drain(..))?; debug!(target: LOG_TARGET, "✅ Committing {} UTXOs for shard {shard} (epoch: {msg_epoch})", utxos_buf.len()); - tx.batch_insert_utxo_updates(utxos_buf.drain(..))?; + tx.batch_insert_utxo_updates(msg_epoch, utxos_buf.drain(..))?; // TODO: there are many ways to do this. This is probably not the best way. But this allows wallet to query for validator fee pool values since // block sync does not sync validator fee pools (due to block diffs being removed on block commit). for substate_data in validator_fee_pools_buf.drain(..) { diff --git a/applications/tari_indexer/src/rest_api/handlers/substates.rs b/applications/tari_indexer/src/rest_api/handlers/substates.rs index c743193211..a6b6819d74 100644 --- a/applications/tari_indexer/src/rest_api/handlers/substates.rs +++ b/applications/tari_indexer/src/rest_api/handlers/substates.rs @@ -67,6 +67,16 @@ pub async fn get_substate( Path(substate_id): Path, Query(req): Query, ) -> HandlerResult> { + if !context + .epoch_manager() + .is_initial_scanning_complete() + .await + .map_err(ErrorResponse::anyhow)? + { + return Err(ErrorResponse::service_unavailable( + "Indexer is still syncing. Please try again later.", + )); + } let maybe_substate = context .substate_manager() .get_substate(&substate_id, req.version) diff --git a/applications/tari_indexer/src/rest_api/handlers/transactions.rs b/applications/tari_indexer/src/rest_api/handlers/transactions.rs index cd2f46eafc..6fb28c7798 100644 --- a/applications/tari_indexer/src/rest_api/handlers/transactions.rs +++ b/applications/tari_indexer/src/rest_api/handlers/transactions.rs @@ -68,13 +68,17 @@ pub async fn submit_transaction( .submit_transaction(transaction) .await .map_err(|e| match e { - TransactionManagerError::NetworkClientError(NetworkClientError::AllValidatorsFailed { .. }) => { + TransactionManagerError::NetworkClientError(NetworkClientError::AllValidatorsFailed { .. }) | + TransactionManagerError::NetworkClientError(NetworkClientError::NoCommitteeMembers) => { ErrorResponse::service_unavailable(format!("All validators failed: {}", e)) }, TransactionManagerError::InvalidTransaction { transaction_id, details, } => ErrorResponse::bad_request(format!("Transaction {} is invalid: {}", transaction_id, details)), + TransactionManagerError::NetworkClientError(NetworkClientError::NoInputsProvided) => { + ErrorResponse::bad_request("Transaction has no inputs".to_string()) + }, e => ErrorResponse::anyhow(e), })?; diff --git a/applications/tari_indexer/src/rest_api/streaming/utxo_stream.rs b/applications/tari_indexer/src/rest_api/streaming/utxo_stream.rs index c76c0668af..e8b00f2efc 100644 --- a/applications/tari_indexer/src/rest_api/streaming/utxo_stream.rs +++ b/applications/tari_indexer/src/rest_api/streaming/utxo_stream.rs @@ -12,7 +12,7 @@ use futures::Stream; use log::*; use tari_indexer_client::{protobuf, types::GetUtxoUpdatesRequest}; use tari_ootle_common_types::{shard::Shard, StateVersion}; -use tari_ootle_wallet_sdk::models::WalletUtxoUpdate; +use tari_ootle_wallet_sdk::models::{UtxoStateUpdateSet, WalletUtxoUpdate}; use crate::{ rest_api::{encoder::Encoder, error::ErrorResponse, streaming::encoding::MimeTypeEncoder}, @@ -98,8 +98,13 @@ impl UtxoUpdateStream { } pub fn next_batch(&mut self, shard: Shard, state_version: StateVersion) -> anyhow::Result { - let (updates_state_version, updates) = self.substate_manager.get_utxo_updates( + let UtxoStateUpdateSet { + updates, + max_state_version, + max_epoch, + } = self.substate_manager.get_utxo_updates( self.request.resource_address, + self.request.from_epoch, shard, state_version, self.request.unspent_only, @@ -113,16 +118,13 @@ impl UtxoUpdateStream { } debug!( target: LOG_TARGET, - "Received {} updates for shard {}, max_state_version {} -> {}", + "Received {} updates for shard {shard}, max_epoch = {max_epoch}, max_state_version {max_state_version} -> {high_watermark_state_version}", updates.len(), - shard, - updates_state_version, - high_watermark_state_version ); self.pending_updates = Some(PendingUpdates { sos_emitted: false, shard, - updates_state_version, + updates_state_version: max_state_version, high_watermark_state_version, updates, index: 0, diff --git a/applications/tari_indexer/src/storage_sqlite/migrations/2023-02-16-145719_initial/up.sql b/applications/tari_indexer/src/storage_sqlite/migrations/2023-02-16-145719_initial/up.sql index 731837ec52..36b463a620 100644 --- a/applications/tari_indexer/src/storage_sqlite/migrations/2023-02-16-145719_initial/up.sql +++ b/applications/tari_indexer/src/storage_sqlite/migrations/2023-02-16-145719_initial/up.sql @@ -136,12 +136,13 @@ create table utxos state_version bigint not NULL, output blob NULL, utxo_tag int not NULL, + epoch bigint not NULL, is_spent boolean not NULL, is_burnt boolean not NULL, is_frozen boolean not NULL, created_at timestamp not null default current_timestamp ); -CREATE INDEX utxos_resource_state_version_shard_idx ON utxos (resource_address, state_version, shard); +CREATE INDEX utxos_resource_state_version_shard_epoch_idx ON utxos (resource_address, state_version, shard, epoch); CREATE UNIQUE INDEX utxos_resource_public_nonce_utxo_tag_uniq_partial ON utxos (resource_address, public_nonce, utxo_tag) WHERE is_spent = false; diff --git a/applications/tari_indexer/src/storage_sqlite/models/utxo.rs b/applications/tari_indexer/src/storage_sqlite/models/utxo.rs index 10e9ccecf3..bdf3f8b316 100644 --- a/applications/tari_indexer/src/storage_sqlite/models/utxo.rs +++ b/applications/tari_indexer/src/storage_sqlite/models/utxo.rs @@ -17,6 +17,7 @@ use crate::storage_sqlite::{schema::utxos, serialization::deserialize_bincode}; #[derive(AsChangeset, Default)] #[diesel(table_name = utxos)] pub(crate) struct UtxoRecordUpdate { + pub epoch: Option, pub version: Option, pub output: Option>>, pub state_version: Option, @@ -36,6 +37,7 @@ pub(crate) struct UtxoRecordInsert { pub state_version: i64, pub output: Option>, pub utxo_tag: i32, + pub epoch: i64, pub is_spent: bool, pub is_burnt: bool, pub is_frozen: bool, @@ -52,6 +54,7 @@ pub(crate) struct UtxoRecord { pub state_version: i64, pub output: Option>, pub _utxo_tag: i32, + pub epoch: i64, pub _is_spent: bool, pub is_burnt: bool, pub is_frozen: bool, diff --git a/applications/tari_indexer/src/storage_sqlite/reader.rs b/applications/tari_indexer/src/storage_sqlite/reader.rs index 71952a8e01..9336b88708 100644 --- a/applications/tari_indexer/src/storage_sqlite/reader.rs +++ b/applications/tari_indexer/src/storage_sqlite/reader.rs @@ -35,7 +35,7 @@ use tari_ootle_common_types::{ }; use tari_ootle_storage::{time::PrimitiveDateTime, Ordering, StorageError}; use tari_ootle_storage_sqlite::SqliteTransaction; -use tari_ootle_wallet_sdk::models::WalletUtxoUpdate; +use tari_ootle_wallet_sdk::models::UtxoStateUpdateSet; use tari_template_lib::{ models::{ResourceAddress, UtxoId}, prelude::{RistrettoPublicKeyBytes, TemplateAddress}, @@ -519,17 +519,19 @@ impl IndexerStoreReadTransaction for SqliteStoreReadTransaction<'_> { fn utxos_get_updates( &mut self, resource_address: ResourceAddress, + from_epoch: Epoch, shard: Shard, from_state_version: StateVersion, unspent_only: bool, limit: u32, - ) -> Result<(StateVersion, Vec), StorageError> { + ) -> Result { const OPERATION: &str = "get_utxo_updates"; use crate::storage_sqlite::schema::utxos; let mut query = utxos::table .filter(utxos::resource_address.eq(resource_address.to_string())) .filter(utxos::state_version.gt(from_state_version.as_u64() as i64)) + .filter(utxos::epoch.ge(from_epoch.as_u64() as i64)) .filter(utxos::shard.eq(shard.as_u32() as i32)) .limit(i64::from(limit)) .order_by(utxos::state_version.asc()) @@ -548,16 +550,23 @@ impl IndexerStoreReadTransaction for SqliteStoreReadTransaction<'_> { let mut updates = Vec::new(); let mut max_state_version = StateVersion::zero(); + let mut max_epoch = Epoch::zero(); for row in rows { let row = row.map_err(|e| StorageError::QueryError { reason: format!("{OPERATION}: {}", e), })?; + let epoch = Epoch(row.epoch as u64); let (state_version, update) = row.try_convert_to_update()?; max_state_version = max_state_version.max(state_version); + max_epoch = max_epoch.max(epoch); updates.push(update); } - Ok((max_state_version, updates)) + Ok(UtxoStateUpdateSet { + updates, + max_state_version, + max_epoch, + }) } fn utxos_list( diff --git a/applications/tari_indexer/src/storage_sqlite/schema.rs b/applications/tari_indexer/src/storage_sqlite/schema.rs index b628c21c38..5ca7a48d83 100644 --- a/applications/tari_indexer/src/storage_sqlite/schema.rs +++ b/applications/tari_indexer/src/storage_sqlite/schema.rs @@ -108,6 +108,7 @@ diesel::table! { state_version -> BigInt, output -> Nullable, utxo_tag -> Integer, + epoch -> BigInt, is_spent -> Bool, is_burnt -> Bool, is_frozen -> Bool, diff --git a/applications/tari_indexer/src/storage_sqlite/writer.rs b/applications/tari_indexer/src/storage_sqlite/writer.rs index a57ca68adc..078ea46e79 100644 --- a/applications/tari_indexer/src/storage_sqlite/writer.rs +++ b/applications/tari_indexer/src/storage_sqlite/writer.rs @@ -116,6 +116,7 @@ impl IndexerStoreWriteTransaction for SqliteStoreWriteTransaction<'_> { fn batch_insert_utxo_updates>( &mut self, + epoch: Epoch, updates: I, ) -> Result<(), StorageError> { const OPERATION: &str = "batch_insert_utxo_updates"; @@ -136,6 +137,7 @@ impl IndexerStoreWriteTransaction for SqliteStoreWriteTransaction<'_> { resource_address, state_version: unspent.state_version.as_u64() as i64, utxo_tag: unspent.utxo_output.tag.value() as i32, + epoch: epoch.as_u64() as i64, is_spent: false, is_burnt: false, is_frozen: unspent.is_frozen, @@ -150,6 +152,7 @@ impl IndexerStoreWriteTransaction for SqliteStoreWriteTransaction<'_> { let resource_address = spent.address.resource_address().to_string(); let commitment = spent.address.id().to_commitment_hex_string(); let update = UtxoRecordUpdate { + epoch: Some(epoch.as_u64() as i64), version: Some(spent.version as i32), // Prune the UTXO data for spent outputs output: Some(None), diff --git a/applications/tari_indexer/src/store.rs b/applications/tari_indexer/src/store.rs index 5c5999615e..86b2b97955 100644 --- a/applications/tari_indexer/src/store.rs +++ b/applications/tari_indexer/src/store.rs @@ -18,7 +18,7 @@ use tari_ootle_storage::{ Ordering, StorageError, }; -use tari_ootle_wallet_sdk::models::WalletUtxoUpdate; +use tari_ootle_wallet_sdk::models::UtxoStateUpdateSet; use tari_template_lib::{ models::{ResourceAddress, UtxoId}, prelude::RistrettoPublicKeyBytes, @@ -138,17 +138,15 @@ pub trait IndexerStoreReadTransaction { ) -> Result; /// Get UTXO updates for a given resource address and shard, starting from a specific state version. - /// - /// Returns a tuple containing the maximum returned state version, and a vector of UTXO - /// updates. fn utxos_get_updates( &mut self, resource_address: ResourceAddress, + from_epoch: Epoch, shard: Shard, from_state_version: StateVersion, unspents_only: bool, limit: u32, - ) -> Result<(StateVersion, Vec), StorageError>; + ) -> Result; fn utxos_list( &mut self, @@ -176,6 +174,7 @@ pub trait IndexerStoreWriteTransaction { ) -> Result<(), StorageError>; fn batch_insert_utxo_updates>( &mut self, + epoch: Epoch, updates: I, ) -> Result<(), StorageError>; fn upsert_substate(&mut self, substate: &SubstateData) -> Result<(), StorageError>; diff --git a/applications/tari_indexer/src/substate_manager.rs b/applications/tari_indexer/src/substate_manager.rs index 238c65495d..b1796fafc7 100644 --- a/applications/tari_indexer/src/substate_manager.rs +++ b/applications/tari_indexer/src/substate_manager.rs @@ -34,11 +34,12 @@ use tari_indexer_lib::substate_scanner::SubstateScanner; use tari_ootle_common_types::{ shard::Shard, substate_type::SubstateType, + Epoch, PeerAddress, StateVersion, VersionedSubstateIdRef, }; -use tari_ootle_wallet_sdk::models::WalletUtxoUpdate; +use tari_ootle_wallet_sdk::models::UtxoStateUpdateSet; use tari_template_lib::{ models::{ResourceAddress, UtxoId}, types::{ @@ -107,13 +108,21 @@ impl SubstateManager { pub fn get_utxo_updates( &self, resource_address: ResourceAddress, + from_epoch: Epoch, shard: Shard, from_state_version: StateVersion, unspent_only: bool, limit: u32, - ) -> Result<(StateVersion, Vec), anyhow::Error> { + ) -> Result { let updates = self.substate_store.with_read_tx(|tx| { - tx.utxos_get_updates(resource_address, shard, from_state_version, unspent_only, limit) + tx.utxos_get_updates( + resource_address, + from_epoch, + shard, + from_state_version, + unspent_only, + limit, + ) })?; Ok(updates) } diff --git a/applications/tari_validator_node/src/config.rs b/applications/tari_validator_node/src/config.rs index 66fa2077c1..525525f4d7 100644 --- a/applications/tari_validator_node/src/config.rs +++ b/applications/tari_validator_node/src/config.rs @@ -131,6 +131,10 @@ impl ValidatorNodeConfig { // self.database.sqlite.path = self.data_dir.as_ref().join(&self.database.sqlite.path); // } } + + pub fn get_global_db_path(&self) -> PathBuf { + self.data_dir.join("global_storage.sqlite") + } } impl Default for ValidatorNodeConfig { diff --git a/applications/tari_validator_node/src/lib.rs b/applications/tari_validator_node/src/lib.rs index b936ead61f..8dd2f4e2f6 100644 --- a/applications/tari_validator_node/src/lib.rs +++ b/applications/tari_validator_node/src/lib.rs @@ -92,7 +92,7 @@ pub async fn run_validator_node( ) -> Result<(), anyhow::Error> { info!(target: LOG_TARGET, "Starting validator node on network {}", config.network); - let db_factory = SqliteDbFactory::new(config.validator_node.data_dir.clone()); + let db_factory = SqliteDbFactory::new(config.validator_node.get_global_db_path()); db_factory .migrate() .map_err(|e| ExitError::new(ExitCode::DatabaseError, e))?; diff --git a/applications/tari_validator_node/src/p2p/rpc/block_sync_task.rs b/applications/tari_validator_node/src/p2p/rpc/block_sync_task.rs index 2163c2b4c2..d8210ff06e 100644 --- a/applications/tari_validator_node/src/p2p/rpc/block_sync_task.rs +++ b/applications/tari_validator_node/src/p2p/rpc/block_sync_task.rs @@ -12,7 +12,7 @@ use tari_ootle_p2p::{ proto::rpc::{sync_blocks_response::SyncData, QuorumCertificates, SyncBlocksResponse}, }; use tari_ootle_storage::{ - consensus_models::{Block, SubstateCreatedProof, SubstateUpdateProof, TransactionRecord}, + consensus_models::{Block, SubstateCreate, SubstateUpdateProof, TransactionRecord}, StateStore, StateStoreReadTransaction, StorageError, @@ -29,7 +29,7 @@ struct BlockData { qcs: Vec, substates: Vec, transactions: Vec, - transaction_receipts: Vec, + transaction_receipts: Vec, } type BlockBuffer = Vec; diff --git a/applications/tari_walletd/src/lib.rs b/applications/tari_walletd/src/lib.rs index e96d1c6f0b..f871b8071d 100644 --- a/applications/tari_walletd/src/lib.rs +++ b/applications/tari_walletd/src/lib.rs @@ -33,10 +33,11 @@ use std::{fs, panic, pin, process}; use log::*; use tari_common_types::seeds::seed_words::SeedWords; -use tari_ootle_common_types::{optional::Optional, NumPreshards}; +use tari_ootle_common_types::{optional::Optional, Network, NumPreshards}; use tari_ootle_wallet_sdk::{ apis::config::{ConfigApi, ConfigKey}, cipher_seed::CipherSeedRestore, + models::EpochBirthday, WalletSdk as Sdk, WalletSdkConfig, }; @@ -92,10 +93,12 @@ pub async fn run_tari_ootle_walletd( // trigger account scanning if needed if needs_seed_recovery { + let cipher_seed_birthday = wallet_sdk.key_manager_api().get_cipher_seed_birthday_epoch()?; let scanner = AccountRecoveryService::new( wallet_sdk.clone(), services.account_monitor_handle.clone(), config.ootle_wallet_daemon.recovery_abandon_count, + cipher_seed_birthday, ); let shutdown_signal = shutdown_signal.clone(); tokio::spawn(async move { @@ -193,6 +196,19 @@ pub fn initialize_wallet_sdk(config: &ApplicationConfig, store: SqliteWalletStor config.ootle_wallet_daemon.indexer_api_url.clone() }; let indexer = IndexerRestApiNetworkInterface::new(indexer_endpoint); - let sdk = WalletSdk::initialize(store, indexer, sdk_config)?; + let birthday = get_epoch_birthday(sdk_config.network); + let sdk = WalletSdk::initialize(store, indexer, sdk_config, birthday)?; Ok(sdk) } + +const fn get_epoch_birthday(network: Network) -> EpochBirthday { + // TODO: set the zero epoch time for each network according to actual zero epoch time + match network { + Network::MainNet => EpochBirthday::far_future(), + Network::StageNet => EpochBirthday::far_future(), + Network::NextNet => EpochBirthday::far_future(), + Network::LocalNet => EpochBirthday::far_future(), + Network::Igor => EpochBirthday::far_future(), + Network::Esmeralda => EpochBirthday::far_future(), + } +} diff --git a/applications/tari_walletd/src/main.rs b/applications/tari_walletd/src/main.rs index 651369b103..83ef9923dc 100644 --- a/applications/tari_walletd/src/main.rs +++ b/applications/tari_walletd/src/main.rs @@ -92,11 +92,13 @@ async fn main() -> Result<(), anyhow::Error> { let view_only_public_key = account_address.address.view_only_key().to_byte_type(); let account_addr = sdk.accounts_api().derive_account_address_from_public_key(&public_key); let is_default = !sdk.accounts_api().any_accounts_exist()?; + let birthday_epoch = sdk.calculate_birthday_epoch(); sdk.accounts_api().add_account( name.as_deref(), &account_addr, account_address.view_only_key_id, account_address.owner_key_id, + birthday_epoch, false, is_default, )?; diff --git a/bindings/src/types/Account.ts b/bindings/src/types/Account.ts index 91975afb64..6b487ace3f 100644 --- a/bindings/src/types/Account.ts +++ b/bindings/src/types/Account.ts @@ -1,5 +1,6 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { ComponentAddress } from "./ComponentAddress"; +import type { Epoch } from "./Epoch"; import type { RistrettoPublicKeyBytes } from "./RistrettoPublicKeyBytes"; import type { KeyId } from "./wallet-daemon-client/KeyId"; @@ -9,6 +10,7 @@ export type Account = { view_only_key_id: KeyId; owner_key_id: KeyId | null; owner_public_key: RistrettoPublicKeyBytes; + birthday_epoch: Epoch; is_confirmed_on_chain: boolean; is_default: boolean; }; diff --git a/bindings/src/types/UtxoStateUpdateSet.ts b/bindings/src/types/UtxoStateUpdateSet.ts index 584386f920..88935e00e3 100644 --- a/bindings/src/types/UtxoStateUpdateSet.ts +++ b/bindings/src/types/UtxoStateUpdateSet.ts @@ -1,5 +1,10 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Epoch } from "./Epoch"; import type { StateVersion } from "./StateVersion"; import type { WalletUtxoUpdate } from "./WalletUtxoUpdate"; -export type UtxoStateUpdateSet = { updates: Array; max_state_version: StateVersion }; +export type UtxoStateUpdateSet = { + updates: Array; + max_state_version: StateVersion; + max_epoch: Epoch; +}; diff --git a/bindings/src/types/tari-indexer-client/GetUtxoUpdatesRequest.ts b/bindings/src/types/tari-indexer-client/GetUtxoUpdatesRequest.ts index 12cb894a25..f2110591b1 100644 --- a/bindings/src/types/tari-indexer-client/GetUtxoUpdatesRequest.ts +++ b/bindings/src/types/tari-indexer-client/GetUtxoUpdatesRequest.ts @@ -1,9 +1,11 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Epoch } from "../Epoch"; import type { ResourceAddress } from "../ResourceAddress"; import type { Shard } from "../Shard"; import type { StateVersion } from "../StateVersion"; export type GetUtxoUpdatesRequest = { + from_epoch: Epoch; shard_state_versions: Array<[Shard, StateVersion]>; resource_address: ResourceAddress; unspent_only?: boolean; diff --git a/clients/tari_indexer_client/src/types.rs b/clients/tari_indexer_client/src/types.rs index d094d5b0f9..5cf4746aaf 100644 --- a/clients/tari_indexer_client/src/types.rs +++ b/clients/tari_indexer_client/src/types.rs @@ -373,6 +373,8 @@ pub struct IndexerReadyResponse {} #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export, export_to = "tari-indexer-client/"))] pub struct GetUtxoUpdatesRequest { + #[serde(default)] + pub from_epoch: Epoch, pub shard_state_versions: Vec<(Shard, StateVersion)>, pub resource_address: ResourceAddress, #[serde(default, skip_serializing_if = "std::ops::Not::not")] diff --git a/clients/tari_indexer_client/tests/streaming.rs b/clients/tari_indexer_client/tests/streaming.rs index e08b4728a4..f27cf7706a 100644 --- a/clients/tari_indexer_client/tests/streaming.rs +++ b/clients/tari_indexer_client/tests/streaming.rs @@ -3,7 +3,7 @@ use futures::TryStreamExt; use tari_indexer_client::{rest_api_client::IndexerRestApiClient, types::GetUtxoUpdatesRequest}; -use tari_ootle_common_types::{NumPreshards, StateVersion}; +use tari_ootle_common_types::{Epoch, NumPreshards, StateVersion}; #[tokio::test] #[ignore = "Requires a running indexer listening on a specific port"] @@ -11,6 +11,7 @@ async fn dev_test() { let mut client = IndexerRestApiClient::connect("http://localhost:12017").unwrap(); let mut stream = client .stream_utxo_updates_protobuf(GetUtxoUpdatesRequest { + from_epoch: Epoch::zero(), shard_state_versions: NumPreshards::current() .all_shards_iter() .map(|shard| (shard, StateVersion::zero())) diff --git a/crates/epoch_manager/src/service/epoch_manager_service.rs b/crates/epoch_manager/src/service/epoch_manager_service.rs index f831b730fb..d72a598c77 100644 --- a/crates/epoch_manager/src/service/epoch_manager_service.rs +++ b/crates/epoch_manager/src/service/epoch_manager_service.rs @@ -113,7 +113,7 @@ impl EpochManagerService { } pub async fn run(&mut self) -> Result<(), EpochManagerError> { - info!(target: LOG_TARGET, "Starting epoch manager"); + info!(target: LOG_TARGET, "🚀 Starting epoch manager"); // first, load initial state self.inner.load_initial_state()?; diff --git a/crates/indexer_lib/src/error.rs b/crates/indexer_lib/src/error.rs index 6969a73c1d..1900cb9783 100644 --- a/crates/indexer_lib/src/error.rs +++ b/crates/indexer_lib/src/error.rs @@ -26,4 +26,6 @@ pub enum IndexerError { FailedToParseTransactionHash(String), #[error("Substate cache operation failed: {0}")] SubstateCacheError(#[from] SubstateCacheError), + #[error("No committee members available: {details}")] + NoCommitteeMembers { details: String }, } diff --git a/crates/indexer_lib/src/substate_scanner.rs b/crates/indexer_lib/src/substate_scanner.rs index be40b78037..d593a3ec2b 100644 --- a/crates/indexer_lib/src/substate_scanner.rs +++ b/crates/indexer_lib/src/substate_scanner.rs @@ -121,6 +121,11 @@ where .committee_provider .get_committee_for_substate(epoch, substate_req.or_zero_version().to_substate_address()) .await?; + if committee.is_empty() { + return Err(IndexerError::NoCommitteeMembers { + details: format!("No committee found for substate {} at epoch {}", substate_req, epoch), + }); + } committee.shuffle(); diff --git a/crates/p2p/proto/rpc.proto b/crates/p2p/proto/rpc.proto index 8fdc8da86a..9934cf80ec 100644 --- a/crates/p2p/proto/rpc.proto +++ b/crates/p2p/proto/rpc.proto @@ -140,20 +140,18 @@ message SubstateData { message SubstateUpdate { oneof update { - SubstateCreatedProof create = 1; - SubstateDestroyedProof destroy = 2; + SubstateCreate create = 1; + SubstateDestroy destroy = 2; } } -message SubstateCreatedProof { +message SubstateCreate { SubstateData substate = 1; - // tari.ootle.consensus.QuorumCertificate created_justify = 2; } -message SubstateDestroyedProof { +message SubstateDestroy { bytes substate_id = 1; uint32 version = 2; - // tari.ootle.consensus.QuorumCertificate destroyed_justify = 3; } message SyncBlocksRequest { @@ -186,7 +184,7 @@ message SyncBlocksResponse { uint32 transaction_count = 5; tari.ootle.transaction.Transaction transaction = 6; uint32 transaction_receipt_count = 7; - SubstateCreatedProof transaction_receipt = 8; + SubstateCreate transaction_receipt = 8; } } diff --git a/crates/p2p/src/block_sync.rs b/crates/p2p/src/block_sync.rs index 69a37f7e51..677a621128 100644 --- a/crates/p2p/src/block_sync.rs +++ b/crates/p2p/src/block_sync.rs @@ -5,7 +5,7 @@ use crate::{ proto, proto::{ consensus::{Block, QuorumCertificate}, - rpc::{sync_blocks_response::SyncData, QuorumCertificates, SubstateCreatedProof, SubstateUpdate}, + rpc::{sync_blocks_response::SyncData, QuorumCertificates, SubstateCreate, SubstateUpdate}, transaction::Transaction, }, }; @@ -60,7 +60,7 @@ impl proto::rpc::SyncBlocksResponse { } } - pub fn into_transaction_receipt(self) -> Option { + pub fn into_transaction_receipt(self) -> Option { match self.sync_data { Some(SyncData::TransactionReceipt(receipt)) => Some(receipt), _ => None, diff --git a/crates/p2p/src/conversions/rpc.rs b/crates/p2p/src/conversions/rpc.rs index 8aca1c3526..ef30edae67 100644 --- a/crates/p2p/src/conversions/rpc.rs +++ b/crates/p2p/src/conversions/rpc.rs @@ -10,9 +10,9 @@ use tari_jellyfish::TreeHash; use tari_ootle_common_types::shard::Shard; use tari_ootle_storage::consensus_models::{ EpochCheckpoint, - SubstateCreatedProof, + SubstateCreate, SubstateData, - SubstateDestroyedProof, + SubstateDestroy, SubstateUpdateProof, SubstateValueOrHash, TreeRootSummary, @@ -23,10 +23,10 @@ use crate::{ proto, }; -impl TryFrom for SubstateCreatedProof { +impl TryFrom for SubstateCreate { type Error = anyhow::Error; - fn try_from(value: proto::rpc::SubstateCreatedProof) -> Result { + fn try_from(value: proto::rpc::SubstateCreate) -> Result { Ok(Self { substate: value .substate @@ -37,19 +37,18 @@ impl TryFrom for SubstateCreatedProof { } } -impl From for proto::rpc::SubstateCreatedProof { - fn from(value: SubstateCreatedProof) -> Self { +impl From for proto::rpc::SubstateCreate { + fn from(value: SubstateCreate) -> Self { Self { substate: Some(value.substate.into()), - // created_justify: Some((&value.created_qc).into()), } } } -impl TryFrom for SubstateDestroyedProof { +impl TryFrom for SubstateDestroy { type Error = anyhow::Error; - fn try_from(value: proto::rpc::SubstateDestroyedProof) -> Result { + fn try_from(value: proto::rpc::SubstateDestroy) -> Result { Ok(Self { substate_id: SubstateId::from_bytes(&value.substate_id)?, version: value.version, @@ -57,12 +56,11 @@ impl TryFrom for SubstateDestroyedProof { } } -impl From for proto::rpc::SubstateDestroyedProof { - fn from(value: SubstateDestroyedProof) -> Self { +impl From for proto::rpc::SubstateDestroy { + fn from(value: SubstateDestroy) -> Self { Self { substate_id: value.substate_id.to_bytes(), version: value.version, - // destroyed_justify: Some((&value.justify).into()), } } } diff --git a/crates/rpc_state_sync/src/state_sync.rs b/crates/rpc_state_sync/src/state_sync.rs index 2b68c50b84..db1ada3eff 100644 --- a/crates/rpc_state_sync/src/state_sync.rs +++ b/crates/rpc_state_sync/src/state_sync.rs @@ -28,7 +28,7 @@ use tari_ootle_storage::{ consensus_models::{ BookkeepingModel, EpochCheckpoint, - SubstateCreatedProof, + SubstateCreate, SubstateRecord, SubstateTransition, SubstateUpdateBatch, @@ -659,7 +659,7 @@ where TConsensusSpec: ConsensusSpec + Send + Sync + 'static fn extract_template_change( // Extra data required by the template db - necessary? epoch: Epoch, - create: &SubstateCreatedProof, + create: &SubstateCreate, ) -> Result, RpcStateSyncError> { let Some(template_address) = create.substate.substate_id.as_template() else { return Ok(None); diff --git a/crates/state_store_rocksdb/src/reader.rs b/crates/state_store_rocksdb/src/reader.rs index 97806f27f6..85a76e531f 100644 --- a/crates/state_store_rocksdb/src/reader.rs +++ b/crates/state_store_rocksdb/src/reader.rs @@ -71,9 +71,9 @@ use tari_ootle_storage::{ PendingShardStateTreeDiff, StateVersionTransitions, SubstateChange, - SubstateCreatedProof, + SubstateCreate, SubstateData, - SubstateDestroyedProof, + SubstateDestroy, SubstateLock, SubstatePledges, SubstateRecord, @@ -1619,8 +1619,8 @@ impl<'tx, TAddr: NodeAddressable + Serialize + DeserializeOwned + 'tx> StateStor let mut updates = Vec::with_capacity(data.transitions.len()); // multi_get returns the substates in the same order as queried, so ordered by transitions - for (data, substate) in data.transitions.iter().zip(substates) { - let update = match data.transition { + for (rec, substate) in data.transitions.iter().zip(substates) { + let update = match rec.transition { StateTransitionType::Up => { let value = value_filter .contains_substate(&substate.substate_id) @@ -1630,7 +1630,7 @@ impl<'tx, TAddr: NodeAddressable + Serialize + DeserializeOwned + 'tx> StateStor || SubstateValueOrHash::Hash(substate.state_hash), |v| SubstateValueOrHash::Value(Box::new(v)), ); - SubstateUpdateProof::Create(SubstateCreatedProof { + SubstateUpdateProof::Create(SubstateCreate { substate: SubstateData { substate_id: substate.substate_id, version: substate.version, @@ -1638,7 +1638,7 @@ impl<'tx, TAddr: NodeAddressable + Serialize + DeserializeOwned + 'tx> StateStor }, }) }, - StateTransitionType::Down => SubstateUpdateProof::Destroy(SubstateDestroyedProof { + StateTransitionType::Down => SubstateUpdateProof::Destroy(SubstateDestroy { substate_id: substate.substate_id, version: substate.version, }), diff --git a/crates/storage/src/consensus_models/block.rs b/crates/storage/src/consensus_models/block.rs index e22aa45618..bb98cf6d89 100644 --- a/crates/storage/src/consensus_models/block.rs +++ b/crates/storage/src/consensus_models/block.rs @@ -53,7 +53,7 @@ use super::{ ForeignProposalAtom, ForeignProposalRecord, PendingShardStateTreeDiff, - SubstateDestroyedProof, + SubstateDestroy, SubstateRecord, TransactionAtom, ValidatorStatsUpdate, @@ -63,7 +63,7 @@ use crate::{ block_header::BlockHeader, substate_update_batch::SubstateUpdateBatch, Command, - SubstateCreatedProof, + SubstateCreate, SubstateUpdateProof, TransactionRecord, }, @@ -797,18 +797,18 @@ impl Block { // TODO: This is currently not used - if we need this in future, we can include the state hash en // lieu of the actual state which does not exist // if substate.created_by_transaction == transaction.id - // { updates.push(SubstateUpdate::Create(SubstateCreatedProof { + // { updates.push(SubstateUpdate::Create(SubstateCreate { // // created_qc: substate.get_created_quorum_certificate(tx)?, // substate: substate.try_into()?, // })); // } else { - updates.push(SubstateUpdateProof::Destroy(SubstateDestroyedProof { + updates.push(SubstateUpdateProof::Destroy(SubstateDestroy { substate_id: substate.substate_id.clone(), version: substate.version, // justify: ProposalCertificate::get(tx, &destroyed.justify)?, })); } else { - updates.push(SubstateUpdateProof::Create(SubstateCreatedProof { + updates.push(SubstateUpdateProof::Create(SubstateCreate { // created_qc: substate.get_created_quorum_certificate(tx)?, substate: substate.into(), })); @@ -822,7 +822,7 @@ impl Block { pub fn get_transaction_receipts( &self, tx: &TTx, - ) -> Result, StorageError> { + ) -> Result, StorageError> { let committed = self .commands() .iter() @@ -838,7 +838,7 @@ impl Block { let receipts = receipts .into_iter() .map(|receipt| { - Ok::<_, StorageError>(SubstateCreatedProof { + Ok::<_, StorageError>(SubstateCreate { substate: receipt.into(), }) }) diff --git a/crates/storage/src/consensus_models/substate.rs b/crates/storage/src/consensus_models/substate.rs index 0b8c94ec40..edf4372b57 100644 --- a/crates/storage/src/consensus_models/substate.rs +++ b/crates/storage/src/consensus_models/substate.rs @@ -271,19 +271,17 @@ impl SubstateRecord { } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SubstateCreatedProof { +pub struct SubstateCreate { pub substate: SubstateData, - // TODO: proof that data was created } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SubstateDestroyedProof { +pub struct SubstateDestroy { pub substate_id: SubstateId, pub version: u32, - // TODO: proof that data was destroyed } -impl SubstateDestroyedProof { +impl SubstateDestroy { pub fn to_versioned_substate_id(&self) -> VersionedSubstateId { VersionedSubstateId::new(self.substate_id.clone(), self.version) } @@ -292,7 +290,6 @@ impl SubstateDestroyedProof { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export))] pub struct SubstateCreated { - // TODO: consider removing this field, it's not used pub at_epoch: Epoch, // Note: This field not strictly necessary, since the shard can be derived from (SubstateId, Version) and // NumPreshards. But the cost is negligible, and it makes the metadata more self-contained. @@ -395,8 +392,8 @@ impl From for SubstateData { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum SubstateUpdateProof { - Create(SubstateCreatedProof), - Destroy(SubstateDestroyedProof), + Create(SubstateCreate), + Destroy(SubstateDestroy), } impl SubstateUpdateProof { @@ -426,7 +423,7 @@ impl SubstateUpdateProof { VersionedSubstateId::new(self.substate_id().clone(), self.version()) } - pub fn as_create(&self) -> Option<&SubstateCreatedProof> { + pub fn as_create(&self) -> Option<&SubstateCreate> { match self { Self::Create(create) => Some(create), _ => None, @@ -449,8 +446,8 @@ impl SubstateUpdateProof { } } -impl From for SubstateUpdateProof { - fn from(value: SubstateCreatedProof) -> Self { +impl From for SubstateUpdateProof { + fn from(value: SubstateCreate) -> Self { Self::Create(value) } } diff --git a/crates/storage_sqlite/src/sqlite_db_factory.rs b/crates/storage_sqlite/src/sqlite_db_factory.rs index 4df72b97b4..b0a62cdfb0 100644 --- a/crates/storage_sqlite/src/sqlite_db_factory.rs +++ b/crates/storage_sqlite/src/sqlite_db_factory.rs @@ -20,7 +20,11 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{fs::create_dir_all, marker::PhantomData, path::PathBuf}; +use std::{ + fs::create_dir_all, + marker::PhantomData, + path::{Path, PathBuf}, +}; use diesel::{sql_query, Connection, RunQueryDsl, SqliteConnection}; use tari_ootle_common_types::NodeAddressable; @@ -35,23 +39,26 @@ const _LOG_TARGET: &str = "tari::ootle::sqlite_storage"; #[derive(Debug, Clone)] pub struct SqliteDbFactory { - data_dir: PathBuf, - _addr: std::marker::PhantomData, + db_path: PathBuf, + _addr: PhantomData, } impl SqliteDbFactory { - pub fn new(data_dir: PathBuf) -> Self { + pub fn new>(db_path: P) -> Self { Self { - data_dir, + db_path: db_path.as_ref().to_path_buf(), _addr: PhantomData, } } fn connect(&self) -> Result { - let database_url = self.data_dir.join("global_storage.sqlite"); - create_dir_all(database_url.parent().unwrap()).map_err(|_| StorageError::FileSystemPathDoesNotExist)?; - let database_url = database_url.to_str().expect("database_url utf-8 error").to_string(); - let connection = SqliteConnection::establish(&database_url).map_err(SqliteStorageError::from)?; + if let Some(parent) = self.db_path.parent() { + create_dir_all(parent).map_err(|e| StorageError::General { + details: format!("Failed to create parent directory for database file: {}", e), + })?; + } + let database_url = self.db_path.to_str().expect("database_url utf-8 error"); + let connection = SqliteConnection::establish(database_url).map_err(SqliteStorageError::from)?; Ok(connection) } } diff --git a/crates/wallet/sdk/src/apis/accounts.rs b/crates/wallet/sdk/src/apis/accounts.rs index e479aea1f6..6a29bd710f 100644 --- a/crates/wallet/sdk/src/apis/accounts.rs +++ b/crates/wallet/sdk/src/apis/accounts.rs @@ -13,6 +13,7 @@ use tari_ootle_address::RistrettoOotleAddress; use tari_ootle_common_types::{ optional::{IsNotFoundError, Optional}, substate_type::SubstateType, + Epoch, Network, }; use tari_template_builtin::ACCOUNT_TEMPLATE_ADDRESS; @@ -32,6 +33,7 @@ use crate::{ Account, AccountUpdate, AccountWithAddress, + EpochBirthday, KeyId, KeyIdOrPublicKey, VaultBalance, @@ -47,6 +49,7 @@ pub struct AccountsApi<'a, TStore, TNetworkInterface> { store: &'a TStore, substates_api: SubstatesApi<'a, TStore, TNetworkInterface>, key_manager_api: KeyManagerApi<'a, TStore>, + epoch_birthday: EpochBirthday, } pub fn derive_account_address_from_public_key(public_key: &RistrettoPublicKeyBytes) -> ComponentAddress { @@ -59,12 +62,14 @@ impl<'a, TStore: WalletStore, TNetworkInterface> AccountsApi<'a, TStore, TNetwor store: &'a TStore, substates_api: SubstatesApi<'a, TStore, TNetworkInterface>, key_manager_api: KeyManagerApi<'a, TStore>, + epoch_birthday: EpochBirthday, ) -> Self { Self { network, store, substates_api, key_manager_api, + epoch_birthday, } } @@ -81,11 +86,14 @@ impl<'a, TStore: WalletStore, TNetworkInterface> AccountsApi<'a, TStore, TNetwor let account_public_key = account_address.address.account_key().to_byte_type(); let account_component_address = derive_account_address_from_public_key(&account_public_key); + let birthday_epoch = self.epoch_birthday.calculate_current_epoch(); + self.add_account( account_name, &account_component_address, account_address.view_only_key_id, account_address.owner_key_id, + birthday_epoch, false, is_default, )?; @@ -97,6 +105,7 @@ impl<'a, TStore: WalletStore, TNetworkInterface> AccountsApi<'a, TStore, TNetwor view_only_key_id: account_address.view_only_key_id, owner_key_id: Some(account_address.owner_key_id), owner_public_key: account_public_key, + birthday_epoch, is_confirmed_on_chain: false, is_default, }, @@ -110,6 +119,7 @@ impl<'a, TStore: WalletStore, TNetworkInterface> AccountsApi<'a, TStore, TNetwor account_address: &ComponentAddress, view_only_key_id: KeyId, owner_key: K, + birthday_epoch: Epoch, is_confirmed_on_chain: bool, is_default: bool, ) -> Result<(), AccountsApiError> { @@ -141,6 +151,7 @@ impl<'a, TStore: WalletStore, TNetworkInterface> AccountsApi<'a, TStore, TNetwor owner_key_id, &owner_pk, &associated_stealth_resources, + birthday_epoch, is_confirmed_on_chain, is_default, )?; diff --git a/crates/wallet/sdk/src/apis/key_manager.rs b/crates/wallet/sdk/src/apis/key_manager.rs index ca07fd5a4b..b5100245cf 100644 --- a/crates/wallet/sdk/src/apis/key_manager.rs +++ b/crates/wallet/sdk/src/apis/key_manager.rs @@ -3,6 +3,7 @@ use blake2::Blake2b; use digest::{consts::U64, crypto_common::rand_core::OsRng}; +use tari_common_types::seeds::cipher_seed; use tari_crypto::{ keys::{PublicKey as _, SecretKey}, ristretto::{RistrettoPublicKey, RistrettoSecretKey}, @@ -11,6 +12,7 @@ use tari_crypto::{ use tari_ootle_address::RistrettoOotleAddress; use tari_ootle_common_types::{ optional::{IsNotFoundError, Optional}, + Epoch, Network, }; use tari_ootle_wallet_crypto::encryption::encrypt_with_password; @@ -24,6 +26,7 @@ use crate::{ DerivedKeyIndex, DerivedKeyPair, DerivedWalletKey, + EpochBirthday, ImportedKeyId, ImportedWalletKey, KeyBranch, @@ -45,6 +48,7 @@ pub struct KeyManagerApi<'a, TStore> { store: &'a TStore, key_store: LocalKeyStore<'a, TStore>, password_manager: PasswordManagerApi<'a, TStore>, + epoch_birthday: EpochBirthday, } impl<'a, TStore: WalletStore> KeyManagerApi<'a, TStore> { @@ -53,12 +57,14 @@ impl<'a, TStore: WalletStore> KeyManagerApi<'a, TStore> { store: &'a TStore, key_store: LocalKeyStore<'a, TStore>, password_manager: PasswordManagerApi<'a, TStore>, + epoch_birthday: EpochBirthday, ) -> Self { Self { network, store, key_store, password_manager, + epoch_birthday, } } @@ -323,6 +329,21 @@ impl<'a, TStore: WalletStore> KeyManagerApi<'a, TStore> { }, } } + + pub fn get_cipher_seed_birthday_epoch(&self) -> Result { + let birthday = self + .key_store + .cipher_seed_birthday() + .map_err(|e| KeyManagerApiError::KeyStoreError { source: e.into() })?; + let Some(birthday) = birthday else { + return Ok(Epoch::zero()); + }; + + let birthday = u64::from(birthday) * cipher_seed::SECONDS_PER_DAY; + let epoch = self.epoch_birthday.calculate_epoch_rel_minotari(birthday); + + Ok(epoch) + } } #[derive(Debug, thiserror::Error)] diff --git a/crates/wallet/sdk/src/key_managers/backend.rs b/crates/wallet/sdk/src/key_managers/backend.rs index b2009a3008..83626d5564 100644 --- a/crates/wallet/sdk/src/key_managers/backend.rs +++ b/crates/wallet/sdk/src/key_managers/backend.rs @@ -19,7 +19,12 @@ pub trait KeyManagerBackend { pub trait WalletKeyStore { type Error; + /// Derive a secret key from the cipher seed for the given branch and key index. fn derive_secret(&self, branch: &str, key_index: DerivedKeyIndex) -> Result; + /// Retrieve an imported secret key by its key identifier. fn get_imported_secret(&self, key: K) -> Result; + + /// Retrieve the cipher seed birthday if it exists. If this is not supported, it is correct to return Ok(None). + fn cipher_seed_birthday(&self) -> Result, Self::Error>; } diff --git a/crates/wallet/sdk/src/key_managers/local.rs b/crates/wallet/sdk/src/key_managers/local.rs index 611530b9df..0cd8f75bc5 100644 --- a/crates/wallet/sdk/src/key_managers/local.rs +++ b/crates/wallet/sdk/src/key_managers/local.rs @@ -59,7 +59,7 @@ pub enum LocalKeyManagerError { PasswordManagerApiError(#[from] PasswordManagerApiError), #[error("Key manager is in read only mode")] ReadOnlyMode, - #[error("Cipher error: {0}")] + #[error("Key store error: {0}")] KeyStoreError(TKeyStoreErr), } diff --git a/crates/wallet/sdk/src/local_key_store.rs b/crates/wallet/sdk/src/local_key_store.rs index 5ab93e2369..f3909e884c 100644 --- a/crates/wallet/sdk/src/local_key_store.rs +++ b/crates/wallet/sdk/src/local_key_store.rs @@ -68,6 +68,11 @@ impl WalletKeyStore for LocalKeyStore<'_, TS })?; Ok(secret) } + + fn cipher_seed_birthday(&self) -> Result, Self::Error> { + let seed = self.get_cipher_seed()?; + Ok(Some(seed.birthday())) + } } #[derive(thiserror::Error, Debug)] diff --git a/crates/wallet/sdk/src/models/account.rs b/crates/wallet/sdk/src/models/account.rs index a3195e07d4..8cc5339977 100644 --- a/crates/wallet/sdk/src/models/account.rs +++ b/crates/wallet/sdk/src/models/account.rs @@ -5,6 +5,7 @@ use std::fmt::{Display, Formatter}; use tari_bor::{Deserialize, Serialize}; use tari_ootle_address::OotleAddress; +use tari_ootle_common_types::Epoch; use tari_template_lib::{models::ComponentAddress, prelude::RistrettoPublicKeyBytes}; use crate::models::KeyId; @@ -17,6 +18,7 @@ pub struct Account { pub view_only_key_id: KeyId, pub owner_key_id: Option, pub owner_public_key: RistrettoPublicKeyBytes, + pub birthday_epoch: Epoch, pub is_confirmed_on_chain: bool, pub is_default: bool, } @@ -38,6 +40,10 @@ impl Account { &self.owner_public_key } + pub fn birthday_epoch(&self) -> Epoch { + self.birthday_epoch + } + pub fn name(&self) -> Option<&String> { self.name.as_ref() } @@ -72,6 +78,10 @@ impl AccountWithAddress { Self { account, address } } + pub fn birthday_epoch(&self) -> Epoch { + self.account.birthday_epoch() + } + pub fn account(&self) -> &Account { &self.account } diff --git a/crates/wallet/sdk/src/models/epoch_birthday.rs b/crates/wallet/sdk/src/models/epoch_birthday.rs new file mode 100644 index 0000000000..ddb64cf8b4 --- /dev/null +++ b/crates/wallet/sdk/src/models/epoch_birthday.rs @@ -0,0 +1,124 @@ +// Copyright 2025 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use std::{num::NonZeroU64, time::Duration}; + +use tari_common_types::seeds::cipher_seed; +use tari_ootle_common_types::Epoch; + +const BIRTHDAY_GENESIS_FROM_UNIX_EPOCH: Duration = Duration::from_secs(cipher_seed::BIRTHDAY_GENESIS_FROM_UNIX_EPOCH); + +#[derive(Debug, Clone, Copy)] +pub struct EpochBirthday { + /// The duration of an epoch in seconds. + /// NOTE: actual epoch time may vary depending on network conditions, which could lead to inaccuracies. + epoch_time_secs: NonZeroU64, + /// The point in time that represents the first epoch (Epoch(0)). Represented as the number seconds since the + /// Minotari epoch (see CipherSeed). + rel_zero_epoch_secs: u64, +} + +impl EpochBirthday { + pub const fn new(epoch_time_secs: NonZeroU64, rel_zero_epoch_secs: u64) -> Self { + Self { + epoch_time_secs, + rel_zero_epoch_secs, + } + } + + /// An epoch birthday that will always calculate to epoch zero. + /// (well until the u64 overflows in 584 billion years...) + pub const fn far_future() -> Self { + Self { + epoch_time_secs: NonZeroU64::new(u64::MAX).unwrap(), + rel_zero_epoch_secs: 1200, + } + } + + pub fn zero_epoch_time_secs(&self) -> u64 { + self.rel_zero_epoch_secs + } + + pub fn now_relative_to_zero_epoch(&self) -> u64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH + BIRTHDAY_GENESIS_FROM_UNIX_EPOCH) + .ok() + .and_then(|t| t.as_secs().checked_sub(self.rel_zero_epoch_secs)) + .unwrap_or_default() + } + + pub fn calculate_current_epoch(&self) -> Epoch { + let now = self.now_relative_to_zero_epoch(); + self.calculate_epoch_rel_zero_epoch(now) + } + + /// Calculate the epoch for a Minotari-relative timestamp in seconds + pub const fn calculate_epoch_rel_minotari(&self, timestamp_secs: u64) -> Epoch { + // We use saturating sub, because the zero epoch time can be in the future, in which case we define the birthday + // epoch as zero + self.calculate_epoch_rel_zero_epoch(timestamp_secs.saturating_sub(self.rel_zero_epoch_secs)) + } + + /// Calculate the epoch for a given timestamp in seconds relative to the zero epoch time. + pub const fn calculate_epoch_rel_zero_epoch(&self, timestamp_secs: u64) -> Epoch { + Epoch(timestamp_secs / self.epoch_time_secs.get()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_always_calculates_zero_if_zero_epoch_time_is_in_the_future() { + let birthday = EpochBirthday::far_future(); + let epoch = birthday.calculate_current_epoch(); + assert_eq!(epoch, Epoch::zero()); + let timestamp_secs = minotari_now() + 3600 * 4; + let epoch = birthday.calculate_epoch_rel_minotari(timestamp_secs); + assert_eq!(epoch, Epoch::zero()); + let timestamp_secs = 7200; + let epoch = birthday.calculate_epoch_rel_zero_epoch(timestamp_secs); + assert_eq!(epoch, Epoch::zero()); + } + + #[test] + fn it_calculates_the_current_epoch() { + let now = minotari_now(); + let expected_epoch = 5; + let rel_zero_epoch_secs = now - (expected_epoch * 1200); + + let birthday = EpochBirthday::new(1200.try_into().unwrap(), rel_zero_epoch_secs); + let calculated_epoch = birthday.calculate_current_epoch(); + assert_eq!(calculated_epoch, Epoch(expected_epoch)); + } + + #[test] + fn it_calculates_the_epoch_relative_to_the_minotari_timestamp() { + let now = minotari_now(); + let zero_epoch = now + 3600; // zero epoch starts at 1 hour after the minotari epoch + + let birthday = EpochBirthday::new(1200.try_into().unwrap(), zero_epoch); + let timestamp_secs = now + 3600 * 4; // 4 hours after the minotari epoch = 3 hour after the zero epoch + let epoch = birthday.calculate_epoch_rel_minotari(timestamp_secs); + assert_eq!(epoch, Epoch(9)); + } + + #[test] + fn it_calculates_the_epoch_relative_to_the_zero_epoch() { + let now = minotari_now(); + let zero_epoch = now + 3600; // zero epoch starts at 1 hour after the minotari epoch + + let birthday = EpochBirthday::new(1200.try_into().unwrap(), zero_epoch); + let timestamp_secs = 7200; // 2 hours after the zero epoch + let epoch = birthday.calculate_epoch_rel_zero_epoch(timestamp_secs); + assert_eq!(epoch, Epoch(6)); + } + + fn minotari_now() -> u64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH + BIRTHDAY_GENESIS_FROM_UNIX_EPOCH) + .map(|t| t.as_secs()) + .unwrap_or_default() + } +} diff --git a/crates/wallet/sdk/src/models/mod.rs b/crates/wallet/sdk/src/models/mod.rs index 7541f2fed0..d10e431fb3 100644 --- a/crates/wallet/sdk/src/models/mod.rs +++ b/crates/wallet/sdk/src/models/mod.rs @@ -5,6 +5,7 @@ mod account; mod authored_template; mod confidential_output; mod config; +mod epoch_birthday; mod event; mod key; mod lock_guard; @@ -21,6 +22,7 @@ pub use account::*; pub use authored_template::*; pub use confidential_output::*; pub use config::Config; +pub use epoch_birthday::*; pub use event::*; pub use key::*; pub use lock_guard::*; diff --git a/crates/wallet/sdk/src/models/utxo_update.rs b/crates/wallet/sdk/src/models/utxo_update.rs index 8793d06aec..5c130267e6 100644 --- a/crates/wallet/sdk/src/models/utxo_update.rs +++ b/crates/wallet/sdk/src/models/utxo_update.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use tari_bor::{Deserialize, Serialize}; -use tari_ootle_common_types::{shard::Shard, StateVersion}; +use tari_ootle_common_types::{shard::Shard, Epoch, StateVersion}; use tari_template_lib::{ models::UtxoId, types::crypto::{RistrettoPublicKeyBytes, UtxoTag}, @@ -41,6 +41,7 @@ pub struct UtxoUpdateSet { pub struct UtxoStateUpdateSet { pub updates: Vec, pub max_state_version: StateVersion, + pub max_epoch: Epoch, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/crates/wallet/sdk/src/network.rs b/crates/wallet/sdk/src/network.rs index 81276d79ae..68c51d33d5 100644 --- a/crates/wallet/sdk/src/network.rs +++ b/crates/wallet/sdk/src/network.rs @@ -11,7 +11,7 @@ use tari_engine_types::{ substate::{Substate, SubstateId, SubstateValue}, Utxo, }; -use tari_ootle_common_types::{shard::Shard, StateVersion}; +use tari_ootle_common_types::{shard::Shard, Epoch, StateVersion}; use tari_template_abi::TemplateDef; use tari_template_lib::{ models::{ResourceAddress, UtxoId}, @@ -63,6 +63,7 @@ pub trait WalletNetworkInterface { fn stream_stealth_utxo_updates( &self, + from_epoch: Epoch, resource_address: ResourceAddress, shard_state_versions: Vec<(Shard, StateVersion)>, unspent_only: bool, diff --git a/crates/wallet/sdk/src/sdk.rs b/crates/wallet/sdk/src/sdk.rs index 83efd2f667..8c4c7e265a 100644 --- a/crates/wallet/sdk/src/sdk.rs +++ b/crates/wallet/sdk/src/sdk.rs @@ -11,6 +11,7 @@ use tari_common_types::seeds::{ use tari_crypto::tari_utilities::SafePassword; use tari_ootle_common_types::{ optional::{IsNotFoundError, Optional}, + Epoch, Network, NetworkParseError, }; @@ -41,6 +42,7 @@ use crate::{ cipher_seed::{CipherSeedRestore, WalletCipherSeed}, key_managers::local::LocalKeyManager, local_key_store::LocalKeyStore, + models::EpochBirthday, network::{StatusResponseError, WalletNetworkInterface}, storage::{WalletStorageError, WalletStore}, }; @@ -62,6 +64,7 @@ pub struct WalletSdk { network_interface: TNetworkInterface, config: WalletSdkConfig, loaded_cipher_seed: WalletCipherSeed, + epoch_birthday: EpochBirthday, } impl WalletSdk @@ -74,6 +77,7 @@ where store: TStore, indexer: TNetworkInterface, config: WalletSdkConfig, + epoch_birthday: EpochBirthday, ) -> Result, WalletSdkError> { // initialize network if let Some(network) = Self::get_store_network(&store)? { @@ -94,6 +98,7 @@ where network_interface: indexer, config, loaded_cipher_seed: WalletCipherSeed::None, + epoch_birthday, }) } @@ -173,6 +178,7 @@ where &self.store, LocalKeyStore::new(&self.loaded_cipher_seed, self.password_manager_api(), &self.store), self.password_manager_api(), + self.epoch_birthday, ) } @@ -202,6 +208,7 @@ where &self.store, self.substate_api(), self.key_manager_api(), + self.epoch_birthday, ) } @@ -266,6 +273,10 @@ where ViewableBalanceApi } + pub fn calculate_birthday_epoch(&self) -> Epoch { + self.epoch_birthday.calculate_current_epoch() + } + /// Tries to get encrypted cipher seed from DB and decrypts it using OS keyring if possible. fn load_cipher_seed(&mut self) -> Result, WalletSdkError> { // Workaround for borrow checker limitation as described in https://blog.polybdenum.com/2024/12/21/four-limitations-of-rust-s-borrow-checker.html diff --git a/crates/wallet/sdk/src/storage/writer.rs b/crates/wallet/sdk/src/storage/writer.rs index 677900ac05..8f844c32e0 100644 --- a/crates/wallet/sdk/src/storage/writer.rs +++ b/crates/wallet/sdk/src/storage/writer.rs @@ -7,7 +7,7 @@ use tari_engine_types::{ resource::Resource, substate::{SubstateDiff, SubstateId}, }; -use tari_ootle_common_types::{shard::Shard, StateVersion, VersionedSubstateIdRef}; +use tari_ootle_common_types::{shard::Shard, Epoch, StateVersion, VersionedSubstateIdRef}; use tari_template_lib::{ models::{ComponentAddress, NonFungibleId, ResourceAddress, UtxoAddress, UtxoId, VaultId}, prelude::{crypto::UtxoTag, Amount, RistrettoPublicKeyBytes, TemplateAddress}, @@ -98,6 +98,7 @@ pub trait WalletStoreWriter: CommittableStore { owner_key_id: Option, owner_public_key: &RistrettoPublicKeyBytes, associated_stealth_resources: &HashSet, + birthday_epoch: Epoch, is_confirmed_on_chain: bool, is_default: bool, ) -> Result<(), WalletStorageError>; diff --git a/crates/wallet/sdk/tests/support/harness.rs b/crates/wallet/sdk/tests/support/harness.rs index 0aedfb42e7..096055acb6 100644 --- a/crates/wallet/sdk/tests/support/harness.rs +++ b/crates/wallet/sdk/tests/support/harness.rs @@ -10,10 +10,18 @@ use tari_engine_types::{ ToByteType, Utxo, }; -use tari_ootle_common_types::{optional::Optional, shard::Shard, Network, StateVersion}; +use tari_ootle_common_types::{optional::Optional, shard::Shard, Epoch, Network, StateVersion}; use tari_ootle_wallet_sdk::{ cipher_seed::CipherSeedRestore, - models::{ConfidentialOutputModel, KeyId, OutputStatus, UtxoUpdateSet, WalletLockDropGuard, WalletLockId}, + models::{ + ConfidentialOutputModel, + EpochBirthday, + KeyId, + OutputStatus, + UtxoUpdateSet, + WalletLockDropGuard, + WalletLockId, + }, network::{SubstateQueryResult, TransactionQueryResult, UtxoUpdateStream, WalletNetworkInterface}, storage::TagAndPublicNoncePair, WalletSdk, @@ -41,10 +49,15 @@ impl Test { let store = SqliteWalletStore::try_open(temp.path().join("data/wallet.sqlite")).unwrap(); store.run_migrations().unwrap(); - let mut sdk = WalletSdk::initialize(store.clone(), PanicNetworkInterface, WalletSdkConfig { - network: Network::LocalNet, - override_keyring_password: Some(SafePassword::from_str("SuuuCh Sekret W0W").unwrap()), - }) + let mut sdk = WalletSdk::initialize( + store.clone(), + PanicNetworkInterface, + WalletSdkConfig { + network: Network::LocalNet, + override_keyring_password: Some(SafePassword::from_str("SuuuCh Sekret W0W").unwrap()), + }, + EpochBirthday::new(1200.try_into().unwrap(), u64::MAX), + ) .unwrap(); sdk.initialize_cipher_seed(CipherSeedRestore::CreateNewIfRequired) .unwrap(); @@ -55,6 +68,7 @@ impl Test { &Test::test_account_address(), KeyId::derived(0), KeyId::derived(0), + Epoch::zero(), true, true, ) @@ -185,6 +199,7 @@ impl WalletNetworkInterface for PanicNetworkInterface { async fn stream_stealth_utxo_updates( &self, + _from_epoch: Epoch, _resource_address: ResourceAddress, _shard_state_versions: Vec<(Shard, StateVersion)>, _unspent_only: bool, diff --git a/crates/wallet/sdk_services/src/account_recovery/service.rs b/crates/wallet/sdk_services/src/account_recovery/service.rs index 9525339d2b..b5e311bc18 100644 --- a/crates/wallet/sdk_services/src/account_recovery/service.rs +++ b/crates/wallet/sdk_services/src/account_recovery/service.rs @@ -10,6 +10,7 @@ use tari_ootle_common_types::{ displayable::Displayable, optional::{IsNotFoundError, Optional}, substate_type::SubstateType, + Epoch, }; use tari_ootle_wallet_sdk::{ apis::config::ConfigKey, @@ -30,6 +31,7 @@ pub struct AccountRecoveryService { wallet_sdk: WalletSdk, account_monitor_handle: AccountMonitorHandle, abandon_after_not_found: usize, + cipher_seed_birthday_epoch: Epoch, } impl AccountRecoveryService @@ -42,11 +44,13 @@ where wallet_sdk: WalletSdk, account_monitor_handle: AccountMonitorHandle, abandon_after_not_found: usize, + cipher_seed_birthday_epoch: Epoch, ) -> Self { Self { wallet_sdk, account_monitor_handle, abandon_after_not_found, + cipher_seed_birthday_epoch, } } @@ -156,6 +160,10 @@ where .optional() .map_err(|e| AccountRecoveryError::NetworkInterfaceError { details: e.to_string() })?; + // We use the cipher seed birthday as the account birthday since that is simpler than attempting to fetch the + // creation epoch for each account. + let birthday_epoch = self.cipher_seed_birthday_epoch; + match result { None => { info!(target: LOG_TARGET, "🔑 Account {} not found on chain. It may have stealth UTXOs owned by its key", account_addr); @@ -167,6 +175,7 @@ where &account_addr, key.as_key_id(), key.as_key_id(), + birthday_epoch, false, // if this is the first account, set it as the default key.key_index == 0, @@ -218,6 +227,7 @@ where &account_addr, KeyId::derived(key.key_index), KeyId::derived(key.key_index), + birthday_epoch, true, // if this is the first account, set it as the default key.key_index == 0, diff --git a/crates/wallet/sdk_services/src/indexer_rest_api.rs b/crates/wallet/sdk_services/src/indexer_rest_api.rs index 87c2d71099..0456b2a169 100644 --- a/crates/wallet/sdk_services/src/indexer_rest_api.rs +++ b/crates/wallet/sdk_services/src/indexer_rest_api.rs @@ -31,6 +31,7 @@ use tari_ootle_common_types::{ displayable::Displayable, optional::IsNotFoundError, shard::Shard, + Epoch, StateVersion, }; use tari_ootle_wallet_sdk::{ @@ -177,6 +178,7 @@ impl WalletNetworkInterface for IndexerRestApiNetworkInterface { async fn stream_stealth_utxo_updates( &self, + from_epoch: Epoch, resource_address: ResourceAddress, shard_state_versions: Vec<(Shard, StateVersion)>, unspent_only: bool, @@ -184,6 +186,7 @@ impl WalletNetworkInterface for IndexerRestApiNetworkInterface { let mut client = self.get_client()?; let stream = client .stream_utxo_updates_protobuf(GetUtxoUpdatesRequest { + from_epoch, shard_state_versions, resource_address, unspent_only, diff --git a/crates/wallet/sdk_services/src/utxo_scanner/scanner_round.rs b/crates/wallet/sdk_services/src/utxo_scanner/scanner_round.rs index 2d80f9939f..682a361689 100644 --- a/crates/wallet/sdk_services/src/utxo_scanner/scanner_round.rs +++ b/crates/wallet/sdk_services/src/utxo_scanner/scanner_round.rs @@ -123,6 +123,7 @@ where .sdk .get_network_interface() .stream_stealth_utxo_updates( + self.account.birthday_epoch(), *self.resource_address, // NOTE that this will request shards in a random order (HashMap). This is good to avoid always // starting with the same shard. diff --git a/crates/wallet/storage_sqlite/migrations/2023-02-08-122514_initial/up.sql b/crates/wallet/storage_sqlite/migrations/2023-02-08-122514_initial/up.sql index 6307666eea..b438879d79 100644 --- a/crates/wallet/storage_sqlite/migrations/2023-02-08-122514_initial/up.sql +++ b/crates/wallet/storage_sqlite/migrations/2023-02-08-122514_initial/up.sql @@ -87,6 +87,7 @@ CREATE TABLE accounts owner_public_key TEXT NOT NULL, view_only_key_id TEXT NOT NULL, owner_key_id TEXT NULL, + birthday_epoch BIGINT NOT NULL, is_default BOOLEAN NOT NULL DEFAULT 0, is_confirmed_on_chain BOOLEAN NOT NULL, stealth_resources TEXT NOT NULL DEFAULT '[]', diff --git a/crates/wallet/storage_sqlite/src/models/account.rs b/crates/wallet/storage_sqlite/src/models/account.rs index 17e27303a9..d262418b13 100644 --- a/crates/wallet/storage_sqlite/src/models/account.rs +++ b/crates/wallet/storage_sqlite/src/models/account.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: BSD-3-Clause use diesel::{Identifiable, Queryable}; +use tari_ootle_common_types::Epoch; use tari_ootle_wallet_sdk::storage::WalletStorageError; use time::PrimitiveDateTime; @@ -19,6 +20,7 @@ pub struct Account { pub owner_public_key: String, pub view_only_key_id: String, pub owner_key_id: Option, + pub birthday_epoch: i64, pub is_default: bool, pub is_confirmed_on_chain: bool, pub _stealth_resource_address: String, @@ -38,6 +40,7 @@ impl Account { owner_key_id: self.owner_key_id.as_ref().map(deserialize_json).transpose()?, view_only_key_id: deserialize_json(&self.view_only_key_id)?, owner_public_key: deserialize_hex_try_from(&self.owner_public_key)?, + birthday_epoch: Epoch(self.birthday_epoch as u64), is_confirmed_on_chain: self.is_confirmed_on_chain, is_default: self.is_default, }) diff --git a/crates/wallet/storage_sqlite/src/schema.rs b/crates/wallet/storage_sqlite/src/schema.rs index dc70e1f98a..0a6309f9b7 100644 --- a/crates/wallet/storage_sqlite/src/schema.rs +++ b/crates/wallet/storage_sqlite/src/schema.rs @@ -8,6 +8,7 @@ diesel::table! { owner_public_key -> Text, view_only_key_id -> Text, owner_key_id -> Nullable, + birthday_epoch -> BigInt, is_default -> Bool, is_confirmed_on_chain -> Bool, stealth_resources -> Text, diff --git a/crates/wallet/storage_sqlite/src/writer.rs b/crates/wallet/storage_sqlite/src/writer.rs index 8efcd9c0a7..7a1e803aed 100644 --- a/crates/wallet/storage_sqlite/src/writer.rs +++ b/crates/wallet/storage_sqlite/src/writer.rs @@ -26,7 +26,7 @@ use tari_engine_types::{ resource::Resource, substate::{SubstateDiff, SubstateId}, }; -use tari_ootle_common_types::{optional::Optional, shard::Shard, StateVersion, VersionedSubstateIdRef}; +use tari_ootle_common_types::{optional::Optional, shard::Shard, Epoch, StateVersion, VersionedSubstateIdRef}; use tari_ootle_wallet_sdk::{ models::{ AccountUpdate, @@ -661,6 +661,7 @@ impl WalletStoreWriter for WriteTransaction<'_> { owner_key_id: Option, owner_public_key: &RistrettoPublicKeyBytes, associated_stealth_resources: &HashSet, + birthday_epoch: Epoch, is_confirmed_on_chain: bool, is_default: bool, ) -> Result<(), WalletStorageError> { @@ -681,6 +682,7 @@ impl WalletStoreWriter for WriteTransaction<'_> { accounts::owner_key_id.eq(owner_key_id.as_ref().map(serialize_json).transpose()?), accounts::owner_public_key.eq(serialize_hex(owner_public_key)), accounts::stealth_resources.eq(serialize_json(&associated_stealth_resources)?), + accounts::birthday_epoch.eq(birthday_epoch.as_u64() as i64), accounts::is_confirmed_on_chain.eq(is_confirmed_on_chain), accounts::is_default.eq(is_default), )) diff --git a/crates/wallet/storage_sqlite/tests/accounts.rs b/crates/wallet/storage_sqlite/tests/accounts.rs index 4c99ac5b93..ad2b217ccc 100644 --- a/crates/wallet/storage_sqlite/tests/accounts.rs +++ b/crates/wallet/storage_sqlite/tests/accounts.rs @@ -3,6 +3,7 @@ use std::str::FromStr; +use tari_ootle_common_types::Epoch; use tari_ootle_wallet_sdk::{ models::{AccountUpdate, KeyId}, storage::{CommittableStore, WalletStoreReader, WalletStoreWriter, WriteableWalletStore}, @@ -25,6 +26,7 @@ fn update_account() { Some(KeyId::derived(0)), &RistrettoPublicKeyBytes::default(), &Default::default(), + Epoch::zero(), false, false, ) diff --git a/utilities/tariswap_test_bench/src/accounts.rs b/utilities/tariswap_test_bench/src/accounts.rs index fee4f27f97..c6007af405 100644 --- a/utilities/tariswap_test_bench/src/accounts.rs +++ b/utilities/tariswap_test_bench/src/accounts.rs @@ -9,7 +9,7 @@ use tari_engine_types::{ indexed_value::IndexedWellKnownTypes, ToByteType, }; -use tari_ootle_common_types::SubstateRequirement; +use tari_ootle_common_types::{Epoch, SubstateRequirement}; use tari_ootle_wallet_sdk::models::{Account, KeyBranch, KeyId}; use tari_template_builtin::ACCOUNT_TEMPLATE_ADDRESS; use tari_template_lib::{ @@ -62,9 +62,15 @@ impl Runner { .find(|vault_id| *vault_id != XTR_FAUCET_VAULT_ADDRESS) .unwrap(); - self.sdk - .accounts_api() - .add_account(None, &account, KeyId::derived(0), KeyId::derived(0), true, true)?; + self.sdk.accounts_api().add_account( + None, + &account, + KeyId::derived(0), + KeyId::derived(0), + Epoch::zero(), + true, + true, + )?; self.sdk .accounts_api() .add_vault(account, vault, XTR, ResourceType::Stealth, Some("XTR".to_string()), 6)?; @@ -137,9 +143,15 @@ impl Runner { }) .expect("New account not found in diff"); - self.sdk - .accounts_api() - .add_account(None, &account_addr, owner.key_id, owner.key_id, true, false)?; + self.sdk.accounts_api().add_account( + None, + &account_addr, + owner.key_id, + owner.key_id, + Epoch::zero(), + true, + false, + )?; let account = self.sdk.accounts_api().get_account_by_address(&account_addr)?; accounts.push(account.account); } diff --git a/utilities/tariswap_test_bench/src/runner.rs b/utilities/tariswap_test_bench/src/runner.rs index 1b3e103197..78d34cc3ca 100644 --- a/utilities/tariswap_test_bench/src/runner.rs +++ b/utilities/tariswap_test_bench/src/runner.rs @@ -7,7 +7,7 @@ use log::info; use tari_crypto::tari_utilities::SafePassword; use tari_engine_types::commit_result::FinalizeResult; use tari_ootle_common_types::Network; -use tari_ootle_wallet_sdk::{cipher_seed::CipherSeedRestore, WalletSdk as Sdk, WalletSdkConfig}; +use tari_ootle_wallet_sdk::{cipher_seed::CipherSeedRestore, models::EpochBirthday, WalletSdk as Sdk, WalletSdkConfig}; use tari_ootle_wallet_sdk_services::indexer_rest_api::IndexerRestApiNetworkInterface; use tari_ootle_wallet_storage_sqlite::SqliteWalletStore; use tari_transaction::{Transaction, TransactionBuilder, TransactionId}; @@ -119,7 +119,7 @@ fn initialize_wallet_sdk>(db_path: P, indexer_url: Url) -> Result override_keyring_password: Some(SafePassword::from_str("N3Va g0nn4 gu355").unwrap()), }; let indexer = IndexerRestApiNetworkInterface::new(indexer_url); - let mut sdk = WalletSdk::initialize(store, indexer, sdk_config)?; + let mut sdk = WalletSdk::initialize(store, indexer, sdk_config, EpochBirthday::far_future())?; sdk.initialize_cipher_seed(CipherSeedRestore::CreateNewIfRequired)?; Ok(sdk) }