Skip to content

Commit

Permalink
Handle failed transactions/blocks (#2120)
Browse files Browse the repository at this point in the history
* Handle BlockState::Failed

* Correctly set the block id, try multiple times to post a block

* Filter issuer accounts by network id, add block id debug log

* Add debug logs and test

* Move test

* Cleanup

* Bring back send_amount_from_block_issuer_account_with_generated_mana test

* Remove the duplicated code

---------

Co-authored-by: Thibault Martinez <[email protected]>
  • Loading branch information
Thoralf-M and thibault-martinez authored Mar 28, 2024
1 parent 571a598 commit 001588e
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 74 deletions.
5 changes: 4 additions & 1 deletion sdk/src/wallet/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,12 @@ impl WalletLedger {
}

// Returns the first possible unexpired block issuer Account id, which can be an implicit account.
pub fn first_block_issuer_account_id(&self, current_slot: SlotIndex) -> Option<AccountId> {
pub fn first_block_issuer_account_id(&self, current_slot: SlotIndex, network_id: u64) -> Option<AccountId> {
self.accounts()
.find_map(|o| {
if o.network_id != network_id {
return None;
}
let account = o.output.as_account();
account.features().block_issuer().and_then(|block_issuer| {
if block_issuer.expiry_slot() > current_slot {
Expand Down
27 changes: 21 additions & 6 deletions sdk/src/wallet/operations/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use crate::{
wallet::{Wallet, WalletError},
};

const MAX_POST_BLOCK_ATTEMPTS: u64 = 3;

impl<S: 'static + SecretManage> Wallet<S>
where
WalletError: From<S::Error>,
Expand All @@ -24,22 +26,24 @@ where
allow_negative_bic: bool,
) -> Result<BlockId, WalletError> {
log::debug!("submit_basic_block");
let protocol_parameters = self.client().get_protocol_parameters().await?;

// If an issuer ID is provided, use it; otherwise, use the first available account or implicit account.
let issuer_id = match issuer_id.into() {
Some(id) => id,
None => {
let current_slot = self.client().get_slot_index().await?;

self.ledger()
.await
.first_block_issuer_account_id(current_slot)
.first_block_issuer_account_id(current_slot, protocol_parameters.network_id())
.ok_or(WalletError::AccountNotFound)?
}
};

let unsigned_block = self.client().build_basic_block(issuer_id, payload).await?;

if !allow_negative_bic {
let protocol_parameters = self.client().get_protocol_parameters().await?;
let work_score = protocol_parameters.work_score(unsigned_block.body.as_basic());
let congestion = self.client().get_account_congestion(&issuer_id, work_score).await?;
if (congestion.reference_mana_cost * work_score as u64) as i128 > congestion.block_issuance_credits {
Expand Down Expand Up @@ -67,10 +71,21 @@ where
self.emit(WalletEvent::TransactionProgress(TransactionProgressEvent::Broadcasting))
.await;

let block_id = self.client().post_block(&block).await?;
log::debug!("submitting block {}", block.id(&protocol_parameters));
log::debug!("submitting block {block:?}");

log::debug!("submitted block {}", block_id);

Ok(block_id)
let mut attempt = 1;
loop {
match self.client().post_block(&block).await {
Ok(block_id) => break Ok(block_id),
Err(err) => {
if attempt >= MAX_POST_BLOCK_ATTEMPTS {
return Err(err.into());
}
}
}
tokio::time::sleep(std::time::Duration::from_secs(attempt)).await;
attempt += 1;
}
}
}
73 changes: 43 additions & 30 deletions sdk/src/wallet/operations/syncing/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,20 @@ where
metadata.earliest_attachment_slot
);
confirmed_unknown_output = true;

let mut block_id = transaction.block_id;
if transaction.block_id.is_none() {
if let Ok(metadata) = self
.client()
.get_included_block_metadata(&transaction.payload.transaction().id())
.await
{
block_id.replace(metadata.block_id);
}
}
updated_transaction_and_outputs(
transaction,
// Some(metadata.block_id),
None,
block_id,
InclusionState::Confirmed,
&mut updated_transactions,
&mut spent_output_ids,
Expand All @@ -156,9 +166,20 @@ where
);
} else {
log::debug!("[SYNC] conflicting transaction {transaction_id}");

let mut block_id = transaction.block_id;
if transaction.block_id.is_none() {
if let Ok(metadata) = self
.client()
.get_included_block_metadata(&transaction.payload.transaction().id())
.await
{
block_id.replace(metadata.block_id);
}
}
updated_transaction_and_outputs(
transaction,
None,
block_id,
InclusionState::Conflicting,
&mut updated_transactions,
&mut spent_output_ids,
Expand All @@ -168,14 +189,6 @@ where
// Do nothing, just need to wait a bit more
TransactionState::Pending => {}
}
// else if input_got_spent {
// process_transaction_with_unknown_state(
// &*self.ledger().await,
// transaction,
// &mut updated_transactions,
// &mut output_ids_to_unlock,
// )?;
// }
}
Err(ClientError::Node(crate::client::node_api::error::Error::NotFound(_))) => {
if input_got_spent {
Expand All @@ -185,28 +198,26 @@ where
&mut updated_transactions,
&mut output_ids_to_unlock,
)?;
} else {
log::debug!(
"[SYNC] setting transaction {transaction_id} without block as conflicting so inputs get available again"
);
for input in transaction.payload.transaction().inputs() {
let Input::Utxo(input) = input;
output_ids_to_unlock.push(*input.output_id());
}
updated_transaction_and_outputs(
transaction,
None,
// No block with this transaction, set it as conflicting so the inputs get available again
InclusionState::Conflicting,
&mut updated_transactions,
&mut spent_output_ids,
);
}
}
Err(e) => return Err(e.into()),
}

// else if input_got_spent {
// process_transaction_with_unknown_state(
// &*self.ledger().await,
// transaction,
// &mut updated_transactions,
// &mut output_ids_to_unlock,
// )?;
// }
// else {
// // Reissue if there was no block id yet, because then we also didn't burn any mana
// log::debug!("[SYNC] reissue transaction {}", transaction.transaction_id);
// let reissued_block = self
// .submit_signed_transaction(transaction.payload.clone(), None)
// .await?;
// transaction.block_id.replace(reissued_block);
// updated_transactions.push(transaction);
// }
}

// updates account with balances, output ids, outputs
Expand All @@ -225,7 +236,9 @@ fn updated_transaction_and_outputs(
updated_transactions: &mut Vec<TransactionWithMetadata>,
spent_output_ids: &mut Vec<OutputId>,
) {
transaction.block_id = block_id;
if block_id.is_some() {
transaction.block_id = block_id;
}
transaction.inclusion_state = inclusion_state;
// get spent inputs
for input in transaction.payload.transaction().inputs() {
Expand Down
12 changes: 7 additions & 5 deletions sdk/src/wallet/operations/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ pub(crate) mod high_level;
pub(crate) mod prepare_output;
mod send_outputs;
mod sign_transaction;
pub(crate) mod submit_transaction;

#[cfg(feature = "storage")]
use crate::wallet::core::WalletLedgerDto;
Expand All @@ -17,7 +16,10 @@ use crate::{
secret::SecretManage,
ClientError,
},
types::block::{output::OutputWithMetadata, payload::signed_transaction::SignedTransactionPayload},
types::block::{
output::OutputWithMetadata,
payload::{signed_transaction::SignedTransactionPayload, Payload},
},
wallet::{
types::{InclusionState, TransactionWithMetadata},
Wallet, WalletError,
Expand Down Expand Up @@ -85,11 +87,11 @@ where
}
drop(wallet_ledger);

// Ignore errors from sending, we will try to send it again during [`sync_pending_transactions`]
let block_id = match self
.submit_signed_transaction(
signed_transaction_data.payload.clone(),
.submit_basic_block(
Some(Payload::from(signed_transaction_data.payload.clone())),
options.as_ref().and_then(|options| options.issuer_id),
true,
)
.await
{
Expand Down
26 changes: 0 additions & 26 deletions sdk/src/wallet/operations/transaction/submit_transaction.rs

This file was deleted.

24 changes: 19 additions & 5 deletions sdk/tests/client/transaction_builder/account_outputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,37 @@

use std::str::FromStr;

use crypto::keys::bip44::Bip44;
use iota_sdk::{
client::{
api::transaction_builder::{Burn, Requirement, TransactionBuilder, TransactionBuilderError, Transitions},
secret::types::InputSigningData,
api::{
transaction_builder::{Burn, Requirement, TransactionBuilder, TransactionBuilderError, Transitions},
GetAddressesOptions,
},
constants::SHIMMER_COIN_TYPE,
secret::{mnemonic::MnemonicSecretManager, types::InputSigningData, SecretManage, SecretManager},
Client,
},
types::block::{
address::{Address, ImplicitAccountCreationAddress},
core::{
basic::{MaxBurnedManaAmount, StrongParents},
BlockHeader,
},
mana::ManaAllotment,
output::{
feature::{BlockIssuerFeature, BlockIssuerKeys, Ed25519PublicKeyHashBlockIssuerKey},
unlock_condition::AddressUnlockCondition,
AccountId, AccountOutputBuilder, BasicOutputBuilder, Output,
},
payload::signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag},
protocol::iota_mainnet_protocol_parameters,
payload::{
signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag},
Payload, SignedTransactionPayload,
},
protocol::{iota_mainnet_protocol_parameters, WorkScore},
rand::output::{rand_output_id_with_slot_index, rand_output_metadata_with_id},
slot::SlotIndex,
slot::{SlotCommitmentId, SlotIndex},
BlockBody, BlockId, UnsignedBlock,
},
};
use pretty_assertions::{assert_eq, assert_ne};
Expand Down
Loading

0 comments on commit 001588e

Please sign in to comment.