Skip to content

Commit

Permalink
Sargon OS Boot fixes (#212)
Browse files Browse the repository at this point in the history
* Replace kotlin base64 with android base 64 and mock it in unit tests

* Make profileStateChanges a shared flow

* Remove planting of debug tree

* Print errors with timber in example app too

* Fix example profile states

* Remove checks before asking for biometrics

* Suspend request for biometrics until activity is at least started

* Report secure storage access error

* Make read and write methods public

* Make encrypt and decrypt methods public

* Implement function to remove a list of keyspecs

* Make generateSecretKey public for now

* Add error kind for biometrics failure

* Export method to derive profile based on bdfs and some accounts

* Emit derived profile in profile state change

* Store profile state into android state change driver

* Make reset keyspec method to regenerate a new key when fail

* Included SargonOsManager into sargon android

* Make drivers explicit

* Replace with new_wallet_with_derived_bdfs

* Change doc

* Allow set profile to set one profile when none exists

* Change iOS test

* Fix android test

* Fix set_profile to notify ProfileStateChange client about the latest mutated profile

* Fix content hint to report correct total amount of personas

* Create initial profile with no networks

* Delete ephemeral profile when booting

* Return error when network does not yet exist

* Profile snapshot id payload (#214)

* wip

* wip

* fix test

* wip

* fix ios tests

* test

* update

* wip

* Bump cargo version

* Profile safe concurrent access (#225)

* wip

* fix

* fix

* wip

* wip

* wip

---------

Co-authored-by: Ghenadie <[email protected]>
  • Loading branch information
micbakos-rdx and GhenadieVP authored Sep 30, 2024
1 parent 424a98f commit 817779d
Show file tree
Hide file tree
Showing 60 changed files with 1,461 additions and 402 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apple/Sources/Sargon/SargonOS/SargonOS+Static+Shared.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ extension SargonOS {
if !isEmulatingFreshInstall, _shared != nil {
throw SargonOSAlreadyBooted()
}
let shared = try await SargonOS.boot(bios: bios)
let shared = await SargonOS.boot(bios: bios)
Self._shared = shared
return shared
}
Expand Down
6 changes: 3 additions & 3 deletions apple/Sources/Sargon/SargonOS/TestOS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ extension TestOS: SargonOSProtocol {}

// MARK: Private
extension TestOS {
private func nextAccountName() throws -> DisplayName {
let index = try accountsForDisplayOnCurrentNetwork.count
return DisplayName(value: "Unnamed \(index)")
private func nextAccountName() -> DisplayName {
let index = (try? accountsForDisplayOnCurrentNetwork.count) ?? 0
return DisplayName(value: "Unnamed \(index)")
}
}

Expand Down
8 changes: 4 additions & 4 deletions apple/Sources/Sargon/Util/EventPublisher.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import AsyncExtensions

public final actor EventPublisher<Element: Sendable> {
public typealias Subject = AsyncPassthroughSubject<Element>
public typealias Stream = AsyncThrowingPassthroughSubject<Element, any Error>
public typealias Subject = AsyncReplaySubject<Element>
public typealias Stream = AsyncThrowingReplaySubject<Element, any Error>

let stream = Stream()
let subject = Subject()
let stream = Stream(bufferSize: 1)
let subject = Subject(bufferSize: 1)

public func eventStream() -> AsyncMulticastSequence<Subject, Stream> {
subject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ final class DriversTests: TestCase {
}

func test_bios_insecure() async throws {
let _ = try await SargonOS.boot(bios: BIOS.insecure())
let _ = await SargonOS.boot(bios: BIOS.insecure())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class EventBusDriverTests: DriverTest<EventBus> {
func test() async throws {
let sut = SUT()

let expectedEvents = Array<EventKind>([.booted, .profileSaved, .factorSourceUpdated, .accountAdded, .profileSaved])
let expectedEvents = Array<EventKind>([.booted, .profileSaved, .profileSaved, .factorSourceUpdated, .accountAdded, .profileSaved])
let task = Task {
var notifications = Set<EventNotification>()
for await notification in await sut.notifications().prefix(expectedEvents.count) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class InsecureStorageDriverTests: DriverTest<Insecure︕!TestOnly︕!Ephemer
func test() async throws {
let sut = SUT.init(keychainService: "test")
let data = Data.sampleAced
let key = SUT.Key.profileSnapshot
let key = SUT.Key.profileSnapshot(profileId: newProfileIdSample())
try await sut.saveData(key: key, data: data)
let loaded = try await sut.loadData(key: key)
XCTAssertEqual(loaded, data)
Expand Down
2 changes: 1 addition & 1 deletion apple/Tests/IntegrationTests/SargonOS/SargonOSTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ final class SargonOSTests: OSTest {
}

func test() async throws {
let _ = try await SUT.boot(
let _ = await SUT.boot(
bios: .init(
drivers: .test()
)
Expand Down
2 changes: 1 addition & 1 deletion crates/sargon/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sargon"
version = "1.1.23"
version = "1.1.24"
edition = "2021"
build = "build.rs"

Expand Down
20 changes: 16 additions & 4 deletions crates/sargon/src/core/error/common_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,22 +230,26 @@ pub enum CommonError {
#[error("Invalid DisplayName cannot be empty.")]
InvalidDisplayNameEmpty = 10062,

#[error("FREE")]
FREE = 10063,
#[error("Failed to access secure storage due to \"{error_message}\" for key {} ", key.identifier())]
SecureStorageAccessError {
key: SecureStorageKey,
error_kind: SecureStorageAccessErrorKind,
error_message: String,
} = 10063,

#[error("Invalid ISO8601 Time string: {bad_value}")]
InvalidISO8601String { bad_value: String } = 10064,

#[error("Unknown account.")]
UnknownAccount = 10065,

#[error("Failed to read from secure storage (Keychain).")]
#[error("Failed to read from secure storage.")]
SecureStorageReadError = 10066,

#[error("Failed to load DeviceFactorSource from secure storage")]
UnableToLoadDeviceFactorSourceFromSecureStorage = 10067,

#[error("Failed to write to secure storage (Keychain).")]
#[error("Failed to write to secure storage.")]
SecureStorageWriteError = 10068,

#[error("Failed Serialize value to JSON.")]
Expand Down Expand Up @@ -644,6 +648,14 @@ pub enum CommonError {
global_address_as_hex: String,
network_id: NetworkID,
} = 10181,

#[error(
"The provided entities do not derive from the given factor source"
)]
EntitiesNotDerivedByFactorSource = 10182,

#[error("The network {network_id} does not exist in profile")]
NoNetworkInProfile { network_id: NetworkID } = 10183,
}

#[uniffi::export]
Expand Down
1 change: 1 addition & 0 deletions crates/sargon/src/profile/logic/account/create_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ mod tests {
.factor_source,
HostId::sample(),
HostInfo::sample(),
None::<Accounts>,
);

let (_, accounts) = sut
Expand Down
22 changes: 12 additions & 10 deletions crates/sargon/src/profile/logic/account/query_accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@ use crate::prelude::*;
impl Profile {
/// Returns the non-hidden accounts on the current network, empty if no accounts
/// on the network
pub fn accounts_on_current_network(&self) -> Accounts {
self.current_network().accounts.non_hidden()
pub fn accounts_on_current_network(&self) -> Result<Accounts> {
self.current_network().map(|n| n.accounts.non_hidden())
}

/// Returns the non-hidden accounts on the current network as `AccountForDisplay`
pub fn accounts_for_display_on_current_network(
&self,
) -> AccountsForDisplay {
self.accounts_on_current_network()
.iter()
.map(AccountForDisplay::from)
.collect::<AccountsForDisplay>()
) -> Result<AccountsForDisplay> {
self.accounts_on_current_network().map(|accounts| {
accounts
.iter()
.map(AccountForDisplay::from)
.collect::<AccountsForDisplay>()
})
}

/// Looks up the account by account address, returns Err if the account is
Expand Down Expand Up @@ -44,7 +46,7 @@ mod tests {
fn test_accounts_on_current_network() {
let sut = SUT::sample();
assert_eq!(
sut.accounts_on_current_network(),
sut.accounts_on_current_network().unwrap(),
Accounts::sample_mainnet()
);
}
Expand All @@ -53,7 +55,7 @@ mod tests {
fn test_accounts_on_current_network_stokenet() {
let sut = SUT::sample_other();
assert_eq!(
sut.accounts_on_current_network(),
sut.accounts_on_current_network().unwrap(),
Accounts::just(Account::sample_stokenet_nadia()) // olivia is hidden
);
}
Expand All @@ -62,7 +64,7 @@ mod tests {
fn test_accounts_for_display_on_current_network() {
let sut = SUT::sample();
assert_eq!(
sut.accounts_for_display_on_current_network(),
sut.accounts_for_display_on_current_network().unwrap(),
Accounts::sample_mainnet()
.iter()
.map(AccountForDisplay::from)
Expand Down
11 changes: 7 additions & 4 deletions crates/sargon/src/profile/logic/gateway/current_gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ impl Profile {
/// The ProfileNetwork of the currently used Network dependent on the `current`
/// Gateway set in AppPreferences. This affects which Accounts users see in
/// "Home screen" in wallet apps.
pub fn current_network(&self) -> &ProfileNetwork {
self.networks
.get_id(self.current_network_id())
.expect("Should have current network")
pub fn current_network(&self) -> Result<&ProfileNetwork> {
let current_network_id = self.current_network_id();
self.networks.get_id(current_network_id).ok_or(
CommonError::NoNetworkInProfile {
network_id: current_network_id,
},
)
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/sargon/src/profile/v100/header/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod device_info_uniffi_fn;
mod header;
mod header_uniffi_fn;
mod profile_id;
mod profile_id_uniffi_fn;

pub use content_hint::*;
pub use device_id::*;
Expand All @@ -17,3 +18,4 @@ pub use device_info_uniffi_fn::*;
pub use header::*;
pub use header_uniffi_fn::*;
pub use profile_id::*;
pub use profile_id_uniffi_fn::*;
1 change: 0 additions & 1 deletion crates/sargon/src/profile/v100/header/profile_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use crate::prelude::*;
)]
#[serde(transparent)]
pub struct ProfileID(pub(crate) Uuid);
uniffi::custom_newtype!(ProfileID, Uuid);

impl FromStr for ProfileID {
type Err = CommonError;
Expand Down
37 changes: 37 additions & 0 deletions crates/sargon/src/profile/v100/header/profile_id_uniffi_fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use crate::prelude::*;

uniffi::custom_newtype!(ProfileID, Uuid);

#[uniffi::export]
pub fn new_profile_id_sample() -> ProfileID {
ProfileID::sample()
}

#[uniffi::export]
pub fn new_profile_id_sample_other() -> ProfileID {
ProfileID::sample_other()
}

#[cfg(test)]
mod uniffi_test {

use super::*;

#[allow(clippy::upper_case_acronyms)]
type SUT = ProfileID;

#[test]
fn hash_of_samples() {
assert_eq!(
HashSet::<SUT>::from_iter([
new_profile_id_sample(),
new_profile_id_sample_other(),
// duplicates should get removed
new_profile_id_sample(),
new_profile_id_sample_other(),
])
.len(),
2
);
}
}
29 changes: 29 additions & 0 deletions crates/sargon/src/profile/v100/networks/network/profile_network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,21 @@ impl ProfileNetwork {
ResourcePreferences::new(),
)
}

/// Instantiates a new `ProfileNetwork` from `network_id` and `accounts`, with all
/// the rest i.e. Personas, AuthorizedDapps all being empty.
pub fn new_with_accounts(
network_id: impl Into<NetworkID>,
accounts: impl Into<Accounts>,
) -> Self {
Self::new(
network_id,
accounts,
Personas::new(),
AuthorizedDapps::new(),
ResourcePreferences::new(),
)
}
}

impl ProfileNetwork {
Expand Down Expand Up @@ -310,6 +325,20 @@ mod tests {
);
}

#[test]
#[should_panic(
expected = "Discrepancy, found an AuthorizedDapp on other network than mainnet"
)]
fn panic_when_network_id_mismatch_between_accounts_when_new_() {
SUT::new(
NetworkID::Mainnet,
Accounts::sample_mainnet(),
Personas::sample_mainnet(),
AuthorizedDapps::just(AuthorizedDapp::sample_stokenet()),
ResourcePreferences::default(),
);
}

#[test]
fn json_roundtrip_sample_mainnet() {
let sut = SUT::sample_mainnet();
Expand Down
10 changes: 8 additions & 2 deletions crates/sargon/src/profile/v100/networks/profile_networks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ impl ProfileNetworks {
pub fn content_hint(&self) -> ContentHint {
let number_of_accounts =
self.iter().fold(0, |acc, x| acc + x.accounts.len());
ContentHint::with_counters(number_of_accounts, 0, self.len())
let number_of_personas =
self.iter().fold(0, |per, x| per + x.personas.len());
ContentHint::with_counters(
number_of_accounts,
number_of_personas,
self.len(),
)
}
}

Expand Down Expand Up @@ -218,7 +224,7 @@ mod tests {
fn content_hint() {
assert_eq!(
SUT::sample().content_hint(),
ContentHint::with_counters(4, 0, 2)
ContentHint::with_counters(4, 4, 2)
);
}

Expand Down
Loading

0 comments on commit 817779d

Please sign in to comment.