Skip to content

Commit 5a1c980

Browse files
feat(fortuna): Use v2 events, add new fields to History API for Explorer (#2845)
* use AnyPool for generic SQL cxn, add postgres migrations * migrations * fix sqlite issues * fix sqlite issues * use requested V2 event * merge * feat(fortuna): use requestV2 event, parse revealed event from tx logs, add gas and callback fields to history API * fix(fortuna): validate request v2 seq_num * chore(fortuna): better name
1 parent 176d76f commit 5a1c980

File tree

10 files changed

+128
-43
lines changed

10 files changed

+128
-43
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/fortuna/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "fortuna"
3-
version = "8.2.6"
3+
version = "9.0.0"
44
edition = "2021"
55

66
[lib]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Remove callback_failed, callback_return_value, and callback_gas_used from the requests table.
2+
ALTER TABLE request DROP COLUMN callback_failed;
3+
ALTER TABLE request DROP COLUMN callback_return_value;
4+
ALTER TABLE request DROP COLUMN callback_gas_used;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Add callback_failed, callback_return_value, and callback_gas_used to the requests table.
2+
ALTER TABLE request ADD COLUMN callback_failed INTEGER;
3+
ALTER TABLE request ADD COLUMN callback_return_value VARCHAR;
4+
ALTER TABLE request ADD COLUMN callback_gas_used VARCHAR(100);

apps/fortuna/src/chain/ethereum.rs

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
use {
44
crate::{
55
api::ChainId,
6-
chain::reader::{
7-
self, BlockNumber, BlockStatus, EntropyReader, EntropyRequestInfo,
8-
RequestedWithCallbackEvent,
9-
},
6+
chain::reader::{self, BlockNumber, BlockStatus, EntropyReader, RequestedV2Event},
107
config::EthereumConfig,
118
eth_utils::{
129
eth_gas_oracle::EthProviderOracle,
@@ -283,6 +280,7 @@ impl<T: JsonRpcClient + 'static> EntropyReader for PythRandom<Provider<T>> {
283280
block_number: request.block_number,
284281
use_blockhash: request.use_blockhash,
285282
callback_status: reader::RequestCallbackStatus::try_from(request.callback_status)?,
283+
gas_limit_10k: request.gas_limit_1_0k,
286284
}))
287285
}
288286
}
@@ -306,33 +304,24 @@ impl<T: JsonRpcClient + 'static> EntropyReader for PythRandom<Provider<T>> {
306304
from_block: BlockNumber,
307305
to_block: BlockNumber,
308306
provider: Address,
309-
) -> Result<Vec<RequestedWithCallbackEvent>> {
310-
let mut event = self.requested_with_callback_filter();
307+
) -> Result<Vec<RequestedV2Event>> {
308+
let mut event = self.requested_2_filter();
311309
event.filter = event
312310
.filter
313311
.address(self.address())
314312
.from_block(from_block)
315313
.to_block(to_block)
316314
.topic1(provider);
317315

318-
let res: Vec<(RequestedWithCallbackFilter, LogMeta)> = event.query_with_meta().await?;
316+
let res: Vec<(Requested2Filter, LogMeta)> = event.query_with_meta().await?;
319317
Ok(res
320318
.into_iter()
321-
.map(|(r, meta)| RequestedWithCallbackEvent {
319+
.map(|(r, meta)| RequestedV2Event {
322320
sequence_number: r.sequence_number,
323-
user_random_number: r.user_random_number,
324-
provider_address: r.request.provider,
325-
requestor: r.requestor,
326-
request: EntropyRequestInfo {
327-
provider: r.request.provider,
328-
sequence_number: r.request.sequence_number,
329-
num_hashes: r.request.num_hashes,
330-
commitment: r.request.commitment,
331-
block_number: r.request.block_number,
332-
requester: r.request.requester,
333-
use_blockhash: r.request.use_blockhash,
334-
is_request_with_callback: r.request.is_request_with_callback,
335-
},
321+
user_random_number: r.user_contribution,
322+
provider_address: r.provider,
323+
sender: r.caller,
324+
gas_limit: r.gas_limit,
336325
log_meta: meta,
337326
})
338327
.filter(|r| r.provider_address == provider)

apps/fortuna/src/chain/reader.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ pub struct EntropyRequestInfo {
4545
}
4646

4747
#[derive(Clone)]
48-
pub struct RequestedWithCallbackEvent {
48+
pub struct RequestedV2Event {
4949
pub sequence_number: u64,
5050
pub user_random_number: [u8; 32],
5151
pub provider_address: Address,
52-
pub requestor: Address,
53-
pub request: EntropyRequestInfo,
52+
pub sender: Address,
53+
pub gas_limit: u32,
5454
pub log_meta: LogMeta,
5555
}
5656

@@ -73,7 +73,7 @@ pub trait EntropyReader: Send + Sync {
7373
from_block: BlockNumber,
7474
to_block: BlockNumber,
7575
provider: Address,
76-
) -> Result<Vec<RequestedWithCallbackEvent>>;
76+
) -> Result<Vec<RequestedV2Event>>;
7777

7878
/// Estimate the gas required to reveal a random number with a callback.
7979
async fn estimate_reveal_with_callback_gas(
@@ -97,6 +97,8 @@ pub struct Request {
9797
pub block_number: BlockNumber,
9898
pub use_blockhash: bool,
9999
pub callback_status: RequestCallbackStatus,
100+
/// The gas limit for the request, in 10k gas units. (i.e., 2 = 20k gas).
101+
pub gas_limit_10k: u16,
100102
}
101103

102104
/// Status values for Request.callback_status
@@ -169,6 +171,7 @@ pub mod mock {
169171
block_number: b,
170172
use_blockhash: u,
171173
callback_status: RequestCallbackStatus::CallbackNotNecessary,
174+
gas_limit_10k: 0,
172175
})
173176
.collect(),
174177
),
@@ -189,6 +192,7 @@ pub mod mock {
189192
block_number,
190193
use_blockhash,
191194
callback_status: RequestCallbackStatus::CallbackNotNecessary,
195+
gas_limit_10k: 0,
192196
});
193197
self
194198
}
@@ -227,7 +231,7 @@ pub mod mock {
227231
_from_block: BlockNumber,
228232
_to_block: BlockNumber,
229233
_provider: Address,
230-
) -> Result<Vec<super::RequestedWithCallbackEvent>> {
234+
) -> Result<Vec<super::RequestedV2Event>> {
231235
Ok(vec![])
232236
}
233237

apps/fortuna/src/eth_utils/utils.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use {
22
crate::{
3-
chain::ethereum::InstrumentedSignablePythContract, eth_utils::nonce_manager::NonceManaged,
3+
chain::ethereum::{InstrumentedSignablePythContract, PythRandomEvents, Revealed2Filter},
4+
eth_utils::nonce_manager::NonceManaged,
45
},
56
anyhow::{anyhow, Result},
67
backoff::ExponentialBackoff,
78
ethabi::ethereum_types::U64,
89
ethers::{
9-
contract::{ContractCall, ContractError},
10+
contract::{ContractCall, ContractError, EthLogDecode},
1011
middleware::Middleware,
1112
providers::{MiddlewareError, ProviderError},
1213
signers::Signer,
@@ -30,6 +31,7 @@ pub struct SubmitTxResult {
3031
pub fee_multiplier: u64,
3132
pub duration: Duration,
3233
pub receipt: TransactionReceipt,
34+
pub revealed_event: Revealed2Filter,
3335
}
3436

3537
#[derive(Clone, Debug)]
@@ -157,7 +159,7 @@ pub async fn submit_tx_with_backoff<T: Middleware + NonceManaged + 'static>(
157159

158160
let num_retries = Arc::new(AtomicU64::new(0));
159161

160-
let success = backoff::future::retry_notify(
162+
let receipt = backoff::future::retry_notify(
161163
backoff,
162164
|| async {
163165
let num_retries = num_retries.load(std::sync::atomic::Ordering::Relaxed);
@@ -186,11 +188,26 @@ pub async fn submit_tx_with_backoff<T: Middleware + NonceManaged + 'static>(
186188
let duration = start_time.elapsed();
187189
let num_retries = num_retries.load(std::sync::atomic::Ordering::Relaxed);
188190

191+
let revealed_event: Revealed2Filter = {
192+
let mut found_event = None;
193+
for log in &receipt.logs {
194+
if let Ok(PythRandomEvents::Revealed2Filter(decoded_event)) =
195+
PythRandomEvents::decode_log(&log.clone().into())
196+
{
197+
found_event = Some(decoded_event);
198+
break;
199+
}
200+
}
201+
// A successful reveal will always emit a Revealed v2 event, so theoretically we should never get here.
202+
found_event.ok_or_else(|| SubmitTxError::ReceiptError(call.tx.clone(), receipt.clone()))?
203+
};
204+
189205
Ok(SubmitTxResult {
190206
num_retries,
191207
fee_multiplier: escalation_policy.get_fee_multiplier_pct(num_retries),
192208
duration,
193-
receipt: success,
209+
receipt,
210+
revealed_event,
194211
})
195212
}
196213

apps/fortuna/src/history.rs

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use {
55
ethers::{
66
core::utils::hex::ToHex,
77
prelude::TxHash,
8-
types::{Address, U256},
8+
types::{Address, Bytes, U256},
99
utils::keccak256,
1010
},
1111
serde::Serialize,
@@ -45,6 +45,17 @@ pub enum RequestEntryState {
4545
#[schema(example = "a905ab56567d31a7fda38ed819d97bc257f3ebe385fc5c72ce226d3bb855f0fe")]
4646
#[serde_as(as = "serde_with::hex::Hex")]
4747
combined_random_number: [u8; 32],
48+
/// Whether the callback to the caller failed.
49+
callback_failed: bool,
50+
/// Return value from the callback. If the callback failed, this field contains
51+
/// the error code and any additional returned data. Note that "" often indicates an out-of-gas error.
52+
/// If the callback returns more than 256 bytes, only the first 256 bytes of the callback return value are included.
53+
/// NOTE: This field is the raw bytes returned from the callback, not hex-decoded. The client should decode it as needed.
54+
callback_return_value: Bytes,
55+
/// How much gas the callback used.
56+
#[schema(example = "567890", value_type = String)]
57+
#[serde(with = "crate::serde::u32")]
58+
callback_gas_used: u32,
4859
},
4960
Failed {
5061
reason: String,
@@ -78,8 +89,7 @@ pub struct RequestStatus {
7889
/// Gas limit for the callback in the smallest unit of the chain.
7990
/// For example, if the native currency is ETH, this will be in wei.
8091
#[schema(example = "500000", value_type = String)]
81-
#[serde(with = "crate::serde::u256")]
82-
pub gas_limit: U256,
92+
pub gas_limit: u32,
8393
/// The user contribution to the random number.
8494
#[schema(example = "a905ab56567d31a7fda38ed819d97bc257f3ebe385fc5c72ce226d3bb855f0fe")]
8595
#[serde_as(as = "serde_with::hex::Hex")]
@@ -121,6 +131,9 @@ struct RequestRow {
121131
provider_random_number: Option<String>,
122132
gas_used: Option<String>,
123133
info: Option<String>,
134+
callback_failed: Option<i64>,
135+
callback_return_value: Option<String>,
136+
callback_gas_used: Option<String>,
124137
}
125138

126139
impl TryFrom<RequestRow> for RequestStatus {
@@ -139,7 +152,9 @@ impl TryFrom<RequestRow> for RequestStatus {
139152
let user_random_number = hex::FromHex::from_hex(row.user_random_number)?;
140153
let request_tx_hash = row.request_tx_hash.parse()?;
141154
let sender = row.sender.parse()?;
142-
let gas_limit = U256::from_dec_str(&row.gas_limit)
155+
let gas_limit = row
156+
.gas_limit
157+
.parse::<u32>()
143158
.map_err(|_| anyhow::anyhow!("Failed to parse gas limit"))?;
144159

145160
let state = match row.state.as_str() {
@@ -173,6 +188,18 @@ impl TryFrom<RequestRow> for RequestStatus {
173188
&user_random_number,
174189
&provider_random_number,
175190
),
191+
// Sqlx::Any doesn't support boolean types, so we need to convert from integer
192+
// https://github.com/launchbadge/sqlx/issues/2778
193+
callback_failed: row.callback_failed.unwrap_or(0) == 1,
194+
callback_return_value: row
195+
.callback_return_value
196+
.map(|s| s.parse::<Bytes>().unwrap_or_default())
197+
.unwrap_or_default(),
198+
callback_gas_used: row
199+
.callback_gas_used
200+
.unwrap_or_default()
201+
.parse::<u32>()
202+
.map_err(|_| anyhow::anyhow!("Failed to parse callback_gas_used"))?,
176203
}
177204
}
178205
"Failed" => RequestEntryState::Failed {
@@ -312,18 +339,29 @@ impl History {
312339
provider_random_number,
313340
gas_used,
314341
combined_random_number: _,
342+
callback_failed,
343+
callback_return_value,
344+
callback_gas_used,
315345
} => {
316346
let reveal_block_number = reveal_block_number as i64;
317347
let reveal_tx_hash: String = reveal_tx_hash.encode_hex();
318348
let provider_random_number: String = provider_random_number.encode_hex();
319349
let gas_used: String = gas_used.to_string();
320-
let result = sqlx::query("UPDATE request SET state = $1, last_updated_at = $2, reveal_block_number = $3, reveal_tx_hash = $4, provider_random_number = $5, gas_used = $6 WHERE network_id = $7 AND sequence = $8 AND provider = $9 AND request_tx_hash = $10")
350+
// Sqlx::Any doesn't support boolean types, so we need to convert to integer
351+
// https://github.com/launchbadge/sqlx/issues/2778
352+
let callback_failed: i64 = if callback_failed { 1 } else { 0 };
353+
let callback_return_value: String = callback_return_value.encode_hex();
354+
let callback_gas_used: String = callback_gas_used.to_string();
355+
let result = sqlx::query("UPDATE request SET state = $1, last_updated_at = $2, reveal_block_number = $3, reveal_tx_hash = $4, provider_random_number = $5, gas_used = $6, callback_failed = $7, callback_return_value = $8, callback_gas_used = $9 WHERE network_id = $10 AND sequence = $11 AND provider = $12 AND request_tx_hash = $13")
321356
.bind("Completed")
322357
.bind(new_status.last_updated_at.timestamp())
323358
.bind(reveal_block_number)
324359
.bind(reveal_tx_hash)
325360
.bind(provider_random_number)
326361
.bind(gas_used)
362+
.bind(callback_failed)
363+
.bind(callback_return_value)
364+
.bind(callback_gas_used)
327365
.bind(network_id)
328366
.bind(sequence)
329367
.bind(provider.clone())
@@ -646,7 +684,7 @@ mod test {
646684
user_random_number: [20; 32],
647685
sender: Address::random(),
648686
state: RequestEntryState::Pending,
649-
gas_limit: U256::from(500_000),
687+
gas_limit: 500_000,
650688
}
651689
}
652690

@@ -665,6 +703,9 @@ mod test {
665703
&status.user_random_number,
666704
&[40; 32],
667705
),
706+
callback_failed: false,
707+
callback_return_value: Default::default(),
708+
callback_gas_used: 100_000,
668709
};
669710
History::update_request_status(&history.pool, status.clone()).await;
670711

@@ -876,6 +917,9 @@ mod test {
876917
&status.user_random_number,
877918
&[40; 32],
878919
),
920+
callback_failed: false,
921+
callback_return_value: Default::default(),
922+
callback_gas_used: 0,
879923
};
880924
History::update_request_status(&history.pool, status.clone()).await;
881925
let mut failed_status = status.clone();

apps/fortuna/src/keeper/process_event.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ use {
33
crate::{
44
chain::{
55
ethereum::PythRandomErrorsErrors,
6-
reader::{RequestCallbackStatus, RequestedWithCallbackEvent},
6+
reader::{RequestCallbackStatus, RequestedV2Event},
77
},
88
eth_utils::utils::{submit_tx_with_backoff, SubmitTxError},
99
history::{RequestEntryState, RequestStatus},
1010
keeper::block::ProcessParams,
1111
},
1212
anyhow::{anyhow, Result},
13-
ethers::{abi::AbiDecode, contract::ContractError, types::U256},
13+
ethers::{abi::AbiDecode, contract::ContractError},
1414
std::time::Duration,
1515
tracing,
1616
};
@@ -20,7 +20,7 @@ use {
2020
sequence_number = event.sequence_number
2121
))]
2222
pub async fn process_event_with_backoff(
23-
event: RequestedWithCallbackEvent,
23+
event: RequestedV2Event,
2424
process_param: ProcessParams,
2525
) -> Result<()> {
2626
let ProcessParams {
@@ -110,10 +110,10 @@ pub async fn process_event_with_backoff(
110110
last_updated_at: chrono::Utc::now(),
111111
request_block_number: event.log_meta.block_number.as_u64(),
112112
request_tx_hash: event.log_meta.transaction_hash,
113-
sender: event.requestor,
113+
sender: event.sender,
114114
user_random_number: event.user_random_number,
115115
state: RequestEntryState::Pending,
116-
gas_limit: U256::from(0), // FIXME(Tejas): set this properly
116+
gas_limit: event.gas_limit,
117117
};
118118
history.add(&status);
119119

@@ -192,6 +192,9 @@ pub async fn process_event_with_backoff(
192192
&event.user_random_number,
193193
&provider_revelation,
194194
),
195+
callback_failed: result.revealed_event.callback_failed,
196+
callback_return_value: result.revealed_event.callback_return_value,
197+
callback_gas_used: result.revealed_event.callback_gas_used,
195198
};
196199
history.add(&status);
197200
tracing::info!(

0 commit comments

Comments
 (0)