2022-08-24 02:56:47 +03:00
|
|
|
//! Helper functions for turning ether's BlockNumber into numbers and updating incoming queries to match.
|
2023-10-11 12:00:50 +03:00
|
|
|
use crate::app::App;
|
2023-10-11 10:12:20 +03:00
|
|
|
use crate::jsonrpc::SingleRequest;
|
2023-06-29 03:42:43 +03:00
|
|
|
use crate::{
|
|
|
|
errors::{Web3ProxyError, Web3ProxyResult},
|
|
|
|
rpcs::blockchain::Web3ProxyBlock,
|
|
|
|
};
|
2022-09-22 02:50:55 +03:00
|
|
|
use anyhow::Context;
|
2023-06-29 03:42:43 +03:00
|
|
|
use derive_more::From;
|
2022-09-21 07:48:21 +03:00
|
|
|
use ethers::{
|
|
|
|
prelude::{BlockNumber, U64},
|
|
|
|
types::H256,
|
|
|
|
};
|
2022-12-17 07:05:01 +03:00
|
|
|
use serde_json::json;
|
2023-07-10 08:52:18 +03:00
|
|
|
use tracing::{error, trace, warn};
|
2022-08-10 05:37:34 +03:00
|
|
|
|
2022-12-20 02:59:01 +03:00
|
|
|
#[allow(non_snake_case)]
|
2023-10-03 23:46:27 +03:00
|
|
|
pub fn BlockNumber_to_U64(block_num: BlockNumber, latest_block: U64) -> (U64, bool) {
|
2022-08-10 05:37:34 +03:00
|
|
|
match block_num {
|
2023-01-31 19:30:24 +03:00
|
|
|
BlockNumber::Earliest => (U64::zero(), false),
|
2022-10-27 01:29:38 +03:00
|
|
|
BlockNumber::Finalized => {
|
|
|
|
warn!("finalized block requested! not yet implemented!");
|
2023-10-03 23:46:27 +03:00
|
|
|
(latest_block - 10, false)
|
2022-10-27 01:29:38 +03:00
|
|
|
}
|
2022-08-10 05:37:34 +03:00
|
|
|
BlockNumber::Latest => {
|
|
|
|
// change "latest" to a number
|
2023-10-03 23:46:27 +03:00
|
|
|
(latest_block, true)
|
2022-08-10 05:37:34 +03:00
|
|
|
}
|
2022-08-23 23:45:00 +03:00
|
|
|
BlockNumber::Number(x) => {
|
|
|
|
// we already have a number
|
2023-01-31 19:30:24 +03:00
|
|
|
(x, false)
|
2022-08-23 23:45:00 +03:00
|
|
|
}
|
2022-08-10 05:37:34 +03:00
|
|
|
BlockNumber::Pending => {
|
2023-01-31 19:30:24 +03:00
|
|
|
// modified is false because we want the backend to see "pending"
|
2022-08-10 05:37:34 +03:00
|
|
|
// TODO: think more about how to handle Pending
|
2023-10-03 23:46:27 +03:00
|
|
|
(latest_block, false)
|
2022-08-10 05:37:34 +03:00
|
|
|
}
|
2022-10-27 01:29:38 +03:00
|
|
|
BlockNumber::Safe => {
|
2023-06-29 03:42:43 +03:00
|
|
|
warn!("safe block requested! not yet implemented!");
|
2023-10-03 23:46:27 +03:00
|
|
|
(latest_block - 3, false)
|
2022-10-27 01:29:38 +03:00
|
|
|
}
|
2022-08-10 05:37:34 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-03 23:46:27 +03:00
|
|
|
#[derive(Clone, Debug, Eq, From, Hash, PartialEq)]
|
2023-06-29 03:42:43 +03:00
|
|
|
pub struct BlockNumAndHash(U64, H256);
|
|
|
|
|
|
|
|
impl BlockNumAndHash {
|
2023-10-28 01:41:24 +03:00
|
|
|
#[inline]
|
|
|
|
pub fn num(&self) -> U64 {
|
|
|
|
self.0
|
2023-06-29 03:42:43 +03:00
|
|
|
}
|
2023-10-28 01:41:24 +03:00
|
|
|
#[inline]
|
2023-06-29 03:42:43 +03:00
|
|
|
pub fn hash(&self) -> &H256 {
|
|
|
|
&self.1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&Web3ProxyBlock> for BlockNumAndHash {
|
|
|
|
fn from(value: &Web3ProxyBlock) -> Self {
|
2023-10-03 23:46:27 +03:00
|
|
|
let n = value.number();
|
2023-06-29 03:42:43 +03:00
|
|
|
let h = *value.hash();
|
2022-11-12 11:24:32 +03:00
|
|
|
|
2023-06-29 03:42:43 +03:00
|
|
|
Self(n, h)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-08 01:28:24 +03:00
|
|
|
/// modify params to always have a block hash and not "latest"
|
2023-10-11 06:54:17 +03:00
|
|
|
/// TODO: it would be nice to replace "latest" with the hash, but not all methods support that
|
2023-10-03 23:46:27 +03:00
|
|
|
pub async fn clean_block_number<'a>(
|
|
|
|
params: &'a mut serde_json::Value,
|
2022-08-10 05:37:34 +03:00
|
|
|
block_param_id: usize,
|
2023-10-03 23:46:27 +03:00
|
|
|
head_block: &'a Web3ProxyBlock,
|
2023-10-11 12:00:50 +03:00
|
|
|
app: Option<&'a App>,
|
2023-11-01 05:46:35 +03:00
|
|
|
) -> Web3ProxyResult<BlockNumOrHash> {
|
2022-08-10 05:37:34 +03:00
|
|
|
match params.as_array_mut() {
|
2022-09-10 03:58:33 +03:00
|
|
|
None => {
|
|
|
|
// TODO: this needs the correct error code in the response
|
2023-07-11 09:08:06 +03:00
|
|
|
Err(anyhow::anyhow!("params not an array").into())
|
2022-09-10 03:58:33 +03:00
|
|
|
}
|
2022-08-10 05:37:34 +03:00
|
|
|
Some(params) => match params.get_mut(block_param_id) {
|
|
|
|
None => {
|
2022-12-24 03:15:48 +03:00
|
|
|
if params.len() == block_param_id {
|
|
|
|
// add the latest block number to the end of the params
|
2023-10-03 23:46:27 +03:00
|
|
|
params.push(json!(head_block.number()));
|
2023-11-01 05:46:35 +03:00
|
|
|
|
|
|
|
Ok(head_block.into())
|
2022-12-24 03:15:48 +03:00
|
|
|
} else {
|
2023-11-01 05:46:35 +03:00
|
|
|
// don't modify the request
|
|
|
|
Err(Web3ProxyError::BadRequest(
|
|
|
|
"unexpected params length".into(),
|
|
|
|
))
|
2022-08-10 05:37:34 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Some(x) => {
|
2023-08-09 01:50:34 +03:00
|
|
|
// dig into the json value to find a BlockNumber or similar block identifier
|
|
|
|
trace!(?x, "inspecting");
|
|
|
|
|
2023-06-29 03:42:43 +03:00
|
|
|
let (block, change) = if let Some(obj) = x.as_object_mut() {
|
2022-09-22 02:50:55 +03:00
|
|
|
// it might be a Map like `{"blockHash": String("0xa5626dc20d3a0a209b1de85521717a3e859698de8ce98bca1b16822b7501f74b")}`
|
2023-02-25 10:31:10 +03:00
|
|
|
if let Some(block_hash) = obj.get("blockHash").cloned() {
|
2022-09-22 02:50:55 +03:00
|
|
|
let block_hash: H256 =
|
|
|
|
serde_json::from_value(block_hash).context("decoding blockHash")?;
|
|
|
|
|
2023-10-03 23:46:27 +03:00
|
|
|
if block_hash == *head_block.hash() {
|
|
|
|
(head_block.into(), false)
|
|
|
|
} else if let Some(app) = app {
|
2023-10-28 01:41:24 +03:00
|
|
|
// TODO: make a jsonrpc query here? cache rates will be better but it adds a network request
|
2023-10-03 23:46:27 +03:00
|
|
|
let block = app
|
|
|
|
.balanced_rpcs
|
2023-10-11 09:32:50 +03:00
|
|
|
.blocks_by_hash
|
|
|
|
.get(&block_hash)
|
2023-10-03 23:46:27 +03:00
|
|
|
.await
|
|
|
|
.context("fetching block number from hash")?;
|
2022-09-21 07:48:21 +03:00
|
|
|
|
2023-11-01 05:46:35 +03:00
|
|
|
(BlockNumOrHash::from(&block), false)
|
2023-10-03 23:46:27 +03:00
|
|
|
} else {
|
|
|
|
return Err(anyhow::anyhow!(
|
|
|
|
"app missing. cannot find block number from hash"
|
|
|
|
)
|
|
|
|
.into());
|
|
|
|
}
|
2022-09-21 07:48:21 +03:00
|
|
|
} else {
|
2023-07-11 09:08:06 +03:00
|
|
|
return Err(anyhow::anyhow!("blockHash missing").into());
|
2022-09-21 07:48:21 +03:00
|
|
|
}
|
|
|
|
} else {
|
2023-06-29 03:42:43 +03:00
|
|
|
// it might be a string like "latest" or a block number or a block hash
|
2022-09-22 02:50:55 +03:00
|
|
|
// TODO: "BlockNumber" needs a better name
|
2023-06-29 03:42:43 +03:00
|
|
|
// TODO: move this to a helper function?
|
2023-10-03 23:46:27 +03:00
|
|
|
let (block_num, changed) = if let Some(block_num) = x.as_u64() {
|
|
|
|
(U64::from(block_num), false)
|
|
|
|
} else if let Ok(block_num) = serde_json::from_value::<U64>(x.to_owned()) {
|
|
|
|
(block_num, false)
|
|
|
|
} else if let Ok(block_number) =
|
|
|
|
serde_json::from_value::<BlockNumber>(x.to_owned())
|
|
|
|
{
|
|
|
|
BlockNumber_to_U64(block_number, head_block.number())
|
|
|
|
} else if let Ok(block_hash) = serde_json::from_value::<H256>(x.clone()) {
|
|
|
|
if block_hash == *head_block.hash() {
|
|
|
|
(head_block.number(), false)
|
|
|
|
} else if let Some(app) = app {
|
2023-10-28 01:41:24 +03:00
|
|
|
// TODO: make a jsonrpc query here? cache rates will be better but it adds a network request
|
2023-10-03 23:46:27 +03:00
|
|
|
let block = app
|
|
|
|
.balanced_rpcs
|
2023-10-11 09:32:50 +03:00
|
|
|
.blocks_by_hash
|
|
|
|
.get(&block_hash)
|
2023-10-03 23:46:27 +03:00
|
|
|
.await
|
|
|
|
.context("fetching block number from hash")?;
|
|
|
|
|
|
|
|
(block.number(), false)
|
|
|
|
} else {
|
|
|
|
return Err(anyhow::anyhow!(
|
|
|
|
"app missing. cannot find block number from hash"
|
|
|
|
)
|
|
|
|
.into());
|
2023-09-20 05:22:28 +03:00
|
|
|
}
|
2023-10-03 23:46:27 +03:00
|
|
|
} else {
|
|
|
|
return Err(anyhow::anyhow!(
|
|
|
|
"param not a block identifier, block number, or block hash"
|
|
|
|
)
|
|
|
|
.into());
|
|
|
|
};
|
|
|
|
|
|
|
|
let head_block_num = head_block.number();
|
2023-09-20 05:22:28 +03:00
|
|
|
|
2023-10-03 23:46:27 +03:00
|
|
|
if block_num > head_block_num {
|
2023-10-13 09:57:12 +03:00
|
|
|
// todo!(option to wait for the block here)
|
2023-10-03 23:46:27 +03:00
|
|
|
return Err(Web3ProxyError::UnknownBlockNumber {
|
|
|
|
known: head_block_num,
|
|
|
|
unknown: block_num,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if block_num == head_block_num {
|
|
|
|
(head_block.into(), changed)
|
|
|
|
} else if let Some(app) = app {
|
2023-10-28 01:41:24 +03:00
|
|
|
// TODO: make a jsonrpc query here? cache rates will be better but it adds a network request
|
2023-11-01 05:56:33 +03:00
|
|
|
if let Some(block_hash) =
|
|
|
|
app.balanced_rpcs.blocks_by_number.get(&block_num).await
|
|
|
|
{
|
|
|
|
(BlockNumAndHash(block_num, block_hash).into(), changed)
|
|
|
|
} else {
|
|
|
|
(BlockNumOrHash::Num(block_num), changed)
|
|
|
|
}
|
2023-06-29 03:42:43 +03:00
|
|
|
} else {
|
2023-11-01 05:46:35 +03:00
|
|
|
(BlockNumOrHash::Num(block_num), changed)
|
2023-06-29 03:42:43 +03:00
|
|
|
}
|
2022-09-22 02:50:55 +03:00
|
|
|
};
|
2022-08-10 05:37:34 +03:00
|
|
|
|
2023-11-01 19:35:34 +03:00
|
|
|
// TODO: this scares me. we need it, so bring it back soon. but for now i think its breaking things
|
|
|
|
// // if we changed "latest" to an actual block, update the params to match
|
|
|
|
// // TODO: should we do hash or number? some functions work with either, but others need a number :cry:
|
|
|
|
// if change {
|
|
|
|
// trace!(old=%x, new=%block.num(), "changing block number");
|
|
|
|
// *x = json!(block.num());
|
|
|
|
// }
|
2022-12-20 00:53:38 +03:00
|
|
|
|
2023-06-29 03:42:43 +03:00
|
|
|
Ok(block)
|
2022-08-10 05:37:34 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-28 01:41:24 +03:00
|
|
|
#[derive(Debug, From, Hash, Eq, PartialEq)]
|
|
|
|
pub enum BlockNumOrHash {
|
|
|
|
Num(U64),
|
|
|
|
And(BlockNumAndHash),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BlockNumOrHash {
|
|
|
|
pub fn num(&self) -> U64 {
|
|
|
|
match self {
|
|
|
|
Self::Num(x) => *x,
|
|
|
|
Self::And(x) => x.num(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-01 05:04:20 +03:00
|
|
|
impl From<&Web3ProxyBlock> for BlockNumOrHash {
|
|
|
|
fn from(value: &Web3ProxyBlock) -> Self {
|
|
|
|
Self::And(value.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-17 07:05:01 +03:00
|
|
|
/// TODO: change this to also return the hash needed?
|
2023-10-03 23:46:27 +03:00
|
|
|
/// this replaces any "latest" identifiers in the JsonRpcRequest with the current block number which feels like the data is structured wrong
|
|
|
|
#[derive(Debug, Default, Hash, Eq, PartialEq)]
|
2023-06-29 03:42:43 +03:00
|
|
|
pub enum CacheMode {
|
2023-10-03 23:46:27 +03:00
|
|
|
SuccessForever,
|
|
|
|
Standard {
|
2023-11-01 05:04:20 +03:00
|
|
|
block_needed: BlockNumOrHash,
|
|
|
|
cache_block: BlockNumAndHash,
|
2023-06-29 03:42:43 +03:00
|
|
|
/// cache jsonrpc errors (server errors are never cached)
|
2023-01-31 19:30:24 +03:00
|
|
|
cache_errors: bool,
|
|
|
|
},
|
2023-10-03 23:46:27 +03:00
|
|
|
Range {
|
2023-10-28 01:41:24 +03:00
|
|
|
from_block: BlockNumOrHash,
|
|
|
|
to_block: BlockNumOrHash,
|
|
|
|
cache_block: BlockNumAndHash,
|
2023-06-29 03:42:43 +03:00
|
|
|
/// cache jsonrpc errors (server errors are never cached)
|
2023-01-31 19:30:24 +03:00
|
|
|
cache_errors: bool,
|
|
|
|
},
|
2023-10-03 23:46:27 +03:00
|
|
|
#[default]
|
|
|
|
Never,
|
2022-12-17 07:05:01 +03:00
|
|
|
}
|
2022-11-12 11:24:32 +03:00
|
|
|
|
2023-10-11 06:54:17 +03:00
|
|
|
/// TODO: i don't like this. we should make an enum with all of these methods and their types
|
|
|
|
/// TODO: serde tagged enums should work since the tag is the method
|
2023-08-09 01:50:34 +03:00
|
|
|
fn get_block_param_id(method: &str) -> Option<usize> {
|
|
|
|
match method {
|
|
|
|
"debug_traceBlockByHash" => Some(0),
|
|
|
|
"debug_traceBlockByNumber" => Some(0),
|
|
|
|
"debug_traceCall" => Some(1),
|
|
|
|
"debug_traceTransaction" => None,
|
|
|
|
"eth_call" => Some(1),
|
|
|
|
"eth_estimateGas" => Some(1),
|
|
|
|
"eth_feeHistory" => Some(1),
|
|
|
|
"eth_getBalance" => Some(1),
|
|
|
|
"eth_getBlockReceipts" => Some(0),
|
|
|
|
"eth_getBlockTransactionCountByNumber" => Some(0),
|
|
|
|
"eth_getCode" => Some(1),
|
|
|
|
"eth_getStorageAt" => Some(2),
|
|
|
|
"eth_getTransactionByBlockNumberAndIndex" => Some(0),
|
|
|
|
"eth_getTransactionCount" => Some(1),
|
|
|
|
"eth_getUncleByBlockNumberAndIndex" => Some(0),
|
|
|
|
"eth_getUncleCountByBlockNumber" => Some(0),
|
2023-08-16 02:37:53 +03:00
|
|
|
"trace_block" => Some(0),
|
2023-08-09 01:50:34 +03:00
|
|
|
"trace_call" => Some(2),
|
2023-08-16 02:37:53 +03:00
|
|
|
"trace_callMany" => Some(1),
|
2023-08-09 01:50:34 +03:00
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-29 03:42:43 +03:00
|
|
|
impl CacheMode {
|
2023-10-03 23:46:27 +03:00
|
|
|
/// like `try_new`, but instead of erroring, it will default to caching with the head block
|
|
|
|
/// returns None if this request should not be cached
|
|
|
|
pub async fn new<'a>(
|
2023-10-11 10:12:20 +03:00
|
|
|
request: &'a mut SingleRequest,
|
2023-10-03 23:46:27 +03:00
|
|
|
head_block: Option<&'a Web3ProxyBlock>,
|
2023-10-11 12:00:50 +03:00
|
|
|
app: Option<&'a App>,
|
2023-06-29 03:42:43 +03:00
|
|
|
) -> Self {
|
2023-10-03 23:46:27 +03:00
|
|
|
match Self::try_new(request, head_block, app).await {
|
2023-10-11 10:12:20 +03:00
|
|
|
Ok(x) => return x,
|
2023-08-09 01:50:34 +03:00
|
|
|
Err(Web3ProxyError::NoBlocksKnown) => {
|
2023-10-03 23:46:27 +03:00
|
|
|
warn!(
|
|
|
|
method = %request.method,
|
|
|
|
params = ?request.params,
|
2023-10-11 09:32:50 +03:00
|
|
|
"no servers available to get block from params but head block known. caching with head block"
|
2023-10-03 23:46:27 +03:00
|
|
|
);
|
2023-08-09 01:50:34 +03:00
|
|
|
}
|
2023-06-29 03:42:43 +03:00
|
|
|
Err(err) => {
|
2023-10-03 23:46:27 +03:00
|
|
|
error!(
|
|
|
|
method = %request.method,
|
|
|
|
params = ?request.params,
|
|
|
|
?err,
|
|
|
|
"could not get block from params. caching with head block"
|
|
|
|
);
|
2023-06-29 03:42:43 +03:00
|
|
|
}
|
2023-10-11 09:32:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(head_block) = head_block {
|
2023-10-28 01:41:24 +03:00
|
|
|
Self::Standard {
|
2023-11-01 05:04:20 +03:00
|
|
|
block_needed: head_block.into(),
|
|
|
|
cache_block: head_block.into(),
|
2023-10-11 09:32:50 +03:00
|
|
|
cache_errors: true,
|
|
|
|
}
|
|
|
|
} else {
|
2023-10-28 01:41:24 +03:00
|
|
|
Self::Never
|
2023-06-29 03:42:43 +03:00
|
|
|
}
|
2023-05-31 02:32:34 +03:00
|
|
|
}
|
2022-08-10 05:37:34 +03:00
|
|
|
|
2023-06-29 03:42:43 +03:00
|
|
|
pub async fn try_new(
|
2023-10-11 10:12:20 +03:00
|
|
|
request: &mut SingleRequest,
|
2023-10-03 23:46:27 +03:00
|
|
|
head_block: Option<&Web3ProxyBlock>,
|
2023-10-11 12:00:50 +03:00
|
|
|
app: Option<&App>,
|
2023-06-29 03:42:43 +03:00
|
|
|
) -> Web3ProxyResult<Self> {
|
2023-10-03 23:46:27 +03:00
|
|
|
let params = &mut request.params;
|
|
|
|
|
|
|
|
if head_block.is_none() {
|
|
|
|
// since we don't have a head block, i don't trust our anything enough to cache
|
|
|
|
return Ok(Self::Never);
|
|
|
|
}
|
|
|
|
|
|
|
|
let head_block = head_block.expect("head_block was just checked above");
|
|
|
|
|
2023-11-01 05:04:20 +03:00
|
|
|
if matches!(params, serde_json::Value::Null) {
|
|
|
|
// no params given. cache with the head block
|
|
|
|
return Ok(Self::Standard {
|
|
|
|
block_needed: head_block.into(),
|
|
|
|
cache_block: head_block.into(),
|
|
|
|
cache_errors: true,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-08-16 02:20:25 +03:00
|
|
|
if let Some(params) = params.as_array() {
|
|
|
|
if params.is_empty() {
|
|
|
|
// no params given. cache with the head block
|
2023-10-03 23:46:27 +03:00
|
|
|
return Ok(Self::Standard {
|
2023-11-01 05:04:20 +03:00
|
|
|
block_needed: head_block.into(),
|
|
|
|
cache_block: head_block.into(),
|
2023-08-16 02:20:25 +03:00
|
|
|
cache_errors: true,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-11 12:00:50 +03:00
|
|
|
match request.method.as_ref() {
|
2023-08-16 02:37:53 +03:00
|
|
|
"debug_traceTransaction" => {
|
|
|
|
// TODO: make sure re-orgs work properly!
|
2023-10-28 01:41:24 +03:00
|
|
|
Ok(Self::SuccessForever)
|
2023-08-16 02:37:53 +03:00
|
|
|
}
|
2023-11-01 05:04:20 +03:00
|
|
|
"eth_gasPrice" => Ok(Self::Never),
|
2023-06-29 03:42:43 +03:00
|
|
|
"eth_getBlockByHash" => {
|
|
|
|
// TODO: double check that any node can serve this
|
|
|
|
// TODO: can a block change? like what if it gets orphaned?
|
2023-07-28 20:38:12 +03:00
|
|
|
// TODO: make sure re-orgs work properly!
|
2023-10-28 01:41:24 +03:00
|
|
|
Ok(Self::SuccessForever)
|
2023-06-29 03:42:43 +03:00
|
|
|
}
|
|
|
|
"eth_getBlockByNumber" => {
|
|
|
|
// TODO: double check that any node can serve this
|
|
|
|
// TODO: CacheSuccessForever if the block is old enough
|
2023-07-28 20:38:12 +03:00
|
|
|
// TODO: make sure re-orgs work properly!
|
2023-10-28 01:41:24 +03:00
|
|
|
Ok(Self::Standard {
|
2023-11-01 05:04:20 +03:00
|
|
|
block_needed: head_block.into(),
|
|
|
|
cache_block: head_block.into(),
|
2023-06-29 03:42:43 +03:00
|
|
|
cache_errors: true,
|
2023-08-09 01:50:34 +03:00
|
|
|
})
|
2023-06-29 03:42:43 +03:00
|
|
|
}
|
|
|
|
"eth_getBlockTransactionCountByHash" => {
|
|
|
|
// TODO: double check that any node can serve this
|
2023-10-28 01:41:24 +03:00
|
|
|
Ok(Self::SuccessForever)
|
2023-06-29 03:42:43 +03:00
|
|
|
}
|
|
|
|
"eth_getLogs" => {
|
2023-10-03 23:46:27 +03:00
|
|
|
// TODO: think about this more
|
2023-06-29 03:42:43 +03:00
|
|
|
// TODO: jsonrpc has a specific code for this
|
|
|
|
let obj = params
|
|
|
|
.get_mut(0)
|
|
|
|
.ok_or_else(|| Web3ProxyError::BadRequest("invalid format. no params".into()))?
|
|
|
|
.as_object_mut()
|
|
|
|
.ok_or_else(|| {
|
|
|
|
Web3ProxyError::BadRequest("invalid format. params not object".into())
|
|
|
|
})?;
|
2022-08-10 05:37:34 +03:00
|
|
|
|
2023-06-29 03:42:43 +03:00
|
|
|
if obj.contains_key("blockHash") {
|
2023-10-28 01:41:24 +03:00
|
|
|
Ok(Self::SuccessForever)
|
2023-06-29 03:42:43 +03:00
|
|
|
} else {
|
|
|
|
let from_block = if let Some(x) = obj.get_mut("fromBlock") {
|
|
|
|
// TODO: use .take instead of clone
|
|
|
|
// what if its a hash?
|
|
|
|
let block_num: BlockNumber = serde_json::from_value(x.clone())?;
|
2022-08-10 05:37:34 +03:00
|
|
|
|
2023-06-29 03:42:43 +03:00
|
|
|
let (block_num, change) =
|
|
|
|
BlockNumber_to_U64(block_num, head_block.number());
|
2022-08-10 05:37:34 +03:00
|
|
|
|
2023-10-30 06:35:25 +03:00
|
|
|
// TODO: double check this. it scares me
|
|
|
|
// if change {
|
|
|
|
// // TODO: include the hash instead of the number?
|
|
|
|
// trace!("changing fromBlock in eth_getLogs. {} -> {}", x, block_num);
|
|
|
|
// *x = json!(block_num);
|
|
|
|
// }
|
2022-08-10 05:37:34 +03:00
|
|
|
|
2023-10-28 01:41:24 +03:00
|
|
|
BlockNumOrHash::Num(block_num)
|
2023-06-29 03:42:43 +03:00
|
|
|
} else {
|
2023-10-28 01:41:24 +03:00
|
|
|
BlockNumOrHash::Num(U64::zero())
|
2023-06-29 03:42:43 +03:00
|
|
|
};
|
2022-08-10 05:37:34 +03:00
|
|
|
|
2023-06-29 03:42:43 +03:00
|
|
|
let to_block = if let Some(x) = obj.get_mut("toBlock") {
|
|
|
|
// TODO: use .take instead of clone
|
|
|
|
// what if its a hash?
|
|
|
|
let block_num: BlockNumber = serde_json::from_value(x.clone())?;
|
2022-08-10 05:37:34 +03:00
|
|
|
|
2023-10-28 01:41:24 +03:00
|
|
|
// sometimes people request `from_block=future, to_block=latest`. latest becomes head and then
|
|
|
|
// TODO: if this is in the future, this cache key won't be very likely to be used again
|
|
|
|
// TODO: delay here until the app has this block?
|
|
|
|
let latest_block = head_block.number().max(from_block.num());
|
|
|
|
|
|
|
|
let (block_num, change) = BlockNumber_to_U64(block_num, latest_block);
|
2022-08-10 05:37:34 +03:00
|
|
|
|
2023-10-30 06:35:25 +03:00
|
|
|
// TODO: double check this. it scares me
|
|
|
|
// if change {
|
|
|
|
// trace!("changing toBlock in eth_getLogs. {} -> {}", x, block_num);
|
|
|
|
// *x = json!(block_num);
|
|
|
|
// }
|
2023-01-31 02:47:17 +03:00
|
|
|
|
2023-10-28 01:41:24 +03:00
|
|
|
if let Some(app) = app {
|
|
|
|
// TODO: make a jsonrpc query here? cache rates will be better but it adds a network request
|
|
|
|
if let Some(block_hash) =
|
|
|
|
app.balanced_rpcs.blocks_by_number.get(&block_num).await
|
|
|
|
{
|
|
|
|
BlockNumOrHash::And(BlockNumAndHash(block_num, block_hash))
|
|
|
|
} else {
|
|
|
|
BlockNumOrHash::Num(block_num)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
BlockNumOrHash::Num(block_num)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
BlockNumOrHash::And(head_block.into())
|
|
|
|
};
|
2023-06-29 04:36:17 +03:00
|
|
|
|
2023-10-28 01:41:24 +03:00
|
|
|
let cache_block = if let BlockNumOrHash::And(x) = &to_block {
|
|
|
|
x.clone()
|
2023-06-29 03:42:43 +03:00
|
|
|
} else {
|
2023-10-28 01:41:24 +03:00
|
|
|
BlockNumAndHash::from(head_block)
|
2023-06-29 03:42:43 +03:00
|
|
|
};
|
2023-01-31 19:30:24 +03:00
|
|
|
|
2023-10-28 01:41:24 +03:00
|
|
|
Ok(Self::Range {
|
2023-06-29 03:42:43 +03:00
|
|
|
from_block,
|
|
|
|
to_block,
|
2023-10-28 01:41:24 +03:00
|
|
|
cache_block,
|
2023-06-29 03:42:43 +03:00
|
|
|
cache_errors: true,
|
2023-08-09 01:50:34 +03:00
|
|
|
})
|
2023-06-29 03:42:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
"eth_getTransactionByBlockHashAndIndex" => {
|
|
|
|
// TODO: check a Cache of recent hashes
|
|
|
|
// try full nodes first. retry will use archive
|
2023-10-28 01:41:24 +03:00
|
|
|
Ok(Self::SuccessForever)
|
2023-06-29 03:42:43 +03:00
|
|
|
}
|
2023-10-28 01:41:24 +03:00
|
|
|
"eth_getTransactionByHash" => Ok(Self::Never),
|
|
|
|
"eth_getTransactionReceipt" => Ok(Self::Never),
|
2023-06-29 03:42:43 +03:00
|
|
|
"eth_getUncleByBlockHashAndIndex" => {
|
|
|
|
// TODO: check a Cache of recent hashes
|
|
|
|
// try full nodes first. retry will use archive
|
|
|
|
// TODO: what happens if this block is uncled later?
|
2023-10-28 01:41:24 +03:00
|
|
|
Ok(Self::SuccessForever)
|
2023-06-29 03:42:43 +03:00
|
|
|
}
|
|
|
|
"eth_getUncleCountByBlockHash" => {
|
|
|
|
// TODO: check a Cache of recent hashes
|
|
|
|
// try full nodes first. retry will use archive
|
|
|
|
// TODO: what happens if this block is uncled later?
|
2023-10-28 01:41:24 +03:00
|
|
|
Ok(Self::SuccessForever)
|
2023-06-29 03:42:43 +03:00
|
|
|
}
|
2023-07-28 20:38:12 +03:00
|
|
|
"eth_maxPriorityFeePerGas" => {
|
|
|
|
// TODO: this might be too aggressive. i think it can change before a block is mined
|
2023-11-01 05:04:20 +03:00
|
|
|
Ok(Self::Never)
|
2023-06-29 03:42:43 +03:00
|
|
|
}
|
2023-10-28 01:41:24 +03:00
|
|
|
"eth_sendRawTransaction" => Ok(Self::Never),
|
|
|
|
"net_listening" => Ok(Self::SuccessForever),
|
|
|
|
"net_version" => Ok(Self::SuccessForever),
|
2023-08-09 01:50:34 +03:00
|
|
|
method => match get_block_param_id(method) {
|
|
|
|
Some(block_param_id) => {
|
2023-11-01 05:46:35 +03:00
|
|
|
let block_needed =
|
|
|
|
clean_block_number(params, block_param_id, head_block, app).await?;
|
|
|
|
|
|
|
|
let cache_block = match &block_needed {
|
|
|
|
BlockNumOrHash::And(block) => block.clone(),
|
|
|
|
BlockNumOrHash::Num(_) => head_block.into(),
|
|
|
|
};
|
2023-08-09 01:50:34 +03:00
|
|
|
|
2023-10-28 01:41:24 +03:00
|
|
|
Ok(Self::Standard {
|
2023-11-01 05:46:35 +03:00
|
|
|
block_needed,
|
|
|
|
cache_block,
|
2023-08-09 01:50:34 +03:00
|
|
|
cache_errors: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
None => Err(Web3ProxyError::UnhandledMethod(method.to_string().into())),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2023-10-03 23:46:27 +03:00
|
|
|
|
2023-10-24 01:38:44 +03:00
|
|
|
#[inline]
|
2023-10-03 23:46:27 +03:00
|
|
|
pub fn cache_jsonrpc_errors(&self) -> bool {
|
|
|
|
match self {
|
|
|
|
Self::Never => false,
|
|
|
|
Self::SuccessForever => true,
|
|
|
|
Self::Standard { cache_errors, .. } => *cache_errors,
|
|
|
|
Self::Range { cache_errors, .. } => *cache_errors,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-24 01:38:44 +03:00
|
|
|
#[inline]
|
2023-10-28 01:41:24 +03:00
|
|
|
pub fn from_block(&self) -> Option<&BlockNumOrHash> {
|
2023-10-03 23:46:27 +03:00
|
|
|
match self {
|
|
|
|
Self::SuccessForever => None,
|
|
|
|
Self::Never => None,
|
2023-10-14 05:41:59 +03:00
|
|
|
Self::Standard { .. } => None,
|
2023-10-03 23:46:27 +03:00
|
|
|
Self::Range { from_block, .. } => Some(from_block),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn is_some(&self) -> bool {
|
|
|
|
!matches!(self, Self::Never)
|
|
|
|
}
|
|
|
|
|
2023-11-01 05:04:20 +03:00
|
|
|
#[inline]
|
|
|
|
pub fn to_block(&self) -> Option<&BlockNumOrHash> {
|
|
|
|
match self {
|
|
|
|
Self::SuccessForever => None,
|
|
|
|
Self::Never => None,
|
|
|
|
Self::Standard {
|
|
|
|
block_needed: block,
|
|
|
|
..
|
|
|
|
} => Some(block),
|
|
|
|
Self::Range { to_block, .. } => Some(to_block),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-28 01:41:24 +03:00
|
|
|
/// get the to_block used **for caching**. This may be the to_block in the request, or it might be the current head block.
|
2023-10-24 01:38:44 +03:00
|
|
|
#[inline]
|
2023-11-01 05:04:20 +03:00
|
|
|
pub fn cache_block(&self) -> Option<&BlockNumAndHash> {
|
2023-10-03 23:46:27 +03:00
|
|
|
match self {
|
|
|
|
Self::SuccessForever => None,
|
|
|
|
Self::Never => None,
|
2023-11-01 05:04:20 +03:00
|
|
|
Self::Standard { cache_block, .. } => Some(cache_block),
|
2023-10-28 01:41:24 +03:00
|
|
|
Self::Range { cache_block, .. } => Some(cache_block),
|
2023-10-03 23:46:27 +03:00
|
|
|
}
|
|
|
|
}
|
2023-08-09 01:50:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::CacheMode;
|
2023-10-03 23:46:27 +03:00
|
|
|
use crate::{
|
|
|
|
errors::Web3ProxyError,
|
2023-10-11 10:12:20 +03:00
|
|
|
jsonrpc::{LooseId, SingleRequest},
|
2023-10-03 23:46:27 +03:00
|
|
|
rpcs::blockchain::Web3ProxyBlock,
|
|
|
|
};
|
2023-08-09 01:50:34 +03:00
|
|
|
use ethers::types::{Block, H256};
|
|
|
|
use serde_json::json;
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
#[test_log::test(tokio::test)]
|
|
|
|
async fn test_fee_history() {
|
|
|
|
let method = "eth_feeHistory";
|
2023-10-03 23:46:27 +03:00
|
|
|
let params = json!([4, "latest", [25, 75]]);
|
2023-08-09 01:50:34 +03:00
|
|
|
|
|
|
|
let head_block = Block {
|
|
|
|
number: Some(1.into()),
|
|
|
|
hash: Some(H256::random()),
|
|
|
|
..Default::default()
|
2023-06-29 03:42:43 +03:00
|
|
|
};
|
2022-08-10 05:37:34 +03:00
|
|
|
|
2023-08-09 01:50:34 +03:00
|
|
|
let head_block = Web3ProxyBlock::try_new(Arc::new(head_block)).unwrap();
|
|
|
|
|
2023-10-11 10:12:20 +03:00
|
|
|
let id = LooseId::Number(9);
|
2023-08-09 01:50:34 +03:00
|
|
|
|
2023-10-11 12:00:50 +03:00
|
|
|
let mut request = SingleRequest::new(id, method.into(), params).unwrap();
|
2023-10-03 23:46:27 +03:00
|
|
|
|
|
|
|
// TODO: instead of empty, check None?
|
|
|
|
let x = CacheMode::try_new(&mut request, Some(&head_block), None)
|
2023-08-09 01:50:34 +03:00
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
x,
|
2023-10-03 23:46:27 +03:00
|
|
|
CacheMode::Standard {
|
2023-11-01 05:04:20 +03:00
|
|
|
block_needed: (&head_block).into(),
|
|
|
|
cache_block: (&head_block).into(),
|
2023-08-09 01:50:34 +03:00
|
|
|
cache_errors: true
|
2023-06-29 03:42:43 +03:00
|
|
|
}
|
2023-08-09 01:50:34 +03:00
|
|
|
);
|
|
|
|
|
2023-08-09 01:58:21 +03:00
|
|
|
// "latest" should have been changed to the block number
|
2023-10-03 23:46:27 +03:00
|
|
|
assert_eq!(request.params.get(1), Some(&json!(head_block.number())));
|
2022-08-10 05:37:34 +03:00
|
|
|
}
|
2023-09-20 05:22:28 +03:00
|
|
|
|
|
|
|
#[test_log::test(tokio::test)]
|
|
|
|
async fn test_eth_call_latest() {
|
|
|
|
let method = "eth_call";
|
|
|
|
|
2023-10-03 23:46:27 +03:00
|
|
|
let params = json!([{"data": "0xdeadbeef", "to": "0x0000000000000000000000000000000000000000"}, "latest"]);
|
2023-09-20 05:22:28 +03:00
|
|
|
|
|
|
|
let head_block = Block {
|
|
|
|
number: Some(18173997.into()),
|
|
|
|
hash: Some(H256::random()),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
let head_block = Web3ProxyBlock::try_new(Arc::new(head_block)).unwrap();
|
|
|
|
|
2023-10-11 10:12:20 +03:00
|
|
|
let id = LooseId::Number(99);
|
2023-10-03 23:46:27 +03:00
|
|
|
|
2023-10-11 12:00:50 +03:00
|
|
|
let mut request = SingleRequest::new(id, method.into(), params).unwrap();
|
2023-09-20 05:22:28 +03:00
|
|
|
|
2023-10-03 23:46:27 +03:00
|
|
|
let x = CacheMode::try_new(&mut request, Some(&head_block), None)
|
2023-09-20 05:22:28 +03:00
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// "latest" should have been changed to the block number
|
2023-10-03 23:46:27 +03:00
|
|
|
assert_eq!(request.params.get(1), Some(&json!(head_block.number())));
|
2023-09-20 05:22:28 +03:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
x,
|
2023-10-03 23:46:27 +03:00
|
|
|
CacheMode::Standard {
|
2023-11-01 05:04:20 +03:00
|
|
|
block_needed: (&head_block).into(),
|
|
|
|
cache_block: (&head_block).into(),
|
2023-09-20 05:22:28 +03:00
|
|
|
cache_errors: true
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2023-10-03 23:46:27 +03:00
|
|
|
|
|
|
|
#[test_log::test(tokio::test)]
|
|
|
|
async fn test_eth_call_future() {
|
|
|
|
let method = "eth_call";
|
|
|
|
|
|
|
|
let head_block_num = 18173997u64;
|
|
|
|
let future_block_num = head_block_num + 1;
|
|
|
|
|
|
|
|
let params = json!([{"data": "0xdeadbeef", "to": "0x0000000000000000000000000000000000000000"}, future_block_num]);
|
|
|
|
|
|
|
|
let head_block: Block<H256> = Block {
|
|
|
|
number: Some(head_block_num.into()),
|
|
|
|
hash: Some(H256::random()),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
let head_block = Web3ProxyBlock::try_new(Arc::new(head_block)).unwrap();
|
|
|
|
|
2023-10-11 12:00:50 +03:00
|
|
|
let mut request = SingleRequest::new(99.into(), method.into(), params).unwrap();
|
2023-10-03 23:46:27 +03:00
|
|
|
|
|
|
|
let x = CacheMode::try_new(&mut request, Some(&head_block), None)
|
|
|
|
.await
|
|
|
|
.unwrap_err();
|
|
|
|
|
|
|
|
// future blocks should get an error
|
|
|
|
match x {
|
|
|
|
Web3ProxyError::UnknownBlockNumber { known, unknown } => {
|
|
|
|
assert_eq!(known.as_u64(), head_block_num);
|
|
|
|
assert_eq!(unknown.as_u64(), future_block_num);
|
|
|
|
}
|
|
|
|
x => panic!("{:?}", x),
|
|
|
|
}
|
|
|
|
|
|
|
|
let x = CacheMode::new(&mut request, Some(&head_block), None).await;
|
|
|
|
|
|
|
|
// TODO: cache with the head block instead?
|
|
|
|
matches!(x, CacheMode::Never);
|
|
|
|
}
|
2022-08-10 05:37:34 +03:00
|
|
|
}
|