From e17f5fe2503b9e823abbd4f4341bb423dd1356b9 Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Fri, 10 Nov 2023 22:29:01 -0500 Subject: [PATCH 1/7] Add EL tests. --- executionlayer/execution-layer.go | 53 +- executionlayer/execution-layer_test.go | 915 ++++++++++++++++++ executionlayer/test-data/block-by-number.txt | 368 +++++++ executionlayer/test-data/call-result-fmt.txt | 6 + .../test-data/rocket-dao-node-trusted-abi.txt | 1 + .../rocket-dao-node-trusted-actions-abi.txt | 1 + .../test-data/rocket-minipool-manager-abi.txt | 1 + .../rocket-node-distributor-factory-abi.txt | 1 + .../test-data/rocket-node-manager-abi.txt | 1 + .../test-data/rocket-smoothing-pool-abi.txt | 1 + .../test-data/rocket-token-reth-abi.txt | 1 + main.go | 4 +- metrics/epoch.go | 8 + metrics/metrics.go | 39 +- 14 files changed, 1377 insertions(+), 23 deletions(-) create mode 100644 executionlayer/execution-layer_test.go create mode 100644 executionlayer/test-data/block-by-number.txt create mode 100644 executionlayer/test-data/call-result-fmt.txt create mode 100644 executionlayer/test-data/rocket-dao-node-trusted-abi.txt create mode 100644 executionlayer/test-data/rocket-dao-node-trusted-actions-abi.txt create mode 100644 executionlayer/test-data/rocket-minipool-manager-abi.txt create mode 100644 executionlayer/test-data/rocket-node-distributor-factory-abi.txt create mode 100644 executionlayer/test-data/rocket-node-manager-abi.txt create mode 100644 executionlayer/test-data/rocket-smoothing-pool-abi.txt create mode 100644 executionlayer/test-data/rocket-token-reth-abi.txt diff --git a/executionlayer/execution-layer.go b/executionlayer/execution-layer.go index 6cd9174..5d303b4 100644 --- a/executionlayer/execution-layer.go +++ b/executionlayer/execution-layer.go @@ -21,6 +21,7 @@ import ( "github.com/rocket-pool/rocketpool-go/rocketpool" rptypes "github.com/rocket-pool/rocketpool-go/types" "go.uber.org/zap" + "golang.org/x/sync/errgroup" ) type ForEachNodeClosure func(common.Address) bool @@ -98,6 +99,8 @@ type CachingExecutionLayer struct { shutdown func() m *metrics.MetricsRegistry + + connected chan bool } func (e *CachingExecutionLayer) setECShutdownCb(cb func()) { @@ -441,6 +444,7 @@ func (e *CachingExecutionLayer) ecEventsConnect(opts *bind.CallOpts) error { newHeadSub.Unsubscribe() }) + e.connected <- true { var noMoreEvents bool var noMoreHeaders bool @@ -494,6 +498,7 @@ func (e *CachingExecutionLayer) Init() error { var err error e.m = metrics.NewMetricsRegistry("execution_layer") + e.connected = make(chan bool, 1) // Pick a cache if e.CachePath == "" { @@ -581,61 +586,72 @@ func (e *CachingExecutionLayer) Init() error { e.Logger.Info("Warming up the cache") // Get all nodes at the given block - nodes, err := node.GetNodes(e.rp, opts) + nodes, err := node.GetNodeAddresses(e.rp, opts) if err != nil { return err } e.Logger.Info("Found nodes to preload", zap.Int("count", len(nodes)), zap.Int64("block", opts.BlockNumber.Int64())) minipoolCount := 0 - for _, n := range nodes { + for _, addr := range nodes { // Allocate a pointer for this node nodeInfo := &nodeInfo{} // Determine their smoothing pool status - nodeInfo.inSmoothingPool, err = node.GetSmoothingPoolRegistrationState(e.rp, n.Address, opts) + nodeInfo.inSmoothingPool, err = node.GetSmoothingPoolRegistrationState(e.rp, addr, opts) if err != nil { return err } // Get their fee distributor address - nodeInfo.feeDistributor, err = node.GetDistributorAddress(e.rp, n.Address, opts) + nodeInfo.feeDistributor, err = node.GetDistributorAddress(e.rp, addr, opts) if err != nil { return err } // Store the smoothing pool state / fee distributor in the node index - err = e.cache.addNodeInfo(n.Address, nodeInfo) + err = e.cache.addNodeInfo(addr, nodeInfo) if err != nil { return err } // Also grab their minipools - minipools, err := minipool.GetNodeMinipools(e.rp, n.Address, opts) + minipoolAddresses, err := minipool.GetNodeMinipoolAddresses(e.rp, addr, opts) if err != nil { return err } - minipoolCount += len(minipools) - for _, minipool := range minipools { - err = e.cache.addMinipoolNode(minipool.Pubkey, n.Address) - if err != nil { - return err - } + minipoolCount += len(minipoolAddresses) + var wg errgroup.Group + wg.SetLimit(64) + for _, m := range minipoolAddresses { + m := m + wg.Go(func() error { + pubkey, err := minipool.GetMinipoolPubkey(e.rp, m, opts) + if err != nil { + return err + } + err = e.cache.addMinipoolNode(pubkey, addr) + if err != nil { + return err + } + return nil + }) + } + err = wg.Wait() + if err != nil { + return err } } // Get all odao nodes at the given block - odaoNodes, err := trustednode.GetMembers(e.rp, opts) + odaoNodes, err := trustednode.GetMemberAddresses(e.rp, opts) if err != nil { return err } for _, member := range odaoNodes { - if !member.Exists { - continue - } - err = e.cache.addOdaoNode(member.Address) + err = e.cache.addOdaoNode(member) if err != nil { return err } @@ -680,6 +696,7 @@ func (e *CachingExecutionLayer) Stop() { if err != nil { e.Logger.Error("error while stopping the cache", zap.Error(err)) } + close(e.connected) } // ForEachNode calls the provided closure with the address of every rocket pool node the ExecutionLayer has observed @@ -724,7 +741,7 @@ func (e *CachingExecutionLayer) GetRPInfo(pubkey rptypes.ValidatorPubkey) (*RPIn e.Logger.Error("Validator was in the minipool index, but not the node index", zap.String("pubkey", pubkey.String()), zap.String("node", nodeAddr.String())) - return nil, fmt.Errorf("node %x not found in cache despite pubkey %x being present", nodeAddr, pubkey) + return nil, fmt.Errorf("node %s not found in cache despite pubkey %s being present", nodeAddr.String(), pubkey.String()) } if nodeInfo.inSmoothingPool { diff --git a/executionlayer/execution-layer_test.go b/executionlayer/execution-layer_test.go new file mode 100644 index 0000000..1d5eb12 --- /dev/null +++ b/executionlayer/execution-layer_test.go @@ -0,0 +1,915 @@ +package executionlayer + +import ( + "bytes" + _ "embed" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "math/big" + "net/http" + "net/http/httptest" + "net/url" + "os" + "strconv" + "strings" + "testing" + + "github.com/Rocket-Pool-Rescue-Node/rescue-proxy/metrics" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/gorilla/websocket" + rptypes "github.com/rocket-pool/rocketpool-go/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 rocketSmoothingPool = "0x000000000000000000000000d4e96ef8eee8678dbff4d535e033ed1a4f7605b7" +const rocketTokenRETH = "0x000000000000000000000000ae78736cd615f374d3085123a210448e74fc6393" + +//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/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 + 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 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()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(metrics.Deinit) + + out := &elTest{ + t: t, + 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, + } + t.Cleanup(s.Close) + return out +} + +func intToHex(i int) string { + return fmt.Sprintf("0x%064x", i) +} + +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 + minipools int + minipoolMap map[rptypes.ValidatorPubkey]interface{} +} + +type happyEC 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"` +} + +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"` +} + +type call struct { + To common.Address `json:"to"` + From common.Address `json:"from"` + Data string `json:"data"` +} + +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_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.Data) == 0 { + e.t.Fatal("eth call with 0-len data") + } + + switch callMsg.To.String() { + case rocketStorage: + selector := callMsg.Data[:10] + input := callMsg.Data[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, "0x000000000000000000000000029d946f28f93399a5b0d09c879fc8c94e596aeb") + 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.Data[:10] + input := callMsg.Data[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.Data[:10] + input := callMsg.Data[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.Data[:10] + input := callMsg.Data[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) + // Simply left-pad with a char out to the desired length + pubkey := fmt.Sprintf("f0f0%092s", addr.String()[2:]) + 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.Data[:10] + input := callMsg.Data[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) + } + + default: + e.t.Log("Unhandled contract call", callMsg.To) + } + default: + e.t.Log("Unhandled eth rpc", m.Method) + } + + return mt, []byte(resp) +} + +func TestELStartStop(t *testing.T) { + et := setup(t, &happyEC{t, + []*mockNode{ + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), + inSP: true, + minipools: 1, + }, + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000002234567899876543210"), + inSP: false, + minipools: 3, + }, + }, + []*mockNode{ + &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 + + et.ec.Stop() + err := <-errs + if err != nil { + t.Fatal(err) + } +} + +func TestSQLELCache(t *testing.T) { + cachePath, err := os.MkdirTemp("", t.Name()) + if err != nil { + t.Fatal(err) + } + + hec := &happyEC{t, + []*mockNode{ + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), + inSP: true, + minipools: 1, + }, + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000002234567899876543210"), + inSP: false, + minipools: 3, + }, + }, + []*mockNode{ + &mockNode{ + addr: common.HexToAddress("0x0000000000222222222222222222222222222222"), + inSP: false, + minipools: 0, + }, + &mockNode{ + addr: common.HexToAddress("0x01"), + inSP: false, + minipools: 0, + }, + }, + } + et := setup(t, hec) + + et.ec.CachePath = cachePath + + 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 + + addr := common.HexToAddress("0x1234567891232222222212345678912345678900") + addr2 := common.HexToAddress("0x1234567891255555555555545678912345678900") + ni := nodeInfo{true, addr2} + + // Add a node to the cache + err = et.ec.cache.addNodeInfo(addr, &ni) + if err != nil { + t.Fatal(err) + } + + // Restart + et.ec.Stop() + err = <-errs + if err != nil { + t.Fatal(err) + } + + metrics.Deinit() + + et = setup(t, &happyEC{t, + []*mockNode{ + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), + inSP: true, + minipools: 1, + }, + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000002234567899876543210"), + inSP: false, + minipools: 3, + }, + }, + []*mockNode{ + &mockNode{ + addr: common.HexToAddress("0x0000000000222222222222222222222222222222"), + inSP: false, + minipools: 0, + }, + }, + }) + + et.ec.CachePath = cachePath + + 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 + + // Check that not found errors come back ok + _, err = et.ec.cache.getNodeInfo(common.HexToAddress("0x0")) + if err == nil { + t.Fatal("expected error") + } else if !strings.EqualFold(err.Error(), "key not found in cache") { + t.Fatal("unexpected error", err) + } + + // Check that the node was loaded from disk + cachedNi, err := et.ec.cache.getNodeInfo(addr) + if err != nil { + t.Fatal(err) + } + + // Check that foreach node now iterates 3x + nodeCount := 0 + err = et.ec.ForEachNode(func(a common.Address) bool { + nodeCount++ + return true + }) + if err != nil { + t.Fatal(err) + } + + if nodeCount != 3 { + t.Fatalf("Expected 3 nodes in foreach iterator, got: %d", nodeCount) + } + + // Check that foreach odaonode iterates 2x + nodeCount = 0 + err = et.ec.ForEachOdaoNode(func(a common.Address) bool { + nodeCount++ + return true + }) + if err != nil { + t.Fatal(err) + } + + if nodeCount != 2 { + t.Fatalf("Expected 2 nodes in odao foreach iterator, got: %d", nodeCount) + } + + // Remove an odao node + err = et.ec.cache.removeOdaoNode(common.HexToAddress("0x01")) + if err != nil { + t.Fatal(err) + } + nodeCount = 0 + err = et.ec.ForEachOdaoNode(func(a common.Address) bool { + nodeCount++ + return true + }) + if err != nil { + t.Fatal(err) + } + + if nodeCount != 1 { + t.Fatalf("Expected 2 nodes in odao foreach iterator, got: %d", nodeCount) + } + + // Check that you can get the node addr from the pubkeys + found := 0 + for _, n := range hec.nodes { + for pk := range n.minipoolMap { + ri, err := et.ec.GetRPInfo(pk) + if err != nil { + t.Fatal(err) + } + found++ + + if ri.NodeAddress.String() != n.addr.String() { + t.Fatal("Mismatched node addresses") + } + + if n.inSP && ri.ExpectedFeeRecipient.String() != common.HexToAddress(rocketSmoothingPool).String() { + t.Fatal("expected node to have fee recipient set to sp") + } + } + } + + if found == 0 { + t.Fatal("Didn't find any cached data") + } + + if cachedNi.feeDistributor.String() != addr2.String() { + t.Fatalf("unexpected fee recipient from cache: %s expected: %s", cachedNi.feeDistributor.String(), addr2.String()) + } + + et.ec.Stop() + err = <-errs + if err != nil { + t.Fatal(err) + } +} + +func TestELGetRPInfoMissing(t *testing.T) { + et := setup(t, &happyEC{t, + []*mockNode{ + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), + inSP: true, + minipools: 1, + }, + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000002234567899876543210"), + inSP: false, + minipools: 3, + }, + }, + []*mockNode{ + &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 + + rpinfo, err := et.ec.GetRPInfo(rptypes.BytesToValidatorPubkey([]byte{0x01})) + if err != nil { + t.Fatal("unexpected error", err) + } + if rpinfo != nil { + t.Fatal("unexpected rp info", rpinfo) + } + + et.ec.Stop() + err = <-errs + if err != nil { + t.Fatal(err) + } +} + +func TestELGetRETHAddress(t *testing.T) { + et := setup(t, &happyEC{t, + []*mockNode{ + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), + inSP: true, + minipools: 1, + }, + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000002234567899876543210"), + inSP: false, + minipools: 3, + }, + }, + []*mockNode{ + &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 + + reth := et.ec.REthAddress() + if reth == nil { + t.Fatal("expected address") + } + + if !bytes.Equal(reth.Bytes(), common.HexToAddress(rocketTokenRETH).Bytes()) { + 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, + []*mockNode{ + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), + inSP: true, + minipools: 1, + }, + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000002234567899876543210"), + inSP: false, + minipools: 3, + }, + }, + []*mockNode{ + &mockNode{ + addr: common.HexToAddress("0x0000000000222222222222222222222222222222"), + inSP: false, + minipools: 0, + }, + }, + } + et := setup(t, hec) + + 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 + + // Check that foreach node now iterates 3x + nodeCount := 0 + err := et.ec.ForEachNode(func(a common.Address) bool { + nodeCount++ + return true + }) + if err != nil { + t.Fatal(err) + } + + if nodeCount != 2 { + t.Fatalf("Expected 2 nodes in foreach iterator, got: %d", nodeCount) + } + + // Check that foreach odaonode iterates 2x + nodeCount = 0 + err = et.ec.ForEachOdaoNode(func(a common.Address) bool { + nodeCount++ + return true + }) + if err != nil { + t.Fatal(err) + } + + if nodeCount != 1 { + t.Fatalf("Expected 1 nodes in odao foreach iterator, got: %d", nodeCount) + } + + // Remove an odao node + err = et.ec.cache.removeOdaoNode(common.HexToAddress("0x01")) + if err != nil { + t.Fatal(err) + } + nodeCount = 0 + err = et.ec.ForEachOdaoNode(func(a common.Address) bool { + nodeCount++ + return true + }) + if err != nil { + t.Fatal(err) + } + + if nodeCount != 1 { + 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 { + for pk := range n.minipoolMap { + ri, err := et.ec.GetRPInfo(pk) + if err != nil { + t.Fatal(err) + } + found++ + + if ri.NodeAddress.String() != n.addr.String() { + t.Fatal("Mismatched node addresses") + } + + if n.inSP && ri.ExpectedFeeRecipient.String() != common.HexToAddress(rocketSmoothingPool).String() { + t.Fatal("expected node to have fee recipient set to sp") + } + } + } + + // Direct getnodeinfo for missing key returns an error + _, err = et.ec.cache.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) + } +} diff --git a/executionlayer/test-data/block-by-number.txt b/executionlayer/test-data/block-by-number.txt new file mode 100644 index 0000000..e812e5c --- /dev/null +++ b/executionlayer/test-data/block-by-number.txt @@ -0,0 +1,368 @@ +{ + "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 new file mode 100644 index 0000000..c797535 --- /dev/null +++ b/executionlayer/test-data/call-result-fmt.txt @@ -0,0 +1,6 @@ +{ + "jsonrpc": "2.0", + "id": %s, + "result": "%s" +} + diff --git a/executionlayer/test-data/rocket-dao-node-trusted-abi.txt b/executionlayer/test-data/rocket-dao-node-trusted-abi.txt new file mode 100644 index 0000000..bcd42b4 --- /dev/null +++ b/executionlayer/test-data/rocket-dao-node-trusted-abi.txt @@ -0,0 +1 @@ +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 new file mode 100644 index 0000000..10e47b8 --- /dev/null +++ b/executionlayer/test-data/rocket-dao-node-trusted-actions-abi.txt @@ -0,0 +1 @@ +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 new file mode 100644 index 0000000..183457d --- /dev/null +++ b/executionlayer/test-data/rocket-minipool-manager-abi.txt @@ -0,0 +1 @@ +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 new file mode 100644 index 0000000..7382fed --- /dev/null +++ b/executionlayer/test-data/rocket-node-distributor-factory-abi.txt @@ -0,0 +1 @@ +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 new file mode 100644 index 0000000..ef0f1bb --- /dev/null +++ b/executionlayer/test-data/rocket-node-manager-abi.txt @@ -0,0 +1 @@ +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 new file mode 100644 index 0000000..0e0b6cf --- /dev/null +++ b/executionlayer/test-data/rocket-smoothing-pool-abi.txt @@ -0,0 +1 @@ +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 new file mode 100644 index 0000000..cd048ff --- /dev/null +++ b/executionlayer/test-data/rocket-token-reth-abi.txt @@ -0,0 +1 @@ +0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000574654a7a7457457476347a59512f6975467a7a6e772f6367746262644144347343326143585252454d795745697243775a45705845574f782f4c79316e562f465464756f6d426c7241674731794f50726d6d7966312b65756b71475a64616965586e78632f457a59566c44667a475534754a37367555674d2b2f5852642b792b595071573667547638665345557765506b596c4c426443463432377755754171687762624e32326d7042353458767631314d576b544a507a594a584246576152353371337161675a7a6343554f4a2f4b54323952305069764d69323178563048716d765764627864664a3543507a3664316c773249554c5a34735770507743634d6b3874386f74395a4d51392b344877326f333673734e6b432b2b4a6f5465304d383445785854384172796a72386e386d31614473416372754254666639786473506b74637a575a4e6e63554749587a414b7131525235364d6c773470536e546f645a41755542453146557748696b5a4548674c68564867537457504d5a6b4848434146767650626357535a507a6e687336756c7053494b4d714c64346e6156583645724664442f66483949394e722f697247364c6c50574f73493552556965383063515a52796768434d69495a6d68707950385a5938414a7a7773517557594770664532694d41436345365a6a763852316a48645837326c45322f714c3169315033644e4e653543617258686a6774704c4658455532574530634872614c326b674d52356a4b6730434232554a695936734351716a345172465869674a33646871763933344e4b42487864365268326f714a512b4270492f4c4365684236317a78624d53736a6364537358525a42386152576b6b41454646444334415245396b45414c59652b546732306243654a653561614271343875757470337245434b5444414a31794c7978546c6e504846456d6571354e4a74564b356b424871726758514b6c694a6b6f4a496b544a636f35686649612f61796f35726d322f746b38505a6b4e5a316f395139664e4f33615664734459493363376c35767a7a554f446a494275377971656972764a4b502b70414a76673545745a5a566777447832505a47686c4e586c6b75587444567a79496a5a4c6d364c726378316138665069612b494775564847496c676e623857484c412b315544743853446733495244582f4538346f485459415978756d7179515073674c3659356c49776a747273776d784f69356854376e5070315738617757336e2b6a734e686a39484331366d7245466f3865717743764276427a55497154304c623874586c6a3245717149364b3671346c63527975544d5a2b712b3943504d6c73366a75746d4638336a6c704b6841564d382b4537384c627a7165755a2b78734546735a636d73584f786c4f64594c79557a65626c66507a7170505535416b2b68484273466a57356673344b334e6f6354744d35307a4266765765567355526137397a525665627749764f4f33503632484c76666b6438383478724e31754e76515071415462733864795a74576f6f59524a436b4237797064635079374577732b6748772b316176646e383462666a3074746c336e527a6f757350304964312f6231426e5647654d34797743477874424e77302f324f357250457644425263716b76574c796172486e767739564864346e52393558754252415443712f4237774e34764f396b74646c766d52445977303554652f46537030306c4b79422f38412f667a6f4e354a52492b4e4f2b7350796e57754f6e32582f474b4276526247764b7138557034335852643461436361634e486b506149533357392f6d44505a506931377a694e6c4839534d72584452456a4d314472793751726d757145304d577a43726c6c546b6f536e5a6b366a39485159314245767a43324c38424e57386562773d3d000000000000000000000000 \ No newline at end of file diff --git a/main.go b/main.go index 8ee6d3a..20d8c55 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,6 @@ package main import ( "context" - "fmt" "os" "go.uber.org/zap" @@ -29,7 +28,7 @@ func main() { logger := initLogger(config) defer func() { - logger.Debug("Flushing logs") + logger.Info("rescue-proxy shutdown completed, flushing logs") _ = logger.Sync() }() @@ -51,5 +50,4 @@ func main() { logger.Panic("error stopping service", zap.Error(err)) } - fmt.Println("rescue-proxy shutdown completed") } diff --git a/metrics/epoch.go b/metrics/epoch.go index c47b308..07875ba 100644 --- a/metrics/epoch.go +++ b/metrics/epoch.go @@ -142,3 +142,11 @@ func InitEpochMetrics() { registry.GaugeFunc("current_idx", CurrentIdx) registry.GaugeFunc("previous_idx", PreviousIdx) } + +func DeinitEpochMetrics() { + if registry == nil { + return + } + registry.UnregisterAll() + registry = nil +} diff --git a/metrics/metrics.go b/metrics/metrics.go index 2a38283..178a502 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -10,6 +10,8 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" ) +var globalCollectors []prometheus.Collector + // Metrics is a package-level singleton to track the state // of all metrics generated by the process type Metrics struct { @@ -18,7 +20,12 @@ type Metrics struct { var mtx *Metrics -type MetricsMap[M prometheus.Metric, O any] struct { +type registerable interface { + prometheus.Metric + prometheus.Collector +} + +type MetricsMap[M registerable, O any] struct { sync.RWMutex m map[string]M initializor func(O) M @@ -31,6 +38,7 @@ type MetricsRegistry struct { counters MetricsMap[prometheus.Counter, prometheus.CounterOpts] gauges MetricsMap[prometheus.Gauge, prometheus.GaugeOpts] histograms MetricsMap[prometheus.Histogram, prometheus.HistogramOpts] + gaugeFuncs []prometheus.GaugeFunc } // Init intializes the metrics package with the given namespace string. @@ -50,6 +58,11 @@ func Init(namespace string) (http.Handler, error) { } func Deinit() { + DeinitEpochMetrics() + for _, c := range globalCollectors { + prometheus.DefaultRegisterer.Unregister(c) + } + globalCollectors = nil mtx = nil } @@ -70,6 +83,22 @@ func NewMetricsRegistry(subsystem string) *MetricsRegistry { m: make(map[string]prometheus.Histogram), initializor: promauto.NewHistogram, }, + gaugeFuncs: make([]prometheus.GaugeFunc, 0), + } +} + +func (r *MetricsRegistry) UnregisterAll() { + for _, m := range r.counters.m { + prometheus.DefaultRegisterer.Unregister(m) + } + for _, m := range r.gauges.m { + prometheus.DefaultRegisterer.Unregister(m) + } + for _, m := range r.histograms.m { + prometheus.DefaultRegisterer.Unregister(m) + } + for _, m := range r.gaugeFuncs { + prometheus.DefaultRegisterer.Unregister(m) } } @@ -96,6 +125,10 @@ func (m *MetricsMap[T, O]) value(name string, opts O) T { val = m.initializor(opts) m.m[name] = val + if globalCollectors == nil { + globalCollectors = make([]prometheus.Collector, 0, 1) + } + globalCollectors = append(globalCollectors, val) return val } @@ -122,11 +155,13 @@ func (m *MetricsRegistry) Gauge(name string) prometheus.Gauge { } func (m *MetricsRegistry) GaugeFunc(name string, handler func() float64) { - _ = promauto.NewGaugeFunc(prometheus.GaugeOpts{ + gf := promauto.NewGaugeFunc(prometheus.GaugeOpts{ Namespace: mtx.namespace, Subsystem: m.subsystem, Name: name, }, handler) + + m.gaugeFuncs = append(m.gaugeFuncs, gf) } // Histogram creates or fetches a prometheus Histogram from the metrics From 98202374aa777598a48f78c9bd5819d39d1b4182 Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Sat, 11 Nov 2023 14:30:29 -0500 Subject: [PATCH 2/7] Final tests for EC --- executionlayer/execution-layer.go | 8 +- executionlayer/execution-layer_test.go | 526 +++++++++++++++++++- executionlayer/test-data/log-filter-fmt.txt | 20 + 3 files changed, 547 insertions(+), 7 deletions(-) create mode 100644 executionlayer/test-data/log-filter-fmt.txt diff --git a/executionlayer/execution-layer.go b/executionlayer/execution-layer.go index 5d303b4..a0e1473 100644 --- a/executionlayer/execution-layer.go +++ b/executionlayer/execution-layer.go @@ -194,19 +194,19 @@ func (e *CachingExecutionLayer) handleMinipoolEvent(event types.Log) { // Grab its minipool (contract) address and use that to find its public key minipoolAddr := common.BytesToAddress(event.Topics[1].Bytes()) - minipoolDetails, err := minipool.GetMinipoolDetails(e.rp, minipoolAddr, nil) + pubkey, err := minipool.GetMinipoolPubkey(e.rp, minipoolAddr, nil) if err != nil { - e.Logger.Warn("Error fetching minipool details for new minipools", zap.String("minipool", minipoolAddr.String()), zap.Error(err)) + 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(minipoolDetails.Pubkey, nodeAddr) + 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", minipoolDetails.Pubkey.String()), zap.String("node", nodeAddr.String())) + e.Logger.Info("Added new minipool", zap.String("pubkey", pubkey.String()), zap.String("node", nodeAddr.String())) } func (e *CachingExecutionLayer) handleOdaoEvent(event types.Log) { diff --git a/executionlayer/execution-layer_test.go b/executionlayer/execution-layer_test.go index 1d5eb12..48be769 100644 --- a/executionlayer/execution-layer_test.go +++ b/executionlayer/execution-layer_test.go @@ -17,8 +17,10 @@ import ( "testing" "github.com/Rocket-Pool-Rescue-Node/rescue-proxy/metrics" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/gorilla/websocket" rptypes "github.com/rocket-pool/rocketpool-go/types" "go.uber.org/zap/zaptest" @@ -32,15 +34,21 @@ const rocketNodeManager = "0x00000000000000000000000089f478e6cc24f052103628f3659 const rocketNodeDistributorFactory = "0x000000000000000000000000e228017f77b3e0785e794e4c0a8a6b935bb4037c" const rocketMinipoolManager = "0x0000000000000000000000006d010c43d4e96d74c422f2e27370af48711b49bf" const rocketDAONodeTrusted = "0x000000000000000000000000b8e783882b11Ff4f6Cef3C501EA0f4b960152cc9" +const rocketDAONodeTrustedActions = "0x000000000000000000000000029d946f28f93399a5b0d09c879fc8c94e596aeb" const rocketSmoothingPool = "0x000000000000000000000000d4e96ef8eee8678dbff4d535e033ed1a4f7605b7" const rocketTokenRETH = "0x000000000000000000000000ae78736cd615f374d3085123a210448e74fc6393" +const backfillNode = "0x000000000000000000000000515f7de509932bdc5ddc4c61e4324b18822c21da" + //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 @@ -129,6 +137,11 @@ 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:]) } @@ -181,6 +194,8 @@ func (e *happyEC) Serve(mt int, data []byte) (int, []byte) { 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) @@ -224,7 +239,7 @@ func (e *happyEC) Serve(mt int, data []byte) (int, []byte) { resp = fmt.Sprintf(callResultFmt, m.ID, rocketTokenRETH) case "0db039c744237e4ce5ef78a0f054ce1d90d4c567f771ca22f1b89eed7a7b901c": e.t.Log("Returning RocketDAONodeTrustedActions address") - resp = fmt.Sprintf(callResultFmt, m.ID, "0x000000000000000000000000029d946f28f93399a5b0d09c879fc8c94e596aeb") + resp = fmt.Sprintf(callResultFmt, m.ID, rocketDAONodeTrustedActions) case "ea051094896ef3b09ab1b794ad5ea695a5ff3906f74a9328e2c16db69d0f3123": e.t.Log("Returning RocketNodeDistributorFactory address") resp = fmt.Sprintf(callResultFmt, m.ID, rocketNodeDistributorFactory) @@ -361,8 +376,7 @@ func (e *happyEC) Serve(mt int, data []byte) (int, []byte) { case "0x3eb535e9": // The input is an address addr := common.HexToAddress(input) - // Simply left-pad with a char out to the desired length - pubkey := fmt.Sprintf("f0f0%092s", addr.String()[2:]) + pubkey := pubkeyFromMinipool(addr) h, err := hex.DecodeString(pubkey) if err != nil { e.t.Fatal(err) @@ -913,3 +927,509 @@ func TestELForEaches(t *testing.T) { t.Fatal(err) } } + +func TestELODAOJoinLeave(t *testing.T) { + et := setup(t, &happyEC{t, + []*mockNode{ + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), + inSP: true, + minipools: 1, + }, + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000002234567899876543210"), + inSP: false, + minipools: 3, + }, + }, + []*mockNode{ + &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{ + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), + inSP: true, + minipools: 1, + }, + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000002234567899876543210"), + inSP: false, + minipools: 3, + }, + }, + []*mockNode{ + &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{ + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), + inSP: true, + minipools: 1, + }, + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000002234567899876543210"), + inSP: false, + minipools: 3, + }, + }, + []*mockNode{ + &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{ + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000001234567899876543210"), + inSP: true, + minipools: 1, + }, + &mockNode{ + addr: common.HexToAddress("0x0000000000000000000002234567899876543210"), + inSP: false, + minipools: 3, + }, + }, + []*mockNode{ + &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) + } +} diff --git a/executionlayer/test-data/log-filter-fmt.txt b/executionlayer/test-data/log-filter-fmt.txt new file mode 100644 index 0000000..b8465e7 --- /dev/null +++ b/executionlayer/test-data/log-filter-fmt.txt @@ -0,0 +1,20 @@ +{ + "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 From 78374e08173f066a6f0219ab0fa7aa5b3d1c5ecc Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Sat, 11 Nov 2023 15:10:59 -0500 Subject: [PATCH 3/7] Update workflows --- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/tests.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index c05396f..2cff0e8 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -17,7 +17,7 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v4 with: go-version: 1.20.8 - uses: actions/checkout@v3 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3ce0ef7..c18de0b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,14 +14,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v4 with: go-version: 1.20.8 - - uses: arduino/setup-protoc@v1 + - uses: arduino/setup-protoc@v2 - 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 - run: | make - - run: go test -v ./... + - run: go test ./... From ef1374df5cd87d50326582c440f43ae1f09a3133 Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Sat, 11 Nov 2023 15:22:39 -0500 Subject: [PATCH 4/7] Update README --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9934c3c..39fd1e2 100644 --- a/README.md +++ b/README.md @@ -30,15 +30,20 @@ Usage of ./rescue-proxy: Whether to enable verbose logging -ec-url string URL to the execution client to use, eg, http://localhost:8545 + -enable-solo-validators + Whether or not to allow solo validators access. (default true) -grpc-addr string Address on which to reply to gRPC requests -grpc-beacon-addr string Address to the beacon node to proxy for gRPC, eg, localhost:4000 + -grpc-tls-cert-file string + Optional TLS Certificate for the gRPC host + -grpc-tls-key-file string + Optional TLS Key for the gRPC host -hmac-secret string The secret to use for HMAC (default "test-secret") -rocketstorage-addr string Address of the Rocket Storage contract. Defaults to mainnet (default "0x1d8f8f00cfa6758d7bE78336684788Fb0ee0Fa46") - ``` * The `-grpc` flags should only be used with a Prysm beacon node. From d5555b034207a080901265cb477daea2866fc166 Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Sat, 11 Nov 2023 15:24:01 -0500 Subject: [PATCH 5/7] Fix typo --- executionlayer/sqlite-cache.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/executionlayer/sqlite-cache.go b/executionlayer/sqlite-cache.go index 13a5f72..24b2c39 100644 --- a/executionlayer/sqlite-cache.go +++ b/executionlayer/sqlite-cache.go @@ -206,7 +206,7 @@ func (s *SqliteCache) init() error { // See if we have a sqlite snapshot we can load if _, err = os.Stat(s.Path + "/" + snapshotFileName); os.IsNotExist(err) { - // Nothing to load, so initalize tables + // Nothing to load, so initialize tables goto cont } From 6745440531d64da36fa90c4353feb16fb38d8a65 Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Sat, 11 Nov 2023 19:14:56 -0500 Subject: [PATCH 6/7] Update README --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 39fd1e2..6237caf 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,16 @@ # Rescue-Proxy -Rocket Pool Rescue Node's Rescue-Proxy is a custom reverse proxy meant to sit between a shared beacon node and its downstream users. It behaves like a normal reverse proxy with the following added features and protections: +[Rocket Rescue Node](https://rescuenode.com)'s Rescue-Proxy is a custom reverse proxy meant to sit between a shared beacon node and its downstream users. It behaves like a normal reverse proxy with the following added features and protections: 1. HMAC authentication via HTTP Basic Auth / GRPC headers 1. Fee Recipient validation for Rocket Pool validator clients 1. Credential expiration -1. Robust caching for frequently accessed immutable chain data -1. GRPC support for Prysm; HTTP support for Nimbus, Lighthouse, and Teku +1. Robust caching for frequently accessed immutable and mutable chain data +1. GRPC support for Prysm; HTTP support for Nimbus, Lighthouse, Lodestar, and Teku +1. Fee Recipient validation for Solo staker validator clients +1. Detailed logging and [metrics](https://status.rescuenode.com). +1. A gRPC API allowing [rescue-api](https://github.com/Rocket-Rescue-Node/rescue-api) to see views of currently active solo and Rocket Pool node operators ## Usage From 7e8e18e7cd7de74660670dcd3bdcedaf7ddf4589 Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Sat, 11 Nov 2023 19:17:50 -0500 Subject: [PATCH 7/7] Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 34f582c..d313829 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -A reverse-proxy for the Rocket Pool Rescue Node +A reverse-proxy for the Rocket Rescue Node Copyright (C) 2022 Jacob Shufro and João Poupino This program is free software: you can redistribute it and/or modify