diff --git a/api/debug.go b/api/debug.go index 128ff5486..271b48a21 100644 --- a/api/debug.go +++ b/api/debug.go @@ -419,18 +419,11 @@ func (d *DebugAPI) traceBlockByNumber( } func (d *DebugAPI) executorAtBlock(block *models.Block) (*evm.BlockExecutor, error) { - previousBlock, err := d.blocks.GetByHeight(block.Height - 1) - if err != nil { - return nil, err - } - - // We need to re-execute all the transactions from the given block, - // on top of the previous block state, to generate the correct traces. - snapshot, err := d.registerStore.GetSnapshotAt(previousBlock.Height) + snapshot, err := d.registerStore.GetSnapshotAt(block.Height) if err != nil { return nil, fmt.Errorf( "failed to get register snapshot at block height %d: %w", - previousBlock.Height, + block.Height, err, ) } diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go index bbcb94c28..352f46cb1 100644 --- a/bootstrap/bootstrap.go +++ b/bootstrap/bootstrap.go @@ -207,7 +207,7 @@ func (b *Bootstrap) StopEventIngestion() { } func (b *Bootstrap) StartAPIServer(ctx context.Context) error { - b.logger.Info().Msg("bootstrap starting metrics server") + b.logger.Info().Msg("bootstrap starting API server") b.server = api.NewServer(b.logger, b.collector, b.config) @@ -372,7 +372,6 @@ func (b *Bootstrap) StopAPIServer() { } func (b *Bootstrap) StartMetricsServer(ctx context.Context) error { - b.metrics = newMetricsWrapper(b.logger, b.config.MetricsPort) return b.metrics.Start(ctx) } @@ -639,45 +638,62 @@ func setupStorage( }, nil } -// Run will run complete bootstrap of the EVM gateway with all the engines. -// Run is a blocking call, but it does signal readiness of the service -// through a channel provided as an argument. -func Run(ctx context.Context, cfg config.Config, ready component.ReadyFunc) error { - boot, err := New(cfg) - if err != nil { - return err - } - +func (b *Bootstrap) Run( + ctx context.Context, + cfg config.Config, + ready component.ReadyFunc, +) error { // Start the API Server first, to avoid any races with incoming // EVM events, that might affect the starting state. - if err := boot.StartAPIServer(ctx); err != nil { + if err := b.StartAPIServer(ctx); err != nil { return fmt.Errorf("failed to start API server: %w", err) } - if err := boot.StartEventIngestion(ctx); err != nil { + if err := b.StartEventIngestion(ctx); err != nil { return fmt.Errorf("failed to start event ingestion engine: %w", err) } - if err := boot.StartMetricsServer(ctx); err != nil { + if err := b.StartMetricsServer(ctx); err != nil { return fmt.Errorf("failed to start metrics server: %w", err) } - if err := boot.StartProfilerServer(ctx); err != nil { + if err := b.StartProfilerServer(ctx); err != nil { return fmt.Errorf("failed to start profiler server: %w", err) } // mark ready ready() + return nil +} + +func (b *Bootstrap) Stop() { + b.logger.Info().Msg("bootstrap received context cancellation, stopping services") + + b.StopEventIngestion() + b.StopMetricsServer() + b.StopAPIServer() + b.StopClient() + b.StopDB() +} + +// Run will run complete bootstrap of the EVM gateway with all the engines. +// Run is a blocking call, but it does signal readiness of the service +// through a channel provided as an argument. +func Run(ctx context.Context, cfg config.Config, ready component.ReadyFunc) error { + boot, err := New(cfg) + if err != nil { + return err + } + + if err := boot.Run(ctx, cfg, ready); err != nil { + return err + } + // if context is canceled start shutdown <-ctx.Done() - boot.logger.Warn().Msg("bootstrap received context cancellation, stopping services") - boot.StopEventIngestion() - boot.StopMetricsServer() - boot.StopAPIServer() - boot.StopClient() - boot.StopDB() + boot.Stop() return nil } diff --git a/go.mod b/go.mod index cfe7ac5d0..71bee8074 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/onflow/atree v0.9.0 github.com/onflow/cadence v1.3.1 - github.com/onflow/flow-go v0.38.1-0.20250213171922-77f4db56bb54 + github.com/onflow/flow-go v0.38.1-0.20250218174738-2181389f9f7d github.com/onflow/flow-go-sdk v1.3.1 github.com/onflow/go-ethereum v1.14.7 github.com/prometheus/client_golang v1.20.5 @@ -135,13 +135,13 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onflow/crypto v0.25.2 // indirect - github.com/onflow/flow-core-contracts/lib/go/contracts v1.4.0 // indirect - github.com/onflow/flow-core-contracts/lib/go/templates v1.4.0 // indirect + github.com/onflow/flow-core-contracts/lib/go/contracts v1.5.1-preview // indirect + github.com/onflow/flow-core-contracts/lib/go/templates v1.5.1-preview // indirect github.com/onflow/flow-ft/lib/go/contracts v1.0.1 // indirect github.com/onflow/flow-ft/lib/go/templates v1.0.1 // indirect - github.com/onflow/flow-nft/lib/go/contracts v1.2.2 // indirect + github.com/onflow/flow-nft/lib/go/contracts v1.2.3 // indirect github.com/onflow/flow-nft/lib/go/templates v1.2.1 // indirect - github.com/onflow/flow/protobuf/go/flow v0.4.7 // indirect + github.com/onflow/flow/protobuf/go/flow v0.4.9 // indirect github.com/onflow/sdks v0.6.0-preview.1 // indirect github.com/onsi/ginkgo v1.16.4 // indirect github.com/onsi/gomega v1.18.1 // indirect diff --git a/go.sum b/go.sum index 718be95a0..3055f70a3 100644 --- a/go.sum +++ b/go.sum @@ -520,24 +520,24 @@ github.com/onflow/cadence v1.3.1 h1:bs9TFHQy8HHbwTtCtg5cLdyndWhmwq55RSwID1cb220= github.com/onflow/cadence v1.3.1/go.mod h1:6/47FljVAdl3/31tShI8JOJW0sXYZHK1PwXkE+yk0qA= github.com/onflow/crypto v0.25.2 h1:GjHunqVt+vPcdqhxxhAXiMIF3YiLX7gTuTR5O+VG2ns= github.com/onflow/crypto v0.25.2/go.mod h1:fY7eLqUdMKV8EGOw301unP8h7PvLVy8/6gVR++/g0BY= -github.com/onflow/flow-core-contracts/lib/go/contracts v1.4.0 h1:R86HaOuk6vpuECZnriEUE7bw9inC2AtdSn8lL/iwQLQ= -github.com/onflow/flow-core-contracts/lib/go/contracts v1.4.0/go.mod h1:9asTBnB6Tw2UlVVtQKyS/egYv3xr4zVlJnJ75z1dfac= -github.com/onflow/flow-core-contracts/lib/go/templates v1.4.0 h1:u2DAG8pk0xFH7TwS70t1gSZ/FtIIZWMSNyiu4SeXBYg= -github.com/onflow/flow-core-contracts/lib/go/templates v1.4.0/go.mod h1:pN768Al/wLRlf3bwugv9TyxniqJxMu4sxnX9eQJam64= +github.com/onflow/flow-core-contracts/lib/go/contracts v1.5.1-preview h1:W+QkNQcIbhtR+zXVROKq0bdDEnvzUfUrQrCmegmwzvc= +github.com/onflow/flow-core-contracts/lib/go/contracts v1.5.1-preview/go.mod h1:LyCICUK6sK1jtEyb+3GuRw5tYfHT1uxACLwLTLxw/0I= +github.com/onflow/flow-core-contracts/lib/go/templates v1.5.1-preview h1:C0PraQFfwpav4nJAf/RPE9BJyYD6lUMvt+cJyiMDeis= +github.com/onflow/flow-core-contracts/lib/go/templates v1.5.1-preview/go.mod h1:pN768Al/wLRlf3bwugv9TyxniqJxMu4sxnX9eQJam64= github.com/onflow/flow-ft/lib/go/contracts v1.0.1 h1:Ts5ob+CoCY2EjEd0W6vdLJ7hLL3SsEftzXG2JlmSe24= github.com/onflow/flow-ft/lib/go/contracts v1.0.1/go.mod h1:PwsL8fC81cjnUnTfmyL/HOIyHnyaw/JA474Wfj2tl6A= github.com/onflow/flow-ft/lib/go/templates v1.0.1 h1:FDYKAiGowABtoMNusLuRCILIZDtVqJ/5tYI4VkF5zfM= github.com/onflow/flow-ft/lib/go/templates v1.0.1/go.mod h1:uQ8XFqmMK2jxyBSVrmyuwdWjTEb+6zGjRYotfDJ5pAE= -github.com/onflow/flow-go v0.38.1-0.20250213171922-77f4db56bb54 h1:JizTOEQ6ME++LOEqOmKUxtM0Q8tpHUvVJBhqBrgDeE0= -github.com/onflow/flow-go v0.38.1-0.20250213171922-77f4db56bb54/go.mod h1:p88l1DhHObIUr+8b0zqkwH0bklZmQksb6IFH8ZaL1Bg= +github.com/onflow/flow-go v0.38.1-0.20250218174738-2181389f9f7d h1:XRefc4rcBjGDEqsj3OB6XjSjSeYwMtysl7jmaLYpg+s= +github.com/onflow/flow-go v0.38.1-0.20250218174738-2181389f9f7d/go.mod h1:VS7MlNHZeDrGm9/jkuMCSvAQTLFXpzQD0BIMB8/QYB8= github.com/onflow/flow-go-sdk v1.3.1 h1:2YdTL/R1/DjMYYmyKgArTeQ93GKvLlfCeCpMVH7b8q4= github.com/onflow/flow-go-sdk v1.3.1/go.mod h1:0rMuCLShdX9F4pLBCPhlMGCFu8gu9SfiXT/Lc9qAi24= -github.com/onflow/flow-nft/lib/go/contracts v1.2.2 h1:XFERNVUDGbZ4ViZjt7P1cGD80mO1PzUJYPfdhXFsGbQ= -github.com/onflow/flow-nft/lib/go/contracts v1.2.2/go.mod h1:eZ9VMMNfCq0ho6kV25xJn1kXeCfxnkhj3MwF3ed08gY= +github.com/onflow/flow-nft/lib/go/contracts v1.2.3 h1:4ju20g1xgDKWBT63rOj5f/Sa4Lc+naCSWT4p31x9yQk= +github.com/onflow/flow-nft/lib/go/contracts v1.2.3/go.mod h1:eZ9VMMNfCq0ho6kV25xJn1kXeCfxnkhj3MwF3ed08gY= github.com/onflow/flow-nft/lib/go/templates v1.2.1 h1:SAALMZPDw9Eb9p5kSLnmnFxjyig1MLiT4JUlLp0/bSE= github.com/onflow/flow-nft/lib/go/templates v1.2.1/go.mod h1:W6hOWU0xltPqNpv9gQX8Pj8Jtf0OmRxc1XX2V0kzJaI= -github.com/onflow/flow/protobuf/go/flow v0.4.7 h1:iP6DFx4wZ3ETORsyeqzHu7neFT3d1CXF6wdK+AOOjmc= -github.com/onflow/flow/protobuf/go/flow v0.4.7/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= +github.com/onflow/flow/protobuf/go/flow v0.4.9 h1:UfsWWqj6VQbEHvaw8kSGvIawCpEfz3gOGZfcdugNxVE= +github.com/onflow/flow/protobuf/go/flow v0.4.9/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/go-ethereum v1.14.7 h1:gg3awYqI02e3AypRdpJKEvNTJ6kz/OhAqRti0h54Wlc= github.com/onflow/go-ethereum v1.14.7/go.mod h1:zV14QLrXyYu5ucvcwHUA0r6UaqveqbXaehAVQJlSW+I= github.com/onflow/nft-storefront/lib/go/contracts v1.0.0 h1:sxyWLqGm/p4EKT6DUlQESDG1ZNMN9GjPCm1gTq7NGfc= diff --git a/metrics/collector.go b/metrics/collector.go index eb11eddc5..126b1a0e8 100644 --- a/metrics/collector.go +++ b/metrics/collector.go @@ -9,6 +9,84 @@ import ( "github.com/rs/zerolog" ) +var apiErrors = prometheus.NewCounter(prometheus.CounterOpts{ + Name: prefixedName("api_errors_total"), + Help: "Total number of API errors", +}) + +var serverPanicsCounters = prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: prefixedName("api_server_panics_total"), + Help: "Total number of panics in the API server", +}, []string{"reason"}) + +var operatorBalance = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: prefixedName("operator_balance"), + Help: "Flow balance of the EVM gateway operator wallet", +}) + +var cadenceBlockHeight = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: prefixedName("cadence_block_height"), + Help: "Current Cadence block height", +}) + +var evmBlockHeight = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: prefixedName("evm_block_height"), + Help: "Current EVM block height", +}) + +var evmBlockIndexedCounter = prometheus.NewCounter(prometheus.CounterOpts{ + Name: prefixedName("blocks_indexed_total"), + Help: "Total number of blocks indexed", +}) + +var evmTxIndexedCounter = prometheus.NewCounter(prometheus.CounterOpts{ + Name: prefixedName("txs_indexed_total"), + Help: "Total number transactions indexed", +}) + +var evmAccountCallCounters = prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: prefixedName("evm_account_interactions_total"), + Help: "Total number of account interactions", +}, []string{"address"}) + +// TODO: Think of adding 'status_code' +var requestDurations = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: prefixedName("api_request_duration_seconds"), + Help: "Duration of the request made a specific API endpoint", + Buckets: prometheus.DefBuckets, +}, []string{"method"}) + +var availableSigningKeys = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: prefixedName("available_signing_keys"), + Help: "Number of keys available for transaction signing", +}) + +var gasEstimationIterations = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: prefixedName("gas_estimation_iterations"), + Help: "Number of iterations taken to estimate the gas of a EVM call/tx", +}) + +var blockIngestionTime = prometheus.NewHistogram(prometheus.HistogramOpts{ + Name: prefixedName("block_ingestion_time_seconds"), + Help: "Time taken to fully ingest an EVM block in the local state index", + Buckets: prometheus.DefBuckets, +}) + +var metrics = []prometheus.Collector{ + apiErrors, + serverPanicsCounters, + cadenceBlockHeight, + evmBlockHeight, + evmBlockIndexedCounter, + evmTxIndexedCounter, + operatorBalance, + evmAccountCallCounters, + requestDurations, + availableSigningKeys, + gasEstimationIterations, + blockIngestionTime, +} + type Collector interface { ApiErrorOccurred() ServerPanicked(reason string) @@ -42,83 +120,6 @@ type DefaultCollector struct { } func NewCollector(logger zerolog.Logger) Collector { - apiErrors := prometheus.NewCounter(prometheus.CounterOpts{ - Name: prefixedName("api_errors_total"), - Help: "Total number of API errors", - }) - - serverPanicsCounters := prometheus.NewCounterVec(prometheus.CounterOpts{ - Name: prefixedName("api_server_panics_total"), - Help: "Total number of panics in the API server", - }, []string{"reason"}) - - operatorBalance := prometheus.NewGauge(prometheus.GaugeOpts{ - Name: prefixedName("operator_balance"), - Help: "Flow balance of the EVM gateway operator wallet", - }) - - cadenceBlockHeight := prometheus.NewGauge(prometheus.GaugeOpts{ - Name: prefixedName("cadence_block_height"), - Help: "Current Cadence block height", - }) - - evmBlockHeight := prometheus.NewGauge(prometheus.GaugeOpts{ - Name: prefixedName("evm_block_height"), - Help: "Current EVM block height", - }) - - evmBlockIndexedCounter := prometheus.NewCounter(prometheus.CounterOpts{ - Name: prefixedName("blocks_indexed_total"), - Help: "Total number of blocks indexed", - }) - - evmTxIndexedCounter := prometheus.NewCounter(prometheus.CounterOpts{ - Name: prefixedName("txs_indexed_total"), - Help: "Total number transactions indexed", - }) - - evmAccountCallCounters := prometheus.NewCounterVec(prometheus.CounterOpts{ - Name: prefixedName("evm_account_interactions_total"), - Help: "Total number of account interactions", - }, []string{"address"}) - - // TODO: Think of adding 'status_code' - requestDurations := prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Name: prefixedName("api_request_duration_seconds"), - Help: "Duration of the request made a specific API endpoint", - Buckets: prometheus.DefBuckets, - }, []string{"method"}) - - availableSigningKeys := prometheus.NewGauge(prometheus.GaugeOpts{ - Name: prefixedName("available_signing_keys"), - Help: "Number of keys available for transaction signing", - }) - - gasEstimationIterations := prometheus.NewGauge(prometheus.GaugeOpts{ - Name: prefixedName("gas_estimation_iterations"), - Help: "Number of iterations taken to estimate the gas of a EVM call/tx", - }) - - blockIngestionTime := prometheus.NewHistogram(prometheus.HistogramOpts{ - Name: prefixedName("block_ingestion_time_seconds"), - Help: "Time taken to fully ingest an EVM block in the local state index", - Buckets: prometheus.DefBuckets, - }) - - metrics := []prometheus.Collector{ - apiErrors, - serverPanicsCounters, - cadenceBlockHeight, - evmBlockHeight, - evmBlockIndexedCounter, - evmTxIndexedCounter, - operatorBalance, - evmAccountCallCounters, - requestDurations, - availableSigningKeys, - gasEstimationIterations, - blockIngestionTime, - } if err := registerMetrics(logger, metrics...); err != nil { logger.Info().Msg("using noop collector as metric register failed") return NopCollector @@ -142,6 +143,10 @@ func NewCollector(logger zerolog.Logger) Collector { func registerMetrics(logger zerolog.Logger, metrics ...prometheus.Collector) error { for _, m := range metrics { + // During E2E tests, the EVM GW might be bootstrapped again + // and again, so we make sure to register the metrics on a + // clean state. + prometheus.Unregister(m) if err := prometheus.Register(m); err != nil { logger.Err(err).Msg("failed to register metric") return err diff --git a/storage/pebble/register_storage.go b/storage/pebble/register_storage.go index c3dc2b4cb..16cbfd84e 100644 --- a/storage/pebble/register_storage.go +++ b/storage/pebble/register_storage.go @@ -178,10 +178,23 @@ func newLookupKey(height uint64, key []byte) *lookupKey { return &lookupKey } -// GetSnapshotAt returns a snapshot of the register index at the given block height. -// the snapshot has a cache. Nil values are cached. -func (r *RegisterStorage) GetSnapshotAt(evmBlockHeight uint64) (types.BackendStorageSnapshot, error) { - return NewStorageSnapshot(r.Get, evmBlockHeight), nil +// GetSnapshotAt returns a snapshot of the register index at the start of the +// given block height (which is the end of the previous block). +// The snapshot has a cache. Nil values are cached. +func (r *RegisterStorage) GetSnapshotAt( + evmBlockHeightOfStartStateToQuery uint64, +) (types.BackendStorageSnapshot, error) { + var snapshotHeightOfEndState uint64 + if evmBlockHeightOfStartStateToQuery > 0 { + // `evmBlockHeightOfStartStateToQuery-1` to get the end state of the previous block. + snapshotHeightOfEndState = evmBlockHeightOfStartStateToQuery - 1 + } else { + // Avoid a possible underflow + snapshotHeightOfEndState = uint64(0) + } + + // NewStorageSnapshot return the end state of a given height. + return NewStorageSnapshot(r.Get, snapshotHeightOfEndState), nil } func registerOwnerMismatch(expected flow.Address, owner flow.Address) error { diff --git a/tests/go.mod b/tests/go.mod index 416b50f9b..e6962b164 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -6,9 +6,9 @@ require ( github.com/goccy/go-json v0.10.2 github.com/onflow/cadence v1.3.1 github.com/onflow/crypto v0.25.2 - github.com/onflow/flow-emulator v1.2.0 + github.com/onflow/flow-emulator v1.2.1-0.20250219181005-4205d790a414 github.com/onflow/flow-evm-gateway v0.0.0-20240201154855-4d4d3d3f19c7 - github.com/onflow/flow-go v0.38.1-0.20250213171922-77f4db56bb54 + github.com/onflow/flow-go v0.38.1-0.20250218174738-2181389f9f7d github.com/onflow/flow-go-sdk v1.3.1 github.com/onflow/go-ethereum v1.14.7 github.com/rs/zerolog v1.33.0 @@ -151,13 +151,13 @@ require ( github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onflow/atree v0.9.0 // indirect github.com/onflow/bridged-usdc/lib/go/contracts v1.0.0 // indirect - github.com/onflow/flow-core-contracts/lib/go/contracts v1.4.0 // indirect - github.com/onflow/flow-core-contracts/lib/go/templates v1.4.0 // indirect + github.com/onflow/flow-core-contracts/lib/go/contracts v1.5.1-preview // indirect + github.com/onflow/flow-core-contracts/lib/go/templates v1.5.1-preview // indirect github.com/onflow/flow-ft/lib/go/contracts v1.0.1 // indirect github.com/onflow/flow-ft/lib/go/templates v1.0.1 // indirect - github.com/onflow/flow-nft/lib/go/contracts v1.2.2 // indirect + github.com/onflow/flow-nft/lib/go/contracts v1.2.3 // indirect github.com/onflow/flow-nft/lib/go/templates v1.2.1 // indirect - github.com/onflow/flow/protobuf/go/flow v0.4.7 // indirect + github.com/onflow/flow/protobuf/go/flow v0.4.9 // indirect github.com/onflow/nft-storefront/lib/go/contracts v1.0.0 // indirect github.com/onflow/sdks v0.6.0-preview.1 // indirect github.com/onflow/wal v1.0.2 // indirect diff --git a/tests/go.sum b/tests/go.sum index 182b28b4b..ae6d4890d 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -794,26 +794,26 @@ github.com/onflow/cadence v1.3.1 h1:bs9TFHQy8HHbwTtCtg5cLdyndWhmwq55RSwID1cb220= github.com/onflow/cadence v1.3.1/go.mod h1:6/47FljVAdl3/31tShI8JOJW0sXYZHK1PwXkE+yk0qA= github.com/onflow/crypto v0.25.2 h1:GjHunqVt+vPcdqhxxhAXiMIF3YiLX7gTuTR5O+VG2ns= github.com/onflow/crypto v0.25.2/go.mod h1:fY7eLqUdMKV8EGOw301unP8h7PvLVy8/6gVR++/g0BY= -github.com/onflow/flow-core-contracts/lib/go/contracts v1.4.0 h1:R86HaOuk6vpuECZnriEUE7bw9inC2AtdSn8lL/iwQLQ= -github.com/onflow/flow-core-contracts/lib/go/contracts v1.4.0/go.mod h1:9asTBnB6Tw2UlVVtQKyS/egYv3xr4zVlJnJ75z1dfac= -github.com/onflow/flow-core-contracts/lib/go/templates v1.4.0 h1:u2DAG8pk0xFH7TwS70t1gSZ/FtIIZWMSNyiu4SeXBYg= -github.com/onflow/flow-core-contracts/lib/go/templates v1.4.0/go.mod h1:pN768Al/wLRlf3bwugv9TyxniqJxMu4sxnX9eQJam64= -github.com/onflow/flow-emulator v1.2.0 h1:Dqamf+sFIgSPG6thRpW+kl3k+wVrquVGI2uVp6y6YJM= -github.com/onflow/flow-emulator v1.2.0/go.mod h1:SNq9U+5gVXm1HV1pFIJn2SFTtQERBMwNWBbSde5ORpA= +github.com/onflow/flow-core-contracts/lib/go/contracts v1.5.1-preview h1:W+QkNQcIbhtR+zXVROKq0bdDEnvzUfUrQrCmegmwzvc= +github.com/onflow/flow-core-contracts/lib/go/contracts v1.5.1-preview/go.mod h1:LyCICUK6sK1jtEyb+3GuRw5tYfHT1uxACLwLTLxw/0I= +github.com/onflow/flow-core-contracts/lib/go/templates v1.5.1-preview h1:C0PraQFfwpav4nJAf/RPE9BJyYD6lUMvt+cJyiMDeis= +github.com/onflow/flow-core-contracts/lib/go/templates v1.5.1-preview/go.mod h1:pN768Al/wLRlf3bwugv9TyxniqJxMu4sxnX9eQJam64= +github.com/onflow/flow-emulator v1.2.1-0.20250219181005-4205d790a414 h1:BqtG980BxX/bY9Bpq1uFm9q1pTvlK2r6nzeXpTyS5TU= +github.com/onflow/flow-emulator v1.2.1-0.20250219181005-4205d790a414/go.mod h1:+RV5j5TYEE0VYtUAEn/vduecy8yK8P+rB+a9BCc3gQA= github.com/onflow/flow-ft/lib/go/contracts v1.0.1 h1:Ts5ob+CoCY2EjEd0W6vdLJ7hLL3SsEftzXG2JlmSe24= github.com/onflow/flow-ft/lib/go/contracts v1.0.1/go.mod h1:PwsL8fC81cjnUnTfmyL/HOIyHnyaw/JA474Wfj2tl6A= github.com/onflow/flow-ft/lib/go/templates v1.0.1 h1:FDYKAiGowABtoMNusLuRCILIZDtVqJ/5tYI4VkF5zfM= github.com/onflow/flow-ft/lib/go/templates v1.0.1/go.mod h1:uQ8XFqmMK2jxyBSVrmyuwdWjTEb+6zGjRYotfDJ5pAE= -github.com/onflow/flow-go v0.38.1-0.20250213171922-77f4db56bb54 h1:JizTOEQ6ME++LOEqOmKUxtM0Q8tpHUvVJBhqBrgDeE0= -github.com/onflow/flow-go v0.38.1-0.20250213171922-77f4db56bb54/go.mod h1:p88l1DhHObIUr+8b0zqkwH0bklZmQksb6IFH8ZaL1Bg= +github.com/onflow/flow-go v0.38.1-0.20250218174738-2181389f9f7d h1:XRefc4rcBjGDEqsj3OB6XjSjSeYwMtysl7jmaLYpg+s= +github.com/onflow/flow-go v0.38.1-0.20250218174738-2181389f9f7d/go.mod h1:VS7MlNHZeDrGm9/jkuMCSvAQTLFXpzQD0BIMB8/QYB8= github.com/onflow/flow-go-sdk v1.3.1 h1:2YdTL/R1/DjMYYmyKgArTeQ93GKvLlfCeCpMVH7b8q4= github.com/onflow/flow-go-sdk v1.3.1/go.mod h1:0rMuCLShdX9F4pLBCPhlMGCFu8gu9SfiXT/Lc9qAi24= -github.com/onflow/flow-nft/lib/go/contracts v1.2.2 h1:XFERNVUDGbZ4ViZjt7P1cGD80mO1PzUJYPfdhXFsGbQ= -github.com/onflow/flow-nft/lib/go/contracts v1.2.2/go.mod h1:eZ9VMMNfCq0ho6kV25xJn1kXeCfxnkhj3MwF3ed08gY= +github.com/onflow/flow-nft/lib/go/contracts v1.2.3 h1:4ju20g1xgDKWBT63rOj5f/Sa4Lc+naCSWT4p31x9yQk= +github.com/onflow/flow-nft/lib/go/contracts v1.2.3/go.mod h1:eZ9VMMNfCq0ho6kV25xJn1kXeCfxnkhj3MwF3ed08gY= github.com/onflow/flow-nft/lib/go/templates v1.2.1 h1:SAALMZPDw9Eb9p5kSLnmnFxjyig1MLiT4JUlLp0/bSE= github.com/onflow/flow-nft/lib/go/templates v1.2.1/go.mod h1:W6hOWU0xltPqNpv9gQX8Pj8Jtf0OmRxc1XX2V0kzJaI= -github.com/onflow/flow/protobuf/go/flow v0.4.7 h1:iP6DFx4wZ3ETORsyeqzHu7neFT3d1CXF6wdK+AOOjmc= -github.com/onflow/flow/protobuf/go/flow v0.4.7/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= +github.com/onflow/flow/protobuf/go/flow v0.4.9 h1:UfsWWqj6VQbEHvaw8kSGvIawCpEfz3gOGZfcdugNxVE= +github.com/onflow/flow/protobuf/go/flow v0.4.9/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/go-ethereum v1.14.7 h1:gg3awYqI02e3AypRdpJKEvNTJ6kz/OhAqRti0h54Wlc= github.com/onflow/go-ethereum v1.14.7/go.mod h1:zV14QLrXyYu5ucvcwHUA0r6UaqveqbXaehAVQJlSW+I= github.com/onflow/nft-storefront/lib/go/contracts v1.0.0 h1:sxyWLqGm/p4EKT6DUlQESDG1ZNMN9GjPCm1gTq7NGfc= diff --git a/tests/integration_test.go b/tests/integration_test.go index 5c9e31693..ed866624d 100644 --- a/tests/integration_test.go +++ b/tests/integration_test.go @@ -437,3 +437,147 @@ func Test_CloudKMSConcurrentTransactionSubmission(t *testing.T) { assert.Equal(t, uint64(1), rcp.Status) } } + +// Test_ForceStartHeightIdempotency verifies that the ingestion process +// remains idempotent when restarting with ForceStartHeight set to an +// earlier block. This ensures that: +// 1. No events are processed twice +// 2. No transactions are lost +// 3. The state remains consistent +func Test_ForceStartHeightIdempotency(t *testing.T) { + srv, err := startEmulator(true) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + defer func() { + cancel() + srv.Stop() + }() + + grpcHost := "localhost:3569" + emu := srv.Emulator() + service := emu.ServiceKey() + + client, err := grpc.NewClient(grpcHost) + require.NoError(t, err) + + time.Sleep(500 * time.Millisecond) // some time to startup + + // create new account with keys used for key-rotation + keyCount := 5 + createdAddr, privateKey, err := bootstrap.CreateMultiKeyAccount( + client, + keyCount, + service.Address, + sc.FungibleToken.Address.HexWithPrefix(), + sc.FlowToken.Address.HexWithPrefix(), + service.PrivateKey, + ) + require.NoError(t, err) + + cfg := config.Config{ + DatabaseDir: t.TempDir(), + AccessNodeHost: grpcHost, + RPCPort: 8545, + RPCHost: "127.0.0.1", + FlowNetworkID: "flow-emulator", + EVMNetworkID: types.FlowEVMPreviewNetChainID, + Coinbase: eoaTestAccount, + COAAddress: *createdAddr, + COAKey: privateKey, + GasPrice: new(big.Int).SetUint64(0), + LogLevel: zerolog.DebugLevel, + LogWriter: testLogWriter(), + TxStateValidation: config.LocalIndexValidation, + } + + rpcTester := &rpcTest{ + url: fmt.Sprintf("%s:%d", cfg.RPCHost, cfg.RPCPort), + } + + boot, err := bootstrap.New(cfg) + require.NoError(t, err) + + ready := make(chan struct{}) + go func() { + err = boot.Run(ctx, cfg, func() { + close(ready) + }) + require.NoError(t, err) + }() + + <-ready + + time.Sleep(3 * time.Second) // some time to startup + + eoaKey, err := crypto.HexToECDSA(eoaTestPrivateKey) + require.NoError(t, err) + + testAddr := common.HexToAddress("55253ed90B70b96C73092D8680915aaF50081194") + + // disable auto-mine so we can control delays + emu.DisableAutoMine() + + totalTxs := keyCount*5 + 3 + hashes := make([]common.Hash, totalTxs) + nonce := uint64(0) + for i := 0; i < totalTxs; i++ { + signed, _, err := evmSign(big.NewInt(10), 21000, eoaKey, nonce, &testAddr, nil) + require.NoError(t, err) + + txHash, err := rpcTester.sendRawTx(signed) + require.NoError(t, err) + hashes[i] = txHash + + // execute commit block every 3 blocks so we make sure we should have + // conflicts with seq numbers if keys not rotated. + if i%3 == 0 { + _, _, _ = emu.ExecuteAndCommitBlock() + } + nonce += 1 + } + + assert.Eventually(t, func() bool { + for _, h := range hashes { + rcp, err := rpcTester.getReceipt(h.String()) + if err != nil || rcp == nil || uint64(1) != rcp.Status { + return false + } + } + + return true + }, time.Second*15, time.Second*1, "all transactions were not executed") + + // Stop the EVM GW service + boot.Stop() + // Set `ForceStartHeight` to an earlier block, to verify that + // the ingestion process is idempotent + cfg.ForceStartCadenceHeight = 1 + + boot, err = bootstrap.New(cfg) + require.NoError(t, err) + + ready2 := make(chan struct{}) + go func() { + err = boot.Run(ctx, cfg, func() { + close(ready2) + }) + require.NoError(t, err) + }() + + <-ready2 + + time.Sleep(3 * time.Second) // some time to startup + + // Verify that the state is consistent after restart + assert.Eventually(t, func() bool { + for _, h := range hashes { + rcp, err := rpcTester.getReceipt(h.String()) + if err != nil || rcp == nil || uint64(1) != rcp.Status { + return false + } + } + + return true + }, time.Second*15, time.Second*1, "all transactions were not executed") +}