mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
feat: add pgeth-monitoring plugin
Signed-off-by: Isma <71719097+Doozers@users.noreply.github.com>
This commit is contained in:
parent
a86ba8fc0d
commit
9eef30aa38
7 changed files with 992 additions and 0 deletions
1
plugins/pgeth-monitoring/.gitignore
vendored
Normal file
1
plugins/pgeth-monitoring/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
plugin.so
|
||||
37
plugins/pgeth-monitoring/README.md
Normal file
37
plugins/pgeth-monitoring/README.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# pgeth-monitoring plugin
|
||||
|
||||
Runs custom tracers while simulating all transactions, encode and feed everything to redis with topics making things easy to subscribe.
|
||||
|
||||
```
|
||||
/head/tx/0xb0ba6c81c185bf7652f9339fdd86f35e47aea38a1215aa107f97d26ca5806c62/0x68c4D9E03D7D902053C428Ca2D74b612Db7F583A/0x5954aB967Bc958940b7EB73ee84797Dc8a2AFbb9/C@0x5954aB967Bc958940b7EB73ee84797Dc8a2AFbb9_20a325d0[S@0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D_6352211e,C@0x4d224452801ACEd8B2F0aebE155379bb5D594381_a9059cbb]
|
||||
```
|
||||
|
||||
The topics follow the following format
|
||||
|
||||
```
|
||||
/CHANNEL/tx/TX_HASH/FROM/TO/CALL_TRACES
|
||||
```
|
||||
|
||||
Where the example above call traces can be interpreted as:
|
||||
|
||||
```
|
||||
C@0x5954aB967Bc958940b7EB73ee84797Dc8a2AFbb9_20a325d0[S@0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D_6352211e,C@0x4d224452801ACEd8B2F0aebE155379bb5D594381_a9059cbb]
|
||||
|
||||
C@0x5954aB967Bc958940b7EB73ee84797Dc8a2AFbb9_20a325d0[
|
||||
S@0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D_6352211e,
|
||||
C@0x4d224452801ACEd8B2F0aebE155379bb5D594381_a9059cbb
|
||||
]
|
||||
```
|
||||
|
||||
- Initial call (`C`) made to `0x5954aB967Bc958940b7EB73ee84797Dc8a2AFbb9` on function selector `20a325d0`
|
||||
- Static call (`S`) made to `0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D` on function selector `6352211e`
|
||||
- Call (`C`) made to `0x4d224452801ACEd8B2F0aebE155379bb5D594381` on function selector `a9059cbb`
|
||||
|
||||
|
||||
The different call modes are
|
||||
|
||||
- `C`, a regular `call`
|
||||
- `S`, a `staticcall`
|
||||
- `D`, a `delegatecall`
|
||||
|
||||
The message payload will provide complete execution details with inputs, outputs, context and code address for every step
|
||||
4
plugins/pgeth-monitoring/config.yaml
Normal file
4
plugins/pgeth-monitoring/config.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
name: "monitoring"
|
||||
config:
|
||||
REDIS_ENDPOINT: "localhost:6379"
|
||||
BEACON_ENDPOINT: "localhost:5052"
|
||||
7
plugins/pgeth-monitoring/dependencies.sh
Executable file
7
plugins/pgeth-monitoring/dependencies.sh
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
#! /bin/bash
|
||||
|
||||
go get github.com/ethereum/go-ethereum/plugins/pgeth-monitoring
|
||||
go get github.com/redis/go-redis/v9@v9.0.3
|
||||
go get github.com/prometheus/client_golang@v1.12.0
|
||||
go get github.com/attestantio/go-eth2-client/http@v0.16.0
|
||||
go mod tidy
|
||||
511
plugins/pgeth-monitoring/pgeth_monitoring.go
Normal file
511
plugins/pgeth-monitoring/pgeth_monitoring.go
Normal file
|
|
@ -0,0 +1,511 @@
|
|||
package pgeth_monitoring
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/redis/go-redis/v9"
|
||||
|
||||
"github.com/attestantio/go-eth2-client/http"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/pgeth/toolkit"
|
||||
"github.com/ethereum/go-ethereum/plugins/pgeth-monitoring/pkg/tracer"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
func Version() {
|
||||
fmt.Println("pgeth-monitoring: v0.0.1")
|
||||
}
|
||||
|
||||
// plugin.so entrypoint
|
||||
func Start(pt *toolkit.PluginToolkit, cfg map[string]interface{}, ctx context.Context, errChan chan error) {
|
||||
var redisEndpointRaw interface{}
|
||||
var redisEndpoint string
|
||||
var beaconEndpointRaw interface{}
|
||||
var beaconEndpoint string
|
||||
var ok bool
|
||||
|
||||
if redisEndpointRaw, ok = cfg["REDIS_ENDPOINT"]; !ok {
|
||||
pt.Logger.Error("missing REDIS_ENDPOINT config var")
|
||||
return
|
||||
}
|
||||
|
||||
if redisEndpoint, ok = redisEndpointRaw.(string); !ok {
|
||||
pt.Logger.Error("invalid REDIS_ENDPOINT value")
|
||||
return
|
||||
}
|
||||
|
||||
if beaconEndpointRaw, ok = cfg["BEACON_ENDPOINT"]; !ok {
|
||||
pt.Logger.Error("missing BEACON_ENDPOINT config var")
|
||||
return
|
||||
}
|
||||
|
||||
if beaconEndpoint, ok = beaconEndpointRaw.(string); !ok {
|
||||
pt.Logger.Error("invalid BEACON_ENDPOINT value")
|
||||
return
|
||||
}
|
||||
|
||||
client, err := http.New(ctx,
|
||||
http.WithAddress(beaconEndpoint),
|
||||
http.WithLogLevel(zerolog.WarnLevel),
|
||||
)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: redisEndpoint,
|
||||
Password: "",
|
||||
DB: 0,
|
||||
})
|
||||
me := NewMonitoringEngine(pt, rdb, client, beaconEndpoint, errChan)
|
||||
|
||||
me.Start(ctx)
|
||||
|
||||
}
|
||||
|
||||
// MonitoringEngine is the main plugin struct
|
||||
// It contains the plugin toolkit, the redis client, and the eth2 client
|
||||
type MonitoringEngine struct {
|
||||
ptk *toolkit.PluginToolkit
|
||||
|
||||
backend *eth.EthAPIBackend
|
||||
chainConfig *params.ChainConfig
|
||||
coinbase common.Address
|
||||
state *state.StateDB
|
||||
header *types.Header
|
||||
|
||||
latestBlock *types.Block
|
||||
|
||||
beaconEndpoint string
|
||||
|
||||
errChan chan error
|
||||
|
||||
rdb *redis.Client
|
||||
eth2 eth2client.Service
|
||||
}
|
||||
|
||||
// AnalyzedTransaction is a transaction with its traces
|
||||
// It is used to encode the transaction and its traces in a redis key
|
||||
// Then the entire struct is sent to redis
|
||||
type AnalyzedTransaction struct {
|
||||
Transaction *types.Transaction `json:"transaction"`
|
||||
From common.Address `json:"from"`
|
||||
Receipt *types.Receipt `json:"receipt"`
|
||||
Traces tracer.Action `json:"traces"`
|
||||
}
|
||||
|
||||
// CachedBlockSimulation is a block simulation that is cached in memory
|
||||
// It is used to avoid simulating the same block twice for finalized blocks
|
||||
// Two hours after the block is cached, it is removed from memory
|
||||
// When a cache block is found as finalized, it is removed from memory
|
||||
type CachedBlockSimulation struct {
|
||||
Time time.Time
|
||||
AnalyzedTransactions []AnalyzedTransaction
|
||||
}
|
||||
|
||||
func NewMonitoringEngine(pt *toolkit.PluginToolkit, rdb *redis.Client, eth2 eth2client.Service, beaconEndpoint string, errChan chan error) *MonitoringEngine {
|
||||
|
||||
return &MonitoringEngine{
|
||||
ptk: pt,
|
||||
backend: pt.Backend.(*eth.EthAPIBackend),
|
||||
chainConfig: pt.Backend.ChainConfig(),
|
||||
beaconEndpoint: beaconEndpoint,
|
||||
errChan: errChan,
|
||||
rdb: rdb,
|
||||
eth2: eth2,
|
||||
}
|
||||
}
|
||||
|
||||
func (me *MonitoringEngine) Start(ctx context.Context) {
|
||||
me.startHeadListener(ctx)
|
||||
}
|
||||
|
||||
func (me *MonitoringEngine) update(ctx context.Context, parent *types.Block) {
|
||||
|
||||
state, _, err := me.backend.StateAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHashWithHash(parent.Hash(), true))
|
||||
if err != nil {
|
||||
me.errChan <- err
|
||||
return
|
||||
}
|
||||
me.header = &types.Header{
|
||||
ParentHash: parent.Hash(),
|
||||
Number: new(big.Int).Add(parent.Number(), common.Big1),
|
||||
GasLimit: parent.GasLimit(),
|
||||
Time: parent.Time() + 12,
|
||||
Coinbase: parent.Coinbase(),
|
||||
BaseFee: eip1559.CalcBaseFee(me.chainConfig, parent.Header()),
|
||||
Difficulty: parent.Difficulty(),
|
||||
}
|
||||
me.coinbase = parent.Coinbase()
|
||||
me.state = state
|
||||
}
|
||||
|
||||
func (me *MonitoringEngine) encodeAndBroadcastCallTrace(ctx context.Context, at *AnalyzedTransaction, channel string) {
|
||||
var topic string
|
||||
if at.Transaction.To() == nil {
|
||||
topic = fmt.Sprintf("/%s/tx/%s/%s/null/%s", channel, at.Transaction.Hash(), at.From, encodeActionCalls(at.Traces))
|
||||
} else {
|
||||
topic = fmt.Sprintf("/%s/tx/%s/%s/%s/%s", channel, at.Transaction.Hash(), at.From, at.Transaction.To(), encodeActionCalls(at.Traces))
|
||||
|
||||
}
|
||||
|
||||
jsoned, err := json.Marshal(*at)
|
||||
if err != nil {
|
||||
me.errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
err = me.rdb.Publish(ctx, topic, jsoned).Err()
|
||||
if err != nil {
|
||||
me.errChan <- err
|
||||
}
|
||||
|
||||
err = me.rdb.Expire(ctx, topic, 1*time.Hour).Err()
|
||||
if err != nil {
|
||||
me.errChan <- err
|
||||
}
|
||||
}
|
||||
|
||||
func (me *MonitoringEngine) encodeAndBroadcast(ctx context.Context, ats []AnalyzedTransaction, channel string) {
|
||||
for _, analyzedTx := range ats {
|
||||
me.encodeAndBroadcastCallTrace(ctx, &analyzedTx, channel)
|
||||
}
|
||||
}
|
||||
|
||||
// analyze is the main function of the plugin
|
||||
// It takes a block and returns a list of AnalyzedTransaction
|
||||
// It simulates the block and gets the traces of all the transactions
|
||||
func (me *MonitoringEngine) analyze(ctx context.Context, block *types.Block, scope string) []AnalyzedTransaction {
|
||||
// We retrieve the parent block
|
||||
parentBlk, err := me.backend.BlockByHash(ctx, block.ParentHash())
|
||||
if err != nil {
|
||||
me.errChan <- err
|
||||
return nil
|
||||
}
|
||||
// We retrieve the state of the parent block
|
||||
state, _, err := me.backend.StateAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHashWithHash(parentBlk.Hash(), true))
|
||||
if err != nil {
|
||||
me.errChan <- err
|
||||
return nil
|
||||
}
|
||||
// We configure the vm to use our monitoring tracer
|
||||
gp := new(core.GasPool).AddGas(block.Header().GasLimit)
|
||||
mt := tracer.MonitoringTracer{}
|
||||
var vmConfig vm.Config = vm.Config{
|
||||
Tracer: &mt,
|
||||
NoBaseFee: false,
|
||||
EnablePreimageRecording: false,
|
||||
ExtraEips: []int{},
|
||||
}
|
||||
analyzedTransactions := []AnalyzedTransaction{}
|
||||
// We simulate all the transactions of the block
|
||||
for idx, tx := range block.Transactions() {
|
||||
state.SetTxContext(tx.Hash(), idx)
|
||||
receipt, err := core.ApplyTransaction(me.chainConfig, me.backend.Ethereum().BlockChain(), &block.Header().Coinbase, gp, state, block.Header(), tx, &block.Header().GasUsed, vmConfig)
|
||||
if err != nil {
|
||||
me.errChan <- err
|
||||
return nil
|
||||
}
|
||||
receipt.EffectiveGasPrice = getEffectiveGasPrice(tx, parentBlk.BaseFee())
|
||||
signer := types.MakeSigner(me.backend.Ethereum().BlockChain().Config(), receipt.BlockNumber, block.Time())
|
||||
from, _ := types.Sender(signer, tx)
|
||||
analyzedTransactions = append(analyzedTransactions, AnalyzedTransaction{
|
||||
Transaction: tx,
|
||||
From: from,
|
||||
Receipt: receipt,
|
||||
Traces: mt.Action,
|
||||
})
|
||||
mt.Clear()
|
||||
}
|
||||
me.ptk.Logger.Info("Simulated txs", "count", len(block.Transactions()), "scope", scope, "number", block.Number())
|
||||
// We encode and broadcast the traces
|
||||
me.encodeAndBroadcast(ctx, analyzedTransactions, scope)
|
||||
me.ptk.Logger.Info("Broadcasted txs", "count", len(block.Transactions()), "scope", scope, "number", block.Number())
|
||||
me.latestBlock = block
|
||||
return analyzedTransactions
|
||||
}
|
||||
|
||||
func (me *MonitoringEngine) analyzePending(ctx context.Context, txs []*types.Transaction) {
|
||||
gp := new(core.GasPool).AddGas(me.header.GasLimit)
|
||||
mt := tracer.MonitoringTracer{}
|
||||
var vmConfig vm.Config = vm.Config{
|
||||
Tracer: &mt,
|
||||
NoBaseFee: false,
|
||||
EnablePreimageRecording: false,
|
||||
ExtraEips: []int{},
|
||||
}
|
||||
analyzedTransactions := []AnalyzedTransaction{}
|
||||
for _, tx := range txs {
|
||||
// we copy the state at the head
|
||||
stateClone := me.state.Copy()
|
||||
stateClone.SetTxContext(tx.Hash(), 0)
|
||||
// we simulate the pending tx on top of it
|
||||
receipt, err := core.ApplyTransaction(me.chainConfig, me.backend.Ethereum().BlockChain(), &me.header.Coinbase, gp, stateClone, me.header, tx, &me.header.GasUsed, vmConfig)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
receipt.EffectiveGasPrice = getEffectiveGasPrice(tx, me.header.BaseFee)
|
||||
signer := types.MakeSigner(me.backend.Ethereum().BlockChain().Config(), receipt.BlockNumber, me.header.Time)
|
||||
from, _ := types.Sender(signer, tx)
|
||||
analyzedTransactions = append(analyzedTransactions, AnalyzedTransaction{
|
||||
Transaction: tx,
|
||||
From: from,
|
||||
Receipt: receipt,
|
||||
Traces: mt.Action,
|
||||
})
|
||||
mt.Clear()
|
||||
}
|
||||
if len(analyzedTransactions) > 0 {
|
||||
// we encode and broadcast the traces under the pending topic
|
||||
me.encodeAndBroadcast(ctx, analyzedTransactions, "pending")
|
||||
}
|
||||
}
|
||||
|
||||
func (me *MonitoringEngine) startHeadListener(ctx context.Context) {
|
||||
|
||||
headChan := make(chan core.ChainHeadEvent)
|
||||
headSubscription := me.backend.SubscribeChainHeadEvent(headChan)
|
||||
pendingChan := make(chan core.NewTxsEvent)
|
||||
pendingSubscription := me.backend.SubscribeNewTxsEvent(pendingChan)
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
cache := make(map[common.Hash]CachedBlockSimulation)
|
||||
analyzedPendingTxs := 0
|
||||
|
||||
highestFinalized := uint64(0)
|
||||
|
||||
defer headSubscription.Unsubscribe()
|
||||
defer pendingSubscription.Unsubscribe()
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case err := <-headSubscription.Err():
|
||||
if err != nil {
|
||||
me.errChan <- err
|
||||
}
|
||||
return
|
||||
case err := <-pendingSubscription.Err():
|
||||
if err != nil {
|
||||
me.errChan <- err
|
||||
}
|
||||
return
|
||||
case newHead := <-headChan:
|
||||
me.update(ctx, newHead.Block)
|
||||
me.ptk.Logger.Info("Head was updated", "number", newHead.Block.NumberU64(), "hash", newHead.Block.Hash(), "root", newHead.Block.Root())
|
||||
|
||||
analyzedTransactions := me.analyze(ctx, newHead.Block, "head")
|
||||
cache[newHead.Block.Hash()] = CachedBlockSimulation{
|
||||
Time: time.Now(),
|
||||
AnalyzedTransactions: analyzedTransactions,
|
||||
}
|
||||
case newTxs := <-pendingChan:
|
||||
if me.header == nil {
|
||||
me.ptk.Logger.Warn("Skipping pending tx simulation, not ready")
|
||||
} else {
|
||||
me.analyzePending(ctx, newTxs.Txs)
|
||||
analyzedPendingTxs += len(newTxs.Txs)
|
||||
}
|
||||
case <-ticker.C:
|
||||
if analyzedPendingTxs > 0 {
|
||||
me.ptk.Logger.Info("Analyzed pending txs", "count", analyzedPendingTxs, "rate", fmt.Sprintf("%f/s", float64(analyzedPendingTxs)/float64(30)))
|
||||
analyzedPendingTxs = 0
|
||||
}
|
||||
|
||||
if me.eth2 == nil {
|
||||
client, err := http.New(ctx,
|
||||
http.WithAddress(me.beaconEndpoint),
|
||||
http.WithLogLevel(zerolog.WarnLevel),
|
||||
)
|
||||
if err != nil {
|
||||
me.errChan <- err
|
||||
continue
|
||||
}
|
||||
me.eth2 = client
|
||||
}
|
||||
|
||||
res, err := me.eth2.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, "finalized")
|
||||
if err != nil {
|
||||
me.errChan <- err
|
||||
continue
|
||||
}
|
||||
|
||||
newHighestFinalized := res.Capella.Message.Body.ExecutionPayload.BlockNumber
|
||||
if highestFinalized == 0 {
|
||||
highestFinalized = res.Capella.Message.Body.ExecutionPayload.BlockNumber
|
||||
me.ptk.Logger.Info("Finalized head was updated", "number", highestFinalized)
|
||||
} else if newHighestFinalized > highestFinalized {
|
||||
me.ptk.Logger.Info("Updating finalized head", "from", highestFinalized, "to", newHighestFinalized)
|
||||
breaked := false
|
||||
for i := highestFinalized + 1; i <= newHighestFinalized; i++ {
|
||||
blk, err := me.backend.BlockByNumber(ctx, rpc.BlockNumber(i))
|
||||
if err != nil {
|
||||
me.errChan <- err
|
||||
breaked = true
|
||||
break
|
||||
}
|
||||
if blk == nil {
|
||||
breaked = true
|
||||
break
|
||||
}
|
||||
if cachedValue, ok := cache[blk.Hash()]; ok {
|
||||
me.encodeAndBroadcast(ctx, cachedValue.AnalyzedTransactions, "finalized")
|
||||
me.ptk.Logger.Info("Broadcasted txs", "count", len(cachedValue.AnalyzedTransactions), "scope", "finalized", "number", i)
|
||||
delete(cache, blk.Hash())
|
||||
}
|
||||
}
|
||||
if breaked {
|
||||
continue
|
||||
}
|
||||
me.ptk.Logger.Info("Finalized head was updated", "number", newHighestFinalized)
|
||||
highestFinalized = newHighestFinalized
|
||||
|
||||
}
|
||||
|
||||
for k, v := range cache {
|
||||
if time.Since(v.Time) > 2*time.Hour {
|
||||
delete(cache, k)
|
||||
}
|
||||
}
|
||||
if len(cache) > 0 {
|
||||
me.ptk.Logger.Info("Cached blocks", "count", len(cache))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getEffectiveGasPrice(tx *types.Transaction, baseFee *big.Int) *big.Int {
|
||||
switch tx.Type() {
|
||||
case types.DynamicFeeTxType:
|
||||
if baseFee == nil {
|
||||
return tx.GasFeeCap()
|
||||
}
|
||||
tip := new(big.Int).Sub(tx.GasFeeCap(), baseFee)
|
||||
if tip.Cmp(tx.GasTipCap()) > 0 {
|
||||
tip.Set(tx.GasTipCap())
|
||||
}
|
||||
return tip.Add(tip, baseFee)
|
||||
case types.AccessListTxType:
|
||||
return tx.GasPrice()
|
||||
case types.LegacyTxType:
|
||||
return tx.GasPrice()
|
||||
default:
|
||||
return big.NewInt(0)
|
||||
}
|
||||
}
|
||||
|
||||
func callTypeToPrefix(c *tracer.Call) string {
|
||||
switch c.Type() {
|
||||
case "call":
|
||||
return "C"
|
||||
case "staticcall":
|
||||
return "S"
|
||||
case "delegatecall":
|
||||
return "D"
|
||||
case "initial_call":
|
||||
return "C"
|
||||
}
|
||||
return "X"
|
||||
}
|
||||
|
||||
func eventTypeToPrefix(e *tracer.Event) string {
|
||||
switch e.LogType {
|
||||
case "log0":
|
||||
fallthrough
|
||||
case "log1":
|
||||
fallthrough
|
||||
case "log2":
|
||||
fallthrough
|
||||
case "log3":
|
||||
fallthrough
|
||||
case "log4":
|
||||
return "L"
|
||||
}
|
||||
return "X"
|
||||
}
|
||||
|
||||
func revertTypeToPrefix(r *tracer.Revert) string {
|
||||
switch r.ErrorType {
|
||||
case "revert":
|
||||
return "R"
|
||||
case "panic":
|
||||
return "P"
|
||||
}
|
||||
return "X"
|
||||
}
|
||||
|
||||
func minInt(a int, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func encodeSelector(c *tracer.Call) string {
|
||||
selector := fmt.Sprintf("%x", c.In[0:minInt(4, len(c.In))])
|
||||
for len(selector) < 8 {
|
||||
selector += "X"
|
||||
}
|
||||
return selector
|
||||
}
|
||||
|
||||
func encodeRevertSelector(c *tracer.Revert) string {
|
||||
selector := fmt.Sprintf("%x", c.Data[0:minInt(4, len(c.Data))])
|
||||
for len(selector) < 8 {
|
||||
selector += "X"
|
||||
}
|
||||
return selector
|
||||
}
|
||||
|
||||
func encodeActionCalls(a tracer.Action) string {
|
||||
res := ""
|
||||
if c, ok := a.(*tracer.Call); ok {
|
||||
prefix := callTypeToPrefix(c)
|
||||
selector := encodeSelector(c)
|
||||
res = fmt.Sprintf("%s@%s_%s", prefix, c.To.String(), selector)
|
||||
if len(a.Children()) > 0 {
|
||||
chldArr := []string{}
|
||||
for _, chld := range a.Children() {
|
||||
chldRes := encodeActionCalls(chld)
|
||||
if len(chldRes) > 0 {
|
||||
chldArr = append(chldArr, chldRes)
|
||||
}
|
||||
}
|
||||
if len(chldArr) > 0 {
|
||||
joinedChldRes := strings.Join(chldArr[:], ",")
|
||||
res = fmt.Sprintf("%s[%s]", res, joinedChldRes)
|
||||
}
|
||||
}
|
||||
}
|
||||
if e, ok := a.(*tracer.Event); ok {
|
||||
concatenatedTopics := ""
|
||||
for _, topic := range e.Topics {
|
||||
concatenatedTopics += topic.String()[2:]
|
||||
}
|
||||
dataLength := len(e.Data)
|
||||
res = fmt.Sprintf("%s@%s_%s_%d", eventTypeToPrefix(e), e.ContextValue.String(), concatenatedTopics, dataLength)
|
||||
}
|
||||
if r, ok := a.(*tracer.Revert); ok {
|
||||
prefix := revertTypeToPrefix(r)
|
||||
selector := encodeRevertSelector(r)
|
||||
res = fmt.Sprintf("%s@%s_%s", prefix, r.ContextValue.String(), selector)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
191
plugins/pgeth-monitoring/pkg/tracer/action.go
Normal file
191
plugins/pgeth-monitoring/pkg/tracer/action.go
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
package tracer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
type Action interface {
|
||||
Type() string
|
||||
Children() []Action
|
||||
Parent() Action
|
||||
Depth() int
|
||||
Log()
|
||||
Has(string) bool
|
||||
Context() common.Address
|
||||
Code() common.Address
|
||||
|
||||
AddChildren(Action)
|
||||
}
|
||||
|
||||
type Call struct {
|
||||
ParentValue Action `json:"-"`
|
||||
DepthValue int `json:"depth,omitempty"`
|
||||
|
||||
TypeValue string `json:"type,omitempty"`
|
||||
CallType string `json:"callType,omitempty"`
|
||||
ChildrenValue []Action `json:"children,omitempty"`
|
||||
|
||||
ContextValue common.Address `json:"context,omitempty"`
|
||||
CodeValue common.Address `json:"code,omitempty"`
|
||||
ForwardedContext common.Address `json:"forwardedContext,omitempty"`
|
||||
ForwardedCode common.Address `json:"forwardedCode,omitempty"`
|
||||
|
||||
From common.Address `json:"from,omitempty"`
|
||||
To common.Address `json:"to,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
In []byte `json:"-"`
|
||||
Out []byte `json:"-"`
|
||||
InHex string `json:"in,omitempty"`
|
||||
OutHex string `json:"out,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Call) Type() string {
|
||||
return c.CallType
|
||||
}
|
||||
|
||||
func (c *Call) Children() []Action {
|
||||
return c.ChildrenValue
|
||||
}
|
||||
|
||||
func (c *Call) Context() common.Address {
|
||||
return c.ContextValue
|
||||
}
|
||||
|
||||
func (c *Call) Code() common.Address {
|
||||
return c.CodeValue
|
||||
}
|
||||
|
||||
func (c *Call) Depth() int {
|
||||
return c.DepthValue
|
||||
}
|
||||
|
||||
func (c *Call) Parent() Action {
|
||||
return c.ParentValue
|
||||
}
|
||||
|
||||
func (c *Call) AddChildren(a Action) {
|
||||
c.ChildrenValue = append(c.ChildrenValue, a)
|
||||
}
|
||||
|
||||
func (c *Call) Log() {
|
||||
fmt.Printf("%s- %s %s to %s (%s:%s) (%d,%d) (%d)\n", strings.Repeat(" ", c.DepthValue), c.Type(), c.From.String(), c.To.String(), c.Context().String(), c.Code().String(), len(c.In), len(c.Out), len(c.ChildrenValue))
|
||||
for _, subcall := range c.ChildrenValue {
|
||||
subcall.Log()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Call) Has(typ string) bool {
|
||||
if c.Type() == typ {
|
||||
return true
|
||||
}
|
||||
for _, chld := range c.ChildrenValue {
|
||||
if chld.Has(typ) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
ParentValue Action `json:"-"`
|
||||
DepthValue int `json:"depth,omitempty"`
|
||||
|
||||
TypeValue string `json:"type,omitempty"`
|
||||
LogType string `json:"logType,omitempty"`
|
||||
|
||||
ContextValue common.Address `json:"context,omitempty"`
|
||||
CodeValue common.Address `json:"code,omitempty"`
|
||||
|
||||
Data []byte `json:"-"`
|
||||
DataHex string `json:"data,omitempty"`
|
||||
Topics []common.Hash `json:"topics,omitempty"`
|
||||
From common.Address `json:"from,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Event) Type() string {
|
||||
return c.LogType
|
||||
}
|
||||
|
||||
func (c *Event) Children() []Action {
|
||||
return []Action{}
|
||||
}
|
||||
|
||||
func (c *Event) Context() common.Address {
|
||||
return c.ContextValue
|
||||
}
|
||||
|
||||
func (c *Event) Code() common.Address {
|
||||
return c.CodeValue
|
||||
}
|
||||
|
||||
func (c *Event) Depth() int {
|
||||
return c.DepthValue
|
||||
}
|
||||
|
||||
func (c *Event) Parent() Action {
|
||||
return c.ParentValue
|
||||
}
|
||||
|
||||
func (c *Event) AddChildren(a Action) {
|
||||
}
|
||||
|
||||
func (c *Event) Log() {
|
||||
fmt.Printf("%s- %s (%s:%s) \n", strings.Repeat(" ", c.DepthValue), c.Type(), c.Context().String(), c.Code().String())
|
||||
}
|
||||
|
||||
func (c *Event) Has(typ string) bool {
|
||||
return c.Type() == typ
|
||||
}
|
||||
|
||||
type Revert struct {
|
||||
ParentValue Action `json:"-"`
|
||||
DepthValue int `json:"depth,omitempty"`
|
||||
|
||||
TypeValue string `json:"type,omitempty"`
|
||||
ErrorType string `json:"errorType,omitempty"`
|
||||
|
||||
ContextValue common.Address `json:"context,omitempty"`
|
||||
CodeValue common.Address `json:"code,omitempty"`
|
||||
|
||||
Data []byte `json:"-"`
|
||||
DataHex string `json:"data,omitempty"`
|
||||
From common.Address `json:"from,omitempty"`
|
||||
}
|
||||
|
||||
func (r *Revert) Type() string {
|
||||
return r.ErrorType
|
||||
}
|
||||
|
||||
func (r *Revert) Children() []Action {
|
||||
return []Action{}
|
||||
}
|
||||
|
||||
func (r *Revert) Context() common.Address {
|
||||
return r.ContextValue
|
||||
}
|
||||
|
||||
func (r *Revert) Code() common.Address {
|
||||
return r.CodeValue
|
||||
}
|
||||
|
||||
func (r *Revert) Depth() int {
|
||||
return r.DepthValue
|
||||
}
|
||||
|
||||
func (r *Revert) Parent() Action {
|
||||
return r.ParentValue
|
||||
}
|
||||
|
||||
func (r *Revert) AddChildren(a Action) {
|
||||
}
|
||||
|
||||
func (r *Revert) Log() {
|
||||
fmt.Printf("%s- %s (%s:%s) %x\n", strings.Repeat(" ", r.DepthValue), r.Type(), r.Context().String(), r.Code().String(), r.Data)
|
||||
}
|
||||
|
||||
func (r *Revert) Has(typ string) bool {
|
||||
return r.Type() == typ
|
||||
}
|
||||
241
plugins/pgeth-monitoring/pkg/tracer/tracer.go
Normal file
241
plugins/pgeth-monitoring/pkg/tracer/tracer.go
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
package tracer
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
)
|
||||
|
||||
type MonitoringTracer struct {
|
||||
Action Action
|
||||
Cursor Action
|
||||
}
|
||||
|
||||
func (m *MonitoringTracer) Clear() {
|
||||
m.Action = nil
|
||||
m.Cursor = nil
|
||||
}
|
||||
|
||||
func (m *MonitoringTracer) CaptureTxStart(gasLimit uint64) {
|
||||
|
||||
}
|
||||
|
||||
func (m *MonitoringTracer) CaptureTxEnd(restGas uint64) {
|
||||
|
||||
}
|
||||
|
||||
func (m *MonitoringTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
copyInput := make([]byte, len(input))
|
||||
usedValue := big.NewInt(0)
|
||||
if value != nil {
|
||||
usedValue.Set(value)
|
||||
}
|
||||
copy(copyInput, input)
|
||||
m.Action = &Call{
|
||||
CallType: "initial_call",
|
||||
TypeValue: "call",
|
||||
ChildrenValue: []Action{},
|
||||
ParentValue: nil,
|
||||
DepthValue: 0,
|
||||
|
||||
ContextValue: common.Address{},
|
||||
CodeValue: common.Address{},
|
||||
|
||||
ForwardedContext: to,
|
||||
ForwardedCode: to,
|
||||
|
||||
From: from,
|
||||
To: to,
|
||||
In: copyInput,
|
||||
InHex: "0x" + hex.EncodeToString(copyInput),
|
||||
Value: "0x" + usedValue.Text(16),
|
||||
}
|
||||
m.Cursor = m.Action
|
||||
}
|
||||
|
||||
func (m *MonitoringTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||
copyOutput := make([]byte, len(output))
|
||||
copy(copyOutput, output)
|
||||
m.Cursor.(*Call).Out = copyOutput
|
||||
m.Cursor.(*Call).OutHex = "0x" + hex.EncodeToString(copyOutput)
|
||||
}
|
||||
|
||||
func (m *MonitoringTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
callType := callOpcodeToString(typ)
|
||||
ctx, code := parentContextAndCode(m.Cursor)
|
||||
forwardedCode := to
|
||||
forwardedContext := to
|
||||
if callType == "delegatecall" {
|
||||
forwardedContext = from
|
||||
}
|
||||
usedValue := big.NewInt(0)
|
||||
if value != nil {
|
||||
usedValue.Set(value)
|
||||
}
|
||||
copyInput := make([]byte, len(input))
|
||||
copy(copyInput, input)
|
||||
call := &Call{
|
||||
CallType: callType,
|
||||
TypeValue: "call",
|
||||
ChildrenValue: []Action{},
|
||||
ParentValue: m.Cursor,
|
||||
DepthValue: m.Cursor.Depth() + 1,
|
||||
|
||||
ForwardedContext: forwardedContext,
|
||||
ForwardedCode: forwardedCode,
|
||||
ContextValue: ctx,
|
||||
CodeValue: code,
|
||||
From: from,
|
||||
To: to,
|
||||
In: copyInput,
|
||||
InHex: "0x" + hex.EncodeToString(copyInput),
|
||||
Value: "0x" + usedValue.Text(16),
|
||||
}
|
||||
m.Cursor.AddChildren(call)
|
||||
m.Cursor = call
|
||||
}
|
||||
|
||||
func (m *MonitoringTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
copyOutput := make([]byte, len(output))
|
||||
copy(copyOutput, output)
|
||||
m.Cursor.(*Call).Out = copyOutput
|
||||
m.Cursor.(*Call).OutHex = "0x" + hex.EncodeToString(copyOutput)
|
||||
m.Cursor = m.Cursor.Parent()
|
||||
}
|
||||
|
||||
func (m *MonitoringTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
if op >= 160 && op <= 164 {
|
||||
stack := scope.Stack.Data()
|
||||
stackLen := len(stack)
|
||||
var offset int64 = 0
|
||||
var size int64 = 0
|
||||
if stackLen >= 2 {
|
||||
offset = stack[stackLen-1].ToBig().Int64()
|
||||
size = stack[stackLen-2].ToBig().Int64()
|
||||
}
|
||||
fetchSize := size
|
||||
var data []byte = []byte{}
|
||||
if int64(scope.Memory.Len()) < offset {
|
||||
fetchSize = 0
|
||||
// generate zero array
|
||||
} else if int64(scope.Memory.Len()) < offset+size {
|
||||
fetchSize -= (offset + size) - int64(scope.Memory.Len())
|
||||
}
|
||||
|
||||
if fetchSize > 0 {
|
||||
data = scope.Memory.GetCopy(offset, fetchSize)
|
||||
}
|
||||
|
||||
if fetchSize < size {
|
||||
data = addZeros(data, size-fetchSize)
|
||||
}
|
||||
|
||||
topics := []common.Hash{}
|
||||
for idx := 0; idx < int(op-160); idx++ {
|
||||
if stackLen-3-idx >= 0 {
|
||||
topics = append(topics, stack[stackLen-3-idx].Bytes32())
|
||||
}
|
||||
}
|
||||
|
||||
ctx, code := parentContextAndCode(m.Cursor)
|
||||
|
||||
m.Cursor.AddChildren(&Event{
|
||||
LogType: fmt.Sprintf("log%d", op-160),
|
||||
TypeValue: "event",
|
||||
Data: data,
|
||||
DataHex: "0x" + hex.EncodeToString(data),
|
||||
Topics: topics,
|
||||
From: scope.Contract.Address(),
|
||||
|
||||
ContextValue: ctx,
|
||||
CodeValue: code,
|
||||
ParentValue: m.Cursor,
|
||||
DepthValue: m.Cursor.Depth() + 1,
|
||||
})
|
||||
}
|
||||
if op == 253 {
|
||||
errorType := "revert"
|
||||
data := []byte{}
|
||||
stack := scope.Stack.Data()
|
||||
stackLen := len(stack)
|
||||
var offset int64 = 0
|
||||
var size int64 = 0
|
||||
if stackLen >= 2 {
|
||||
offset = stack[stackLen-1].ToBig().Int64()
|
||||
size = stack[stackLen-2].ToBig().Int64()
|
||||
}
|
||||
fetchSize := size
|
||||
if int64(scope.Memory.Len()) < offset {
|
||||
fetchSize = 0
|
||||
// generate zero array
|
||||
} else if int64(scope.Memory.Len()) < offset+size {
|
||||
fetchSize -= (offset + size) - int64(scope.Memory.Len())
|
||||
}
|
||||
|
||||
if fetchSize > 0 {
|
||||
data = scope.Memory.GetCopy(offset, fetchSize)
|
||||
}
|
||||
|
||||
if fetchSize < size {
|
||||
data = addZeros(data, size-fetchSize)
|
||||
}
|
||||
|
||||
ctx, code := parentContextAndCode(m.Cursor)
|
||||
|
||||
m.Cursor.AddChildren(&Revert{
|
||||
ErrorType: errorType,
|
||||
TypeValue: "revert",
|
||||
Data: data,
|
||||
DataHex: "0x" + hex.EncodeToString(data),
|
||||
From: scope.Contract.Address(),
|
||||
ContextValue: ctx,
|
||||
CodeValue: code,
|
||||
ParentValue: m.Cursor,
|
||||
DepthValue: m.Cursor.Depth() + 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MonitoringTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
if op != 253 {
|
||||
ctx, code := parentContextAndCode(m.Cursor)
|
||||
m.Cursor.AddChildren(&Revert{
|
||||
ErrorType: "panic",
|
||||
TypeValue: "revert",
|
||||
Data: []byte{},
|
||||
DataHex: "0x" + hex.EncodeToString([]byte{}),
|
||||
From: scope.Contract.Address(),
|
||||
ContextValue: ctx,
|
||||
CodeValue: code,
|
||||
ParentValue: m.Cursor,
|
||||
DepthValue: m.Cursor.Depth() + 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func callOpcodeToString(c vm.OpCode) string {
|
||||
switch c {
|
||||
case 241:
|
||||
return "call"
|
||||
case 244:
|
||||
return "delegatecall"
|
||||
case 250:
|
||||
return "staticcall"
|
||||
default:
|
||||
return fmt.Sprintf("unknown %d", c)
|
||||
}
|
||||
}
|
||||
|
||||
func parentContextAndCode(p Action) (common.Address, common.Address) {
|
||||
if p != nil {
|
||||
return p.(*Call).ForwardedContext, p.(*Call).ForwardedCode
|
||||
}
|
||||
return common.Address{}, common.Address{}
|
||||
}
|
||||
|
||||
func addZeros(arr []byte, zeros int64) []byte {
|
||||
return append(arr, make([]byte, zeros)...)
|
||||
}
|
||||
Loading…
Reference in a new issue