diff --git a/chain/indexer/integrated/processor/state.go b/chain/indexer/integrated/processor/state.go index 566fc5592..0b133221a 100644 --- a/chain/indexer/integrated/processor/state.go +++ b/chain/indexer/integrated/processor/state.go @@ -58,6 +58,9 @@ import ( receipttask "github.com/filecoin-project/lily/tasks/messages/receipt" msapprovaltask "github.com/filecoin-project/lily/tasks/msapprovals" + // fevm task + fevmactorstatstask "github.com/filecoin-project/lily/tasks/fevmactorstats" + "github.com/filecoin-project/lily/chain/indexer/tasktype" "github.com/filecoin-project/lily/metrics" "github.com/filecoin-project/lily/model" @@ -628,6 +631,12 @@ func MakeProcessors(api tasks.DataSource, indexerTasks []string) (*IndexerProces case tasktype.ChainConsensus: out.TipsetProcessors[t] = consensustask.NewTask(api) + // + // FEVM + // + case tasktype.FEVMActorStats: + out.TipsetProcessors[t] = fevmactorstatstask.NewTask(api) + case BuiltinTaskName: out.ReportProcessors[t] = indexertask.NewTask(api) default: diff --git a/chain/indexer/integrated/processor/state_internal_test.go b/chain/indexer/integrated/processor/state_internal_test.go index 432f98617..a37d00830 100644 --- a/chain/indexer/integrated/processor/state_internal_test.go +++ b/chain/indexer/integrated/processor/state_internal_test.go @@ -53,7 +53,7 @@ func TestNewProcessor(t *testing.T) { require.NoError(t, err) require.Equal(t, t.Name(), proc.name) require.Len(t, proc.actorProcessors, 24) - require.Len(t, proc.tipsetProcessors, 9) + require.Len(t, proc.tipsetProcessors, 10) require.Len(t, proc.tipsetsProcessors, 9) require.Len(t, proc.builtinProcessors, 1) diff --git a/chain/indexer/integrated/processor/state_test.go b/chain/indexer/integrated/processor/state_test.go index 01494d896..819670982 100644 --- a/chain/indexer/integrated/processor/state_test.go +++ b/chain/indexer/integrated/processor/state_test.go @@ -402,7 +402,7 @@ func TestMakeProcessorsAllTasks(t *testing.T) { proc, err := processor.MakeProcessors(nil, append(tasktype.AllTableTasks, processor.BuiltinTaskName)) require.NoError(t, err) require.Len(t, proc.ActorProcessors, 24) - require.Len(t, proc.TipsetProcessors, 9) + require.Len(t, proc.TipsetProcessors, 10) require.Len(t, proc.TipsetsProcessors, 9) require.Len(t, proc.ReportProcessors, 1) } diff --git a/chain/indexer/tasktype/table_tasks.go b/chain/indexer/tasktype/table_tasks.go index f1f1040e1..13e1ca4e3 100644 --- a/chain/indexer/tasktype/table_tasks.go +++ b/chain/indexer/tasktype/table_tasks.go @@ -44,6 +44,7 @@ const ( VerifiedRegistryVerifier = "verified_registry_verifier" VerifiedRegistryVerifiedClient = "verified_registry_verified_client" VerifiedRegistryClaim = "verified_registry_claim" + FEVMActorStats = "fevm_actor_stats" ) var AllTableTasks = []string{ @@ -89,6 +90,7 @@ var AllTableTasks = []string{ VerifiedRegistryVerifier, VerifiedRegistryVerifiedClient, VerifiedRegistryClaim, + FEVMActorStats, } var TableLookup = map[string]struct{}{ @@ -134,6 +136,7 @@ var TableLookup = map[string]struct{}{ VerifiedRegistryVerifier: {}, VerifiedRegistryVerifiedClient: {}, VerifiedRegistryClaim: {}, + FEVMActorStats: {}, } var TableComment = map[string]string{ @@ -179,6 +182,7 @@ var TableComment = map[string]string{ VerifiedRegistryVerifier: ``, VerifiedRegistryVerifiedClient: ``, VerifiedRegistryClaim: ``, + FEVMActorStats: ``, } var TableFieldComments = map[string]map[string]string{ @@ -281,4 +285,5 @@ var TableFieldComments = map[string]map[string]string{ VerifiedRegistryVerifier: {}, VerifiedRegistryVerifiedClient: {}, VerifiedRegistryClaim: {}, + FEVMActorStats: {}, } diff --git a/chain/indexer/tasktype/tasks.go b/chain/indexer/tasktype/tasks.go index 0d6fd23a0..6bed854cb 100644 --- a/chain/indexer/tasktype/tasks.go +++ b/chain/indexer/tasktype/tasks.go @@ -17,6 +17,7 @@ const ( MultisigApprovalsTask = "msapprovals" // task that extracts multisig actor approvals ImplicitMessageTask = "implicitmessage" // task that extract implicitly executed messages: cron tick and block reward. ChainConsensusTask = "consensus" + FEVMTask = "fevm" ) var TaskLookup = map[string][]string{ @@ -90,6 +91,9 @@ var TaskLookup = map[string][]string{ ChainConsensusTask: { ChainConsensus, }, + FEVMTask: { + FEVMActorStats, + }, } func MakeTaskNames(tasks []string) ([]string, error) { diff --git a/chain/indexer/tasktype/tasks_test.go b/chain/indexer/tasktype/tasks_test.go index 851227632..ecc0de66c 100644 --- a/chain/indexer/tasktype/tasks_test.go +++ b/chain/indexer/tasktype/tasks_test.go @@ -101,7 +101,7 @@ func TestMakeAllTaskAliasNames(t *testing.T) { } func TestMakeAllTaskNames(t *testing.T) { - const TotalTableTasks = 42 + const TotalTableTasks = 43 actual, err := tasktype.MakeTaskNames(tasktype.AllTableTasks) require.NoError(t, err) // if this test fails it means a new task name was added, update the above test diff --git a/model/fevm/fevmactorstats.go b/model/fevm/fevmactorstats.go new file mode 100644 index 000000000..24e509ed8 --- /dev/null +++ b/model/fevm/fevmactorstats.go @@ -0,0 +1,56 @@ +package fevm + +import ( + "context" + + "go.opencensus.io/tag" + + "github.com/filecoin-project/lily/metrics" + "github.com/filecoin-project/lily/model" +) + +type FEVMActorStats struct { + tableName struct{} `pg:"fevm_actor_stats"` // nolint: structcheck + + // Height message was executed at. + Height int64 `pg:",pk,notnull,use_zero"` + + // Balance of EVM actor in attoFIL. + ContractBalance string `pg:",notnull"` + // Balance of Eth account actor in attoFIL. + EthAccountBalance string `pg:",notnull"` + // Balance of Placeholder Actor in attoFIL. + PlaceholderBalance string `pg:",notnull"` + + // number of contracts + ContractCount uint64 `pg:",use_zero"` + // number of unique contracts + UniqueContractCount uint64 `pg:",use_zero"` + // number of Eth account actors + EthAccountCount uint64 `pg:",use_zero"` + // number of placeholder actors + PlaceholderCount uint64 `pg:",use_zero"` +} + +func (f *FEVMActorStats) Persist(ctx context.Context, s model.StorageBatch, version model.Version) error { + ctx, _ = tag.New(ctx, tag.Upsert(metrics.Table, "fevm_actor_stats")) + stop := metrics.Timer(ctx, metrics.PersistDuration) + defer stop() + + metrics.RecordCount(ctx, metrics.PersistModel, 1) + return s.PersistModel(ctx, f) +} + +type FEVMActorStatsList []*FEVMActorStats + +func (f FEVMActorStatsList) Persist(ctx context.Context, s model.StorageBatch, version model.Version) error { + if len(f) == 0 { + return nil + } + ctx, _ = tag.New(ctx, tag.Upsert(metrics.Table, "fevm_actor_stats")) + stop := metrics.Timer(ctx, metrics.PersistDuration) + defer stop() + + metrics.RecordCount(ctx, metrics.PersistModel, len(f)) + return s.PersistModel(ctx, f) +} diff --git a/schemas/v1/19_fevm_actor_stats.go b/schemas/v1/19_fevm_actor_stats.go new file mode 100644 index 000000000..8bab3732d --- /dev/null +++ b/schemas/v1/19_fevm_actor_stats.go @@ -0,0 +1,20 @@ +package v1 + +func init() { + patches.Register( + 19, + ` + CREATE TABLE IF NOT EXISTS {{ .SchemaName | default "public"}}.fevm_actor_stats ( + height BIGINT NOT NULL, + contract_balance TEXT NOT NULL, + eth_account_balance TEXT NOT NULL, + placeholder_balance TEXT NOT NULL, + contract_count BIGINT NOT NULL, + unique_contract_count BIGINT NOT NULL, + eth_account_count BIGINT NOT NULL, + placeholder_count BIGINT NOT NULL, + PRIMARY KEY(height) + ); +`, + ) +} diff --git a/storage/sql.go b/storage/sql.go index 64c10b42d..73b7b0dfa 100644 --- a/storage/sql.go +++ b/storage/sql.go @@ -27,6 +27,7 @@ import ( "github.com/filecoin-project/lily/model/blocks" "github.com/filecoin-project/lily/model/chain" "github.com/filecoin-project/lily/model/derived" + "github.com/filecoin-project/lily/model/fevm" "github.com/filecoin-project/lily/model/messages" "github.com/filecoin-project/lily/model/msapprovals" "github.com/filecoin-project/lily/model/visor" @@ -91,6 +92,8 @@ var Models = []interface{}{ (*verifreg.VerifiedRegistryVerifier)(nil), (*verifreg.VerifiedRegistryVerifiedClient)(nil), (*verifreg.VerifiedRegistryClaim)(nil), + + (*fevm.FEVMActorStats)(nil), } var log = logging.Logger("lily/storage") diff --git a/tasks/fevmactorstats/task.go b/tasks/fevmactorstats/task.go new file mode 100644 index 000000000..08abe4ca4 --- /dev/null +++ b/tasks/fevmactorstats/task.go @@ -0,0 +1,121 @@ +package fevmactorstats + +import ( + "context" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lily/model/fevm" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/chain/actors/builtin" + evm2 "github.com/filecoin-project/lotus/chain/actors/builtin/evm" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/types" + "github.com/ipfs/go-cid" + logging "github.com/ipfs/go-log/v2" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + + "github.com/filecoin-project/lily/model" + visormodel "github.com/filecoin-project/lily/model/visor" + "github.com/filecoin-project/lily/tasks" +) + +var log = logging.Logger("lily/tasks/fevmactorstats") + +type Task struct { + node tasks.DataSource +} + +func NewTask(node tasks.DataSource) *Task { + return &Task{ + node: node, + } +} + +func (p *Task) ProcessTipSet(ctx context.Context, ts *types.TipSet) (model.Persistable, *visormodel.ProcessingReport, error) { + _, span := otel.Tracer("").Start(ctx, "ProcessTipSet") + if span.IsRecording() { + span.SetAttributes( + attribute.String("tipset", ts.Key().String()), + attribute.Int64("height", int64(ts.Height())), + attribute.String("processor", "fevmactorstats"), + ) + } + report := &visormodel.ProcessingReport{ + Height: int64(ts.Height()), + StateRoot: ts.ParentState().String(), + } + + st, err := state.LoadStateTree(p.node.Store(), ts.ParentState()) + if err != nil { + return nil, report, err + } + + log.Infow("iterating over all actors") + count := 0 + evmBalance := abi.NewTokenAmount(0) + ethAccountBalance := abi.NewTokenAmount(0) + placeholderBalance := abi.NewTokenAmount(0) + evmCount := 0 + ethAccountCount := 0 + placeholderCount := 0 + bytecodeCIDs := []cid.Cid{} + + _ = st.ForEach(func(addr address.Address, act *types.Actor) error { + count++ + + if builtin.IsEvmActor(act.Code) { + evmBalance = types.BigAdd(evmBalance, act.Balance) + evmCount++ + es, err := evm2.Load(p.node.Store(), act) + if err != nil { + log.Errorw("fail to load evm actorcount: ", "error", err) + return err + } + bcid, err := es.GetBytecodeCID() + if err != nil { + log.Errorw("fail to get evm bytecode: ", "error", err) + return err + } + bytecodeCIDs = append(bytecodeCIDs, bcid) + } + + if builtin.IsEthAccountActor(act.Code) { + ethAccountBalance = types.BigAdd(ethAccountBalance, act.Balance) + ethAccountCount++ + } + + if builtin.IsPlaceholderActor(act.Code) { + placeholderBalance = types.BigAdd(placeholderBalance, act.Balance) + placeholderCount++ + } + + return nil + }) + + uniqueBytecode := unique(bytecodeCIDs) + + return &fevm.FEVMActorStats{ + Height: int64(ts.Height()), + ContractBalance: evmBalance.String(), + EthAccountBalance: ethAccountBalance.String(), + PlaceholderBalance: placeholderBalance.String(), + ContractCount: uint64(evmCount), + UniqueContractCount: uint64(len(uniqueBytecode)), + EthAccountCount: uint64(ethAccountCount), + PlaceholderCount: uint64(placeholderCount), + }, report, nil +} +func unique(intSlice []cid.Cid) []cid.Cid { + keys := make(map[cid.Cid]bool) + list := []cid.Cid{} + for _, entry := range intSlice { + if _, value := keys[entry]; !value { + keys[entry] = true + list = append(list, entry) + } + } + return list +}