Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e93324b
Add allow preconfirmation parameter to end point
AurelienFT Apr 4, 2025
c1c1a04
changelog
AurelienFT Apr 4, 2025
3922b3d
Update status changes subscription
AurelienFT Apr 4, 2025
e052e10
SImplify implementation for transaction_status_change and modify subm…
AurelienFT Apr 4, 2025
2c6ff01
Change status type
AurelienFT Apr 4, 2025
ff1d2ac
format
AurelienFT Apr 4, 2025
be78e5b
Update all tests
AurelienFT Apr 4, 2025
d1cedac
Update crates/fuel-core/src/schema/tx/types.rs
AurelienFT Apr 4, 2025
deb4c78
Add new test and avoid changes in submit_and_await_status client func…
AurelienFT Apr 7, 2025
5d7b07b
Add new client method and change behavior of transaction_status_change
AurelienFT Apr 7, 2025
79ce8b0
Fix clippy & tests
AurelienFT Apr 7, 2025
ad46cde
Merge branch 'master' into make_preconfirmation_api_optional
AurelienFT Apr 7, 2025
fc91841
Merge branch 'master' into make_preconfirmation_api_optional
AurelienFT Apr 7, 2025
ec3c92f
fix test
AurelienFT Apr 7, 2025
f4591cd
fix test
AurelienFT Apr 7, 2025
e94b818
Simplified integration tests to use `assemble_tx`.
xgreenx Apr 7, 2025
b5f5a63
Fix the test after latest modifications
xgreenx Apr 7, 2025
6a75a49
Merge branch 'master' into make_preconfirmation_api_optional
xgreenx Apr 7, 2025
d9d1cf7
Fix flakiness
xgreenx Apr 7, 2025
c42573d
Merge branch 'master' into make_preconfirmation_api_optional
xgreenx Apr 7, 2025
1e47f97
Fix flakiness with dropped DB
xgreenx Apr 8, 2025
cf7fd60
Merge branch 'master' into make_preconfirmation_api_optional
xgreenx Apr 8, 2025
1075737
Change naming parameter
AurelienFT Apr 8, 2025
114d0ac
Merge branch 'master' into make_preconfirmation_api_optional
AurelienFT Apr 8, 2025
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
1 change: 1 addition & 0 deletions .changes/changed/2925.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Make preconfirmation optional on API endpoints.
12 changes: 8 additions & 4 deletions crates/client/assets/schema.sdl
Original file line number Diff line number Diff line change
Expand Up @@ -1334,7 +1334,11 @@ type Subscription {
"""
The ID of the transaction
"""
id: TransactionId!
id: TransactionId!,
"""
If true, accept to receive the preconfirmation status
"""
allowPreconfirmation: Boolean
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd prefer either includePrecofirmations or just preconfirmations

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to includePrecomfirmation it's still singular as you can only receive 1 preconfirmation for a TX.

): TransactionStatus!
"""
Submits transaction to the `TxPool` and await either success or failure.
Expand All @@ -1343,9 +1347,9 @@ type Subscription {
"""
Submits the transaction to the `TxPool` and returns a stream of events.
Compared to the `submitAndAwait`, the stream also contains
`SubmittedStatus` as an intermediate state.
`SubmittedStatus` and potentially preconfirmation as an intermediate state.
"""
submitAndAwaitStatus(tx: HexString!, estimatePredicates: Boolean): TransactionStatus!
submitAndAwaitStatus(tx: HexString!, estimatePredicates: Boolean, allowPreconfirmation: Boolean): TransactionStatus!
contractStorageSlots(contractId: ContractId!): StorageSlot!
contractStorageBalances(contractId: ContractId!): ContractBalance!
}
Expand Down Expand Up @@ -1387,7 +1391,7 @@ type Transaction {
outputContract: ContractOutput
witnesses: [HexString!]
receiptsRoot: Bytes32
status: TransactionStatus
status(allowPreconfirmation: Boolean): TransactionStatus
script: HexString
scriptData: HexString
bytecodeWitnessIndex: U16
Expand Down
9 changes: 7 additions & 2 deletions crates/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -938,8 +938,10 @@ impl FuelClient {
pub async fn submit_and_await_status<'a>(
&'a self,
tx: &'a Transaction,
allow_preconfirmation: bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a new function which has this preconf argument? submit_and_await_preconfirmation perhaps?

it makes the function call more clear

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the idea of having a new endpoint for each parameter we have. Because what if we had the same thing for estimate_predicates and you want to make a submit_and_await_status with both options, you have to make two calls ? but you can't because it's a submit.

) -> io::Result<impl Stream<Item = io::Result<TransactionStatus>> + 'a> {
self.submit_and_await_status_opt(tx, None).await
self.submit_and_await_status_opt(tx, None, Some(allow_preconfirmation))
.await
}

/// Similar to [`Self::submit_and_await_commit_opt`], but includes all intermediate states.
Expand All @@ -948,13 +950,16 @@ impl FuelClient {
&'a self,
tx: &'a Transaction,
estimate_predicates: Option<bool>,
allow_preconfirmation: Option<bool>,
) -> io::Result<impl Stream<Item = io::Result<TransactionStatus>> + 'a> {
use cynic::SubscriptionBuilder;
use schema::tx::SubmitAndAwaitStatusArg;
let tx = tx.clone().to_bytes();
let s = schema::tx::SubmitAndAwaitStatusSubscription::build(
TxWithEstimatedPredicatesArg {
SubmitAndAwaitStatusArg {
tx: HexString(Bytes(tx)),
estimate_predicates,
allow_preconfirmation,
},
);

Expand Down
13 changes: 11 additions & 2 deletions crates/client/src/client/schema/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,15 @@ pub struct TxWithEstimatedPredicatesArg {
pub estimate_predicates: Option<bool>,
}

#[derive(cynic::QueryVariables)]
pub struct SubmitAndAwaitStatusArg {
pub tx: HexString,
#[cynic(skip_serializing_if = "Option::is_none")]
pub estimate_predicates: Option<bool>,
#[cynic(skip_serializing_if = "Option::is_none")]
pub allow_preconfirmation: Option<bool>,
}

#[derive(cynic::QueryFragment, Clone, Debug)]
#[cynic(
schema_path = "./assets/schema.sdl",
Expand Down Expand Up @@ -643,10 +652,10 @@ pub struct SubmitAndAwaitSubscriptionWithTransaction {
#[cynic(
schema_path = "./assets/schema.sdl",
graphql_type = "Subscription",
variables = "TxWithEstimatedPredicatesArg"
variables = "SubmitAndAwaitStatusArg"
)]
pub struct SubmitAndAwaitStatusSubscription {
#[arguments(tx: $tx, estimatePredicates: $estimate_predicates)]
#[arguments(tx: $tx, estimatePredicates: $estimate_predicates, allowPreconfirmation: $allow_preconfirmation)]
pub submit_and_await_status: TransactionStatus,
}

Expand Down
11 changes: 8 additions & 3 deletions crates/fuel-core/src/query/subscriptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub(crate) async fn transaction_status_change<'a, State>(
state: State,
stream: BoxStream<'a, TxStatusMessage>,
transaction_id: Bytes32,
allow_preconfirmation: bool,
) -> impl Stream<Item = anyhow::Result<ApiTxStatus>> + 'a
where
State: TxnStatusChangeState + Send + Sync + 'a,
Expand All @@ -50,7 +51,7 @@ where
.chain(stream)
// Keep taking the stream until the oneshot channel is closed.
.take_until(closed)
.map(move |status| {
.filter_map(move |status | {
if status.is_final() {
if let Some(close) = close.take() {
let _ = close.send(());
Expand All @@ -60,11 +61,15 @@ where
match status {
TxStatusMessage::Status(status) => {
let status = ApiTxStatus::new(transaction_id, status);
Ok(status)
if !allow_preconfirmation && (matches!(status, ApiTxStatus::PreconfirmationFailure(_)) || matches!(status, ApiTxStatus::PreconfirmationSuccess(_))) {
futures::future::ready(None)
} else {
futures::future::ready(Some(Ok(status)))
}
},
// Map a failed status to an error for the api.
TxStatusMessage::FailedStatus => {
Err(anyhow::anyhow!("Failed to get transaction status"))
futures::future::ready(Some(Err(anyhow::anyhow!("Failed to get transaction status"))))
}
}
})
Expand Down
2 changes: 1 addition & 1 deletion crates/fuel-core/src/query/subscriptions/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ fn test_tsc_inner(
});

let stream = futures::stream::iter(stream).boxed();
super::transaction_status_change(mock_state, stream, txn_id(0))
super::transaction_status_change(mock_state, stream, txn_id(0), true)
.await
.collect::<Vec<_>>()
.await
Expand Down
57 changes: 43 additions & 14 deletions crates/fuel-core/src/schema/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,8 @@ impl TxStatusSubscription {
&self,
ctx: &'a Context<'a>,
#[graphql(desc = "The ID of the transaction")] id: TransactionId,
#[graphql(desc = "If true, accept to receive the preconfirmation status")]
allow_preconfirmation: Option<bool>,
) -> anyhow::Result<impl Stream<Item = async_graphql::Result<TransactionStatus>> + 'a>
{
let tx_status_manager = ctx.data_unchecked::<DynTxStatusManager>();
Expand All @@ -634,11 +636,14 @@ impl TxStatusSubscription {
tx_status_manager,
query,
};
Ok(
transaction_status_change(status_change_state, rx, id.into())
.await
.map_err(async_graphql::Error::from),
Ok(transaction_status_change(
status_change_state,
rx,
id.into(),
allow_preconfirmation.unwrap_or(false),
)
.await
.map_err(async_graphql::Error::from))
}

/// Submits transaction to the `TxPool` and await either success or failure.
Expand All @@ -653,7 +658,7 @@ impl TxStatusSubscription {
> {
use tokio_stream::StreamExt;
let subscription =
submit_and_await_status(ctx, tx, estimate_predicates.unwrap_or(false))
submit_and_await_status(ctx, tx, estimate_predicates.unwrap_or(false), false)
.await?;

Ok(subscription
Expand All @@ -663,24 +668,32 @@ impl TxStatusSubscription {

/// Submits the transaction to the `TxPool` and returns a stream of events.
/// Compared to the `submitAndAwait`, the stream also contains
/// `SubmittedStatus` as an intermediate state.
/// `SubmittedStatus` and potentially preconfirmation as an intermediate state.
#[graphql(complexity = "query_costs().submit_and_await + child_complexity")]
async fn submit_and_await_status<'a>(
&self,
ctx: &'a Context<'a>,
tx: HexString,
estimate_predicates: Option<bool>,
allow_preconfirmation: Option<bool>,
) -> async_graphql::Result<
impl Stream<Item = async_graphql::Result<TransactionStatus>> + 'a,
> {
submit_and_await_status(ctx, tx, estimate_predicates.unwrap_or(false)).await
submit_and_await_status(
ctx,
tx,
estimate_predicates.unwrap_or(false),
allow_preconfirmation.unwrap_or(false),
)
.await
}
}

async fn submit_and_await_status<'a>(
ctx: &'a Context<'a>,
tx: HexString,
estimate_predicates: bool,
allow_preconfirmation: bool,
) -> async_graphql::Result<
impl Stream<Item = async_graphql::Result<TransactionStatus>> + 'a,
> {
Expand All @@ -703,13 +716,29 @@ async fn submit_and_await_status<'a>(
txpool.insert(tx).await?;

Ok(subscription
.map(move |event| match event {
TxStatusMessage::Status(status) => {
let status = TransactionStatus::new(tx_id, status);
Ok(status)
}
TxStatusMessage::FailedStatus => {
Err(anyhow::anyhow!("Failed to get transaction status").into())
.filter_map(move |status| {
match status {
TxStatusMessage::Status(status) => {
let status = TransactionStatus::new(tx_id, status);
if !allow_preconfirmation
&& (matches!(
status,
TransactionStatus::PreconfirmationFailure(_)
) || matches!(
status,
TransactionStatus::PreconfirmationSuccess(_)
))
{
None
} else {
Some(Ok(status))
}
}
// Map a failed status to an error for the api.
TxStatusMessage::FailedStatus => Some(Err(anyhow::anyhow!(
"Failed to get transaction status"
)
.into())),
}
})
.take(3))
Expand Down
36 changes: 32 additions & 4 deletions crates/fuel-core/src/schema/tx/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -769,15 +769,21 @@ impl Transaction {
async fn status(
&self,
ctx: &Context<'_>,
allow_preconfirmation: Option<bool>,
) -> async_graphql::Result<Option<TransactionStatus>> {
let id = self.1;
let query = ctx.read_view()?;

let tx_status_manager = ctx.data_unchecked::<DynTxStatusManager>();

get_tx_status(id, query.as_ref(), tx_status_manager)
.await
.map_err(Into::into)
get_tx_status(
id,
query.as_ref(),
tx_status_manager,
allow_preconfirmation.unwrap_or(false),
)
.await
.map_err(Into::into)
}

async fn script(&self) -> Option<HexString> {
Expand Down Expand Up @@ -1097,6 +1103,7 @@ pub(crate) async fn get_tx_status(
id: fuel_core_types::fuel_types::Bytes32,
query: &ReadView,
tx_status_manager: &DynTxStatusManager,
allow_preconfirmation: bool,
) -> Result<Option<TransactionStatus>, StorageError> {
let api_result = query
.tx_status(&id)
Expand All @@ -1109,7 +1116,28 @@ pub(crate) async fn get_tx_status(
None => {
let status = tx_status_manager.status(id).await?;
match status {
Some(status) => Ok(Some(TransactionStatus::new(id, status))),
Some(status) => {
let status = TransactionStatus::new(id, status);

// Filter out preconfirmation statuses if not allowed. Converting to submitted status
// because it's the closest to the preconfirmation status.
// Having `now()` as timestamp isn't ideal but shouldn't cause much inconsistency.
if !allow_preconfirmation
&& (matches!(
status,
TransactionStatus::PreconfirmationSuccess(_)
) || matches!(
status,
TransactionStatus::PreconfirmationFailure(_)
))
{
Ok(Some(TransactionStatus::Submitted(SubmittedStatus(
Tai64::now(),
))))
} else {
Ok(Some(status))
}
}
None => Ok(None),
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/fuel-core/src/service/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ impl FuelService {
db,
tx_status_manager,
};
Ok(transaction_status_change(state, rx, id).await)
Ok(transaction_status_change(state, rx, id, false).await)
}
}

Expand Down
2 changes: 1 addition & 1 deletion tests/test-helpers/src/counter_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ pub async fn deploy(
let contract_id = CreateMetadata::compute(&tx).unwrap().contract_id;

let tx = tx.into();
let mut status_stream = client.submit_and_await_status(&tx).await.unwrap();
let mut status_stream = client.submit_and_await_status(&tx, true).await.unwrap();
let intermediate_status = status_stream.next().await.unwrap().unwrap();
assert!(matches!(
intermediate_status,
Expand Down
Loading
Loading