web3-proxy/web3_proxy/src/block_number.rs

288 lines
10 KiB
Rust
Raw Normal View History

//! Helper functions for turning ether's BlockNumber into numbers and updating incoming queries to match.
2022-09-22 02:50:55 +03:00
use anyhow::Context;
use ethers::{
prelude::{BlockNumber, U64},
types::H256,
};
2023-02-27 10:52:37 +03:00
use log::{trace, warn};
2022-12-17 07:05:01 +03:00
use serde_json::json;
use std::sync::Arc;
2022-08-10 05:37:34 +03:00
use crate::{frontend::authorization::Authorization, rpcs::many::Web3Rpcs};
2022-09-22 02:50:55 +03:00
2022-12-20 02:59:01 +03:00
#[allow(non_snake_case)]
pub fn block_num_to_U64(block_num: BlockNumber, latest_block: U64) -> (U64, bool) {
2022-08-10 05:37:34 +03:00
match block_num {
BlockNumber::Earliest => (U64::zero(), false),
2022-10-27 01:29:38 +03:00
BlockNumber::Finalized => {
warn!("finalized block requested! not yet implemented!");
(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
(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
(x, false)
2022-08-23 23:45:00 +03:00
}
2022-08-10 05:37:34 +03:00
BlockNumber::Pending => {
// 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
(latest_block, false)
2022-08-10 05:37:34 +03:00
}
2022-10-27 01:29:38 +03:00
BlockNumber::Safe => {
warn!("finalized block requested! not yet implemented!");
(latest_block - 3, false)
2022-10-27 01:29:38 +03:00
}
2022-08-10 05:37:34 +03:00
}
}
/// modify params to always have a block number and not "latest"
2022-11-12 11:24:32 +03:00
2022-09-22 02:50:55 +03:00
pub async fn clean_block_number(
authorization: &Arc<Authorization>,
2022-08-10 05:37:34 +03:00
params: &mut serde_json::Value,
block_param_id: usize,
latest_block: U64,
rpcs: &Web3Rpcs,
2022-08-10 05:37:34 +03:00
) -> anyhow::Result<U64> {
match params.as_array_mut() {
2022-09-10 03:58:33 +03:00
None => {
// TODO: this needs the correct error code in the response
Err(anyhow::anyhow!("params not an array"))
}
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-01-31 20:47:19 +03:00
params.push(json!(latest_block));
2022-12-24 03:15:48 +03:00
} else {
// don't modify the request. only cache with current block
// TODO: more useful log that include the
warn!("unexpected params length");
2022-08-10 05:37:34 +03:00
}
2022-12-24 03:15:48 +03:00
// don't modify params, just cache with the current block
2022-08-10 05:37:34 +03:00
Ok(latest_block)
}
Some(x) => {
// convert the json value to a BlockNumber
let (block_num, 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-01-31 20:47:19 +03:00
let block = rpcs
.block(authorization, &block_hash, None)
.await
.context("fetching block number from hash")?;
2023-02-25 10:31:10 +03:00
// TODO: we do not change the
(*block.number(), false)
} else {
2022-09-22 02:50:55 +03:00
return Err(anyhow::anyhow!("blockHash missing"));
}
} else {
2022-09-22 02:50:55 +03:00
// it might be a string like "latest" or a block number
// TODO: "BlockNumber" needs a better name
2023-01-31 22:05:29 +03:00
// TODO: use take instead of clone
let block_number = serde_json::from_value::<BlockNumber>(x.clone())
2023-01-31 20:47:19 +03:00
.context("checking params for BlockNumber")?;
2022-08-10 05:37:34 +03:00
2022-12-20 00:53:38 +03:00
block_num_to_U64(block_number, latest_block)
2022-09-22 02:50:55 +03:00
};
2022-08-10 05:37:34 +03:00
// if we changed "latest" to a number, update the params to match
if change {
2023-01-31 20:47:19 +03:00
*x = json!(block_num);
2022-12-20 00:53:38 +03:00
}
2022-08-10 05:37:34 +03:00
Ok(block_num)
}
},
}
}
2022-12-17 07:05:01 +03:00
/// TODO: change this to also return the hash needed?
pub enum BlockNeeded {
CacheSuccessForever,
CacheNever,
Cache {
block_num: U64,
cache_errors: bool,
},
CacheRange {
from_block_num: U64,
to_block_num: U64,
cache_errors: bool,
},
2022-12-17 07:05:01 +03:00
}
2022-11-12 11:24:32 +03:00
2022-09-22 02:50:55 +03:00
pub async fn block_needed(
authorization: &Arc<Authorization>,
2022-08-10 05:37:34 +03:00
method: &str,
params: Option<&mut serde_json::Value>,
2022-09-07 06:54:16 +03:00
head_block_num: U64,
rpcs: &Web3Rpcs,
2022-12-17 07:05:01 +03:00
) -> anyhow::Result<BlockNeeded> {
// some requests have potentially very large responses
// TODO: only skip caching if the response actually is large
if method.starts_with("trace_") || method == "debug_traceTransaction" {
return Ok(BlockNeeded::CacheNever);
}
2022-09-30 07:18:18 +03:00
let params = if let Some(params) = params {
// grab the params so we can inspect and potentially modify them
2022-09-30 07:18:18 +03:00
params
} else {
// if no params, no block is needed
2022-12-17 07:05:01 +03:00
// TODO: check all the methods with no params, some might not be cacheable
// caching with the head block /should/ always be okay
2022-12-17 07:05:01 +03:00
return Ok(BlockNeeded::Cache {
block_num: head_block_num,
cache_errors: true,
});
2022-09-30 07:18:18 +03:00
};
2022-08-10 05:37:34 +03:00
2023-01-31 20:47:19 +03:00
// get the index for the BlockNumber
2022-08-23 23:45:00 +03:00
// The BlockNumber is usually the last element.
2022-08-10 05:37:34 +03:00
// TODO: double check these. i think some of the getBlock stuff will never need archive
let block_param_id = match method {
"eth_call" => 1,
"eth_estimateGas" => 1,
"eth_getBalance" => 1,
"eth_getBlockByHash" => {
// TODO: double check that any node can serve this
2022-12-17 07:05:01 +03:00
// TODO: can a block change? like what if it gets orphaned?
return Ok(BlockNeeded::CacheSuccessForever);
2022-08-10 05:37:34 +03:00
}
"eth_getBlockByNumber" => {
// TODO: double check that any node can serve this
2022-12-17 07:05:01 +03:00
// TODO: CacheSuccessForever if the block is old enough
return Ok(BlockNeeded::Cache {
block_num: head_block_num,
cache_errors: true,
});
2022-08-10 05:37:34 +03:00
}
2022-11-26 06:53:30 +03:00
"eth_getBlockReceipts" => 0,
2022-08-10 05:37:34 +03:00
"eth_getBlockTransactionCountByHash" => {
// TODO: double check that any node can serve this
2022-12-17 07:05:01 +03:00
return Ok(BlockNeeded::CacheSuccessForever);
2022-08-10 05:37:34 +03:00
}
"eth_getBlockTransactionCountByNumber" => 0,
"eth_getCode" => 1,
"eth_getLogs" => {
2022-12-17 07:05:01 +03:00
// TODO: think about this more
2022-09-30 07:18:18 +03:00
// TODO: jsonrpc has a specific code for this
2023-02-27 09:59:42 +03:00
// TODO: this shouldn't be a 500. this should be a 400. 500 will make haproxy retry a bunch
2022-09-30 07:18:18 +03:00
let obj = params[0]
.as_object_mut()
.ok_or_else(|| anyhow::anyhow!("invalid format"))?;
2022-08-10 05:37:34 +03:00
if obj.contains_key("blockHash") {
2023-02-27 09:59:42 +03:00
return Ok(BlockNeeded::CacheSuccessForever);
} else {
let from_block_num = if let Some(x) = obj.get_mut("fromBlock") {
2023-01-31 20:47:19 +03:00
// TODO: use .take instead of clone
let block_num: BlockNumber = serde_json::from_value(x.clone())?;
2022-08-10 05:37:34 +03:00
let (block_num, change) = block_num_to_U64(block_num, head_block_num);
2022-08-10 05:37:34 +03:00
if change {
2023-02-27 09:59:42 +03:00
trace!("changing fromBlock in eth_getLogs. {} -> {}", x, block_num);
*x = json!(block_num);
}
2022-08-10 05:37:34 +03:00
block_num
} else {
let (block_num, _) = block_num_to_U64(BlockNumber::Earliest, head_block_num);
2022-08-10 05:37:34 +03:00
block_num
};
2022-08-10 05:37:34 +03:00
let to_block_num = if let Some(x) = obj.get_mut("toBlock") {
2023-01-31 20:47:19 +03:00
// TODO: use .take instead of clone
let block_num: BlockNumber = serde_json::from_value(x.clone())?;
2022-08-10 05:37:34 +03:00
let (block_num, change) = block_num_to_U64(block_num, head_block_num);
2022-08-10 05:37:34 +03:00
if change {
2023-02-27 09:59:42 +03:00
trace!("changing toBlock in eth_getLogs. {} -> {}", x, block_num);
*x = json!(block_num);
}
block_num
} else {
head_block_num
};
return Ok(BlockNeeded::CacheRange {
2023-02-11 07:45:57 +03:00
from_block_num,
to_block_num,
2022-12-17 07:05:01 +03:00
cache_errors: true,
});
2022-08-10 05:37:34 +03:00
}
}
"eth_getStorageAt" => 2,
"eth_getTransactionByHash" => {
// TODO: not sure how best to look these up
// try full nodes first. retry will use archive
2022-12-17 07:05:01 +03:00
return Ok(BlockNeeded::Cache {
block_num: head_block_num,
cache_errors: true,
});
2022-08-10 05:37:34 +03:00
}
"eth_getTransactionByBlockHashAndIndex" => {
2022-09-05 08:53:58 +03:00
// TODO: check a Cache of recent hashes
2022-08-10 05:37:34 +03:00
// try full nodes first. retry will use archive
2022-12-17 07:05:01 +03:00
return Ok(BlockNeeded::CacheSuccessForever);
2022-08-10 05:37:34 +03:00
}
"eth_getTransactionByBlockNumberAndIndex" => 0,
"eth_getTransactionCount" => 1,
"eth_getTransactionReceipt" => {
// TODO: not sure how best to look these up
// try full nodes first. retry will use archive
2022-12-17 07:05:01 +03:00
return Ok(BlockNeeded::Cache {
block_num: head_block_num,
cache_errors: true,
});
2022-08-10 05:37:34 +03:00
}
"eth_getUncleByBlockHashAndIndex" => {
2022-09-05 08:53:58 +03:00
// TODO: check a Cache of recent hashes
2022-08-10 05:37:34 +03:00
// try full nodes first. retry will use archive
2022-12-17 07:05:01 +03:00
return Ok(BlockNeeded::CacheSuccessForever);
2022-08-10 05:37:34 +03:00
}
"eth_getUncleByBlockNumberAndIndex" => 0,
"eth_getUncleCountByBlockHash" => {
2022-09-05 08:53:58 +03:00
// TODO: check a Cache of recent hashes
2022-08-10 05:37:34 +03:00
// try full nodes first. retry will use archive
2022-12-17 07:05:01 +03:00
return Ok(BlockNeeded::CacheSuccessForever);
2022-08-10 05:37:34 +03:00
}
"eth_getUncleCountByBlockNumber" => 0,
_ => {
// some other command that doesn't take block numbers as an argument
2022-12-17 07:05:01 +03:00
// since we are caching with the head block, it should be safe to cache_errors
return Ok(BlockNeeded::Cache {
block_num: head_block_num,
cache_errors: true,
});
2022-08-10 05:37:34 +03:00
}
};
match clean_block_number(authorization, params, block_param_id, head_block_num, rpcs).await {
2022-12-17 07:05:01 +03:00
Ok(block_num) => Ok(BlockNeeded::Cache {
block_num,
cache_errors: true,
}),
2022-08-10 05:37:34 +03:00
Err(err) => {
2022-11-12 11:24:32 +03:00
warn!("could not get block from params. err={:?}", err);
2022-12-17 07:05:01 +03:00
Ok(BlockNeeded::Cache {
block_num: head_block_num,
cache_errors: true,
})
2022-08-10 05:37:34 +03:00
}
}
}