From 8babdef81fecd44e349cc9bcf8b41a2a2d43ff76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20Petruni=C4=87?= Date: Mon, 16 Sep 2024 17:52:22 +0200 Subject: [PATCH] feat: check hashi message (#48) --- Makefile | 1 + chains/evm/abi/yaho.go | 498 ++++++++++++++++++ chains/evm/config/config.go | 1 + chains/evm/config/config_test.go | 6 +- chains/evm/listener/events/events.go | 3 +- chains/evm/listener/events/handlers/hashi.go | 50 ++ .../listener/events/handlers/hashi_test.go | 69 +++ chains/evm/listener/events/handlers/logs.go | 41 ++ chains/evm/listener/handlers/step.go | 95 +--- chains/evm/listener/handlers/step_test.go | 34 +- main.go | 14 +- mock/logs.go | 57 ++ mock/step.go | 78 ++- 13 files changed, 808 insertions(+), 139 deletions(-) create mode 100644 chains/evm/abi/yaho.go create mode 100644 chains/evm/listener/events/handlers/hashi.go create mode 100644 chains/evm/listener/events/handlers/hashi_test.go create mode 100644 chains/evm/listener/events/handlers/logs.go create mode 100644 mock/logs.go diff --git a/Makefile b/Makefile index 37d64f5..18d658c 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ genmocks: mockgen -source=./chains/evm/executor/executor.go -destination=./mock/executor.go -package mock mockgen -source=./chains/evm/prover/prover.go -destination=./mock/prover.go -package mock mockgen -destination=./mock/store.go -package mock github.com/sygmaprotocol/sygma-core/store KeyValueReaderWriter + mockgen -destination=./mock/logs.go -package mock -source=./chains/evm/listener/events/handlers/logs.go PLATFORMS := linux/amd64 darwin/amd64 darwin/arm64 linux/arm temp = $(subst /, ,$@) diff --git a/chains/evm/abi/yaho.go b/chains/evm/abi/yaho.go new file mode 100644 index 0000000..e2de540 --- /dev/null +++ b/chains/evm/abi/yaho.go @@ -0,0 +1,498 @@ +// The Licensed Work is (c) 2023 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package abi + +const YahoABI = ` +[ + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "targetChainId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "contract IReporter[]", + "name": "reporters", + "type": "address[]" + }, + { + "internalType": "contract IAdapter[]", + "name": "adapters", + "type": "address[]" + } + ], + "internalType": "struct Message", + "name": "message", + "type": "tuple" + } + ], + "name": "InvalidMessage", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxThreshold", + "type": "uint256" + } + ], + "name": "InvalidThreshold", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "messageId", + "type": "uint256" + } + ], + "name": "MessageHashNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "NoAdaptersGiven", + "type": "error" + }, + { + "inputs": [], + "name": "NoMessageIdsGiven", + "type": "error" + }, + { + "inputs": [], + "name": "NoMessagesGiven", + "type": "error" + }, + { + "inputs": [], + "name": "NoReportersGiven", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "arrayOne", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "arrayTwo", + "type": "uint256" + } + ], + "name": "UnequalArrayLengths", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "messageId", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "targetChainId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "contract IReporter[]", + "name": "reporters", + "type": "address[]" + }, + { + "internalType": "contract IAdapter[]", + "name": "adapters", + "type": "address[]" + } + ], + "indexed": false, + "internalType": "struct Message", + "name": "message", + "type": "tuple" + } + ], + "name": "MessageDispatched", + "type": "event" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "targetChainId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "contract IReporter[]", + "name": "reporters", + "type": "address[]" + }, + { + "internalType": "contract IAdapter[]", + "name": "adapters", + "type": "address[]" + } + ], + "internalType": "struct Message", + "name": "message", + "type": "tuple" + } + ], + "name": "calculateMessageHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sourceChainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "dispatcherAddress", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "messageHash", + "type": "bytes32" + } + ], + "name": "calculateMessageId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "targetChainId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "contract IReporter[]", + "name": "reporters", + "type": "address[]" + }, + { + "internalType": "contract IAdapter[]", + "name": "adapters", + "type": "address[]" + } + ], + "name": "dispatchMessage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "targetChainId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "contract IReporter[]", + "name": "reporters", + "type": "address[]" + }, + { + "internalType": "contract IAdapter[]", + "name": "adapters", + "type": "address[]" + } + ], + "name": "dispatchMessageToAdapters", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "targetChainId", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "thresholds", + "type": "uint256[]" + }, + { + "internalType": "address[]", + "name": "receivers", + "type": "address[]" + }, + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + }, + { + "internalType": "contract IReporter[]", + "name": "reporters", + "type": "address[]" + }, + { + "internalType": "contract IAdapter[]", + "name": "adapters", + "type": "address[]" + } + ], + "name": "dispatchMessagesToAdapters", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "messageId", + "type": "uint256" + } + ], + "name": "getPendingMessageHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "targetChainId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "contract IReporter[]", + "name": "reporters", + "type": "address[]" + }, + { + "internalType": "contract IAdapter[]", + "name": "adapters", + "type": "address[]" + } + ], + "internalType": "struct Message[]", + "name": "messages", + "type": "tuple[]" + } + ], + "name": "relayMessagesToAdapters", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "payable", + "type": "function" + } +] +` diff --git a/chains/evm/config/config.go b/chains/evm/config/config.go index 85bf607..8819124 100644 --- a/chains/evm/config/config.go +++ b/chains/evm/config/config.go @@ -15,6 +15,7 @@ type EVMConfig struct { BeaconEndpoint string `split_words:"true"` Router string Spectre string + Yaho string Spec string `default:"mainnet"` MaxGasPrice int64 `default:"500000000000" split_words:"true"` GasMultiplier float64 `default:"1" split_words:"true"` diff --git a/chains/evm/config/config_test.go b/chains/evm/config/config_test.go index f2db111..fcb06cf 100644 --- a/chains/evm/config/config_test.go +++ b/chains/evm/config/config_test.go @@ -39,7 +39,7 @@ func (s *EVMConfigTestSuite) Test_LoadEVMConfig_SuccessfulLoad_DefaultValues() { os.Setenv("SPECTRE_DOMAINS_1_ENDPOINT", "http://endpoint.com") os.Setenv("SPECTRE_DOMAINS_1_KEY", "key") os.Setenv("SPECTRE_DOMAINS_1_SPECTRE", "spectre") - os.Setenv("SPECTRE_DOMAINS_1_ROUTER", "router") + os.Setenv("SPECTRE_DOMAINS_1_YAHO", "yaho") os.Setenv("SPECTRE_DOMAINS_1_BEACON_ENDPOINT", "endpoint") os.Setenv("SPECTRE_DOMAINS_2_ROUTER", "invalid") os.Setenv("SPECTRE_DOMAINS_1_STARTING_PERIOD", "500") @@ -53,7 +53,7 @@ func (s *EVMConfigTestSuite) Test_LoadEVMConfig_SuccessfulLoad_DefaultValues() { Key: "key", Endpoint: "http://endpoint.com", }, - Router: "router", + Yaho: "yaho", Spectre: "spectre", Spec: "mainnet", GasMultiplier: 1, @@ -74,6 +74,7 @@ func (s *EVMConfigTestSuite) Test_LoadEVMConfig_SuccessfulLoad() { os.Setenv("SPECTRE_DOMAINS_1_KEY", "key") os.Setenv("SPECTRE_DOMAINS_1_SPECTRE", "spectre") os.Setenv("SPECTRE_DOMAINS_1_ROUTER", "router") + os.Setenv("SPECTRE_DOMAINS_1_YAHO", "yaho") os.Setenv("SPECTRE_DOMAINS_1_BEACON_ENDPOINT", "endpoint") os.Setenv("SPECTRE_DOMAINS_1_MAX_GAS_PRICE", "1000") os.Setenv("SPECTRE_DOMAINS_1_BLOCK_INTERVAL", "10") @@ -98,6 +99,7 @@ func (s *EVMConfigTestSuite) Test_LoadEVMConfig_SuccessfulLoad() { Endpoint: "http://endpoint.com", }, Router: "router", + Yaho: "yaho", Spectre: "spectre", Spec: "testnet", GasMultiplier: 1, diff --git a/chains/evm/listener/events/events.go b/chains/evm/listener/events/events.go index 1086643..66023e0 100644 --- a/chains/evm/listener/events/events.go +++ b/chains/evm/listener/events/events.go @@ -15,7 +15,8 @@ func (es EventSig) GetTopic() common.Hash { } const ( - DepositSig EventSig = "Deposit(uint8,uint8,bytes32,uint64,address,bytes)" + DepositSig EventSig = "Deposit(uint8,uint8,bytes32,uint64,address,bytes)" + MessageDispatchedSig EventSig = "MessageDispatched(uint256,(uint256,uint256,uint256,address,address,bytes,address[],address[]))" ) // Deposit struct holds event data raised by Deposit event on-chain diff --git a/chains/evm/listener/events/handlers/hashi.go b/chains/evm/listener/events/handlers/hashi.go new file mode 100644 index 0000000..2b408fd --- /dev/null +++ b/chains/evm/listener/events/handlers/hashi.go @@ -0,0 +1,50 @@ +// The Licensed Work is (c) 2023 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package handlers + +import ( + "math/big" + "strings" + + ethereumABI "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/sygmaprotocol/spectre-node/chains/evm/abi" + "github.com/sygmaprotocol/spectre-node/chains/evm/listener/events" +) + +type HashiDomainCollector struct { + domainID uint8 + yahoAddress common.Address + yahoABI ethereumABI.ABI + eventFetcher EventFetcher + domains []uint8 +} + +func NewHashiDomainCollector( + domainID uint8, + yahoAddress common.Address, + eventFetcher EventFetcher, + domains []uint8, +) *HashiDomainCollector { + abi, _ := ethereumABI.JSON(strings.NewReader(abi.YahoABI)) + return &HashiDomainCollector{ + domainID: domainID, + yahoAddress: yahoAddress, + yahoABI: abi, + domains: domains, + eventFetcher: eventFetcher, + } +} + +func (h *HashiDomainCollector) CollectDomains(startBlock *big.Int, endBlock *big.Int) ([]uint8, error) { + logs, err := fetchLogs(h.eventFetcher, startBlock, endBlock, h.yahoAddress, string(events.MessageDispatchedSig)) + if err != nil { + return []uint8{}, err + } + + if len(logs) == 0 { + return []uint8{}, nil + } + return h.domains, nil +} diff --git a/chains/evm/listener/events/handlers/hashi_test.go b/chains/evm/listener/events/handlers/hashi_test.go new file mode 100644 index 0000000..5a93cbf --- /dev/null +++ b/chains/evm/listener/events/handlers/hashi_test.go @@ -0,0 +1,69 @@ +// The Licensed Work is (c) 2023 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package handlers_test + +import ( + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/suite" + "github.com/sygmaprotocol/spectre-node/chains/evm/listener/events" + "github.com/sygmaprotocol/spectre-node/chains/evm/listener/events/handlers" + "github.com/sygmaprotocol/spectre-node/mock" + evmMessage "github.com/sygmaprotocol/sygma-core/relayer/message" + "go.uber.org/mock/gomock" +) + +type HashiHandlerTestSuite struct { + suite.Suite + + hashiHandler *handlers.HashiDomainCollector + + msgChan chan []*evmMessage.Message + mockEventFetcher *mock.MockEventFetcher + domains []uint8 + sourceDomain uint8 + yahoAddress common.Address +} + +func TestRunHashiHandlerTestSuite(t *testing.T) { + suite.Run(t, new(HashiHandlerTestSuite)) +} + +func (s *HashiHandlerTestSuite) SetupTest() { + ctrl := gomock.NewController(s.T()) + s.mockEventFetcher = mock.NewMockEventFetcher(ctrl) + s.msgChan = make(chan []*evmMessage.Message, 2) + s.sourceDomain = 1 + s.yahoAddress = common.HexToAddress("0xa83114A443dA1CecEFC50368531cACE9F37fCCcb") + s.hashiHandler = handlers.NewHashiDomainCollector( + s.sourceDomain, + s.yahoAddress, + s.mockEventFetcher, + s.domains, + ) +} + +func (s *HashiHandlerTestSuite) Test_CollectDomains_FetchingLogFails() { + s.mockEventFetcher.EXPECT().FetchEventLogs(gomock.Any(), s.yahoAddress, string(events.MessageDispatchedSig), big.NewInt(100), big.NewInt(1100)).Return([]types.Log{}, nil) + s.mockEventFetcher.EXPECT().FetchEventLogs(gomock.Any(), s.yahoAddress, string(events.MessageDispatchedSig), big.NewInt(1101), big.NewInt(2101)).Return([]types.Log{{}}, fmt.Errorf("error")) + + _, err := s.hashiHandler.CollectDomains(big.NewInt(100), big.NewInt(2568)) + + s.NotNil(err) +} + +func (s *HashiHandlerTestSuite) Test_CollectDomains_ValidMessage() { + s.mockEventFetcher.EXPECT().FetchEventLogs(gomock.Any(), s.yahoAddress, string(events.MessageDispatchedSig), big.NewInt(100), big.NewInt(1100)).Return([]types.Log{}, nil) + s.mockEventFetcher.EXPECT().FetchEventLogs(gomock.Any(), s.yahoAddress, string(events.MessageDispatchedSig), big.NewInt(1101), big.NewInt(2101)).Return([]types.Log{{}}, nil) + s.mockEventFetcher.EXPECT().FetchEventLogs(gomock.Any(), s.yahoAddress, string(events.MessageDispatchedSig), big.NewInt(2102), big.NewInt(2568)).Return([]types.Log{}, nil) + + domains, err := s.hashiHandler.CollectDomains(big.NewInt(100), big.NewInt(2568)) + + s.Nil(err) + s.Equal(domains, s.domains) +} diff --git a/chains/evm/listener/events/handlers/logs.go b/chains/evm/listener/events/handlers/logs.go new file mode 100644 index 0000000..d7a8192 --- /dev/null +++ b/chains/evm/listener/events/handlers/logs.go @@ -0,0 +1,41 @@ +// The Licensed Work is (c) 2023 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +package handlers + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +const ( + MAX_BLOCK_RANGE int64 = 1000 +) + +type EventFetcher interface { + FetchEventLogs(ctx context.Context, contractAddress common.Address, event string, startBlock *big.Int, endBlock *big.Int) ([]types.Log, error) +} + +// fetchLogs calls fetch event logs multiple times with a predefined block range to prevent +// rpc errors when the block range is too large +func fetchLogs(eventFetcher EventFetcher, startBlock, endBlock *big.Int, contract common.Address, eventSignature string) ([]types.Log, error) { + allLogs := make([]types.Log, 0) + for startBlock.Cmp(endBlock) < 0 { + rangeEnd := new(big.Int).Add(startBlock, big.NewInt(MAX_BLOCK_RANGE)) + if rangeEnd.Cmp(endBlock) > 0 { + rangeEnd = endBlock + } + + logs, err := eventFetcher.FetchEventLogs(context.Background(), contract, eventSignature, startBlock, rangeEnd) + if err != nil { + return nil, err + } + allLogs = append(allLogs, logs...) + startBlock = new(big.Int).Add(rangeEnd, big.NewInt(1)) + } + + return allLogs, nil +} diff --git a/chains/evm/listener/handlers/step.go b/chains/evm/listener/handlers/step.go index 6fd3865..3a6db70 100644 --- a/chains/evm/listener/handlers/step.go +++ b/chains/evm/listener/handlers/step.go @@ -7,18 +7,12 @@ import ( "context" "fmt" "math/big" - "strings" "github.com/attestantio/go-eth2-client/api" apiv1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec" mapset "github.com/deckarep/golang-set/v2" - ethereumABI "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/rs/zerolog/log" - "github.com/sygmaprotocol/spectre-node/chains/evm/abi" - "github.com/sygmaprotocol/spectre-node/chains/evm/listener/events" evmMessage "github.com/sygmaprotocol/spectre-node/chains/evm/message" "github.com/sygmaprotocol/spectre-node/chains/evm/prover" "github.com/sygmaprotocol/sygma-core/relayer/message" @@ -26,10 +20,6 @@ import ( const EXECUTION_STATE_ROOT_INDEX = 34 -type EventFetcher interface { - FetchEventLogs(ctx context.Context, contractAddress common.Address, event string, startBlock *big.Int, endBlock *big.Int) ([]types.Log, error) -} - type Prover interface { StepProof(args *prover.StepArgs) (*prover.EvmProof[evmMessage.SyncStepInput], error) RotateProof(args *prover.RotateArgs) (*prover.EvmProof[struct{}], error) @@ -41,41 +31,39 @@ type BlockFetcher interface { SignedBeaconBlock(ctx context.Context, opts *api.SignedBeaconBlockOpts) (*api.Response[*spec.VersionedSignedBeaconBlock], error) } +type DomainCollector interface { + CollectDomains(startBlock *big.Int, endBlock *big.Int) ([]uint8, error) +} + type StepEventHandler struct { msgChan chan []*message.Message - eventFetcher EventFetcher - blockFetcher BlockFetcher - prover Prover + blockFetcher BlockFetcher + domainCollectors []DomainCollector + prover Prover - domainID uint8 - allDomains []uint8 - routerABI ethereumABI.ABI - routerAddress common.Address + domainID uint8 + domains []uint8 latestBlock uint64 } func NewStepEventHandler( msgChan chan []*message.Message, - eventFetcher EventFetcher, + domainCollectors []DomainCollector, blockFetcher BlockFetcher, prover Prover, - routerAddress common.Address, domainID uint8, domains []uint8, ) *StepEventHandler { - routerABI, _ := ethereumABI.JSON(strings.NewReader(abi.RouterABI)) return &StepEventHandler{ - eventFetcher: eventFetcher, - blockFetcher: blockFetcher, - prover: prover, - routerAddress: routerAddress, - routerABI: routerABI, - msgChan: msgChan, - domainID: domainID, - allDomains: domains, - latestBlock: 0, + blockFetcher: blockFetcher, + prover: prover, + domainCollectors: domainCollectors, + msgChan: msgChan, + domainID: domainID, + domains: domains, + latestBlock: 0, } } @@ -141,54 +129,17 @@ func (h *StepEventHandler) destinationDomains(slot uint64) ([]uint8, uint64, err if err != nil { return domains.ToSlice(), 0, err } - endBlock := block.Data.Deneb.Message.Body.ExecutionPayload.BlockNumber if h.latestBlock == 0 { - return h.allDomains, endBlock, nil + return h.domains, endBlock, nil } - deposits, err := h.fetchDeposits(big.NewInt(int64(h.latestBlock)), big.NewInt(int64(endBlock))) - if err != nil { - return domains.ToSlice(), endBlock, err - } - if len(deposits) == 0 { - return domains.ToSlice(), endBlock, nil - } - for _, deposit := range deposits { - domains.Add(deposit.DestinationDomainID) - } - - return domains.ToSlice(), endBlock, nil -} - -func (h *StepEventHandler) fetchDeposits(startBlock *big.Int, endBlock *big.Int) ([]*events.Deposit, error) { - logs, err := h.eventFetcher.FetchEventLogs(context.Background(), h.routerAddress, string(events.DepositSig), startBlock, endBlock) - if err != nil { - return nil, err - } - - deposits := make([]*events.Deposit, 0) - for _, dl := range logs { - d, err := h.unpackDeposit(dl.Data) + for _, collector := range h.domainCollectors { + collectedDomains, err := collector.CollectDomains(new(big.Int).SetUint64(h.latestBlock), new(big.Int).SetUint64(endBlock)) if err != nil { - log.Error().Msgf("Failed unpacking deposit event log: %v", err) - continue + return domains.ToSlice(), 0, err } - d.SenderAddress = common.BytesToAddress(dl.Topics[1].Bytes()) - - log.Debug().Msgf("Found deposit log in block: %d, TxHash: %s, contractAddress: %s, sender: %s", dl.BlockNumber, dl.TxHash, dl.Address, d.SenderAddress) - deposits = append(deposits, d) - } - - return deposits, nil -} - -func (h *StepEventHandler) unpackDeposit(data []byte) (*events.Deposit, error) { - var d events.Deposit - err := h.routerABI.UnpackIntoInterface(&d, "Deposit", data) - if err != nil { - return &events.Deposit{}, err + domains.Append(collectedDomains...) } - - return &d, nil + return domains.ToSlice(), endBlock, nil } diff --git a/chains/evm/listener/handlers/step_test.go b/chains/evm/listener/handlers/step_test.go index 0373d0c..daa3094 100644 --- a/chains/evm/listener/handlers/step_test.go +++ b/chains/evm/listener/handlers/step_test.go @@ -5,7 +5,6 @@ package handlers_test import ( "context" - "encoding/hex" "fmt" "math/big" "testing" @@ -15,8 +14,6 @@ import ( "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/deneb" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/suite" "github.com/sygmaprotocol/spectre-node/chains/evm/listener/handlers" evmMessage "github.com/sygmaprotocol/spectre-node/chains/evm/message" @@ -38,10 +35,10 @@ type StepHandlerTestSuite struct { depositHandler *handlers.StepEventHandler - msgChan chan []*message.Message - mockEventFetcher *mock.MockEventFetcher - mockStepProver *mock.MockProver - mockBlockFetcher *mock.MockBlockFetcher + msgChan chan []*message.Message + mockDomainCollector *mock.MockDomainCollector + mockStepProver *mock.MockProver + mockBlockFetcher *mock.MockBlockFetcher sourceDomain uint8 } @@ -52,17 +49,16 @@ func TestRunConfigTestSuite(t *testing.T) { func (s *StepHandlerTestSuite) SetupTest() { ctrl := gomock.NewController(s.T()) - s.mockEventFetcher = mock.NewMockEventFetcher(ctrl) + s.mockDomainCollector = mock.NewMockDomainCollector(ctrl) s.mockStepProver = mock.NewMockProver(ctrl) s.mockBlockFetcher = mock.NewMockBlockFetcher(ctrl) s.msgChan = make(chan []*message.Message, 10) s.sourceDomain = 1 s.depositHandler = handlers.NewStepEventHandler( s.msgChan, - s.mockEventFetcher, + []handlers.DomainCollector{s.mockDomainCollector, s.mockDomainCollector}, s.mockBlockFetcher, s.mockStepProver, - common.HexToAddress("0xb0b13f0109ef097C3Aa70Fb543EA4942114A845d"), s.sourceDomain, []uint8{1, 2, 3}) } @@ -152,6 +148,7 @@ func (s *StepHandlerTestSuite) Test_HandleEvents_FirstStep_StepExecuted() { } func (s *StepHandlerTestSuite) Test_HandleEvents_SecondStep_MissingDeposits() { + s.mockDomainCollector.EXPECT().CollectDomains(big.NewInt(100), big.NewInt(110)).Return([]uint8{}, nil).Times(2) s.mockStepProver.EXPECT().StepArgs().Return(&prover.StepArgs{ Update: &consensus.LightClientFinalityUpdateDeneb{ FinalizedHeader: &consensus.LightClientHeaderDeneb{ @@ -214,7 +211,6 @@ func (s *StepHandlerTestSuite) Test_HandleEvents_SecondStep_MissingDeposits() { }, }, }, nil) - s.mockEventFetcher.EXPECT().FetchEventLogs(context.Background(), gomock.Any(), gomock.Any(), big.NewInt(100), big.NewInt(110)).Return([]types.Log{}, nil) err = s.depositHandler.HandleEvents(&apiv1.Finality{ Finalized: &phase0.Checkpoint{ @@ -228,6 +224,8 @@ func (s *StepHandlerTestSuite) Test_HandleEvents_SecondStep_MissingDeposits() { } func (s *StepHandlerTestSuite) Test_HandleEvents_SecondStep_ValidDeposits() { + s.mockDomainCollector.EXPECT().CollectDomains(big.NewInt(100), big.NewInt(110)).Return([]uint8{2}, nil) + s.mockDomainCollector.EXPECT().CollectDomains(big.NewInt(100), big.NewInt(110)).Return([]uint8{3}, nil) s.mockStepProver.EXPECT().StepArgs().Return(&prover.StepArgs{ Update: &consensus.LightClientFinalityUpdateDeneb{ FinalizedHeader: &consensus.LightClientHeaderDeneb{ @@ -290,16 +288,6 @@ func (s *StepHandlerTestSuite) Test_HandleEvents_SecondStep_ValidDeposits() { }, }, }, nil) - validDepositData, _ := hex.DecodeString("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000") - s.mockEventFetcher.EXPECT().FetchEventLogs(context.Background(), gomock.Any(), gomock.Any(), big.NewInt(100), big.NewInt(110)).Return([]types.Log{ - { - Data: validDepositData, - Topics: []common.Hash{ - {}, - common.HexToHash("0xd68eb9b5E135b96c1Af165e1D8c4e2eB0E1CE4CD"), - }, - }, - }, nil) s.mockStepProver.EXPECT().StepProof(gomock.Any()).Return(&prover.EvmProof[evmMessage.SyncStepInput]{}, nil) err = s.depositHandler.HandleEvents(&apiv1.Finality{ @@ -312,7 +300,9 @@ func (s *StepHandlerTestSuite) Test_HandleEvents_SecondStep_ValidDeposits() { msgs, err := readFromChannel(s.msgChan) s.Nil(err) s.Equal(len(msgs), 1) - s.Equal(msgs[0].Destination, uint8(2)) + msgs, err = readFromChannel(s.msgChan) + s.Nil(err) + s.Equal(len(msgs), 1) _, err = readFromChannel(s.msgChan) s.NotNil(err) } diff --git a/main.go b/main.go index 17ba1c4..bc84477 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ import ( "github.com/sygmaprotocol/spectre-node/chains/evm/executor" "github.com/sygmaprotocol/spectre-node/chains/evm/lightclient" "github.com/sygmaprotocol/spectre-node/chains/evm/listener" + hashi "github.com/sygmaprotocol/spectre-node/chains/evm/listener/events/handlers" "github.com/sygmaprotocol/spectre-node/chains/evm/listener/handlers" evmMessage "github.com/sygmaprotocol/spectre-node/chains/evm/message" "github.com/sygmaprotocol/spectre-node/chains/evm/prover" @@ -131,8 +132,17 @@ func main() { lightClient := lightclient.NewLightClient(config.BeaconEndpoint) p := prover.NewProver(proverClient, beaconProvider, lightClient, prover.Spec(config.Spec), config.FinalityThreshold, config.SlotsPerEpoch) - routerAddress := common.HexToAddress(config.Router) - stepHandler := handlers.NewStepEventHandler(msgChan, client, beaconProvider, p, routerAddress, id, targetDomains) + + domainCollectors := []handlers.DomainCollector{} + if config.Yaho != "" { + domainCollectors = append(domainCollectors, hashi.NewHashiDomainCollector( + id, + common.HexToAddress(config.Yaho), + client, + targetDomains, + )) + } + stepHandler := handlers.NewStepEventHandler(msgChan, domainCollectors, beaconProvider, p, id, targetDomains) rotateHandler := handlers.NewRotateHandler(msgChan, periodStore, p, id, targetDomains, config.CommitteePeriodLength, latestPeriod) evmListener = listener.NewEVMListener(beaconProvider, []listener.EventHandler{rotateHandler, stepHandler}, id, time.Duration(config.RetryInterval)*time.Second) } diff --git a/mock/logs.go b/mock/logs.go new file mode 100644 index 0000000..f3c8aac --- /dev/null +++ b/mock/logs.go @@ -0,0 +1,57 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./chains/evm/listener/events/handlers/logs.go +// +// Generated by this command: +// +// mockgen -destination=./mock/logs.go -package mock -source=./chains/evm/listener/events/handlers/logs.go +// +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + big "math/big" + reflect "reflect" + + common "github.com/ethereum/go-ethereum/common" + types "github.com/ethereum/go-ethereum/core/types" + gomock "go.uber.org/mock/gomock" +) + +// MockEventFetcher is a mock of EventFetcher interface. +type MockEventFetcher struct { + ctrl *gomock.Controller + recorder *MockEventFetcherMockRecorder +} + +// MockEventFetcherMockRecorder is the mock recorder for MockEventFetcher. +type MockEventFetcherMockRecorder struct { + mock *MockEventFetcher +} + +// NewMockEventFetcher creates a new mock instance. +func NewMockEventFetcher(ctrl *gomock.Controller) *MockEventFetcher { + mock := &MockEventFetcher{ctrl: ctrl} + mock.recorder = &MockEventFetcherMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockEventFetcher) EXPECT() *MockEventFetcherMockRecorder { + return m.recorder +} + +// FetchEventLogs mocks base method. +func (m *MockEventFetcher) FetchEventLogs(ctx context.Context, contractAddress common.Address, event string, startBlock, endBlock *big.Int) ([]types.Log, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchEventLogs", ctx, contractAddress, event, startBlock, endBlock) + ret0, _ := ret[0].([]types.Log) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchEventLogs indicates an expected call of FetchEventLogs. +func (mr *MockEventFetcherMockRecorder) FetchEventLogs(ctx, contractAddress, event, startBlock, endBlock any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchEventLogs", reflect.TypeOf((*MockEventFetcher)(nil).FetchEventLogs), ctx, contractAddress, event, startBlock, endBlock) +} diff --git a/mock/step.go b/mock/step.go index a8b90e3..9534405 100644 --- a/mock/step.go +++ b/mock/step.go @@ -15,51 +15,11 @@ import ( api "github.com/attestantio/go-eth2-client/api" spec "github.com/attestantio/go-eth2-client/spec" - common "github.com/ethereum/go-ethereum/common" - types "github.com/ethereum/go-ethereum/core/types" message "github.com/sygmaprotocol/spectre-node/chains/evm/message" prover "github.com/sygmaprotocol/spectre-node/chains/evm/prover" gomock "go.uber.org/mock/gomock" ) -// MockEventFetcher is a mock of EventFetcher interface. -type MockEventFetcher struct { - ctrl *gomock.Controller - recorder *MockEventFetcherMockRecorder -} - -// MockEventFetcherMockRecorder is the mock recorder for MockEventFetcher. -type MockEventFetcherMockRecorder struct { - mock *MockEventFetcher -} - -// NewMockEventFetcher creates a new mock instance. -func NewMockEventFetcher(ctrl *gomock.Controller) *MockEventFetcher { - mock := &MockEventFetcher{ctrl: ctrl} - mock.recorder = &MockEventFetcherMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockEventFetcher) EXPECT() *MockEventFetcherMockRecorder { - return m.recorder -} - -// FetchEventLogs mocks base method. -func (m *MockEventFetcher) FetchEventLogs(ctx context.Context, contractAddress common.Address, event string, startBlock, endBlock *big.Int) ([]types.Log, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FetchEventLogs", ctx, contractAddress, event, startBlock, endBlock) - ret0, _ := ret[0].([]types.Log) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// FetchEventLogs indicates an expected call of FetchEventLogs. -func (mr *MockEventFetcherMockRecorder) FetchEventLogs(ctx, contractAddress, event, startBlock, endBlock any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchEventLogs", reflect.TypeOf((*MockEventFetcher)(nil).FetchEventLogs), ctx, contractAddress, event, startBlock, endBlock) -} - // MockProver is a mock of Prover interface. type MockProver struct { ctrl *gomock.Controller @@ -180,3 +140,41 @@ func (mr *MockBlockFetcherMockRecorder) SignedBeaconBlock(ctx, opts any) *gomock mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignedBeaconBlock", reflect.TypeOf((*MockBlockFetcher)(nil).SignedBeaconBlock), ctx, opts) } + +// MockDomainCollector is a mock of DomainCollector interface. +type MockDomainCollector struct { + ctrl *gomock.Controller + recorder *MockDomainCollectorMockRecorder +} + +// MockDomainCollectorMockRecorder is the mock recorder for MockDomainCollector. +type MockDomainCollectorMockRecorder struct { + mock *MockDomainCollector +} + +// NewMockDomainCollector creates a new mock instance. +func NewMockDomainCollector(ctrl *gomock.Controller) *MockDomainCollector { + mock := &MockDomainCollector{ctrl: ctrl} + mock.recorder = &MockDomainCollectorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDomainCollector) EXPECT() *MockDomainCollectorMockRecorder { + return m.recorder +} + +// CollectDomains mocks base method. +func (m *MockDomainCollector) CollectDomains(startBlock, endBlock *big.Int) ([]uint8, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CollectDomains", startBlock, endBlock) + ret0, _ := ret[0].([]uint8) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CollectDomains indicates an expected call of CollectDomains. +func (mr *MockDomainCollectorMockRecorder) CollectDomains(startBlock, endBlock any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CollectDomains", reflect.TypeOf((*MockDomainCollector)(nil).CollectDomains), startBlock, endBlock) +}