df2f3d340f
* fix popularity contest * more info in the Debug for Web3Rpc * add frontend_requests and cache_misses to the Balance query * add more to balance and stats flushing and improved test coverage * it compiles * deserializer for Ulid to Uuid I think a wrapper type on Ulid that implements sea_orm::Value is probably better * rename variable to match struct name * add deserializer for Address -> Vec<u8> * sql sum returns a Decimal. need to convert to u64 * assert more * one log and assert more * log more * use a helper to get the user's rpc provider * this should be 2 now that we have a public and authed call * this should be zero. the public has the cache miss * instrument cu calcs * trace the value we took, not the default that replaced it * move usd_per_chain into config * remove some extra logging * use Arc::into_inner to maybe avoid a race * off by 1 * pass paid credits used instead of returning it this lets us use it to write to our user balance cache first. importantly, this keeps us from holding a write lock while writing to mysql * no cache misses expected in this test * actually check the admin * put the balance checks back now that the rest of the test works * archive request is being set incorrectly * wow howd we manage flipping the greater than sign on archive depth * move latest_balance and premium_credits_used to before any stats are emitted * lint * and build undoes the linting. fun i didnt even want to lint them in the first place, so this is fine * missed incrementing total_spent when not incrementing total_spent_paid_credits * use the credits on self * use the credits on self (pt 2) * fix type for 10 cu query * convert the requestmetadata on the other side of the channel * logs * viewing stats is allowed even without a balance * move paid_credits_used to AuthorizationChecks * wip * test_sum_credits_used finally passes * UserBalanceCache::get_or_insert * re-enable rpc_secret_key_cache * move invalidate to a helper function and always call it **after** the db is commited * fix PartialEq and Eq on RpcSecretKey * cargo upgrade |
||
---|---|---|
.cargo | ||
.vscode | ||
bin | ||
config | ||
deferred-rate-limiter | ||
docs | ||
entities | ||
latency | ||
migration | ||
payment-contracts | ||
quick_cache_ttl | ||
rate-counter | ||
redis-rate-limiter | ||
scripts | ||
web3_proxy | ||
wrk | ||
.dockerignore | ||
.env | ||
.gitignore | ||
Cargo.lock | ||
Cargo.toml | ||
docker-compose.common.yml | ||
docker-compose.prod.yml | ||
docker-compose.yml | ||
Dockerfile | ||
example.sql | ||
FAQ.md | ||
Jenkinsfile | ||
LICENSE | ||
README.md | ||
rust-toolchain.toml | ||
TODO.md |
web3_proxy
Web3_proxy is a fast caching and load balancing proxy for web3 (Ethereum or similar) JsonRPC servers.
Under construction! This code is under active development. If you want to run this proxy youself, send me a message on Twitter and I can explain things that aren't documented yet. Most RPC methods are supported, but filters are coming soon. And of course, more tests are always needed.
Signed transactions (eth_sendRawTransaction) are sent in parallel to the configured private RPCs (eden, ethermine, flashbots, etc.).
All other requests are sent to an RPC server on the latest block (llamanodes, alchemy, moralis, rivet, your own node, or one of many other providers). If multiple servers are in sync, they are prioritized by active_requests
and request latency. Note that this means that the fastest server is most likely to serve requests and slow servers are unlikely to ever get any requests.
Each server has different limits to configure. The soft_limit
is the number of parallel active requests where a server starts to slow down. The hard_limit
is where a server starts giving rate limits or other errors.
Quick development
brew install librdkafka
orsudo apt-get install librdkafka-dev
- Run
docker-compose up -d
to start the database and caches. Seedocker-compose.yml
for details. - Copy
./config/example.toml
to./config/development.toml
and change settings to match your setup. - Run
cargo
commands:
$ cargo run --release -- --help
Compiling web3_proxy v0.1.0 (/home/bryan/src/web3_proxy/web3_proxy)
Finished release [optimized + debuginfo] target(s) in 17.69s
Running `target/release/web3_proxy --help`
Usage: web3_proxy [--port <port>] [--workers <workers>] [--config <config>]
web3_proxy is a fast caching and load balancing proxy for web3 (Ethereum or similar) JsonRPC servers.
Options:
--port what port the proxy should listen on
--workers number of worker threads
--config path to a toml of rpc servers
--help display usage information
Start the server with the defaults (listen on http://localhost:8544
and use ./config/development.toml
which uses the database and cache running under docker and proxies to a bunch of public nodes:
cargo run --release -- proxyd
Quickly run tests:
RUST_LOG=web3_proxy=trace,info cargo nextest run
Run more tests:
RUST_LOG=web3_proxy=trace,info cargo nextest run --features tests-needing-docker
Common commands
Create a user:
cargo run -- --db-url "$YOUR_DB_URL" create_user --address "$USER_ADDRESS_0x"
Check that the proxy is working:
curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"web3_clientVersion","id":1}' 127.0.0.1:8544
curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber", "params": ["latest", false],"id":1}' 127.0.0.1:8544
curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getBalance", "params": ["0x0000000000000000000000000000000000000000", "latest"],"id":1}' 127.0.0.1:8544
Check that the websocket is working:
$ websocat ws://127.0.0.1:8544
{"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["newHeads"]}
{"jsonrpc": "2.0", "id": 2, "method": "eth_subscribe", "params": ["newPendingTransactions"]}
{"jsonrpc": "2.0", "id": 3, "method": "eth_subscribe", "params": ["newPendingFullTransactions"]}
{"jsonrpc": "2.0", "id": 4, "method": "eth_subscribe", "params": ["newPendingRawTransactions"]}
You can copy config/example.toml
to config/production-$CHAINNAME.toml
and then run docker-compose up --build -d
start proxies for many chains.
Compare 3 RPCs:
web3_proxy_cli health_compass https://eth.llamarpc.com https://eth-ski.llamarpc.com https://rpc.ankr.com/eth
Run migrations
Generally it is simplest to just run the app to run migrations. It runs migrations on start.
But if you want to run them manually (generally only useful in development):
cd migration
cargo run up
Create a user:
web3_proxy_cli --config ... create_user --address 0x0000000000000000000000000000000000000000 --email infra@llamanodes.com --description "..."
Give a user unlimited requests per second:
Copy the ULID key (or UUID key) out of the above command, and put it into the following command.
web3_proxy_cli --config ... change_user_tier_by_key "$RPC_ULID_KEY_FROM_PREV_COMMAND" "Unlimited"
Health compass
Health check 3 servers and error if the first one doesn't match the others.
web3_proxy_cli health_compass https://eth.llamarpc.com/ https://rpc.ankr.com/eth https://cloudflare-eth.com
Adding new database tables
cargo install sea-orm-cli
- (optional) drop the current dev db
sea-orm-cli migrate
sea-orm-cli generate entity -u mysql://root:dev_web3_proxy@127.0.0.1:13306/dev_web3_proxy -o entities/src --with-serde both
- Be careful when adding the
--tables THE,MODIFIED,TABLES
flag. It will delete relationships if they aren't listed
- Be careful when adding the
- After running the above, you will need to manually fix some things
- Add any derives that got removed (like
Default
) Vec<u8>
->sea_orm::prelude::Uuid
(Related: https://github.com/SeaQL/sea-query/issues/375)i8
->bool
(Related: https://github.com/SeaQL/sea-orm/issues/924)- add all the tables back into
mod.rs
- Add any derives that got removed (like
Flame Graphs
Flame graphs make a developer's join of finding slow code painless:
$ cat /proc/sys/kernel/kptr_restrict
1
$ echo 0 | sudo tee /proc/sys/kernel/kptr_restrict
0
$ cat /proc/sys/kernel/perf_event_paranoid
4
$ echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid
-1
$ CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph --bin web3_proxy_cli --no-inline -- proxyd
Be sure to use --no-inline
or perf will be VERY slow
GDB
Developers can run the proxy under gdb for advanced debugging:
cargo build --release && RUST_LOG=info,web3_proxy=debug,ethers_providers::rpc=off rust-gdb --args target/debug/web3_proxy --listen-port 7503 --rpc-config-path ./config/production-eth.toml
TODO: also enable debug symbols in the release build by modifying the root Cargo.toml
Load Testing
Test the proxy:
wrk -t12 -c400 -d30s --latency http://127.0.0.1:8544/health
wrk -t12 -c400 -d30s --latency http://127.0.0.1:8544/status
wrk -s ./wrk/getBlockNumber.lua -t12 -c400 -d30s --latency http://127.0.0.1:8544/u/$API_KEY
wrk -s ./wrk/getLatestBlockByNumber.lua -t12 -c400 -d30s --latency http://127.0.0.1:8544/u/$API_KEY
Test geth (assuming it is on 8545):
wrk -s ./wrk/getBlockNumber.lua -t12 -c400 -d30s --latency http://127.0.0.1:8545
wrk -s ./wrk/getLatestBlockByNumber.lua -t12 -c400 -d30s --latency http://127.0.0.1:8545
Test erigon (assuming it is on 8945):
wrk -s ./wrk/getBlockNumber.lua -t12 -c400 -d30s --latency http://127.0.0.1:8945
wrk -s ./wrk/getLatestBlockByNumber.lua -t12 -c400 -d30s --latency http://127.0.0.1:8945
Note: Testing with getLatestBlockByNumber.lua
is not great because the latest block changes and so one run is likely to be very different than another.
Run ethspam and versus for a more realistic load test:
ethspam --rpc http://127.0.0.1:8544 | versus --concurrency=10 --stop-after=1000 http://127.0.0.1:8544
ethspam --rpc http://127.0.0.1:8544/u/$API_KEY | versus --concurrency=100 --stop-after=10000 http://127.0.0.1:8544/u/$API_KEY