generated from ethereum-optimism/.github
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Monitorism
Global_events
is module a that listen events onchain. (#20)
* feat: global_events listen on the logs of each blocks. * feat: Yaml template + logs fitering * chore: add current block infos + ChainId to ensure we are on the correct chain * chore: move the rules into a dedicated folders * chore: rename the param * feat: add logging and infos to ensure no mistake when running * feat: add the possibilities or reading of the rules at once. * feat: add a new general structure that hold the addresses and events. * feat: add the feature to monitore the empty addresses (all the addresses) + add the fact we don't suport the `sep:` inside the yaml files. * feat: Insert the `common.Address` type * feat: Monitore all the events when the addresses are empty. * feat: add the `common.hash()` to have a more robust check. * chore: add the `globalconfig.yml` this is file is NOT NECESSARY but just to share with the team if necessary. * feat: add the types tests * feat: changes the current struct to a better `GlobalConfiguration`. * feat: adds metrics + clean the codes * readme: add a short readme during the creation * chore: update the test * Rename README.md to README.md * chore: fix bugs + added the comments for the events 1 signer * feat: small git clone to fetch from github embeeded into the binary * chore: fix the name of the env * bump: version git * readme fix the `-` * chore: errors handling + rpc message error logs. * chore: fix comments + add the rules * Update README.md * Update README.md * Update README.md * chore: remove the useless comments + comment the `cloneRepo()` if needed in the future. * chore: remove the `SignerCanBeRemove()` * chore: comment the dependencies for `cloneRepo()` if we need to use it the future. * chore: remove not necessary function. * chore: remove the `CloneRepo()` function.
- Loading branch information
Showing
24 changed files
with
839 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
configuration: | ||
- version: "1.0" | ||
name: Safe Watcher | ||
priority: P0 | ||
addresses: [] | ||
events: | ||
- keccak256_signature: 0x23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d23 | ||
signature: ExecutionFailure(bytes32,uint256) | ||
- keccak256_signature: 0x442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e | ||
signature: ExecutionSuccess(bytes32,uint256) | ||
- version: "1.0" | ||
name: SystemConfig Config Updates | ||
priority: P2 | ||
addresses: [] | ||
events: | ||
- keccak256_signature: 0x82f8cc4439cd78202f3081cd05a23d895e011595628770738fc5ba8ecba469ed | ||
signature: ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# This watches all contacts for OP, Mode, and Base mainnets for two logs. | ||
# These logs are emitted by Safes, so this effectively watches for all | ||
# transactions from any Safe on these chains. | ||
version: 1.0 | ||
name: Safe Watcher | ||
priority: P0 | ||
addresses: # /!\ We are not supporting EIP 3770 yet, if the address is not starting by `0x`, this will panic by safety measure. | ||
events: | ||
- signature: ExecutionFailure(bytes32,uint256) | ||
- signature: ExecutionSuccess(bytes32,uint256) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# This watches the L1 SystemConfig for OP Mainnet for config updates. | ||
# - eth/op/SystemConfig | ||
version: 1.0 | ||
name: SystemConfig Config Updates | ||
priority: P2 | ||
addresses: # /!\ We are not supporting EIP 3770 yet, if the address is not starting by 0x, this will panic by safety measure. | ||
events: | ||
- signature: ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data) | ||
# Notice that the above signature is not normalized. The normalized version is: | ||
# ConfigUpdate(uint256,uint8,bytes) | ||
# But for UX |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
## Global Events monitoring | ||
|
||
This monitoring modules for the yaml rules added with the format ⚠️ This readme will be move into the global readme in the future. | ||
|
||
CLI and Docs: | ||
```bash | ||
NAME: | ||
Monitorism global_events - Monitors global events with YAML configuration | ||
|
||
USAGE: | ||
Monitorism global_events [command options] [arguments...] | ||
|
||
DESCRIPTION: | ||
Monitors global events with YAML configuration | ||
|
||
OPTIONS: | ||
--l1.node.url value Node URL of L1 peer (default: "http://127.0.0.1:8545") [$GLOBAL_EVENT_MON_L1_NODE_URL] | ||
--nickname value Nickname of chain being monitored [$GLOBAL_EVENT_MON_NICKNAME] | ||
--PathYamlRules value Path to the yaml file containing the events to monitor [$GLOBAL_EVENT_MON_PATH_YAML] | ||
--log.level value The lowest log level that will be output (default: INFO) [$MONITORISM_LOG_LEVEL] | ||
--log.format value Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json', 'json-pretty', (default: text) [$MONITORISM_LOG_FORMAT] | ||
--log.color Color the log output if in terminal mode (default: false) [$MONITORISM_LOG_COLOR] | ||
--metrics.enabled Enable the metrics server (default: false) [$MONITORISM_METRICS_ENABLED] | ||
--metrics.addr value Metrics listening address (default: "0.0.0.0") [$MONITORISM_METRICS_ADDR] | ||
--metrics.port value Metrics listening port (default: 7300) [$MONITORISM_METRICS_PORT] | ||
--loop.interval.msec value Loop interval of the monitor in milliseconds (default: 60000) [$MONITORISM_LOOP_INTERVAL_MSEC] | ||
--help, -h show help | ||
|
||
``` | ||
The rules are located here: `op-monitorism/global_events/rules/` then we have multiples folders depending the networks you want to monitore (`mainnet` or `sepolia`) for now. | ||
```yaml | ||
# This is a TEMPLATE file please copy this one | ||
# This watches all contacts for OP, Mode, and Base mainnets for two logs. | ||
version: 1.0 | ||
name: Template SafeExecution Events (Success/Failure) L1 # Please put the L1 or L2 at the end of the name. | ||
priority: P5 # This is a test, so it is a P5 | ||
#If addresses is empty like below it will watch all addresses otherwise you can address specific addresses. | ||
addresses: | ||
# - 0xbEb5Fc579115071764c7423A4f12eDde41f106Ed # Specific Addresses /!\ We are not supporting EIP 3770 yet, if the address is not starting by 0x, this will panic by safety measure. | ||
events: | ||
- signature: ExecutionFailure(bytes32,uint256) # List of the events to watch for the addresses. | ||
- signature: ExecutionSuccess(bytes32,uint256) # List of the events to watch for the addresses. | ||
``` | ||
To run it: | ||
```bash | ||
|
||
go run ../cmd/monitorism global_events --nickname MySuperNickName --l1.node.url https://localhost:8545 --PathYamlRules /tmp/Monitorism/op-monitorism/global_events/rules/rules_mainnet_L1 --loop.interval.msec 12000 | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package global_events | ||
|
||
import ( | ||
// "fmt" | ||
|
||
opservice "github.com/ethereum-optimism/optimism/op-service" | ||
|
||
// "github.com/ethereum/go-ethereum/common" | ||
|
||
"github.com/urfave/cli/v2" | ||
) | ||
|
||
// args in CLI have to be standardized and clean. | ||
const ( | ||
L1NodeURLFlagName = "l1.node.url" | ||
NicknameFlagName = "nickname" | ||
PathYamlRulesFlagName = "PathYamlRules" | ||
) | ||
|
||
type CLIConfig struct { | ||
L1NodeURL string | ||
Nickname string | ||
PathYamlRules string | ||
// Optional | ||
} | ||
|
||
func ReadCLIFlags(ctx *cli.Context) (CLIConfig, error) { | ||
cfg := CLIConfig{ | ||
L1NodeURL: ctx.String(L1NodeURLFlagName), | ||
Nickname: ctx.String(NicknameFlagName), | ||
PathYamlRules: ctx.String(PathYamlRulesFlagName), | ||
} | ||
|
||
return cfg, nil | ||
} | ||
|
||
func CLIFlags(envVar string) []cli.Flag { | ||
return []cli.Flag{ | ||
&cli.StringFlag{ | ||
Name: L1NodeURLFlagName, | ||
Usage: "Node URL of L1 peer", | ||
Value: "http://127.0.0.1:8545", | ||
EnvVars: opservice.PrefixEnvVar(envVar, "L1_NODE_URL"), | ||
}, | ||
&cli.StringFlag{ | ||
Name: NicknameFlagName, | ||
Usage: "Nickname of chain being monitored", | ||
EnvVars: opservice.PrefixEnvVar(envVar, "NICKNAME"), //need to change the name to BLOCKCHAIN_NAME | ||
Required: true, | ||
}, | ||
&cli.StringFlag{ | ||
Name: PathYamlRulesFlagName, | ||
Usage: "Path to the yaml file containing the events to monitor", | ||
EnvVars: opservice.PrefixEnvVar(envVar, "PATH_YAML"), //need to change the name to BLOCKCHAIN_NAME | ||
Required: true, | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
package global_events | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/ethereum-optimism/optimism/op-bindings/bindings" | ||
"github.com/ethereum-optimism/optimism/op-service/metrics" | ||
"github.com/ethereum/go-ethereum" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/crypto" | ||
"github.com/ethereum/go-ethereum/ethclient" | ||
"github.com/ethereum/go-ethereum/log" | ||
"github.com/prometheus/client_golang/prometheus" | ||
"regexp" | ||
"strings" | ||
"time" | ||
) | ||
|
||
const ( | ||
MetricsNamespace = "global_events_mon" | ||
) | ||
|
||
// Monitor is the main struct of the monitor. | ||
type Monitor struct { | ||
log log.Logger | ||
|
||
l1Client *ethclient.Client | ||
globalconfig GlobalConfiguration | ||
// nickname is the nickname of the monitor (we need to change the name this is not an ideal one here). | ||
nickname string | ||
safeAddress *bindings.OptimismPortalCaller | ||
|
||
LiveAddress *common.Address | ||
|
||
filename string //filename of the yaml rules | ||
yamlconfig Configuration | ||
|
||
// Prometheus metrics | ||
eventEmitted *prometheus.GaugeVec | ||
unexpectedRpcErrors *prometheus.CounterVec | ||
} | ||
|
||
// ChainIDToName() allows to convert the chainID to a human readable name. | ||
// For now only ethereum + Sepolia are supported. | ||
func ChainIDToName(chainID int64) string { | ||
switch chainID { | ||
case 1: | ||
return "Ethereum [Mainnet]" | ||
case 11155111: | ||
return "Sepolia [Testnet]" | ||
} | ||
return "The `ChainID` is Not defined into the `chaindIDToName` function, this is probably a custom chain otherwise something is going wrong!" | ||
} | ||
|
||
// NewMonitor creates a new Monitor instance. | ||
func NewMonitor(ctx context.Context, log log.Logger, m metrics.Factory, cfg CLIConfig) (*Monitor, error) { | ||
l1Client, err := ethclient.Dial(cfg.L1NodeURL) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to dial l1 rpc: %w", err) | ||
} | ||
fmt.Printf("--------------------------------------- Global_events_mon (Infos) -----------------------------\n") | ||
ChainID, err := l1Client.ChainID(context.Background()) | ||
if err != nil { | ||
log.Crit("Failed to retrieve chain ID: %v", err) | ||
} | ||
header, err := l1Client.HeaderByNumber(context.Background(), nil) | ||
if err != nil { | ||
log.Crit("Failed to fetch the latest block header", "error", err) | ||
} | ||
// display the infos at the start to ensure everything is correct. | ||
fmt.Printf("latestBlockNumber: %s\n", header.Number) | ||
fmt.Printf("chainId: %+v\n", ChainIDToName(ChainID.Int64())) | ||
fmt.Printf("PathYaml: %v\n", cfg.PathYamlRules) | ||
fmt.Printf("Nickname: %v\n", cfg.Nickname) | ||
fmt.Printf("L1NodeURL: %v\n", cfg.L1NodeURL) | ||
globalConfig, err := ReadAllYamlRules(cfg.PathYamlRules) | ||
if err != nil { | ||
log.Crit("Failed to read the yaml rules", "error", err.Error()) | ||
} | ||
// create a globalconfig empty | ||
fmt.Printf("GlobalConfig: %#v\n", globalConfig.Configuration) | ||
globalConfig.DisplayMonitorAddresses() | ||
fmt.Printf("--------------------------------------- End of Infos -----------------------------\n") | ||
time.Sleep(10 * time.Second) // sleep for 10 seconds usefull to read the information before the prod. | ||
return &Monitor{ | ||
log: log, | ||
l1Client: l1Client, | ||
globalconfig: globalConfig, | ||
|
||
nickname: cfg.Nickname, | ||
eventEmitted: m.NewGaugeVec(prometheus.GaugeOpts{ | ||
Namespace: MetricsNamespace, | ||
Name: "eventEmitted", | ||
Help: "Event monitored emitted an log", | ||
}, []string{"nickname", "rulename", "priority", "functionName", "address"}), | ||
unexpectedRpcErrors: m.NewCounterVec(prometheus.CounterOpts{ | ||
Namespace: MetricsNamespace, | ||
Name: "unexpectedRpcErrors", | ||
Help: "number of unexpcted rpc errors", | ||
}, []string{"section", "name"}), | ||
}, nil | ||
} | ||
|
||
// formatSignature allows to format the signature of a function to be able to hash it. | ||
// e.g: "transfer(address owner, uint256 amount)" -> "transfer(address,uint256)" | ||
func formatSignature(signature string) string { | ||
// Regex to extract function name and parameters | ||
r := regexp.MustCompile(`(\w+)\s*\(([^)]*)\)`) | ||
matches := r.FindStringSubmatch(signature) | ||
if len(matches) != 3 { | ||
return "" | ||
} | ||
// Function name | ||
funcName := matches[1] | ||
// Parameters, split by commas | ||
params := matches[2] | ||
// Clean parameters to keep only types | ||
cleanParams := make([]string, 0) | ||
for _, param := range strings.Split(params, ",") { | ||
parts := strings.Fields(param) | ||
if len(parts) > 0 { | ||
cleanParams = append(cleanParams, parts[0]) | ||
} | ||
} | ||
// Return formatted function signature | ||
return fmt.Sprintf("%s(%s)", funcName, strings.Join(cleanParams, ",")) | ||
} | ||
|
||
// FormatAndHash allow to Format the signature (e.g: "transfer(address,uint256)") to create the keccak256 hash associated with it. | ||
// Formatting allows use to use "transfer(address owner, uint256 amount)" instead of "transfer(address,uint256)" | ||
func FormatAndHash(signature string) common.Hash { | ||
formattedSignature := formatSignature(signature) | ||
if formattedSignature == "" { | ||
panic("Invalid signature") | ||
} | ||
hash := crypto.Keccak256([]byte(formattedSignature)) | ||
return common.BytesToHash(hash) | ||
|
||
} | ||
|
||
// Run the monitor functions declared as a monitor method. | ||
func (m *Monitor) Run(ctx context.Context) { | ||
m.checkEvents(ctx) | ||
} | ||
|
||
// checkEvents function to check the events. If an events is emitted onchain and match the rules defined in the yaml file, then we will display the event. | ||
func (m *Monitor) checkEvents(ctx context.Context) { //TODO: Ensure the logs crit are not causing panic in runtime! | ||
header, err := m.l1Client.HeaderByNumber(context.Background(), nil) | ||
if err != nil { | ||
m.unexpectedRpcErrors.WithLabelValues("L1", "HeaderByNumber").Inc() | ||
m.log.Warn("Failed to retrieve latest block header", "error", err.Error()) //TODO:need to wait 12 and retry here! | ||
return | ||
} | ||
|
||
latestBlockNumber := header.Number | ||
query := ethereum.FilterQuery{ | ||
FromBlock: latestBlockNumber, | ||
ToBlock: latestBlockNumber, | ||
// Addresses: []common.Address{}, //if empty means that all addresses are monitored should be this value for optimisation and avoiding to take every logs every time -> m.globalconfig.GetUniqueMonitoredAddresses | ||
} | ||
|
||
logs, err := m.l1Client.FilterLogs(context.Background(), query) | ||
if err != nil { //TODO:need to wait 12 and retry here! | ||
m.unexpectedRpcErrors.WithLabelValues("L1", "FilterLogs").Inc() | ||
m.log.Warn("Failed to retrieve logs:", "error", err.Error()) | ||
return | ||
} | ||
|
||
for _, vLog := range logs { | ||
if len(vLog.Topics) > 0 { //Ensure no anonymous event is here. | ||
if len(m.globalconfig.SearchIfATopicIsInsideAnAlert(vLog.Topics[0]).Events) > 0 { // We matched an alert! | ||
config := m.globalconfig.SearchIfATopicIsInsideAnAlert(vLog.Topics[0]) | ||
fmt.Printf("-------------------------- Event Detected ------------------------\n") | ||
fmt.Printf("TxHash: h%s\nAddress:%s\nTopics: %s\n", vLog.TxHash, vLog.Address, vLog.Topics) | ||
fmt.Printf("The current config that matched this function: %v\n", config) | ||
fmt.Printf("----------------------------------------------------------------\n") | ||
m.eventEmitted.WithLabelValues(m.nickname, config.Name, config.Priority, config.Events[0].Signature, vLog.Address.String()).Set(float64(1)) | ||
|
||
} | ||
} | ||
|
||
} | ||
m.log.Info("Checking events..", "CurrentBlock", latestBlockNumber) | ||
|
||
} | ||
|
||
// Close closes the monitor. | ||
func (m *Monitor) Close(_ context.Context) error { | ||
m.l1Client.Close() | ||
return nil | ||
} |
Oops, something went wrong.