Skip to content

Commit

Permalink
feat: add retry mechanism (#1035)
Browse files Browse the repository at this point in the history
- Implemented a retry mechanism
- Added RetryConfig struct to specify retry behavior
- Introduced RetryableClient for convenient interaction with remote service, including automatic retries for network issues
- Separated transaction sending and response retrieval for improved code organization



Co-authored-by: Ahmed Sagdati <[email protected]>
Co-authored-by: MujkicA <[email protected]>
Co-authored-by: hal3e <[email protected]>
  • Loading branch information
4 people authored Sep 13, 2023
1 parent c145802 commit 337d0ea
Show file tree
Hide file tree
Showing 40 changed files with 1,137 additions and 270 deletions.
2 changes: 0 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ serde = { version = "1.0.183", default-features = false }
serde_json = "1.0.104"
serde_with = { version = "3.2.0", default-features = false }
sha2 = { version = "0.10.7", default-features = false }
strum = "0.25.0"
strum_macros = "0.25.2"
syn = "2.0.28"
tai64 = { version = "4.0.0", default-features = false }
tempfile = { version = "3.7.1", default-features = false }
Expand Down
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- [Running a short-lived Fuel node with the SDK](./connecting/short-lived.md)
- [Rocksdb](./connecting/rocksdb.md)
- [Querying the blockchain](./connecting/querying.md)
- [Retrying upon errors](./connecting/retrying.md)
- [Accounts](./accounts.md)
- [Managing wallets](./wallets/index.md)
- [Creating a wallet from a private key](./wallets/private-keys.md)
Expand Down
8 changes: 7 additions & 1 deletion docs/src/calling-contracts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,10 @@ Here's an example. Suppose your Sway contract has two ABI methods called `initia

The example above uses all the default configurations and performs a simple contract call.

Next, we'll see how we can further configure the many different parameters in a contract call
Furthermore, if you need to separate submission from value retrieval for any reason, you can do so as follows:

```rust,ignore
{{#include ../../../examples/contracts/src/lib.rs:submit_response_contract}}
```

Next, we'll see how we can further configure the many different parameters in a contract call.
6 changes: 6 additions & 0 deletions docs/src/calling-contracts/multicalls.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ Next, you provide the prepared calls to your `MultiContractCallHandler` and opti

> **Note:** any transaction parameters configured on separate contract calls are disregarded in favor of the parameters provided to `MultiContractCallHandler`.
Furthermore, if you need to separate submission from value retrieval for any reason, you can do so as follows:

```rust,ignore
{{#include ../../../examples/contracts/src/lib.rs:submit_response_multicontract}}
```

## Output values

To get the output values of the bundled calls, you need to provide explicit type annotations when saving the result of `call()` or `simulate()` to a variable:
Expand Down
34 changes: 34 additions & 0 deletions docs/src/connecting/retrying.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Retrying requests

The [`Provider`](https://docs.rs/fuels/0.47.0/fuels/accounts/provider/struct.Provider.html) can be configured to retry a request upon receiving a `io::Error`.

> Note: Currently all node errors are received as `io::Error`s. So, if configured, a retry will happen even if, for example, a transaction failed to verify.
We can configure the number of retry attempts and the retry strategy as detailed below.

## RetryConfig

The retry behavior can be altered by giving a custom `RetryConfig`. It allows for configuring the maximum number of attempts and the interval strategy used.

```rust, ignore
{{#include ../../../packages/fuels-accounts/src/provider/retry_util.rs:retry_config}}
```

```rust, ignore
{{#include ../../../examples/providers/src/lib.rs:configure_retry}}
```

## Interval strategy - Backoff

`Backoff` defines different strategies for managing intervals between retry attempts.
Each strategy allows you to customize the waiting time before a new attempt based on the number of attempts made.

### Variants

- `Linear(Duration)`: `Default` Increases the waiting time linearly with each attempt.
- `Exponential(Duration)`: Doubles the waiting time with each attempt.
- `Fixed(Duration)`: Uses a constant waiting time between attempts.

```rust, ignore
{{#include ../../../packages/fuels-accounts/src/provider/retry_util.rs:backoff}}
```
6 changes: 6 additions & 0 deletions docs/src/running-scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ You can run a script using its JSON-ABI and the path to its binary file. You can
{{#include ../../packages/fuels/tests/scripts.rs:script_with_arguments}}
````

Furthermore, if you need to separate submission from value retrieval for any reason, you can do so as follows:

```rust,ignore
{{#include ../../packages/fuels/tests/scripts.rs:submit_response_script}}
```

## Running scripts with transaction parameters

The method for passing transaction parameters is the same as [with contracts](./calling-contracts/tx-params.md). As a reminder, the workflow would look like this:
Expand Down
26 changes: 23 additions & 3 deletions examples/contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ mod tests {
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::{
client::FuelClient,
fuel_node::{Config, FuelService},
prelude::Provider,
};

// Run the fuel node.
Expand All @@ -23,8 +23,8 @@ mod tests {
.map_err(|err| error!(InfrastructureError, "{err}"))?;

// Create a client that will talk to the node created above.
let client = FuelClient::from(server.bound_address);
assert!(client.health().await?);
let client = Provider::from(server.bound_address).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
Expand Down Expand Up @@ -199,6 +199,18 @@ mod tests {
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract

// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;

let value = response.response().await?.value;

// ANCHOR_END: submit_response_contract
assert_eq!(42, value);

Ok(())
}

Expand Down Expand Up @@ -597,6 +609,14 @@ mod tests {
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);

// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract

assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);

Ok(())
}

Expand Down
6 changes: 3 additions & 3 deletions examples/cookbook/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ mod tests {

// ANCHOR: custom_chain_provider
let node_config = Config::local_node();
let (_provider, _bound_address) =
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await;
// ANCHOR_END: custom_chain_provider
Ok(())
Expand All @@ -138,7 +138,7 @@ mod tests {
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);

let (provider, _) = setup_test_provider(coins, vec![], None, None).await;
let provider = setup_test_provider(coins, vec![], None, None).await;

wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
Expand Down Expand Up @@ -171,7 +171,7 @@ mod tests {
wallet_1.sign_transaction(&mut tb);
let tx = tb.build()?;

provider.send_transaction(tx).await?;
provider.send_transaction_and_await_commit(tx).await?;

let balances = wallet_2.get_balances().await?;

Expand Down
2 changes: 1 addition & 1 deletion examples/predicates/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ mod tests {
})
.collect::<Vec<_>>();

let (provider, _) = setup_test_provider(all_coins, vec![], None, None).await;
let provider = setup_test_provider(all_coins, vec![], None, None).await;

[&mut wallet, &mut wallet2, &mut wallet3, &mut receiver]
.iter_mut()
Expand Down
13 changes: 10 additions & 3 deletions examples/providers/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#[cfg(test)]
mod tests {
use std::time::Duration;

use fuels::prelude::Result;

#[tokio::test]
Expand All @@ -26,8 +28,8 @@ mod tests {
dbg!(wallet.address().to_string());
// ANCHOR_END: connect_to_testnet

let (_, addr) = setup_test_provider(vec![], vec![], None, None).await;
let port = addr.port();
let provider = setup_test_provider(vec![], vec![], None, None).await;
let port = provider.url().split(':').last().unwrap();

// ANCHOR: local_node_address
let _provider = Provider::connect(format!("127.0.0.1:{port}"))
Expand Down Expand Up @@ -61,7 +63,12 @@ mod tests {
);
// ANCHOR_END: setup_single_asset

let (provider, _) = setup_test_provider(coins.clone(), vec![], None, None).await;
// ANCHOR: configure_retry
let retry_config = RetryConfig::new(3, Backoff::Fixed(Duration::from_secs(2)))?;
let provider = setup_test_provider(coins.clone(), vec![], None, None)
.await
.with_retry_config(retry_config);
// ANCHOR_END: configure_retry
// ANCHOR_END: setup_test_blockchain

// ANCHOR: get_coins
Expand Down
14 changes: 7 additions & 7 deletions examples/wallets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ mod tests {
use fuels::prelude::*;

// Use the test helper to setup a test provider.
let (provider, _address) = setup_test_provider(vec![], vec![], None, None).await;
let provider = setup_test_provider(vec![], vec![], None, None).await;

// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
Expand All @@ -24,7 +24,7 @@ mod tests {
use fuels::{accounts::fuel_crypto::SecretKey, prelude::*};

// Use the test helper to setup a test provider.
let (provider, _address) = setup_test_provider(vec![], vec![], None, None).await;
let provider = setup_test_provider(vec![], vec![], None, None).await;

// Setup the private key.
let secret = SecretKey::from_str(
Expand All @@ -46,7 +46,7 @@ mod tests {
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";

// Use the test helper to setup a test provider.
let (provider, _address) = setup_test_provider(vec![], vec![], None, None).await;
let provider = setup_test_provider(vec![], vec![], None, None).await;

// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
Expand Down Expand Up @@ -74,7 +74,7 @@ mod tests {
let mut rng = rand::thread_rng();

// Use the test helper to setup a test provider.
let (provider, _address) = setup_test_provider(vec![], vec![], None, None).await;
let provider = setup_test_provider(vec![], vec![], None, None).await;

let password = "my_master_password";

Expand All @@ -100,7 +100,7 @@ mod tests {
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";

// Use the test helper to setup a test provider.
let (provider, _address) = setup_test_provider(vec![], vec![], None, None).await;
let provider = setup_test_provider(vec![], vec![], None, None).await;

// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
Expand Down Expand Up @@ -252,7 +252,7 @@ mod tests {
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let (provider, _socket_addr) = setup_test_provider(coins.clone(), vec![], None, None).await;
let provider = setup_test_provider(coins.clone(), vec![], None, None).await;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
Expand Down Expand Up @@ -293,7 +293,7 @@ mod tests {
let assets = vec![asset_base, asset_1, asset_2];

let coins = setup_custom_assets_coins(wallet.address(), &assets);
let (provider, _socket_addr) = setup_test_provider(coins, vec![], None, None).await;
let provider = setup_test_provider(coins, vec![], None, None).await;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
Expand Down
25 changes: 18 additions & 7 deletions packages/fuels-accounts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,12 @@ pub trait Account: ViewOnlyAccount {
let tx = self
.add_fee_resources(tx_builder, previous_base_amount)
.await?;
let tx_id = provider.send_transaction_and_await_commit(tx).await?;

let tx_id = provider.send_transaction(tx).await?;
let receipts = provider.get_receipts(&tx_id).await?;
let receipts = provider
.tx_status(&tx_id)
.await?
.take_receipts_checked(None)?;

Ok((tx_id, receipts))
}
Expand Down Expand Up @@ -271,8 +274,12 @@ pub trait Account: ViewOnlyAccount {

let tx = self.add_fee_resources(tb, base_amount).await?;

let tx_id = provider.send_transaction(tx).await?;
let receipts = provider.get_receipts(&tx_id).await?;
let tx_id = provider.send_transaction_and_await_commit(tx).await?;

let receipts = provider
.tx_status(&tx_id)
.await?
.take_receipts_checked(None)?;

Ok((tx_id.to_string(), receipts))
}
Expand Down Expand Up @@ -300,8 +307,12 @@ pub trait Account: ViewOnlyAccount {
);

let tx = self.add_fee_resources(tb, amount).await?;
let tx_id = provider.send_transaction(tx).await?;
let receipts = provider.get_receipts(&tx_id).await?;
let tx_id = provider.send_transaction_and_await_commit(tx).await?;

let receipts = provider
.tx_status(&tx_id)
.await?
.take_receipts_checked(None)?;

let message_id = extract_message_id(&receipts)
.expect("MessageId could not be retrieved from tx receipts.");
Expand Down Expand Up @@ -413,7 +424,7 @@ mod tests {

assert_eq!(wallet.address().hash(), recovered_address.hash());

// Verify the signature
// Verify signature
signature.verify(&recovered_address, &message)?;
// ANCHOR_END: sign_tx

Expand Down
Loading

0 comments on commit 337d0ea

Please sign in to comment.