Skip to content

Commit

Permalink
separate send_transaction and get_receipts (#1073)
Browse files Browse the repository at this point in the history
Refactored the `submit_and_await_commit_with_receipts` function to
separate transaction submission and receipt retrieval into individual
functions: `submit_tx` and `get_receipts`.

Co-authored-by: Ahmed Sagdati <37515857+segfault-magnet@users.noreply.github.com>
  • Loading branch information
Salka1988 and segfault-magnet authored Aug 16, 2023
1 parent c7b6b95 commit df21541
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 52 deletions.
2 changes: 1 addition & 1 deletion examples/cookbook/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ mod tests {
.build()?;
wallet_1.sign_transaction(&mut tx)?;

let _receipts = provider.send_transaction(&tx).await?;
provider.send_transaction(&tx).await?;

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

Expand Down
24 changes: 14 additions & 10 deletions packages/fuels-accounts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,15 @@ pub trait Account: ViewOnlyAccount {
asset_id: AssetId,
tx_parameters: TxParameters,
) -> Result<(TxId, Vec<Receipt>)> {
let provider = self.try_provider()?;

let inputs = self
.get_asset_inputs_for_amount(asset_id, amount, None)
.await?;

let outputs = self.get_asset_outputs_for_amount(to, asset_id, amount);

let consensus_parameters = self.try_provider()?.consensus_parameters();
let consensus_parameters = provider.consensus_parameters();

let tx_builder = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, tx_parameters)
.set_consensus_parameters(consensus_parameters);
Expand All @@ -212,7 +214,8 @@ pub trait Account: ViewOnlyAccount {
.add_fee_resources(tx_builder, previous_base_amount, None)
.await?;

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

Ok((tx.id(consensus_parameters.chain_id.into()), receipts))
}
Expand All @@ -233,6 +236,8 @@ pub trait Account: ViewOnlyAccount {
asset_id: AssetId,
tx_parameters: TxParameters,
) -> std::result::Result<(String, Vec<Receipt>), Error> {
let provider = self.try_provider()?;

let zeroes = Bytes32::zeroed();
let plain_contract_id: ContractId = to.into();

Expand All @@ -255,7 +260,7 @@ pub trait Account: ViewOnlyAccount {
];

// Build transaction and sign it
let params = self.try_provider()?.consensus_parameters();
let params = provider.consensus_parameters();

let tb = ScriptTransactionBuilder::prepare_contract_transfer(
plain_contract_id,
Expand All @@ -276,8 +281,8 @@ pub trait Account: ViewOnlyAccount {

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

let tx_id = tx.id(params.chain_id.into());
let receipts = self.try_provider()?.send_transaction(&tx).await?;
let tx_id = self.try_provider()?.send_transaction(&tx).await?;
let receipts = provider.get_receipts(&tx_id).await?;

Ok((tx_id.to_string(), receipts))
}
Expand All @@ -291,8 +296,8 @@ pub trait Account: ViewOnlyAccount {
amount: u64,
tx_parameters: TxParameters,
) -> std::result::Result<(TxId, MessageId, Vec<Receipt>), Error> {
let params = self.try_provider()?.consensus_parameters();
let chain_id = params.chain_id;
let provider = self.try_provider()?;

let inputs = self
.get_asset_inputs_for_amount(BASE_ASSET_ID, amount, None)
.await?;
Expand All @@ -305,9 +310,8 @@ pub trait Account: ViewOnlyAccount {
);

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

let tx_id = tx.id(chain_id.into());
let receipts = self.try_provider()?.send_transaction(&tx).await?;
let tx_id = provider.send_transaction(&tx).await?;
let receipts = provider.get_receipts(&tx_id).await?;

let message_id = extract_message_id(&receipts)
.expect("MessageId could not be retrieved from tx receipts.");
Expand Down
50 changes: 13 additions & 37 deletions packages/fuels-accounts/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,31 +126,6 @@ impl From<ProviderError> for Error {
}
}

/// Extends the functionality of the [`FuelClient`].
#[async_trait::async_trait]
pub trait ClientExt {
// TODO: It should be part of the `fuel-core-client`. See https://github.com/FuelLabs/fuel-core/issues/1178
/// Submits transaction, await confirmation and return receipts.
async fn submit_and_await_commit_with_receipts(
&self,
tx: &fuel_tx::Transaction,
) -> io::Result<(TransactionStatus, Option<Vec<Receipt>>)>;
}

#[async_trait::async_trait]
impl ClientExt for FuelClient {
async fn submit_and_await_commit_with_receipts(
&self,
tx: &fuel_tx::Transaction,
) -> io::Result<(TransactionStatus, Option<Vec<Receipt>>)> {
let tx_id = self.submit(tx).await?;
let status = self.await_transaction_commit(&tx_id).await?;
let receipts = self.receipts(&tx_id).await?;

Ok((status, receipts))
}
}

/// Encapsulates common client operations in the SDK.
/// Note that you may also use `client`, which is an instance
/// of `FuelClient`, directly, which provides a broader API.
Expand All @@ -169,7 +144,7 @@ impl Provider {
}

/// Sends a transaction to the underlying Provider's client.
pub async fn send_transaction<T: Transaction + Clone>(&self, tx: &T) -> Result<Vec<Receipt>> {
pub async fn send_transaction<T: Transaction + Clone>(&self, tx: &T) -> Result<TxId> {
let tolerance = 0.0;
let TransactionCost {
gas_used,
Expand Down Expand Up @@ -199,10 +174,15 @@ impl Provider {
&self.consensus_parameters(),
)?;

let (status, receipts) = self.submit_with_feedback(tx.clone()).await?;
let receipts = receipts.map_or(vec![], |v| v);
Self::if_failure_generate_error(&status, &receipts)?;
let tx_id = self.submit_tx(tx.clone()).await?;

Ok(tx_id)
}

pub async fn get_receipts(&self, tx_id: &TxId) -> Result<Vec<Receipt>> {
let tx_status = self.client.transaction_status(tx_id).await?;
let receipts = self.client.receipts(tx_id).await?.map_or(vec![], |v| v);
Self::if_failure_generate_error(&tx_status, &receipts)?;
Ok(receipts)
}

Expand Down Expand Up @@ -230,14 +210,10 @@ impl Provider {
Ok(())
}

async fn submit_with_feedback(
&self,
tx: impl Transaction,
) -> ProviderResult<(TransactionStatus, Option<Vec<Receipt>>)> {
self.client
.submit_and_await_commit_with_receipts(&tx.into())
.await
.map_err(Into::into)
async fn submit_tx(&self, tx: impl Transaction) -> ProviderResult<TxId> {
let tx_id = self.client.submit(&tx.into()).await?;
self.client.await_transaction_commit(&tx_id).await?;
Ok(tx_id)
}

#[cfg(feature = "fuel-core-lib")]
Expand Down
2 changes: 1 addition & 1 deletion packages/fuels-programs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ description = "Fuel Rust SDK contracts."
async-trait = { workspace = true, default-features = false }
bytes = { workspace = true, features = ["serde"] }
fuel-abi-types = { workspace = true }
fuel-asm = { workspace = true }
fuel-tx = { workspace = true }
fuel-types = { workspace = true, features = ["default"] }
fuel-asm = { workspace = true }
fuels-accounts = { workspace = true }
fuels-core = { workspace = true }
hex = { workspace = true, default-features = false, features = ["std"] }
Expand Down
44 changes: 42 additions & 2 deletions packages/fuels-programs/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,25 @@ where
.map_err(|err| map_revert_error(err, &self.log_decoder))
}

pub async fn submit(mut self) -> Result<ContractCallHandler<T, D>> {
let tx = self.build_tx().await?;
let provider = self.account.try_provider()?;

let consensus_parameters = provider.consensus_parameters();
self.cached_tx_id = Some(tx.id(consensus_parameters.chain_id.into()));
provider.send_transaction(&tx).await?;
Ok(self)
}

pub async fn response(self) -> Result<FuelCallResponse<D>> {
let receipts = self
.account
.try_provider()?
.get_receipts(&self.cached_tx_id.expect("Cached tx_id is missing"))
.await?;
self.get_response(receipts)
}

/// Call a contract's method on the node, in a simulated manner, meaning the state of the
/// blockchain is *not* modified but simulated.
///
Expand All @@ -495,7 +514,8 @@ where
let receipts = if simulate {
provider.checked_dry_run(&tx).await?
} else {
provider.send_transaction(&tx).await?
let tx_id = provider.send_transaction(&tx).await?;
provider.get_receipts(&tx_id).await?
};

self.get_response(receipts)
Expand Down Expand Up @@ -695,6 +715,25 @@ impl<T: Account> MultiContractCallHandler<T> {
.map_err(|err| map_revert_error(err, &self.log_decoder))
}

pub async fn submit(mut self) -> Result<MultiContractCallHandler<T>> {
let tx = self.build_tx().await?;
let provider = self.account.try_provider()?;

let consensus_parameters = provider.consensus_parameters();
self.cached_tx_id = Some(tx.id(consensus_parameters.chain_id.into()));
provider.send_transaction(&tx).await?;
Ok(self)
}

pub async fn response<D: Tokenizable + Debug>(self) -> Result<FuelCallResponse<D>> {
let receipts = self
.account
.try_provider()?
.get_receipts(&self.cached_tx_id.expect("Cached tx_id is missing"))
.await?;
self.get_response(receipts)
}

/// Call contract methods on the node, in a simulated manner, meaning the state of the
/// blockchain is *not* modified but simulated.
/// It is the same as the [call] method because the API is more user-friendly this way.
Expand All @@ -718,7 +757,8 @@ impl<T: Account> MultiContractCallHandler<T> {
let receipts = if simulate {
provider.checked_dry_run(&tx).await?
} else {
provider.send_transaction(&tx).await?
let tx_id = provider.send_transaction(&tx).await?;
provider.get_receipts(&tx_id).await?
};

self.get_response(receipts)
Expand Down
27 changes: 26 additions & 1 deletion packages/fuels-programs/src/script_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,10 @@ where
let receipts = if simulate {
self.provider.checked_dry_run(&tx).await?
} else {
self.provider.send_transaction(&tx).await?
let tx_id = self.provider.send_transaction(&tx).await?;
self.provider.get_receipts(&tx_id).await?
};

self.get_response(receipts)
}

Expand All @@ -245,6 +247,29 @@ where
.map_err(|err| map_revert_error(err, &self.log_decoder))
}

pub async fn submit(mut self) -> Result<ScriptCallHandler<T, D>> {
let tb = self.prepare_builder().await?;
let base_amount = self.calculate_base_asset_sum();
let tx = self
.account
.add_fee_resources(tb, base_amount, None)
.await?;
let consensus_parameters = self.provider.consensus_parameters();
self.cached_tx_id = Some(tx.id(consensus_parameters.chain_id.into()));

self.provider.send_transaction(&tx).await?;
Ok(self)
}

pub async fn response(self) -> Result<FuelCallResponse<D>> {
let receipts = self
.account
.try_provider()?
.get_receipts(&self.cached_tx_id.expect("Cached tx_id is missing"))
.await?;
self.get_response(receipts)
}

/// Call a script on the node, in a simulated manner, meaning the state of the
/// blockchain is *not* modified but simulated.
/// It is the same as the [`call`] method because the API is more user-friendly this way.
Expand Down
40 changes: 40 additions & 0 deletions packages/fuels/tests/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1441,3 +1441,43 @@ fn db_rocksdb() {
})
.unwrap();
}

#[tokio::test]
async fn test_contract_submit_and_response() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "packages/fuels/tests/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);

let contract_methods = contract_instance.methods();
let handle = contract_methods.get(5, 6).submit().await?;
let response = handle.response().await?;

assert_eq!(response.value, 11);

let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(7);
let call_handler_2 = contract_methods.get_single(42);

let mut multi_call_handler = MultiContractCallHandler::new(wallet.clone());

multi_call_handler
.add_call(call_handler_1)
.add_call(call_handler_2);

let handle = multi_call_handler.submit().await?;
let (val_1, val_2): (u64, u64) = handle.response().await?.value;

assert_eq!(val_1, 7);
assert_eq!(val_2, 42);

Ok(())
}
27 changes: 27 additions & 0 deletions packages/fuels/tests/scripts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,30 @@ async fn test_script_b256() -> Result<()> {
assert!(response.value);
Ok(())
}

#[tokio::test]
async fn test_script_submit_and_response() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "packages/fuels/tests/scripts/script_struct"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);

let my_struct = MyStruct {
number: 42,
boolean: true,
};

let handle = script_instance.main(my_struct).submit().await?;
let response = handle.response().await?;

assert_eq!(response.value, 42);
Ok(())
}

0 comments on commit df21541

Please sign in to comment.