2023-06-20 03:47:38 +03:00
|
|
|
//! Compute Units based on median request latencies and sizes.
|
|
|
|
//! Designed to match Alchemy's system.
|
|
|
|
//! I'm sure there will be memes about us copying, but the user experience of consistency makes a lot of sense to me.
|
|
|
|
//! TODO? pricing based on latency and bytes and
|
|
|
|
//! TODO: rate limit on compute units
|
|
|
|
//! TODO: pricing on compute units
|
|
|
|
//! TODO: script that queries influx and calculates observed relative costs
|
|
|
|
|
|
|
|
use migration::sea_orm::prelude::Decimal;
|
2023-09-13 07:44:23 +03:00
|
|
|
use std::{ops::Add, str::FromStr};
|
2023-07-12 10:35:07 +03:00
|
|
|
use tracing::{instrument, trace, warn};
|
2023-06-20 03:47:38 +03:00
|
|
|
|
2023-07-12 10:35:07 +03:00
|
|
|
pub fn default_usd_per_cu(chain_id: u64) -> Decimal {
|
|
|
|
match chain_id {
|
|
|
|
999_001_999 => Decimal::from_str("0.10").unwrap(),
|
2023-09-02 21:32:10 +03:00
|
|
|
1 | 31337 => Decimal::from_str("0.000000400000000000000").unwrap(),
|
2023-08-31 07:02:31 +03:00
|
|
|
_ => Decimal::from_str("0.000000533333333333333").unwrap(),
|
2023-07-12 10:35:07 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-19 00:18:05 +03:00
|
|
|
pub fn default_cu_per_byte(_chain_id: u64, method: &str) -> Decimal {
|
|
|
|
match method {
|
|
|
|
"eth_subscribe(newPendingTransactions)" => Decimal::new(16, 2),
|
|
|
|
_ => Decimal::new(4, 2),
|
|
|
|
}
|
2023-09-18 22:26:52 +03:00
|
|
|
}
|
|
|
|
|
2023-07-12 10:35:07 +03:00
|
|
|
#[derive(Debug)]
|
2023-06-20 03:47:38 +03:00
|
|
|
pub struct ComputeUnit(Decimal);
|
|
|
|
|
2023-09-13 07:44:23 +03:00
|
|
|
impl<T> Add<T> for ComputeUnit
|
|
|
|
where
|
|
|
|
T: Into<Decimal>,
|
|
|
|
{
|
|
|
|
type Output = Self;
|
|
|
|
|
|
|
|
fn add(self, rhs: T) -> Self::Output {
|
|
|
|
Self(self.0 + rhs.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-20 03:47:38 +03:00
|
|
|
impl ComputeUnit {
|
|
|
|
/// costs can vary widely depending on method and chain
|
2023-07-12 10:35:07 +03:00
|
|
|
#[instrument(level = "trace")]
|
2023-06-27 07:09:33 +03:00
|
|
|
pub fn new(method: &str, chain_id: u64, response_bytes: u64) -> Self {
|
|
|
|
// TODO: this works, but this is fragile. think of a better way to check the method is a subscription
|
|
|
|
if method.ends_with(')') {
|
2023-09-18 22:26:52 +03:00
|
|
|
return Self::variable_price(chain_id, method, response_bytes);
|
2023-06-27 07:09:33 +03:00
|
|
|
}
|
|
|
|
|
2023-08-22 22:59:01 +03:00
|
|
|
let cu = if method.starts_with("admin_") || method.starts_with("alchemy_") {
|
2023-07-11 01:13:49 +03:00
|
|
|
// maybe charge extra since they are doing things they aren't supposed to
|
|
|
|
return Self::unimplemented();
|
2023-06-29 20:24:05 +03:00
|
|
|
} else {
|
|
|
|
match (chain_id, method) {
|
|
|
|
(1101, "zkevm_batchNumber") => 0,
|
|
|
|
(1101, "zkevm_batchNumberByBlockNumber") => 0,
|
|
|
|
(1101, "zkevm_consolidatedBlockNumber") => 0,
|
|
|
|
(1101, "zkevm_getBatchByNumber") => 0,
|
|
|
|
(1101, "zkevm_getBroadcastURI") => 0,
|
|
|
|
(1101, "zkevm_isBlockConsolidated") => 0,
|
|
|
|
(1101, "zkevm_isBlockVirtualized") => 0,
|
|
|
|
(1101, "zkevm_verifiedBatchNumber") => 0,
|
|
|
|
(1101, "zkevm_virtualBatchNumber") => 0,
|
|
|
|
(137, "bor_getAuthor") => 10,
|
|
|
|
(137, "bor_getCurrentProposer") => 10,
|
|
|
|
(137, "bor_getCurrentValidators") => 10,
|
|
|
|
(137, "bor_getRootHash") => 10,
|
|
|
|
(137, "bor_getSignersAtHash") => 10,
|
|
|
|
(_, "debug_traceBlockByHash") => 497,
|
|
|
|
(_, "debug_traceBlockByNumber") => 497,
|
|
|
|
(_, "debug_traceCall") => 309,
|
|
|
|
(_, "debug_traceTransaction") => 309,
|
|
|
|
(_, "erigon_forks") => 24,
|
|
|
|
(_, "erigon_getHeaderByHash") => 24,
|
|
|
|
(_, "erigon_getHeaderByNumber") => 24,
|
|
|
|
(_, "erigon_getLogsByHash") => 24,
|
|
|
|
(_, "erigon_issuance") => 24,
|
|
|
|
(_, "eth_accounts") => 10,
|
|
|
|
(_, "eth_blockNumber") => 10,
|
|
|
|
(_, "eth_call") => 26,
|
|
|
|
(_, "eth_chainId") => 0,
|
|
|
|
(_, "eth_createAccessList") => 10,
|
|
|
|
(_, "eth_estimateGas") => 87,
|
|
|
|
(_, "eth_estimateUserOperationGas") => 500,
|
|
|
|
(_, "eth_feeHistory") => 10,
|
|
|
|
(_, "eth_gasPrice") => 19,
|
|
|
|
(_, "eth_getBalance") => 19,
|
|
|
|
(_, "eth_getBlockByHash") => 21,
|
|
|
|
(_, "eth_getBlockByNumber") => 16,
|
|
|
|
(_, "eth_getBlockReceipts") => 500,
|
|
|
|
(_, "eth_getBlockTransactionCountByHash") => 20,
|
|
|
|
(_, "eth_getBlockTransactionCountByNumber") => 20,
|
|
|
|
(_, "eth_getCode") => 19,
|
|
|
|
(_, "eth_getFilterChanges") => 20,
|
|
|
|
(_, "eth_getFilterLogs") => 75,
|
|
|
|
(_, "eth_getLogs") => 75,
|
|
|
|
(_, "eth_getProof") => 21,
|
|
|
|
(_, "eth_getStorageAt") => 17,
|
|
|
|
(_, "eth_getTransactionByBlockHashAndIndex") => 15,
|
|
|
|
(_, "eth_getTransactionByBlockNumberAndIndex") => 15,
|
|
|
|
(_, "eth_getTransactionByHash") => 17,
|
|
|
|
(_, "eth_getTransactionCount") => 26,
|
|
|
|
(_, "eth_getTransactionReceipt") => 15,
|
|
|
|
(_, "eth_getUncleByBlockHashAndIndex") => 15,
|
|
|
|
(_, "eth_getUncleByBlockNumberAndIndex") => 15,
|
|
|
|
(_, "eth_getUncleCountByBlockHash") => 15,
|
|
|
|
(_, "eth_getUncleCountByBlockNumber") => 15,
|
|
|
|
(_, "eth_getUserOperationByHash") => 17,
|
|
|
|
(_, "eth_getUserOperationReceipt") => 15,
|
|
|
|
(_, "eth_maxPriorityFeePerGas") => 10,
|
2023-09-13 07:44:23 +03:00
|
|
|
(_, "eth_newBlockFilter") => {
|
|
|
|
// TODO: 20
|
|
|
|
return Self::unimplemented();
|
|
|
|
}
|
|
|
|
(_, "eth_newFilter") => {
|
|
|
|
// TODO: 20
|
|
|
|
return Self::unimplemented();
|
|
|
|
}
|
|
|
|
(_, "eth_newPendingTransactionFilter") => {
|
|
|
|
// TODO: 20
|
|
|
|
return Self::unimplemented();
|
|
|
|
}
|
2023-06-29 20:24:05 +03:00
|
|
|
(_, "eth_pollSubscriptions") => {
|
|
|
|
return Self::unimplemented();
|
|
|
|
}
|
|
|
|
(_, "eth_protocolVersion") => 0,
|
|
|
|
(_, "eth_sendRawTransaction") => 250,
|
|
|
|
(_, "eth_sendUserOperation") => 1000,
|
|
|
|
(_, "eth_subscribe") => 10,
|
|
|
|
(_, "eth_supportedEntryPoints") => 5,
|
|
|
|
(_, "eth_syncing") => 0,
|
|
|
|
(_, "eth_uninstallFilter") => 10,
|
|
|
|
(_, "eth_unsubscribe") => 10,
|
|
|
|
(_, "net_listening") => 0,
|
|
|
|
(_, "net_version") => 0,
|
|
|
|
(_, "test") => 0,
|
|
|
|
(_, "trace_block") => 24,
|
|
|
|
(_, "trace_call") => 75,
|
2023-07-07 22:34:12 +03:00
|
|
|
(_, "trace_callMany") => 75 * 3,
|
2023-06-29 20:24:05 +03:00
|
|
|
(_, "trace_filter") => 75,
|
|
|
|
(_, "trace_get") => 17,
|
|
|
|
(_, "trace_rawTransaction") => 75,
|
|
|
|
(_, "trace_replayBlockTransactions") => 2983,
|
|
|
|
(_, "trace_replayTransaction") => 2983,
|
|
|
|
(_, "trace_transaction") => 26,
|
2023-09-13 07:44:23 +03:00
|
|
|
(_, "txpool_content") => {
|
2023-09-18 22:26:52 +03:00
|
|
|
return Self::variable_price(chain_id, method, response_bytes) + 1000;
|
2023-09-13 07:44:23 +03:00
|
|
|
}
|
2023-07-20 23:19:50 +03:00
|
|
|
(_, "invalid_method") => 100,
|
2023-06-29 20:24:05 +03:00
|
|
|
(_, "web3_clientVersion") => 15,
|
2023-09-02 20:30:42 +03:00
|
|
|
(_, "web3_bundlerVersion") => 15,
|
2023-06-29 20:24:05 +03:00
|
|
|
(_, "web3_sha3") => 15,
|
2023-07-26 07:06:03 +03:00
|
|
|
(_, "ots_getInternalOperations") => 1000,
|
|
|
|
(_, "ots_hasCode") => 1000,
|
|
|
|
(_, "ots_getTransactionError") => 1000,
|
|
|
|
(_, "ots_traceTransaction") => 1000,
|
|
|
|
(_, "ots_getBlockDetails") => 1000,
|
|
|
|
(_, "ots_getBlockDetailsByHash") => 1000,
|
|
|
|
(_, "ots_getBlockTransactions") => 1000,
|
|
|
|
(_, "ots_searchTransactionsBefore") => 1000,
|
|
|
|
(_, "ots_searchTransactionsAfter") => 1000,
|
|
|
|
(_, "ots_getTransactionBySenderAndNonce") => 1000,
|
|
|
|
(_, "ots_getContractCreator") => 1000,
|
2023-06-29 20:24:05 +03:00
|
|
|
(_, method) => {
|
2023-07-06 13:51:39 +03:00
|
|
|
// TODO: emit a stat
|
|
|
|
// TODO: do some filtering on these to tarpit peers that are trying to do silly things like sql/influx inject
|
2023-06-29 20:24:05 +03:00
|
|
|
warn!("unknown method {}", method);
|
|
|
|
return Self::unimplemented();
|
|
|
|
}
|
2023-06-20 03:47:38 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let cu = Decimal::from(cu);
|
|
|
|
|
2023-07-12 10:35:07 +03:00
|
|
|
trace!(%cu);
|
|
|
|
|
2023-06-20 03:47:38 +03:00
|
|
|
Self(cu)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// notifications and subscription responses cost per-byte
|
2023-07-12 10:35:07 +03:00
|
|
|
#[instrument(level = "trace")]
|
2023-09-18 22:26:52 +03:00
|
|
|
pub fn variable_price<D: Into<Decimal> + std::fmt::Debug>(
|
|
|
|
chain_id: u64,
|
|
|
|
method: &str,
|
|
|
|
num_bytes: D,
|
|
|
|
) -> Self {
|
2023-09-19 00:18:05 +03:00
|
|
|
let cu = num_bytes.into() * default_cu_per_byte(chain_id, method);
|
2023-06-20 03:47:38 +03:00
|
|
|
|
|
|
|
Self(cu)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// requesting an unimplemented function costs 2 CU
|
|
|
|
pub fn unimplemented() -> Self {
|
|
|
|
Self(2.into())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Compute cost per request
|
|
|
|
/// All methods cost the same
|
|
|
|
/// The number of bytes are based on input, and output bytes
|
2023-07-12 10:35:07 +03:00
|
|
|
#[instrument(level = "trace")]
|
2023-07-06 04:18:10 +03:00
|
|
|
pub fn cost(
|
|
|
|
&self,
|
|
|
|
archive_request: bool,
|
|
|
|
cache_hit: bool,
|
|
|
|
error_response: bool,
|
2023-07-12 10:35:07 +03:00
|
|
|
usd_per_cu: &Decimal,
|
2023-07-06 04:18:10 +03:00
|
|
|
) -> Decimal {
|
|
|
|
if error_response {
|
2023-07-12 10:35:07 +03:00
|
|
|
trace!("error responses are free");
|
2023-07-06 04:18:10 +03:00
|
|
|
return 0.into();
|
|
|
|
}
|
2023-06-27 07:09:33 +03:00
|
|
|
|
2023-06-20 03:47:38 +03:00
|
|
|
let mut cost = self.0 * usd_per_cu;
|
|
|
|
|
2023-07-12 10:35:07 +03:00
|
|
|
trace!(%cost, "base");
|
|
|
|
|
2023-06-21 00:22:14 +03:00
|
|
|
if archive_request {
|
2023-07-12 10:35:07 +03:00
|
|
|
// TODO: get from config
|
2023-06-21 00:22:14 +03:00
|
|
|
cost *= Decimal::from_str("2.5").unwrap();
|
2023-07-12 10:35:07 +03:00
|
|
|
|
|
|
|
trace!(%cost, "archive_request");
|
2023-06-21 00:22:14 +03:00
|
|
|
}
|
|
|
|
|
2023-06-20 03:47:38 +03:00
|
|
|
if cache_hit {
|
2023-07-12 10:35:07 +03:00
|
|
|
// cache hits get a 25% discount
|
|
|
|
// TODO: get from config
|
|
|
|
cost *= Decimal::from_str("0.75").unwrap();
|
|
|
|
|
|
|
|
trace!(%cost, "cache_hit");
|
2023-06-20 03:47:38 +03:00
|
|
|
}
|
|
|
|
|
2023-07-12 10:35:07 +03:00
|
|
|
trace!(%cost, "final");
|
|
|
|
|
2023-06-20 03:47:38 +03:00
|
|
|
cost
|
|
|
|
}
|
|
|
|
}
|