diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml deleted file mode 100644 index ed09d5d..0000000 --- a/.github/workflows/golangci-lint.yml +++ /dev/null @@ -1,47 +0,0 @@ -# Taken from https://github.com/golangci/golangci-lint-action -name: golangci-lint -on: - push: - tags: - - v* - branches: - - master - - main - pull_request: -permissions: - contents: read - # Optional: allow read access to pull request. Use with `only-new-issues` option. - # pull-requests: read -jobs: - golangci: - name: lint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: 1.24.2 - - name: golangci-lint - uses: golangci/golangci-lint-action@v7 - with: - # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: latest - - # Optional: working directory, useful for monorepos - # working-directory: somedir - - # Optional: golangci-lint command line arguments. - # args: --issues-exit-code=0 - - # Optional: show only new issues if it's a pull request. The default value is `false`. - # only-new-issues: true - - # Optional: if set to true then the all caching functionality will be complete disabled, - # takes precedence over all other caching options. - # skip-cache: true - - # Optional: if set to true then the action don't cache or restore ~/go/pkg. - # skip-pkg-cache: true - - # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. - # skip-build-cache: true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3e88b54..2e7f63a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,9 +22,9 @@ jobs: go-version: 1.24.2 - uses: arduino/setup-protoc@v3 - run: | - go install google.golang.org/protobuf/cmd/protoc-gen-go - go get google.golang.org/grpc/cmd/protoc-gen-go-grpc - go install google.golang.org/grpc/cmd/protoc-gen-go-grpc + go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.6 + go get google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 - run: | make # run the tests and create a coverage report @@ -46,6 +46,32 @@ jobs: if-no-files-found: error include-hidden-files: true id: html-upload-step + - name: golangci-lint + uses: golangci/golangci-lint-action@v7 + with: + # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version + version: latest + + # Optional: working directory, useful for monorepos + # working-directory: somedir + + # Optional: golangci-lint command line arguments. + # args: --issues-exit-code=0 + + # Optional: show only new issues if it's a pull request. The default value is `false`. + # only-new-issues: true + + # Optional: if set to true then the all caching functionality will be complete disabled, + # takes precedence over all other caching options. + # skip-cache: true + + # Optional: if set to true then the action don't cache or restore ~/go/pkg. + # skip-pkg-cache: true + + # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. + # skip-build-cache: true + + outputs: artifact-url: ${{ steps.html-upload-step.outputs.artifact-url }} diff --git a/.gitignore b/.gitignore index 6265f16..bc2fcd9 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ rescue-proxy # Dependency directories (remove the comment below to include it) # vendor/ + +executionlayer/dataprovider/abis/*_encoding.go +pb/ diff --git a/Makefile b/Makefile index 11b4583..ec6553e 100644 --- a/Makefile +++ b/Makefile @@ -1,31 +1,61 @@ -VERSION = v2.1.1 +VERSION = v3.0.0 + +ABIGEN_CMD := go run github.com/ethereum/go-ethereum/cmd/abigen@v1.16.1 --v2 SOURCEDIR := . SOURCES := $(shell find $(SOURCEDIR) -name '*.go') PROTO_IN := proto PROTO_OUT := pb +PROTOS := $(PROTO_OUT)/api.pb.go $(PROTO_OUT)/api_grpc.pb.go PROTO_DEPS := $(wildcard $(PROTO_IN)/*.proto) +MULTICALL_ABI_DIR := executionlayer/dataprovider/abis +MULTICALL_ABI_JSON_DIR := $(MULTICALL_ABI_DIR)/json +ABI_ENCODINGS = $(MULTICALL_ABI_DIR)/multicall_encoding.go \ + $(MULTICALL_ABI_DIR)/rocketstorage_encoding.go \ + $(MULTICALL_ABI_DIR)/rocketnodemanager_encoding.go \ + $(MULTICALL_ABI_DIR)/rocketnodedistributorfactory_encoding.go \ + $(MULTICALL_ABI_DIR)/rocketminipoolmanager_encoding.go \ + $(MULTICALL_ABI_DIR)/rocketdaonodetrusted_encoding.go \ + $(MULTICALL_ABI_DIR)/eip1271_encoding.go \ + $(MULTICALL_ABI_DIR)/vaultsregistry_encoding.go \ + $(MULTICALL_ABI_DIR)/ethprivvault_encoding.go + .PHONY: all -all: protos +all: $(PROTOS) $(ABI_ENCODINGS) go build . -.PHONY: protos -protos: $(PROTO_DEPS) +executionlayer/dataprovider/abis/multicall_encoding.go: $(MULTICALL_ABI_JSON_DIR)/multicall_abi.json + $(ABIGEN_CMD) --abi $< --pkg abis --type Multicall3 --out $@ +executionlayer/dataprovider/abis/rocketstorage_encoding.go: $(MULTICALL_ABI_JSON_DIR)/rocketstorage_abi.json + $(ABIGEN_CMD) --abi $< --pkg abis --type RocketStorage --out $@ +executionlayer/dataprovider/abis/rocketnodemanager_encoding.go: $(MULTICALL_ABI_JSON_DIR)/rocketnodemanager_abi.json + $(ABIGEN_CMD) --abi $< --pkg abis --type RocketNodeManager --out $@ +executionlayer/dataprovider/abis/rocketnodedistributorfactory_encoding.go: $(MULTICALL_ABI_JSON_DIR)/rocketnodedistributorfactory_abi.json + $(ABIGEN_CMD) --abi $< --pkg abis --type RocketNodeDistributorFactory --out $@ +executionlayer/dataprovider/abis/rocketminipoolmanager_encoding.go: $(MULTICALL_ABI_JSON_DIR)/rocketminipoolmanager_abi.json + $(ABIGEN_CMD) --abi $< --pkg abis --type RocketMinipoolManager --out $@ +executionlayer/dataprovider/abis/rocketdaonodetrusted_encoding.go: $(MULTICALL_ABI_JSON_DIR)/rocketdaonodetrusted_abi.json + $(ABIGEN_CMD) --abi $< --pkg abis --type RocketDaoNodeTrusted --out $@ +executionlayer/dataprovider/abis/eip1271_encoding.go: $(MULTICALL_ABI_JSON_DIR)/eip1271_abi.json + $(ABIGEN_CMD) --abi $< --pkg abis --type EIP1271 --out $@ +executionlayer/dataprovider/abis/vaultsregistry_encoding.go: $(MULTICALL_ABI_JSON_DIR)/vaults-registry.json + $(ABIGEN_CMD) --abi $< --pkg abis --type VaultsRegistry --out $@ +executionlayer/dataprovider/abis/ethprivvault_encoding.go: $(MULTICALL_ABI_JSON_DIR)/eth-priv-vault.json + $(ABIGEN_CMD) --abi $< --pkg abis --type EthPrivVault --out $@ + +$(PROTO_OUT): + mkdir -p $@ + +$(PROTOS): $(PROTO_DEPS) $(PROTO_OUT) protoc -I=./$(PROTO_IN) --go_out=paths=source_relative:$(PROTO_OUT) \ --go-grpc_out=paths=source_relative:$(PROTO_OUT) $(PROTO_DEPS) -SW_DIR := executionlayer/stakewise -ABI_DIR := $(SW_DIR)/abis -$(SW_DIR)/vaults-registry-encoding.go: $(ABI_DIR)/vaults-registry.json - go run github.com/ethereum/go-ethereum/cmd/abigen@v1.15.11 --v2 --abi $< --pkg stakewise --type vaultsRegistry --out $@ -$(SW_DIR)/eth-priv-vault-encoding.go: $(ABI_DIR)/eth-priv-vault.json - go run github.com/ethereum/go-ethereum/cmd/abigen@v1.15.11 --v2 --abi $< --pkg stakewise --type ethPrivVault --out $@ - .PHONY: clean clean: rm -f pb/* rm -f api-client + rm -f $(ABI_ENCODINGS) .PHONY: docker docker: all diff --git a/config/config.go b/config/config.go index 8bc8919..f551518 100644 --- a/config/config.go +++ b/config/config.go @@ -7,7 +7,9 @@ import ( "net/url" "os" "strings" + "time" + "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" ) @@ -38,22 +40,25 @@ func (c *CredentialSecrets) Set(arg string) error { return nil } +var Multicall3Address = common.HexToAddress("0xcA11bde05977b3631167028862bE2a173976CA11") + type Config struct { - BeaconURL *url.URL - ExecutionURL *url.URL - ListenAddr string - APIListenAddr string - AdminListenAddr string - GRPCListenAddr string - GRPCBeaconAddr string - GRPCTLSCertFile string - GRPCTLSKeyFile string - RocketStorageAddr string - SWVaultsRegistryAddr string - CredentialSecrets CredentialSecrets - EnableSoloValidators bool - Debug bool - ForceBNJSON bool + BeaconURL *url.URL + ExecutionURL *url.URL + ListenAddr string + APIListenAddr string + AdminListenAddr string + GRPCListenAddr string + GRPCBeaconAddr string + GRPCTLSCertFile string + GRPCTLSKeyFile string + RocketStorageAddr string + SWVaultsRegistryAddr string + CredentialSecrets CredentialSecrets + EnableSoloValidators bool + Debug bool + ForceBNJSON bool + ExecutionRefreshInterval time.Duration } func InitFlags() *Config { @@ -81,6 +86,7 @@ Use 'dd if=/dev/urandom bs=4 count=8 | base64' if you need to generate a new sec enableSoloValidatorsFlag := flag.Bool("enable-solo-validators", true, "Whether or not to allow solo validators access.") forceBNJSONFlag := flag.Bool("force-bn-json", false, "Disables SSZ in the BN.") swiseVaultsRegistryAddrFlag := flag.String("swise-vaults-registry-addr", "0x3a0008a588772446f6e656133C2D5029CC4FC20E", "Address of the Stakewise Vaults Registry contract. Defaults to mainnet. Pass empty string to disable.") + executionRefreshInterval := flag.Duration("execution-refresh-interval", 32*12*time.Second, "Interval at which to refresh the execution layer cache. Defaults to once per epoch.") flag.Parse() @@ -173,5 +179,6 @@ Use 'dd if=/dev/urandom bs=4 count=8 | base64' if you need to generate a new sec config.Debug = *debug config.ForceBNJSON = *forceBNJSONFlag config.SWVaultsRegistryAddr = *swiseVaultsRegistryAddrFlag + config.ExecutionRefreshInterval = *executionRefreshInterval return config } diff --git a/executionlayer/cache.go b/executionlayer/cache.go index 1ca492c..de74d90 100644 --- a/executionlayer/cache.go +++ b/executionlayer/cache.go @@ -1,8 +1,7 @@ package executionlayer import ( - "math/big" - + "github.com/Rocket-Rescue-Node/rescue-proxy/executionlayer/dataprovider" "github.com/ethereum/go-ethereum/common" rptypes "github.com/rocket-pool/smartnode/bindings/types" ) @@ -17,14 +16,10 @@ type Cache interface { init() error getMinipoolNode(rptypes.ValidatorPubkey) (common.Address, error) addMinipoolNode(rptypes.ValidatorPubkey, common.Address) error - getNodeInfo(common.Address) (*nodeInfo, error) - addNodeInfo(common.Address, *nodeInfo) error + getNodeInfo(common.Address) (*dataprovider.NodeInfo, error) + addNodeInfo(common.Address, *dataprovider.NodeInfo) error forEachNode(ForEachNodeClosure) error addOdaoNode(common.Address) error removeOdaoNode(common.Address) error forEachOdaoNode(ForEachNodeClosure) error - setHighestBlock(*big.Int) - getHighestBlock() *big.Int - deinit() error - reset() error } diff --git a/executionlayer/dataprovider/abis/json/eip1271_abi.json b/executionlayer/dataprovider/abis/json/eip1271_abi.json new file mode 100644 index 0000000..dff70ac --- /dev/null +++ b/executionlayer/dataprovider/abis/json/eip1271_abi.json @@ -0,0 +1,22 @@ +[ + { + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + }, + { + "name": "_signature", + "type": "bytes" + } + ], + "name": "isValidSignature", + "outputs": [ + { + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/executionlayer/stakewise/abis/eth-priv-vault.json b/executionlayer/dataprovider/abis/json/eth-priv-vault.json similarity index 100% rename from executionlayer/stakewise/abis/eth-priv-vault.json rename to executionlayer/dataprovider/abis/json/eth-priv-vault.json diff --git a/executionlayer/dataprovider/abis/json/multicall_abi.json b/executionlayer/dataprovider/abis/json/multicall_abi.json new file mode 100644 index 0000000..0da96a5 --- /dev/null +++ b/executionlayer/dataprovider/abis/json/multicall_abi.json @@ -0,0 +1,440 @@ +[ + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "returnData", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowFailure", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call3[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate3", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowFailure", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call3Value[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate3Value", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "blockAndAggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getBasefee", + "outputs": [ + { + "internalType": "uint256", + "name": "basefee", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "getBlockHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getChainId", + "outputs": [ + { + "internalType": "uint256", + "name": "chainid", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockCoinbase", + "outputs": [ + { + "internalType": "address", + "name": "coinbase", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockDifficulty", + "outputs": [ + { + "internalType": "uint256", + "name": "difficulty", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockGasLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "gaslimit", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "getEthBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastBlockHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "requireSuccess", + "type": "bool" + }, + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryAggregate", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "requireSuccess", + "type": "bool" + }, + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryBlockAndAggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + } + ] \ No newline at end of file diff --git a/executionlayer/dataprovider/abis/json/rocketdaonodetrusted_abi.json b/executionlayer/dataprovider/abis/json/rocketdaonodetrusted_abi.json new file mode 100644 index 0000000..e0d34c8 --- /dev/null +++ b/executionlayer/dataprovider/abis/json/rocketdaonodetrusted_abi.json @@ -0,0 +1,399 @@ +[ + { + "inputs": [ + { + "internalType": "contract RocketStorageInterface", + "name": "_rocketStorageAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_confirmDisableBootstrapMode", + "type": "bool" + } + ], + "name": "bootstrapDisable", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_id", + "type": "string" + }, + { + "internalType": "string", + "name": "_url", + "type": "string" + }, + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "bootstrapMember", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_settingContractName", + "type": "string" + }, + { + "internalType": "string", + "name": "_settingPath", + "type": "string" + }, + { + "internalType": "bool", + "name": "_value", + "type": "bool" + } + ], + "name": "bootstrapSettingBool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_settingContractName", + "type": "string" + }, + { + "internalType": "string", + "name": "_settingPath", + "type": "string" + }, + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + } + ], + "name": "bootstrapSettingUint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "decrementMemberUnbondedValidatorCount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getBootstrapModeDisabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_index", + "type": "uint256" + } + ], + "name": "getMemberAt", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getMemberID", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getMemberIsChallenged", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getMemberIsValid", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getMemberJoinedTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getMemberLastProposalTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMemberMinRequired", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_proposalType", + "type": "string" + }, + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getMemberProposalExecutedTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMemberQuorumVotesRequired", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getMemberRPLBondAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getMemberUnbondedValidatorCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getMemberUrl", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "incrementMemberUnbondedValidatorCount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_id", + "type": "string" + }, + { + "internalType": "string", + "name": "_url", + "type": "string" + } + ], + "name": "memberJoinRequired", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/executionlayer/dataprovider/abis/json/rocketminipoolmanager_abi.json b/executionlayer/dataprovider/abis/json/rocketminipoolmanager_abi.json new file mode 100644 index 0000000..9bff53e --- /dev/null +++ b/executionlayer/dataprovider/abis/json/rocketminipoolmanager_abi.json @@ -0,0 +1,780 @@ +[ + { + "inputs": [ + { + "internalType": "contract RocketStorageInterface", + "name": "_rocketStorageAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minipool", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "BeginBondReduction", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minipool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "member", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "CancelReductionVoted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minipool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "node", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "MinipoolCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minipool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "node", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "MinipoolDestroyed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minipool", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "ReductionCancelled", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_salt", + "type": "uint256" + } + ], + "name": "createMinipool", + "outputs": [ + { + "internalType": "contract RocketMinipoolInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_salt", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_validatorPubkey", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "_bondAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_currentBalance", + "type": "uint256" + } + ], + "name": "createVacantMinipool", + "outputs": [ + { + "internalType": "contract RocketMinipoolInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "decrementNodeStakingMinipoolCount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "destroyMinipool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getActiveMinipoolCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getFinalisedMinipoolCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_index", + "type": "uint256" + } + ], + "name": "getMinipoolAt", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_pubkey", + "type": "bytes" + } + ], + "name": "getMinipoolByPubkey", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMinipoolCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_limit", + "type": "uint256" + } + ], + "name": "getMinipoolCountPerStatus", + "outputs": [ + { + "internalType": "uint256", + "name": "initialisedCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "prelaunchCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "stakingCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawableCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dissolvedCount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_minipoolAddress", + "type": "address" + } + ], + "name": "getMinipoolDepositType", + "outputs": [ + { + "internalType": "enum MinipoolDeposit", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_minipoolAddress", + "type": "address" + } + ], + "name": "getMinipoolDestroyed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_minipoolAddress", + "type": "address" + } + ], + "name": "getMinipoolExists", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_minipoolAddress", + "type": "address" + } + ], + "name": "getMinipoolPubkey", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_minipoolAddress", + "type": "address" + } + ], + "name": "getMinipoolRPLSlashed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_minipoolAddress", + "type": "address" + } + ], + "name": "getMinipoolWithdrawalCredentials", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getNodeActiveMinipoolCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getNodeFinalisedMinipoolCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_index", + "type": "uint256" + } + ], + "name": "getNodeMinipoolAt", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getNodeMinipoolCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getNodeStakingMinipoolCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_depositSize", + "type": "uint256" + } + ], + "name": "getNodeStakingMinipoolCountBySize", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_index", + "type": "uint256" + } + ], + "name": "getNodeValidatingMinipoolAt", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getNodeValidatingMinipoolCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_limit", + "type": "uint256" + } + ], + "name": "getPrelaunchMinipools", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getStakingMinipoolCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_index", + "type": "uint256" + } + ], + "name": "getVacantMinipoolAt", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVacantMinipoolCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "incrementNodeFinalisedMinipoolCount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "incrementNodeStakingMinipoolCount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "removeVacantMinipool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_pubkey", + "type": "bytes" + } + ], + "name": "setMinipoolPubkey", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "tryDistribute", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_previousBond", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_newBond", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_previousFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_newFee", + "type": "uint256" + } + ], + "name": "updateNodeStakingMinipoolCount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/executionlayer/dataprovider/abis/json/rocketnodedistributorfactory_abi.json b/executionlayer/dataprovider/abis/json/rocketnodedistributorfactory_abi.json new file mode 100644 index 0000000..b66d28d --- /dev/null +++ b/executionlayer/dataprovider/abis/json/rocketnodedistributorfactory_abi.json @@ -0,0 +1,84 @@ +[ + { + "inputs": [ + { + "internalType": "contract RocketStorageInterface", + "name": "_rocketStorageAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "ProxyCreated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "createProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getProxyAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProxyBytecode", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/executionlayer/dataprovider/abis/json/rocketnodemanager_abi.json b/executionlayer/dataprovider/abis/json/rocketnodemanager_abi.json new file mode 100644 index 0000000..b6226a1 --- /dev/null +++ b/executionlayer/dataprovider/abis/json/rocketnodemanager_abi.json @@ -0,0 +1,768 @@ +[ + { + "inputs": [ + { + "internalType": "contract RocketStorageInterface", + "name": "_rocketStorageAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "node", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "withdrawalAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "NodeRPLWithdrawalAddressSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "node", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "NodeRPLWithdrawalAddressUnset", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "node", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "NodeRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "node", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "network", + "type": "uint256" + } + ], + "name": "NodeRewardNetworkChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "node", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "state", + "type": "bool" + } + ], + "name": "NodeSmoothingPoolStateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "node", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "NodeTimezoneLocationSet", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "confirmRPLWithdrawalAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getAverageNodeFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getFeeDistributorInitialised", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_limit", + "type": "uint256" + } + ], + "name": "getNodeAddresses", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_index", + "type": "uint256" + } + ], + "name": "getNodeAt", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNodeCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_limit", + "type": "uint256" + } + ], + "name": "getNodeCountPerTimezone", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "timezone", + "type": "string" + }, + { + "internalType": "uint256", + "name": "count", + "type": "uint256" + } + ], + "internalType": "struct RocketNodeManagerInterface.TimezoneCount[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getNodeDetails", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "exists", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "registrationTime", + "type": "uint256" + }, + { + "internalType": "string", + "name": "timezoneLocation", + "type": "string" + }, + { + "internalType": "bool", + "name": "feeDistributorInitialised", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeDistributorAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "rewardNetwork", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rplStake", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "effectiveRPLStake", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimumRPLStake", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maximumRPLStake", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "ethMatched", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "ethMatchedLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minipoolCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "balanceETH", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "balanceRETH", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "balanceRPL", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "balanceOldRPL", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "depositCreditBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "distributorBalanceUserETH", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "distributorBalanceNodeETH", + "type": "uint256" + }, + { + "internalType": "address", + "name": "withdrawalAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "pendingWithdrawalAddress", + "type": "address" + }, + { + "internalType": "bool", + "name": "smoothingPoolRegistrationState", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "smoothingPoolRegistrationChanged", + "type": "uint256" + }, + { + "internalType": "address", + "name": "nodeAddress", + "type": "address" + } + ], + "internalType": "struct NodeDetails", + "name": "nodeDetails", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getNodeExists", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getNodePendingRPLWithdrawalAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getNodePendingWithdrawalAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getNodeRPLWithdrawalAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getNodeRPLWithdrawalAddressIsSet", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getNodeRegistrationTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getNodeTimezoneLocation", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getNodeWithdrawalAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getRewardNetwork", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_limit", + "type": "uint256" + } + ], + "name": "getSmoothingPoolRegisteredNodeCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getSmoothingPoolRegistrationChanged", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getSmoothingPoolRegistrationState", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialiseFeeDistributor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_timezoneLocation", + "type": "string" + } + ], + "name": "registerNode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_newRPLWithdrawalAddress", + "type": "address" + }, + { + "internalType": "bool", + "name": "_confirm", + "type": "bool" + } + ], + "name": "setRPLWithdrawalAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_network", + "type": "uint256" + } + ], + "name": "setRewardNetwork", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_state", + "type": "bool" + } + ], + "name": "setSmoothingPoolRegistrationState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_timezoneLocation", + "type": "string" + } + ], + "name": "setTimezoneLocation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "unsetRPLWithdrawalAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/executionlayer/dataprovider/abis/json/rocketstorage_abi.json b/executionlayer/dataprovider/abis/json/rocketstorage_abi.json new file mode 100644 index 0000000..019c02f --- /dev/null +++ b/executionlayer/dataprovider/abis/json/rocketstorage_abi.json @@ -0,0 +1,564 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldGuardian", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newGuardian", + "type": "address" + } + ], + "name": "GuardianChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "node", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "withdrawalAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "NodeWithdrawalAddressSet", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "addUint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "confirmGuardian", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "confirmWithdrawalAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + } + ], + "name": "deleteAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + } + ], + "name": "deleteBool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + } + ], + "name": "deleteBytes", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + } + ], + "name": "deleteBytes32", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + } + ], + "name": "deleteInt", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + } + ], + "name": "deleteString", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + } + ], + "name": "deleteUint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + } + ], + "name": "getAddress", + "outputs": [ + { + "internalType": "address", + "name": "r", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + } + ], + "name": "getBool", + "outputs": [ + { + "internalType": "bool", + "name": "r", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + } + ], + "name": "getBytes", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + } + ], + "name": "getBytes32", + "outputs": [ + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getDeployedStatus", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getGuardian", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + } + ], + "name": "getInt", + "outputs": [ + { + "internalType": "int256", + "name": "r", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getNodePendingWithdrawalAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "getNodeWithdrawalAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + } + ], + "name": "getString", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + } + ], + "name": "getUint", + "outputs": [ + { + "internalType": "uint256", + "name": "r", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_value", + "type": "address" + } + ], + "name": "setAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + }, + { + "internalType": "bool", + "name": "_value", + "type": "bool" + } + ], + "name": "setBool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_value", + "type": "bytes" + } + ], + "name": "setBytes", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_value", + "type": "bytes32" + } + ], + "name": "setBytes32", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "setDeployedStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newAddress", + "type": "address" + } + ], + "name": "setGuardian", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + }, + { + "internalType": "int256", + "name": "_value", + "type": "int256" + } + ], + "name": "setInt", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "_value", + "type": "string" + } + ], + "name": "setString", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + } + ], + "name": "setUint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_newWithdrawalAddress", + "type": "address" + }, + { + "internalType": "bool", + "name": "_confirm", + "type": "bool" + } + ], + "name": "setWithdrawalAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_key", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "subUint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/executionlayer/stakewise/abis/vaults-registry.json b/executionlayer/dataprovider/abis/json/vaults-registry.json similarity index 100% rename from executionlayer/stakewise/abis/vaults-registry.json rename to executionlayer/dataprovider/abis/json/vaults-registry.json diff --git a/executionlayer/dataprovider/interface.go b/executionlayer/dataprovider/interface.go new file mode 100644 index 0000000..cf27dd3 --- /dev/null +++ b/executionlayer/dataprovider/interface.go @@ -0,0 +1,29 @@ +package dataprovider + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + rptypes "github.com/rocket-pool/smartnode/bindings/types" +) + +type NodeInfo struct { + InSmoothingPool bool + FeeDistributor common.Address +} + +type DataProvider interface { + GetAllNodes(opts *bind.CallOpts) (map[common.Address]*NodeInfo, error) + GetAllMinipools(nodeMap map[common.Address]*NodeInfo, opts *bind.CallOpts) (map[common.Address][]rptypes.ValidatorPubkey, error) + GetAllOdaoNodes(opts *bind.CallOpts) ([]common.Address, error) + GetREthAddress() common.Address + GetSmoothingPoolAddress() common.Address + StakewiseFeeRecipient(opts *bind.CallOpts, address common.Address) (*common.Address, error) + ValidateEIP1271(opts *bind.CallOpts, dataHash common.Hash, signature []byte, address common.Address) (bool, error) + + HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) + RefreshAddresses(opts *bind.CallOpts) error +} diff --git a/executionlayer/dataprovider/multicall.go b/executionlayer/dataprovider/multicall.go new file mode 100644 index 0000000..c1dc7ab --- /dev/null +++ b/executionlayer/dataprovider/multicall.go @@ -0,0 +1,518 @@ +package dataprovider + +import ( + "bytes" + "context" + "errors" + "fmt" + "math/big" + + "github.com/Rocket-Rescue-Node/rescue-proxy/executionlayer/dataprovider/abis" + "github.com/Rocket-Rescue-Node/rescue-proxy/executionlayer/stakewise" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + rptypes "github.com/rocket-pool/smartnode/bindings/types" +) + +type Multicall struct { + NodeBatchSize int + SWVaultsRegistryAddr string + + client bind.ContractBackend + rocketStorageAddress common.Address + multicallInstance *bind.BoundContract + rocketDaoNodeTrustedInstance *bind.BoundContract + + rocketNodeManagerAddress common.Address + rocketNodeDistributorFactoryAddress common.Address + rocketMinipoolManagerAddress common.Address + rocketTokenREthAddress common.Address + rocketSmoothingPoolAddress common.Address + + // Checkers for vaults and mev escrow + vaultsChecker *stakewise.VaultsChecker +} + +var multicall3 *abis.Multicall3 +var rocketStorage *abis.RocketStorage +var rocketNodeManager *abis.RocketNodeManager +var rocketNodeDistributorFactory *abis.RocketNodeDistributorFactory +var rocketMinipoolManager *abis.RocketMinipoolManager +var rocketDaoNodeTrusted *abis.RocketDaoNodeTrusted +var eip1271 *abis.EIP1271 + +func init() { + multicall3 = abis.NewMulticall3() + rocketStorage = abis.NewRocketStorage() + rocketNodeManager = abis.NewRocketNodeManager() + rocketNodeDistributorFactory = abis.NewRocketNodeDistributorFactory() + rocketMinipoolManager = abis.NewRocketMinipoolManager() + rocketDaoNodeTrusted = abis.NewRocketDaoNodeTrusted() + eip1271 = abis.NewEIP1271() +} + +func NewMulticall(ctx context.Context, client bind.ContractBackend, + rocketStorageAddress common.Address, + contractAddress common.Address, +) (*Multicall, error) { + + // get the head block height + headBlock, err := client.HeaderByNumber(ctx, nil) + if err != nil { + return nil, fmt.Errorf("failed to get head block: %w", err) + } + + headBlockHeight := headBlock.Number + opts := bind.CallOpts{ + Context: ctx, + BlockNumber: headBlockHeight, + } + + out := &Multicall{ + client: client, + rocketStorageAddress: rocketStorageAddress, + multicallInstance: multicall3.Instance(client, contractAddress), + NodeBatchSize: 100, + } + + err = out.RefreshAddresses(&opts) + if err != nil { + return nil, err + } + return out, nil +} + +func (m *Multicall) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + return m.client.HeaderByNumber(ctx, number) +} + +func (m *Multicall) RefreshAddresses(opts *bind.CallOpts) error { + storageInstance := rocketStorage.Instance(m.client, m.rocketStorageAddress) + + // hashes precomputed from https://emn178.github.io/online-tools/keccak_256.html + + // nodeManagerKey is a keccak256 of "contract.addressrocketNodeManager" + nodeManagerKey := common.HexToHash("0xaf00be55c9fb8f543c04e0aa0d70351b880c1bfafffd15b60065a4a50c85ec94") + // get nodeManagerAddress from storage + nodeManagerAddressCallPacked := rocketStorage.PackGetAddress(nodeManagerKey) + nodeManagerAddressResponsePacked, err := storageInstance.CallRaw(opts, nodeManagerAddressCallPacked) + if err != nil { + return fmt.Errorf("failed to get node manager address: %w", err) + } + nodeManagerAddress, err := rocketStorage.UnpackGetAddress(nodeManagerAddressResponsePacked) + if err != nil { + return fmt.Errorf("failed to unpack node manager address: %w", err) + } + + // nodeDistributorFactoryKey is a keccak256 of "contract.addressrocketNodeDistributorFactory" + nodeDistributorFactoryKey := common.HexToHash("0xea051094896ef3b09ab1b794ad5ea695a5ff3906f74a9328e2c16db69d0f3123") + nodeDistributorFactoryAddressCallPacked := rocketStorage.PackGetAddress(nodeDistributorFactoryKey) + nodeDistributorFactoryAddressResponsePacked, err := storageInstance.CallRaw(opts, nodeDistributorFactoryAddressCallPacked) + if err != nil { + return fmt.Errorf("failed to get node distributor factory address: %w", err) + } + nodeDistributorFactoryAddress, err := rocketStorage.UnpackGetAddress(nodeDistributorFactoryAddressResponsePacked) + if err != nil { + return fmt.Errorf("failed to unpack node distributor factory address: %w", err) + } + + // minipoolManagerKey is a keccak256 of "contract.addressrocketMinipoolManager" + minipoolManagerKey := common.HexToHash("0xe9dfec9339b94a131861a58f1bb4ac4c1ce55c7ffe8550e0b6ebcfde87bb012f") + minipoolManagerAddressCallPacked := rocketStorage.PackGetAddress(minipoolManagerKey) + minipoolManagerAddressResponsePacked, err := storageInstance.CallRaw(opts, minipoolManagerAddressCallPacked) + if err != nil { + return fmt.Errorf("failed to get minipool manager address: %w", err) + } + minipoolManagerAddress, err := rocketStorage.UnpackGetAddress(minipoolManagerAddressResponsePacked) + if err != nil { + return fmt.Errorf("failed to unpack minipool manager address: %w", err) + } + + // rocketDAONodeTrustedKey is a keccak256 of "contract.addressrocketDAONodeTrusted" + rocketDAONodeTrustedKey := common.HexToHash("0x9a354e1bb2e38ca826db7a8d061cfb0ed7dbd83d241a2cbe4fd5218f9bb4333f") + rocketDAONodeTrustedAddressCallPacked := rocketStorage.PackGetAddress(rocketDAONodeTrustedKey) + rocketDAONodeTrustedAddressResponsePacked, err := storageInstance.CallRaw(opts, rocketDAONodeTrustedAddressCallPacked) + if err != nil { + return fmt.Errorf("failed to get rocket dao node trusted address: %w", err) + } + rocketDAONodeTrustedAddress, err := rocketStorage.UnpackGetAddress(rocketDAONodeTrustedAddressResponsePacked) + if err != nil { + return fmt.Errorf("failed to unpack rocket dao node trusted address: %w", err) + } + + // rethAddressKey is a keccak256 of "contract.addressrocketTokenRETH" + rethAddressKey := common.HexToHash("0xe3744443225bff7cc22028be036b80de58057d65a3fdca0a3df329f525e31ccc") + rethAddressCallPacked := rocketStorage.PackGetAddress(rethAddressKey) + rethAddressResponsePacked, err := storageInstance.CallRaw(opts, rethAddressCallPacked) + if err != nil { + return fmt.Errorf("failed to get reth address: %w", err) + } + rethAddress, err := rocketStorage.UnpackGetAddress(rethAddressResponsePacked) + if err != nil { + return fmt.Errorf("failed to unpack reth address: %w", err) + } + + // smoothingPoolAddressKey is a keccak256 of "contract.addressrocketSmoothingPool" + smoothingPoolAddressKey := common.HexToHash("0x822231720aef9b264db1d9ca053137498f759c28b243f45c44db1d39d6bce46e") + smoothingPoolAddressCallPacked := rocketStorage.PackGetAddress(smoothingPoolAddressKey) + smoothingPoolAddressResponsePacked, err := storageInstance.CallRaw(opts, smoothingPoolAddressCallPacked) + if err != nil { + return fmt.Errorf("failed to get smoothing pool address: %w", err) + } + smoothingPoolAddress, err := rocketStorage.UnpackGetAddress(smoothingPoolAddressResponsePacked) + if err != nil { + return fmt.Errorf("failed to unpack smoothing pool address: %w", err) + } + + m.rocketNodeManagerAddress = nodeManagerAddress + m.rocketNodeDistributorFactoryAddress = nodeDistributorFactoryAddress + m.rocketMinipoolManagerAddress = minipoolManagerAddress + m.rocketDaoNodeTrustedInstance = rocketDaoNodeTrusted.Instance(m.client, rocketDAONodeTrustedAddress) + m.rocketTokenREthAddress = rethAddress + m.rocketSmoothingPoolAddress = smoothingPoolAddress + + return nil +} + +func (m *Multicall) GetAllNodes(opts *bind.CallOpts) (map[common.Address]*NodeInfo, error) { + // Create the rocketNodeManagerInstance + rocketNodeManagerInstance := rocketNodeManager.Instance(m.client, m.rocketNodeManagerAddress) + // Get the total number of registered nodes + getNodeCountPacked := rocketNodeManager.PackGetNodeCount() + getNodeCountResponsePacked, err := rocketNodeManagerInstance.CallRaw(opts, getNodeCountPacked) + if err != nil { + return nil, fmt.Errorf("failed to get node count: %w", err) + } + nodeCount, err := rocketNodeManager.UnpackGetNodeCount(getNodeCountResponsePacked) + if err != nil { + return nil, fmt.Errorf("failed to unpack node count: %w", err) + } + + // Grab up to m.NodeBatchSize at a time, and return the total number of nodes + nodeMap := make(map[common.Address]*NodeInfo) + nodeAddresses := make([]common.Address, 0, nodeCount.Int64()) + for i := int64(0); i < nodeCount.Int64(); i += int64(m.NodeBatchSize) { + getNodeAddressesPacked := rocketNodeManager.PackGetNodeAddresses(big.NewInt(i), big.NewInt(int64(m.NodeBatchSize))) + getNodesResponsePacked, err := rocketNodeManagerInstance.CallRaw(opts, getNodeAddressesPacked) + if err != nil { + return nil, fmt.Errorf("failed to get nodes: %w", err) + } + nodes, err := rocketNodeManager.UnpackGetNodeAddresses(getNodesResponsePacked) + if err != nil { + return nil, fmt.Errorf("failed to unpack nodes: %w", err) + } + nodeAddresses = append(nodeAddresses, nodes...) + } + + // In batches of 500, populate FeeDistributor + for i := 0; i < len(nodeAddresses); i += m.NodeBatchSize { + batch := nodeAddresses[i:min(i+m.NodeBatchSize, len(nodeAddresses))] + + calls := make([]abis.Multicall3Call3, 0, len(batch)) + for _, node := range batch { + calls = append(calls, abis.Multicall3Call3{ + Target: m.rocketNodeDistributorFactoryAddress, + AllowFailure: false, + CallData: rocketNodeDistributorFactory.PackGetProxyAddress(node), + }) + } + + // Run the multicall call + mcPacked := multicall3.PackAggregate3(calls) + mcResponsePacked, err := m.multicallInstance.CallRaw(opts, mcPacked) + if err != nil { + return nil, fmt.Errorf("failed to call multicall: %w", err) + } + mcResponse, err := multicall3.UnpackAggregate3(mcResponsePacked) + if err != nil { + return nil, fmt.Errorf("failed to unpack multicall response: %w", err) + } + + // Unpack the inner responses. + for j, call := range mcResponse { + if !call.Success { + return nil, errors.New("multicall element failed") + } + rawResult := call.ReturnData + distributorAddress, err := rocketNodeDistributorFactory.UnpackGetProxyAddress(rawResult) + if err != nil { + return nil, fmt.Errorf("failed to unpack distributor address: %w", err) + } + // batch is 0-index, but the response will be in the same order as the calls + // Therefor, batch[j] is the address of the node for each result + // Since this is the first datum for each node, create a new NodeInfo struct. + nodeMap[batch[j]] = &NodeInfo{ + FeeDistributor: distributorAddress, + } + } + } + + // In batches of 500, populate InSmoothingPool + for i := 0; i < len(nodeAddresses); i += m.NodeBatchSize { + batch := nodeAddresses[i:min(i+m.NodeBatchSize, len(nodeAddresses))] + + calls := make([]abis.Multicall3Call3, 0, len(batch)) + for _, node := range batch { + calls = append(calls, abis.Multicall3Call3{ + Target: m.rocketNodeManagerAddress, + AllowFailure: false, + CallData: rocketNodeManager.PackGetSmoothingPoolRegistrationState(node), + }) + } + + // Run the multicall call + mcPacked := multicall3.PackAggregate3(calls) + mcResponsePacked, err := m.multicallInstance.CallRaw(opts, mcPacked) + if err != nil { + return nil, fmt.Errorf("failed to call multicall: %w", err) + } + mcResponse, err := multicall3.UnpackAggregate3(mcResponsePacked) + if err != nil { + return nil, fmt.Errorf("failed to unpack multicall response: %w", err) + } + + // Unpack the inner responses. + for j, call := range mcResponse { + if !call.Success { + return nil, errors.New("multicall element failed") + } + rawResult := call.ReturnData + smoothingPoolRegistrationState, err := rocketNodeManager.UnpackGetSmoothingPoolRegistrationState(rawResult) + if err != nil { + return nil, fmt.Errorf("failed to unpack smoothing pool registration state: %w", err) + } + nodeMap[batch[j]].InSmoothingPool = smoothingPoolRegistrationState + } + } + return nodeMap, nil +} + +func (m *Multicall) GetAllMinipools(nodes map[common.Address]*NodeInfo, opts *bind.CallOpts) (map[common.Address][]rptypes.ValidatorPubkey, error) { + type nodeInfo struct { + address common.Address + minipoolCount *big.Int + minipools []common.Address + validators []rptypes.ValidatorPubkey + } + + // Create a nodeInfo slice from the nodeMap + nodeInfos := make([]*nodeInfo, 0, len(nodes)) + for addr := range nodes { + nodeInfos = append(nodeInfos, &nodeInfo{ + address: addr, + }) + } + + // Working 500 nodes at a time, get the minipool count + for i := 0; i < len(nodeInfos); i += m.NodeBatchSize { + batch := nodeInfos[i:min(i+m.NodeBatchSize, len(nodeInfos))] + + calls := make([]abis.Multicall3Call3, 0, len(batch)) + for _, node := range batch { + calls = append(calls, abis.Multicall3Call3{ + Target: m.rocketMinipoolManagerAddress, + AllowFailure: false, + CallData: rocketMinipoolManager.PackGetNodeMinipoolCount(node.address), + }) + } + + mcPacked := multicall3.PackAggregate3(calls) + mcResponsePacked, err := m.multicallInstance.CallRaw(opts, mcPacked) + if err != nil { + return nil, fmt.Errorf("failed to call multicall: %w", err) + } + mcResponse, err := multicall3.UnpackAggregate3(mcResponsePacked) + if err != nil { + return nil, fmt.Errorf("failed to unpack multicall response: %w", err) + } + + for j, call := range mcResponse { + if !call.Success { + return nil, errors.New("multicall element failed") + } + rawResult := call.ReturnData + minipoolCount, err := rocketMinipoolManager.UnpackGetNodeMinipoolCount(rawResult) + if err != nil { + return nil, fmt.Errorf("failed to unpack minipool count: %w", err) + } + batch[j].minipoolCount = minipoolCount + } + } + + // Working 500 nodes at a time, get the minipool addresses + for i := 0; i < len(nodeInfos); i += m.NodeBatchSize { + batch := nodeInfos[i:min(i+m.NodeBatchSize, len(nodeInfos))] + + calls := make([]abis.Multicall3Call3, 0, len(batch)) + for _, node := range batch { + for j := big.NewInt(0); j.Cmp(node.minipoolCount) < 0; j.Add(j, big.NewInt(1)) { + calls = append(calls, abis.Multicall3Call3{ + Target: m.rocketMinipoolManagerAddress, + AllowFailure: false, + CallData: rocketMinipoolManager.PackGetNodeMinipoolAt(node.address, big.NewInt(0).Set(j)), + }) + } + } + + mcPacked := multicall3.PackAggregate3(calls) + mcResponsePacked, err := m.multicallInstance.CallRaw(opts, mcPacked) + if err != nil { + return nil, fmt.Errorf("failed to call multicall: %w", err) + } + mcResponse, err := multicall3.UnpackAggregate3(mcResponsePacked) + if err != nil { + return nil, fmt.Errorf("failed to unpack multicall response: %w", err) + } + + resultIndex := 0 + for j := range batch { + for range batch[j].minipoolCount.Int64() { + rawResult := mcResponse[resultIndex].ReturnData + minipoolAddress, err := rocketMinipoolManager.UnpackGetNodeMinipoolAt(rawResult) + if err != nil { + return nil, fmt.Errorf("failed to unpack minipool address: %w", err) + } + batch[j].minipools = append(batch[j].minipools, minipoolAddress) + resultIndex++ + } + } + } + + // Working 500 nodes at a time, get the minipool pubkeys + for i := 0; i < len(nodeInfos); i += m.NodeBatchSize { + batch := nodeInfos[i:min(i+m.NodeBatchSize, len(nodeInfos))] + + calls := make([]abis.Multicall3Call3, 0, len(batch)) + for _, node := range batch { + for _, minipool := range node.minipools { + calls = append(calls, abis.Multicall3Call3{ + Target: m.rocketMinipoolManagerAddress, + AllowFailure: false, + CallData: rocketMinipoolManager.PackGetMinipoolPubkey(minipool), + }) + } + } + + mcPacked := multicall3.PackAggregate3(calls) + mcResponsePacked, err := m.multicallInstance.CallRaw(opts, mcPacked) + if err != nil { + return nil, fmt.Errorf("failed to call multicall: %w", err) + } + mcResponse, err := multicall3.UnpackAggregate3(mcResponsePacked) + if err != nil { + return nil, fmt.Errorf("failed to unpack multicall response: %w", err) + } + + resultIndex := 0 + for j := range batch { + for range batch[j].minipoolCount.Int64() { + rawResult := mcResponse[resultIndex].ReturnData + minipoolPubkey, err := rocketMinipoolManager.UnpackGetMinipoolPubkey(rawResult) + if err != nil { + return nil, fmt.Errorf("failed to unpack minipool pubkey: %w", err) + } + batch[j].validators = append(batch[j].validators, rptypes.ValidatorPubkey(minipoolPubkey)) + resultIndex++ + } + } + } + + // Finally, create an address->[]pubkey map to return + out := make(map[common.Address][]rptypes.ValidatorPubkey, len(nodeInfos)) + for _, nodeInfo := range nodeInfos { + out[nodeInfo.address] = nodeInfo.validators + } + + return out, nil +} + +func (m *Multicall) GetAllOdaoNodes(opts *bind.CallOpts) ([]common.Address, error) { + // There aren't enough odao nodes to justify custom multicall + // Just use RP's exisitng functionality + memberCountCallPacked := rocketDaoNodeTrusted.PackGetMemberCount() + memberCountResponsePacked, err := m.rocketDaoNodeTrustedInstance.CallRaw(opts, memberCountCallPacked) + if err != nil { + return nil, fmt.Errorf("failed to call multicall: %w", err) + } + memberCount, err := rocketDaoNodeTrusted.UnpackGetMemberCount(memberCountResponsePacked) + if err != nil { + return nil, fmt.Errorf("failed to unpack member count: %w", err) + } + out := make([]common.Address, 0, memberCount.Int64()) + for i := big.NewInt(0); i.Cmp(memberCount) < 0; i.Add(i, big.NewInt(1)) { + getMemberCallPacked := rocketDaoNodeTrusted.PackGetMemberAt(i) + getMemberResponsePacked, err := m.rocketDaoNodeTrustedInstance.CallRaw(opts, getMemberCallPacked) + if err != nil { + return nil, fmt.Errorf("failed to call getMemberAt: %w", err) + } + memberAddress, err := rocketDaoNodeTrusted.UnpackGetMemberAt(getMemberResponsePacked) + if err != nil { + return nil, fmt.Errorf("failed to unpack member address: %w", err) + } + out = append(out, memberAddress) + } + + return out, nil +} + +func (m *Multicall) GetREthAddress() common.Address { + return m.rocketTokenREthAddress +} + +func (m *Multicall) GetSmoothingPoolAddress() common.Address { + return m.rocketSmoothingPoolAddress +} + +func (m *Multicall) StakewiseFeeRecipient(opts *bind.CallOpts, address common.Address) (*common.Address, error) { + if m.SWVaultsRegistryAddr == "" { + return nil, errors.New("SWVaultsRegistryAddr is not set") + } + if m.vaultsChecker == nil { + m.vaultsChecker = stakewise.NewVaultsChecker(m.client, common.HexToAddress(m.SWVaultsRegistryAddr)) + } + + return m.vaultsChecker.IsVault(opts, address) +} + +var ErrNoData = errors.New("no data were returned from the EVM, did you pass the correct smart contract wallet address?") +var ErrBadData = errors.New("the evm returned data with an unexpected length, did you pass the correct smart contract wallet address?") +var ErrInternal = errors.New("an internal error occurred, please contact the maintainers") + +func (m *Multicall) ValidateEIP1271(opts *bind.CallOpts, dataHash common.Hash, signature []byte, address common.Address) (bool, error) { + // Encode the function call + encodedData := eip1271.PackIsValidSignature(dataHash, signature) + + // Make the contract call + data, err := m.client.CallContract(opts.Context, ethereum.CallMsg{ + To: &address, + Data: encodedData, + }, nil) + if err != nil { + return false, ErrInternal + } + + if len(data) == 0 { + return false, ErrNoData + } + + if len(data) < 4 { + return false, ErrBadData + } + + // Trim the trailing bytes from the evm + data = data[:4] + + // Check the return value, it should be exactly 4 bytes long + if len(data) != 4 { + return false, ErrBadData + } + + // The expected return value for a valid signature is 0x1626ba7e + // bytes4(keccak256("isValidSignature(bytes32,bytes)") + // invalid signatures return 4 bytes that do not match the magic + expectedReturnValue := [4]byte{0x16, 0x26, 0xba, 0x7e} + return bytes.Equal(data, expectedReturnValue[:]), nil + +} diff --git a/executionlayer/execution-layer.go b/executionlayer/execution-layer.go index c16350f..4b37f6f 100644 --- a/executionlayer/execution-layer.go +++ b/executionlayer/execution-layer.go @@ -1,41 +1,21 @@ package executionlayer import ( - "bytes" "context" - "errors" "fmt" - "math/big" - "net/url" - "strings" + "sync/atomic" "time" - "github.com/Rocket-Rescue-Node/rescue-proxy/executionlayer/stakewise" + "github.com/Rocket-Rescue-Node/rescue-proxy/executionlayer/dataprovider" "github.com/Rocket-Rescue-Node/rescue-proxy/metrics" - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/rocket-pool/smartnode/bindings/minipool" - "github.com/rocket-pool/smartnode/bindings/node" - "github.com/rocket-pool/smartnode/bindings/rocketpool" rptypes "github.com/rocket-pool/smartnode/bindings/types" - "github.com/rocket-pool/smartnode/bindings/utils/state" "go.uber.org/zap" ) type ForEachNodeClosure func(common.Address) bool -const reconnectRetries = 10 - -type nodeInfo struct { - inSmoothingPool bool - feeDistributor common.Address -} - type RPInfo struct { ExpectedFeeRecipient *common.Address NodeAddress common.Address @@ -52,580 +32,100 @@ type ExecutionLayer interface { ValidateEIP1271(ctx context.Context, dataHash common.Hash, signature []byte, address common.Address) (bool, error) } +type cacheRCU struct { + Cache + + smoothingPoolAddress common.Address + rethAddress common.Address +} + // CachingExecutionLayer is a bespoke execution layer client for the rescue proxy. // It abstracts away all the work to cache in-memory the data needed to enforce // that fee recipients are 'correct'. type CachingExecutionLayer struct { // Fields passed in by the constructor which are later referenced - Logger *zap.Logger - ECURL *url.URL - RocketStorageAddr string - SWVaultsRegistryAddr string - - // The rocketpool-go client and its ethclient instance - - rp *rocketpool.RocketPool - client *ethclient.Client - - // Smart contracts we either read from or need the address of - - rocketNodeManager *rocketpool.Contract - rocketMinipoolManager *rocketpool.Contract - smoothingPool *rocketpool.Contract - rEth *rocketpool.Contract - rocketDaoNodeTrustedActions *rocketpool.Contract - - // The "topics" of the events we subscribe to - - nodeRegisteredTopic common.Hash - smoothingPoolStatusChangedTopic common.Hash - minipoolLaunchedTopic common.Hash - odaoJoinedTopic common.Hash - odaoLeftTopic common.Hash - odaoKickedTopic common.Hash - - // The "topics" and contract filter for the events we subscribe to - query ethereum.FilterQuery - - // Channels for those subscriptions - events chan types.Log - newHeaders chan *types.Header - - cache Cache + Logger *zap.Logger + Context context.Context + RefreshInterval time.Duration + DataProvider dataprovider.DataProvider - // Checkers for vaults and mev escrow - vaultsChecker *stakewise.VaultsChecker - - // ethclient subscription needs to be manually closed on shutdown - ethclientShutdownCb func() - - // A context that can be canceled in order to gracefully stop the EL abstraction - ctx context.Context - // A function that cancels that context - shutdown func() + cache atomic.Pointer[cacheRCU] m *metrics.MetricsRegistry - connected chan bool -} - -func (e *CachingExecutionLayer) setECShutdownCb(cb func()) { - if cb == nil { - e.ethclientShutdownCb = nil - return - } - - e.ethclientShutdownCb = func() { - cb() - e.Logger.Info("Unsubscribed from EL events") - } -} - -func (e *CachingExecutionLayer) handleNodeEvent(event types.Log) { - - // Check if it's a node registration - if bytes.Equal(event.Topics[0].Bytes(), e.nodeRegisteredTopic.Bytes()) { - var err error - - addr := common.BytesToAddress(event.Topics[1].Bytes()) - // When we see new nodes register, assume they aren't in the SP and add to index - nodeInfo := &nodeInfo{} - // Get their fee distributor address - nodeInfo.feeDistributor, err = node.GetDistributorAddress(e.rp, addr, nil) - if err != nil { - e.Logger.Warn("Couldn't get fee distributor address for newly registered node", zap.String("node", addr.String())) - } - err = e.cache.addNodeInfo(addr, nodeInfo) - if err != nil { - e.Logger.Error("Failed to add nodeInfo to cache", zap.Error(err)) - } - - e.m.Counter("node_registration_added").Inc() - e.Logger.Info("New node registered", zap.String("addr", addr.String())) - return - } - - // Otherwise it should be a smoothing pool update - if bytes.Equal(event.Topics[0].Bytes(), e.smoothingPoolStatusChangedTopic.Bytes()) { - var n *nodeInfo - // When we see a SP status change, update the pointer in the index - nodeAddr := common.BytesToAddress(event.Topics[1].Bytes()) - status := big.NewInt(0).SetBytes(event.Data) - - // Attempt to load the node - n, err := e.cache.getNodeInfo(nodeAddr) - if err != nil { - _, ok := err.(*NotFoundError) - - if !ok { - e.Logger.Panic("Got an error from the cache while looking up a node", - zap.String("addr", nodeAddr.String()), zap.Error(err)) - } - - // Odd that we don't have this node already, but add it and carry on - e.Logger.Warn("Unknown node updated its smoothing pool status", zap.String("addr", nodeAddr.String())) - n = &nodeInfo{} - // Get their fee distributor address - n.feeDistributor, err = node.GetDistributorAddress(e.rp, nodeAddr, nil) - if err != nil { - e.Logger.Warn("Couldn't compute fee distributor address for unknown node", zap.String("node", nodeAddr.String())) - } - - } - - e.Logger.Info("Node SP status changed", zap.String("addr", nodeAddr.String()), zap.Bool("in_sp", status.Cmp(big.NewInt(1)) == 0)) - n.inSmoothingPool = status.Cmp(big.NewInt(1)) == 0 - err = e.cache.addNodeInfo(nodeAddr, n) - if err != nil { - e.Logger.Error("Failed to add nodeInfo to cache", zap.Error(err)) - } - - e.m.Counter("smoothing_pool_status_changed").Inc() - return - } - - e.Logger.Warn("Event with unknown topic received", zap.String("string", event.Topics[0].String())) -} - -func (e *CachingExecutionLayer) handleMinipoolEvent(event types.Log) { - - // Make sure it's an event for the only topic we subscribed to, minipool launches - if !bytes.Equal(event.Topics[0].Bytes(), e.minipoolLaunchedTopic.Bytes()) { - e.Logger.Warn("Event with unknown topic received", zap.String("string", event.Topics[0].String())) - return - } - - // When a new minipool launches, grab its node address. - nodeAddr := common.BytesToAddress(event.Topics[2].Bytes()) - - // Grab its minipool (contract) address and use that to find its public key - minipoolAddr := common.BytesToAddress(event.Topics[1].Bytes()) - pubkey, err := minipool.GetMinipoolPubkey(e.rp, minipoolAddr, nil) - if err != nil { - e.Logger.Warn("Error fetching minipool pubkey for new minipool", zap.String("minipool", minipoolAddr.String()), zap.Error(err)) - return - } - - // Finally, update the minipool index - err = e.cache.addMinipoolNode(pubkey, nodeAddr) - if err != nil { - e.Logger.Warn("Error updating minipool cache", zap.Error(err)) - } - e.m.Counter("minipool_launch_received").Inc() - e.Logger.Info("Added new minipool", zap.String("pubkey", pubkey.String()), zap.String("node", nodeAddr.String())) -} - -func (e *CachingExecutionLayer) handleOdaoEvent(event types.Log) { - - if bytes.Equal(event.Topics[0].Bytes(), e.odaoJoinedTopic.Bytes()) { - addr := common.BytesToAddress(event.Topics[1].Bytes()) - - err := e.cache.addOdaoNode(addr) - if err != nil { - e.Logger.Warn("Error updating odao cache", zap.Error(err)) - } - return - } - - if bytes.Equal(event.Topics[0].Bytes(), e.odaoLeftTopic.Bytes()) || - bytes.Equal(event.Topics[0].Bytes(), e.odaoKickedTopic.Bytes()) { - - addr := common.BytesToAddress(event.Topics[1].Bytes()) - - err := e.cache.removeOdaoNode(addr) - if err != nil { - e.Logger.Warn("Error updating odao cache", zap.Error(err)) - } - return - } - - e.Logger.Warn("Event with unknown topic received", zap.String("string", event.Topics[0].String())) -} - -func (e *CachingExecutionLayer) handleEvent(event types.Log) { - // events from the rocketNodeManager contract - e.m.Counter("subscription_event_received").Inc() - if bytes.Equal(e.rocketNodeManager.Address[:], event.Address[:]) { - e.handleNodeEvent(event) - goto out - } - - // events from the rocketMinipoolManager contract - if bytes.Equal(e.rocketMinipoolManager.Address[:], event.Address[:]) { - e.handleMinipoolEvent(event) - goto out - } - - // events from the rocketDAONodeTrustedActions contract - if bytes.Equal(e.rocketDaoNodeTrustedActions.Address[:], event.Address[:]) { - e.handleOdaoEvent(event) - goto out - } - - // Shouldn't ever happen, barring a bug in ethclient - e.Logger.Warn("Received event for unknown contract", zap.String("address", event.Address.String())) -out: - // We should always update highestBlock when we receive any event - e.cache.setHighestBlock(big.NewInt(int64(event.BlockNumber))) -} - -// Gets the current block and loads any events we missed between highestBlock and the current one -// If we get disconnected from the EC, we may need to backfill. -// Additionally, we do some slow work on startup which takes 8-9 blocks, so we do that work, -// subscribe for future events, and then backfill the events in between before processing future events. -// -// All of this is only necessary because SubscribeFilterLogs doesn't seem to send old events, no matter -// what FromBlock is set to. -func (e *CachingExecutionLayer) backfillEvents() error { - // Since highestBlock was the highest processed block, start one block after - start := big.NewInt(0).Add(e.cache.getHighestBlock(), big.NewInt(1)) - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - // Get current block - header, err := e.client.HeaderByNumber(ctx, nil) - if err != nil { - return err - } - stop := header.Number - - e.Logger.Info("Checking if backfill neeeded", - zap.Uint64("cache height", start.Uint64()), - zap.Uint64("current height", stop.Uint64()), - zap.Int("cmp", start.Cmp(stop))) - - // Make sure there is actually a gap before backfilling - if stop.Cmp(start) < 0 { - e.Logger.Info("No blocks to backfill events from") - return nil - } - - missedEvents, err := e.client.FilterLogs(ctx, ethereum.FilterQuery{ - // We only want events for 2 contracts - Addresses: []common.Address{*e.rocketMinipoolManager.Address, *e.rocketNodeManager.Address}, - FromBlock: start, - // The current block is actually the last block processed by the EC, so play any events from it as well - // The range is inclusive - ToBlock: stop, - Topics: [][]common.Hash{{ - e.nodeRegisteredTopic, - e.smoothingPoolStatusChangedTopic, - e.minipoolLaunchedTopic, - e.odaoJoinedTopic, - e.odaoLeftTopic, - e.odaoKickedTopic, - }}, - }) - - if err != nil { - return err - } - - for _, event := range missedEvents { - e.handleEvent(event) - e.m.Counter("backfill_events").Inc() - } - - // Force the highest block to update, as we may not have received any events in it, which would have updated it - e.cache.setHighestBlock(stop) - - delta := big.NewInt(0).Sub(stop, start) - - // If start == stop we actually fill that one block, so add one to delta - delta = delta.Add(delta, big.NewInt(1)) - e.m.Counter("backfill_blocks").Add(float64(delta.Uint64())) - - e.Logger.Info("Backfilled events", zap.Int("events", len(missedEvents)), - zap.Uint64("blocks", delta.Uint64()), - zap.Int64("start", start.Int64()), zap.Int64("stop", stop.Int64())) - return nil -} - -// Will likely attempt to reconnect, and will overwrite the pointers passed with the new subscription objects -func (e *CachingExecutionLayer) handleSubscriptionError(err error, logEventSub **ethereum.Subscription, headerSub **ethereum.Subscription) { - if e.ctx.Err() != nil { - // We're shutting down, so return quietly - return - } - - e.m.Counter("subscription_disconnected").Inc() - e.Logger.Warn("Error received from eth client subscription", zap.Error(err)) - // Attempt to reconnect `reconnectRetries` times with steadily increasing waits - for i := 0; i < reconnectRetries; i++ { - - e.Logger.Warn("Attempting to reconnect", zap.Int("attempt", i+1)) - e.m.Counter("reconnection_attempt").Inc() - s, err := e.client.SubscribeFilterLogs(context.Background(), e.query, e.events) - if err == nil { - e.Logger.Warn("Reconnected", zap.Int("attempt", i+1)) - - // Resubscribe to new headers - no retries - h, err := e.client.SubscribeNewHead(context.Background(), e.newHeaders) - if err != nil { - e.Logger.Warn("Couldn't resubscribe to block headers after reconnecting") - break - } - - e.setECShutdownCb(func() { - s.Unsubscribe() - h.Unsubscribe() - }) - - // Now that we've reconnected, we need to backfill - err = e.backfillEvents() - if err != nil { - // Failed to backfill - e.Logger.Panic("Couldn't backfill blocks after reconnecting to execution client") - } - - *logEventSub = &s - *headerSub = &h - return - } - - e.Logger.Warn("Error trying to reconnect to execution client", zap.Error(err)) - select { - case <-e.ctx.Done(): - // We're shutting down, so exit now - e.Logger.Info("Terminating while re-establishing the connection to the EL") - return - case <-time.After(time.Duration(i) * (5 * time.Second)): - // Loop again - } - } - - // Failed to reconnect after 10 tries - e.Logger.Panic("Couldn't re-establish eth client connection") + // A ticker to refresh the cache + ticker *time.Ticker } -// Registers to receive the events we care about -func (e *CachingExecutionLayer) ecEventsConnect(_ *bind.CallOpts) error { - var err error - - e.nodeRegisteredTopic = crypto.Keccak256Hash([]byte("NodeRegistered(address,uint256)")) - e.smoothingPoolStatusChangedTopic = crypto.Keccak256Hash([]byte("NodeSmoothingPoolStateChanged(address,bool)")) - e.minipoolLaunchedTopic = crypto.Keccak256Hash([]byte("MinipoolCreated(address,address,uint256)")) - e.odaoJoinedTopic = crypto.Keccak256Hash([]byte("ActionJoined(address,uint256,uint256)")) - e.odaoLeftTopic = crypto.Keccak256Hash([]byte("ActionLeave(address,uint256,uint256)")) - e.odaoKickedTopic = crypto.Keccak256Hash([]byte("ActionKick(address,uint256,uint256)")) - - // Subscribe to events from rocketNodeManager and rocketMinipoolManager - e.query = ethereum.FilterQuery{ - Addresses: []common.Address{*e.rocketMinipoolManager.Address, *e.rocketNodeManager.Address}, - Topics: [][]common.Hash{{ - e.nodeRegisteredTopic, - e.smoothingPoolStatusChangedTopic, - e.minipoolLaunchedTopic, - e.odaoJoinedTopic, - e.odaoLeftTopic, - e.odaoKickedTopic, - }}, - } - - e.events = make(chan types.Log, 32) - sub, err := e.client.SubscribeFilterLogs(context.Background(), e.query, e.events) - if err != nil { - return err - } - - e.newHeaders = make(chan *types.Header, 32) - newHeadSub, err := e.client.SubscribeNewHead(context.Background(), e.newHeaders) - if err != nil { - return err - } - - e.Logger.Info("Subscribed to EL events") - - // After subscribing, we need to grab the current block and replay events between highestBlock and the current one. - // While we were building the cache from cold, we may have missed some events. - err = e.backfillEvents() - if err != nil { - return err - } - - // Make sure we can unsubscribe on shutdown - e.setECShutdownCb(func() { - sub.Unsubscribe() - newHeadSub.Unsubscribe() - }) - - e.connected <- true - { - var noMoreEvents bool - var noMoreHeaders bool - - logSubscription := &sub - newHeadSubscription := &newHeadSub - for { - - select { - case err := <-(*logSubscription).Err(): - (*newHeadSubscription).Unsubscribe() - e.handleSubscriptionError(err, &logSubscription, &newHeadSubscription) - case err := <-(*newHeadSubscription).Err(): - (*logSubscription).Unsubscribe() - e.handleSubscriptionError(err, &logSubscription, &newHeadSubscription) - case event, ok := <-e.events: - noMoreEvents = !ok - if !noMoreEvents { - e.handleEvent(event) - } - case newHeader, ok := <-e.newHeaders: - noMoreHeaders = !ok - if !noMoreHeaders { - // Just advance highest block - e.m.Counter("block_header_received").Inc() - e.Logger.Debug("New block received", - zap.Int64("new height", newHeader.Number.Int64()), - zap.Int64("old height", e.cache.getHighestBlock().Int64())) - e.cache.setHighestBlock(newHeader.Number) - - // Continue here to check for new events - continue - } - } - - // If we didn't process any events in the select and the channels are closed, - // no new events will come, so break the loop - if noMoreEvents && noMoreHeaders { - e.Logger.Debug("Finished processing events", zap.Int64("height", e.cache.getHighestBlock().Int64())) - break - } - - } - } - - return nil -} +func (e *CachingExecutionLayer) newCache(loggerFunc func(fmt string, fields ...zap.Field)) error { + out := MapsCache{} -// Init creates and warms up the ExecutionLayer cache. -func (e *CachingExecutionLayer) Init() error { - var err error - - e.m = metrics.NewMetricsRegistry("execution_layer") - e.connected = make(chan bool, 1) - - if e.cache == nil { - e.cache = &MapsCache{} - } - - e.ctx, e.shutdown = context.WithCancel(context.Background()) - - if err := e.cache.init(); err != nil { + if err := out.init(); err != nil { return fmt.Errorf("unable to init cache: %w", err) } - e.client, err = ethclient.Dial(e.ECURL.String()) - if err != nil { - return err - } - rocketStorageAddr := common.HexToAddress(e.RocketStorageAddr) - e.rp, err = rocketpool.NewRocketPool(e.client, rocketStorageAddr) - if err != nil { - return err - } - - // Set up the vaults checker - if e.SWVaultsRegistryAddr != "" { - e.vaultsChecker = stakewise.NewVaultsChecker(e.client, common.HexToAddress(e.SWVaultsRegistryAddr)) - } - // First, get the current block - ctx, cancel := context.WithTimeout(e.ctx, 5*time.Second) + ctx, cancel := context.WithTimeout(e.Context, 5*time.Second) defer cancel() - header, err := e.client.HeaderByNumber(ctx, nil) - if err != nil { - return err - } - - // Create opts to query state at the latest block - opts := &bind.CallOpts{BlockNumber: header.Number} - - // Get the state contracts - stateContracts, ok := stateContractsMap[rocketStorageAddr] - if !ok { - return fmt.Errorf("no state contracts found for rocket storage address: %s", rocketStorageAddr.String()) - } - networkContracts, err := state.NewNetworkContracts(e.rp, stateContracts.MulticallAddress, stateContracts.StateManagerAddress, opts) - if err != nil { - return fmt.Errorf("unable to create network contracts: %w", err) - } - - // Load contracts - e.rocketNodeManager, err = e.rp.GetContract("rocketNodeManager", opts) + header, err := e.DataProvider.HeaderByNumber(ctx, nil) if err != nil { return err } - e.rocketMinipoolManager, err = e.rp.GetContract("rocketMinipoolManager", opts) - if err != nil { - return err - } - - e.smoothingPool, err = e.rp.GetContract("rocketSmoothingPool", opts) - if err != nil { - return err - } + // Create a new context for multicall + // It's pretty snappy, so timeout after just 2 minutes. + mcCtx, cancel := context.WithTimeout(e.Context, 2*time.Minute) + defer cancel() - e.rEth, err = e.rp.GetContract("rocketTokenRETH", opts) - if err != nil { - return err - } + // Create opts to query state at the latest block + opts := &bind.CallOpts{BlockNumber: header.Number, Context: mcCtx} - e.rocketDaoNodeTrustedActions, err = e.rp.GetContract("rocketDAONodeTrustedActions", opts) + // Refresh the addresses from rocketStorage in case there has been a protocol upgrade + err = e.DataProvider.RefreshAddresses(opts) if err != nil { - return err + loggerFunc("Error refreshing addresses", zap.Error(err)) } - e.Logger.Info("Warming up the cache") + loggerFunc("Warming up the cache") // Get all nodes at the given block - nodes, err := state.GetAllNativeNodeDetails(e.rp, networkContracts) + nodes, err := e.DataProvider.GetAllNodes(opts) if err != nil { - return err + return fmt.Errorf("error getting all nodes: %w", err) } - e.Logger.Info("Loaded nodes", zap.Int("count", len(nodes)), zap.Int64("block", opts.BlockNumber.Int64())) + loggerFunc("Loaded nodes", zap.Int("count", len(nodes)), zap.Int64("block", opts.BlockNumber.Int64())) // Get all minipools at the given block - minipools, err := state.GetAllNativeMinipoolDetails(e.rp, networkContracts) + minipools, err := e.DataProvider.GetAllMinipools(nodes, opts) if err != nil { - return err - } - e.Logger.Info("Loaded minipools", zap.Int("count", len(minipools)), zap.Int64("block", opts.BlockNumber.Int64())) - - // Create a node addr -> []*minipooldetails map - minipoolMap := make(map[common.Address][]*state.NativeMinipoolDetails) - - // Iterate over the minipools and add them to the map - for _, minipool := range minipools { - minipoolMap[minipool.NodeAddress] = append(minipoolMap[minipool.NodeAddress], &minipool) + return fmt.Errorf("error getting all minipools: %w", err) } + loggerFunc("Loaded minipools", zap.Int("count", len(minipools)), zap.Int64("block", opts.BlockNumber.Int64())) minipoolCount := 0 - for _, nativeNodeDetails := range nodes { + for addr, node := range nodes { // Allocate a pointer for this node - nodeInfo := &nodeInfo{} + nodeInfo := &dataprovider.NodeInfo{} // Determine their smoothing pool status - nodeInfo.inSmoothingPool = nativeNodeDetails.SmoothingPoolRegistrationState - nodeInfo.feeDistributor = nativeNodeDetails.FeeDistributorAddress + nodeInfo.InSmoothingPool = node.InSmoothingPool + nodeInfo.FeeDistributor = node.FeeDistributor // Store the smoothing pool state / fee distributor in the node index - err = e.cache.addNodeInfo(nativeNodeDetails.NodeAddress, nodeInfo) + err = out.addNodeInfo(addr, nodeInfo) if err != nil { return fmt.Errorf("unable to add node info: %w", err) } // Also grab their minipools - minipools, ok := minipoolMap[nativeNodeDetails.NodeAddress] + minipools, ok := minipools[addr] if !ok { continue } minipoolCount += len(minipools) for _, minipool := range minipools { - err = e.cache.addMinipoolNode(minipool.Pubkey, nativeNodeDetails.NodeAddress) + err = out.addMinipoolNode(minipool, addr) if err != nil { return fmt.Errorf("unable to add minipool node: %w", err) } @@ -633,75 +133,98 @@ func (e *CachingExecutionLayer) Init() error { } // Get all odao nodes at the given block - odaoNodes, err := state.GetAllOracleDaoMemberDetails(e.rp, networkContracts) + odaoNodes, err := e.DataProvider.GetAllOdaoNodes(opts) if err != nil { - return err + return fmt.Errorf("error getting all odao nodes: %w", err) } for _, member := range odaoNodes { - err = e.cache.addOdaoNode(member.Address) + err = out.addOdaoNode(member) if err != nil { return fmt.Errorf("unable to add odao node: %w", err) } } - e.Logger.Info("Loaded odao nodes", zap.Int("count", len(odaoNodes)), zap.Int64("block", opts.BlockNumber.Int64())) + loggerFunc("Loaded odao nodes", zap.Int("count", len(odaoNodes)), zap.Int64("block", opts.BlockNumber.Int64())) - // Set highestBlock to the cache's highestBlock, since it was just warmed up - e.cache.setHighestBlock(opts.BlockNumber) + smoothingPoolAddress := e.DataProvider.GetSmoothingPoolAddress() + rethAddress := e.DataProvider.GetREthAddress() - e.Logger.Info("Pre-loaded nodes and minipools", + loggerFunc("Loaded nodes and minipools snapshot", zap.Int("nodes", len(nodes)), zap.Int("minipools", minipoolCount), zap.Int("odao nodes", len(odaoNodes))) + e.cache.Store(&cacheRCU{Cache: &out, smoothingPoolAddress: smoothingPoolAddress, rethAddress: rethAddress}) + + e.m.Gauge("execution_cache_nodes").Set(float64(len(nodes))) + e.m.Gauge("execution_cache_minipools").Set(float64(minipoolCount)) + e.m.Gauge("execution_cache_odao_nodes").Set(float64(len(odaoNodes))) + + // Useful for data freshness on the dashboard + // as well as in alerts. + // + // Only set it if the cache is successfully populated + e.m.Gauge("execution_cache_timestamp").Set(float64(header.Time)) return nil } -func (e *CachingExecutionLayer) Start() error { - // First, get the current block - header, err := e.client.HeaderByNumber(e.ctx, nil) - if err != nil { - return err - } - - // Create opts to query state at the latest block - opts := &bind.CallOpts{BlockNumber: header.Number} +// Init creates and warms up the ExecutionLayer cache. +func (e *CachingExecutionLayer) Init() error { - return e.ecEventsConnect(opts) -} + e.m = metrics.NewMetricsRegistry("execution_layer") -// Stop shuts down this ExecutionLayer -func (e *CachingExecutionLayer) Stop() { - e.Logger.Info("Stopping ethclient") - e.shutdown() - if e.ethclientShutdownCb != nil { - e.ethclientShutdownCb() - } - close(e.events) - close(e.newHeaders) - e.Logger.Info("Stopping EL cache") - err := e.cache.deinit() - if err != nil { - e.Logger.Error("error while stopping the cache", zap.Error(err)) + if e.Context == nil { + e.Context = context.Background() + } + + if err := e.newCache(e.Logger.Info); err != nil { + return fmt.Errorf("error warming up the cache: %w", err) + } + + // Once the cache is warm, start a background process to refresh it + if e.RefreshInterval > 0 { + e.ticker = time.NewTicker(e.RefreshInterval) + go func() { + e.Logger.Info("Starting cache refresh", zap.Duration("interval", e.RefreshInterval)) + for { + select { + case <-e.Context.Done(): + e.ticker.Stop() + return + case <-e.ticker.C: + e.Logger.Info("Refreshing cache") + if err := e.newCache(e.Logger.Debug); err != nil { + e.Logger.Error("error refreshing cache", zap.Error(err)) + e.m.Counter("cache_refresh_error").Inc() + continue + } + e.m.Counter("cache_refresh_success").Inc() + } + } + }() } - close(e.connected) + + return nil } // ForEachNode calls the provided closure with the address of every rocket pool node the ExecutionLayer has observed func (e *CachingExecutionLayer) ForEachNode(closure ForEachNodeClosure) error { - return e.cache.forEachNode(closure) + c := e.cache.Load() + return c.forEachNode(closure) } // ForEachOdaoNode calls the provided closure with the address of every odao node the ExecutionLayer has observed func (e *CachingExecutionLayer) ForEachOdaoNode(closure ForEachNodeClosure) error { - return e.cache.forEachOdaoNode(closure) + c := e.cache.Load() + return c.forEachOdaoNode(closure) } // GetRPInfo returns the expected fee recipient and node address for a validator, or nil if the validator is not a minipool func (e *CachingExecutionLayer) GetRPInfo(pubkey rptypes.ValidatorPubkey) (*RPInfo, error) { + c := e.cache.Load() - nodeAddr, err := e.cache.getMinipoolNode(pubkey) + nodeAddr, err := c.getMinipoolNode(pubkey) if err != nil { _, ok := err.(*NotFoundError) if !ok { @@ -714,7 +237,7 @@ func (e *CachingExecutionLayer) GetRPInfo(pubkey rptypes.ValidatorPubkey) (*RPIn return nil, nil } - nodeInfo, err := e.cache.getNodeInfo(nodeAddr) + nodeInfo, err := c.getNodeInfo(nodeAddr) if err != nil { _, ok := err.(*NotFoundError) if !ok { @@ -733,93 +256,30 @@ func (e *CachingExecutionLayer) GetRPInfo(pubkey rptypes.ValidatorPubkey) (*RPIn return nil, fmt.Errorf("node %s not found in cache despite pubkey %s being present", nodeAddr.String(), pubkey.String()) } - if nodeInfo.inSmoothingPool { + if nodeInfo.InSmoothingPool { return &RPInfo{ - ExpectedFeeRecipient: e.smoothingPool.Address, + ExpectedFeeRecipient: &c.smoothingPoolAddress, NodeAddress: nodeAddr, }, nil } return &RPInfo{ - ExpectedFeeRecipient: &nodeInfo.feeDistributor, + ExpectedFeeRecipient: &nodeInfo.FeeDistributor, NodeAddress: nodeAddr, }, nil } // REthAddress is a convenience function to get the rEth contract address func (e *CachingExecutionLayer) REthAddress() *common.Address { - return e.rEth.Address + c := e.cache.Load() + return &c.rethAddress } -// EIP1271ABI is the ABI for the EIP-1271 isValidSignature function -var eip1271ABI *abi.ABI - -// getEIP1271ABI returns the EIP1271ABI -func getEIP1271ABI() *abi.ABI { - if eip1271ABI != nil { - return eip1271ABI - } - - const abiJSON = `[{"inputs":[{"name":"_hash","type":"bytes32"},{"name":"_signature","type":"bytes"}],"name":"isValidSignature","outputs":[{"type":"bytes4"}],"stateMutability":"view","type":"function"}]` - parsedABI, err := abi.JSON(strings.NewReader(abiJSON)) - if err != nil { - panic(fmt.Sprintf("failed to parse EIP1271 ABI: %v", err)) - } - eip1271ABI = &parsedABI - return eip1271ABI -} - -var ErrNoData = errors.New("no data were returned from the EVM, did you pass the correct smart contract wallet address?") -var ErrBadData = errors.New("the evm returned data with an unexpected length, did you pass the correct smart contract wallet address?") -var ErrInternal = errors.New("an internal error occurred, please contact the maintainers") - // ValidateEIP1271 validates an EIP-1271 signature func (e *CachingExecutionLayer) ValidateEIP1271(ctx context.Context, dataHash common.Hash, signature []byte, address common.Address) (bool, error) { - parsedABI := getEIP1271ABI() - - // Encode the function call - encodedData, err := parsedABI.Pack("isValidSignature", dataHash, signature) - if err != nil { - e.Logger.Warn("error packing isValidSignature call", zap.Error(err)) - return false, ErrInternal - } - - // Make the contract call - data, err := e.client.CallContract(ctx, ethereum.CallMsg{ - To: &address, - Data: encodedData, - }, nil) - if err != nil { - e.Logger.Warn("error querying the execution client to validate an EIP1271 signature", zap.Error(err)) - return false, ErrInternal - } - - if len(data) == 0 { - return false, ErrNoData - } - - if len(data) < 4 { - return false, ErrBadData - } - - // Trim the trailing bytes from the evm - data = data[:4] - - // Check the return value, it should be exactly 4 bytes long - if len(data) != 4 { - return false, ErrBadData - } - - // The expected return value for a valid signature is 0x1626ba7e - // bytes4(keccak256("isValidSignature(bytes32,bytes)") - // invalid signatures return 4 bytes that do not match the magic - expectedReturnValue := [4]byte{0x16, 0x26, 0xba, 0x7e} - return bytes.Equal(data, expectedReturnValue[:]), nil + return e.DataProvider.ValidateEIP1271(&bind.CallOpts{Context: ctx}, dataHash, signature, address) } func (e *CachingExecutionLayer) StakewiseFeeRecipient(ctx context.Context, address common.Address) (*common.Address, error) { - if e.vaultsChecker != nil { - return e.vaultsChecker.IsVault(ctx, address) - } - return nil, nil + return e.DataProvider.StakewiseFeeRecipient(&bind.CallOpts{Context: ctx}, address) } diff --git a/executionlayer/execution-layer_test.go b/executionlayer/execution-layer_test.go index 3477a08..f05a618 100644 --- a/executionlayer/execution-layer_test.go +++ b/executionlayer/execution-layer_test.go @@ -5,116 +5,37 @@ import ( "context" _ "embed" "encoding/hex" - "encoding/json" "errors" "fmt" "math/big" - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "strings" "testing" "time" + "github.com/Rocket-Rescue-Node/rescue-proxy/executionlayer/dataprovider" "github.com/Rocket-Rescue-Node/rescue-proxy/metrics" - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/gorilla/websocket" rptypes "github.com/rocket-pool/smartnode/bindings/types" "go.uber.org/zap/zaptest" - "golang.org/x/exp/slices" ) -var upgrader = websocket.Upgrader{} - -const rocketStorage = "0x1d8f8f00cfa6758d7bE78336684788Fb0ee0Fa46" -const rocketNodeManager = "0x00000000000000000000000089f478e6cc24f052103628f36598d4c14da3d287" -const rocketNodeDistributorFactory = "0x000000000000000000000000e228017f77b3e0785e794e4c0a8a6b935bb4037c" -const rocketMinipoolManager = "0x0000000000000000000000006d010c43d4e96d74c422f2e27370af48711b49bf" -const rocketDAONodeTrusted = "0x000000000000000000000000b8e783882b11Ff4f6Cef3C501EA0f4b960152cc9" -const rocketDAONodeTrustedActions = "0x000000000000000000000000029d946f28f93399a5b0d09c879fc8c94e596aeb" const rocketSmoothingPool = "0x000000000000000000000000d4e96ef8eee8678dbff4d535e033ed1a4f7605b7" const rocketTokenRETH = "0x000000000000000000000000ae78736cd615f374d3085123a210448e74fc6393" -const backfillNode = "0x000000000000000000000000515f7de509932bdc5ddc4c61e4324b18822c21da" - const eip1271SmartContractValidSignerAddress = "0x1234567890123456789012345678901234567890" const eip1271SmartContractInvalidSignerAddress = "0x1234567890123456789012345678901234567891" const eip1271ValidSignature = "0x0000000000000000000000000000000000000000000000000000000000000456" const eip1271InvalidSignature = "0x0000000000000000000000000000000000000000000000000000000000000789" -//go:embed test-data/block-by-number.txt -var blockByNumberFmt string - -//go:embed test-data/call-result-fmt.txt -var callResultFmt string - -//go:embed test-data/log-filter-fmt.txt -var logFilterFmt string - -//go:embed test-data/rocket-node-manager-abi.txt -var rocketNodeManagerAbi string - -//go:embed test-data/rocket-minipool-manager-abi.txt -var rocketMinipoolManagerAbi string - -//go:embed test-data/rocket-smoothing-pool-abi.txt -var rocketSmoothingPoolAbi string - -//go:embed test-data/rocket-token-reth-abi.txt -var rocketTokenRETHAbi string - -//go:embed test-data/rocket-dao-node-trusted-actions-abi.txt -var rocketDAONodeTrustedActionsAbi string - -//go:embed test-data/rocket-node-distributor-factory-abi.txt -var rocketNodeDistributorFactoryAbi string - -//go:embed test-data/rocket-dao-node-trusted-abi.txt -var rocketDAONodeTrustedAbi string - -type mockEC interface { - Serve(int, []byte) (int, []byte) -} - type elTest struct { t *testing.T - m mockEC + m *mockEC ec *CachingExecutionLayer } -func (e *elTest) ServeHTTP(w http.ResponseWriter, r *http.Request) { - c, err := upgrader.Upgrade(w, r, nil) - if err != nil { - e.t.Fatal(err) - } - defer func() { - _ = c.Close() - }() - for { - mt, data, err := c.ReadMessage() - if err != nil { - e.t.Log("mockEC recv err:", err) - return - } - - e.t.Logf("mockEC recv: %d - %s\n", mt, string(data)) - mt, data = e.m.Serve(mt, data) - e.t.Logf("mockEC resp: %d - %s\n", mt, string(data)) - - err = c.WriteMessage(mt, data) - if err != nil { - e.t.Log("mockEC resp err:", err) - return - } - } -} - -func setup(t *testing.T, m mockEC) *elTest { - _, err := metrics.Init("cc_test_" + t.Name()) +func setup(t *testing.T, m *mockEC) *elTest { + _, err := metrics.Init("ec_test_" + t.Name()) if err != nil { t.Fatal(err) } @@ -125,35 +46,17 @@ func setup(t *testing.T, m mockEC) *elTest { m: m, } - s := httptest.NewServer(out) - url, err := url.Parse(s.URL) - // replace scheme - url.Scheme = "ws" if err != nil { t.Fatal(err) } out.ec = &CachingExecutionLayer{ - Logger: zaptest.NewLogger(t), - ECURL: url, - RocketStorageAddr: rocketStorage, + Logger: zaptest.NewLogger(t), + Context: t.Context(), } - t.Cleanup(s.Close) + out.ec.DataProvider = out.m return out } -func intToHex(i int) string { - return fmt.Sprintf("0x%064x", i) -} - -func pubkeyFromMinipool(addr common.Address) string { - // Simply left-pad with a char out to the desired length - return fmt.Sprintf("f0f0%092s", addr.String()[2:]) -} - -func addrToMinipool(idx uint64, addr common.Address) string { - return fmt.Sprintf("0x%044x%s", idx+1, addr.String()[2+20:]) -} - type mockNode struct { addr common.Address inSP bool @@ -161,347 +64,92 @@ type mockNode struct { minipoolMap map[rptypes.ValidatorPubkey]interface{} } -type happyEC struct { +type mockEC struct { t *testing.T nodes []*mockNode daoNodes []*mockNode } -// Lifted from geth source https://github.com/ethereum/go-ethereum/blob/master/rpc/json.go#L51 -type jsonError struct { - Code int `json:"code"` - Message string `json:"message"` - Data interface{} `json:"data,omitempty"` -} +var _ dataprovider.DataProvider = &mockEC{} -type jsonrpcMessage struct { - Version string `json:"jsonrpc,omitempty"` - ID json.RawMessage `json:"id,omitempty"` - Method string `json:"method,omitempty"` - Params json.RawMessage `json:"params,omitempty"` - Error *jsonError `json:"error,omitempty"` - Result json.RawMessage `json:"result,omitempty"` +func (m *mockEC) GetAllNodes(opts *bind.CallOpts) (map[common.Address]*dataprovider.NodeInfo, error) { + out := make(map[common.Address]*dataprovider.NodeInfo) + for _, node := range m.nodes { + out[node.addr] = &dataprovider.NodeInfo{ + InSmoothingPool: node.inSP, + FeeDistributor: node.addr, + } + } + return out, nil } -type call struct { - To common.Address `json:"to"` - From common.Address `json:"from"` - Input string `json:"input"` +func pubkeyFromMinipool(addr common.Address) string { + // Simply left-pad with a char out to the desired length + return fmt.Sprintf("f0f0%092s", addr.String()[2:]) } -func (e *happyEC) Serve(mt int, data []byte) (int, []byte) { - m := jsonrpcMessage{} - err := json.Unmarshal(data, &m) - if err != nil { - e.t.Fatal(err) - } - - var resp string - switch m.Method { - case "eth_getBlockByNumber": - resp = fmt.Sprintf(blockByNumberFmt, m.ID, "0x11af2c8") - case "eth_subscribe", "eth_unsubscribe": - resp = fmt.Sprintf(callResultFmt, m.ID, "0x") - case "eth_getLogs": - resp = fmt.Sprintf(logFilterFmt, m.ID, backfillNode) - case "eth_call": - var paramsArray []json.RawMessage - err := json.Unmarshal(m.Params, ¶msArray) - if err != nil { - e.t.Fatal(err) - } - - if len(paramsArray) == 0 { - e.t.Fatal("eth call with 0-len params") - } - - var callMsg call - err = json.Unmarshal(paramsArray[0], &callMsg) - if err != nil { - e.t.Fatal(err) - } - - if len(callMsg.Input) == 0 { - e.t.Fatal("eth call with 0-len data") - } - - switch callMsg.To.String() { - case rocketStorage: - selector := callMsg.Input[:10] - input := callMsg.Input[10:] - switch selector { - // Get Address - case "0x21f8a721": - switch input { - case "af00be55c9fb8f543c04e0aa0d70351b880c1bfafffd15b60065a4a50c85ec94": - e.t.Log("Returning RocketNodeManager address") - resp = fmt.Sprintf(callResultFmt, m.ID, rocketNodeManager) - case "e9dfec9339b94a131861a58f1bb4ac4c1ce55c7ffe8550e0b6ebcfde87bb012f": - e.t.Log("Returning RocketMinipoolManager address") - resp = fmt.Sprintf(callResultFmt, m.ID, rocketMinipoolManager) - case "822231720aef9b264db1d9ca053137498f759c28b243f45c44db1d39d6bce46e": - e.t.Log("Returning RocketSmoothingPool address") - resp = fmt.Sprintf(callResultFmt, m.ID, rocketSmoothingPool) - case "e3744443225bff7cc22028be036b80de58057d65a3fdca0a3df329f525e31ccc": - e.t.Log("Returning RocketTokenRETH address") - resp = fmt.Sprintf(callResultFmt, m.ID, rocketTokenRETH) - case "0db039c744237e4ce5ef78a0f054ce1d90d4c567f771ca22f1b89eed7a7b901c": - e.t.Log("Returning RocketDAONodeTrustedActions address") - resp = fmt.Sprintf(callResultFmt, m.ID, rocketDAONodeTrustedActions) - case "ea051094896ef3b09ab1b794ad5ea695a5ff3906f74a9328e2c16db69d0f3123": - e.t.Log("Returning RocketNodeDistributorFactory address") - resp = fmt.Sprintf(callResultFmt, m.ID, rocketNodeDistributorFactory) - case "9a354e1bb2e38ca826db7a8d061cfb0ed7dbd83d241a2cbe4fd5218f9bb4333f": - e.t.Log("Returning RocketDAONodeTrusted address") - resp = fmt.Sprintf(callResultFmt, m.ID, rocketDAONodeTrusted) - - default: - e.t.Log("Unhandled GetAddress", input) - } - // Get String - case "0x986e791a": - switch input { - case "b665755e7f514adae7d03140292e555a67796a7f6d6193f2b69e1988efc42a7c": - e.t.Log("Returning RocketNodeManager abi") - resp = fmt.Sprintf(callResultFmt, m.ID, rocketNodeManagerAbi) - case "8dbcb47ea1b95b945ad8a07ea9995ed9f9f05c9d32b7abf92ab673ab5c0e88f4": - e.t.Log("Returning RocketMinipoolManager abi") - resp = fmt.Sprintf(callResultFmt, m.ID, rocketMinipoolManagerAbi) - case "68df011a367b483345047bcd57214093f1a4920f99771f783e52523d2e8c9359": - e.t.Log("Returning RocketSmoothingPool abi") - resp = fmt.Sprintf(callResultFmt, m.ID, rocketSmoothingPoolAbi) - case "66fa1687b0fe549b3c17422e7889850e38d08ecd92c902a63818ba19c20be1f8": - e.t.Log("Returning RocketTokenRETH abi") - resp = fmt.Sprintf(callResultFmt, m.ID, rocketTokenRETHAbi) - case "b76727ffbb601cda11c24a90a1e005855a7b67865beb7b9fe18c606e85b5bb9f": - e.t.Log("Returning RocketDAONodeTrustedActions abi") - resp = fmt.Sprintf(callResultFmt, m.ID, rocketDAONodeTrustedActionsAbi) - case "fa18b901ccc1aac57eee8923beef96533e7f0a4140718a753ba8efe9b253649b": - e.t.Log("Returning RocketNodeDistributorFactory abi") - resp = fmt.Sprintf(callResultFmt, m.ID, rocketNodeDistributorFactoryAbi) - case "72496a0f6ba2c8ba96a29f6abeb2147ad89ec172d1a8ce84fd85828fd8475ed4": - e.t.Log("Returning RocketDAONodeTrusted abi") - resp = fmt.Sprintf(callResultFmt, m.ID, rocketDAONodeTrustedAbi) - default: - e.t.Log("Unhandled GetString", input) - } - default: - e.t.Log("Unhandled rocketStorage selector", selector) - } - - case common.HexToAddress(rocketNodeManager).String(): - selector := callMsg.Input[:10] - input := callMsg.Input[10:] - switch selector { - // GetNodeCount - case "0x39bf397e": - count := len(e.nodes) - resp = fmt.Sprintf(callResultFmt, m.ID, intToHex(count)) - // GetNodeAt - case "0xba75d806": - // Make sure input is in range - idx, err := hex.DecodeString(input) - if err != nil { - e.t.Fatal(err) - } - i := big.NewInt(0).SetBytes(idx).Uint64() - if i >= uint64(len(e.nodes)) { - e.t.Fatal("Out-of-bounds node requested") - } - n := e.nodes[i] - addrStr := n.addr.String()[2:] - resp = fmt.Sprintf(callResultFmt, m.ID, "0x000000000000000000000000"+addrStr) - // GetSmoothingPoolRegistrationState - case "0xa4cef9dd": - // The input is an address - addr := common.HexToAddress(input) - for _, n := range e.nodes { - if n.addr != addr { - continue - } - if n.inSP { - resp = fmt.Sprintf(callResultFmt, m.ID, "0x0000000000000000000000000000000000000000000000000000000000000001") - } else { - resp = fmt.Sprintf(callResultFmt, m.ID, "0x0000000000000000000000000000000000000000000000000000000000000000") - } - break - } - - default: - e.t.Log("Unhandled rocketNodeManager selector", selector) - } - - case common.HexToAddress(rocketNodeDistributorFactory).String(): - selector := callMsg.Input[:10] - input := callMsg.Input[10:] - switch selector { - // GetProxyAddress(address) - case "0xfa2a5b01": - // Use the reverse of the the node address as the fr - // The input is an address - addr := common.HexToAddress(input).Bytes() - slices.Reverse(addr) - // Return fee recipient address - resp = fmt.Sprintf(callResultFmt, m.ID, "0x000000000000000000000000"+common.BytesToAddress(addr).String()[2:]) - default: - e.t.Log("Unhandled rocketNodeDistributorFactory selector", selector) - } - case common.HexToAddress(rocketMinipoolManager).String(): - selector := callMsg.Input[:10] - input := callMsg.Input[10:] - switch selector { - // GetNodeMinipoolCount(address) - case "0x1ce9ec33": - // The input is an address - addr := common.HexToAddress(input) - for _, n := range e.nodes { - if n.addr != addr { - continue - } - resp = fmt.Sprintf(callResultFmt, m.ID, intToHex(n.minipools)) - break - } - // GetNodeMinipoolAt(address,idx) - case "0x8b300029": - // The input is an address and idx - addr := common.HexToAddress(input[:64]) - idx, err := strconv.ParseUint(input[64+48:], 16, 32) - if err != nil { - e.t.Fatal(err) - } - for _, n := range e.nodes { - if n.addr != addr { - continue - } - if idx >= uint64(n.minipools) { - e.t.Fatal("idx exceeds node count", idx, n.minipools) - } - // Prepend the minipool number to the node address - resp = fmt.Sprintf(callResultFmt, m.ID, addrToMinipool(idx, addr)) - break - } - // GetMinipoolPubkey(address) - case "0x3eb535e9": - // The input is an address - addr := common.HexToAddress(input) - pubkey := pubkeyFromMinipool(addr) - h, err := hex.DecodeString(pubkey) - if err != nil { - e.t.Fatal(err) - } - typ, err := abi.NewType("bytes", "bytes", nil) - if err != nil { - e.t.Fatal(err) - } - - args := abi.Arguments{abi.Argument{Type: typ, Name: "blah"}} - - solBytes, err := args.Pack(h) - if err != nil { - e.t.Fatal(err) - } - - // Find the node and add the pubkey to its map, for the tests to reference - for _, n := range e.nodes { - if !bytes.HasSuffix(addr.Bytes(), n.addr.Bytes()[10:]) { - continue - } - if n.minipoolMap == nil { - n.minipoolMap = make(map[rptypes.ValidatorPubkey]interface{}) - } - pk := rptypes.BytesToValidatorPubkey(h) - n.minipoolMap[pk] = struct{}{} - } - - resp = fmt.Sprintf(callResultFmt, m.ID, fmt.Sprintf("0x%s", hex.EncodeToString(solBytes))) - - default: - e.t.Log("Unhandled rocketMinipoolManager selector", selector) - } - case common.HexToAddress(rocketDAONodeTrusted).String(): - selector := callMsg.Input[:10] - input := callMsg.Input[10:] - switch selector { - // GetMemberCount() - case "0x997072f7": - count := len(e.daoNodes) - resp = fmt.Sprintf(callResultFmt, m.ID, intToHex(count)) - // GetMemberAt(uint256) - case "0xe992c817": - // Input is just a number - i, err := strconv.ParseUint(input, 16, 64) - if err != nil { - e.t.Fatal(err) - } - - if i >= uint64(len(e.daoNodes)) { - e.t.Fatal("index too large for dao nodes") - } - - n := e.daoNodes[int(i)] - resp = fmt.Sprintf(callResultFmt, m.ID, "0x000000000000000000000000"+n.addr.String()[2:]) - default: - e.t.Log("Unhandled rocketDAONodeTrusted selector", selector) +func (m *mockEC) GetAllMinipools(nodes map[common.Address]*dataprovider.NodeInfo, opts *bind.CallOpts) (map[common.Address][]rptypes.ValidatorPubkey, error) { + out := make(map[common.Address][]rptypes.ValidatorPubkey) + for _, node := range m.nodes { + node.minipoolMap = make(map[rptypes.ValidatorPubkey]interface{}) + for range node.minipools { + pubkey := pubkeyFromMinipool(node.addr) + h, err := hex.DecodeString(pubkey) + if err != nil { + return nil, errors.New("unexpected invalid pubkey") } + out[node.addr] = append(out[node.addr], rptypes.BytesToValidatorPubkey(h)) + node.minipoolMap[rptypes.BytesToValidatorPubkey(h)] = struct{}{} + } + } + return out, nil +} - case eip1271SmartContractValidSignerAddress, eip1271SmartContractInvalidSignerAddress: - // Define the ABI for the isValidSignature function - const abiJSON = `[{"inputs":[{"name":"_hash","type":"bytes32"},{"name":"_signature","type":"bytes"}],"name":"isValidSignature","outputs":[{"type":"bytes4"}],"stateMutability":"view","type":"function"}]` +func (m *mockEC) GetAllOdaoNodes(opts *bind.CallOpts) ([]common.Address, error) { + out := make([]common.Address, len(m.daoNodes)) + for i, node := range m.daoNodes { + out[i] = node.addr + } + return out, nil +} - parsedABI, err := abi.JSON(strings.NewReader(abiJSON)) - if err != nil { - e.t.Fatalf("Failed to parse ABI: %v", err) - } +func (m *mockEC) GetREthAddress() common.Address { + return common.HexToAddress(rocketTokenRETH) +} - // Decode the function call - methodID, err := hex.DecodeString(callMsg.Input[2:10]) - if err != nil { - e.t.Fatalf("Failed to decode method ID: %v", err) - } - method, err := parsedABI.MethodById(methodID) - if err != nil { - e.t.Fatalf("Failed to get method from ABI: %v", err) - } +func (m *mockEC) GetSmoothingPoolAddress() common.Address { + return common.HexToAddress(rocketSmoothingPool) +} - // Decode the rest of the data - callDataArguments, err := hex.DecodeString(callMsg.Input[10:]) - if err != nil { - e.t.Fatalf("Failed to decode call data: %v", err) - } - args, err := method.Inputs.Unpack(callDataArguments) - if err != nil { - e.t.Fatalf("Failed to unpack arguments: %v", err) - } +func (m *mockEC) StakewiseFeeRecipient(opts *bind.CallOpts, address common.Address) (*common.Address, error) { + return nil, nil +} - // Extract the parameters - // Only really care about the signature, because in our tests we're assuming that the data hash is correct - signature := args[1].([]byte) +func (m *mockEC) ValidateEIP1271(opts *bind.CallOpts, dataHash common.Hash, signature []byte, address common.Address) (bool, error) { + if bytes.Equal(address[:], common.FromHex(eip1271SmartContractInvalidSignerAddress)) { + return false, dataprovider.ErrNoData + } + if bytes.Equal(signature, common.FromHex(eip1271ValidSignature)) { + return true, nil + } + return false, nil +} - if callMsg.To == common.HexToAddress(eip1271SmartContractValidSignerAddress) { - if bytes.Equal(signature, common.FromHex(eip1271ValidSignature)) { - // Return valid signature result - resp = fmt.Sprintf(callResultFmt, m.ID, "0x1626ba7e") - } else { - // Return invalid signature result - resp = fmt.Sprintf(callResultFmt, m.ID, "0xffffffff") - } - } else if callMsg.To == common.HexToAddress(eip1271SmartContractInvalidSignerAddress) { - resp = fmt.Sprintf(callResultFmt, m.ID, "0xdead") - } else { - e.t.Log("Unhandled isValidSignature call to", callMsg.To.String()) - } - default: - e.t.Log("Unhandled contract call", callMsg.To) - } - default: - e.t.Log("Unhandled eth rpc", m.Method) +func (m *mockEC) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + if number == nil { + return &types.Header{Number: big.NewInt(1111)}, nil } + return &types.Header{ + Number: number, + }, nil +} - return mt, []byte(resp) +func (m *mockEC) RefreshAddresses(opts *bind.CallOpts) error { + return nil } func TestELStartStop(t *testing.T) { - et := setup(t, &happyEC{t, + et := setup(t, &mockEC{t, []*mockNode{ { addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), @@ -526,27 +174,10 @@ func TestELStartStop(t *testing.T) { if err := et.ec.Init(); err != nil { t.Fatal(err) } - - errs := make(chan error) - go func() { - if err := et.ec.Start(); err != nil { - errs <- err - } - close(errs) - }() - - // Wait for connection - <-et.ec.connected - - et.ec.Stop() - err := <-errs - if err != nil { - t.Fatal(err) - } } func TestELGetRPInfoMissing(t *testing.T) { - et := setup(t, &happyEC{t, + et := setup(t, &mockEC{t, []*mockNode{ { addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), @@ -572,17 +203,6 @@ func TestELGetRPInfoMissing(t *testing.T) { t.Fatal(err) } - errs := make(chan error) - go func() { - if err := et.ec.Start(); err != nil { - errs <- err - } - close(errs) - }() - - // Wait for connection - <-et.ec.connected - rpinfo, err := et.ec.GetRPInfo(rptypes.BytesToValidatorPubkey([]byte{0x01})) if err != nil { t.Fatal("unexpected error", err) @@ -590,16 +210,50 @@ func TestELGetRPInfoMissing(t *testing.T) { if rpinfo != nil { t.Fatal("unexpected rp info", rpinfo) } +} - et.ec.Stop() - err = <-errs - if err != nil { +func TestELGetRPInfo(t *testing.T) { + mockEC := &mockEC{t, + []*mockNode{ + { + addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), + inSP: true, + minipools: 1, + }, + { + addr: common.HexToAddress("0x0000000000000000000002234567899876543210"), + inSP: false, + minipools: 3, + }, + }, + []*mockNode{ + { + addr: common.HexToAddress("0x0000000000222222222222222222222222222222"), + inSP: false, + minipools: 0, + }, + }, + } + et := setup(t, mockEC) + if err := et.ec.Init(); err != nil { t.Fatal(err) } + + for _, node := range mockEC.nodes { + for pubkey := range node.minipoolMap { + rpinfo, err := et.ec.GetRPInfo(pubkey) + if err != nil { + t.Fatal("unexpected error", err) + } + if rpinfo == nil { + t.Fatal("expected rp info", rpinfo) + } + } + } } func TestELGetRETHAddress(t *testing.T) { - et := setup(t, &happyEC{t, + et := setup(t, &mockEC{t, []*mockNode{ { addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), @@ -625,17 +279,6 @@ func TestELGetRETHAddress(t *testing.T) { t.Fatal(err) } - errs := make(chan error) - go func() { - if err := et.ec.Start(); err != nil { - errs <- err - } - close(errs) - }() - - // Wait for connection - <-et.ec.connected - reth := et.ec.REthAddress() if reth == nil { t.Fatal("expected address") @@ -645,15 +288,10 @@ func TestELGetRETHAddress(t *testing.T) { t.Fatal("Expected reth token address to match") } - et.ec.Stop() - err := <-errs - if err != nil { - t.Fatal(err) - } } func TestELForEaches(t *testing.T) { - hec := &happyEC{t, + hec := &mockEC{t, []*mockNode{ { addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), @@ -680,17 +318,6 @@ func TestELForEaches(t *testing.T) { t.Fatal(err) } - errs := make(chan error) - go func() { - if err := et.ec.Start(); err != nil { - errs <- err - } - close(errs) - }() - - // Wait for connection - <-et.ec.connected - // Check that foreach node now iterates 3x nodeCount := 0 err := et.ec.ForEachNode(func(a common.Address) bool { @@ -720,7 +347,7 @@ func TestELForEaches(t *testing.T) { } // Remove an odao node - err = et.ec.cache.removeOdaoNode(common.HexToAddress("0x01")) + err = et.ec.cache.Load().removeOdaoNode(common.HexToAddress("0x01")) if err != nil { t.Fatal(err) } @@ -737,12 +364,6 @@ func TestELForEaches(t *testing.T) { t.Fatalf("Expected 2 nodes in odao foreach iterator, got: %d", nodeCount) } - // Couple bonuse checks for coverage. highest block doesn't increase when it would be backwards - et.ec.cache.setHighestBlock(big.NewInt(0)) - if et.ec.cache.getHighestBlock().Uint64() == 0 { - t.Fatal("block should not have decreased") - } - // Get rpinfo as with the sql cache found := 0 for _, n := range hec.nodes { @@ -764,528 +385,16 @@ func TestELForEaches(t *testing.T) { } // Direct getnodeinfo for missing key returns an error - _, err = et.ec.cache.getNodeInfo(common.HexToAddress("0xff")) + _, err = et.ec.cache.Load().getNodeInfo(common.HexToAddress("0xff")) if err == nil { t.Fatal("expected error") } else if !errors.Is(err, &NotFoundError{}) { t.Fatal("unexpected error", err) } - - et.ec.Stop() - err = <-errs - if err != nil { - t.Fatal(err) - } -} - -func TestELODAOJoinLeave(t *testing.T) { - et := setup(t, &happyEC{t, - []*mockNode{ - { - addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), - inSP: true, - minipools: 1, - }, - { - addr: common.HexToAddress("0x0000000000000000000002234567899876543210"), - inSP: false, - minipools: 3, - }, - }, - []*mockNode{ - { - addr: common.HexToAddress("0x0000000000222222222222222222222222222222"), - inSP: false, - minipools: 0, - }, - }, - }) - - if err := et.ec.Init(); err != nil { - t.Fatal(err) - } - - errs := make(chan error) - go func() { - if err := et.ec.Start(); err != nil { - errs <- err - } - close(errs) - }() - - // Wait for connection - <-et.ec.connected - - // New odao node - a := common.HexToAddress("0x0f010f") - - // Simulate odao joined event - et.ec.handleEvent(types.Log{ - Address: common.HexToAddress(rocketDAONodeTrustedActions), - Topics: []common.Hash{ - common.BytesToHash( - et.ec.odaoJoinedTopic.Bytes(), - ), - common.BytesToHash( - a.Bytes(), - ), - }, - }) - - // Check membership - found := false - err := et.ec.ForEachOdaoNode(func(n common.Address) bool { - if bytes.Equal(n.Bytes(), a.Bytes()) { - found = true - return false - } - return true - }) - if err != nil { - t.Fatal(err) - } - - if !found { - t.Fatalf("didn't find %s in active odao set", a.String()) - } - - // Simulate odao left event - et.ec.handleEvent(types.Log{ - Address: common.HexToAddress(rocketDAONodeTrustedActions), - Topics: []common.Hash{ - common.BytesToHash( - et.ec.odaoLeftTopic.Bytes(), - ), - common.BytesToHash( - a.Bytes(), - ), - }, - }) - // Check membership - found = false - err = et.ec.ForEachOdaoNode(func(n common.Address) bool { - if bytes.Equal(n.Bytes(), a.Bytes()) { - found = true - return false - } - return true - }) - if err != nil { - t.Fatal(err) - } - - if found { - t.Fatalf("founnd %s in active odao set", a.String()) - } - - // Finally, make sure we don't crash on unknown topics - et.ec.handleEvent(types.Log{ - Address: common.HexToAddress(rocketDAONodeTrustedActions), - Topics: []common.Hash{ - common.HexToHash("0x10101010"), - common.BytesToHash( - a.Bytes(), - ), - }, - }) - - et.ec.Stop() - err = <-errs - if err != nil { - t.Fatal(err) - } -} - -func TestELNodeEvents(t *testing.T) { - et := setup(t, &happyEC{t, - []*mockNode{ - { - addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), - inSP: true, - minipools: 1, - }, - { - addr: common.HexToAddress("0x0000000000000000000002234567899876543210"), - inSP: false, - minipools: 3, - }, - }, - []*mockNode{ - { - addr: common.HexToAddress("0x0000000000222222222222222222222222222222"), - inSP: false, - minipools: 0, - }, - }, - }) - - if err := et.ec.Init(); err != nil { - t.Fatal(err) - } - - errs := make(chan error) - go func() { - if err := et.ec.Start(); err != nil { - errs <- err - } - close(errs) - }() - - // Wait for connection - <-et.ec.connected - - // New node - a := common.HexToAddress("0x0f010f") - - // Simulate node register event - et.ec.handleEvent(types.Log{ - Address: common.HexToAddress(rocketNodeManager), - Topics: []common.Hash{ - common.BytesToHash( - et.ec.nodeRegisteredTopic.Bytes(), - ), - common.BytesToHash( - a.Bytes(), - ), - }, - }) - - // Check membership - found := false - err := et.ec.ForEachNode(func(n common.Address) bool { - if bytes.Equal(n.Bytes(), a.Bytes()) { - found = true - return false - } - return true - }) - if err != nil { - t.Fatal(err) - } - - if !found { - t.Fatalf("didn't find %s in active node set", a.String()) - } - - // Add a minipool - minipoolAddr := common.HexToAddress("0x1f101f") - et.ec.handleEvent(types.Log{ - Address: common.HexToAddress(rocketMinipoolManager), - Topics: []common.Hash{ - common.BytesToHash( - et.ec.minipoolLaunchedTopic.Bytes(), - ), - common.BytesToHash( - minipoolAddr.Bytes(), - ), - common.BytesToHash( - a.Bytes(), - ), - }, - }) - pubkey := pubkeyFromMinipool(minipoolAddr) - h, err := hex.DecodeString(pubkey) - if err != nil { - t.Fatal(err) - } - - rpinfo, err := et.ec.GetRPInfo(rptypes.BytesToValidatorPubkey(h)) - if err != nil { - t.Fatal(err) - } - - if rpinfo.ExpectedFeeRecipient.String() == common.HexToAddress(rocketSmoothingPool).String() { - t.Fatal("Expected new node to not be in SP") - } - - if rpinfo.NodeAddress.String() != a.String() { - t.Fatalf("rpinfo has unexpected node addr %s, expected %s", rpinfo.NodeAddress.String(), a.String()) - } - - // Opt into the SP - et.ec.handleEvent(types.Log{ - Address: common.HexToAddress(rocketNodeManager), - Topics: []common.Hash{ - common.BytesToHash( - et.ec.smoothingPoolStatusChangedTopic.Bytes(), - ), - common.BytesToHash( - a.Bytes(), - ), - }, - Data: big.NewInt(1).Bytes(), - }) - - rpinfo, err = et.ec.GetRPInfo(rptypes.BytesToValidatorPubkey(h)) - if err != nil { - t.Fatal(err) - } - - if rpinfo.ExpectedFeeRecipient.String() != common.HexToAddress(rocketSmoothingPool).String() { - t.Fatal("Expected new node to be in SP") - } - - if rpinfo.NodeAddress.String() != a.String() { - t.Fatalf("rpinfo has unexpected node addr %s, expected %s", rpinfo.NodeAddress.String(), a.String()) - } - - // Finally, make sure we don't crash on unknown topics/contracts - et.ec.handleEvent(types.Log{ - Address: common.HexToAddress(rocketNodeManager), - Topics: []common.Hash{ - common.HexToHash("0x10101010"), - common.BytesToHash( - a.Bytes(), - ), - }, - }) - et.ec.handleEvent(types.Log{ - Address: common.HexToAddress(rocketMinipoolManager), - Topics: []common.Hash{ - common.HexToHash("0x10101010"), - common.BytesToHash( - a.Bytes(), - ), - }, - }) - et.ec.handleEvent(types.Log{ - Address: common.HexToAddress(rocketDAONodeTrusted), - Topics: []common.Hash{ - common.HexToHash("0x10101010"), - common.BytesToHash( - a.Bytes(), - ), - }, - }) - - et.ec.Stop() - err = <-errs - if err != nil { - t.Fatal(err) - } -} - -func TestELSPChangeUnknownNode(t *testing.T) { - et := setup(t, &happyEC{t, - []*mockNode{ - { - addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), - inSP: true, - minipools: 1, - }, - { - addr: common.HexToAddress("0x0000000000000000000002234567899876543210"), - inSP: false, - minipools: 3, - }, - }, - []*mockNode{ - { - addr: common.HexToAddress("0x0000000000222222222222222222222222222222"), - inSP: false, - minipools: 0, - }, - }, - }) - - if err := et.ec.Init(); err != nil { - t.Fatal(err) - } - - errs := make(chan error) - go func() { - if err := et.ec.Start(); err != nil { - errs <- err - } - close(errs) - }() - - // Wait for connection - <-et.ec.connected - - // New node - a := common.HexToAddress("0x0f010f") - - // Add a minipool - minipoolAddr := common.HexToAddress("0x1f101f") - et.ec.handleEvent(types.Log{ - Address: common.HexToAddress(rocketMinipoolManager), - Topics: []common.Hash{ - common.BytesToHash( - et.ec.minipoolLaunchedTopic.Bytes(), - ), - common.BytesToHash( - minipoolAddr.Bytes(), - ), - common.BytesToHash( - a.Bytes(), - ), - }, - }) - pubkey := pubkeyFromMinipool(minipoolAddr) - h, err := hex.DecodeString(pubkey) - if err != nil { - t.Fatal(err) - } - - _, err = et.ec.GetRPInfo(rptypes.BytesToValidatorPubkey(h)) - if err == nil { - t.Fatal("expected error") - } else if !strings.HasPrefix(err.Error(), fmt.Sprintf("node %s not found in cache despite pubkey", a.String())) { - t.Fatal("unexpected error", err) - } - - // Opt into the SP - et.ec.handleEvent(types.Log{ - Address: common.HexToAddress(rocketNodeManager), - Topics: []common.Hash{ - common.BytesToHash( - et.ec.smoothingPoolStatusChangedTopic.Bytes(), - ), - common.BytesToHash( - a.Bytes(), - ), - }, - Data: big.NewInt(1).Bytes(), - }) - - rpinfo, err := et.ec.GetRPInfo(rptypes.BytesToValidatorPubkey(h)) - if err != nil { - t.Fatal(err) - } - - if rpinfo.ExpectedFeeRecipient.String() != common.HexToAddress(rocketSmoothingPool).String() { - t.Fatal("Expected new node to be in SP") - } - - if rpinfo.NodeAddress.String() != a.String() { - t.Fatalf("rpinfo has unexpected node addr %s, expected %s", rpinfo.NodeAddress.String(), a.String()) - } - - et.ec.Stop() - err = <-errs - if err != nil { - t.Fatal(err) - } -} - -func TestELHandleSubscriptionError(t *testing.T) { - et := setup(t, &happyEC{t, - []*mockNode{ - { - addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), - inSP: true, - minipools: 1, - }, - { - addr: common.HexToAddress("0x0000000000000000000002234567899876543210"), - inSP: false, - minipools: 3, - }, - }, - []*mockNode{ - { - addr: common.HexToAddress("0x0000000000222222222222222222222222222222"), - inSP: false, - minipools: 0, - }, - }, - }) - - if err := et.ec.Init(); err != nil { - t.Fatal(err) - } - - errs := make(chan error) - go func() { - if err := et.ec.Start(); err != nil { - errs <- err - } - close(errs) - }() - - // Wait for connection - <-et.ec.connected - - // New node - a := common.HexToAddress("0x0f010f") - - // Simulate node register event - et.ec.handleEvent(types.Log{ - Address: common.HexToAddress(rocketNodeManager), - Topics: []common.Hash{ - common.BytesToHash( - et.ec.nodeRegisteredTopic.Bytes(), - ), - common.BytesToHash( - a.Bytes(), - ), - }, - }) - - // Check membership - found := false - err := et.ec.ForEachNode(func(n common.Address) bool { - if bytes.Equal(n.Bytes(), a.Bytes()) { - found = true - return false - } - return true - }) - if err != nil { - t.Fatal(err) - } - - if !found { - t.Fatalf("didn't find %s in active node set", a.String()) - } - - // Force a reconnect - var sub *ethereum.Subscription - var headSub *ethereum.Subscription - et.ec.handleSubscriptionError(fmt.Errorf("fake error"), &sub, &headSub) - - // Force a backfill - highestBlock := et.ec.cache.(*MapsCache).highestBlock - highestBlock.Sub(highestBlock, big.NewInt(20)) - err = et.ec.backfillEvents() - if err != nil { - t.Fatal(err) - } - - // One node should have been added - // Check membership - found = false - err = et.ec.ForEachNode(func(n common.Address) bool { - if bytes.Equal(common.HexToAddress(backfillNode).Bytes(), n.Bytes()) { - found = true - return false - } - return true - }) - if err != nil { - t.Fatal(err) - } - - if !found { - t.Fatalf("didn't find %s in active node set", a.String()) - } - - // Make sure we can exit normally - t.Cleanup(func() { - (*sub).Unsubscribe() - (*headSub).Unsubscribe() - }) - - et.ec.Stop() - err = <-errs - if err != nil { - t.Fatal(err) - } } func TestValidateEIP1271(t *testing.T) { - et := setup(t, &happyEC{t, + et := setup(t, &mockEC{t, []*mockNode{ { addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), @@ -1306,17 +415,6 @@ func TestValidateEIP1271(t *testing.T) { t.Fatal(err) } - errs := make(chan error) - go func() { - if err := et.ec.Start(); err != nil { - errs <- err - } - close(errs) - }() - - // Wait for connection - <-et.ec.connected - // Test cases testCases := []struct { name string @@ -1374,10 +472,4 @@ func TestValidateEIP1271(t *testing.T) { } }) } - - et.ec.Stop() - err := <-errs - if err != nil { - t.Fatal(err) - } } diff --git a/executionlayer/maps-cache.go b/executionlayer/maps-cache.go index 1f54452..bb17e8b 100644 --- a/executionlayer/maps-cache.go +++ b/executionlayer/maps-cache.go @@ -2,9 +2,9 @@ package executionlayer import ( "fmt" - "math/big" "sync" + "github.com/Rocket-Rescue-Node/rescue-proxy/executionlayer/dataprovider" "github.com/ethereum/go-ethereum/common" rptypes "github.com/rocket-pool/smartnode/bindings/types" ) @@ -29,11 +29,6 @@ type MapsCache struct { // We store oDAO nodes for the api. We need to be able to remove them if they're // kicked or leave, so this is a map of address -> bool odaoNodeIndex *sync.Map - - // We need to detect gaps in the event stream when there are connection issues, and - // backfill missing data, so we keep track of the highest block for which we received - // an event here. - highestBlock *big.Int } func (m *MapsCache) init() error { @@ -41,7 +36,6 @@ func (m *MapsCache) init() error { m.minipoolIndex = &sync.Map{} m.nodeIndex = &sync.Map{} m.odaoNodeIndex = &sync.Map{} - m.highestBlock = big.NewInt(0) return nil } @@ -66,14 +60,14 @@ func (m *MapsCache) addMinipoolNode(pubkey rptypes.ValidatorPubkey, nodeAddr com return nil } -func (m *MapsCache) getNodeInfo(nodeAddr common.Address) (*nodeInfo, error) { +func (m *MapsCache) getNodeInfo(nodeAddr common.Address) (*dataprovider.NodeInfo, error) { void, ok := m.nodeIndex.Load(nodeAddr) if !ok { return nil, &NotFoundError{} } - nodeInfo, ok := void.(*nodeInfo) + nodeInfo, ok := void.(*dataprovider.NodeInfo) if !ok { return nil, fmt.Errorf("could not convert cache result into *nodeInfo") } @@ -81,7 +75,7 @@ func (m *MapsCache) getNodeInfo(nodeAddr common.Address) (*nodeInfo, error) { return nodeInfo, nil } -func (m *MapsCache) addNodeInfo(nodeAddr common.Address, node *nodeInfo) error { +func (m *MapsCache) addNodeInfo(nodeAddr common.Address, node *dataprovider.NodeInfo) error { m.nodeIndex.Store(nodeAddr, node) return nil @@ -118,26 +112,3 @@ func (m *MapsCache) forEachOdaoNode(closure ForEachNodeClosure) error { return nil } - -func (m *MapsCache) setHighestBlock(block *big.Int) { - if m.highestBlock.Cmp(block) >= 0 { - return - } - - // Someone else owns this pointer, so make a new one - m.highestBlock = big.NewInt(0) - m.highestBlock.Add(block, m.highestBlock) -} - -func (m *MapsCache) getHighestBlock() *big.Int { - - return m.highestBlock -} - -func (m *MapsCache) deinit() error { - return nil -} - -func (m *MapsCache) reset() error { - return m.init() -} diff --git a/executionlayer/multicall.go b/executionlayer/multicall.go deleted file mode 100644 index 8a66436..0000000 --- a/executionlayer/multicall.go +++ /dev/null @@ -1,31 +0,0 @@ -package executionlayer - -import "github.com/ethereum/go-ethereum/common" - -type stateContracts struct { - MulticallAddress common.Address - StateManagerAddress common.Address -} - -var stateContractsMap map[common.Address]stateContracts - -const rocketStorageMainnetString = "0x1d8f8f00cfa6758d7bE78336684788Fb0ee0Fa46" -const multicallMainnetString = "0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696" -const balancbatcherMainnetString = "0xb1f8e55c7f64d203c1400b9d8555d050f94adf39" - -const rocketStorageTestnetString = "0x594Fb75D3dc2DFa0150Ad03F99F97817747dd4E1" -const multicallTestnetString = "0xc5fA61aA6Ec012d1A2Ea38f31ADAf4D06c8725E7" -const balancbatcherTestnetString = "0xB80b500CF68a956b6f149F1C48E8F07EEF4486Ce" - -func init() { - stateContractsMap = map[common.Address]stateContracts{ - common.HexToAddress(rocketStorageMainnetString): { - MulticallAddress: common.HexToAddress(multicallMainnetString), - StateManagerAddress: common.HexToAddress(balancbatcherMainnetString), - }, - common.HexToAddress(rocketStorageTestnetString): { - MulticallAddress: common.HexToAddress(multicallTestnetString), - StateManagerAddress: common.HexToAddress(balancbatcherTestnetString), - }, - } -} diff --git a/executionlayer/stakewise/eth-priv-vault-encoding.go b/executionlayer/stakewise/eth-priv-vault-encoding.go index c7326cb..3929a02 100644 --- a/executionlayer/stakewise/eth-priv-vault-encoding.go +++ b/executionlayer/stakewise/eth-priv-vault-encoding.go @@ -51,7 +51,8 @@ func (c *EthPrivVault) Instance(backend bind.ContractBackend, addr common.Addres } // PackMevEscrow is the Go binding used to pack the parameters required for calling -// the contract method with ID 0x3229fa95. +// the contract method with ID 0x3229fa95. This method will panic if any +// invalid/nil inputs are passed. // // Solidity: function mevEscrow() view returns(address) func (ethPrivVault *EthPrivVault) PackMevEscrow() []byte { @@ -62,6 +63,15 @@ func (ethPrivVault *EthPrivVault) PackMevEscrow() []byte { return enc } +// TryPackMevEscrow is the Go binding used to pack the parameters required for calling +// the contract method with ID 0x3229fa95. This method will return an error +// if any inputs are invalid/nil. +// +// Solidity: function mevEscrow() view returns(address) +func (ethPrivVault *EthPrivVault) TryPackMevEscrow() ([]byte, error) { + return ethPrivVault.abi.Pack("mevEscrow") +} + // UnpackMevEscrow is the Go binding that unpacks the parameters returned // from invoking the contract method with ID 0x3229fa95. // @@ -72,5 +82,5 @@ func (ethPrivVault *EthPrivVault) UnpackMevEscrow(data []byte) (common.Address, return *new(common.Address), err } out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) - return out0, err + return out0, nil } diff --git a/executionlayer/stakewise/vaults-registry-encoding.go b/executionlayer/stakewise/vaults-registry-encoding.go index 9405637..75b5097 100644 --- a/executionlayer/stakewise/vaults-registry-encoding.go +++ b/executionlayer/stakewise/vaults-registry-encoding.go @@ -51,7 +51,8 @@ func (c *VaultsRegistry) Instance(backend bind.ContractBackend, addr common.Addr } // PackVaults is the Go binding used to pack the parameters required for calling -// the contract method with ID 0xa622ee7c. +// the contract method with ID 0xa622ee7c. This method will panic if any +// invalid/nil inputs are passed. // // Solidity: function vaults(address ) view returns(bool) func (vaultsRegistry *VaultsRegistry) PackVaults(arg0 common.Address) []byte { @@ -62,6 +63,15 @@ func (vaultsRegistry *VaultsRegistry) PackVaults(arg0 common.Address) []byte { return enc } +// TryPackVaults is the Go binding used to pack the parameters required for calling +// the contract method with ID 0xa622ee7c. This method will return an error +// if any inputs are invalid/nil. +// +// Solidity: function vaults(address ) view returns(bool) +func (vaultsRegistry *VaultsRegistry) TryPackVaults(arg0 common.Address) ([]byte, error) { + return vaultsRegistry.abi.Pack("vaults", arg0) +} + // UnpackVaults is the Go binding that unpacks the parameters returned // from invoking the contract method with ID 0xa622ee7c. // @@ -72,5 +82,5 @@ func (vaultsRegistry *VaultsRegistry) UnpackVaults(data []byte) (bool, error) { return *new(bool), err } out0 := *abi.ConvertType(out[0], new(bool)).(*bool) - return out0, err + return out0, nil } diff --git a/executionlayer/stakewise/vaults.go b/executionlayer/stakewise/vaults.go index 4de2f39..2460f58 100644 --- a/executionlayer/stakewise/vaults.go +++ b/executionlayer/stakewise/vaults.go @@ -1,13 +1,11 @@ package stakewise import ( - "context" "sync" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" ) type cacheValue struct { @@ -18,13 +16,13 @@ type cacheValue struct { type VaultsChecker struct { registryInstance *bind.BoundContract c sync.Map - ec *ethclient.Client + ec bind.ContractBackend } var vaultsRegistryAbi = NewVaultsRegistry() var vaultAbi = NewEthPrivVault() -func NewVaultsChecker(ec *ethclient.Client, vaultsRegistryAddress common.Address) *VaultsChecker { +func NewVaultsChecker(ec bind.ContractBackend, vaultsRegistryAddress common.Address) *VaultsChecker { registryInstance := vaultsRegistryAbi.Instance(ec, vaultsRegistryAddress) return &VaultsChecker{ @@ -35,17 +33,14 @@ func NewVaultsChecker(ec *ethclient.Client, vaultsRegistryAddress common.Address } // Returns the vault's mevEscrow address if it is a vault, otherwise returns nil -func (v *VaultsChecker) IsVault(ctx context.Context, vaultAddress common.Address) (*common.Address, error) { - callOpts := bind.CallOpts{ - Context: ctx, - } +func (v *VaultsChecker) IsVault(callOpts *bind.CallOpts, vaultAddress common.Address) (*common.Address, error) { var cv cacheValue value, ok := v.c.Load(vaultAddress) if ok { cv = value.(cacheValue) } if !ok || cv.lastChecked.Add(24*time.Hour).Before(time.Now()) { - resp, err := v.registryInstance.CallRaw(&callOpts, vaultsRegistryAbi.PackVaults(vaultAddress)) + resp, err := v.registryInstance.CallRaw(callOpts, vaultsRegistryAbi.PackVaults(vaultAddress)) if err != nil { return nil, err } @@ -61,7 +56,7 @@ func (v *VaultsChecker) IsVault(ctx context.Context, vaultAddress common.Address // If it is a vault, we need to get the mevEscrow address vaultContract := vaultAbi.Instance(v.ec, vaultAddress) call := vaultAbi.PackMevEscrow() - resp, err = vaultContract.CallRaw(&callOpts, call) + resp, err = vaultContract.CallRaw(callOpts, call) if err != nil { return nil, err } diff --git a/executionlayer/test-data/block-by-number.txt b/executionlayer/test-data/block-by-number.txt deleted file mode 100644 index e812e5c..0000000 --- a/executionlayer/test-data/block-by-number.txt +++ /dev/null @@ -1,368 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": %s, - "result": { - "baseFeePerGas": "0xb9df74103", - "difficulty": "0x0", - "extraData": "0x6265617665726275696c642e6f7267", - "gasLimit": "0x1c9c380", - "gasUsed": "0x109826a", - "hash": "0x1e6cff9821d371e69f232065c3a6b9b3fa96772d7687c51f3caae6b08850c94b", - "logsBloom": "0xaf2b103769c62262580a40c0ac1ca29d184968825b89221429e98d50b4eb7c56821533ad8914227464a4bb20c886296aa78189098cf77dd77895a4b9043c98a10416899a6d206b2928a340ffc43020a1808301870dc6fc40308417dccce3095a2b2644459b9b644be1154e19508c7e55a6c20a2710381621d654c4dcc58b182c4806b67137248961956431c941c200a651d4cca79d15dde8c779e35278b8be2bab60111c1f81e7cf927261e958215ecc140c64604490c0ba58602703168993015238c012306f9884000008a2423a1c0356c1a00b16862254d02c038ed110e64cc771bc4c1a81a0218a8614a82812504977fc9141e7e84940085dc4f0643514d7", - "miner": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", - "mixHash": "0xc66f17fafad947e3f52c07e7d8f97636671fb6a8b430dbc8d23bd5b95e7e8cf6", - "nonce": "0x0000000000000000", - "number": "%s", - "parentHash": "0xaebf5ad31c37d7014e8432038faeb837e5f0415798a83dc1efa7109cdfbd71a4", - "receiptsRoot": "0x6341ef807d317285259eb887fed0f3cf095996477f5af77f79f9c4358e9e7363", - "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "size": "0x3717a", - "stateRoot": "0x9ee6b41b3b5c4dfb4eacaf9a460a517983396d0ba609017c3de07828e76d53bd", - "timestamp": "0x654e6ff3", - "totalDifficulty": "0xc70d815d562d3cfa955", - "transactions": [ - "0xe32600a7e0e8173046395eef8b922378d856b30b487d9759ac2dae341bd2a91e", - "0x69c2aef5cf127ead7d6ae79b22c8fbbb0f105e479da5f0ae32f3b1a4841c626b", - "0x04a7f593ca7f5cfd2c64fe0875facec5f7fb9e9064eae5eafdbecb5fd38d3832", - "0x644dd23819345cef99056e9bbfb2ade6ed25926e9cbe9af3ca53a9147f8ce220", - "0x184917a4645e79d5afb834b08fbf8de0033f4254c84e0b228e1db19818925305", - "0xd98e1f69bbf640c39323b8f57d1c1e8887090693d0d0071476114e66dc83e6cd", - "0xe9b2af0e4eec6d732d118a820c8be1992669c1905c40cf9a03c3f60edb5d0bc5", - "0xe2369c23027d33e865c5a1aa9fc18e262e010c20a3b54d5de24d624bc0182481", - "0xa7c41c10c7ecbfbd087804b418aae7542b7503d1e842a32df760e3ecd5606321", - "0x8353e16e1f0b24c4a47943d78fa6a88b9ff0da2032ae70748e6d2253cdf243f2", - "0xc55d52a938e5e4f999985f49e3d7cc3381454acef757fe339c67a8a9acf1422e", - "0x273d0a69c208c1889d66df7c888069e6b7ee843abef608bad8442ec803afe00c", - "0x1471af816f746434d5a8348853aa8868c0f0ea6f7127987f83cf7e7205a424ea", - "0x8fe0c53b8840ee06307fb9980d07bafd80fe531dfb7d63e6b5e86572a2ad24ac", - "0x6cfb27b3453eff1e091f1f1f3fb00b66dd049ef7903028d25cc5dfbca4f85666", - "0x57ce17423f6a16a090abd09eb95886ccb1923ea579cc4d3e7e7fb281182013c7", - "0xdbe62ea3cbc67c69a4da1d103afdef6b77fbe615f6ac4d77dd1f305b65dc6e09", - "0xd1af6c6463d93531739a7718313708b50df8b910a8b0d99cd029549a9ad6d9c4", - "0xb05912060713932712dc70b16e647d48646180f13d082a8d152b50a669be26d3", - "0x2a2ab86bc608ea20c8a213c460ec50c892616139abc1177936b63b76395018e9", - "0x118c353cc57862865037fe9813e59f290e09e4e7ac09875468ea8f81402782e0", - "0xa6fd8f9f4aab67748ffaa18e6cda0c585bd158d78db7f04308956add88377586", - "0xf5519dc881c81bd854767d886d1a940b9a31816de73e350946aa37779c319d22", - "0xb11f74f0499de2099f2289aad2ec3d08b79ef73001339e2e082f078f1e88434a", - "0xaa8d95fc0884f7011e0c13782ad7dd05039590a03d5e259aba451c12d3b742a0", - "0xc6ad44f8288f040d455f35d8fc6ff7e8c9d4d2754187a8b68ba9fbdc04954f2e", - "0x2d808db86133aecde58a95252279f1c12482cd9a5da2874ddad179ce747b3cb6", - "0xf16e94ec7a646628fa7df837d11e314785c4d755809958ee3f9fa17e1785557f", - "0x79fee277875c21a04c9dd39eff92c0cfb3e318b97185721f1c06de19b360c273", - "0x699958e8f97d08296fc1da347546a6138866b522910c0de5075561fc1ebb26f3", - "0xa2c873911c621a51563aa6afcdb5d163c452ef44f523dbb7f941a1ac1ea6da40", - "0xf56dca89c7ecc459601c3af70cab5e7c911e57a733db957a42f6c693876d6a66", - "0x7e45e369394c02b43bd0e4918d5721164d4d585c64233fa15e73ca85e6fbe3f1", - "0x63c02a24c6f7beaf63ac9ef273ccbec8be35a09b49e888e5380cccb8e54922c0", - "0x2f8f1f1d8625239095876126b51a044535a6b1e0dd2f421fdb5ccefdb9179fa5", - "0x30a9740513525f48b6aa3e3a2b31e790251f8e5002b5afbdf07e571ee516d6b2", - "0xace3f8624e2adcc4e13244a837f1e229998dd8feae9cd7b565de2f92345ba73b", - "0x54b05cd6e90d9ac07e30c90d067f4474bbcbf9ddf6290c01425e9ae86915ed07", - "0xb3a1deb0c0b25632d150c982ca4b382bd6ab0c399c973b26837631f9cc2c1e1b", - "0x700e716e117d83547a072a2661decdc72795af73588063af995174d2ca75c0b3", - "0x08f95ce6800827c0a2fd4c6422d9b0057dd2c56b7cba50e138570863798ec6f0", - "0xc440caf6538da886ee0649dd21b83ae6e909547386eb45ce5abb0a7ae40b0e3e", - "0xff411586fb0f382df8a4b6c7ec6d21f51502d51c09a369daf91de7556d7f43af", - "0xce257b923b413ba17ded49b2167cd53225ad52250e4d8454141e467da7eb153c", - "0x416b35f476bbd5bfc01382030c0020c7b219cdd0544c5e6977109e3a6e762b96", - "0x3bc5badb19e4679306efe06e11509b4403a3b1c810b78d8c4e76647fb4b1be8d", - "0xb4b10e3aa96275d5bff895823c385831e5751bbfa6d20109d993dbd6d392b617", - "0x6a6df0cf2add02ce3151b3200c14564df7b3e73682057456ee8e5be23da0f74b", - "0x32ef2706e883520955f9bb47cb301e3faf9432488f641ecad23e241010c30ece", - "0x72c42342a96cd248cfecf0aacbbf900b798576844562b65abcd94b1b2cb99f02", - "0x8f33f6ff0532fcf3c8d8018d7c86d822025c5c634312a1b70198b80cb1f8937b", - "0xa50e7bf67c59a015b5b503c785ec208f5970e94b7fa2aefd6f98e37afed42c30", - "0xda324bcde08c7b7827755afb12e08310ecd7fd149f1a3884638121a4cbd61529", - "0xa559c0de0f93819fe603df39599fbfa995be14b7f2ddadec211d08f4de5fed11", - "0x573959eb3e6b9b02b323d5370dd61538123dd552e88d02065a6500f06fb7c651", - "0xa0c566e5a16f8a870dd2ba772500101108d51afc4495c1bf90722d49d071b0b1", - "0x156c9b44b36abb2f3e80b3781591a1f3b153090b0171bfab235c00cf2187f011", - "0x782b712dd53468603f779b96b93db7a4ab8b8822ba45e59176c917a0b93235cd", - "0xca01647a82a3704e2830f85adf4df066c4453f4f358843fd91ea223e0de32446", - "0x48fa25150a280edffef63ad76678b04f9fcbe2e40f88161f0dc940b16168ff02", - "0x49a229bdd1465d5cc5ad4e1ccaf503b0068839432170e3f332568fa87e550475", - "0xe3e476bde0676f102dd08159d85f0807db94a2b8a9e684f1f2c8378fe60f24bc", - "0x2b592ff1c3c329bf45891c7859fdaaf809a8b7c1234a38277abaeee9a96b1027", - "0x9c8cd05c7bc04f4c28593360f13d7b74eebbbfa789af3355500ed060d5da0a28", - "0x238087fd55609dd0f0eff77c5d28c0be72706805984e7649294c15dfcf32b13b", - "0x7c44b2caca9e6954003425d88449448b8c89c64a8a5d7de433d904ce41cac9f0", - "0x03e3b6d8afad1e50d19eccbe18f1009cbceea98da44f2ccde60c9ceb1fe024c4", - "0xc3f70c70ceb55baf915eb721deb54c4da7bba1918a907b4e07e97f5e05094c3a", - "0xb14a5ff10b6e21aa32d5135758f5239ce0e1fb4a2d6383136a8644e7070a5000", - "0x687827f3c49e803f88597cf847d20c72bf8f1b19f3d746021ef87731aad9c8d8", - "0x5fc3d1396bdd5b9f41ca925b52cbde12f4edbb7e7de5542ed12b2ab4fca97344", - "0x16a866d524a3187fc7a9f998abffee342aff659373a3fc96d0a8f8e840ecabc4", - "0xa53a9484ef59039ec7c57db89769857beb0604c9da4988b4acc0f38c0472f9d8", - "0xff1e5d1d8382cf543c76d1f3b1bb5eba69c95f840661491e87b7354119abb1ce", - "0x9db4da9872eaa125edd55aa3dc78a329913a8efbfa9f0fe9ea1be16b7717520b", - "0xe0f18f58ea4de675f012815f4f09ea41cee189ecfcb9329b756734a35eed168c", - "0xf83c1aa59421852562d681e44f6c4b0a6d597a71c0028bb9381ff842fbccd0a7", - "0x705c3aae6c496cd609090dca3e6e99497652be430aa2db482d06bdb774ab9809", - "0x05d3221f209af7c0942e8d10f3d838b36e6dd6d6f20c30038805cf8977e02abb", - "0xa9a66ece11a957ab6c63a4ec66b71885e253a849f3fda63258087fbe960ff9c3", - "0x724cf08fa40c7ae5dc654c4219678c7e7a7d651e55880c1265c0aa87db7cf1f6", - "0x03579374fb4f397618cda96f957ae1627679406ecb774040e7688b4beb98827e", - "0xc8d8fe6f55a8c73d73bcf676124ca14847344439337938dbdcbf7d09a6af7806", - "0x62983aa5706dddccd2caf78a3165ceb0bb6b3e72e6e5df274a868b1c4aef1c1d", - "0xa39b7bab5068bd339d5fe966518b3cb4feb8599e946b2724dc4ac47343f38f7a", - "0xe32c0806bafff271d1581b045af6d3052dcc30805213810ef54fe3a45cbbcf16", - "0x15df30a73c172be6b70299fee7e7ff7b00dc98be90bc745c896aea4bba26abf2", - "0xd9204d656146342cbf0bba88a82b5bc18ce2d1f82b0bb5153bf19f023d81f036", - "0x1a947c7ac30530e6d02f571be4408009f9d488dccab4da98c3be7c363c1ddb8a", - "0xb69c2f73dbc6f6f0b177595d7b8e9217f001261c407386115985e149cf26c80e", - "0x126b4756c9c1dcb62dfefa8c102ed8f41743b060af1cb646853c22425691fdc7", - "0x9d617dedd7c679df25031364eaf1103dd577c6a9357915452ba24e3ee6f3054d", - "0x44c60cd4e2dc649a963e65e4c5b7584ee9d27396587545e158f8caae62a67b98", - "0xc82261a122c310c7bd485b5f669fa9c7c4c3cd5f36e98f550da673d28ac3a5b6", - "0xe5746a9184ef92cb3747467a52b5c743a11703cb4b4592a448255daf4aef2dd5", - "0x58b641cf22e528689f1322fd4690918a6baf5ae8411d7867ddf4f80aa23a5f95", - "0x57c328cb66623d2b69cd6fd7665051794a44f758b52624f036ebe306195864bb", - "0x593095c1fcd70c025b3425f67f277f7836edac9405a4c62afa91501ae7f05f26", - "0xcc4267d6f4f0563d15f90f16d58696b7a32d48d8b3507d49f183580ba1ffd671", - "0x46918a74114bbf71bf2dcefa5c8a9cc0c035d5cde03412c9ea0a9c745f6067da", - "0x09a2e5ae1c5042d178c257d4bcd97fa30d263475c1aa6fcf85fec20cee04c305", - "0xb22171de28deb22890dc461e86a92242361ec68ba24177a3a4848e6ec6df3c43", - "0x0180784e087535c15a729e4a01b1c5e221eaff47adf9428567b42af545af12e9", - "0x301c5390e2f989529af02349cfc6f71a2c9cb74760aa57214e9638e9f591029a", - "0xd50f24020526c43db6b8476787e008392fde62ac87c6896d9252acab027c454e", - "0xdd7d4e570b058e5fb660c41df382ab335c9d082e5b00746b7382863226f6f96c", - "0x1bc7a49b1a0f927057f9e2ab4c12b5d0b27f910335084d52d112d388780d121a", - "0xcb2c022367f1dc942d9b61d75ddf63901a33ba53bf3df07ef75e3470e441b14d", - "0x3316ba41b76db52ff4a8eb21bb2b30efe9db848d5cae953492894261cb415062", - "0x0992642b28b6fccaea88eb00133a35b1812aa2202f6d9337f2fb5d1772de9ca8", - "0xbdeb123a1ee1530d5a672fec641a7152363f953631b00c6a432fea94e93f5c22", - "0x200d409a0520042e2d9a1afe5766cc32cc1319e5b17813b09cee53ebd533bb0c", - "0xc9c571b564253a1a83e1d109c39141852f4d99d99f70a8dc52926c0f0667ab54", - "0x48b159b06c41181fe3fb186d66d2a808ece96b9865aea7ea7907aaa98e4002d0", - "0x22af59c0a0f4bccad22e35d20a17db400e740d166fdf565aea4820af5fc96d35", - "0x0d9fa5cb5d7b321965cb74c5e75943847d849ff3cb650d51cd1995b6823ef902", - "0x7cf45a35fb723c2e35ee96f38c4462b15ff50d50542971ed2fcb092f224aa925", - "0x082a78848aeb61eeef6b7bc8ac8414bd7bb76deaba9f06ee273b043331a49823", - "0x95f02035501861a39e535c580bfe1c6ddbe0127f93af67b92e55ff807c83cf7e", - "0xdadf247c40d79b4d72258ad3e87378ab1d9d649c127d7dafe6f8cb76d5870503", - "0xc717c8ffe8d4859d4552915705e11792e07c050bf7edeada17adc9ca29eb5644", - "0xd13b096e74c0d7a46d370d7a56738c89c8088c332fa66fe38482e2ab8fec6083", - "0xe2188ae117fe02a9db73ecdf9fdc5294dd6e48a11199fd851c68767753ab0fce", - "0x0511c51fa21c86a60817798573990140cfeccf59c543a8624755c6c6ddb01146", - "0xddffed42bf47f2ca588a2971c34d50c273de4aee518bd2241f1d3df7c6e60633", - "0x75c17ae27dd30195559ddad96508539d3efbb6b1f549420552cc8ae30264dcd3", - "0x864748970eb0ec56641a62ca79b28f6010233f7d48e70e1be971522f4c5a74d3", - "0xb8257201eb44b1c025338184027a2b825eebd294b89824dc689e45c281d62578", - "0x07ab9ae5fed6cd4ade5ec951eeeb5ed8512a224b27e9217d5b5235b570dad25e", - "0x9bde0062447cef43f146a13876bc45c9c8073257ae1d8b70e2fcf4eb251b9bce", - "0x9aa2f9acb82067432fef796fb4cd41c0e509cd86d494e9af5419f1ca3f6ab888", - "0x52378e152ca0041097fd8d312b815a8d733d6c62647a427b71ad413192affcad", - "0x241a13a492341fa1552649885952afe736fcb586853660781264f6305122b9b9", - "0x01194e03e9c604cfcade8c990b05a9caddcdb352422678be5e6b55995f1b3e9e", - "0x7e3712b51325c1c732bb48306d61c94e1855a68af1c81113c0f3e9fd0498a2c8", - "0x7c05e5446fb692fc2a6b71df3397655288272200141947f7aaa9564cfee14630", - "0x8818504cff5211fe743f83a1f5882172fa2429e6bd551293460bc7a94bb5332c", - "0xd05d2f0b844f3bad2fd3d973b332e8be6b900619af604e45bc7744a913a26c84", - "0xc147eee6db88c13207a7679c59a9db427072bed5f1c0e1447b836cf24ac3db3d", - "0x914484609090cc828addfbd25ea15cfbe2ffd69da8a32637ef5b1e211331df92", - "0xcac3e724de214e1f9383b10f0392a676fa8208d0d9f1a022a00562e8c7db211f", - "0xecd0b8442a8a3b89554be62f06177a4682e4cdee71b30c2654575329b44a382e", - "0x0d9bbf232396918751f0fd71ec9f92c3ccfb7c44d6bf24841260ad5694ad53aa", - "0x79d902cd7ba5fd3714db29794b704bea5e86218ad5f181810df5dad5172cde95", - "0x33c08b7c7bd22b53514419dd6b50971d37bd6cda15b67f8088b08666033f9d70", - "0xc11e8aaa148f8e5d1a388d30c1eccfa6cb59956468c11d67ab7dc3dd727c311d", - "0x535020eb8794c1860a719bc5aaf658990226296355b3a7292d3b47c9d4364f66", - "0xafb3916f6edebf8f95553cb367a586f700079990a45699f54bbbb669c82b7d4d", - "0x993ecb8796b291f4979c0d1ed3a3fb911756e9601ac937c36c56414ca13d3820", - "0x58939f2e002b54dbc50cf70a3b43dd5622e2bf9c08b8a939d8ea4002aad95ab6", - "0x95039dd8bdd51128dbc5803d0b78145dec39c8abf0ad92ecf0e5846b63d5de4a", - "0xa7f35386eac20afac9aa2d9ec20021497564f2629618aebe9cf9b76cbe61f2df", - "0xf54d194cb2cb52a1b7e136e8c239777cd7bc4446a395632625405c6b090c2a3f", - "0x9d11aff3247c9b9d23576aa6d52d98242d2142f5e8b6e26f612d8af17ccc40f6", - "0x3bd316378251c8a0bca5255ae82c22a2cd30261b7b70cd9d3cf1a240f5e1ade5", - "0x36626d41b858a8b64dc289ccd1fc356ebe72f84c27f6bf3d47088ec50d1fec69", - "0xd542ce845fea295b419671a54a8dbf2b45683c912aad0894e579c772b2db9624", - "0xcc7fd3a6bb06e0e1093574c51a10b9c43f3dabd3d05975f534ae26a7c232aa0d", - "0x1bacdefa45cf35d4ec40aceb674c0f526c4b6776ff52c632b52a68d64db85bae", - "0x6be381f7585094ce69201ec2283a09664e130b5cee11f671483448886ff6a048", - "0xeb786e15a2fab17572834ce8f17152325da64551bfaac986d11b9841103c43c1", - "0x5e807d116c34d8150014b17e2bf71c3e7501e2ff147e79a67fe248641414f4ed", - "0x5891a452113493c1e3868cf1f39c4876a003424dfc0f1c5d40dc4415d249994c", - "0x017b10d732f557f08c6b2a55f22319c78a9e537d715b3c84d876c865482417e5", - "0x7a15027029eb201842f05c34deadd7da441e57cca088eb932e77f928f88a0129", - "0xec8f5044c2f1c2500c5457da3eb74b4bb6b060226319fad4cd39a709b3c02780", - "0xa7717180a197b1434d5fa60ba07c5ce4b3f21aaccfda0c8a79de28bcb0c3624b", - "0x8d0867cf105721674d092884608deaee24bdd69e98a594713505f105e15779cf", - "0x9657811fa58d60ee49df37b98e6e1cfe59371a3014894d847bc945415610a8e0", - "0x679f8544638a9a0193f98c8bfff7125fd363c0e116e346e2edcc89f3da1d048f", - "0xc40941495a61afcf920ed6b30f075dde9eb419d3410aeda80bc6f920b07f5cd8", - "0xb9abd2eaf1e9b5cfa87afb60e1e7224f034094a76e3d67f356ff6c95298ae6f9", - "0xaab045214ee7d5c0e23e222d4690cf98a9dfce7991d6ce126238406cdfd1dc34", - "0x0c0c03eb53e9b64f116e91712991e2fee93fa6db6c6aa6f201f5fb077d89640c", - "0x0030f43127ddfc0aeb920d8307974c18321ca7cb53d691e4afccaf8941a55126", - "0x60dc06ade9a254cd30ce7114fcbab0e739a3385fc7322cccb119c088e80cd451", - "0xf52d63fa50ce3e1d6e064451f80f00b4ed50dca307bd78bb0f25c953662498f2", - "0xe64f5dd376d0e60a55d8eb4c1c1e7b6713679e48f9759e81f11e5cd6d63b5377", - "0x6aab6d2b842ee99bdc4c89690c3a68911b957bd5efe8a2c3ca62b228c0eff201", - "0xcdbbd5a459474b521aae246d5b54e87f8fa1b85837ded7d7a81fdd22c2a1f814", - "0xd7ac9729b397f0d398adba5c72cf75e374b95ec434e8c62647a5c7ddfdc2954d", - "0xd9461a86ddc7e4e59fc4fc2adb75115fdac2160d205045efb0153b0263d1aec9", - "0x33b85c0d5ccdcc2198008f8e2be0a72ae54e085c50cacb9a3d6532ac5b548a61", - "0x546e471e1b089ad6c7c0dc2b4cdbf2cad4323d95e6e28f93463b87b67f7b8f48", - "0x480c735a872672872a887b97aff4e31b38d7ba1a8603e1122ff4eeb90b87a06d", - "0x9baac806de03f88ab7dc8e34381f4b5734994d15ed3ba8497afcea42e488e351", - "0x93a75062b8ed82a920465da5768e7f78c1cb14dc13cfecebffc43905197ccca7", - "0x691d4504577f8ca61bc8788b6fee0455941cbbf0b7ab5567b66f79329ba16bb6", - "0x5b7d290ba1881207433511658892d34f595821e67e0a915059955613a8250eb2", - "0x69b9971333b7b4cded6a3bc42a07d08882df6db2bf9a2874e4905882db471afa", - "0x6a4f74132ff0484739955e61dc9d56036d000c63962b52055a72551d360e3ea4", - "0x1e17670d957456c733c70bd28cde80f47eaf91f2f39c2c23719141d2ffec9690", - "0x39dbef008d6cc22924ac560fdc325126d48dc1a902635cce12006da949fc9cca", - "0xe599e4b5e3129813bff84afdf9ac43fc2f39696341c08c0316853a7103851424", - "0xbb44685a09f0cedff52928973bbdea1e3111d2685f494e06016a2d2d10d2450c", - "0x0b076b6a31d51548b2f4a89106330f106e8cafb98c135aa1cd69f7725c300d36", - "0xbbb78874ef945e9d70016f30760184d1c5257f4488c44e5f303a99c8cbcd3353", - "0x432e7755525c017d235599e24d0c8ab181590dc85019c0aa4c60a99375cb5883", - "0x68d6343dcc7e088937440593d66d8b9ba4e9ebb8638f6c589c28a26e97eba449", - "0x89d52a23d929fdcaa0981585bd3aea0cb91f19051a385aaddf3f095b29180909", - "0xbbd2abaf78600c7dd9735a1b18041504ec4993e921d2a5c451651886bb2bbefa", - "0x1b4f167db10e84ef494bfdf276d94b7b223049929ac9201b061867f2710ca393", - "0x57c064b01e0e6fa86652b6030115c0ae8c780969862715b3bc5b85076373ab19", - "0x6b639cee228c61814e87bc0e657316d3b812c268d3f834dec0374f34470a2eef", - "0x0bb282e79bde8786721e4703701039ac03e67517aac653a79001782277f9ba42", - "0x0c3f6fb7413af14f268a18baa72a7a6d0aa4ec2e721be6f9ef62cd3ef18344a1", - "0x5c1026b653c950d791056d35e445e51447b9a5e2dfd7a2ab73936a32e4ab6b2d", - "0x4bb93973a1e94b49b8cbcfff71ddfc75950b0fa8ae0cf4fdd29c496ad30debd4", - "0x8ce6f1c4598bed052f02f670df6f76dc86905e5cf00fd708a1f64539afa4ffd1", - "0x7b5b44e163a1590adfea2a1819f3bab8062f5a7beaa77f71b8d91719d2b36c09", - "0x6e54b736fc421b1630ed6647df239676b6bbd846c0af20e55c4c5796117d0a8a", - "0xf9e6f05392de8db30c25574a252cd29f353cf610d8588a397dcdfa804f4d8d59", - "0x7c0773a6772440c23f9662f6389df23909f3d3ab04b9642dbce2f5b6f3a933f8", - "0xbf35c44d316fbc9dec641d80c6f61ec02bef2c527259fdbc5d13a484ece42f04", - "0x86fbd375a65c3685bc2eddae081a6f6341efda2271d53c4c3f6ff55b3321d40c", - "0x4dd05c8f673952262c4bf166ece8f2514c1660c100980743a77480b201c611c1", - "0xcb8aa3265b20caaebf7da5b2b82e630acc1b0ec0f518bc7c282868afba2c6c45", - "0x68ce4c9180dcc3e43a3423e3161e60c888c153280775f731a9b8a5ff08bfa9ee", - "0x20c34ea0cc5f197bc41be61488d8252fa0da5eb232b29857e8c87ddcb3d4b9ce", - "0x7f75dea50aa195aacaff8c46fbc7be919745df4d1df89182cbd5b4829f44efc6", - "0x11326e5b267524322fe8349a89be7c392ec6f6bd609896473f39288228a08955", - "0xb7a381d92b0b7a8771b38c86db330563bdc0bafb5ebae13ae90fe61232635e37", - "0xed5b222c9e1573f7cf6519d39adc20f2c6dbc445872a77d33e6c5d843df49550", - "0xe023032ef88376065b82528efb30e5b7d09ff4614783e473c8299009bc7f7aa2", - "0xf46ac518cf304c045f46113e92a96a0b9d9c387397bbe35b53b01dd337942afd", - "0x463a1fedb3d950152c1f2218d5a4fcd12efb46cd081ee69a78887fd21efd996b", - "0x4915cb1006265c1f2b0c4add8c83af4a8629e8c96c0be5fa866197c329ef53ce", - "0x4f1c6c6b89bdd1c107210d70201757e78732aeb880d5f79d0b005c1308b5d30a", - "0x87bf0647b3456cd3d1b462c13145644961fe056d6de22f136f2d1d57843b32cb", - "0xdc9e62aef0e9058ba1f7f44b238ea1c29aacc93934d3adb7a4c32dbcd6fae478", - "0xe13420ef311f969b4b8799ee01495b6744090fd9b8ea88ee81cbf792440a6438", - "0x923080e2d5a2013073468dbc14cb0e498da713fbb2538e26cbc58dbad1a8791d", - "0xbdf1575942567f8c74b09a2ed98c70ffd4cdbe3777721d661966b21aabde521e", - "0x70c13ddd5a83826b356886fa7ac6932589ec3d3a166000ffa78ab0ec4f6627af", - "0x2de43da869aac3dd94e44145fe4c23620a97065383445c8799a2ec66e2a065e8", - "0x02362cca63a0e1a52cd738360d325ec243de7584b4f73703292af3e7af51bb40", - "0x8852045c830e84f8291d76943525be87ed586af5a46d5a5a19370b6b4a0bde34", - "0x1e41c1c77d61fabad29ed2dc33ddc60585ccd5eea3e18282d349aaf4eadee994", - "0xf8547cfa6c30cb0ab0afc2f9624e2280b96b1b6bfed7473f13990d49c2b7ef0b", - "0xbdb5166c5f48d0261d8ca818dda1083a329ddf8d5dc6160935a08631eca59767", - "0x8f5441083b634b29e028c91af613c49da746c02eed369f5cf42b132b4f74b1f7" - ], - "transactionsRoot": "0x32472cf39ec0cb26f701200855d70dc9dffae5ff63b53582581861d62ece557b", - "uncles": [], - "withdrawals": [ - { - "index": "0x17038c6", - "validatorIndex": "0x8973f", - "address": "0x8306300ffd616049fd7e4b0354a64da835c1a81c", - "amount": "0x1055f78" - }, - { - "index": "0x17038c7", - "validatorIndex": "0x89740", - "address": "0x562ab7dca86f8947f5b066a663d83452954a71a2", - "amount": "0x104de81" - }, - { - "index": "0x17038c8", - "validatorIndex": "0x89741", - "address": "0x562ab7dca86f8947f5b066a663d83452954a71a2", - "amount": "0x37c9f7f" - }, - { - "index": "0x17038c9", - "validatorIndex": "0x89742", - "address": "0x562ab7dca86f8947f5b066a663d83452954a71a2", - "amount": "0x104add8" - }, - { - "index": "0x17038ca", - "validatorIndex": "0x89743", - "address": "0x562ab7dca86f8947f5b066a663d83452954a71a2", - "amount": "0x1042cc5" - }, - { - "index": "0x17038cb", - "validatorIndex": "0x89744", - "address": "0x562ab7dca86f8947f5b066a663d83452954a71a2", - "amount": "0x104ad77" - }, - { - "index": "0x17038cc", - "validatorIndex": "0x89745", - "address": "0x562ab7dca86f8947f5b066a663d83452954a71a2", - "amount": "0x1043f81" - }, - { - "index": "0x17038cd", - "validatorIndex": "0x89746", - "address": "0x562ab7dca86f8947f5b066a663d83452954a71a2", - "amount": "0x3802e76" - }, - { - "index": "0x17038ce", - "validatorIndex": "0x89747", - "address": "0x562ab7dca86f8947f5b066a663d83452954a71a2", - "amount": "0x1047f46" - }, - { - "index": "0x17038cf", - "validatorIndex": "0x89748", - "address": "0x562ab7dca86f8947f5b066a663d83452954a71a2", - "amount": "0x1039ec3" - }, - { - "index": "0x17038d0", - "validatorIndex": "0x89749", - "address": "0x562ab7dca86f8947f5b066a663d83452954a71a2", - "amount": "0x104c280" - }, - { - "index": "0x17038d1", - "validatorIndex": "0x8974a", - "address": "0x562ab7dca86f8947f5b066a663d83452954a71a2", - "amount": "0x104bf38" - }, - { - "index": "0x17038d2", - "validatorIndex": "0x8974b", - "address": "0x562ab7dca86f8947f5b066a663d83452954a71a2", - "amount": "0x10464c3" - }, - { - "index": "0x17038d3", - "validatorIndex": "0x8974c", - "address": "0x562ab7dca86f8947f5b066a663d83452954a71a2", - "amount": "0x104a2aa" - }, - { - "index": "0x17038d4", - "validatorIndex": "0x8974d", - "address": "0x562ab7dca86f8947f5b066a663d83452954a71a2", - "amount": "0x104a280" - }, - { - "index": "0x17038d5", - "validatorIndex": "0x8974e", - "address": "0x562ab7dca86f8947f5b066a663d83452954a71a2", - "amount": "0x104dd9c" - } - ], - "withdrawalsRoot": "0xc4bffe564e0af459512f9f519a6ea938222764d5ee579946beb307b05c9a47b1" - } -} diff --git a/executionlayer/test-data/call-result-fmt.txt b/executionlayer/test-data/call-result-fmt.txt deleted file mode 100644 index c797535..0000000 --- a/executionlayer/test-data/call-result-fmt.txt +++ /dev/null @@ -1,6 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": %s, - "result": "%s" -} - diff --git a/executionlayer/test-data/log-filter-fmt.txt b/executionlayer/test-data/log-filter-fmt.txt deleted file mode 100644 index b8465e7..0000000 --- a/executionlayer/test-data/log-filter-fmt.txt +++ /dev/null @@ -1,20 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": %s, - "result": [ - { - "address": "0x89f478e6cc24f052103628f36598d4c14da3d287", - "topics": [ - "0xed2d3ca39683fb0f50a70ed75c33a19bfe200e529d99e6f7518453b3fc4e9be4", - "%s" - ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000001", - "blockNumber": "0x11af690", - "transactionHash": "0xaf1efc9b218bb157c5a2c37ee09ae2f09e8fe7633f2133f993d6beefb57c7346", - "transactionIndex": "0x91", - "blockHash": "0x8ec4c967252827760de3aaac166da2dc00faf099c5f20706acb815552a672c47", - "logIndex": "0x130", - "removed": false - } - ] -} \ No newline at end of file diff --git a/executionlayer/test-data/rocket-dao-node-trusted-abi.txt b/executionlayer/test-data/rocket-dao-node-trusted-abi.txt deleted file mode 100644 index bcd42b4..0000000 --- a/executionlayer/test-data/rocket-dao-node-trusted-abi.txt +++ /dev/null @@ -1 +0,0 @@ -0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000414654a7a565745747a6d7a41512f6973647a6a377745415a7963354965306f6b3761644c306b736c6b39466763545547695171544a64504c664b2f794d775462597852353673316d74744e2b336e37517250667978754d674b6e56746e442b565044557267355074624274615a52615851436c5039365662536e36447674465234416c666c6f4268547341615777476b3538456c39484442695445476547374f657a59506e483934664231617573595a786f544868436464767869716b795041624a676d73504d7a4b755659464e524f616a7a6d66434b774c56625738447a35452f37694d356756557a7155776a724c5132374156356e2b3451724261656d6259484f6f4c68392b726b584568714a34744e49304b433232646d64686750574c373155637851387933747755384158307570546134634461574443353558744c4264694d67556961624145792f6478702f374b4d41445148746948384d4b514831725a43715348394944666b742f43713461734a516b753336773231354b453264496b46655a41396a774f7449616d4868705949584168636d4c547431586158694b762b42453936624650714d65683568724146344c52395058444234335a79564b755352336f3232786d7262452b4a417a4242464c6732646f464732463749514462476657716852464e6942477a664850756269794673744b32506148774631674154455063465775386135766c45796b376d5a6c61665172307a366a75393764755366344d693533493363314263754a7075417a793264347662415a63686e7a7646783336756b54384444454e6b7832505434774c39494c6f443155504a524745542b45426f59714b626c4b56747334744a6579394867474251757a6f33507230414c335563795139754c497754653865563065334e394c6755627066307268375a48777945685465314c4a33336278544e4f456843542f7654664158614936376e4243593553515977436745313756327a75574431736a514957526946715046304f5a344d4c71694146305a365431766661447941726f46776265554452305541784f44306f487a453344494e77337a6f77765459316e76343174324c61427879786170446c4a58334b596364736f624a6f526e61544247717763394461664c6d5950395a3878576d7234726c746e6875736e39763431362b4b4c7a67705950506d72784a344e3176726e6e65754f6549526d7848582f55395958433869565171587057514c662b637a377937356f7834777a4a793956616862396d77315233456f3034765879524868683233373551543762503337624b49776736355661324d5759616570706c585559674445584b587a35384b314a3851574770713764597745484e2f324972533366726f3838316467302b553961654d6a79622f44485870443841435a572b376a5837696e2b48453d000000000000000000000000 \ No newline at end of file diff --git a/executionlayer/test-data/rocket-dao-node-trusted-actions-abi.txt b/executionlayer/test-data/rocket-dao-node-trusted-actions-abi.txt deleted file mode 100644 index 10e47b8..0000000 --- a/executionlayer/test-data/rocket-dao-node-trusted-actions-abi.txt +++ /dev/null @@ -1 +0,0 @@ -0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000228654a7a74566b3150776b41512f53746d7a7a325a61417733314a6a347751573945554b47335145334c4c743150366f4e34622b374c615566304a496d5655546a7258526d337237335a72624461455734444a30317044644b486931714365496c447048304346585361714432624b6a6f4175327a5652726d654a386b7a594169435969455a5a493430655745506d4d616a66466875384742374d56364842426a77654c41575a6879775733736f314c4a45474b59436977712f4d6e47616b633949466b484b77492b4b56347135326e4f5142674d7171775a6669416a50562b5252696f69494765546b5a574b34633072434946796a71795a624e414e2b5259705a366a62346565694b67644d6c524946756e4755567044536341735935332b6658317757534a59765331357677306c33736f512b74567a4a48536d734b4d4549706632746a656e556b733565446f44684d597a3855704536464e644b7372376e352b6e75712f302b337834556c38655a7644396932434f6e69332b375774763168424456337366636e614969516d3138696339577a6a62747a4f536b71344a476c634a567777714d4f4c34586d544d6e5532354e5043432f47525571725a6472506636656b7232325477373376593767454e3863312b6b4650696252624a36473642454f4c4a4a64787474684f42315067397235716778364976624f66795150446a75557677326e49322b5859476c4c4c6c6f303473647062763459646256302f416c4462766773000000000000000000000000000000000000000000000000 \ No newline at end of file diff --git a/executionlayer/test-data/rocket-minipool-manager-abi.txt b/executionlayer/test-data/rocket-minipool-manager-abi.txt deleted file mode 100644 index 183457d..0000000 --- a/executionlayer/test-data/rocket-minipool-manager-abi.txt +++ /dev/null @@ -1 +0,0 @@ -0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000072c654a7a7457746c75327a67552f5a58437a336b674a61353953376f41413077485154506f5042524263556c65706b4a6c796444693146503033346665346c32535939645267586b4a456c47383544303650486468507638594a4e6d6f7273724236382f54587973734d6b6a2f6e6f78773848706738367771774661765075623247315a3356563741412f347866636d447863485649495068394d557678666f4c31383456574a5a68754a7262676357446e2f645867374b43436a2f55465a676b5461704a474d3379624151544d436d755a6f53567936716f625441594870624a51775a56585779502f4c7a364d59417766544c4d362b4341683754457130312f484835484e3367645a7378474e747944703330753342676d57544c4b383354507a712f576a44327473324774446e3948584b797356636c777a61506c38425344785173332b4a426b4e336e6d5071494c44695635746e6f64783568565736365437326741705653414668516e316f4e6a5552784678456b74466463712f4544504e6546474357575a6970444568464c44593057556b4f716c454f746f44496347697775422f775979692b6b5438702f794b6c687667563847784c32796a714f69504c61526b426f6f4a555178796867486f33556b304773566f50614d476964516741654e7353656143495839686a2f4c485634492f412b4c666238704544726754705268526c4d5477505142593871343563354a4a466f356235324a49362f434361444d55733673344e52476b6974677a4570415150592f376c7534763857676f666d6b48666d5961476b4e3854775731696c4661564157796a546c54675a6c635a46487a5a566d6363796c645a476867657a55475641675a497857364e39646f702f3059533458615474696e6b644d2b526a513030696a386b457355487353784d425a77696c4b3047485543595a555569426776596d3169485151374e6a59694333384f425353647744354d7158503459683774574e68423451764a61525649777032646b772f724b44503636706a7a724363744364704f43552f38485732444a69623846764259686b5a3078385964366561535958725334386854527945684f61324e74397773724978663748543469616b456466686a47586474724272774e5a4645666838412b6d553652336f38416b735a4e5676515171674954704964573553724242784744415a42766a2b436850754b7669575a4139504d573778555662776e4f365135495a724b2f326d512b76376d656e37336f397a6874574e59515467344f6f50574632486d574d38444545374966637a6348666e34775166392b3535566967456869346a79355a4f6f44596865334d4e5072785077766153456c3250335841555156676b4c637a655065367a534e6c3479683957352f5336786575646739503146442f54612f5265786c543746712b336458613058313733756e777a65524c6a486a6c757651416d52647a41326836544e5754696a68683052354d3139373745357761324e426b6d7a656e4e4e6d71335741514e722b7279535069436a53715a5338616235306669555945704245692f6e6d436a6e496567457977384a7456585638446a4e435363594d596c5a5a6d6e34384f416e4a4e667365466f306350525958355a5648514a3957746b6559756a764579716d646c47706d4257443139747a547030367452354d654549464152476c384e6b56566b3249474c6d57636b4f4172506e3578556479615657386d4b6b655063394b6173573562696339344949593053456c2f4b2b5339446343737637347648354a41454e6a7a6e71532f6e2f386662507578544b722f3368503745524e51726b705244345a786b347073303946307169454248626a734e7a434447614f6e6738484a453155746a34654469366c6f4942696d6b52325073436943724730424c367134483448616f6f6f78516f3763394e6969375a616163366249706a583273785a574a43534853387768374a6f78375468317255614f503456305051326c6836635353344e41793965496e757135756e3158664a7638334e797759736279614c325431434e474945545279334e586865564a6f2b7a66765861334432546153304179494a615773596e5878436435486f345348316d6b5243327261627070646f773977756578394c2b466f79783857582b487a66774b51776546364e63783639467279684239642f6f526253574968692f6d763678707458513330544130635a6f56725468672b343655415076782b4e6c49696c504c36753779706f53625a326f395568687a2f3956736b51686b6f4a64526d584c6e424a707a57526b564c32454d2f435a764a78307a5871365673494a52366e514e6f7930364d76537372476a732f702b34367339494b3534305055714d42786b74666c394c2f716e686d6f4d6e773859665a79412b397858787261636633396b31667731364f515a4f4346614579387459704b64596a4759797a4b2b62775766565348315048634c572f6d48584d38704d7a332f77487556496f2b0000000000000000000000000000000000000000 \ No newline at end of file diff --git a/executionlayer/test-data/rocket-node-distributor-factory-abi.txt b/executionlayer/test-data/rocket-node-distributor-factory-abi.txt deleted file mode 100644 index 7382fed..0000000 --- a/executionlayer/test-data/rocket-node-distributor-factory-abi.txt +++ /dev/null @@ -1 +0,0 @@ -0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000017c654a79746b384675677a414d686c396c79706b486d4872626474706830725474566c5756535579465268506b4f4b78523158656667613470677a494f75784838782f2f3347374d2b71744c57676231617264744852724a5166635161315570705a356c413839326230352f49372b774964766a636967725171444a6c5964384b743351746544434730487370633938487a69394f6d30783542736158774a435856636c527174625a47694c6b466159623475795a67706147367051644659676f376c30517a4149716a396d51327541427a5656706b4149754f442b304d416434467232534f38516e517145315359674e577536494c763770536f506b5332644637514c666d6d6d513833316953613337777653496d684b2f6b724949566e4e7264494e6a68397a5250305a4737517a4f412b5769386c4e4166574561714136456677474e6e4d596677677264374c6238537053304d3446474e6b735863636d552f7a475537706172797a55497450676e5359536262784a6a554c673d00000000 \ No newline at end of file diff --git a/executionlayer/test-data/rocket-node-manager-abi.txt b/executionlayer/test-data/rocket-node-manager-abi.txt deleted file mode 100644 index ef0f1bb..0000000 --- a/executionlayer/test-data/rocket-node-manager-abi.txt +++ /dev/null @@ -1 +0,0 @@ -0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000006b4654a7a4e575631766f7a67552f53757250466372417a6247387a62626d64574f31486172704b7439714562567862354f55524f497750526a522f50663136543567414342356d4f58747754624235396a33334f767a6632505552517663704f4e507430585077326d4d637a75336859342b6a5353535778536b4f615863534b663045784d6b734955767857644e456763585978696d42636448394a796838394b705a686c74746d383438447177632f764636504d674d4872334541597a534c7a5a6c766a4a4637414734517a334936776238354d6d6b734c6142396d305451476b3665374c543876666f7a41446e2b624a376b6c6f474757345557566a384a58564b4e5064735379705549504e764e63305967546851327a76696742626435525163727466356635577951547a557449362b61432f3672446a583356474b64525a6e457337715972506d4e73646969545638323546306f675842475867424e4b5279744647594b6e517359394259787970567a75424d5368486d6a51684450707341414379706d674131597152764f537045383978487142564e32383937353868486a614c5275586a4969414b30524a664f3437524644584579693064674d4a494b525630534e4d432b6b7a346a6a5344536751704553476d746f324f527a5a7769535a625747574d625446575462757944575a4a346c356a4f4c707257326346503137616f624b565a3445542f694270304f694751464f554e6e7435486e676946436a5377677956796768304e65634f51466c58756870535647454f4f53743169736f3732796e663549597278494a4a6b7269435a6f7579634b516841355278413939445352774642474549512f7331754d53696563364e69434263754a716a5741336e74592b57705544346e752b34384f4b544a734c317852354b43545a61374b726a6c4d306e352b78734f53433265395973453979302f61696d6d444e597455642f446e436c3231666e636579554d342b575a6f31574d6c57613133566a5470554b556535352b527653582b784a7074475957347a787263344d68484d6f6d775a4233756b714d5a625136696454415468637052496459634974625635534c544f796e747a73305158505162506f6e6e554e4c6169336331575a737a32363755532f2f35376b326a6278704d71357971755852747348315a7536527539794a7465725064775069336a45446854316a57716a477654766b7a797547506d2f33576f657a5a7865494c6a5148663555724662544e666d76794f65544f594c2b7a52756e6e54684c7647306d6d5a57494b62536f382b633557727447715a636632322b4b63304c45746351573664504e2b5835723273325333624e6f576e7978517850487069434d63706f56324165356573463453396f494a72744f74502b78616f614f37376133464236313775393931696d64466d367038734b34613635724b696a7447325564616e525a384e5535362f335a4c634f536a583171316a747939424c6e564b74666c6a73706f7569616e33714a3278744e4e7043792b37565a787a66586830424d342f69614a37506a775342312b4e423044786567354750355a553962507856697966326c474e6874394e6c6930663167416868427248457233642f4844562b6644544137645652342f2b6371594d68464336534c444b58397477666d642f65385135453273627243756576444e4f4474616e44465337624636356d4b432b5265565170764d447349325a5367316c67724b775a2f6e3051327336357558776f48706373664e4a306f4f36685743746737624439456545363032427a4a56424e6953577337634e79316a3931616170446a77584f7558502b31335847487354687a57654b4f747a764f7277647a6671325051594764447a52566779584f64363531526a58693638424858574971774c67336c6c764e5a6f757176617273467437316d764e6b376f4263514a4e58502f6347677738494668496864622b57626643654b6657486c416b5545384867656a4d43662f486f58395354397a4664356942337036453347486777466b76696873553253316c4271514956554a6f337a7472716d6c565a4630744471494541537052433658613767536a7a615641395371384d762f653332564c30397170684b6767415152646a722b626868363637304332564e4a566e4e363866784536356652644c6e314e7661373779592f75707a352b31656344614c62503534396e72356a50744d753746712b3671523836766b4a6d4834716734306b49345159323459546e334947573039367936336757774e48484950546177766b5a3036797a32437432554e446d7863474a617878717933374669455839467873706f4e773d000000000000000000000000 \ No newline at end of file diff --git a/executionlayer/test-data/rocket-smoothing-pool-abi.txt b/executionlayer/test-data/rocket-smoothing-pool-abi.txt deleted file mode 100644 index 0e0b6cf..0000000 --- a/executionlayer/test-data/rocket-smoothing-pool-abi.txt +++ /dev/null @@ -1 +0,0 @@ -0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000019c654a79645572464f777a41512f52586b4f524d53714d7247774d4441416b674d565252646e4574726b646952665536777176343764687669576d6b717842626e337233333774317444307a4933704a682b545a38456d6f4a375966726b65574d4b306b614f4e32394b6636463945354b77773566417167426a69786a45726f414c50556c344b6d754e52726a7933546d67656e48736369594953423874515356614155355835564b39754367616a463265475644326e4a507949375a675945487555355a62374f42316d435775713778473275572b3435544a526e4338776935693134724632576d576c433454514c7a53424d4c7153766a58644c4d4e684d6536392f3344342b52422f78516b694c584c2b412f58435136764d4a557a49426e3271502b464c53764e597779516e4641372b45734f6155616d77625552716941567062574c69574962614b54314d566d5a66474477444569477973354261486759776c656e4968476a6d4c413150624332474a783566726d62716462727134715a6a564f325a364354684c37382b584847496f666468633974513d3d00000000 \ No newline at end of file diff --git a/executionlayer/test-data/rocket-token-reth-abi.txt b/executionlayer/test-data/rocket-token-reth-abi.txt deleted file mode 100644 index cd048ff..0000000 --- a/executionlayer/test-data/rocket-token-reth-abi.txt +++ /dev/null @@ -1 +0,0 @@ -0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000574654a7a7457457476347a59512f6975467a7a6e772f6367746262644144347343326143585252454d795745697243775a45705845574f782f4c79316e562f465464756f6d426c7241674731794f50726d6d7966312b65756b71475a64616965586e78632f457a59566c44667a475534754a37367555674d2b2f5852642b792b595071573667547638665345557765506b596c4c426443463432377755754171687762624e32326d7042353458767631314d576b544a507a594a584246576152353371337161675a7a6343554f4a2f4b54323952305069764d69323178563048716d765764627864664a3543507a3664316c773249554c5a34735770507743634d6b3874386f74395a4d51392b344877326f333673734e6b432b2b4a6f5465304d383445785854384172796a72386e386d31614473416372754254666639786473506b74637a575a4e6e63554749587a414b7131525235364d6c773470536e546f645a41755542453146557748696b5a4548674c68564867537457504d5a6b4848434146767650626357535a507a6e687336756c7053494b4d714c64346e6156583645724664442f66483949394e722f697247364c6c50574f73493552556965383063515a52796768434d69495a6d68707950385a5938414a7a7773517557594770664532694d41436345365a6a763852316a48645837326c45322f714c3169315033644e4e653543617258686a6774704c4658455532574530634872614c326b674d52356a4b6730434232554a695936734351716a345172465869674a33646871763933344e4b42487864365268326f714a512b4270492f4c4365684236317a78624d53736a6364537358525a42386152576b6b41454646444334415245396b45414c59652b546732306243654a653561614271343875757470337245434b5444414a31794c7978546c6e504846456d6571354e4a74564b356b424871726758514b6c694a6b6f4a496b544a636f35686649612f61796f35726d322f746b38505a6b4e5a316f395139664e4f33615664734459493363376c35767a7a554f446a494275377971656972764a4b502b70414a76673545745a5a566777447832505a47686c4e586c6b75587444567a79496a5a4c6d364c726378316138665069612b494775564847496c676e623857484c412b315544743853446733495244582f4538346f485459415978756d7179515073674c3659356c49776a747273776d784f69356854376e5070315738617757336e2b6a734e686a39484331366d7245466f3865717743764276427a55497154304c623874586c6a3245717149364b3671346c63527975544d5a2b712b3943504d6c73366a75746d4638336a6c704b6841564d382b4537384c627a7165755a2b78734546735a636d73584f786c4f64594c79557a65626c66507a7170505535416b2b68484273466a57356673344b334e6f6354744d35307a4266765765567355526137397a525665627749764f4f33503632484c76666b6438383478724e31754e76515071415462733864795a74576f6f59524a436b4237797064635079374577732b6748772b316176646e383462666a3074746c336e527a6f757350304964312f6231426e5647654d34797743477874424e77302f324f357250457644425263716b76574c796172486e767739564864346e52393558754252415443712f4237774e34764f396b74646c766d52445977303554652f46537030306c4b79422f38412f667a6f4e354a52492b4e4f2b7350796e57754f6e32582f474b4276526247764b7138557034335852643461436361634e486b506149533357392f6d44505a506931377a694e6c4839534d72584452456a4d314472793751726d757145304d577a43726c6c546b6f536e5a6b366a39485159314245767a43324c38424e57386562773d3d000000000000000000000000 \ No newline at end of file diff --git a/go.mod b/go.mod index d33f2cf..f66dc5c 100644 --- a/go.mod +++ b/go.mod @@ -11,14 +11,12 @@ require ( github.com/attestantio/go-eth2-client v0.26.0 github.com/ethereum/go-ethereum v1.15.11 github.com/gorilla/mux v1.8.0 - github.com/gorilla/websocket v1.5.3 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.20.0 github.com/rocket-pool/smartnode/bindings v0.0.0-20250523024043-4478efc9d256 github.com/rs/zerolog v1.32.0 go.uber.org/zap v1.25.0 - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa - google.golang.org/grpc v1.64.1 + google.golang.org/grpc v1.65.0 google.golang.org/protobuf v1.34.2 ) @@ -45,8 +43,8 @@ require ( github.com/goccy/go-yaml v1.17.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.17.1 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect github.com/holiman/uint256 v1.3.2 // indirect github.com/huandu/go-clone v1.6.0 // indirect github.com/klauspost/compress v1.17.9 // indirect @@ -82,8 +80,8 @@ require ( golang.org/x/sync v0.14.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 0b66c44..1441904 100644 --- a/go.sum +++ b/go.sum @@ -103,8 +103,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= -github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= +github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -146,8 +146,6 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.17.1 h1:LSsiG61v9IzzxMkqEr6nrix4miJ github.com/grpc-ecosystem/grpc-gateway/v2 v2.17.1/go.mod h1:Hbb13e3/WtqQ8U5hLGkek9gJvBLasHuPFI0UEGfnQ10= github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= @@ -379,17 +377,17 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20210401141331-865547bb08e2/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= -google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= -google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= 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= diff --git a/pb/api.pb.go b/pb/api.pb.go deleted file mode 100644 index 47e27b2..0000000 --- a/pb/api.pb.go +++ /dev/null @@ -1,464 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.6 -// protoc v3.21.12 -// source: api.proto - -package pb - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" - unsafe "unsafe" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type RocketPoolNodesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *RocketPoolNodesRequest) Reset() { - *x = RocketPoolNodesRequest{} - mi := &file_api_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *RocketPoolNodesRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RocketPoolNodesRequest) ProtoMessage() {} - -func (x *RocketPoolNodesRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RocketPoolNodesRequest.ProtoReflect.Descriptor instead. -func (*RocketPoolNodesRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{0} -} - -type RocketPoolNodes struct { - state protoimpl.MessageState `protogen:"open.v1"` - NodeIds [][]byte `protobuf:"bytes,1,rep,name=node_ids,json=nodeIds,proto3" json:"node_ids,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *RocketPoolNodes) Reset() { - *x = RocketPoolNodes{} - mi := &file_api_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *RocketPoolNodes) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RocketPoolNodes) ProtoMessage() {} - -func (x *RocketPoolNodes) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RocketPoolNodes.ProtoReflect.Descriptor instead. -func (*RocketPoolNodes) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{1} -} - -func (x *RocketPoolNodes) GetNodeIds() [][]byte { - if x != nil { - return x.NodeIds - } - return nil -} - -type OdaoNodesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *OdaoNodesRequest) Reset() { - *x = OdaoNodesRequest{} - mi := &file_api_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *OdaoNodesRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*OdaoNodesRequest) ProtoMessage() {} - -func (x *OdaoNodesRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[2] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use OdaoNodesRequest.ProtoReflect.Descriptor instead. -func (*OdaoNodesRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{2} -} - -type OdaoNodes struct { - state protoimpl.MessageState `protogen:"open.v1"` - NodeIds [][]byte `protobuf:"bytes,1,rep,name=node_ids,json=nodeIds,proto3" json:"node_ids,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *OdaoNodes) Reset() { - *x = OdaoNodes{} - mi := &file_api_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *OdaoNodes) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*OdaoNodes) ProtoMessage() {} - -func (x *OdaoNodes) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[3] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use OdaoNodes.ProtoReflect.Descriptor instead. -func (*OdaoNodes) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{3} -} - -func (x *OdaoNodes) GetNodeIds() [][]byte { - if x != nil { - return x.NodeIds - } - return nil -} - -type SoloValidatorsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *SoloValidatorsRequest) Reset() { - *x = SoloValidatorsRequest{} - mi := &file_api_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *SoloValidatorsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SoloValidatorsRequest) ProtoMessage() {} - -func (x *SoloValidatorsRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[4] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SoloValidatorsRequest.ProtoReflect.Descriptor instead. -func (*SoloValidatorsRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{4} -} - -type SoloValidators struct { - state protoimpl.MessageState `protogen:"open.v1"` - WithdrawalAddresses [][]byte `protobuf:"bytes,1,rep,name=withdrawal_addresses,json=withdrawalAddresses,proto3" json:"withdrawal_addresses,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *SoloValidators) Reset() { - *x = SoloValidators{} - mi := &file_api_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *SoloValidators) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SoloValidators) ProtoMessage() {} - -func (x *SoloValidators) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[5] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SoloValidators.ProtoReflect.Descriptor instead. -func (*SoloValidators) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{5} -} - -func (x *SoloValidators) GetWithdrawalAddresses() [][]byte { - if x != nil { - return x.WithdrawalAddresses - } - return nil -} - -type ValidateEIP1271Request struct { - state protoimpl.MessageState `protogen:"open.v1"` - DataHash []byte `protobuf:"bytes,1,opt,name=data_hash,json=dataHash,proto3" json:"data_hash,omitempty"` - Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` - Address []byte `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ValidateEIP1271Request) Reset() { - *x = ValidateEIP1271Request{} - mi := &file_api_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ValidateEIP1271Request) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ValidateEIP1271Request) ProtoMessage() {} - -func (x *ValidateEIP1271Request) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[6] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ValidateEIP1271Request.ProtoReflect.Descriptor instead. -func (*ValidateEIP1271Request) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{6} -} - -func (x *ValidateEIP1271Request) GetDataHash() []byte { - if x != nil { - return x.DataHash - } - return nil -} - -func (x *ValidateEIP1271Request) GetSignature() []byte { - if x != nil { - return x.Signature - } - return nil -} - -func (x *ValidateEIP1271Request) GetAddress() []byte { - if x != nil { - return x.Address - } - return nil -} - -type ValidateEIP1271Response struct { - state protoimpl.MessageState `protogen:"open.v1"` - Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"` - Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ValidateEIP1271Response) Reset() { - *x = ValidateEIP1271Response{} - mi := &file_api_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ValidateEIP1271Response) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ValidateEIP1271Response) ProtoMessage() {} - -func (x *ValidateEIP1271Response) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[7] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ValidateEIP1271Response.ProtoReflect.Descriptor instead. -func (*ValidateEIP1271Response) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{7} -} - -func (x *ValidateEIP1271Response) GetValid() bool { - if x != nil { - return x.Valid - } - return false -} - -func (x *ValidateEIP1271Response) GetError() string { - if x != nil { - return x.Error - } - return "" -} - -var File_api_proto protoreflect.FileDescriptor - -const file_api_proto_rawDesc = "" + - "\n" + - "\tapi.proto\x12\x02pb\"\x18\n" + - "\x16RocketPoolNodesRequest\",\n" + - "\x0fRocketPoolNodes\x12\x19\n" + - "\bnode_ids\x18\x01 \x03(\fR\anodeIds\"\x12\n" + - "\x10OdaoNodesRequest\"&\n" + - "\tOdaoNodes\x12\x19\n" + - "\bnode_ids\x18\x01 \x03(\fR\anodeIds\"\x17\n" + - "\x15SoloValidatorsRequest\"C\n" + - "\x0eSoloValidators\x121\n" + - "\x14withdrawal_addresses\x18\x01 \x03(\fR\x13withdrawalAddresses\"m\n" + - "\x16ValidateEIP1271Request\x12\x1b\n" + - "\tdata_hash\x18\x01 \x01(\fR\bdataHash\x12\x1c\n" + - "\tsignature\x18\x02 \x01(\fR\tsignature\x12\x18\n" + - "\aaddress\x18\x03 \x01(\fR\aaddress\"E\n" + - "\x17ValidateEIP1271Response\x12\x14\n" + - "\x05valid\x18\x01 \x01(\bR\x05valid\x12\x14\n" + - "\x05error\x18\x02 \x01(\tR\x05error2\x99\x02\n" + - "\x03Api\x12G\n" + - "\x12GetRocketPoolNodes\x12\x1a.pb.RocketPoolNodesRequest\x1a\x13.pb.RocketPoolNodes\"\x00\x125\n" + - "\fGetOdaoNodes\x12\x14.pb.OdaoNodesRequest\x1a\r.pb.OdaoNodes\"\x00\x12D\n" + - "\x11GetSoloValidators\x12\x19.pb.SoloValidatorsRequest\x1a\x12.pb.SoloValidators\"\x00\x12L\n" + - "\x0fValidateEIP1271\x12\x1a.pb.ValidateEIP1271Request\x1a\x1b.pb.ValidateEIP1271Response\"\x00B\x06Z\x04./pbb\x06proto3" - -var ( - file_api_proto_rawDescOnce sync.Once - file_api_proto_rawDescData []byte -) - -func file_api_proto_rawDescGZIP() []byte { - file_api_proto_rawDescOnce.Do(func() { - file_api_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_proto_rawDesc), len(file_api_proto_rawDesc))) - }) - return file_api_proto_rawDescData -} - -var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 8) -var file_api_proto_goTypes = []any{ - (*RocketPoolNodesRequest)(nil), // 0: pb.RocketPoolNodesRequest - (*RocketPoolNodes)(nil), // 1: pb.RocketPoolNodes - (*OdaoNodesRequest)(nil), // 2: pb.OdaoNodesRequest - (*OdaoNodes)(nil), // 3: pb.OdaoNodes - (*SoloValidatorsRequest)(nil), // 4: pb.SoloValidatorsRequest - (*SoloValidators)(nil), // 5: pb.SoloValidators - (*ValidateEIP1271Request)(nil), // 6: pb.ValidateEIP1271Request - (*ValidateEIP1271Response)(nil), // 7: pb.ValidateEIP1271Response -} -var file_api_proto_depIdxs = []int32{ - 0, // 0: pb.Api.GetRocketPoolNodes:input_type -> pb.RocketPoolNodesRequest - 2, // 1: pb.Api.GetOdaoNodes:input_type -> pb.OdaoNodesRequest - 4, // 2: pb.Api.GetSoloValidators:input_type -> pb.SoloValidatorsRequest - 6, // 3: pb.Api.ValidateEIP1271:input_type -> pb.ValidateEIP1271Request - 1, // 4: pb.Api.GetRocketPoolNodes:output_type -> pb.RocketPoolNodes - 3, // 5: pb.Api.GetOdaoNodes:output_type -> pb.OdaoNodes - 5, // 6: pb.Api.GetSoloValidators:output_type -> pb.SoloValidators - 7, // 7: pb.Api.ValidateEIP1271:output_type -> pb.ValidateEIP1271Response - 4, // [4:8] is the sub-list for method output_type - 0, // [0:4] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name -} - -func init() { file_api_proto_init() } -func file_api_proto_init() { - if File_api_proto != nil { - return - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_rawDesc), len(file_api_proto_rawDesc)), - NumEnums: 0, - NumMessages: 8, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_api_proto_goTypes, - DependencyIndexes: file_api_proto_depIdxs, - MessageInfos: file_api_proto_msgTypes, - }.Build() - File_api_proto = out.File - file_api_proto_goTypes = nil - file_api_proto_depIdxs = nil -} diff --git a/pb/api_grpc.pb.go b/pb/api_grpc.pb.go deleted file mode 100644 index ecde5f1..0000000 --- a/pb/api_grpc.pb.go +++ /dev/null @@ -1,235 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.5.1 -// - protoc v3.21.12 -// source: api.proto - -package pb - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.64.0 or later. -const _ = grpc.SupportPackageIsVersion9 - -const ( - Api_GetRocketPoolNodes_FullMethodName = "/pb.Api/GetRocketPoolNodes" - Api_GetOdaoNodes_FullMethodName = "/pb.Api/GetOdaoNodes" - Api_GetSoloValidators_FullMethodName = "/pb.Api/GetSoloValidators" - Api_ValidateEIP1271_FullMethodName = "/pb.Api/ValidateEIP1271" -) - -// ApiClient is the client API for Api service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type ApiClient interface { - GetRocketPoolNodes(ctx context.Context, in *RocketPoolNodesRequest, opts ...grpc.CallOption) (*RocketPoolNodes, error) - GetOdaoNodes(ctx context.Context, in *OdaoNodesRequest, opts ...grpc.CallOption) (*OdaoNodes, error) - GetSoloValidators(ctx context.Context, in *SoloValidatorsRequest, opts ...grpc.CallOption) (*SoloValidators, error) - ValidateEIP1271(ctx context.Context, in *ValidateEIP1271Request, opts ...grpc.CallOption) (*ValidateEIP1271Response, error) -} - -type apiClient struct { - cc grpc.ClientConnInterface -} - -func NewApiClient(cc grpc.ClientConnInterface) ApiClient { - return &apiClient{cc} -} - -func (c *apiClient) GetRocketPoolNodes(ctx context.Context, in *RocketPoolNodesRequest, opts ...grpc.CallOption) (*RocketPoolNodes, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(RocketPoolNodes) - err := c.cc.Invoke(ctx, Api_GetRocketPoolNodes_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *apiClient) GetOdaoNodes(ctx context.Context, in *OdaoNodesRequest, opts ...grpc.CallOption) (*OdaoNodes, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(OdaoNodes) - err := c.cc.Invoke(ctx, Api_GetOdaoNodes_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *apiClient) GetSoloValidators(ctx context.Context, in *SoloValidatorsRequest, opts ...grpc.CallOption) (*SoloValidators, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(SoloValidators) - err := c.cc.Invoke(ctx, Api_GetSoloValidators_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *apiClient) ValidateEIP1271(ctx context.Context, in *ValidateEIP1271Request, opts ...grpc.CallOption) (*ValidateEIP1271Response, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(ValidateEIP1271Response) - err := c.cc.Invoke(ctx, Api_ValidateEIP1271_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -// ApiServer is the server API for Api service. -// All implementations must embed UnimplementedApiServer -// for forward compatibility. -type ApiServer interface { - GetRocketPoolNodes(context.Context, *RocketPoolNodesRequest) (*RocketPoolNodes, error) - GetOdaoNodes(context.Context, *OdaoNodesRequest) (*OdaoNodes, error) - GetSoloValidators(context.Context, *SoloValidatorsRequest) (*SoloValidators, error) - ValidateEIP1271(context.Context, *ValidateEIP1271Request) (*ValidateEIP1271Response, error) - mustEmbedUnimplementedApiServer() -} - -// UnimplementedApiServer must be embedded to have -// forward compatible implementations. -// -// NOTE: this should be embedded by value instead of pointer to avoid a nil -// pointer dereference when methods are called. -type UnimplementedApiServer struct{} - -func (UnimplementedApiServer) GetRocketPoolNodes(context.Context, *RocketPoolNodesRequest) (*RocketPoolNodes, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetRocketPoolNodes not implemented") -} -func (UnimplementedApiServer) GetOdaoNodes(context.Context, *OdaoNodesRequest) (*OdaoNodes, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetOdaoNodes not implemented") -} -func (UnimplementedApiServer) GetSoloValidators(context.Context, *SoloValidatorsRequest) (*SoloValidators, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetSoloValidators not implemented") -} -func (UnimplementedApiServer) ValidateEIP1271(context.Context, *ValidateEIP1271Request) (*ValidateEIP1271Response, error) { - return nil, status.Errorf(codes.Unimplemented, "method ValidateEIP1271 not implemented") -} -func (UnimplementedApiServer) mustEmbedUnimplementedApiServer() {} -func (UnimplementedApiServer) testEmbeddedByValue() {} - -// UnsafeApiServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to ApiServer will -// result in compilation errors. -type UnsafeApiServer interface { - mustEmbedUnimplementedApiServer() -} - -func RegisterApiServer(s grpc.ServiceRegistrar, srv ApiServer) { - // If the following call pancis, it indicates UnimplementedApiServer was - // embedded by pointer and is nil. This will cause panics if an - // unimplemented method is ever invoked, so we test this at initialization - // time to prevent it from happening at runtime later due to I/O. - if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { - t.testEmbeddedByValue() - } - s.RegisterService(&Api_ServiceDesc, srv) -} - -func _Api_GetRocketPoolNodes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(RocketPoolNodesRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ApiServer).GetRocketPoolNodes(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Api_GetRocketPoolNodes_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ApiServer).GetRocketPoolNodes(ctx, req.(*RocketPoolNodesRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Api_GetOdaoNodes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(OdaoNodesRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ApiServer).GetOdaoNodes(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Api_GetOdaoNodes_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ApiServer).GetOdaoNodes(ctx, req.(*OdaoNodesRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Api_GetSoloValidators_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(SoloValidatorsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ApiServer).GetSoloValidators(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Api_GetSoloValidators_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ApiServer).GetSoloValidators(ctx, req.(*SoloValidatorsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Api_ValidateEIP1271_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ValidateEIP1271Request) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ApiServer).ValidateEIP1271(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Api_ValidateEIP1271_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ApiServer).ValidateEIP1271(ctx, req.(*ValidateEIP1271Request)) - } - return interceptor(ctx, in, info, handler) -} - -// Api_ServiceDesc is the grpc.ServiceDesc for Api service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var Api_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "pb.Api", - HandlerType: (*ApiServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "GetRocketPoolNodes", - Handler: _Api_GetRocketPoolNodes_Handler, - }, - { - MethodName: "GetOdaoNodes", - Handler: _Api_GetOdaoNodes_Handler, - }, - { - MethodName: "GetSoloValidators", - Handler: _Api_GetSoloValidators_Handler, - }, - { - MethodName: "ValidateEIP1271", - Handler: _Api_ValidateEIP1271_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "api.proto", -} diff --git a/router/router.go b/router/router.go index ba393f2..3934ece 100644 --- a/router/router.go +++ b/router/router.go @@ -490,6 +490,9 @@ func (pr *ProxyRouter) Init() { GRPCAuthenticator: pr.grpcAuthenticate, PrepareBeaconProposerGuard: pr.prepareBeaconProposerGuard, RegisterValidatorGuard: pr.registerValidatorGuard, + // A single validator consumes 425 bytes of prepare_beacon_proposer, which is the largest request body size. + // We'll set the max to 1MB, which is enough for over 2000 validators in one request. + MaxRequestBodySize: 1024 * 1024, } pr.gbp.TLS.CertFile = pr.TLSCertFile pr.gbp.TLS.KeyFile = pr.TLSKeyFile diff --git a/service.go b/service.go index 8939817..7b7294d 100644 --- a/service.go +++ b/service.go @@ -13,7 +13,10 @@ import ( "github.com/Rocket-Rescue-Node/rescue-proxy/config" "github.com/Rocket-Rescue-Node/rescue-proxy/consensuslayer" "github.com/Rocket-Rescue-Node/rescue-proxy/executionlayer" + "github.com/Rocket-Rescue-Node/rescue-proxy/executionlayer/dataprovider" "github.com/Rocket-Rescue-Node/rescue-proxy/router" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" "go.uber.org/zap" ) @@ -86,12 +89,34 @@ func (s *Service) run(ctx context.Context, errs chan error) { }() } + // Create the execution client + executionClient, err := ethclient.Dial(s.Config.ExecutionURL.String()) + if err != nil { + s.errs <- fmt.Errorf("unable to init execution client: %w", err) + return + } + + // Parse rocketstorage address + if !common.IsHexAddress(s.Config.RocketStorageAddr) { + s.errs <- fmt.Errorf("invalid rocket storage address: %s", s.Config.RocketStorageAddr) + return + } + rocketStorageAddr := common.HexToAddress(s.Config.RocketStorageAddr) + + // Create a multicaller + mc, err := dataprovider.NewMulticall(s.ctx, executionClient, rocketStorageAddr, config.Multicall3Address) + if err != nil { + s.errs <- fmt.Errorf("unable to init multicall: %w", err) + return + } + mc.SWVaultsRegistryAddr = s.Config.SWVaultsRegistryAddr + // Connect to and initialize the execution layer el := &executionlayer.CachingExecutionLayer{ - ECURL: s.Config.ExecutionURL, - RocketStorageAddr: s.Config.RocketStorageAddr, - Logger: s.Logger, - SWVaultsRegistryAddr: s.Config.SWVaultsRegistryAddr, + DataProvider: mc, + Logger: s.Logger, + RefreshInterval: s.Config.ExecutionRefreshInterval, + Context: s.ctx, } s.el = el // Init() blocks until the cache is warmed up. This is good, we don't want to @@ -100,13 +125,6 @@ func (s *Service) run(ctx context.Context, errs chan error) { s.errs <- fmt.Errorf("unable to init Execution Layer client: %w", err) return } - // After Init() we still have to call Start() to subscribe to new blocks - go func() { - s.Logger.Info("Starting EL monitor") - if err := el.Start(); err != nil { - s.errs <- fmt.Errorf("EL error: %w", err) - } - }() // Connect to and initialize the consensus layer cl := consensuslayer.NewCachingConsensusLayer(s.Config.BeaconURL, s.Logger, s.Config.ForceBNJSON) @@ -116,11 +134,6 @@ func (s *Service) run(ctx context.Context, errs chan error) { // On Init() it will create the client and warm the validator key cache, which // is needed to serve responses to rescue-api if err := cl.Init(s.ctx); err != nil { - // Optimization: serialize the EL cache by stopping it now so we can recover - // faster. - el.Stop() - // Only write the error to the channel after so we don't panic while writing - // the cache to disk s.errs <- fmt.Errorf("unable to init Consensus Layer client: %w", err) return } @@ -186,11 +199,6 @@ func (s *Service) run(ctx context.Context, errs chan error) { } s.Logger.Info("Stopped internal API") - // Disconnect from the execution client as soon as feasible after shutting down http - // handlers so that we can serialize the cache - el.Stop() - s.Logger.Info("Stopped executionlayer") - // Disconnect from the consensus client cl.Deinit() s.Logger.Info("Stopped consensuslayer")