508 lines
45 KiB
Markdown
508 lines
45 KiB
Markdown
# Todo
|
||
|
||
## MVP
|
||
|
||
These are roughly in order of completition
|
||
|
||
- [x] simple proxy
|
||
- [x] better locking. when lots of requests come in, we seem to be in the way of block updates
|
||
- [x] load balance between multiple RPC servers
|
||
- [x] support more than just ETH
|
||
- [x] option to disable private rpc and send everything to primary
|
||
- [x] support websocket clients
|
||
- we support websockets for the backends already, but we need them for the frontend too
|
||
- [x] health check nodes by block height
|
||
- [x] Dockerfile
|
||
- [x] docker-compose.yml
|
||
- [x] after connecting to a server, check that it gives the expected chainId
|
||
- [x] the ethermine rpc is usually fastest. but its in the private tier. since we only allow synced rpcs, we are going to not have an rpc a lot of the time
|
||
- [x] if not backends. return a 502 instead of delaying?
|
||
- [x] move from warp to axum
|
||
- [x] handle websocket disconnect and reconnect
|
||
- [x] eth_sendRawTransaction should return the most common result, not the first
|
||
- [x] use redis and redis-cell for rate limits
|
||
- [x] it works for a few seconds and then gets stuck on something.
|
||
- [x] its working with one backend node, but multiple breaks. something to do with pending transactions
|
||
- [x] dashmap entry api is easy to deadlock! be careful with it!
|
||
- [x] the web3proxyapp object gets cloned for every call. why do we need any arcs inside that? shouldn't they be able to connect to the app's? can we just use static lifetimes
|
||
- [x] refactor Connection::spawn. have it return a handle to the spawned future of it running with block and transaction subscriptions
|
||
- [x] refactor Connections::spawn. have it return a handle that is selecting on those handles?
|
||
- [x] some production configs are occassionally stuck waiting at 100% cpu
|
||
- they stop processing new blocks. i'm guessing 2 blocks arrive at the same time, but i thought our locks would handle that
|
||
- even after removing a bunch of the locks, the deadlock still happens. i can't reliably reproduce. i just let it run for awhile and it happens.
|
||
- running gdb shows the thread at tokio tungstenite thread is spinning near 100% cpu and none of the rest of the program is proceeding
|
||
- fixed by https://github.com/gakonst/ethers-rs/pull/1287
|
||
- [x] when sending with private relays, brownie's tx.wait can think the transaction was dropped. smarter retry on eth_getTransactionByHash and eth_getTransactionReceipt (maybe only if we sent the transaction ourselves)
|
||
- [x] if web3 proxy gets an http error back, retry another node
|
||
- [x] endpoint for health checks. if no synced servers, give a 502 error
|
||
- [x] rpc errors propagate too far. one subscription failing ends the app. isolate the providers more (might already be fixed)
|
||
- [x] incoming rate limiting (by ip)
|
||
- [x] connection pool for redis
|
||
- [x] automatically route to archive server when necessary
|
||
- originally, no processing was done to params; they were just serde_json::RawValue. this is probably fastest, but we need to look for "latest" and count elements, so we have to use serde_json::Value
|
||
- when getting the next server, filtering on "archive" isn't going to work well. need to check inner instead
|
||
- [x] if the requested block is ahead of the best block, return without querying any backend servers
|
||
- [x] http servers should check block at the very start
|
||
- [x] subscription id should be per connection, not global
|
||
- [x] when under load, i'm seeing "http interval lagging!". sometimes it happens when not loaded.
|
||
- we were skipping our delay interval when block hash wasn't changed. so if a block was ever slow, the http provider would get the same hash twice and then would try eth_getBlockByNumber a ton of times
|
||
- [x] inspect any jsonrpc errors. if its something like "header not found" or "block with id $x not found" retry on another node (and add a negative score to that server)
|
||
- this error seems to happen when we use load balanced backend rpcs like pokt and ankr
|
||
- [x] RESPONSE_CACHE_CAP in bytes instead of number of entries
|
||
- [x] if we don't cache errors, then in-flight request caching is going to bottleneck
|
||
- i think now that we retry header not found and similar, caching errors should be fine
|
||
- [x] RESPONSE_CACHE_CAP from config
|
||
- [x] web3_sha3 rpc command
|
||
- [x] test that launches anvil and connects the proxy to it and does some basic queries
|
||
- [x] need to have some sort of shutdown signaling. doesn't need to be graceful at this point, but should be eventually
|
||
- [x] if the fastest server has hit rate limits, we won't be able to serve any traffic until another server is synced.
|
||
- thundering herd problem if we only allow a lag of 0 blocks
|
||
- we can improve this by only publishing the synced connections once a threshold of total available soft and hard limits is passed. how can we do this without hammering redis? at least its only once per block per server
|
||
- [x] instead of tracking `pending_synced_connections`, have a mapping of where all connections are individually. then each change, re-check for consensus.
|
||
- [x] synced connections swap threshold set to 1 so that it always serves something
|
||
- [x] cli tool for creating new users
|
||
- [x] incoming rate limiting by api key
|
||
- [x] sort forked blocks by total difficulty like geth does
|
||
- [x] refactor result type on active handlers to use a cleaner success/error so we can use the try operator
|
||
- [x] give users different rate limits looked up from the database
|
||
- [x] Add a "weight" key to the servers. Sort on that after block. keep most requests local
|
||
- [x] cache db query results for user data. db is a big bottleneck right now
|
||
- [x] allow blocking public requests
|
||
- [x] Got warning: "WARN subscribe_new_heads:send_block: web3_proxy::connection: unable to get block from https://rpc.ethermine.org: Deserialization Error: expected value at line 1 column 1. Response: error code: 1015". this is cloudflare rate limiting on fetching a block, but this is a private rpc. why is there a block subscription?
|
||
- [x] im seeing ethspam occasionally try to query a future block. something must be setting the head block too early
|
||
- [x] we were sorting best block the wrong direction. i flipped a.cmp(b) to b.cmp(a) so that the largest would be first, but then i used 'max_by' which looks at the end of the list
|
||
- [x] HTTP GET to the websocket endpoints should redirect instead of giving an ugly error
|
||
- [x] load the redirected page from config
|
||
- [x] prettier output for create_user command. need the key in hex
|
||
- [x] drop redis-cell in favor of a simpler (and faster) implementation.
|
||
- redis-cell was giving me weird errors and it isn't worth debugging it right now.
|
||
- [x] create user script should allow setting the api key
|
||
- [x] disable redis persistence in dev
|
||
- [x] attach a request id to every web request
|
||
- [x] attach user id (not IP!) to each request
|
||
- [x] fantom_1 | 2022-08-10T22:19:43.522465Z WARN web3_proxy::jsonrpc: forwarding error err=missing field `jsonrpc` at line 1 column 60
|
||
- [x] i think the server isn't following the spec. we need a context attached to more errors so we know which one
|
||
- [x] make jsonrpc default to "2.0" (including the custom deserializer that handles the RawValues)
|
||
- [x] if the eth_call (or similar) params include a block, we can cache for that
|
||
- [x] when block subscribers receive blocks, store them in a block_map
|
||
- [x] eth_blockNumber without a backend request
|
||
- [x] if we send a transaction to private rpcs and then people query it on public rpcs things, some interfaces might think the transaction is dropped (i saw this happen in a brownie script of mine). how should we handle this?
|
||
- [x] send getTransaction rpc requests to the private rpc tier
|
||
- [x] I'm hitting infura rate limits very quickly. I feel like that means something is very inefficient
|
||
- whenever blocks were slow, we started checking as fast as possible
|
||
- [x] create user script should allow setting requests per minute
|
||
- [x] cache api keys that are not in the database
|
||
- [x] improve consensus block selection. Our goal is to find the highest work chain with a block over a minimum threshold of sum_soft_limit.
|
||
- [x] i saw a fork of like 300 blocks. probably just because a node was restarted and had fallen behind. need some checks to ignore things that are far behind. this improvement should fix this problem
|
||
- [x] A new block arrives at a connection.
|
||
- [x] It checks that it isn't the same that it already has (which is a problem with polling nodes)
|
||
- [x] If its new to this node...
|
||
- [x] if the block does not have total work, check our cache. otherwise, query the node
|
||
- [x] save the block num and hash so that http polling doesn't send duplicates
|
||
- [x] send the deduped block through a channel to be handled by the connections grouping.
|
||
- [x] The connections group...
|
||
- [x] input = rpc, new_block
|
||
- [x] adds the block and rpc to it's internal maps
|
||
- [x] connection_heads: HashMap<rpc_name, blockhash>
|
||
- [x] block_map: DashMap<blockhash, Arc<Block>>
|
||
- [x] block_num: DashMap<U64, H256>
|
||
- [x] blockchain: DiGraphMap<blockhash, ?>
|
||
- [x] iterate the rpc_map to find the highest_work_block
|
||
- [x] update synced connections
|
||
- [x] send the block through new head_block_sender
|
||
- [x] rewrite cannonical_block to work as long as there are no forks
|
||
- [x] rewrite cannonical_block (again) and related functions to handle forks
|
||
- [x] got a very large number of possible heads here. i think maybe a server was very far out of sync. we should drop servers behind by too much
|
||
eth_1 | 2022-08-10T23:26:06.377129Z WARN web3_proxy::connections: chain is forked! 261 possible heads. 1/2/5/5 rpcs have 0xd403…3c5d
|
||
eth_1 | 2022-08-10T23:26:08.917603Z WARN web3_proxy::connections: chain is forked! 262 possible heads. 1/2/5/5 rpcs have 0x0538…bfff
|
||
eth_1 | 2022-08-10T23:26:10.195014Z WARN web3_proxy::connections: chain is forked! 262 possible heads. 1/2/5/5 rpcs have 0x0538…bfff
|
||
eth_1 | 2022-08-10T23:26:10.195658Z WARN web3_proxy::connections: chain is forked! 262 possible heads. 2/3/5/5 rpcs have 0x0538…bfff
|
||
- [x] todo!("handle equal") and also less and greater
|
||
- [x] "chain is forked" message is wrong. it includes nodes just being on different heights of the same chain. need a smarter check
|
||
- i think there is also a bug because i've seen "server not synced" a couple times
|
||
- [x] bug around eth_getBlockByHash sometimes causes tokio to lock up
|
||
- i keep a mapping of blocks so that i can go from hash -> block. it has some consistent hashing it does to split them up across multiple maps each with their own lock. so a lot of the time reads dont block writes because they are in different internal maps. this was fine. but after changing my fork detection logic to use the same rules as erigon, i discovered that when you get blocks from a websocket subscription in erigon and geth, theres a missing field (https://github.com/ledgerwatch/erigon/issues/5190). so i added a query to get the block that includes the missing field.
|
||
- but i did this in a way where i was holding the write lock open while doing the query. the "new" block that has the missing field ends up in the same bucket and it also wants a write lock. oops. entry api has very sharp edges. don't ever await inside a match on DashMap::entry
|
||
- [x] requests for "Get transactions receipts" are routed to the private_rpcs and not the balanced_rpcs. do this better.
|
||
- [x] quick fix, send to balanced_rpcs for now. we will just live with errors on new transactions.
|
||
- this was intentional so that recently confirmed transactions go to a server that is more likely to have the tx.
|
||
- but under heavy load, we hit their rate limits. need a "retry_until_success" function that goes to balanced_rpcs. or maybe store in redis the txids that we broadcast privately and use that to route.
|
||
- [x] some of the DashMaps grow unbounded! Make/find a "SizedDashMap" that cleans up old rows with some garbage collection task
|
||
- moka is exactly what we need
|
||
- [x] if block data limit is 0, say Unknown in Debug output
|
||
- [x] basic request method stats (using the user_id and other fields that are in the tracing frame)
|
||
- [x] refactor from_anyhow_error to have consistent error codes and http codes. maybe implement the Error trait
|
||
- [x] improve rpc weights. i think theres still a potential thundering herd
|
||
- [x] improved logging with useful instrumentation
|
||
- [x] right now the block_map is unbounded. move this to redis and do some calculations to be sure about RAM usage
|
||
- [x] synced connections swap threshold should come from config
|
||
- [x] right now we send too many getTransaction queries to the private rpc tier and i are being rate limited by some of them. change to be serial and weight by hard/soft limit.
|
||
- [x] ip blocking gives a 500 and not the proper error code
|
||
- [x] need a reconnect that doesn't unwrap
|
||
- [x] need a retrying_reconnect that is used everywhere reconnect is. have exponential backoff here
|
||
- [x] it looks like our reconnect logic is not always firing. we need to make reconnect more robust!
|
||
- i am pretty sure that this is actually servers that fail to connect on initial setup (maybe the rpcs that are on the wrong chain are just timing out and they aren't set to reconnect?)
|
||
- [x] chain rolled back 1/1/1 con_head=15510065 (0xa4a3…d2d8) rpc_head=15510065 (0xa4a3…d2d8) rpc=local_erigon_archive
|
||
- include the old head number and block in the log
|
||
- [x] exponential backoff when reconnecting a connection
|
||
- [x] once the merge happens, we don't want to use total difficulty and instead just care about the number
|
||
- [x] rewrite rate limiting to have a tiered cache. do not put redis in the hot path
|
||
- instead, we should check a local cache for the current rate limit (+1) and spawn an update to the local cache from redis in the background.
|
||
- [x] when there are a LOT of concurrent requests, we see errors. i thought that was a problem with redis cell, but it happens with my simpler rate limit. now i think the problem is actually with bb8
|
||
- https://docs.rs/redis/latest/redis/aio/struct.ConnectionManager.html or https://crates.io/crates/deadpool-redis?
|
||
- WARN http_request: redis_rate_limit::errors: redis error err=Response was of incompatible type: "Response type not string compatible." (response was int(500237)) id=01GC6514JWN5PS1NCWJCGJTC94 method=POST
|
||
- [x] web3_proxy_error_count{path = "backend_rpc/request"} is inflated by a bunch of reverts. do not log reverts as warn.
|
||
- erigon gives `method=eth_call reqid=986147 t=1.151551ms err="execution reverted"`
|
||
- [x] database migration to change user_keys.requests_per_minute to bigunsigned (max of 18446744073709551615)
|
||
- [x] change user creation script to have a "unlimited requests per minute" flag that sets it to u64::MAX (18446744073709551615)
|
||
- [x] in /status, block hashes has a lower count than block numbers. how is that possible?
|
||
- we weren't calling sync. now we are
|
||
- [x] opt-in debug mode that inspects responses for reverts and saves the request to the database for the user.
|
||
- [x] Api keys need option to lock to IP, cors header, referer, user agent, etc
|
||
- [x] /user/logout to clear bearer token and jwt
|
||
- [x] bearer tokens should expire
|
||
- [x] login endpoint needs its own rate limiter
|
||
- we don't want an rpc request limit of 0 to block logins
|
||
- for security, we want these limits low.
|
||
- [x] user login should return the bearer token and the user keys
|
||
- [x] use siwe messages and signatures for sign up and login
|
||
- [x] check for bearer token on /rpc
|
||
- [x] ip blocking logs a warn. we don't need that
|
||
- [x] Ulid instead of Uuid for user keys
|
||
- <https://discord.com/channels/873880840487206962/900758376164757555/1012942974608474142>
|
||
- since users are actively using our service, we will need to support both
|
||
- [x] get to /, when not serving a websocket, should have a simple welcome page. maybe with a button to update your wallet.
|
||
- [x] instead of giving a rate limit error code, delay the connection's response at the start. reject if incoming requests is super high?
|
||
- [x] did this by checking a key/ip-specific semaphore before checking rate limits
|
||
- [x] emit user stat on cache hit
|
||
- [x] emit user stat on cache miss
|
||
- [x] have migration use tokio instead of async-std
|
||
- [x] user create script should allow a description field
|
||
- [x] change stats to using the database
|
||
- [x] emit user stat on retry
|
||
- [x] improve `web3_proxy_cli check_config`
|
||
- print out warnings if important settings are missing
|
||
- [x] if unknown config items, error
|
||
- unknown configs are almost always a mistake. usually from me changing config parsing on my side and old fields not being updated to the new way
|
||
- [x] also need to change how we disable rpcs since i was using an unknown field
|
||
- [x] [paginate responses](https://www.sea-ql.org/SeaORM/docs/basic-crud/select/#paginate-result)
|
||
- [x] graceful shutdown. stop taking new requests and don't stop until all outstanding queries are handled
|
||
- https://github.com/tokio-rs/mini-redis/blob/master/src/shutdown.rs
|
||
- we need this because we need to be sure all the queries are saved in the db. maybe put stuff in Drop
|
||
- need an flume::watch on unflushed stats that we can subscribe to. wait for it to flip to true
|
||
- [x] don't use unix timestamps for response_millis since leap seconds will confuse it
|
||
- [x] config to allow origins even on the anonymous endpoints
|
||
- [x] send logs to sentry
|
||
- [x] login should return the user id
|
||
- [x] when we show keys, also show the key's id
|
||
- [x] add config for concurrent requests from public requests
|
||
- [x] new endpoints for users (not totally sure about the exact paths, but these features are all needed):
|
||
- [x] sign in
|
||
- [x] login should include the key id, not just the key ULID
|
||
- [x] sign out
|
||
- [x] GET profile endpoint
|
||
- [x] POST profile endpoint
|
||
- [x] GET stats endpoint
|
||
- [x] display distribution of methods per api key (eth_call, eth_getLogs, etc.) (only with authentication!)
|
||
- [x] get aggregate stats endpoint
|
||
- [x] display requests per second per api key (only with authentication!)
|
||
- [x] POST key endpoint
|
||
- [x] generate a new key from a web endpoint
|
||
- [x] modifying key settings such as private relay, revert logging, ip/origin/etc checks
|
||
- [x] GET logged reverts on an endpoint that **requires authentication**.
|
||
- [x] endpoint to list keys without having to sign a message to log in again
|
||
- [x] rename user_key to rpc_key
|
||
- [x] in code
|
||
- [x] in database with a migration
|
||
- [x] instead of requests_per_minute on every key, have a "user_tier" that gets joined
|
||
- [x] document url params with examples
|
||
- [x] improve "docs/http routes.txt"
|
||
- [x] remove request per minute and concurrency limits from the keys. those are on the user tiers now.
|
||
- [x] revertLogs db table should have rpc_key_id on it
|
||
- [x] the relation in Relation is wrong now. it is called user_key_id, but point to the rpc key table
|
||
- [x] instruments are missing. maybe that is why sentry had broken traces
|
||
- [x] description should default to an empty string instead of being nullable
|
||
- [x] include if archive query or not in the stats
|
||
- [x] fix test not shutting down
|
||
- [x] proper authentication on rpc_key_id
|
||
- we have bearer token auth for user_id, but rpc_key_id needs more code
|
||
- [x] use rpc_key_id instead of user_id in the redirect
|
||
- [x] /status should include the server weights
|
||
- [-] add configurable size limits to all the Caches
|
||
- instead of configuring each cache with MB sizes, have one value for total memory footprint and then percentages for each cache
|
||
|
||
- [ ] actually block unauthenticated requests instead of emitting warning of "allowing without auth during development!"
|
||
|
||
## V1
|
||
|
||
These are not yet ordered. There might be duplicates. We might not actually need all of these.
|
||
|
||
- [ ] logging of "bad response!" is way too verbose
|
||
- [ ] config parsing is strict right now. this makes it hard to deploy on git push since configs need to change along with it
|
||
- [ ] i think our "best" server picking is incorrect somehow.
|
||
- we upgraded erigon to a version with a broken websocket
|
||
- that made it clear we still route to the lagged server sometimes. this is bad, but retries keep it from giving users bad data.
|
||
- [ ] when displaying the user's data, they just see an opaque id for their tier. We should join that data
|
||
- [ ] add indexes to speed up stat queries
|
||
- [ ] the public rpc is rate limited by ip and the authenticated rpc is rate limit by key
|
||
- this means if a dapp uses the authenticated RPC on their website, they could get rate limited more easily
|
||
- [ ] add cacheing to speed up stat queries
|
||
- [ ] take an option to set a non-default role when creating a user
|
||
- [ ] different prune levels for free tiers
|
||
- [ ] have a test that runs ethspam and versus
|
||
- [ ] status page show git hash of running version
|
||
- [ ] Email confirmation
|
||
- [ ] we'll need a pretty template email that the backend will send.
|
||
- [ ] That will link them to a a page on llamanodes.com
|
||
- [ ] There, they click "confirm" (or JavaScript does it for them automatically) to POST to this new endpoint
|
||
- [ ] test in the migration repo that sets up a sqlite database that runs up and down
|
||
- [ ] unbounded queues are risky. add limits
|
||
- [-] let users choose a % to log (or maybe x/second). someone like curve logging all reverts will be a BIG database very quickly
|
||
- this must be opt-in or spawned since it will slow things down and will make their calls less private
|
||
- [ ] automatic pruning of old revert logs once too many are collected
|
||
- [ ] we currently default to 0.0 and don't expose a way to edit it. we have a database row, but we don't use it
|
||
- [ ] after running for a while, https://eth-ski.llamanodes.com/status is only at 157 blocks and hashes. i thought they would be near 10k after running for a while
|
||
- adding uptime to the status should help
|
||
- i think this is already in our todo list
|
||
- [ ] improve private transactions. keep re-broadcasting until they are confirmed
|
||
- [ ] with a test that creates a user and modifies their key
|
||
- [ ] Uuid/Ulid instead of big_unsigned for database ids
|
||
- might have to use Uuid in sea-orm and then convert to Ulid on display
|
||
- https://www.kostolansky.sk/posts/how-to-migrate-to-uuid/
|
||
- [ ] make the "not synced" error more verbose
|
||
- I think there is a bug in our synced_rpcs filtering. likely in has_block_data
|
||
- seeing "not synced" when I load https://vfat.tools/esd/
|
||
- [ ] emit stdandard deviation?
|
||
- [ ] emit global stat on retry
|
||
- [ ] emit global stat on no servers synced
|
||
- [ ] emit global stat on error (maybe just use sentry, but graphs are handy)
|
||
- if we wait until the error handler to emit the stat, i don't think we have access to the authorized_request
|
||
- [ ] endpoint (and cli script) to rotate api key
|
||
- [ ] if no bearer token found in redis (likely because it expired), send 401 unauthorized
|
||
- [ ] user create script should allow multiple keys per user
|
||
- [ ] somehow the proxy thought latest was hours behind. need internal health check that forces reconnect if this happens
|
||
- [ ] display concurrent requests per api key (only with authentication!)
|
||
- [ ] change "remember me" to last until 4 weeks of no use, rather than 4 weeks since login
|
||
- [ ] BUG! if sending transactions gets "INTERNAL_ERROR: existing tx with same hash", fake a success message
|
||
- ERROR http_request:request:try_send_all_upstream_servers: web3_proxy::rpcs::request: bad response! err=JsonRpcClientError(JsonRpcError(JsonRpcError { code: -32000, message: "INTERNAL_ERROR: existing tx with same hash", data: None })) method=eth_sendRawTransaction rpc=local_erigon_alpha_archive id=01GF4HV03Y4ZNKQV8DW5NDQ5CG method=POST authorized_request=User(Some(SqlxMySqlPoolConnection), AuthorizedKey { ip: 10.11.12.15, origin: None, user_key_id: 4, log_revert_chance: 0.0000 }) self=Web3Connections { conns: {"local_erigon_alpha_archive_ws": Web3Connection { name: "local_erigon_alpha_archive_ws", blocks: "all", .. }, "local_geth_ws": Web3Connection { name: "local_geth_ws", blocks: 64, .. }, "local_erigon_alpha_archive": Web3Connection { name: "local_erigon_alpha_archive", blocks: "all", .. }}, .. } authorized_request=Some(User(Some(SqlxMySqlPoolConnection), AuthorizedKey { ip: 10.11.12.15, origin: None, user_key_id: 4, log_revert_chance: 0.0000 })) request=JsonRpcRequest { id: RawValue(39), method: "eth_sendRawTransaction", .. } request_metadata=Some(RequestMetadata { datetime: 2022-10-11T22:14:57.406829095Z, period_seconds: 60, request_bytes: 633, backend_requests: 0, no_servers: 0, error_response: false, response_bytes: 0, response_millis: 0 }) block_needed=None
|
||
- [ ] BUG? WARN http_request:request: web3_proxy::block_number: could not get block from params err=unexpected params length id=01GF4HTRKM4JV6NX52XSF9AYMW method=POST authorized_request=User(Some(SqlxMySqlPoolConnection), AuthorizedKey { ip: 10.11.12.15, origin: None, user_key_id: 4, log_revert_chance: 0.0000 })
|
||
- why is it failing to get the block from params when its set to None? That should be the simple case
|
||
- [ ] BUG: i think if all backend servers stop, the server doesn't properly reconnect. It appears to stop listening on 8854, but not shut down.
|
||
- [ ] if user-specific caches have evictions that aren't from timeouts, log a warning
|
||
- [ ] make sure the email address is valid. probably have a "verified" column in the database
|
||
- [ ] if invalid user id given, we give a 500. should be a different error code instead
|
||
- WARN http_request: web3_proxy::frontend::errors: anyhow err=UserKey was not a ULID or UUID id=01GER4VBTS0FDHEBR96D1JRDZF method=POST
|
||
- [ ] admin-only endpoint for seeing a user's stats for support requests
|
||
- [ ] from what i thought, /status should show hashes > numbers!
|
||
- but block numbers count is maxed out (10k)
|
||
- and block hashes count is tiny (83)
|
||
- what is going on? when the server fist launches they are in sync
|
||
- [ ] related BUG? WARN web3_proxy::rpcs::blockchain: Missing connection_head_block in block_hashes. Fetching now connection_head_hash=0x4b7a…14b5 conn_name=local_erigon_alpha_archive rpc=local_erigon_alpha_archive
|
||
- i see this a lot more than expected. why is it happening so much? better logs needed
|
||
- [ ] after adding semaphores (or maybe something else), CPU load seems a lot higher. investigate
|
||
- [ ] proper support for Finalized and Safe block queries
|
||
- [ ] admin-only page for viewing user stat pages
|
||
- [ ] geth sometimes gives an empty response instead of an error response. figure out a good way to catch this and not serve it
|
||
- [ ] GET balance endpoint
|
||
- [ ] POST balance endpoint
|
||
- [ ] eth_1 | 2022-10-11T22:14:57.408114Z ERROR http_request:request:try_send_all_upstream_servers: web3_proxy::rpcs::request: bad response! err=JsonRpcClientError(JsonRpcError(JsonRpcError { code: -32000, message: "INTERNAL_ERROR: existing tx with same hash", data: None })) method=eth_sendRawTransaction rpc=local_erigon_alpha_archive id=01GF4HV03Y4ZNKQV8DW5NDQ5CG method=POST authorized_request=User(Some(SqlxMySqlPoolConnection), AuthorizedKey { ip: 10.11.12.15, origin: None, user_key_id: 4, log_revert_chance: 0.0000 }) self=Web3Connections { conns: {"local_erigon_alpha_archive_ws": Web3Connection { name: "local_erigon_alpha_archive_ws", blocks: "all", .. }, "local_geth_ws": Web3Connection { name: "local_geth_ws", blocks: 64, .. }, "local_erigon_alpha_archive": Web3Connection { name: "local_erigon_alpha_archive", blocks: "all", .. }}, .. } authorized_request=Some(User(Some(SqlxMySqlPoolConnection), AuthorizedKey { ip: 10.11.12.15, origin: None, user_key_id: 4, log_revert_chance: 0.0000 })) request=JsonRpcRequest { id: RawValue(39), method: "eth_sendRawTransaction", .. } request_metadata=Some(RequestMetadata { datetime: 2022-10-11T22:14:57.406829095Z, period_seconds: 60, request_bytes: 633, backend_requests: 0, no_servers: 0, error_response: false, response_bytes: 0, response_millis: 0 }) block_needed=None
|
||
- eth_sendRawTransaction should accept "INTERNAL_ERROR: existing tx with same hash" as a successful response. we just want to be sure that the server has our tx and in this case, it does.
|
||
- [ ] EIP1271 for siwe
|
||
- [ ] Limited throughput during high traffic
|
||
- [ ] implement filters and other unimplemented rpc methods
|
||
- multiple teams need log filters and subscriptions.
|
||
- would be nice if our subscriptions had better gaurentees than geth/erigon do, but maybe simpler to just setup a broadcast channel and proxy all the respones to a backend instead
|
||
- [ ] instead of Option<...> in our frontend function signatures, use result and then the try operator so that we get our errors wrapped in json
|
||
- [ ] revert logs should have a maximum age and a maximum count to keep the database from being huge
|
||
- [ ] user login should also return a jwt (jsonwebtoken rust crate should make it easy)
|
||
- [-] if we request an old block, more servers can handle it than we currently use.
|
||
- [ ] instead of the one list of just heads, store our intermediate mappings (rpcs_by_hash, rpcs_by_num, blocks_by_hash) in SyncedConnections. this shouldn't be too much slower than what we have now
|
||
- [ ] remove the if/else where we optionally route to archive and refactor to require a BlockNumber enum
|
||
- [ ] then check syncedconnections for the blockNum. if num given, use the cannonical chain to figure out the winning hash
|
||
- [ ] this means if someone requests a recent but not ancient block, they can use all our servers, even the slower ones. need smart sorting for priority here
|
||
- [ ] script that looks at config and estimates max memory used by caches
|
||
- [ ] favicon
|
||
- eth_1 | 2022-09-07T17:10:48.431536Z WARN web3_proxy::jsonrpc: forwarding error err=nothing to see here
|
||
- use the one on https://staging.llamanodes.com/
|
||
- [ ] warn if no servers have transaction subscriptions
|
||
- [ ] if no servers have transaction subscriptions, and a user tries to subscribe, make sure the error is user friendly
|
||
- [ ] only allow transaction and full block subscriptions if the user is registered?
|
||
- [ ] eth_subscribe logs (https://geth.ethereum.org/docs/rpc/pubsub)
|
||
- [ ] make private transactions opt in (its already in the database, but not our code)
|
||
- [ ] write a function for receipts that tries balanced_rpcs and only if they all error should it try private relays
|
||
- [ ] automatic retries with a timeout or until all the servers have been tried.
|
||
- i had the websocket die on me in the middle of a long test. only one in-flight request failed because of it. the rest delayed. figure out how to catch these ones since websocket fails sadly seem common
|
||
- [ ] nice output when cargo doc is run
|
||
- [ ] cache more things locally or in redis
|
||
- [ ] stats when forks are resolved (and what chain they were on?)
|
||
- [ ] emit stats for user's successes, retries, failures, with the types of requests, chain, rpc
|
||
- [ ] cli for creating and editing api keys
|
||
- [ ] Only subscribe to transactions when someone is listening and if the server has opted in to it
|
||
- [ ] When sending eth_sendRawTransaction, retry errors
|
||
- [ ] If we need an archive server and no servers in sync, exit immediately with an error instead of waiting 60 seconds
|
||
- [ ] 60 second timeout is too short. Maybe do that for free tier and larger timeout for paid. Problem is that some queries can take over 1000 seconds
|
||
- [ ] when handling errors from axum parsing the Json...Enum, the errors don't get wrapped in json. i think we need a axum::Layer
|
||
- [ ] don't "unwrap" anywhere. give proper errors
|
||
- [ ] handle log subscriptions
|
||
- probably as a paid feature
|
||
- [ ] on ETH, we no longer use total difficulty, but other chains might
|
||
- if total difficulty is not on the block and we aren't on ETH, fetch the full block instead of just the header
|
||
- if total difficulty is set and non-zero, use it for consensus instead of just the number
|
||
- [ ] if we subscribe to a server that is syncing, it gives us null block_data_limit. when it catches up, we don't ever send queries to it. we need to recheck block_data_limit
|
||
- [ ] add a "failover" tier that is only used if balanced_rpcs has "no servers synced"
|
||
- use this tier (and private tier) to check timestamp on latest block. if we are behind that by more than a few seconds, something is wrong
|
||
- [ ] sometimes when fetching a txid through the proxy it fails, but fetching from the backends works fine
|
||
- check flashprofits logs for examples
|
||
- [ ] relevant erigon changelogs: add pendingTransactionWithBody subscription method (#5675)
|
||
|
||
|
||
## V2
|
||
|
||
These are not ordered. I think some rows also accidently got deleted here. Check git history.
|
||
|
||
- [ ] handle user payments
|
||
- [ ] separate daemon (or users themselves) call POST /users/process_transaction
|
||
- checks a transaction to see if it modifies a user's balance. records results in a sql database
|
||
- we will have our own event subscriber watching for "deposit" events, but sometimes events get missed and users might incorrectly "transfer" the tokens directly to an address instead of using the dapp
|
||
- [ ] refactor so configs can change while running
|
||
- this will probably be a rather large change, but is necessary when we have autoscaling
|
||
- create the app without applying any config to it
|
||
- have a blocking future watching the config file and calling app.apply_config() on first load and on change
|
||
- work started on this in the "config_reloads" branch. because of how we pass channels around during spawn, this requires a larger refactor.
|
||
- [ ] if a rpc fails to connect at start, retry later instead of skipping it forever (need config hot reloads first)
|
||
- [ ] jwt auth so people can easily switch from infura
|
||
- [ ] automated soft limit
|
||
- look at average request time for getBlock? i'm not sure how good a proxy that will be for serving eth_call, but its a start
|
||
- https://crates.io/crates/histogram-sampler
|
||
- [ ] interval for http subscriptions should be based on block time. load from config is easy, but better to query. currently hard coded to 13 seconds
|
||
|
||
in another repo: event subscriber
|
||
- [ ] watch for transfer events to our contract and submit them to /payment/$tx_hash
|
||
- [ ] cli tool that support can run to manually check and submit a transaction
|
||
|
||
## "Maybe some day" and other Miscellaneous Things
|
||
|
||
- [ ] tool to revoke bearer tokens that clears redis
|
||
- [ ] eth_getBlockByNumber and similar calls served from the block map
|
||
- will need all Block<TxHash> **and** Block<TransactionReceipt> in caches or fetched efficiently
|
||
- so maybe we don't want this. we can just use the general request cache for these. they will only require 1 request and it means requests won't get in the way as much on writes as new blocks arrive.
|
||
- after looking at my request logs, i think its worth doing this. no point hitting the backends with requests for blocks multiple times. will also help with cache hit rates since we can keep recent blocks in a separate cache
|
||
- [ ] Public bsc server got “0” for block data limit (ninicoin)
|
||
- [ ] cli tool for resetting api keys
|
||
- [ ] cli tool for checking config
|
||
- [ ] benchmarks of the different Cache implementations (futures vs dash)
|
||
- [ ] Advanced load testing scripts so we can find optimal cost servers
|
||
- [ ] benchmarks from https://github.com/llamafolio/llamafolio-api/
|
||
- [ ] benchmarks from ethspam and versus
|
||
- [ ] benchmarks from other things
|
||
- [ ] quick script that calls all the curve-api endpoints once and checks for success, then calls wrk to hammer it
|
||
- [ ] https://github.com/curvefi/curve-api
|
||
- [ ] test /api/getGaugesmethod
|
||
- usually times out after vercel's 60 second timeout
|
||
- one time got: Error invalid Json response ""
|
||
- [ ] i think all the async methods in ethers need tracing instrument. something like `cfgif(tracing, tracing::instrument)`
|
||
- if they do that, i think my request_id will show up on their logs
|
||
- [ ] page that prints a graphviz dotfile of the blockchain
|
||
- [ ] search for all the "TODO" and `todo!(...)` items in the code and move them here
|
||
- [ ] add the backend server to the header?
|
||
- [ ] have a low-latency option that always tries at least two servers in parallel and then returns the first success?
|
||
- this doubles our request load though. maybe only if the first one doesn't respond very quickly?
|
||
- [ ] zero downtime deploys
|
||
- [ ] are we using Acquire/Release/AcqRel properly? or do we need other modes?
|
||
- [ ] use https://github.com/ledgerwatch/interfaces to talk to erigon directly instead of through erigon's rpcdaemon (possible example code which uses ledgerwatch/interfaces: https://github.com/akula-bft/akula/tree/master)
|
||
- [ ] subscribe to pending transactions and build an intelligent gas estimator
|
||
- [ ] flashbots specific methods
|
||
- [ ] flashbots protect fast mode or not? probably fast matches most user's needs, but no reverts is nice.
|
||
- [ ] https://docs.flashbots.net/flashbots-auction/searchers/advanced/rpc-endpoint#authentication maybe have per-user keys. or pass their header on if its set
|
||
- [ ] if no redis set, but public rate limits are set, exit with an error
|
||
- [ ] i saw "WebSocket connection closed unexpectedly" but no log about reconnecting
|
||
- need better logs on this because afaict it did reconnect
|
||
- [ ] if archive servers are added to the rotation while they are still syncing, they might get requests too soon. keep archive servers out of the configs until they are done syncing. full nodes should be fine to add to the configs even while syncing, though its a wasted connection
|
||
- [ ] better document load tests: docker run --rm --name spam shazow/ethspam --rpc http://$LOCAL_IP:8544 | versus --concurrency=100 --stop-after=10000 http://$LOCAL_IP:8544; docker stop spam
|
||
- [ ] if the call is something simple like "symbol" or "decimals", cache that too. though i think this could bite us.
|
||
- [ ] add a subscription that returns the head block number and hash but nothing else
|
||
- [ ] if chain split detected, what should we do? don't send transactions?
|
||
- [ ] archive check works well for local servers, but public nodes (especially on other chains) seem to give unreliable results. likely because of load balancers. maybe have a "max block data limit"
|
||
- [ ] https://docs.rs/derive_builder/latest/derive_builder/
|
||
- [ ] Detect orphaned transactions
|
||
- [ ] https://crates.io/crates/reqwest-middleware easy retry with exponential back off
|
||
- Though I think we want retries that go to other backends instead
|
||
- [ ] Some of the pub things should probably be "pub(crate)"
|
||
- [ ] Maybe storing pending txs on receipt in a dashmap is wrong. We want to store in a timer_heap (or similar) when we actually send. This way there's no lock contention until the race is over.
|
||
- [ ] Support "safe" block height. It's planned for eth2 but we can kind of do it now but just doing head block num-3
|
||
- [ ] Archive check on BSC gave “archive” when it isn’t. and FTM gave 90k for all servers even though they should be archive
|
||
- [ ] cache eth_getLogs in a database?
|
||
- [ ] stats for "read amplification". how many backend requests do we send compared to frontend requests we received?
|
||
- [ ] fully test retrying when "header not found"
|
||
- i saw "header not found" on a simple eth_getCode query to a public load balanced bsc archive node on block 1
|
||
- [ ] weird flapping fork could have more useful logs. like, howd we get to 1/1/4 and fork. geth changed its mind 3 times?
|
||
- should we change our code to follow the same consensus rules as geth? our first seen still seems like a reasonable choice
|
||
- other chains might change all sorts of things about their fork choice rules
|
||
2022-07-22T23:52:18.593956Z WARN block_receiver: web3_proxy::connections: chain is forked! 1 possible heads. 1/1/4 rpcs have 0xa906…5bc1 rpc=Web3Connection { url: "ws://127.0.0.1:8546", data: 64, .. } new_block_num=15195517
|
||
2022-07-22T23:52:18.983441Z WARN block_receiver: web3_proxy::connections: chain is forked! 1 possible heads. 1/1/4 rpcs have 0x70e8…48e0 rpc=Web3Connection { url: "ws://127.0.0.1:8546", data: 64, .. } new_block_num=15195517
|
||
2022-07-22T23:52:19.350720Z WARN block_receiver: web3_proxy::connections: chain is forked! 2 possible heads. 1/2/4 rpcs have 0x70e8…48e0 rpc=Web3Connection { url: "ws://127.0.0.1:8549", data: "archive", .. } new_block_num=15195517
|
||
2022-07-22T23:52:26.041140Z WARN block_receiver: web3_proxy::connections: chain is forked! 2 possible heads. 2/4/4 rpcs have 0x70e8…48e0 rpc=Web3Connection { url: "http://127.0.0.1:8549", data: "archive", .. } new_block_num=15195517
|
||
- [ ] threshold should check actual available request limits (if any) instead of just the soft limit
|
||
- [ ] foreign key on_update and on_delete
|
||
- [ ] database creation timestamps
|
||
- [ ] better error handling. we warn too often for validation errors and use the same error code for most every request
|
||
- [ ] use &str more instead of String. lifetime annotations get really annoying though
|
||
- [ ] tarpit instead of reject requests (unless theres a lot)
|
||
- [ ] tune database connection pool size. i think a single web3_proxy currently maxes out our server
|
||
- [ ] subscribing to transactions should be configurable per server. listening to paid servers can get expensive
|
||
- [ ] archive servers should be lowest priority
|
||
- [ ] docker build context is really big. we must be including target or something
|
||
- [ ] ip detection needs work so that everything doesnt show up as 172.x.x.x
|
||
- [ ] status page leaks our urls which contain secrets. change that to use names
|
||
- [ ] PR to add this to sea orm prelude:
|
||
```
|
||
#[cfg(feature = "with-uuid")]
|
||
pub use uuid::Builder as UuidBuilder;
|
||
```
|
||
- [ ] rate limit thoughts:
|
||
- if someone subscribes to all pending transactions, how should that count against rate limits
|
||
- when those rate limits are hit, what should happen?
|
||
- missing pending transactions might be okay, but not missing confirmed blocks
|
||
- [ ] for easier errors in the axum code, i think we need to have our own type that wraps anyhow::Result+Error
|
||
- [ ] fix ip detection when running in dev
|
||
- [ ] double check weight sorting code
|
||
- [ ] sea-orm brings in async-std, but we are using tokio. benchmark switching
|
||
- [ ] this query always times out, but erigon can serve it quickly: `curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"debug_traceBlockByNumber","params":["latest"],"id":1}' 127.0.0.1:8544' 127.0.0.1:8544`
|
||
{"jsonrpc":"2.0","id":null,"error":{"code":-32099,"message":"deadline has elapsed"}}
|
||
- [ ] figure out rate limits for private rpcs. eden v1 gives 500 error instead of a code for rate limits
|
||
- [ ] https://gitlab.com/moka-labs/tiered-cache-example
|
||
- [ ] web3connection3.block(...) might wait forever. be sure to do it safely
|
||
- [ ] search for all "todo!"
|
||
- [ ] when using a bunch of slow public servers, i see "no servers in sync" even when things should be right
|
||
- maybe iterate connection heads by total weight? i still think we need to include parent hashes
|
||
- [ ] i see "No block found" sometimes for a single server's block. Not sure why since reads should happen after writes
|
||
- [ ] whats going on here? why is it rolling back? maybe total_difficulty was a LOT higher?
|
||
- 2022-09-05T19:21:39.763630Z WARN web3_proxy::rpcs::blockchain: chain rolled back 1/6/7 head=15479604 (0xf809…6a2c) rpc=infura_free
|
||
- i wish i had more logs. its possible that 15479605 came immediatly after
|
||
- [ ] keep it working without redis and a database
|
||
- [ ] web3 on rpc1 exited without errors. maybe promote some shutdown messages from debug to info?
|
||
- [ ] better handling for offline http servers
|
||
- if we get a connection refused, we should remove the server's block info so it is taken out of rotation
|
||
- [ ] web3_proxy_cli command should read database settings from config
|
||
- [ ] how should we handle reverting transactions? they won't confirm for a while after we send them
|
||
- [ ] allow configuration of the expiration time of bearer tokens. currently defaults to 4 weeks
|
||
- [ ] emit stat when an IP/key goes over rate limits
|
||
- [ ] readme command should run create_user commands via docker-compose
|
||
- [ ] helper for UUID <-> ULID
|
||
- [ ] Wrapping extractors in Result makes them optional and gives you the reason the extraction failed
|
||
- [ ] at concurrency 100, ethspam is getting 400 and 422 errors. figure out why. probably something with redis or mysql, but maybe its something else like spawning
|
||
- [ ] emit per-key stats for latency of semaphore awaits. if this starts to grow, people will know they are hitting limits and need a higher tier
|
||
- [ ] need a status page for your wallet's rpc. show head block information with age
|
||
- [ ] hit counts seem wrong. how are we hitting the backend so much more than the frontend? retries on disconnect don't seem to fit that
|
||
web3_proxy_hit_count{path = "app/proxy_web3_rpc_request"} 857270
|
||
web3_proxy_hit_count{path = "backend_rpc/request"} 1396127
|
||
- [ ] replace serde_json::Value with https://lib.rs/crates/ijson (more memory efficient)
|
||
- [ ] have a log all option? instead of just reverts, log all request/responses? can be very useful for debugging but would flood our database. maybe better for them to do that on their client side
|
||
- [ ] failsafe. if no blocks or transactions in some time, warn and reset the connection
|
||
- [ ] WARN http_request:request: web3_proxy::block_number: could not get block from params err=unexpected params length id=01GF4HTRKM4JV6NX52XSF9AYMW method=POST authorized_request=User(Some(SqlxMySqlPoolConnection), AuthorizedKey { ip: 10.11.12.15, origin: None, user_key_id: 4, log_revert_chance: 0.0000 })
|
||
- [ ] having tons of worker threads can actually make us slower if they keep waking to steal work from eachother. need benchmarks
|
||
- [ ] change the wrk data to log requests and errors to a file
|
||
- [ ] if redis is not set and login page is visited, users get a 502. should be 501
|
||
- [ ] allow passing the authorization header to the anonymous rpc endpoint
|
||
- [ ] sentry profiling
|