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

Handle failed transactions/blocks #2120

Merged
merged 19 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
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> {
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved
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
21 changes: 18 additions & 3 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,9 +71,20 @@ where
self.emit(WalletEvent::TransactionProgress(TransactionProgressEvent::Broadcasting))
.await;

log::debug!("submitting block {}", block.id(&protocol_parameters));
for attempt in 1..MAX_POST_BLOCK_ATTEMPTS {
if let Ok(block_id) = self.client().post_block(&block).await {
log::debug!("submitted block {}", block_id);
return Ok(block_id);
}
tokio::time::sleep(std::time::Duration::from_secs(attempt)).await;
}

log::debug!("submitting block {block:?}");

let block_id = self.client().post_block(&block).await?;
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved

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

Ok(block_id)
}
Expand Down
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
90 changes: 89 additions & 1 deletion sdk/tests/client/transaction_builder/basic_outputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,25 @@

use std::str::FromStr;

use crypto::keys::bip44::Bip44;
use iota_sdk::{
client::api::transaction_builder::{Requirement, TransactionBuilder, TransactionBuilderError},
client::{
api::{
transaction_builder::{Requirement, TransactionBuilder, TransactionBuilderError},
GetAddressesOptions,
},
constants::SHIMMER_COIN_TYPE,
secret::{SecretManage, SecretManager},
Client,
},
types::block::{
address::{Address, AddressCapabilities, MultiAddress, RestrictedAddress, WeightedAddress},
core::basic::StrongParents,
mana::ManaAllotment,
output::{unlock_condition::AddressUnlockCondition, AccountId, BasicOutputBuilder, NftId},
payload::{Payload, SignedTransactionPayload},
protocol::iota_mainnet_protocol_parameters,
BlockBody, BlockId,
},
};
use pretty_assertions::assert_eq;
Expand Down Expand Up @@ -2596,3 +2608,79 @@ fn automatic_allotment_provided_in_and_output() {
);
assert_eq!(selected.transaction.outputs()[0].mana(), 1);
}

#[tokio::test]
async fn basic_input_automatic_allotment() {
let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic().unwrap()).unwrap();

let ed25519_address = secret_manager
.generate_ed25519_addresses(GetAddressesOptions::default().with_range(0..1))
.await
.unwrap()[0]
.clone()
.into_inner();

let protocol_parameters = iota_mainnet_protocol_parameters().clone();
let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap();

let reference_mana_cost = 1;

let inputs = build_inputs(
[(
Basic {
amount: 10_000_000,
mana: 10_000,
address: ed25519_address.clone(),
native_token: None,
sender: None,
sdruc: None,
timelock: None,
expiration: None,
},
Some(Bip44::new(SHIMMER_COIN_TYPE)),
)],
Some(SLOT_INDEX),
);

let outputs = vec![
BasicOutputBuilder::new_with_amount(1_000_000)
.add_unlock_condition(AddressUnlockCondition::new(ed25519_address.clone()))
.finish_output()
.unwrap(),
];

let selected = TransactionBuilder::new(
inputs.clone(),
outputs.clone(),
[ed25519_address],
SLOT_INDEX,
SLOT_COMMITMENT_ID,
protocol_parameters.clone(),
)
.with_min_mana_allotment(account_id_1, reference_mana_cost)
.finish()
.unwrap();

let unlocks = secret_manager
.transaction_unlocks(&selected, &protocol_parameters)
.await
.unwrap();

let signed_transaction_payload = SignedTransactionPayload::new(selected.transaction.clone(), unlocks).unwrap();

let basic_block_body = BlockBody::build_basic(
StrongParents::from_vec(vec![BlockId::new([0; 36])]).unwrap(),
(protocol_parameters.work_score_parameters(), reference_mana_cost),
)
.with_payload(Payload::from(signed_transaction_payload))
.finish()
.unwrap();

assert!(unsorted_eq(&selected.inputs_data, &inputs));
assert_eq!(selected.transaction.outputs().len(), 2);
assert_eq!(selected.transaction.allotments().len(), 1);
assert_eq!(
selected.transaction.allotments().first().unwrap().mana(),
basic_block_body.max_burned_mana(),
);
}
Loading