From 1800ddce0abf8257ddd75d8b6fa9ae5d8c291791 Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Tue, 3 Dec 2024 09:54:49 +0100 Subject: [PATCH 1/7] Implement relays checker --- abi/MEVBoostRelaysAllowList.json | 182 +++ cmd/main.go | 7 + internal/adapters/api/api_adapter.go | 45 +- .../dappmanager/dappmanager_adapter.go | 45 + .../bindings/mevboost_relays_allowlist.go | 1402 +++++++++++++++++ .../mevboost_relays_allowlist_adapter.go | 54 + .../mevboost_relays_allowlist_events.go | 8 + .../application/ports/relaysallowed_port.go | 10 + internal/application/ports/relaysused_port.go | 7 + .../application/services/relaysChecker.go | 148 ++ internal/config/config_loader.go | 24 +- 11 files changed, 1918 insertions(+), 14 deletions(-) create mode 100644 abi/MEVBoostRelaysAllowList.json create mode 100644 internal/adapters/dappmanager/dappmanager_adapter.go create mode 100644 internal/adapters/mevBoostRelaysAllowList/bindings/mevboost_relays_allowlist.go create mode 100644 internal/adapters/mevBoostRelaysAllowList/mevboost_relays_allowlist_adapter.go create mode 100644 internal/application/domain/mevboost_relays_allowlist_events.go create mode 100644 internal/application/ports/relaysallowed_port.go create mode 100644 internal/application/ports/relaysused_port.go create mode 100644 internal/application/services/relaysChecker.go diff --git a/abi/MEVBoostRelaysAllowList.json b/abi/MEVBoostRelaysAllowList.json new file mode 100644 index 0000000..eab467c --- /dev/null +++ b/abi/MEVBoostRelaysAllowList.json @@ -0,0 +1,182 @@ +[ + { + "name": "RelayAdded", + "inputs": [ + { "name": "uri_hash", "type": "string", "indexed": true }, + { + "name": "relay", + "type": "tuple", + "components": [ + { "name": "uri", "type": "string" }, + { "name": "operator", "type": "string" }, + { "name": "is_mandatory", "type": "bool" }, + { "name": "description", "type": "string" } + ], + "indexed": false + } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "RelayRemoved", + "inputs": [ + { "name": "uri_hash", "type": "string", "indexed": true }, + { "name": "uri", "type": "string", "indexed": false } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "AllowedListUpdated", + "inputs": [ + { "name": "allowed_list_version", "type": "uint256", "indexed": true } + ], + "anonymous": false, + "type": "event" + }, + { + "name": "OwnerChanged", + "inputs": [{ "name": "new_owner", "type": "address", "indexed": true }], + "anonymous": false, + "type": "event" + }, + { + "name": "ManagerChanged", + "inputs": [{ "name": "new_manager", "type": "address", "indexed": true }], + "anonymous": false, + "type": "event" + }, + { + "name": "ERC20Recovered", + "inputs": [ + { "name": "token", "type": "address", "indexed": true }, + { "name": "amount", "type": "uint256", "indexed": false }, + { "name": "recipient", "type": "address", "indexed": true } + ], + "anonymous": false, + "type": "event" + }, + { + "stateMutability": "nonpayable", + "type": "constructor", + "inputs": [{ "name": "owner", "type": "address" }], + "outputs": [] + }, + { + "stateMutability": "view", + "type": "function", + "name": "get_relays_amount", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256" }] + }, + { + "stateMutability": "view", + "type": "function", + "name": "get_owner", + "inputs": [], + "outputs": [{ "name": "", "type": "address" }] + }, + { + "stateMutability": "view", + "type": "function", + "name": "get_manager", + "inputs": [], + "outputs": [{ "name": "", "type": "address" }] + }, + { + "stateMutability": "view", + "type": "function", + "name": "get_relays", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "tuple[]", + "components": [ + { "name": "uri", "type": "string" }, + { "name": "operator", "type": "string" }, + { "name": "is_mandatory", "type": "bool" }, + { "name": "description", "type": "string" } + ] + } + ] + }, + { + "stateMutability": "view", + "type": "function", + "name": "get_relay_by_uri", + "inputs": [{ "name": "relay_uri", "type": "string" }], + "outputs": [ + { + "name": "", + "type": "tuple", + "components": [ + { "name": "uri", "type": "string" }, + { "name": "operator", "type": "string" }, + { "name": "is_mandatory", "type": "bool" }, + { "name": "description", "type": "string" } + ] + } + ] + }, + { + "stateMutability": "view", + "type": "function", + "name": "get_allowed_list_version", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256" }] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "add_relay", + "inputs": [ + { "name": "uri", "type": "string" }, + { "name": "operator", "type": "string" }, + { "name": "is_mandatory", "type": "bool" }, + { "name": "description", "type": "string" } + ], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "remove_relay", + "inputs": [{ "name": "uri", "type": "string" }], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "change_owner", + "inputs": [{ "name": "owner", "type": "address" }], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "set_manager", + "inputs": [{ "name": "manager", "type": "address" }], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "dismiss_manager", + "inputs": [], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "recover_erc20", + "inputs": [ + { "name": "token", "type": "address" }, + { "name": "amount", "type": "uint256" }, + { "name": "recipient", "type": "address" } + ], + "outputs": [] + }, + { "stateMutability": "nonpayable", "type": "fallback" } +] diff --git a/cmd/main.go b/cmd/main.go index 450289a..a5cc9bf 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -7,9 +7,11 @@ import ( csfeedistributor "lido-events/internal/adapters/csFeeDistributor" csfeedistributorimpl "lido-events/internal/adapters/csFeeDistributorImpl" csmodule "lido-events/internal/adapters/csModule" + "lido-events/internal/adapters/dappmanager" "lido-events/internal/adapters/execution" exitvalidator "lido-events/internal/adapters/exitValidator" "lido-events/internal/adapters/ipfs" + relayschecker "lido-events/internal/adapters/mevBoostRelaysAllowList" "lido-events/internal/adapters/notifier" proxyapi "lido-events/internal/adapters/proxyApi" "lido-events/internal/adapters/storage" @@ -67,6 +69,11 @@ func main() { if err != nil { logger.Warn("Telegram notifier not initialized: %v", err) } + dappmanagerAdapter := dappmanager.NewDappmanagerAdapter(networkConfig.DappmanagerUrl, networkConfig.MevBoostDnpName) + relaysAllowListAdapter, err := relayschecker.NewRelaysCheckerAdapter(networkConfig.WsURL, networkConfig.MEVBoostRelaysAllowListAddres, networkConfig.DappmanagerUrl, networkConfig.MevBoostDnpName) + if err != nil { + logger.Fatal("Failed to initialize RelaysChecker adapter: %v", err) + } // Start HTTP server apiAdapter := api.NewAPIAdapter(storageAdapter, notifierAdapter, networkConfig.CORS) diff --git a/internal/adapters/api/api_adapter.go b/internal/adapters/api/api_adapter.go index 83075bd..a39eeb9 100644 --- a/internal/adapters/api/api_adapter.go +++ b/internal/adapters/api/api_adapter.go @@ -15,19 +15,23 @@ import ( // APIHandler holds the necessary dependencies for API endpoints type APIHandler struct { - StoragePort ports.StoragePort - NotifierPort ports.NotifierPort - Router *mux.Router - adapterPrefix string + StoragePort ports.StoragePort + NotifierPort ports.NotifierPort + RelaysUsedPort ports.RelaysUsedPort + RelaysAllowedPort ports.RelaysAllowedPort + Router *mux.Router + adapterPrefix string } // NewAPIAdapter initializes the APIHandler and sets up routes with CORS enabled -func NewAPIAdapter(storagePort ports.StoragePort, notifierPort ports.NotifierPort, allowedOrigins []string) *APIHandler { +func NewAPIAdapter(storagePort ports.StoragePort, notifierPort ports.NotifierPort, relaysUsedPort ports.RelaysUsedPort, relaysAllowedPort ports.RelaysAllowedPort, allowedOrigins []string) *APIHandler { h := &APIHandler{ - StoragePort: storagePort, - NotifierPort: notifierPort, - Router: mux.NewRouter(), - adapterPrefix: "API", + StoragePort: storagePort, + NotifierPort: notifierPort, + RelaysUsedPort: relaysUsedPort, + RelaysAllowedPort: relaysAllowedPort, + Router: mux.NewRouter(), + adapterPrefix: "API", } // Set up API routes @@ -57,6 +61,8 @@ func (h *APIHandler) SetupRoutes() { h.Router.HandleFunc("/api/v0/events_indexer/operatorId", h.DeleteOperator).Methods("DELETE", "OPTIONS") h.Router.HandleFunc("/api/v0/events_indexer/operator_performance", h.GetOperatorPerformance).Methods("GET", "OPTIONS") h.Router.HandleFunc("/api/v0/events_indexer/exit_requests", h.GetExitRequests).Methods("GET", "OPTIONS") + h.Router.HandleFunc("/api/v0/events_indexer/relays_allowed", h.GetRelaysAllowed).Methods("GET", "OPTIONS") + h.Router.HandleFunc("/api/v0/events_indexer/relays_used", h.GetRelaysUsed).Methods("GET", "OPTIONS") // Add a generic OPTIONS handler to ensure preflight requests are handled h.Router.Methods("OPTIONS").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -64,6 +70,27 @@ func (h *APIHandler) SetupRoutes() { }) } +// GetRelaysAllowed retrieves the list of allowed relays +func (h *APIHandler) GetRelaysAllowed(w http.ResponseWriter, r *http.Request) { + logger.DebugWithPrefix("API", "GetRelaysAllowed request received") + relays, err := h.RelaysAllowedPort.GetRelaysAllowList() + if err != nil { + logger.ErrorWithPrefix("API", "Error fetching allowed relays: %v", err) + writeErrorResponse(w, "Error fetching allowed relays", http.StatusInternalServerError) + return + } + + jsonResponse, err := json.Marshal(relays) + if err != nil { + logger.ErrorWithPrefix("API", "Error generating JSON response in GetRelaysAllowed: %v", err) + writeErrorResponse(w, "Error generating JSON response", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(jsonResponse) +} + // GetTelegramConfig retrieves the Telegram configuration func (h *APIHandler) GetTelegramConfig(w http.ResponseWriter, r *http.Request) { logger.DebugWithPrefix("API", "GetTelegramConfig request received") diff --git a/internal/adapters/dappmanager/dappmanager_adapter.go b/internal/adapters/dappmanager/dappmanager_adapter.go new file mode 100644 index 0000000..2fb98f4 --- /dev/null +++ b/internal/adapters/dappmanager/dappmanager_adapter.go @@ -0,0 +1,45 @@ +package dappmanager + +import ( + "context" + "encoding/json" + "fmt" + "net/http" +) + +type DappmanagerAdapter struct { + dappmanagerUrl string + mevBootDnpName string +} + +// NewDappmanagerAdapter creates a new instance of DappmanagerAdapter with provided dappmanagerUrl and mevBoostDnpName +func NewDappmanagerAdapter(dappmanagerUrl, mevBoostDnpName string) *DappmanagerAdapter { + return &DappmanagerAdapter{ + dappmanagerUrl, + mevBoostDnpName, + } +} + +// GetRelaysUsed fetches env RELAYS from MEV BOOST pkg +func (da *DappmanagerAdapter) GetRelaysUsed(ctx context.Context) ([]string, error) { + url := fmt.Sprintf("%s/env/%s?envName=RELAYS", da.dappmanagerUrl, da.mevBootDnpName) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create GET request for URL %s: %w", url, err) + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to send request to Dappmanager at %s: %w", url, err) + } + + defer resp.Body.Close() + + var result []string + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, fmt.Errorf("failed to decode response for GetRelaysUsed: %w", err) + } + + return result, nil +} diff --git a/internal/adapters/mevBoostRelaysAllowList/bindings/mevboost_relays_allowlist.go b/internal/adapters/mevBoostRelaysAllowList/bindings/mevboost_relays_allowlist.go new file mode 100644 index 0000000..685c160 --- /dev/null +++ b/internal/adapters/mevBoostRelaysAllowList/bindings/mevboost_relays_allowlist.go @@ -0,0 +1,1402 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package bindings + +import ( + "errors" + "math/big" + "strings" + + "lido-events/internal/application/domain" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// Struct0 is an auto generated low-level Go binding around an user-defined struct. +// type Struct0 struct { +// Uri string +// Operator string +// IsMandatory bool +// Description string +// } + +// BindingsMetaData contains all meta data concerning the Bindings contract. +var BindingsMetaData = &bind.MetaData{ + ABI: "[{\"name\":\"RelayAdded\",\"inputs\":[{\"name\":\"uri_hash\",\"type\":\"string\",\"indexed\":true},{\"name\":\"relay\",\"type\":\"tuple\",\"components\":[{\"name\":\"uri\",\"type\":\"string\"},{\"name\":\"operator\",\"type\":\"string\"},{\"name\":\"is_mandatory\",\"type\":\"bool\"},{\"name\":\"description\",\"type\":\"string\"}],\"indexed\":false}],\"anonymous\":false,\"type\":\"event\"},{\"name\":\"RelayRemoved\",\"inputs\":[{\"name\":\"uri_hash\",\"type\":\"string\",\"indexed\":true},{\"name\":\"uri\",\"type\":\"string\",\"indexed\":false}],\"anonymous\":false,\"type\":\"event\"},{\"name\":\"AllowedListUpdated\",\"inputs\":[{\"name\":\"allowed_list_version\",\"type\":\"uint256\",\"indexed\":true}],\"anonymous\":false,\"type\":\"event\"},{\"name\":\"OwnerChanged\",\"inputs\":[{\"name\":\"new_owner\",\"type\":\"address\",\"indexed\":true}],\"anonymous\":false,\"type\":\"event\"},{\"name\":\"ManagerChanged\",\"inputs\":[{\"name\":\"new_manager\",\"type\":\"address\",\"indexed\":true}],\"anonymous\":false,\"type\":\"event\"},{\"name\":\"ERC20Recovered\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"indexed\":true},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false},{\"name\":\"recipient\",\"type\":\"address\",\"indexed\":true}],\"anonymous\":false,\"type\":\"event\"},{\"stateMutability\":\"nonpayable\",\"type\":\"constructor\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\"}],\"outputs\":[]},{\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"get_relays_amount\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}]},{\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"get_owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\"}]},{\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"get_manager\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\"}]},{\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"get_relays\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"tuple[]\",\"components\":[{\"name\":\"uri\",\"type\":\"string\"},{\"name\":\"operator\",\"type\":\"string\"},{\"name\":\"is_mandatory\",\"type\":\"bool\"},{\"name\":\"description\",\"type\":\"string\"}]}]},{\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"get_relay_by_uri\",\"inputs\":[{\"name\":\"relay_uri\",\"type\":\"string\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"components\":[{\"name\":\"uri\",\"type\":\"string\"},{\"name\":\"operator\",\"type\":\"string\"},{\"name\":\"is_mandatory\",\"type\":\"bool\"},{\"name\":\"description\",\"type\":\"string\"}]}]},{\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"get_allowed_list_version\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}]},{\"stateMutability\":\"nonpayable\",\"type\":\"function\",\"name\":\"add_relay\",\"inputs\":[{\"name\":\"uri\",\"type\":\"string\"},{\"name\":\"operator\",\"type\":\"string\"},{\"name\":\"is_mandatory\",\"type\":\"bool\"},{\"name\":\"description\",\"type\":\"string\"}],\"outputs\":[]},{\"stateMutability\":\"nonpayable\",\"type\":\"function\",\"name\":\"remove_relay\",\"inputs\":[{\"name\":\"uri\",\"type\":\"string\"}],\"outputs\":[]},{\"stateMutability\":\"nonpayable\",\"type\":\"function\",\"name\":\"change_owner\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\"}],\"outputs\":[]},{\"stateMutability\":\"nonpayable\",\"type\":\"function\",\"name\":\"set_manager\",\"inputs\":[{\"name\":\"manager\",\"type\":\"address\"}],\"outputs\":[]},{\"stateMutability\":\"nonpayable\",\"type\":\"function\",\"name\":\"dismiss_manager\",\"inputs\":[],\"outputs\":[]},{\"stateMutability\":\"nonpayable\",\"type\":\"function\",\"name\":\"recover_erc20\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\"},{\"name\":\"recipient\",\"type\":\"address\"}],\"outputs\":[]},{\"stateMutability\":\"nonpayable\",\"type\":\"fallback\"}]", +} + +// BindingsABI is the input ABI used to generate the binding from. +// Deprecated: Use BindingsMetaData.ABI instead. +var BindingsABI = BindingsMetaData.ABI + +// Bindings is an auto generated Go binding around an Ethereum contract. +type Bindings struct { + BindingsCaller // Read-only binding to the contract + BindingsTransactor // Write-only binding to the contract + BindingsFilterer // Log filterer for contract events +} + +// BindingsCaller is an auto generated read-only Go binding around an Ethereum contract. +type BindingsCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// BindingsTransactor is an auto generated write-only Go binding around an Ethereum contract. +type BindingsTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// BindingsFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type BindingsFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// BindingsSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type BindingsSession struct { + Contract *Bindings // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// BindingsCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type BindingsCallerSession struct { + Contract *BindingsCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// BindingsTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type BindingsTransactorSession struct { + Contract *BindingsTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// BindingsRaw is an auto generated low-level Go binding around an Ethereum contract. +type BindingsRaw struct { + Contract *Bindings // Generic contract binding to access the raw methods on +} + +// BindingsCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type BindingsCallerRaw struct { + Contract *BindingsCaller // Generic read-only contract binding to access the raw methods on +} + +// BindingsTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type BindingsTransactorRaw struct { + Contract *BindingsTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewBindings creates a new instance of Bindings, bound to a specific deployed contract. +func NewBindings(address common.Address, backend bind.ContractBackend) (*Bindings, error) { + contract, err := bindBindings(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Bindings{BindingsCaller: BindingsCaller{contract: contract}, BindingsTransactor: BindingsTransactor{contract: contract}, BindingsFilterer: BindingsFilterer{contract: contract}}, nil +} + +// NewBindingsCaller creates a new read-only instance of Bindings, bound to a specific deployed contract. +func NewBindingsCaller(address common.Address, caller bind.ContractCaller) (*BindingsCaller, error) { + contract, err := bindBindings(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &BindingsCaller{contract: contract}, nil +} + +// NewBindingsTransactor creates a new write-only instance of Bindings, bound to a specific deployed contract. +func NewBindingsTransactor(address common.Address, transactor bind.ContractTransactor) (*BindingsTransactor, error) { + contract, err := bindBindings(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &BindingsTransactor{contract: contract}, nil +} + +// NewBindingsFilterer creates a new log filterer instance of Bindings, bound to a specific deployed contract. +func NewBindingsFilterer(address common.Address, filterer bind.ContractFilterer) (*BindingsFilterer, error) { + contract, err := bindBindings(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &BindingsFilterer{contract: contract}, nil +} + +// bindBindings binds a generic wrapper to an already deployed contract. +func bindBindings(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := BindingsMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Bindings *BindingsRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Bindings.Contract.BindingsCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Bindings *BindingsRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Bindings.Contract.BindingsTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Bindings *BindingsRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Bindings.Contract.BindingsTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Bindings *BindingsCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Bindings.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Bindings *BindingsTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Bindings.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Bindings *BindingsTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Bindings.Contract.contract.Transact(opts, method, params...) +} + +// GetAllowedListVersion is a free data retrieval call binding the contract method 0x76650ad3. +// +// Solidity: function get_allowed_list_version() view returns(uint256) +func (_Bindings *BindingsCaller) GetAllowedListVersion(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _Bindings.contract.Call(opts, &out, "get_allowed_list_version") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetAllowedListVersion is a free data retrieval call binding the contract method 0x76650ad3. +// +// Solidity: function get_allowed_list_version() view returns(uint256) +func (_Bindings *BindingsSession) GetAllowedListVersion() (*big.Int, error) { + return _Bindings.Contract.GetAllowedListVersion(&_Bindings.CallOpts) +} + +// GetAllowedListVersion is a free data retrieval call binding the contract method 0x76650ad3. +// +// Solidity: function get_allowed_list_version() view returns(uint256) +func (_Bindings *BindingsCallerSession) GetAllowedListVersion() (*big.Int, error) { + return _Bindings.Contract.GetAllowedListVersion(&_Bindings.CallOpts) +} + +// GetManager is a free data retrieval call binding the contract method 0x9e4a0fc4. +// +// Solidity: function get_manager() view returns(address) +func (_Bindings *BindingsCaller) GetManager(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _Bindings.contract.Call(opts, &out, "get_manager") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// GetManager is a free data retrieval call binding the contract method 0x9e4a0fc4. +// +// Solidity: function get_manager() view returns(address) +func (_Bindings *BindingsSession) GetManager() (common.Address, error) { + return _Bindings.Contract.GetManager(&_Bindings.CallOpts) +} + +// GetManager is a free data retrieval call binding the contract method 0x9e4a0fc4. +// +// Solidity: function get_manager() view returns(address) +func (_Bindings *BindingsCallerSession) GetManager() (common.Address, error) { + return _Bindings.Contract.GetManager(&_Bindings.CallOpts) +} + +// GetOwner is a free data retrieval call binding the contract method 0x0ac298dc. +// +// Solidity: function get_owner() view returns(address) +func (_Bindings *BindingsCaller) GetOwner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _Bindings.contract.Call(opts, &out, "get_owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// GetOwner is a free data retrieval call binding the contract method 0x0ac298dc. +// +// Solidity: function get_owner() view returns(address) +func (_Bindings *BindingsSession) GetOwner() (common.Address, error) { + return _Bindings.Contract.GetOwner(&_Bindings.CallOpts) +} + +// GetOwner is a free data retrieval call binding the contract method 0x0ac298dc. +// +// Solidity: function get_owner() view returns(address) +func (_Bindings *BindingsCallerSession) GetOwner() (common.Address, error) { + return _Bindings.Contract.GetOwner(&_Bindings.CallOpts) +} + +// GetRelayByUri is a free data retrieval call binding the contract method 0xf5f33c7b. +// +// Solidity: function get_relay_by_uri(string relay_uri) view returns((string,string,bool,string)) +func (_Bindings *BindingsCaller) GetRelayByUri(opts *bind.CallOpts, relay_uri string) (domain.RelayAllowed, error) { + var out []interface{} + err := _Bindings.contract.Call(opts, &out, "get_relay_by_uri", relay_uri) + + if err != nil { + return *new(domain.RelayAllowed), err + } + + out0 := *abi.ConvertType(out[0], new(domain.RelayAllowed)).(*domain.RelayAllowed) + + return out0, err + +} + +// GetRelayByUri is a free data retrieval call binding the contract method 0xf5f33c7b. +// +// Solidity: function get_relay_by_uri(string relay_uri) view returns((string,string,bool,string)) +func (_Bindings *BindingsSession) GetRelayByUri(relay_uri string) (domain.RelayAllowed, error) { + return _Bindings.Contract.GetRelayByUri(&_Bindings.CallOpts, relay_uri) +} + +// GetRelayByUri is a free data retrieval call binding the contract method 0xf5f33c7b. +// +// Solidity: function get_relay_by_uri(string relay_uri) view returns((string,string,bool,string)) +func (_Bindings *BindingsCallerSession) GetRelayByUri(relay_uri string) (domain.RelayAllowed, error) { + return _Bindings.Contract.GetRelayByUri(&_Bindings.CallOpts, relay_uri) +} + +// GetRelays is a free data retrieval call binding the contract method 0x04e469ea. +// +// Solidity: function get_relays() view returns((string,string,bool,string)[]) +func (_Bindings *BindingsCaller) GetRelays(opts *bind.CallOpts) ([]domain.RelayAllowed, error) { + var out []interface{} + err := _Bindings.contract.Call(opts, &out, "get_relays") + + if err != nil { + return *new([]domain.RelayAllowed), err + } + + out0 := *abi.ConvertType(out[0], new([]domain.RelayAllowed)).(*[]domain.RelayAllowed) + + return out0, err + +} + +// GetRelays is a free data retrieval call binding the contract method 0x04e469ea. +// +// Solidity: function get_relays() view returns((string,string,bool,string)[]) +func (_Bindings *BindingsSession) GetRelays() ([]domain.RelayAllowed, error) { + return _Bindings.Contract.GetRelays(&_Bindings.CallOpts) +} + +// GetRelays is a free data retrieval call binding the contract method 0x04e469ea. +// +// Solidity: function get_relays() view returns((string,string,bool,string)[]) +func (_Bindings *BindingsCallerSession) GetRelays() ([]domain.RelayAllowed, error) { + return _Bindings.Contract.GetRelays(&_Bindings.CallOpts) +} + +// GetRelaysAmount is a free data retrieval call binding the contract method 0x312c3165. +// +// Solidity: function get_relays_amount() view returns(uint256) +func (_Bindings *BindingsCaller) GetRelaysAmount(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _Bindings.contract.Call(opts, &out, "get_relays_amount") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetRelaysAmount is a free data retrieval call binding the contract method 0x312c3165. +// +// Solidity: function get_relays_amount() view returns(uint256) +func (_Bindings *BindingsSession) GetRelaysAmount() (*big.Int, error) { + return _Bindings.Contract.GetRelaysAmount(&_Bindings.CallOpts) +} + +// GetRelaysAmount is a free data retrieval call binding the contract method 0x312c3165. +// +// Solidity: function get_relays_amount() view returns(uint256) +func (_Bindings *BindingsCallerSession) GetRelaysAmount() (*big.Int, error) { + return _Bindings.Contract.GetRelaysAmount(&_Bindings.CallOpts) +} + +// AddRelay is a paid mutator transaction binding the contract method 0x2e21ecef. +// +// Solidity: function add_relay(string uri, string operator, bool is_mandatory, string description) returns() +func (_Bindings *BindingsTransactor) AddRelay(opts *bind.TransactOpts, uri string, operator string, is_mandatory bool, description string) (*types.Transaction, error) { + return _Bindings.contract.Transact(opts, "add_relay", uri, operator, is_mandatory, description) +} + +// AddRelay is a paid mutator transaction binding the contract method 0x2e21ecef. +// +// Solidity: function add_relay(string uri, string operator, bool is_mandatory, string description) returns() +func (_Bindings *BindingsSession) AddRelay(uri string, operator string, is_mandatory bool, description string) (*types.Transaction, error) { + return _Bindings.Contract.AddRelay(&_Bindings.TransactOpts, uri, operator, is_mandatory, description) +} + +// AddRelay is a paid mutator transaction binding the contract method 0x2e21ecef. +// +// Solidity: function add_relay(string uri, string operator, bool is_mandatory, string description) returns() +func (_Bindings *BindingsTransactorSession) AddRelay(uri string, operator string, is_mandatory bool, description string) (*types.Transaction, error) { + return _Bindings.Contract.AddRelay(&_Bindings.TransactOpts, uri, operator, is_mandatory, description) +} + +// ChangeOwner is a paid mutator transaction binding the contract method 0x253c8bd4. +// +// Solidity: function change_owner(address owner) returns() +func (_Bindings *BindingsTransactor) ChangeOwner(opts *bind.TransactOpts, owner common.Address) (*types.Transaction, error) { + return _Bindings.contract.Transact(opts, "change_owner", owner) +} + +// ChangeOwner is a paid mutator transaction binding the contract method 0x253c8bd4. +// +// Solidity: function change_owner(address owner) returns() +func (_Bindings *BindingsSession) ChangeOwner(owner common.Address) (*types.Transaction, error) { + return _Bindings.Contract.ChangeOwner(&_Bindings.TransactOpts, owner) +} + +// ChangeOwner is a paid mutator transaction binding the contract method 0x253c8bd4. +// +// Solidity: function change_owner(address owner) returns() +func (_Bindings *BindingsTransactorSession) ChangeOwner(owner common.Address) (*types.Transaction, error) { + return _Bindings.Contract.ChangeOwner(&_Bindings.TransactOpts, owner) +} + +// DismissManager is a paid mutator transaction binding the contract method 0x417a02b4. +// +// Solidity: function dismiss_manager() returns() +func (_Bindings *BindingsTransactor) DismissManager(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Bindings.contract.Transact(opts, "dismiss_manager") +} + +// DismissManager is a paid mutator transaction binding the contract method 0x417a02b4. +// +// Solidity: function dismiss_manager() returns() +func (_Bindings *BindingsSession) DismissManager() (*types.Transaction, error) { + return _Bindings.Contract.DismissManager(&_Bindings.TransactOpts) +} + +// DismissManager is a paid mutator transaction binding the contract method 0x417a02b4. +// +// Solidity: function dismiss_manager() returns() +func (_Bindings *BindingsTransactorSession) DismissManager() (*types.Transaction, error) { + return _Bindings.Contract.DismissManager(&_Bindings.TransactOpts) +} + +// RecoverErc20 is a paid mutator transaction binding the contract method 0xedd885b4. +// +// Solidity: function recover_erc20(address token, uint256 amount, address recipient) returns() +func (_Bindings *BindingsTransactor) RecoverErc20(opts *bind.TransactOpts, token common.Address, amount *big.Int, recipient common.Address) (*types.Transaction, error) { + return _Bindings.contract.Transact(opts, "recover_erc20", token, amount, recipient) +} + +// RecoverErc20 is a paid mutator transaction binding the contract method 0xedd885b4. +// +// Solidity: function recover_erc20(address token, uint256 amount, address recipient) returns() +func (_Bindings *BindingsSession) RecoverErc20(token common.Address, amount *big.Int, recipient common.Address) (*types.Transaction, error) { + return _Bindings.Contract.RecoverErc20(&_Bindings.TransactOpts, token, amount, recipient) +} + +// RecoverErc20 is a paid mutator transaction binding the contract method 0xedd885b4. +// +// Solidity: function recover_erc20(address token, uint256 amount, address recipient) returns() +func (_Bindings *BindingsTransactorSession) RecoverErc20(token common.Address, amount *big.Int, recipient common.Address) (*types.Transaction, error) { + return _Bindings.Contract.RecoverErc20(&_Bindings.TransactOpts, token, amount, recipient) +} + +// RemoveRelay is a paid mutator transaction binding the contract method 0xf5a70a80. +// +// Solidity: function remove_relay(string uri) returns() +func (_Bindings *BindingsTransactor) RemoveRelay(opts *bind.TransactOpts, uri string) (*types.Transaction, error) { + return _Bindings.contract.Transact(opts, "remove_relay", uri) +} + +// RemoveRelay is a paid mutator transaction binding the contract method 0xf5a70a80. +// +// Solidity: function remove_relay(string uri) returns() +func (_Bindings *BindingsSession) RemoveRelay(uri string) (*types.Transaction, error) { + return _Bindings.Contract.RemoveRelay(&_Bindings.TransactOpts, uri) +} + +// RemoveRelay is a paid mutator transaction binding the contract method 0xf5a70a80. +// +// Solidity: function remove_relay(string uri) returns() +func (_Bindings *BindingsTransactorSession) RemoveRelay(uri string) (*types.Transaction, error) { + return _Bindings.Contract.RemoveRelay(&_Bindings.TransactOpts, uri) +} + +// SetManager is a paid mutator transaction binding the contract method 0x9aece83e. +// +// Solidity: function set_manager(address manager) returns() +func (_Bindings *BindingsTransactor) SetManager(opts *bind.TransactOpts, manager common.Address) (*types.Transaction, error) { + return _Bindings.contract.Transact(opts, "set_manager", manager) +} + +// SetManager is a paid mutator transaction binding the contract method 0x9aece83e. +// +// Solidity: function set_manager(address manager) returns() +func (_Bindings *BindingsSession) SetManager(manager common.Address) (*types.Transaction, error) { + return _Bindings.Contract.SetManager(&_Bindings.TransactOpts, manager) +} + +// SetManager is a paid mutator transaction binding the contract method 0x9aece83e. +// +// Solidity: function set_manager(address manager) returns() +func (_Bindings *BindingsTransactorSession) SetManager(manager common.Address) (*types.Transaction, error) { + return _Bindings.Contract.SetManager(&_Bindings.TransactOpts, manager) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() returns() +func (_Bindings *BindingsTransactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) { + return _Bindings.contract.RawTransact(opts, calldata) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() returns() +func (_Bindings *BindingsSession) Fallback(calldata []byte) (*types.Transaction, error) { + return _Bindings.Contract.Fallback(&_Bindings.TransactOpts, calldata) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() returns() +func (_Bindings *BindingsTransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { + return _Bindings.Contract.Fallback(&_Bindings.TransactOpts, calldata) +} + +// BindingsAllowedListUpdatedIterator is returned from FilterAllowedListUpdated and is used to iterate over the raw logs and unpacked data for AllowedListUpdated events raised by the Bindings contract. +type BindingsAllowedListUpdatedIterator struct { + Event *BindingsAllowedListUpdated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *BindingsAllowedListUpdatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(BindingsAllowedListUpdated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(BindingsAllowedListUpdated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *BindingsAllowedListUpdatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *BindingsAllowedListUpdatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// BindingsAllowedListUpdated represents a AllowedListUpdated event raised by the Bindings contract. +type BindingsAllowedListUpdated struct { + AllowedListVersion *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAllowedListUpdated is a free log retrieval operation binding the contract event 0x49f5627aa055ec3fcd474f99c8b7799b798c04af7b9f215305512c867e5a1839. +// +// Solidity: event AllowedListUpdated(uint256 indexed allowed_list_version) +func (_Bindings *BindingsFilterer) FilterAllowedListUpdated(opts *bind.FilterOpts, allowed_list_version []*big.Int) (*BindingsAllowedListUpdatedIterator, error) { + + var allowed_list_versionRule []interface{} + for _, allowed_list_versionItem := range allowed_list_version { + allowed_list_versionRule = append(allowed_list_versionRule, allowed_list_versionItem) + } + + logs, sub, err := _Bindings.contract.FilterLogs(opts, "AllowedListUpdated", allowed_list_versionRule) + if err != nil { + return nil, err + } + return &BindingsAllowedListUpdatedIterator{contract: _Bindings.contract, event: "AllowedListUpdated", logs: logs, sub: sub}, nil +} + +// WatchAllowedListUpdated is a free log subscription operation binding the contract event 0x49f5627aa055ec3fcd474f99c8b7799b798c04af7b9f215305512c867e5a1839. +// +// Solidity: event AllowedListUpdated(uint256 indexed allowed_list_version) +func (_Bindings *BindingsFilterer) WatchAllowedListUpdated(opts *bind.WatchOpts, sink chan<- *BindingsAllowedListUpdated, allowed_list_version []*big.Int) (event.Subscription, error) { + + var allowed_list_versionRule []interface{} + for _, allowed_list_versionItem := range allowed_list_version { + allowed_list_versionRule = append(allowed_list_versionRule, allowed_list_versionItem) + } + + logs, sub, err := _Bindings.contract.WatchLogs(opts, "AllowedListUpdated", allowed_list_versionRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(BindingsAllowedListUpdated) + if err := _Bindings.contract.UnpackLog(event, "AllowedListUpdated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseAllowedListUpdated is a log parse operation binding the contract event 0x49f5627aa055ec3fcd474f99c8b7799b798c04af7b9f215305512c867e5a1839. +// +// Solidity: event AllowedListUpdated(uint256 indexed allowed_list_version) +func (_Bindings *BindingsFilterer) ParseAllowedListUpdated(log types.Log) (*BindingsAllowedListUpdated, error) { + event := new(BindingsAllowedListUpdated) + if err := _Bindings.contract.UnpackLog(event, "AllowedListUpdated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// BindingsERC20RecoveredIterator is returned from FilterERC20Recovered and is used to iterate over the raw logs and unpacked data for ERC20Recovered events raised by the Bindings contract. +type BindingsERC20RecoveredIterator struct { + Event *BindingsERC20Recovered // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *BindingsERC20RecoveredIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(BindingsERC20Recovered) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(BindingsERC20Recovered) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *BindingsERC20RecoveredIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *BindingsERC20RecoveredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// BindingsERC20Recovered represents a ERC20Recovered event raised by the Bindings contract. +type BindingsERC20Recovered struct { + Token common.Address + Amount *big.Int + Recipient common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterERC20Recovered is a free log retrieval operation binding the contract event 0x8619312ed4eff1cf9f0116e6db2f49d9570a86f0350d1c5ad1bd0f7b0cf9e132. +// +// Solidity: event ERC20Recovered(address indexed token, uint256 amount, address indexed recipient) +func (_Bindings *BindingsFilterer) FilterERC20Recovered(opts *bind.FilterOpts, token []common.Address, recipient []common.Address) (*BindingsERC20RecoveredIterator, error) { + + var tokenRule []interface{} + for _, tokenItem := range token { + tokenRule = append(tokenRule, tokenItem) + } + + var recipientRule []interface{} + for _, recipientItem := range recipient { + recipientRule = append(recipientRule, recipientItem) + } + + logs, sub, err := _Bindings.contract.FilterLogs(opts, "ERC20Recovered", tokenRule, recipientRule) + if err != nil { + return nil, err + } + return &BindingsERC20RecoveredIterator{contract: _Bindings.contract, event: "ERC20Recovered", logs: logs, sub: sub}, nil +} + +// WatchERC20Recovered is a free log subscription operation binding the contract event 0x8619312ed4eff1cf9f0116e6db2f49d9570a86f0350d1c5ad1bd0f7b0cf9e132. +// +// Solidity: event ERC20Recovered(address indexed token, uint256 amount, address indexed recipient) +func (_Bindings *BindingsFilterer) WatchERC20Recovered(opts *bind.WatchOpts, sink chan<- *BindingsERC20Recovered, token []common.Address, recipient []common.Address) (event.Subscription, error) { + + var tokenRule []interface{} + for _, tokenItem := range token { + tokenRule = append(tokenRule, tokenItem) + } + + var recipientRule []interface{} + for _, recipientItem := range recipient { + recipientRule = append(recipientRule, recipientItem) + } + + logs, sub, err := _Bindings.contract.WatchLogs(opts, "ERC20Recovered", tokenRule, recipientRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(BindingsERC20Recovered) + if err := _Bindings.contract.UnpackLog(event, "ERC20Recovered", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseERC20Recovered is a log parse operation binding the contract event 0x8619312ed4eff1cf9f0116e6db2f49d9570a86f0350d1c5ad1bd0f7b0cf9e132. +// +// Solidity: event ERC20Recovered(address indexed token, uint256 amount, address indexed recipient) +func (_Bindings *BindingsFilterer) ParseERC20Recovered(log types.Log) (*BindingsERC20Recovered, error) { + event := new(BindingsERC20Recovered) + if err := _Bindings.contract.UnpackLog(event, "ERC20Recovered", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// BindingsManagerChangedIterator is returned from FilterManagerChanged and is used to iterate over the raw logs and unpacked data for ManagerChanged events raised by the Bindings contract. +type BindingsManagerChangedIterator struct { + Event *BindingsManagerChanged // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *BindingsManagerChangedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(BindingsManagerChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(BindingsManagerChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *BindingsManagerChangedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *BindingsManagerChangedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// BindingsManagerChanged represents a ManagerChanged event raised by the Bindings contract. +type BindingsManagerChanged struct { + NewManager common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterManagerChanged is a free log retrieval operation binding the contract event 0x198db6e425fb8aafd1823c6ca50be2d51e5764571a5ae0f0f21c6812e45def0b. +// +// Solidity: event ManagerChanged(address indexed new_manager) +func (_Bindings *BindingsFilterer) FilterManagerChanged(opts *bind.FilterOpts, new_manager []common.Address) (*BindingsManagerChangedIterator, error) { + + var new_managerRule []interface{} + for _, new_managerItem := range new_manager { + new_managerRule = append(new_managerRule, new_managerItem) + } + + logs, sub, err := _Bindings.contract.FilterLogs(opts, "ManagerChanged", new_managerRule) + if err != nil { + return nil, err + } + return &BindingsManagerChangedIterator{contract: _Bindings.contract, event: "ManagerChanged", logs: logs, sub: sub}, nil +} + +// WatchManagerChanged is a free log subscription operation binding the contract event 0x198db6e425fb8aafd1823c6ca50be2d51e5764571a5ae0f0f21c6812e45def0b. +// +// Solidity: event ManagerChanged(address indexed new_manager) +func (_Bindings *BindingsFilterer) WatchManagerChanged(opts *bind.WatchOpts, sink chan<- *BindingsManagerChanged, new_manager []common.Address) (event.Subscription, error) { + + var new_managerRule []interface{} + for _, new_managerItem := range new_manager { + new_managerRule = append(new_managerRule, new_managerItem) + } + + logs, sub, err := _Bindings.contract.WatchLogs(opts, "ManagerChanged", new_managerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(BindingsManagerChanged) + if err := _Bindings.contract.UnpackLog(event, "ManagerChanged", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseManagerChanged is a log parse operation binding the contract event 0x198db6e425fb8aafd1823c6ca50be2d51e5764571a5ae0f0f21c6812e45def0b. +// +// Solidity: event ManagerChanged(address indexed new_manager) +func (_Bindings *BindingsFilterer) ParseManagerChanged(log types.Log) (*BindingsManagerChanged, error) { + event := new(BindingsManagerChanged) + if err := _Bindings.contract.UnpackLog(event, "ManagerChanged", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// BindingsOwnerChangedIterator is returned from FilterOwnerChanged and is used to iterate over the raw logs and unpacked data for OwnerChanged events raised by the Bindings contract. +type BindingsOwnerChangedIterator struct { + Event *BindingsOwnerChanged // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *BindingsOwnerChangedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(BindingsOwnerChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(BindingsOwnerChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *BindingsOwnerChangedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *BindingsOwnerChangedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// BindingsOwnerChanged represents a OwnerChanged event raised by the Bindings contract. +type BindingsOwnerChanged struct { + NewOwner common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOwnerChanged is a free log retrieval operation binding the contract event 0xa2ea9883a321a3e97b8266c2b078bfeec6d50c711ed71f874a90d500ae2eaf36. +// +// Solidity: event OwnerChanged(address indexed new_owner) +func (_Bindings *BindingsFilterer) FilterOwnerChanged(opts *bind.FilterOpts, new_owner []common.Address) (*BindingsOwnerChangedIterator, error) { + + var new_ownerRule []interface{} + for _, new_ownerItem := range new_owner { + new_ownerRule = append(new_ownerRule, new_ownerItem) + } + + logs, sub, err := _Bindings.contract.FilterLogs(opts, "OwnerChanged", new_ownerRule) + if err != nil { + return nil, err + } + return &BindingsOwnerChangedIterator{contract: _Bindings.contract, event: "OwnerChanged", logs: logs, sub: sub}, nil +} + +// WatchOwnerChanged is a free log subscription operation binding the contract event 0xa2ea9883a321a3e97b8266c2b078bfeec6d50c711ed71f874a90d500ae2eaf36. +// +// Solidity: event OwnerChanged(address indexed new_owner) +func (_Bindings *BindingsFilterer) WatchOwnerChanged(opts *bind.WatchOpts, sink chan<- *BindingsOwnerChanged, new_owner []common.Address) (event.Subscription, error) { + + var new_ownerRule []interface{} + for _, new_ownerItem := range new_owner { + new_ownerRule = append(new_ownerRule, new_ownerItem) + } + + logs, sub, err := _Bindings.contract.WatchLogs(opts, "OwnerChanged", new_ownerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(BindingsOwnerChanged) + if err := _Bindings.contract.UnpackLog(event, "OwnerChanged", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOwnerChanged is a log parse operation binding the contract event 0xa2ea9883a321a3e97b8266c2b078bfeec6d50c711ed71f874a90d500ae2eaf36. +// +// Solidity: event OwnerChanged(address indexed new_owner) +func (_Bindings *BindingsFilterer) ParseOwnerChanged(log types.Log) (*BindingsOwnerChanged, error) { + event := new(BindingsOwnerChanged) + if err := _Bindings.contract.UnpackLog(event, "OwnerChanged", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// BindingsRelayAddedIterator is returned from FilterRelayAdded and is used to iterate over the raw logs and unpacked data for RelayAdded events raised by the Bindings contract. +type BindingsRelayAddedIterator struct { + Event *BindingsRelayAdded // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *BindingsRelayAddedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(BindingsRelayAdded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(BindingsRelayAdded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *BindingsRelayAddedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *BindingsRelayAddedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// BindingsRelayAdded represents a RelayAdded event raised by the Bindings contract. +type BindingsRelayAdded struct { + UriHash common.Hash + Relay domain.RelayAllowed + Raw types.Log // Blockchain specific contextual infos +} + +// FilterRelayAdded is a free log retrieval operation binding the contract event 0xeee5faa84d45af657ab405cdbf2c6a8a6d466e83fa694a358fee5ff84431d0bf. +// +// Solidity: event RelayAdded(string indexed uri_hash, (string,string,bool,string) relay) +func (_Bindings *BindingsFilterer) FilterRelayAdded(opts *bind.FilterOpts, uri_hash []string) (*BindingsRelayAddedIterator, error) { + + var uri_hashRule []interface{} + for _, uri_hashItem := range uri_hash { + uri_hashRule = append(uri_hashRule, uri_hashItem) + } + + logs, sub, err := _Bindings.contract.FilterLogs(opts, "RelayAdded", uri_hashRule) + if err != nil { + return nil, err + } + return &BindingsRelayAddedIterator{contract: _Bindings.contract, event: "RelayAdded", logs: logs, sub: sub}, nil +} + +// WatchRelayAdded is a free log subscription operation binding the contract event 0xeee5faa84d45af657ab405cdbf2c6a8a6d466e83fa694a358fee5ff84431d0bf. +// +// Solidity: event RelayAdded(string indexed uri_hash, (string,string,bool,string) relay) +func (_Bindings *BindingsFilterer) WatchRelayAdded(opts *bind.WatchOpts, sink chan<- *BindingsRelayAdded, uri_hash []string) (event.Subscription, error) { + + var uri_hashRule []interface{} + for _, uri_hashItem := range uri_hash { + uri_hashRule = append(uri_hashRule, uri_hashItem) + } + + logs, sub, err := _Bindings.contract.WatchLogs(opts, "RelayAdded", uri_hashRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(BindingsRelayAdded) + if err := _Bindings.contract.UnpackLog(event, "RelayAdded", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseRelayAdded is a log parse operation binding the contract event 0xeee5faa84d45af657ab405cdbf2c6a8a6d466e83fa694a358fee5ff84431d0bf. +// +// Solidity: event RelayAdded(string indexed uri_hash, (string,string,bool,string) relay) +func (_Bindings *BindingsFilterer) ParseRelayAdded(log types.Log) (*BindingsRelayAdded, error) { + event := new(BindingsRelayAdded) + if err := _Bindings.contract.UnpackLog(event, "RelayAdded", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// BindingsRelayRemovedIterator is returned from FilterRelayRemoved and is used to iterate over the raw logs and unpacked data for RelayRemoved events raised by the Bindings contract. +type BindingsRelayRemovedIterator struct { + Event *BindingsRelayRemoved // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *BindingsRelayRemovedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(BindingsRelayRemoved) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(BindingsRelayRemoved) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *BindingsRelayRemovedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *BindingsRelayRemovedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// BindingsRelayRemoved represents a RelayRemoved event raised by the Bindings contract. +type BindingsRelayRemoved struct { + UriHash common.Hash + Uri string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterRelayRemoved is a free log retrieval operation binding the contract event 0xef756634af7ee7210f786ec0f91930afa63fda84d9ff6493ae681c332055dadb. +// +// Solidity: event RelayRemoved(string indexed uri_hash, string uri) +func (_Bindings *BindingsFilterer) FilterRelayRemoved(opts *bind.FilterOpts, uri_hash []string) (*BindingsRelayRemovedIterator, error) { + + var uri_hashRule []interface{} + for _, uri_hashItem := range uri_hash { + uri_hashRule = append(uri_hashRule, uri_hashItem) + } + + logs, sub, err := _Bindings.contract.FilterLogs(opts, "RelayRemoved", uri_hashRule) + if err != nil { + return nil, err + } + return &BindingsRelayRemovedIterator{contract: _Bindings.contract, event: "RelayRemoved", logs: logs, sub: sub}, nil +} + +// WatchRelayRemoved is a free log subscription operation binding the contract event 0xef756634af7ee7210f786ec0f91930afa63fda84d9ff6493ae681c332055dadb. +// +// Solidity: event RelayRemoved(string indexed uri_hash, string uri) +func (_Bindings *BindingsFilterer) WatchRelayRemoved(opts *bind.WatchOpts, sink chan<- *BindingsRelayRemoved, uri_hash []string) (event.Subscription, error) { + + var uri_hashRule []interface{} + for _, uri_hashItem := range uri_hash { + uri_hashRule = append(uri_hashRule, uri_hashItem) + } + + logs, sub, err := _Bindings.contract.WatchLogs(opts, "RelayRemoved", uri_hashRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(BindingsRelayRemoved) + if err := _Bindings.contract.UnpackLog(event, "RelayRemoved", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseRelayRemoved is a log parse operation binding the contract event 0xef756634af7ee7210f786ec0f91930afa63fda84d9ff6493ae681c332055dadb. +// +// Solidity: event RelayRemoved(string indexed uri_hash, string uri) +func (_Bindings *BindingsFilterer) ParseRelayRemoved(log types.Log) (*BindingsRelayRemoved, error) { + event := new(BindingsRelayRemoved) + if err := _Bindings.contract.UnpackLog(event, "RelayRemoved", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/internal/adapters/mevBoostRelaysAllowList/mevboost_relays_allowlist_adapter.go b/internal/adapters/mevBoostRelaysAllowList/mevboost_relays_allowlist_adapter.go new file mode 100644 index 0000000..a97341b --- /dev/null +++ b/internal/adapters/mevBoostRelaysAllowList/mevboost_relays_allowlist_adapter.go @@ -0,0 +1,54 @@ +package relayschecker + +import ( + "context" + "fmt" + + "lido-events/internal/adapters/mevBoostRelaysAllowList/bindings" + "lido-events/internal/application/domain" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" +) + +type RelaysCheckerAdapter struct { + Client *ethclient.Client + MevBoostRelaysAllowListAddress common.Address + DappmanagerUrl string + MevBoostDnpName string +} + +func NewRelaysCheckerAdapter( + wsURL string, + mevBoostRelaysAllowListAddress common.Address, + dappmanagerUrl string, + mevBoostDnpName string, +) (*RelaysCheckerAdapter, error) { + client, err := ethclient.Dial(wsURL) + if err != nil { + return nil, fmt.Errorf("failed to connect to Ethereum client at %s: %w", wsURL, err) + } + + return &RelaysCheckerAdapter{ + client, + mevBoostRelaysAllowListAddress, + dappmanagerUrl, + mevBoostDnpName, + }, nil +} + +// getRelaysAllowList fetches relays allow list from the SC +func (mev *RelaysCheckerAdapter) GetRelaysAllowList(ctx context.Context) ([]domain.RelayAllowed, error) { + relaysAllowListContract, err := bindings.NewBindings(mev.MevBoostRelaysAllowListAddress, mev.Client) + if err != nil { + return nil, fmt.Errorf("failed to create MevBoostRelaysAllowList contract instance: %w", err) + } + + relaysAllowList, err := relaysAllowListContract.GetRelays(&bind.CallOpts{Context: ctx}) + if err != nil { + return nil, fmt.Errorf("failed to get relays allow list: %w", err) + } + + return relaysAllowList, nil +} diff --git a/internal/application/domain/mevboost_relays_allowlist_events.go b/internal/application/domain/mevboost_relays_allowlist_events.go new file mode 100644 index 0000000..09b85cf --- /dev/null +++ b/internal/application/domain/mevboost_relays_allowlist_events.go @@ -0,0 +1,8 @@ +package domain + +type RelayAllowed struct { + Uri string + Operator string + IsMandatory bool + Description string +} diff --git a/internal/application/ports/relaysallowed_port.go b/internal/application/ports/relaysallowed_port.go new file mode 100644 index 0000000..9dc03ce --- /dev/null +++ b/internal/application/ports/relaysallowed_port.go @@ -0,0 +1,10 @@ +package ports + +import ( + "context" + "lido-events/internal/application/domain" +) + +type RelaysAllowedPort interface { + GetRelaysAllowList(ctx context.Context) ([]domain.RelayAllowed, error) +} diff --git a/internal/application/ports/relaysused_port.go b/internal/application/ports/relaysused_port.go new file mode 100644 index 0000000..fb36a52 --- /dev/null +++ b/internal/application/ports/relaysused_port.go @@ -0,0 +1,7 @@ +package ports + +import "context" + +type RelaysUsedPort interface { + GetRelaysUsed(ctx context.Context) ([]string, error) +} diff --git a/internal/application/services/relaysChecker.go b/internal/application/services/relaysChecker.go new file mode 100644 index 0000000..38e981e --- /dev/null +++ b/internal/application/services/relaysChecker.go @@ -0,0 +1,148 @@ +package services + +import ( + "context" + "fmt" + "sync" + "time" + + "lido-events/internal/application/domain" + "lido-events/internal/application/ports" + "lido-events/internal/logger" +) + +type RelayCronService struct { + relaysAllowedPort ports.RelaysAllowedPort + relaysUsedPort ports.RelaysUsedPort + notifierPort ports.NotifierPort + servicePrefix string +} + +func NewRelayCronService( + relaysAllowedPort ports.RelaysAllowedPort, + relaysUsedPort ports.RelaysUsedPort, + notifierPort ports.NotifierPort, + servicePrefix string, +) *RelayCronService { + return &RelayCronService{ + relaysAllowedPort: relaysAllowedPort, + relaysUsedPort: relaysUsedPort, + notifierPort: notifierPort, + servicePrefix: "RelaysChecker", + } +} + +func (rcs *RelayCronService) StartRelayMonitoringCron(ctx context.Context, interval time.Duration, wg *sync.WaitGroup, firstExecutionComplete chan struct{}) { + defer wg.Done() + wg.Add(1) + + <-firstExecutionComplete + logger.DebugWithPrefix(rcs.servicePrefix, "Signal received, starting periodic cron for monitoring relays") + + // Execute immediately on startup + if err := rcs.monitorRelays(ctx); err != nil { + logger.ErrorWithPrefix(rcs.servicePrefix, "Error monitoring relays: %v", err) + } + + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + if err := rcs.monitorRelays(ctx); err != nil { + logger.ErrorWithPrefix(rcs.servicePrefix, "Error monitoring relays: %v", err) + } + case <-ctx.Done(): + logger.InfoWithPrefix(rcs.servicePrefix, "Stopping periodic cron for monitoring relays") + return + } + } +} + +func (rcs *RelayCronService) monitorRelays(ctx context.Context) error { + // Fetch allowed relays + allowedRelays, err := rcs.relaysAllowedPort.GetRelaysAllowList(ctx) + if err != nil { + return fmt.Errorf("failed to fetch allowed relays: %w", err) + } + + // Fetch used relays + usedRelays, err := rcs.relaysUsedPort.GetRelaysUsed(ctx) + if err != nil { + return fmt.Errorf("failed to fetch used relays: %w", err) + } + + // Analyze relays + blacklistedRelays, missingMandatoryRelays := rcs.analyzeRelays(allowedRelays, usedRelays) + + // Send notifications if issues are found + if len(blacklistedRelays) > 0 { + message := rcs.buildBlacklistNotification(blacklistedRelays) + if err := rcs.notifierPort.SendNotification(message); err != nil { + return fmt.Errorf("failed to send blacklist notification: %w", err) + } + } + + if len(missingMandatoryRelays) > 0 { + message := rcs.buildMissingMandatoryNotification(missingMandatoryRelays) + if err := rcs.notifierPort.SendNotification(message); err != nil { + return fmt.Errorf("failed to send missing mandatory relays notification: %w", err) + } + } + + return nil +} + +func (rcs *RelayCronService) analyzeRelays(allowedRelays []domain.RelayAllowed, usedRelays []string) ([]string, []domain.RelayAllowed) { + allowedMap := make(map[string]domain.RelayAllowed) + for _, relay := range allowedRelays { + allowedMap[relay.Uri] = relay + } + + var blacklistedRelays []string + var missingMandatoryRelays []domain.RelayAllowed + + // Check for blacklisted relays + for _, used := range usedRelays { + if _, exists := allowedMap[used]; !exists { + blacklistedRelays = append(blacklistedRelays, used) + } + } + + // Check for missing mandatory relays + for _, allowed := range allowedRelays { + if allowed.IsMandatory { + found := false + for _, used := range usedRelays { + if allowed.Uri == used { + found = true + break + } + } + if !found { + missingMandatoryRelays = append(missingMandatoryRelays, allowed) + } + } + } + + return blacklistedRelays, missingMandatoryRelays +} + +func (rcs *RelayCronService) buildBlacklistNotification(blacklistedRelays []string) string { + message := "🚨 *Relay Blacklist Warning*\n\nThe following relays are being used but are not allowed:\n" + for _, relay := range blacklistedRelays { + message += fmt.Sprintf("- `%s`\n", relay) + } + message += "\nPlease review and address these issues promptly." + return message +} + +func (rcs *RelayCronService) buildMissingMandatoryNotification(missingMandatoryRelays []domain.RelayAllowed) string { + message := "⚠️ *Mandatory Relay Missing*\n\nThe following mandatory relays are not being used:\n" + for _, relay := range missingMandatoryRelays { + message += fmt.Sprintf("- `%s` (Operator: %s, Description: %s)\n", relay.Uri, relay.Operator, relay.Description) + } + message += "\nPlease ensure these relays are used as required." + return message +} diff --git a/internal/config/config_loader.go b/internal/config/config_loader.go index 4f4cbae..e0ab285 100644 --- a/internal/config/config_loader.go +++ b/internal/config/config_loader.go @@ -11,6 +11,8 @@ import ( ) type Config struct { + MevBoostDnpName string + DappmanagerUrl string SignerUrl string IpfsUrl string WsURL string @@ -24,11 +26,12 @@ type Config struct { CORS []string // Individual contract addresses - CSAccountingAddress common.Address - CSFeeDistributorAddress common.Address - CSFeeDistributorImplAddress common.Address - VEBOAddress common.Address - CSModuleAddress common.Address + CSAccountingAddress common.Address + CSFeeDistributorAddress common.Address + CSFeeDistributorImplAddress common.Address + VEBOAddress common.Address + CSModuleAddress common.Address + MEVBoostRelaysAllowListAddres common.Address // Block number of the deployment of the VEBO contract and the CSFeeDistributor contract VeboBlockDeployment uint64 @@ -60,6 +63,11 @@ func parseCORS(envCORS string, defaultCORS []string) []string { } func LoadNetworkConfig() (Config, error) { + dappmanagerUrl := os.Getenv("DAPPMANAGER_URL") + if dappmanagerUrl == "" { + dappmanagerUrl = "http://my.dappnode" + } + apiPortStr := os.Getenv("API_PORT") apiPort := uint64(8080) if apiPortStr != "" { @@ -119,6 +127,8 @@ func LoadNetworkConfig() (Config, error) { beaconchainURL = "http://beacon-chain.holesky.dncore.dappnode:3500" } config = Config{ + MevBoostDnpName: "mev-boost-holesky.dnp.dappnode.eth", + DappmanagerUrl: dappmanagerUrl, SignerUrl: "http://signer.holesky.dncore.dappnode:9000", IpfsUrl: ipfsUrl, WsURL: wsURL, @@ -133,6 +143,7 @@ func LoadNetworkConfig() (Config, error) { CSFeeDistributorAddress: common.HexToAddress("0xD7ba648C8F72669C6aE649648B516ec03D07c8ED"), CSFeeDistributorImplAddress: common.HexToAddress("0xe1863C61d2AF2899f06223152ebaaf993C29aEa7"), VEBOAddress: common.HexToAddress("0xffDDF7025410412deaa05E3E1cE68FE53208afcb"), + MEVBoostRelaysAllowListAddres: common.HexToAddress("0x2d86C5855581194a386941806E38cA119E50aEA3"), VeboBlockDeployment: uint64(30701), CsFeeDistributorBlockDeployment: uint64(1774650), CSModuleAddress: common.HexToAddress("0x4562c3e63c2e586cD1651B958C22F88135aCAd4f"), @@ -151,6 +162,8 @@ func LoadNetworkConfig() (Config, error) { beaconchainURL = "http://beacon-chain.mainnet.dncore.dappnode:3500" } config = Config{ + MevBoostDnpName: "mev-boost.dnp.dappnode.eth", + DappmanagerUrl: dappmanagerUrl, SignerUrl: "http://signer.mainnet.dncore.dappnode:9000", IpfsUrl: ipfsUrl, WsURL: wsURL, @@ -165,6 +178,7 @@ func LoadNetworkConfig() (Config, error) { CSFeeDistributorAddress: common.HexToAddress("0xD99CC66fEC647E68294C6477B40fC7E0F6F618D0"), CSFeeDistributorImplAddress: common.HexToAddress("0x17Fc610ecbbAc3f99751b3B2aAc1bA2b22E444f0"), VEBOAddress: common.HexToAddress("0x0De4Ea0184c2ad0BacA7183356Aea5B8d5Bf5c6e"), + MEVBoostRelaysAllowListAddres: common.HexToAddress("0xF95f069F9AD107938F6ba802a3da87892298610E"), VeboBlockDeployment: uint64(17172556), CsFeeDistributorBlockDeployment: uint64(20935463), CSModuleAddress: common.HexToAddress("0xdA7dE2ECdDfccC6c3AF10108Db212ACBBf9EA83F"), From be65624191c8459bad15baeffbffdf22d8a15e60 Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Tue, 3 Dec 2024 11:23:29 +0100 Subject: [PATCH 2/7] Implement api --- cmd/main.go | 12 ++++---- internal/adapters/api/api_adapter.go | 28 +++++++++++++++++-- .../bindings/mevboost_relays_allowlist.go | 0 .../relaysAllowed_adapter.go} | 14 +++++----- .../relaysUsed_adapter.go} | 12 ++++---- 5 files changed, 45 insertions(+), 21 deletions(-) rename internal/adapters/{mevBoostRelaysAllowList => relaysAllowed}/bindings/mevboost_relays_allowlist.go (100%) rename internal/adapters/{mevBoostRelaysAllowList/mevboost_relays_allowlist_adapter.go => relaysAllowed/relaysAllowed_adapter.go} (81%) rename internal/adapters/{dappmanager/dappmanager_adapter.go => relaysUsed/relaysUsed_adapter.go} (68%) diff --git a/cmd/main.go b/cmd/main.go index a5cc9bf..a1c92b6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -7,13 +7,13 @@ import ( csfeedistributor "lido-events/internal/adapters/csFeeDistributor" csfeedistributorimpl "lido-events/internal/adapters/csFeeDistributorImpl" csmodule "lido-events/internal/adapters/csModule" - "lido-events/internal/adapters/dappmanager" "lido-events/internal/adapters/execution" exitvalidator "lido-events/internal/adapters/exitValidator" "lido-events/internal/adapters/ipfs" - relayschecker "lido-events/internal/adapters/mevBoostRelaysAllowList" "lido-events/internal/adapters/notifier" proxyapi "lido-events/internal/adapters/proxyApi" + relaysallowed "lido-events/internal/adapters/relaysAllowed" + relaysused "lido-events/internal/adapters/relaysUsed" "lido-events/internal/adapters/storage" "lido-events/internal/adapters/vebo" "lido-events/internal/logger" @@ -69,14 +69,14 @@ func main() { if err != nil { logger.Warn("Telegram notifier not initialized: %v", err) } - dappmanagerAdapter := dappmanager.NewDappmanagerAdapter(networkConfig.DappmanagerUrl, networkConfig.MevBoostDnpName) - relaysAllowListAdapter, err := relayschecker.NewRelaysCheckerAdapter(networkConfig.WsURL, networkConfig.MEVBoostRelaysAllowListAddres, networkConfig.DappmanagerUrl, networkConfig.MevBoostDnpName) + relaysUsedAdapter := relaysused.NewRelaysUsedAdapter(networkConfig.DappmanagerUrl, networkConfig.MevBoostDnpName) + relaysAllowedAdapter, err := relaysallowed.NewRelaysAllowedAdapter(networkConfig.WsURL, networkConfig.MEVBoostRelaysAllowListAddres, networkConfig.DappmanagerUrl, networkConfig.MevBoostDnpName) if err != nil { - logger.Fatal("Failed to initialize RelaysChecker adapter: %v", err) + logger.Fatal("Failed to initialize relaysAllowedAdapter: %v", err) } // Start HTTP server - apiAdapter := api.NewAPIAdapter(storageAdapter, notifierAdapter, networkConfig.CORS) + apiAdapter := api.NewAPIAdapter(ctx, storageAdapter, notifierAdapter, relaysUsedAdapter, relaysAllowedAdapter, networkConfig.CORS) server := &http.Server{ Addr: ":" + strconv.FormatUint(networkConfig.ApiPort, 10), Handler: apiAdapter.Router, diff --git a/internal/adapters/api/api_adapter.go b/internal/adapters/api/api_adapter.go index a39eeb9..5d0df58 100644 --- a/internal/adapters/api/api_adapter.go +++ b/internal/adapters/api/api_adapter.go @@ -1,6 +1,7 @@ package api import ( + "context" "encoding/json" "math/big" "net/http" @@ -15,6 +16,7 @@ import ( // APIHandler holds the necessary dependencies for API endpoints type APIHandler struct { + ctx context.Context StoragePort ports.StoragePort NotifierPort ports.NotifierPort RelaysUsedPort ports.RelaysUsedPort @@ -24,8 +26,9 @@ type APIHandler struct { } // NewAPIAdapter initializes the APIHandler and sets up routes with CORS enabled -func NewAPIAdapter(storagePort ports.StoragePort, notifierPort ports.NotifierPort, relaysUsedPort ports.RelaysUsedPort, relaysAllowedPort ports.RelaysAllowedPort, allowedOrigins []string) *APIHandler { +func NewAPIAdapter(ctx context.Context, storagePort ports.StoragePort, notifierPort ports.NotifierPort, relaysUsedPort ports.RelaysUsedPort, relaysAllowedPort ports.RelaysAllowedPort, allowedOrigins []string) *APIHandler { h := &APIHandler{ + ctx: ctx, StoragePort: storagePort, NotifierPort: notifierPort, RelaysUsedPort: relaysUsedPort, @@ -73,7 +76,7 @@ func (h *APIHandler) SetupRoutes() { // GetRelaysAllowed retrieves the list of allowed relays func (h *APIHandler) GetRelaysAllowed(w http.ResponseWriter, r *http.Request) { logger.DebugWithPrefix("API", "GetRelaysAllowed request received") - relays, err := h.RelaysAllowedPort.GetRelaysAllowList() + relays, err := h.RelaysAllowedPort.GetRelaysAllowList(h.ctx) if err != nil { logger.ErrorWithPrefix("API", "Error fetching allowed relays: %v", err) writeErrorResponse(w, "Error fetching allowed relays", http.StatusInternalServerError) @@ -91,6 +94,27 @@ func (h *APIHandler) GetRelaysAllowed(w http.ResponseWriter, r *http.Request) { w.Write(jsonResponse) } +// GetRelaysUsed retrieves the list of used relays +func (h *APIHandler) GetRelaysUsed(w http.ResponseWriter, r *http.Request) { + logger.DebugWithPrefix("API", "GetRelaysUsed request received") + relays, err := h.RelaysUsedPort.GetRelaysUsed(h.ctx) + if err != nil { + logger.ErrorWithPrefix("API", "Error fetching used relays: %v", err) + writeErrorResponse(w, "Error fetching used relays", http.StatusInternalServerError) + return + } + + jsonResponse, err := json.Marshal(relays) + if err != nil { + logger.ErrorWithPrefix("API", "Error generating JSON response in GetRelaysUsed: %v", err) + writeErrorResponse(w, "Error generating JSON response", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(jsonResponse) +} + // GetTelegramConfig retrieves the Telegram configuration func (h *APIHandler) GetTelegramConfig(w http.ResponseWriter, r *http.Request) { logger.DebugWithPrefix("API", "GetTelegramConfig request received") diff --git a/internal/adapters/mevBoostRelaysAllowList/bindings/mevboost_relays_allowlist.go b/internal/adapters/relaysAllowed/bindings/mevboost_relays_allowlist.go similarity index 100% rename from internal/adapters/mevBoostRelaysAllowList/bindings/mevboost_relays_allowlist.go rename to internal/adapters/relaysAllowed/bindings/mevboost_relays_allowlist.go diff --git a/internal/adapters/mevBoostRelaysAllowList/mevboost_relays_allowlist_adapter.go b/internal/adapters/relaysAllowed/relaysAllowed_adapter.go similarity index 81% rename from internal/adapters/mevBoostRelaysAllowList/mevboost_relays_allowlist_adapter.go rename to internal/adapters/relaysAllowed/relaysAllowed_adapter.go index a97341b..8709b31 100644 --- a/internal/adapters/mevBoostRelaysAllowList/mevboost_relays_allowlist_adapter.go +++ b/internal/adapters/relaysAllowed/relaysAllowed_adapter.go @@ -1,10 +1,10 @@ -package relayschecker +package relaysallowed import ( "context" "fmt" - "lido-events/internal/adapters/mevBoostRelaysAllowList/bindings" + "lido-events/internal/adapters/relaysAllowed/bindings" "lido-events/internal/application/domain" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -12,25 +12,25 @@ import ( "github.com/ethereum/go-ethereum/ethclient" ) -type RelaysCheckerAdapter struct { +type RelaysAllowedAdapter struct { Client *ethclient.Client MevBoostRelaysAllowListAddress common.Address DappmanagerUrl string MevBoostDnpName string } -func NewRelaysCheckerAdapter( +func NewRelaysAllowedAdapter( wsURL string, mevBoostRelaysAllowListAddress common.Address, dappmanagerUrl string, mevBoostDnpName string, -) (*RelaysCheckerAdapter, error) { +) (*RelaysAllowedAdapter, error) { client, err := ethclient.Dial(wsURL) if err != nil { return nil, fmt.Errorf("failed to connect to Ethereum client at %s: %w", wsURL, err) } - return &RelaysCheckerAdapter{ + return &RelaysAllowedAdapter{ client, mevBoostRelaysAllowListAddress, dappmanagerUrl, @@ -39,7 +39,7 @@ func NewRelaysCheckerAdapter( } // getRelaysAllowList fetches relays allow list from the SC -func (mev *RelaysCheckerAdapter) GetRelaysAllowList(ctx context.Context) ([]domain.RelayAllowed, error) { +func (mev *RelaysAllowedAdapter) GetRelaysAllowList(ctx context.Context) ([]domain.RelayAllowed, error) { relaysAllowListContract, err := bindings.NewBindings(mev.MevBoostRelaysAllowListAddress, mev.Client) if err != nil { return nil, fmt.Errorf("failed to create MevBoostRelaysAllowList contract instance: %w", err) diff --git a/internal/adapters/dappmanager/dappmanager_adapter.go b/internal/adapters/relaysUsed/relaysUsed_adapter.go similarity index 68% rename from internal/adapters/dappmanager/dappmanager_adapter.go rename to internal/adapters/relaysUsed/relaysUsed_adapter.go index 2fb98f4..9f55e1d 100644 --- a/internal/adapters/dappmanager/dappmanager_adapter.go +++ b/internal/adapters/relaysUsed/relaysUsed_adapter.go @@ -1,4 +1,4 @@ -package dappmanager +package relaysused import ( "context" @@ -7,21 +7,21 @@ import ( "net/http" ) -type DappmanagerAdapter struct { +type RelaysUsedAdapter struct { dappmanagerUrl string mevBootDnpName string } -// NewDappmanagerAdapter creates a new instance of DappmanagerAdapter with provided dappmanagerUrl and mevBoostDnpName -func NewDappmanagerAdapter(dappmanagerUrl, mevBoostDnpName string) *DappmanagerAdapter { - return &DappmanagerAdapter{ +// NewRelaysUsedAdapter creates a new instance of DappmanagerAdapter with provided dappmanagerUrl and mevBoostDnpName +func NewRelaysUsedAdapter(dappmanagerUrl, mevBoostDnpName string) *RelaysUsedAdapter { + return &RelaysUsedAdapter{ dappmanagerUrl, mevBoostDnpName, } } // GetRelaysUsed fetches env RELAYS from MEV BOOST pkg -func (da *DappmanagerAdapter) GetRelaysUsed(ctx context.Context) ([]string, error) { +func (da *RelaysUsedAdapter) GetRelaysUsed(ctx context.Context) ([]string, error) { url := fmt.Sprintf("%s/env/%s?envName=RELAYS", da.dappmanagerUrl, da.mevBootDnpName) req, err := http.NewRequest("GET", url, nil) if err != nil { From fefee2f65c65e4bc1b151e449724cd2d56e55b61 Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Tue, 3 Dec 2024 11:34:33 +0100 Subject: [PATCH 3/7] remove first execution complete --- cmd/main.go | 4 ++++ internal/application/services/relaysChecker.go | 6 +----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index a1c92b6..bcc40ca 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -136,6 +136,10 @@ func main() { validatorExitRequestScannerService := services.NewValidatorExitRequestEventScanner(storageAdapter, notifierAdapter, veboAdapter, executionAdapter, beaconchainAdapter, networkConfig.VeboBlockDeployment) validatorEjectorService := services.NewValidatorEjectorService(storageAdapter, notifierAdapter, exitValidatorAdapter, beaconchainAdapter) pendingHashesLoaderService := services.NewPendingHashesLoader(storageAdapter, ipfsAdapter) + relaysCheckerService := services.NewRelayCronService(relaysAllowedAdapter, relaysUsedAdapter, notifierAdapter) + + // Relays + relaysCheckerService.StartRelayMonitoringCron(ctx, 5*time.Minute, &wg) // DistributionLogUpdated distributionLogUpdatedExecutionComplete := make(chan struct{}) diff --git a/internal/application/services/relaysChecker.go b/internal/application/services/relaysChecker.go index 38e981e..f6cdc5e 100644 --- a/internal/application/services/relaysChecker.go +++ b/internal/application/services/relaysChecker.go @@ -22,7 +22,6 @@ func NewRelayCronService( relaysAllowedPort ports.RelaysAllowedPort, relaysUsedPort ports.RelaysUsedPort, notifierPort ports.NotifierPort, - servicePrefix string, ) *RelayCronService { return &RelayCronService{ relaysAllowedPort: relaysAllowedPort, @@ -32,13 +31,10 @@ func NewRelayCronService( } } -func (rcs *RelayCronService) StartRelayMonitoringCron(ctx context.Context, interval time.Duration, wg *sync.WaitGroup, firstExecutionComplete chan struct{}) { +func (rcs *RelayCronService) StartRelayMonitoringCron(ctx context.Context, interval time.Duration, wg *sync.WaitGroup) { defer wg.Done() wg.Add(1) - <-firstExecutionComplete - logger.DebugWithPrefix(rcs.servicePrefix, "Signal received, starting periodic cron for monitoring relays") - // Execute immediately on startup if err := rcs.monitorRelays(ctx); err != nil { logger.ErrorWithPrefix(rcs.servicePrefix, "Error monitoring relays: %v", err) From d78c4d4a7000f00bb94b35364b76192a75c49e85 Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Tue, 3 Dec 2024 11:37:15 +0100 Subject: [PATCH 4/7] run go --- cmd/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/main.go b/cmd/main.go index bcc40ca..2b09d11 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -139,7 +139,7 @@ func main() { relaysCheckerService := services.NewRelayCronService(relaysAllowedAdapter, relaysUsedAdapter, notifierAdapter) // Relays - relaysCheckerService.StartRelayMonitoringCron(ctx, 5*time.Minute, &wg) + go relaysCheckerService.StartRelayMonitoringCron(ctx, 5*time.Minute, &wg) // DistributionLogUpdated distributionLogUpdatedExecutionComplete := make(chan struct{}) From 2d8bd1ce8ded5e9634896bd7309947dd2db51d14 Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Tue, 3 Dec 2024 16:10:05 +0100 Subject: [PATCH 5/7] fix get relays --- .../adapters/relaysUsed/relaysUsed_adapter.go | 31 +++++++++--- .../application/services/relaysChecker.go | 47 ++++++++++--------- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/internal/adapters/relaysUsed/relaysUsed_adapter.go b/internal/adapters/relaysUsed/relaysUsed_adapter.go index 9f55e1d..96bb588 100644 --- a/internal/adapters/relaysUsed/relaysUsed_adapter.go +++ b/internal/adapters/relaysUsed/relaysUsed_adapter.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "strings" ) type RelaysUsedAdapter struct { @@ -23,7 +24,7 @@ func NewRelaysUsedAdapter(dappmanagerUrl, mevBoostDnpName string) *RelaysUsedAda // GetRelaysUsed fetches env RELAYS from MEV BOOST pkg func (da *RelaysUsedAdapter) GetRelaysUsed(ctx context.Context) ([]string, error) { url := fmt.Sprintf("%s/env/%s?envName=RELAYS", da.dappmanagerUrl, da.mevBootDnpName) - req, err := http.NewRequest("GET", url, nil) + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, fmt.Errorf("failed to create GET request for URL %s: %w", url, err) } @@ -33,13 +34,31 @@ func (da *RelaysUsedAdapter) GetRelaysUsed(ctx context.Context) ([]string, error if err != nil { return nil, fmt.Errorf("failed to send request to Dappmanager at %s: %w", url, err) } - defer resp.Body.Close() - var result []string - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - return nil, fmt.Errorf("failed to decode response for GetRelaysUsed: %w", err) + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected response status: %d", resp.StatusCode) + } + + var responseString string + if err := json.NewDecoder(resp.Body).Decode(&responseString); err != nil { + return nil, fmt.Errorf("failed to decode response body: %w", err) + } + + // Split the comma-separated string into a slice of strings + relays := []string{} + if responseString != "" { + relays = splitAndTrim(responseString, ",") } - return result, nil + return relays, nil +} + +// Helper function to split and trim strings +func splitAndTrim(input string, sep string) []string { + parts := strings.Split(input, sep) + for i := range parts { + parts[i] = strings.TrimSpace(parts[i]) + } + return parts } diff --git a/internal/application/services/relaysChecker.go b/internal/application/services/relaysChecker.go index f6cdc5e..f0b1936 100644 --- a/internal/application/services/relaysChecker.go +++ b/internal/application/services/relaysChecker.go @@ -70,7 +70,7 @@ func (rcs *RelayCronService) monitorRelays(ctx context.Context) error { } // Analyze relays - blacklistedRelays, missingMandatoryRelays := rcs.analyzeRelays(allowedRelays, usedRelays) + blacklistedRelays, mandatoryRelaysUsed := rcs.analyzeRelays(allowedRelays, usedRelays) // Send notifications if issues are found if len(blacklistedRelays) > 0 { @@ -80,8 +80,9 @@ func (rcs *RelayCronService) monitorRelays(ctx context.Context) error { } } - if len(missingMandatoryRelays) > 0 { - message := rcs.buildMissingMandatoryNotification(missingMandatoryRelays) + // Change: Notify if no mandatory relays are being used + if mandatoryRelaysUsed == 0 { + message := rcs.buildMissingMandatoryNotification(allowedRelays) if err := rcs.notifierPort.SendNotification(message); err != nil { return fmt.Errorf("failed to send missing mandatory relays notification: %w", err) } @@ -90,14 +91,14 @@ func (rcs *RelayCronService) monitorRelays(ctx context.Context) error { return nil } -func (rcs *RelayCronService) analyzeRelays(allowedRelays []domain.RelayAllowed, usedRelays []string) ([]string, []domain.RelayAllowed) { +func (rcs *RelayCronService) analyzeRelays(allowedRelays []domain.RelayAllowed, usedRelays []string) ([]string, int) { allowedMap := make(map[string]domain.RelayAllowed) for _, relay := range allowedRelays { allowedMap[relay.Uri] = relay } var blacklistedRelays []string - var missingMandatoryRelays []domain.RelayAllowed + mandatoryRelaysUsed := 0 // Check for blacklisted relays for _, used := range usedRelays { @@ -106,39 +107,41 @@ func (rcs *RelayCronService) analyzeRelays(allowedRelays []domain.RelayAllowed, } } - // Check for missing mandatory relays + // Check for mandatory relays in use for _, allowed := range allowedRelays { if allowed.IsMandatory { - found := false for _, used := range usedRelays { if allowed.Uri == used { - found = true + mandatoryRelaysUsed++ break } } - if !found { - missingMandatoryRelays = append(missingMandatoryRelays, allowed) - } } } - return blacklistedRelays, missingMandatoryRelays + return blacklistedRelays, mandatoryRelaysUsed } -func (rcs *RelayCronService) buildBlacklistNotification(blacklistedRelays []string) string { - message := "🚨 *Relay Blacklist Warning*\n\nThe following relays are being used but are not allowed:\n" - for _, relay := range blacklistedRelays { - message += fmt.Sprintf("- `%s`\n", relay) +func (rcs *RelayCronService) buildMissingMandatoryNotification(allowedRelays []domain.RelayAllowed) string { + message := "⚠️ Mandatory Relay Alert\n\nNo mandatory relays are currently in use. At least one mandatory relay must be used. Edit your relays in the stakers UI\n" + message += "\nMandatory relays:\n" + for _, relay := range allowedRelays { + if relay.IsMandatory { + message += fmt.Sprintf("- `%s` (Operator: %s, Description: %s)\n", relay.Uri, relay.Operator, relay.Description) + } + } + message += "\nPlease ensure at least one mandatory relay is in use. Allowed are:" + for _, relay := range allowedRelays { + message += fmt.Sprintf("\n- `%s`", relay.Uri) } - message += "\nPlease review and address these issues promptly." return message } -func (rcs *RelayCronService) buildMissingMandatoryNotification(missingMandatoryRelays []domain.RelayAllowed) string { - message := "⚠️ *Mandatory Relay Missing*\n\nThe following mandatory relays are not being used:\n" - for _, relay := range missingMandatoryRelays { - message += fmt.Sprintf("- `%s` (Operator: %s, Description: %s)\n", relay.Uri, relay.Operator, relay.Description) +func (rcs *RelayCronService) buildBlacklistNotification(blacklistedRelays []string) string { + message := "🚨 Relay Blacklist Warning\n\nThe following relays are being used but are not allowed:\n" + for _, relay := range blacklistedRelays { + message += fmt.Sprintf("- `%s`\n", relay) } - message += "\nPlease ensure these relays are used as required." + message += "\nPlease review and address these issues promptly." return message } From 1afcc0d576ff0ce00d4eb723d5c28e8da4c7d1f5 Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Thu, 5 Dec 2024 12:16:24 +0100 Subject: [PATCH 6/7] fix initialization --- cmd/main.go | 2 +- internal/adapters/api/api_adapter.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index c86f30a..3275300 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -54,7 +54,7 @@ func main() { logger.Fatal("Failed to initialize relaysAllowedAdapter: %v", err) } - apiAdapter := api.NewAPIAdapter(storageAdapter, relaysUsedAdapter, relaysAllowedAdapter, networkConfig.CORS) + apiAdapter := api.NewAPIAdapter(ctx, storageAdapter, relaysUsedAdapter, relaysAllowedAdapter, networkConfig.CORS) proxyApiAdapter := proxyapi.NewProxyAPIAdapter(networkConfig.CORS, networkConfig.LidoKeysApiUrl) // Initialize API services diff --git a/internal/adapters/api/api_adapter.go b/internal/adapters/api/api_adapter.go index 2a0adc4..6c57a72 100644 --- a/internal/adapters/api/api_adapter.go +++ b/internal/adapters/api/api_adapter.go @@ -34,11 +34,10 @@ func (h *APIHandler) GetRouter() http.Handler { } // NewAPIAdapter initializes the APIHandler and sets up routes with CORS enabled -func NewAPIAdapter(ctx context.Context, storagePort ports.StoragePort, notifierPort ports.NotifierPort, relaysUsedPort ports.RelaysUsedPort, relaysAllowedPort ports.RelaysAllowedPort, allowedOrigins []string) *APIHandler { +func NewAPIAdapter(ctx context.Context, storagePort ports.StoragePort, relaysUsedPort ports.RelaysUsedPort, relaysAllowedPort ports.RelaysAllowedPort, allowedOrigins []string) *APIHandler { h := &APIHandler{ ctx: ctx, StoragePort: storagePort, - NotifierPort: notifierPort, RelaysUsedPort: relaysUsedPort, RelaysAllowedPort: relaysAllowedPort, Router: mux.NewRouter(), From d5111ae25d94cd7648abd9767ba8e52dda831069 Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Thu, 5 Dec 2024 12:42:04 +0100 Subject: [PATCH 7/7] remove new line --- cmd/main.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 3275300..aa80058 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -100,10 +100,9 @@ func main() { pendingHashesLoaderService := services.NewPendingHashesLoader(storageAdapter, ipfsAdapter) relaysCheckerService := services.NewRelayCronService(relaysAllowedAdapter, relaysUsedAdapter, notifierAdapter) - // Relays + // Start domain services go relaysCheckerService.StartRelayMonitoringCron(ctx, 5*time.Minute, &wg) - // Start domain services distributionLogUpdatedExecutionComplete := make(chan struct{}) go distributionLogUpdatedScannerService.ScanDistributionLogUpdatedEventsCron(ctx, 384*time.Second, &wg, distributionLogUpdatedExecutionComplete) go pendingHashesLoaderService.LoadPendingHashesCron(ctx, 3*time.Hour, &wg, distributionLogUpdatedExecutionComplete)