Skip to content

Commit ee95a51

Browse files
caposselenategraf
andauthored
BM-272: Fix expired status (github#100)
A proof request is expired when the current block number is greater than the sum `startBidding` + `timeout` and it's not fulfilled. A proof request expiration is stored into the contract state only during a `lockinRequest`. As such, querying the status of an expired proof request yields an `expired` response only if the proof request has been locked in, and `unknown` otherwise. To minimise this limitation, the `get_status` method accept an optional `expires_at` parameter the caller should provide. The same parameter is made mandatory for the `wait_for_proof_fulfillment` method. --------- Co-authored-by: Victor Graf <[email protected]>
1 parent 3a3bb3d commit ee95a51

File tree

8 files changed

+67
-53
lines changed

8 files changed

+67
-53
lines changed

crates/boundless-market/src/bin/cli.rs

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,6 @@ enum Command {
7272
GetProof {
7373
/// The proof request identifier
7474
request_id: U256,
75-
/// Wait until the request is fulfilled
76-
#[clap(short, long, default_value = "false")]
77-
wait: bool,
7875
},
7976
/// Verify the proof of the given request against
8077
/// the SetVerifier contract.
@@ -88,6 +85,8 @@ enum Command {
8885
Status {
8986
/// The proof request identifier
9087
request_id: U256,
88+
/// The block number at which the request expires
89+
expires_at: Option<u64>,
9190
},
9291
}
9392

@@ -204,25 +203,16 @@ async fn main() -> Result<()> {
204203
market.slash(request_id).await?;
205204
tracing::info!("Request slashed: 0x{request_id:x}");
206205
}
207-
Command::GetProof { request_id, wait } => {
208-
let (journal, seal) = if wait {
209-
market
210-
.wait_for_request_fulfillment(request_id, Duration::from_secs(5), None)
211-
.await?
212-
} else {
213-
market.get_request_fulfillment(request_id).await?
214-
};
206+
Command::GetProof { request_id } => {
207+
let (journal, seal) = market.get_request_fulfillment(request_id).await?;
215208
tracing::info!(
216209
"Journal: {} - Seal: {}",
217210
serde_json::to_string_pretty(&journal)?,
218211
serde_json::to_string_pretty(&seal)?
219212
);
220213
}
221214
Command::VerifyProof { request_id, image_id } => {
222-
let (journal, seal) = market
223-
.wait_for_request_fulfillment(request_id, Duration::from_secs(5), None)
224-
.await?;
225-
215+
let (journal, seal) = market.get_request_fulfillment(request_id).await?;
226216
let journal_digest = <[u8; 32]>::from(Journal::new(journal.to_vec()).digest()).into();
227217
set_verifier
228218
.verify(seal, image_id, journal_digest)
@@ -231,8 +221,8 @@ async fn main() -> Result<()> {
231221
.map_err(|_| anyhow::anyhow!("Verification failed"))?;
232222
tracing::info!("Proof for request id 0x{request_id:x} verified successfully.");
233223
}
234-
Command::Status { request_id } => {
235-
let status = market.get_status(request_id).await?;
224+
Command::Status { request_id, expires_at } => {
225+
let status = market.get_status(request_id, expires_at).await?;
236226
tracing::info!("Status: {:?}", status);
237227
}
238228
};
@@ -336,8 +326,9 @@ where
336326
);
337327

338328
if args.wait {
339-
let (journal, seal) =
340-
market.wait_for_request_fulfillment(request_id, Duration::from_secs(5), None).await?;
329+
let (journal, seal) = market
330+
.wait_for_request_fulfillment(request_id, Duration::from_secs(5), request.expires_at())
331+
.await?;
341332
tracing::info!(
342333
"Journal: {} - Seal: {}",
343334
serde_json::to_string_pretty(&journal)?,
@@ -396,8 +387,9 @@ where
396387
);
397388

398389
if wait {
399-
let (journal, seal) =
400-
market.wait_for_request_fulfillment(request_id, Duration::from_secs(5), None).await?;
390+
let (journal, seal) = market
391+
.wait_for_request_fulfillment(request_id, Duration::from_secs(5), request.expires_at())
392+
.await?;
401393
tracing::info!(
402394
"Journal: {} - Seal: {}",
403395
serde_json::to_string_pretty(&journal)?,

crates/boundless-market/src/contracts/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ impl ProvingRequest {
130130
Self { offer, ..self }
131131
}
132132

133+
/// Returns the block number at which the request expires.
134+
pub fn expires_at(&self) -> u64 {
135+
self.offer.biddingStart + self.offer.timeout as u64
136+
}
137+
133138
/// Check that the request is valid and internally consistent.
134139
///
135140
/// If any field are empty, or if two fields conflict (e.g. the max price is less than the min

crates/boundless-market/src/contracts/proof_market.rs

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// All rights reserved.
44

5-
use std::time::{Duration, Instant};
5+
use std::time::Duration;
66

77
use alloy::{
88
network::Ethereum,
@@ -441,22 +441,33 @@ where
441441
}
442442

443443
/// Returns the [ProofStatus] of a proving request.
444-
pub async fn get_status(&self, request_id: U256) -> Result<ProofStatus, MarketError> {
444+
///
445+
/// The `expires_at` parameter is the block number at which the request expires.
446+
pub async fn get_status(
447+
&self,
448+
request_id: U256,
449+
expires_at: Option<u64>,
450+
) -> Result<ProofStatus, MarketError> {
445451
let block_number = self.get_latest_block().await?;
446452

447453
if self.is_fulfilled(request_id).await.context("Failed to check fulfillment status")? {
448454
return Ok(ProofStatus::Fulfilled);
449455
}
450456

457+
if let Some(expires_at) = expires_at {
458+
if block_number > expires_at {
459+
return Ok(ProofStatus::Expired);
460+
}
461+
}
462+
451463
if self.is_locked_in(request_id).await.context("Failed to check locked status")? {
464+
let deadline = self.instance.requestDeadline(U192::from(request_id)).call().await?._0;
465+
if block_number > deadline && deadline > 0 {
466+
return Ok(ProofStatus::Expired);
467+
};
452468
return Ok(ProofStatus::Locked);
453469
}
454470

455-
let deadline = self.instance.requestDeadline(U192::from(request_id)).call().await?._0;
456-
if block_number > deadline && deadline > 0 {
457-
return Ok(ProofStatus::Expired);
458-
};
459-
460471
Ok(ProofStatus::Unknown)
461472
}
462473

@@ -526,7 +537,7 @@ where
526537
&self,
527538
request_id: U256,
528539
) -> Result<(Bytes, Bytes), MarketError> {
529-
match self.get_status(request_id).await? {
540+
match self.get_status(request_id, None).await? {
530541
ProofStatus::Expired => Err(MarketError::RequestHasExpired(request_id)),
531542
ProofStatus::Fulfilled => self.query_fulfilled_event(request_id, None, None).await,
532543
_ => Err(MarketError::RequestNotFulfilled(request_id)),
@@ -542,16 +553,10 @@ where
542553
&self,
543554
request_id: U256,
544555
retry_interval: Duration,
545-
timeout: Option<Duration>,
556+
expires_at: u64,
546557
) -> Result<(Bytes, Bytes), MarketError> {
547-
let start_time = Instant::now();
548558
loop {
549-
if let Some(timeout_duration) = timeout {
550-
if start_time.elapsed() >= timeout_duration {
551-
return Err(MarketError::TimeoutReached(request_id));
552-
}
553-
}
554-
let status = self.get_status(request_id).await?;
559+
let status = self.get_status(request_id, Some(expires_at)).await?;
555560
match status {
556561
ProofStatus::Expired => return Err(MarketError::RequestHasExpired(request_id)),
557562
ProofStatus::Fulfilled => {
@@ -624,7 +629,7 @@ where
624629
.context(format!("Failed to get EOA nonce for {:?}", self.caller))?;
625630
let id: u32 = nonce.try_into().context("Failed to convert nonce to u32")?;
626631
let request_id = request_id(&self.caller, id);
627-
match self.get_status(U256::from(request_id)).await? {
632+
match self.get_status(U256::from(request_id), None).await? {
628633
ProofStatus::Unknown => return Ok(id),
629634
_ => Err(MarketError::Error(anyhow!("index already in use"))),
630635
}
@@ -882,6 +887,7 @@ mod tests {
882887
};
883888

884889
let request = new_request(1, &ctx).await;
890+
let expires_at = request.expires_at();
885891

886892
let request_id =
887893
ctx.customer_market.submit_request(&request, &ctx.customer_signer).await.unwrap();
@@ -900,7 +906,10 @@ mod tests {
900906
// Lockin the request
901907
ctx.prover_market.lockin_request(&request, &customer_sig, None).await.unwrap();
902908
assert!(ctx.customer_market.is_locked_in(request_id).await.unwrap());
903-
assert!(ctx.customer_market.get_status(request_id).await.unwrap() == ProofStatus::Locked);
909+
assert!(
910+
ctx.customer_market.get_status(request_id, Some(expires_at)).await.unwrap()
911+
== ProofStatus::Locked
912+
);
904913

905914
// mock the fulfillment
906915
let (root, set_verifier_seal, fulfillment, market_seal) =
@@ -937,6 +946,7 @@ mod tests {
937946
};
938947

939948
let request = new_request(1, &ctx).await;
949+
let expires_at = request.expires_at();
940950

941951
let request_id =
942952
ctx.customer_market.submit_request(&request, &ctx.customer_signer).await.unwrap();
@@ -955,7 +965,10 @@ mod tests {
955965
// Lockin the request
956966
ctx.prover_market.lockin_request(&request, &customer_sig, None).await.unwrap();
957967
assert!(ctx.customer_market.is_locked_in(request_id).await.unwrap());
958-
assert!(ctx.customer_market.get_status(request_id).await.unwrap() == ProofStatus::Locked);
968+
assert!(
969+
ctx.customer_market.get_status(request_id, Some(expires_at)).await.unwrap()
970+
== ProofStatus::Locked
971+
);
959972

960973
// mock the fulfillment
961974
let (root, set_verifier_seal, fulfillment, market_seal) =

crates/boundless-market/src/sdk/client.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,11 @@ where
148148
&self,
149149
request_id: U256,
150150
check_interval: std::time::Duration,
151-
timeout: Option<std::time::Duration>,
151+
expires_at: u64,
152152
) -> Result<(Bytes, Bytes), ClientError> {
153153
Ok(self
154154
.proof_market
155-
.wait_for_request_fulfillment(request_id, check_interval, timeout)
155+
.wait_for_request_fulfillment(request_id, check_interval, expires_at)
156156
.await?)
157157
}
158158
}

crates/broker/src/market_monitor.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,14 @@ where
122122
continue;
123123
}
124124

125-
let req_status = match market.get_status(request_id).await {
126-
Ok(val) => val,
127-
Err(err) => {
128-
tracing::warn!("Failed to get request status: {err:?}");
129-
continue;
130-
}
131-
};
125+
let req_status =
126+
match market.get_status(request_id, Some(event.request.expires_at())).await {
127+
Ok(val) => val,
128+
Err(err) => {
129+
tracing::warn!("Failed to get request status: {err:?}");
130+
continue;
131+
}
132+
};
132133

133134
if !matches!(req_status, ProofStatus::Unknown) {
134135
tracing::debug!(

crates/broker/src/order_monitor.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,11 @@ where
8181
return Err(LockOrderErr::InvalidStatus(order.status));
8282
}
8383

84-
let order_status =
85-
self.market.get_status(order_id).await.context("Failed to get order status")?;
84+
let order_status = self
85+
.market
86+
.get_status(order_id, Some(order.request.expires_at()))
87+
.await
88+
.context("Failed to get order status")?;
8689
if order_status != ProofStatus::Unknown {
8790
tracing::warn!("Order {order_id:x} not open: {order_status:?}, skipping");
8891
// TODO: fetch some chain data to find out who / and for how much the order

crates/broker/src/tests/e2e.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ async fn simple_e2e() {
104104
.wait_for_request_fulfillment(
105105
U256::from(request.id),
106106
Duration::from_secs(1),
107-
Some(Duration::from_secs(60)),
107+
request.expires_at(),
108108
)
109109
.await
110110
.unwrap();

examples/counter/apps/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ async fn run(
173173
.wait_for_request_fulfillment(
174174
request_id,
175175
Duration::from_secs(5), // check every 5 seconds
176-
None, // no timeout
176+
request.expires_at(),
177177
)
178178
.await?;
179179
tracing::info!("Request {} fulfilled", request_id);

0 commit comments

Comments
 (0)