Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finish early strategy #3

Merged
merged 4 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


> [!IMPORTANT]
> This TEMPRORARY repo is intended for **internal use only** and will later be merged into [Sargon]https://github.com/radixdlt/sargon.
> This TEMPORARY repo is intended for **internal use only** and will later be merged into [Sargon]https://github.com/radixdlt/sargon.
> Interfaces will be changing regularly, and we do not recommend other developers integrate the library or align with these standards.

MFA work.
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![allow(internal_features)]
#![feature(core_intrinsics)]
#![feature(iter_repeat_n)]
#![feature(async_closure)]

mod derivation;
mod signing;
Expand Down
4 changes: 4 additions & 0 deletions src/signing/collector/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
mod signatures_collecting_continuation;
mod signatures_collector;
mod signatures_collector_dependencies;
mod signatures_collector_preprocessor;
mod signatures_collector_state;
mod signing_finish_early_strategy;

pub use signatures_collecting_continuation::*;
pub use signatures_collector::*;
pub use signatures_collector_preprocessor::*;
pub use signing_finish_early_strategy::*;
14 changes: 14 additions & 0 deletions src/signing/collector/signatures_collecting_continuation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// Whether to continue collecting signatures or finish early.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SignaturesCollectingContinuation {
/// It is meaningless to continue collecting signatures, either since either
/// all transactions are valid, and the collector is configured to finish early
/// in that case, or some transaction is invalid and the collector is configured
/// finish early in that case.
FinishEarly,

/// We should continue collecting signatures, either since the collector is
/// configured to not finish early, even though we can, or since we cannot
/// finish early since not enough factor sources have been signed with.
Continue,
}
154 changes: 123 additions & 31 deletions src/signing/collector/signatures_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
/// Used by our tests. But Sargon will typically wanna use `SignaturesCollector::new` and passing
/// it a
pub(crate) fn with(
finish_early_when_all_transactions_are_valid: bool,
finish_early_strategy: SigningFinishEarlyStrategy,
all_factor_sources_in_profile: IndexSet<HDFactorSource>,
transactions: IndexSet<TXToSign>,
interactors: Arc<dyn SignatureCollectingInteractors>,
Expand All @@ -36,11 +36,8 @@
let preprocessor = SignaturesCollectorPreprocessor::new(transactions);
let (petitions, factors) = preprocessor.preprocess(all_factor_sources_in_profile);

let dependencies = SignaturesCollectorDependencies::new(
finish_early_when_all_transactions_are_valid,
interactors,
factors,
);
let dependencies =
SignaturesCollectorDependencies::new(finish_early_strategy, interactors, factors);
let state = SignaturesCollectorState::new(petitions);

Self {
Expand All @@ -50,7 +47,7 @@
}

pub fn with_signers_extraction<F>(
finish_early_when_all_transactions_are_valid: bool,
finish_early_strategy: SigningFinishEarlyStrategy,
all_factor_sources_in_profile: IndexSet<HDFactorSource>,
transactions: IndexSet<TransactionIntent>,
interactors: Arc<dyn SignatureCollectingInteractors>,
Expand All @@ -65,7 +62,7 @@
.collect::<Result<IndexSet<TXToSign>>>()?;

let collector = Self::with(
finish_early_when_all_transactions_are_valid,
finish_early_strategy,

Check warning on line 65 in src/signing/collector/signatures_collector.rs

View check run for this annotation

Codecov / codecov/patch

src/signing/collector/signatures_collector.rs#L65

Added line #L65 was not covered by tests
all_factor_sources_in_profile,
transactions,
interactors,
Expand All @@ -75,13 +72,13 @@
}

pub fn new(
finish_early_when_all_transactions_are_valid: bool,
finish_early_strategy: SigningFinishEarlyStrategy,
transactions: IndexSet<TransactionIntent>,
interactors: Arc<dyn SignatureCollectingInteractors>,
profile: &Profile,
) -> Result<Self> {
Self::with_signers_extraction(
finish_early_when_all_transactions_are_valid,
finish_early_strategy,
profile.factor_sources.clone(),
transactions,
interactors,
Expand All @@ -102,18 +99,49 @@
}
}

use SignaturesCollectingContinuation::*;

// === PRIVATE ===
impl SignaturesCollector {
/// If all transactions already would fail, or if all transactions already are done, then
/// no point in continuing.
/// Returning `Continue` means that we should continue collecting signatures.
///
/// `Ok(true)` means "continue", `Ok(false)` means "stop, we are done". `Err(_)` means "stop, we have failed".
fn continue_if_necessary(&self) -> Result<bool> {
self.state
.borrow()
.petitions
.borrow()
.continue_if_necessary()
/// Returning `FinishEarly` if it is meaningless to continue collecting signatures,
/// either since all transactions are valid and this collector is configured
/// to finish early in that case, or if some transaction is invalid and this
/// collector is configured to finish early in that case.
///
/// N.B. this method does not concern itself with how many or which
/// factor sources are left to sign with, that is handled by the main loop,
/// i.e. this might return `false` even though there is not factor sources
/// left to sign with.
fn continuation(&self) -> SignaturesCollectingContinuation {
let finish_early_strategy = self.dependencies.finish_early_strategy;
let when_all_transactions_are_valid =
finish_early_strategy.when_all_transactions_are_valid.0;
let when_some_transaction_is_invalid =
finish_early_strategy.when_some_transaction_is_invalid.0;

let petitions_status = self.state.borrow().petitions.borrow().status();

if petitions_status.are_all_valid() {
if when_all_transactions_are_valid == FinishEarly {
info!("All valid && should finish early => finish early");
return FinishEarly;
} else {
debug!(
"All valid, BUT the collector is configured to NOT finish early => Continue"
);
}
} else if petitions_status.is_some_invalid() {
if when_some_transaction_is_invalid == FinishEarly {
info!("Some invalid && should finish early => finish early");
return FinishEarly;
} else {
debug!("Some transactions invalid, BUT the collector is configured to NOT finish early in case of failures => Continue");
}
}

Continue
}

async fn use_factor_sources(&self, factor_sources_of_kind: &FactorSourcesOfKind) -> Result<()> {
Expand Down Expand Up @@ -172,7 +200,7 @@
// Report the results back to the collector
self.process_batch_response(response);

if !self.continue_if_necessary()? {
if self.continuation() == FinishEarly {
break;
}
}
Expand All @@ -195,13 +223,8 @@
async fn sign_with_factors(&self) -> Result<()> {
let factors_of_kind = self.dependencies.factors_of_kind.clone();
for factor_sources_of_kind in factors_of_kind.into_iter() {
if self
.dependencies
.finish_early_when_all_transactions_are_valid
&& !self.continue_if_necessary()?
{
info!("Finished early");
break; // finished early, we have fulfilled signing requirements of all transactions
if self.continuation() == FinishEarly {
break;
}
info!(
"Use(?) #{:?} factors of kind: {:?}",
Expand Down Expand Up @@ -315,7 +338,7 @@
#[test]
fn invalid_profile_unknown_account() {
let res = SignaturesCollector::new(
true,
SigningFinishEarlyStrategy::default(),
IndexSet::from_iter([TransactionIntent::new([Account::a0().entity_address()], [])]),
Arc::new(TestSignatureCollectingInteractors::new(
SimulatedUser::prudent_no_fail(),
Expand All @@ -328,7 +351,7 @@
#[test]
fn invalid_profile_unknown_persona() {
let res = SignaturesCollector::new(
true,
SigningFinishEarlyStrategy::default(),
IndexSet::from_iter([TransactionIntent::new([], [Persona::p0().entity_address()])]),
Arc::new(TestSignatureCollectingInteractors::new(
SimulatedUser::prudent_no_fail(),
Expand All @@ -343,7 +366,7 @@
let factors_sources = HDFactorSource::all();
let persona = Persona::p0();
let collector = SignaturesCollector::new(
true,
SigningFinishEarlyStrategy::default(),
IndexSet::from_iter([TransactionIntent::new([], [persona.entity_address()])]),
Arc::new(TestSignatureCollectingInteractors::new(
SimulatedUser::prudent_no_fail(),
Expand All @@ -355,6 +378,75 @@
assert!(outcome.successful())
}

#[actix_rt::test]
async fn continues_even_with_failed_tx_when_configured_to() {
let factor_sources = &HDFactorSource::all();
let a0 = &Account::a0();
let a1 = &Account::a1();

let t0 = TransactionIntent::address_of([a1], []);
let t1 = TransactionIntent::address_of([a0], []);

let profile = Profile::new(factor_sources.clone(), [a0, a1], []);

let collector = SignaturesCollector::new(
SigningFinishEarlyStrategy::new(
WhenAllTransactionsAreValid(FinishEarly),
WhenSomeTransactionIsInvalid(Continue),
),
IndexSet::<TransactionIntent>::from_iter([t0.clone(), t1.clone()]),
Arc::new(TestSignatureCollectingInteractors::new(
SimulatedUser::prudent_with_failures(SimulatedFailures::with_simulated_failures([
FactorSourceIDFromHash::fs1(),
])),
)),
&profile,
)
.unwrap();

let outcome = collector.collect_signatures().await;
assert!(!outcome.successful());
assert_eq!(outcome.failed_transactions().len(), 1);
assert_eq!(outcome.successful_transactions().len(), 1);
}

#[actix_rt::test]
async fn continues_even_when_all_valid_if_configured_to() {
sensible_env_logger::safe_init!();
let test = async move |when_all_valid: WhenAllTransactionsAreValid,
expected_sig_count: usize| {
let factor_sources = &HDFactorSource::all();
let a5 = &Account::a5();

let t0 = TransactionIntent::address_of([a5], []);

let profile = Profile::new(factor_sources.clone(), [a5], []);

let collector = SignaturesCollector::new(
SigningFinishEarlyStrategy::new(
when_all_valid,
WhenSomeTransactionIsInvalid::default(),
),
IndexSet::<TransactionIntent>::from_iter([t0.clone()]),
Arc::new(TestSignatureCollectingInteractors::new(
SimulatedUser::prudent_no_fail(),
)),
&profile,
)
.unwrap();

let outcome = collector.collect_signatures().await;
assert!(outcome.successful());
assert_eq!(
outcome.signatures_of_successful_transactions().len(),
expected_sig_count
);
};

test(WhenAllTransactionsAreValid(FinishEarly), 1).await;
test(WhenAllTransactionsAreValid(Continue), 2).await;
}

#[test]
fn test_profile() {
let factor_sources = &HDFactorSource::all();
Expand All @@ -376,7 +468,7 @@
let profile = Profile::new(factor_sources.clone(), [a0, a1, a2, a6], [p0, p1, p2, p6]);

let collector = SignaturesCollector::new(
true,
SigningFinishEarlyStrategy::default(),
IndexSet::<TransactionIntent>::from_iter([
t0.clone(),
t1.clone(),
Expand Down
6 changes: 3 additions & 3 deletions src/signing/collector/signatures_collector_dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub(super) struct SignaturesCollectorDependencies {
/// If `true` we stop collecting signatures as soon as all transactions are
/// valid. This is typically always set to `true`, but can be useful for
/// tests to set to `false` to see how the system behaves.
pub(super) finish_early_when_all_transactions_are_valid: bool,
pub(super) finish_early_strategy: SigningFinishEarlyStrategy,

/// A collection of "interactors" used to sign with factor sources.
pub(super) interactors: Arc<dyn SignatureCollectingInteractors>,
Expand All @@ -23,12 +23,12 @@ pub(super) struct SignaturesCollectorDependencies {

impl SignaturesCollectorDependencies {
pub fn new(
finish_early_when_all_transactions_are_valid: bool,
finish_early_strategy: SigningFinishEarlyStrategy,
interactors: Arc<dyn SignatureCollectingInteractors>,
factors_of_kind: IndexSet<FactorSourcesOfKind>,
) -> Self {
Self {
finish_early_when_all_transactions_are_valid,
finish_early_strategy,
interactors,
factors_of_kind,
}
Expand Down
37 changes: 37 additions & 0 deletions src/signing/collector/signing_finish_early_strategy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use crate::prelude::*;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WhenAllTransactionsAreValid(pub SignaturesCollectingContinuation);

impl Default for WhenAllTransactionsAreValid {
fn default() -> Self {
Self(SignaturesCollectingContinuation::FinishEarly)
}
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WhenSomeTransactionIsInvalid(pub SignaturesCollectingContinuation);

impl Default for WhenSomeTransactionIsInvalid {
fn default() -> Self {
Self(SignaturesCollectingContinuation::FinishEarly)
}
}

/// Strategy to use for finishing early, i.e. stop collecting more signatures
#[derive(Clone, Default, Copy, Debug, PartialEq, Eq)]
pub struct SigningFinishEarlyStrategy {
pub when_all_transactions_are_valid: WhenAllTransactionsAreValid,
pub when_some_transaction_is_invalid: WhenSomeTransactionIsInvalid,
}
impl SigningFinishEarlyStrategy {
pub fn new(
when_all_transactions_are_valid: WhenAllTransactionsAreValid,
when_some_transaction_is_invalid: WhenSomeTransactionIsInvalid,
) -> Self {
Self {
when_all_transactions_are_valid,
when_some_transaction_is_invalid,
}
}
}
11 changes: 0 additions & 11 deletions src/signing/petition_types/petition_entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,17 +183,6 @@ impl PetitionEntity {
}
}

/// `Ok(true)` means "continue", `Ok(false)` means "stop, we are done". `Err(_)` means "stop, we have failed".
pub(super) fn continue_if_necessary(&self) -> Result<bool> {
match self.status() {
PetitionFactorsStatus::InProgress => Ok(true),
PetitionFactorsStatus::Finished(PetitionFactorsStatusFinished::Fail) => {
Err(CommonError::Failure)
}
PetitionFactorsStatus::Finished(PetitionFactorsStatusFinished::Success) => Ok(false),
}
}

pub fn status_if_skipped_factors(
&self,
factor_source_ids: IndexSet<FactorSourceIDFromHash>,
Expand Down
Loading
Loading