From a27641293b55a79240a0b07221798aecc42dc9f6 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Tue, 21 Jan 2025 14:08:29 +0200 Subject: [PATCH 01/19] Implement the RPCBlockHeaderSubscriber for indexing finalized results --- Makefile | 33 ++ bootstrap/bootstrap.go | 2 +- go.mod | 37 +- go.sum | 80 ++- services/ingestion/block_header_subscriber.go | 529 ++++++++++++++++++ services/requester/cross-spork_client.go | 18 + tests/go.mod | 37 +- tests/go.sum | 84 ++- 8 files changed, 691 insertions(+), 129 deletions(-) create mode 100644 services/ingestion/block_header_subscriber.go diff --git a/Makefile b/Makefile index ef0e054cd..b3ff2fa32 100644 --- a/Makefile +++ b/Makefile @@ -227,3 +227,36 @@ endif docker run $(MODE) -p $(HOST_PORT):8545 -p $(HOST_METRICS_PORT):8080 $(MOUNT) "$(CONTAINER_REGISTRY)/flow-evm-gateway:$(IMAGE_TAG)" $(CMD_ARGS) +.PHONY: start-testnet +start-testnet: + rm -rf testnet-db-block-headers/ + rm -rf metrics/data/ + go run cmd/main.go run \ + --database-dir=testnet-db-block-headers \ + --access-node-grpc-host=access.devnet.nodes.onflow.org:9000 \ + --access-node-spork-hosts=access-001.devnet51.nodes.onflow.org:9000 \ + --flow-network-id=flow-testnet \ + --init-cadence-height=211176670 \ + --ws-enabled=true \ + --coinbase=FACF71692421039876a5BB4F10EF7A439D8ef61E \ + --coa-address=0x62631c28c9fc5a91 \ + --coa-key=2892fba444f1d5787739708874e3b01160671924610411ac787ac1379d420f49 \ + --gas-price=100 \ + --log-level=info + +.PHONY: start-mainnet +start-mainnet: + rm -rf mainnet-db-block-headers/ + rm -rf metrics/data/ + go run cmd/main.go run \ + --database-dir=mainnet-db-block-headers \ + --access-node-grpc-host=access.mainnet.nodes.onflow.org:9000 \ + --access-node-spork-hosts=access-001.mainnet25.nodes.onflow.org:9000 \ + --flow-network-id=flow-mainnet \ + --init-cadence-height=85981135 \ + --ws-enabled=true \ + --coinbase=FACF71692421039876a5BB4F10EF7A439D8ef61E \ + --coa-address=0xf1ab99c82dee3526 \ + --coa-key=2892fba444f1d5787739708874e3b01160671924610411ac787ac1379d420f49 \ + --gas-price=100 \ + --log-level=info diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go index 78a57083d..718e46671 100644 --- a/bootstrap/bootstrap.go +++ b/bootstrap/bootstrap.go @@ -142,7 +142,7 @@ func (b *Bootstrap) StartEventIngestion(ctx context.Context) error { chainID := b.config.FlowNetworkID // create event subscriber - subscriber := ingestion.NewRPCEventSubscriber( + subscriber := ingestion.NewRPCBlockHeaderSubscriber( b.logger, b.client, chainID, diff --git a/go.mod b/go.mod index af84aab9f..a4cda12f0 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,10 @@ require ( github.com/cockroachdb/pebble v1.1.1 github.com/goccy/go-json v0.10.2 github.com/hashicorp/go-multierror v1.1.1 - github.com/onflow/atree v0.8.0 - github.com/onflow/cadence v1.2.2 + github.com/onflow/atree v0.8.1 + github.com/onflow/cadence v1.3.0 github.com/onflow/flow-go v0.38.0-preview.0.4 - github.com/onflow/flow-go-sdk v1.2.3 + github.com/onflow/flow-go-sdk v1.3.0 github.com/onflow/go-ethereum v1.14.7 github.com/prometheus/client_golang v1.18.0 github.com/rs/cors v1.8.0 @@ -18,20 +18,19 @@ require ( github.com/sethvargo/go-limiter v1.0.0 github.com/sethvargo/go-retry v0.2.3 github.com/spf13/cobra v1.8.1 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 go.uber.org/ratelimit v0.3.1 golang.org/x/exp v0.0.0-20240119083558-1b970713d09a golang.org/x/sync v0.8.0 - google.golang.org/grpc v1.63.2 + google.golang.org/grpc v1.64.1 ) require ( - cloud.google.com/go v0.112.0 // indirect - cloud.google.com/go/compute v1.24.0 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go v0.112.1 // indirect + cloud.google.com/go/compute/metadata v0.5.0 // indirect cloud.google.com/go/iam v1.1.6 // indirect cloud.google.com/go/kms v1.15.7 // indirect - cloud.google.com/go/storage v1.36.0 // indirect + cloud.google.com/go/storage v1.38.0 // indirect github.com/DataDog/zstd v1.5.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/SaveTheRbtz/mph v0.1.1-0.20240117162131-4166ec7869bc // indirect @@ -88,7 +87,7 @@ require ( github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/googleapis/gax-go/v2 v2.12.2 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -181,10 +180,10 @@ require ( github.com/vmihailenco/msgpack/v4 v4.3.11 // indirect github.com/vmihailenco/tagparser v0.1.1 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/zeebo/blake3 v0.2.3 // indirect + github.com/zeebo/blake3 v0.2.4 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect @@ -196,19 +195,19 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.28.0 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/oauth2 v0.17.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect gonum.org/v1/gonum v0.14.0 // indirect - google.golang.org/api v0.162.0 // indirect + google.golang.org/api v0.169.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.3.0 // indirect diff --git a/go.sum b/go.sum index f4226b2e0..f3cb76227 100644 --- a/go.sum +++ b/go.sum @@ -17,18 +17,16 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= -cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= -cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= @@ -45,8 +43,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8= -cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg= +cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -102,8 +100,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= @@ -179,8 +175,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= -github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.13.10 h1:Ppdil79nN+Vc+mXfge0AuUgmKWuVv4eMqzoIVSdqZek= @@ -337,8 +331,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= -github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA= +github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= @@ -424,7 +418,6 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= -github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -521,10 +514,10 @@ github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onflow/atree v0.8.0 h1:qg5c6J1gVDNObughpEeWm8oxqhPGdEyGrda121GM4u0= -github.com/onflow/atree v0.8.0/go.mod h1:yccR+LR7xc1Jdic0mrjocbHvUD7lnVvg8/Ct1AA5zBo= -github.com/onflow/cadence v1.2.2 h1:LwigF/2lPiXlwX5rFn71KeMpmW5Iu/f/JtsPLLULBCc= -github.com/onflow/cadence v1.2.2/go.mod h1:PYX1xLejqswtDsQzN93x/VpfSKNyjUk6hrkc/mpv7xs= +github.com/onflow/atree v0.8.1 h1:DAnPnL9/Ks3LaAnkQVokokTBG/znTW0DJfovDtJDhLI= +github.com/onflow/atree v0.8.1/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= +github.com/onflow/cadence v1.3.0 h1:COTlTqUACtTvOeFe7+jP9UDVEU3M3OZzrbzzsEbyqCk= +github.com/onflow/cadence v1.3.0/go.mod h1:638c9Zy25EwflSEE7tBFAVM9N6uwcWt77sgKpyYfSTc= 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= @@ -537,8 +530,8 @@ github.com/onflow/flow-ft/lib/go/templates v1.0.1 h1:FDYKAiGowABtoMNusLuRCILIZDt github.com/onflow/flow-ft/lib/go/templates v1.0.1/go.mod h1:uQ8XFqmMK2jxyBSVrmyuwdWjTEb+6zGjRYotfDJ5pAE= github.com/onflow/flow-go v0.38.0-preview.0.4 h1:vjnp6btehu3X/aYjsXYlA3r/GGYeB05so0d7ICtXbmg= github.com/onflow/flow-go v0.38.0-preview.0.4/go.mod h1:c4ubAQ2WIMYY/TOaBvbajROEFWv2HwhKeGOsEdLPIM0= -github.com/onflow/flow-go-sdk v1.2.3 h1:jb+0dIXBO12Zt8x3c2xDXYPv6k3sRTUvhe59M+EcXTI= -github.com/onflow/flow-go-sdk v1.2.3/go.mod h1:jMaffBTlAIdutx+pBhRIigLZFIBYSDDST0Uax1rW2qo= +github.com/onflow/flow-go-sdk v1.3.0 h1:17xYDaemQBoK2pb9F4S8bH7eAKJSqsuj+DlNHv/FCL0= +github.com/onflow/flow-go-sdk v1.3.0/go.mod h1:Rp424erpOcP7XaZ2zVrOEhBImn6CQIaNX7EaqXdyRa8= 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/templates v1.2.1 h1:SAALMZPDw9Eb9p5kSLnmnFxjyig1MLiT4JUlLp0/bSE= @@ -676,8 +669,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= @@ -719,11 +712,10 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= -github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= +github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -735,10 +727,10 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= @@ -863,8 +855,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -874,8 +866,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= -golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1059,8 +1051,8 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.162.0 h1:Vhs54HkaEpkMBdgGdOT2P6F0csGG/vxDS0hWHJzmmps= -google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= +google.golang.org/api v0.169.0 h1:QwWPy71FgMWqJN/l6jVlFHUa29a7dcUy02I8o799nPY= +google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1108,10 +1100,10 @@ google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1129,8 +1121,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1143,8 +1135,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/services/ingestion/block_header_subscriber.go b/services/ingestion/block_header_subscriber.go new file mode 100644 index 000000000..2ab7d46b6 --- /dev/null +++ b/services/ingestion/block_header_subscriber.go @@ -0,0 +1,529 @@ +package ingestion + +import ( + "context" + "errors" + "fmt" + "sort" + "strings" + + "github.com/onflow/cadence/common" + "github.com/onflow/flow-evm-gateway/models" + errs "github.com/onflow/flow-evm-gateway/models/errors" + "github.com/onflow/flow-evm-gateway/services/requester" + "github.com/onflow/flow-go-sdk" + "github.com/onflow/flow-go/fvm/evm/events" + "github.com/onflow/flow-go/fvm/systemcontracts" + flowGo "github.com/onflow/flow-go/model/flow" + "github.com/rs/zerolog" +) + +var _ EventSubscriber = &RPCEventSubscriber{} + +type RPCBlockHeaderSubscriber struct { + logger zerolog.Logger + + client *requester.CrossSporkClient + chain flowGo.ChainID + keyLock requester.KeyLock + height uint64 + + recovery bool + recoveredEvents []flow.Event +} + +func NewRPCBlockHeaderSubscriber( + logger zerolog.Logger, + client *requester.CrossSporkClient, + chainID flowGo.ChainID, + keyLock requester.KeyLock, + startHeight uint64, +) *RPCBlockHeaderSubscriber { + logger = logger.With().Str("component", "subscriber").Logger() + return &RPCBlockHeaderSubscriber{ + logger: logger, + + client: client, + chain: chainID, + keyLock: keyLock, + height: startHeight, + } +} + +// Subscribe will retrieve all the events from the provided height. If the height is from previous +// sporks, it will first backfill all the events in all the previous sporks, and then continue +// to listen all new events in the current spork. +// +// If error is encountered during backfill the subscription will end and the response chanel will be closed. +func (r *RPCBlockHeaderSubscriber) Subscribe(ctx context.Context) <-chan models.BlockEvents { + // buffered channel so that the decoding of the events can happen in parallel to other operations + eventsChan := make(chan models.BlockEvents, 1000) + + go func() { + defer func() { + close(eventsChan) + }() + + // if the height is from the previous spork, backfill all the eventsChan from previous sporks first + if r.client.IsPastSpork(r.height) { + r.logger.Info(). + Uint64("height", r.height). + Msg("height found in previous spork, starting to backfill") + + // backfill all the missed events, handling of context cancellation is done by the producer + for ev := range r.backfill(ctx, r.height) { + eventsChan <- ev + + if ev.Err != nil { + return + } + + // keep updating height, so after we are done back-filling + // it will be at the first height in the current spork + r.height = ev.Events.CadenceHeight() + } + + // after back-filling is done, increment height by one, + // so we start with the height in the current spork + r.height = r.height + 1 + } + + r.logger.Info(). + Uint64("next-height", r.height). + Msg("backfilling done, subscribe for live data") + + // subscribe in the current spork, handling of context cancellation is done by the producer + for ev := range r.subscribe(ctx, r.height) { + eventsChan <- ev + } + + r.logger.Warn().Msg("ended subscription for events") + }() + + return eventsChan +} + +// subscribe to events by the provided height and handle any errors. +// +// Subscribing to EVM specific events and handle any disconnection errors +// as well as context cancellations. +func (r *RPCBlockHeaderSubscriber) subscribe(ctx context.Context, height uint64) <-chan models.BlockEvents { + eventsChan := make(chan models.BlockEvents) + + _, err := r.client.GetBlockHeaderByHeight(ctx, height) + if err != nil { + err = fmt.Errorf("failed to subscribe for events, the block height %d doesn't exist: %w", height, err) + eventsChan <- models.NewBlockEventsError(err) + return eventsChan + } + + blockHeadersChan, errChan, err := r.client.SubscribeBlockHeadersFromStartHeight( + ctx, + height, + flow.BlockStatusFinalized, + ) + if err != nil { + eventsChan <- models.NewBlockEventsError( + fmt.Errorf("failed to subscribe to events by block height: %d, with: %w", height, err), + ) + return eventsChan + } + lastReceivedHeight := height + + go func() { + defer func() { + close(eventsChan) + }() + + for ctx.Err() == nil { + select { + case <-ctx.Done(): + r.logger.Info().Msg("event ingestion received done signal") + return + + case blockHeader, ok := <-blockHeadersChan: + if !ok { + var err error + err = errs.ErrDisconnected + if ctx.Err() != nil { + err = ctx.Err() + } + eventsChan <- models.NewBlockEventsError(err) + return + } + + var blockEvents flow.BlockEvents + + for _, eventType := range blocksFilter(r.chain).EventTypes { + evts, err := r.client.GetEventsForHeightRange( + ctx, + eventType, + blockHeader.Height, + blockHeader.Height, + ) + if err != nil { + return + } + + if len(evts) != 1 { + return + } + blockEvent := evts[0] + blockEvents.Events = append(blockEvents.Events, blockEvent.Events...) + } + + evmEvents := models.NewSingleBlockEvents(blockEvents) + // if events contain an error, or we are in a recovery mode + if evmEvents.Err != nil || r.recovery { + evmEvents = r.recover(ctx, blockEvents, evmEvents.Err) + // if we are still in recovery go to the next event + if r.recovery { + continue + } + } + + for _, evt := range blockEvents.Events { + r.keyLock.UnlockKey(evt.TransactionID) + } + r.keyLock.Notify(blockHeader.Height) + lastReceivedHeight = blockHeader.Height + + eventsChan <- evmEvents + + case err, ok := <-errChan: + if !ok { + var err error + err = errs.ErrDisconnected + if ctx.Err() != nil { + err = ctx.Err() + } + eventsChan <- models.NewBlockEventsError(err) + return + } + + if strings.Contains(errors.Unwrap(err).Error(), "DeadlineExceeded") || + strings.Contains(errors.Unwrap(err).Error(), "unexpected EOF") { + blockHeadersChan, errChan, err = r.client.SubscribeBlockHeadersFromStartHeight( + ctx, + lastReceivedHeight+1, + flow.BlockStatusFinalized, + ) + if err != nil { + eventsChan <- models.NewBlockEventsError( + fmt.Errorf("failed to subscribe to events by block height: %d, with: %w", height, err), + ) + return + } + } else { + eventsChan <- models.NewBlockEventsError(fmt.Errorf("%w: %w", errs.ErrDisconnected, err)) + return + } + } + } + }() + + return eventsChan +} + +// backfill returns a channel that is filled with block events from the provided fromCadenceHeight up to the first +// height in the current spork. +func (r *RPCBlockHeaderSubscriber) backfill(ctx context.Context, fromCadenceHeight uint64) <-chan models.BlockEvents { + eventsChan := make(chan models.BlockEvents) + + go func() { + defer func() { + close(eventsChan) + }() + + for { + // check if the current fromCadenceHeight is still in past sporks, and if not return since we are done with backfilling + if !r.client.IsPastSpork(fromCadenceHeight) { + r.logger.Info(). + Uint64("height", fromCadenceHeight). + Msg("completed backfilling") + + return + } + + var err error + fromCadenceHeight, err = r.backfillSporkFromHeight(ctx, fromCadenceHeight, eventsChan) + if err != nil { + r.logger.Error().Err(err).Msg("error backfilling spork") + eventsChan <- models.NewBlockEventsError(err) + return + } + + r.logger.Info(). + Uint64("next-cadence-height", fromCadenceHeight). + Msg("reached the end of spork, checking next spork") + } + }() + + return eventsChan +} + +// / backfillSporkFromHeight will fill the eventsChan with block events from the provided fromHeight up to the first height in the spork that comes +// after the spork of the provided fromHeight. +func (r *RPCBlockHeaderSubscriber) backfillSporkFromHeight(ctx context.Context, fromCadenceHeight uint64, eventsChan chan<- models.BlockEvents) (uint64, error) { + evmAddress := common.Address(systemcontracts.SystemContractsForChain(r.chain).EVMContract.Address) + + blockExecutedEvent := common.NewAddressLocation( + nil, + evmAddress, + string(events.EventTypeBlockExecuted), + ).ID() + + transactionExecutedEvent := common.NewAddressLocation( + nil, + evmAddress, + string(events.EventTypeTransactionExecuted), + ).ID() + + lastHeight, err := r.client.GetLatestHeightForSpork(ctx, fromCadenceHeight) + if err != nil { + eventsChan <- models.NewBlockEventsError(err) + return 0, err + } + + r.logger.Info(). + Uint64("start-height", fromCadenceHeight). + Uint64("last-spork-height", lastHeight). + Msg("backfilling spork") + + for fromCadenceHeight < lastHeight { + r.logger.Debug().Msg(fmt.Sprintf("backfilling [%d / %d] ...", fromCadenceHeight, lastHeight)) + + startHeight := fromCadenceHeight + endHeight := fromCadenceHeight + maxRangeForGetEvents + if endHeight > lastHeight { + endHeight = lastHeight + } + + blocks, err := r.client.GetEventsForHeightRange(ctx, blockExecutedEvent, startHeight, endHeight) + if err != nil { + return 0, fmt.Errorf("failed to get block events: %w", err) + } + + transactions, err := r.client.GetEventsForHeightRange(ctx, transactionExecutedEvent, startHeight, endHeight) + if err != nil { + return 0, fmt.Errorf("failed to get block events: %w", err) + } + + if len(transactions) != len(blocks) { + return 0, fmt.Errorf("transactions and blocks have different length") + } + + // sort both, just in case + sort.Slice(blocks, func(i, j int) bool { + return blocks[i].Height < blocks[j].Height + }) + sort.Slice(transactions, func(i, j int) bool { + return transactions[i].Height < transactions[j].Height + }) + + for i := range transactions { + if transactions[i].Height != blocks[i].Height { + return 0, fmt.Errorf("transactions and blocks have different height") + } + + // append the transaction events to the block events + blocks[i].Events = append(blocks[i].Events, transactions[i].Events...) + + evmEvents := models.NewSingleBlockEvents(blocks[i]) + if evmEvents.Err != nil && errors.Is(evmEvents.Err, errs.ErrMissingBlock) { + evmEvents, err = r.accumulateBlockEvents( + ctx, + blocks[i], + blockExecutedEvent, + transactionExecutedEvent, + ) + if err != nil { + return 0, err + } + eventsChan <- evmEvents + // advance the height + fromCadenceHeight = evmEvents.Events.CadenceHeight() + 1 + break + } + eventsChan <- evmEvents + + // advance the height + fromCadenceHeight = evmEvents.Events.CadenceHeight() + 1 + } + + } + return fromCadenceHeight, nil +} + +// accumulateBlockEvents will keep fetching `EVM.TransactionExecuted` events +// until it finds their `EVM.BlockExecuted` event. +// At that point it will return the valid models.BlockEvents. +func (r *RPCBlockHeaderSubscriber) accumulateBlockEvents( + ctx context.Context, + block flow.BlockEvents, + blockExecutedEventType string, + txExecutedEventType string, +) (models.BlockEvents, error) { + evmEvents := models.NewSingleBlockEvents(block) + currentHeight := block.Height + transactionEvents := make([]flow.Event, 0) + + for evmEvents.Err != nil && errors.Is(evmEvents.Err, errs.ErrMissingBlock) { + blocks, err := r.client.GetEventsForHeightRange( + ctx, + blockExecutedEventType, + currentHeight, + currentHeight+maxRangeForGetEvents, + ) + if err != nil { + return models.BlockEvents{}, fmt.Errorf("failed to get block events: %w", err) + } + + transactions, err := r.client.GetEventsForHeightRange( + ctx, + txExecutedEventType, + currentHeight, + currentHeight+maxRangeForGetEvents, + ) + if err != nil { + return models.BlockEvents{}, fmt.Errorf("failed to get block events: %w", err) + } + + if len(transactions) != len(blocks) { + return models.BlockEvents{}, fmt.Errorf("transactions and blocks have different length") + } + + // sort both, just in case + sort.Slice(blocks, func(i, j int) bool { + return blocks[i].Height < blocks[j].Height + }) + sort.Slice(transactions, func(i, j int) bool { + return transactions[i].Height < transactions[j].Height + }) + + for i := range blocks { + if transactions[i].Height != blocks[i].Height { + return models.BlockEvents{}, fmt.Errorf("transactions and blocks have different height") + } + + // If no EVM.BlockExecuted event found, keep accumulating the incoming + // EVM.TransactionExecuted events, until we find the EVM.BlockExecuted + // event that includes them. + if len(blocks[i].Events) == 0 { + txEvents := transactions[i].Events + // Sort `EVM.TransactionExecuted` events + sort.Slice(txEvents, func(i, j int) bool { + if txEvents[i].TransactionIndex != txEvents[j].TransactionIndex { + return txEvents[i].TransactionIndex < txEvents[j].TransactionIndex + } + return txEvents[i].EventIndex < txEvents[j].EventIndex + }) + transactionEvents = append(transactionEvents, txEvents...) + } else { + blocks[i].Events = append(blocks[i].Events, transactionEvents...) + // We use `models.NewMultiBlockEvents`, as the `transactionEvents` + // are coming from different Flow blocks. + evmEvents = models.NewMultiBlockEvents(blocks[i]) + if evmEvents.Err == nil { + return evmEvents, nil + } + } + + currentHeight = blocks[i].Height + 1 + } + } + return evmEvents, nil +} + +// fetchMissingData is used as a backup mechanism for fetching EVM-related +// events, when the event streaming API returns an inconsistent response. +// An inconsistent response could be an EVM block that references EVM +// transactions which are not present in the response. It falls back +// to using grpc requests instead of streaming. +func (r *RPCBlockHeaderSubscriber) fetchMissingData( + ctx context.Context, + blockEvents flow.BlockEvents, +) models.BlockEvents { + // remove existing events + blockEvents.Events = nil + + for _, eventType := range blocksFilter(r.chain).EventTypes { + recoveredEvents, err := r.client.GetEventsForHeightRange( + ctx, + eventType, + blockEvents.Height, + blockEvents.Height, + ) + if err != nil { + return models.NewBlockEventsError(err) + } + + if len(recoveredEvents) != 1 { + return models.NewBlockEventsError( + fmt.Errorf( + "received %d but expected 1 event for height %d", + len(recoveredEvents), + blockEvents.Height, + ), + ) + } + + blockEvents.Events = append(blockEvents.Events, recoveredEvents[0].Events...) + } + + return models.NewSingleBlockEvents(blockEvents) +} + +// accumulateEventsMissingBlock will keep receiving transaction events until it can produce a valid +// EVM block event containing a block and transactions. At that point it will reset the recovery mode +// and return the valid block events. +func (r *RPCBlockHeaderSubscriber) accumulateEventsMissingBlock(events flow.BlockEvents) models.BlockEvents { + txEvents := events.Events + // Sort `EVM.TransactionExecuted` events + sort.Slice(txEvents, func(i, j int) bool { + if txEvents[i].TransactionIndex != txEvents[j].TransactionIndex { + return txEvents[i].TransactionIndex < txEvents[j].TransactionIndex + } + return txEvents[i].EventIndex < txEvents[j].EventIndex + }) + r.recoveredEvents = append(r.recoveredEvents, txEvents...) + events.Events = r.recoveredEvents + + // We use `models.NewMultiBlockEvents`, as the `transactionEvents` + // are coming from different Flow blocks. + recovered := models.NewMultiBlockEvents(events) + r.recovery = recovered.Err != nil + + if !r.recovery { + r.recoveredEvents = nil + } + + return recovered +} + +// recover tries to recover from an invalid data sent over the event stream. +// +// An invalid data can be a cause of corrupted index or network issue from the source, +// in which case we might miss one of the events (missing transaction), or it can be +// due to a failure from the system transaction which commits an EVM block, which results +// in missing EVM block event but present transactions. +func (r *RPCBlockHeaderSubscriber) recover( + ctx context.Context, + events flow.BlockEvents, + err error, +) models.BlockEvents { + r.logger.Warn().Err(err).Msgf( + "failed to parse EVM block events for Flow height: %d, entering recovery", + events.Height, + ) + + if errors.Is(err, errs.ErrMissingBlock) || r.recovery { + return r.accumulateEventsMissingBlock(events) + } + + if errors.Is(err, errs.ErrMissingTransactions) { + return r.fetchMissingData(ctx, events) + } + + return models.NewBlockEventsError(err) +} diff --git a/services/requester/cross-spork_client.go b/services/requester/cross-spork_client.go index cddfc9297..955f3475c 100644 --- a/services/requester/cross-spork_client.go +++ b/services/requester/cross-spork_client.go @@ -9,6 +9,7 @@ import ( errs "github.com/onflow/flow-evm-gateway/models/errors" "github.com/onflow/flow-go-sdk" "github.com/onflow/flow-go-sdk/access" + "github.com/onflow/flow-go-sdk/access/grpc" flowGo "github.com/onflow/flow-go/model/flow" "github.com/rs/zerolog" "go.uber.org/ratelimit" @@ -249,6 +250,23 @@ func (c *CrossSporkClient) GetEventsForHeightRange( return client.GetEventsForHeightRange(ctx, eventType, startHeight, endHeight) } +func (c *CrossSporkClient) SubscribeBlockHeadersFromStartHeight( + ctx context.Context, + startHeight uint64, + blockStatus flow.BlockStatus, +) (<-chan flow.BlockHeader, <-chan error, error) { + client, err := c.getClientForHeight(startHeight) + if err != nil { + return nil, nil, err + } + grpcClient, ok := (client).(*grpc.Client) + if !ok { + return nil, nil, fmt.Errorf("unable to convert to Flow grpc.Client") + } + + return grpcClient.SubscribeBlockHeadersFromStartHeight(ctx, startHeight, blockStatus) +} + func (c *CrossSporkClient) Close() error { var merr *multierror.Error diff --git a/tests/go.mod b/tests/go.mod index 65b9c20e4..5435f864e 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -4,24 +4,23 @@ go 1.22 require ( github.com/goccy/go-json v0.10.2 - github.com/onflow/cadence v1.2.2 + github.com/onflow/cadence v1.3.0 github.com/onflow/crypto v0.25.2 github.com/onflow/flow-emulator v1.1.1-0.20241125195348-4e121ffb12af github.com/onflow/flow-evm-gateway v0.0.0-20240201154855-4d4d3d3f19c7 github.com/onflow/flow-go v0.38.0-preview.0.4 - github.com/onflow/flow-go-sdk v1.2.3 + github.com/onflow/flow-go-sdk v1.3.0 github.com/onflow/go-ethereum v1.14.7 github.com/rs/zerolog v1.33.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 ) require ( - cloud.google.com/go v0.112.0 // indirect - cloud.google.com/go/compute v1.24.0 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go v0.112.1 // indirect + cloud.google.com/go/compute/metadata v0.5.0 // indirect cloud.google.com/go/iam v1.1.6 // indirect cloud.google.com/go/kms v1.15.7 // indirect - cloud.google.com/go/storage v1.36.0 // indirect + cloud.google.com/go/storage v1.38.0 // indirect github.com/DataDog/zstd v1.5.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/SaveTheRbtz/mph v0.1.1-0.20240117162131-4166ec7869bc // indirect @@ -91,7 +90,7 @@ require ( github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/googleapis/gax-go/v2 v2.12.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect @@ -151,7 +150,7 @@ require ( github.com/multiformats/go-multistream v0.5.0 // indirect github.com/multiformats/go-varint v0.0.7 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/onflow/atree v0.8.0 // indirect + github.com/onflow/atree v0.8.1 // 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-ft/lib/go/contracts v1.0.1 // indirect @@ -205,10 +204,10 @@ require ( github.com/vmihailenco/msgpack/v4 v4.3.11 // indirect github.com/vmihailenco/tagparser v0.1.1 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/zeebo/blake3 v0.2.3 // indirect + github.com/zeebo/blake3 v0.2.4 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect @@ -222,8 +221,8 @@ require ( go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/oauth2 v0.17.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/term v0.25.0 // indirect @@ -231,13 +230,13 @@ require ( golang.org/x/time v0.5.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect gonum.org/v1/gonum v0.14.0 // indirect - google.golang.org/api v0.162.0 // indirect + google.golang.org/api v0.169.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/grpc v1.63.2 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/grpc v1.64.1 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools v2.2.0+incompatible // indirect diff --git a/tests/go.sum b/tests/go.sum index fc082fd7b..a967861ab 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -17,18 +17,16 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= -cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= -cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= @@ -45,8 +43,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8= -cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg= +cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -151,8 +149,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= @@ -254,8 +250,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= -github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.13.10 h1:Ppdil79nN+Vc+mXfge0AuUgmKWuVv4eMqzoIVSdqZek= @@ -466,8 +460,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= -github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA= +github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -615,7 +609,6 @@ github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= -github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -783,10 +776,10 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onflow/atree v0.8.0 h1:qg5c6J1gVDNObughpEeWm8oxqhPGdEyGrda121GM4u0= -github.com/onflow/atree v0.8.0/go.mod h1:yccR+LR7xc1Jdic0mrjocbHvUD7lnVvg8/Ct1AA5zBo= -github.com/onflow/cadence v1.2.2 h1:LwigF/2lPiXlwX5rFn71KeMpmW5Iu/f/JtsPLLULBCc= -github.com/onflow/cadence v1.2.2/go.mod h1:PYX1xLejqswtDsQzN93x/VpfSKNyjUk6hrkc/mpv7xs= +github.com/onflow/atree v0.8.1 h1:DAnPnL9/Ks3LaAnkQVokokTBG/znTW0DJfovDtJDhLI= +github.com/onflow/atree v0.8.1/go.mod h1:FT6udJF9Q7VQTu3wknDhFX+VV4D44ZGdqtTAE5iztck= +github.com/onflow/cadence v1.3.0 h1:COTlTqUACtTvOeFe7+jP9UDVEU3M3OZzrbzzsEbyqCk= +github.com/onflow/cadence v1.3.0/go.mod h1:638c9Zy25EwflSEE7tBFAVM9N6uwcWt77sgKpyYfSTc= 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= @@ -801,8 +794,8 @@ github.com/onflow/flow-ft/lib/go/templates v1.0.1 h1:FDYKAiGowABtoMNusLuRCILIZDt github.com/onflow/flow-ft/lib/go/templates v1.0.1/go.mod h1:uQ8XFqmMK2jxyBSVrmyuwdWjTEb+6zGjRYotfDJ5pAE= github.com/onflow/flow-go v0.38.0-preview.0.4 h1:vjnp6btehu3X/aYjsXYlA3r/GGYeB05so0d7ICtXbmg= github.com/onflow/flow-go v0.38.0-preview.0.4/go.mod h1:c4ubAQ2WIMYY/TOaBvbajROEFWv2HwhKeGOsEdLPIM0= -github.com/onflow/flow-go-sdk v1.2.3 h1:jb+0dIXBO12Zt8x3c2xDXYPv6k3sRTUvhe59M+EcXTI= -github.com/onflow/flow-go-sdk v1.2.3/go.mod h1:jMaffBTlAIdutx+pBhRIigLZFIBYSDDST0Uax1rW2qo= +github.com/onflow/flow-go-sdk v1.3.0 h1:17xYDaemQBoK2pb9F4S8bH7eAKJSqsuj+DlNHv/FCL0= +github.com/onflow/flow-go-sdk v1.3.0/go.mod h1:Rp424erpOcP7XaZ2zVrOEhBImn6CQIaNX7EaqXdyRa8= 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/templates v1.2.1 h1:SAALMZPDw9Eb9p5kSLnmnFxjyig1MLiT4JUlLp0/bSE= @@ -1016,8 +1009,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= @@ -1068,11 +1061,10 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= -github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= +github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -1088,10 +1080,10 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= @@ -1240,8 +1232,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1251,8 +1243,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= -golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1454,8 +1446,8 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.162.0 h1:Vhs54HkaEpkMBdgGdOT2P6F0csGG/vxDS0hWHJzmmps= -google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= +google.golang.org/api v0.169.0 h1:QwWPy71FgMWqJN/l6jVlFHUa29a7dcUy02I8o799nPY= +google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1507,12 +1499,12 @@ google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= -google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe h1:weYsP+dNijSQVoLAb5bpUos3ciBpNU/NEVlHFKrk8pg= -google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:SCz6T5xjNXM4QFPRwxHcfChp7V+9DcXR3ay2TkHR8Tg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240304161311-37d4d3c04a78 h1:YqFWYZXim8bG9v68xU8WjTZmYKb5M5dMeSOWIp6jogI= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:vh/N7795ftP0AkN1w8XKqN4w1OdUKXW5Eummda+ofv8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -1535,8 +1527,8 @@ google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1549,8 +1541,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 7d36012cde6f171a82e9d116bcc6f55af9f94d5d Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Thu, 23 Jan 2025 13:41:51 +0200 Subject: [PATCH 02/19] Rename RPCBlockHeaderSubscriber to RPCBlockTrackingSubscriber --- bootstrap/bootstrap.go | 2 +- ...criber.go => block_tracking_subscriber.go} | 26 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) rename services/ingestion/{block_header_subscriber.go => block_tracking_subscriber.go} (93%) diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go index 718e46671..af89179df 100644 --- a/bootstrap/bootstrap.go +++ b/bootstrap/bootstrap.go @@ -142,7 +142,7 @@ func (b *Bootstrap) StartEventIngestion(ctx context.Context) error { chainID := b.config.FlowNetworkID // create event subscriber - subscriber := ingestion.NewRPCBlockHeaderSubscriber( + subscriber := ingestion.NewRPCBlockTrackingSubscriber( b.logger, b.client, chainID, diff --git a/services/ingestion/block_header_subscriber.go b/services/ingestion/block_tracking_subscriber.go similarity index 93% rename from services/ingestion/block_header_subscriber.go rename to services/ingestion/block_tracking_subscriber.go index 2ab7d46b6..feb15aac2 100644 --- a/services/ingestion/block_header_subscriber.go +++ b/services/ingestion/block_tracking_subscriber.go @@ -18,9 +18,9 @@ import ( "github.com/rs/zerolog" ) -var _ EventSubscriber = &RPCEventSubscriber{} +var _ EventSubscriber = &RPCBlockTrackingSubscriber{} -type RPCBlockHeaderSubscriber struct { +type RPCBlockTrackingSubscriber struct { logger zerolog.Logger client *requester.CrossSporkClient @@ -32,15 +32,15 @@ type RPCBlockHeaderSubscriber struct { recoveredEvents []flow.Event } -func NewRPCBlockHeaderSubscriber( +func NewRPCBlockTrackingSubscriber( logger zerolog.Logger, client *requester.CrossSporkClient, chainID flowGo.ChainID, keyLock requester.KeyLock, startHeight uint64, -) *RPCBlockHeaderSubscriber { +) *RPCBlockTrackingSubscriber { logger = logger.With().Str("component", "subscriber").Logger() - return &RPCBlockHeaderSubscriber{ + return &RPCBlockTrackingSubscriber{ logger: logger, client: client, @@ -55,7 +55,7 @@ func NewRPCBlockHeaderSubscriber( // to listen all new events in the current spork. // // If error is encountered during backfill the subscription will end and the response chanel will be closed. -func (r *RPCBlockHeaderSubscriber) Subscribe(ctx context.Context) <-chan models.BlockEvents { +func (r *RPCBlockTrackingSubscriber) Subscribe(ctx context.Context) <-chan models.BlockEvents { // buffered channel so that the decoding of the events can happen in parallel to other operations eventsChan := make(chan models.BlockEvents, 1000) @@ -107,7 +107,7 @@ func (r *RPCBlockHeaderSubscriber) Subscribe(ctx context.Context) <-chan models. // // Subscribing to EVM specific events and handle any disconnection errors // as well as context cancellations. -func (r *RPCBlockHeaderSubscriber) subscribe(ctx context.Context, height uint64) <-chan models.BlockEvents { +func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint64) <-chan models.BlockEvents { eventsChan := make(chan models.BlockEvents) _, err := r.client.GetBlockHeaderByHeight(ctx, height) @@ -227,7 +227,7 @@ func (r *RPCBlockHeaderSubscriber) subscribe(ctx context.Context, height uint64) // backfill returns a channel that is filled with block events from the provided fromCadenceHeight up to the first // height in the current spork. -func (r *RPCBlockHeaderSubscriber) backfill(ctx context.Context, fromCadenceHeight uint64) <-chan models.BlockEvents { +func (r *RPCBlockTrackingSubscriber) backfill(ctx context.Context, fromCadenceHeight uint64) <-chan models.BlockEvents { eventsChan := make(chan models.BlockEvents) go func() { @@ -264,7 +264,7 @@ func (r *RPCBlockHeaderSubscriber) backfill(ctx context.Context, fromCadenceHeig // / backfillSporkFromHeight will fill the eventsChan with block events from the provided fromHeight up to the first height in the spork that comes // after the spork of the provided fromHeight. -func (r *RPCBlockHeaderSubscriber) backfillSporkFromHeight(ctx context.Context, fromCadenceHeight uint64, eventsChan chan<- models.BlockEvents) (uint64, error) { +func (r *RPCBlockTrackingSubscriber) backfillSporkFromHeight(ctx context.Context, fromCadenceHeight uint64, eventsChan chan<- models.BlockEvents) (uint64, error) { evmAddress := common.Address(systemcontracts.SystemContractsForChain(r.chain).EVMContract.Address) blockExecutedEvent := common.NewAddressLocation( @@ -358,7 +358,7 @@ func (r *RPCBlockHeaderSubscriber) backfillSporkFromHeight(ctx context.Context, // accumulateBlockEvents will keep fetching `EVM.TransactionExecuted` events // until it finds their `EVM.BlockExecuted` event. // At that point it will return the valid models.BlockEvents. -func (r *RPCBlockHeaderSubscriber) accumulateBlockEvents( +func (r *RPCBlockTrackingSubscriber) accumulateBlockEvents( ctx context.Context, block flow.BlockEvents, blockExecutedEventType string, @@ -440,7 +440,7 @@ func (r *RPCBlockHeaderSubscriber) accumulateBlockEvents( // An inconsistent response could be an EVM block that references EVM // transactions which are not present in the response. It falls back // to using grpc requests instead of streaming. -func (r *RPCBlockHeaderSubscriber) fetchMissingData( +func (r *RPCBlockTrackingSubscriber) fetchMissingData( ctx context.Context, blockEvents flow.BlockEvents, ) models.BlockEvents { @@ -477,7 +477,7 @@ func (r *RPCBlockHeaderSubscriber) fetchMissingData( // accumulateEventsMissingBlock will keep receiving transaction events until it can produce a valid // EVM block event containing a block and transactions. At that point it will reset the recovery mode // and return the valid block events. -func (r *RPCBlockHeaderSubscriber) accumulateEventsMissingBlock(events flow.BlockEvents) models.BlockEvents { +func (r *RPCBlockTrackingSubscriber) accumulateEventsMissingBlock(events flow.BlockEvents) models.BlockEvents { txEvents := events.Events // Sort `EVM.TransactionExecuted` events sort.Slice(txEvents, func(i, j int) bool { @@ -507,7 +507,7 @@ func (r *RPCBlockHeaderSubscriber) accumulateEventsMissingBlock(events flow.Bloc // in which case we might miss one of the events (missing transaction), or it can be // due to a failure from the system transaction which commits an EVM block, which results // in missing EVM block event but present transactions. -func (r *RPCBlockHeaderSubscriber) recover( +func (r *RPCBlockTrackingSubscriber) recover( ctx context.Context, events flow.BlockEvents, err error, From 75a1d183f31aea5d115006b7e27a690215aaaa7b Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Thu, 23 Jan 2025 13:52:11 +0200 Subject: [PATCH 03/19] Remove redundant fetching of block header prior to SubscribeBlockHeadersFromStartHeight --- services/ingestion/block_tracking_subscriber.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/services/ingestion/block_tracking_subscriber.go b/services/ingestion/block_tracking_subscriber.go index feb15aac2..fc317ffc2 100644 --- a/services/ingestion/block_tracking_subscriber.go +++ b/services/ingestion/block_tracking_subscriber.go @@ -110,13 +110,6 @@ func (r *RPCBlockTrackingSubscriber) Subscribe(ctx context.Context) <-chan model func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint64) <-chan models.BlockEvents { eventsChan := make(chan models.BlockEvents) - _, err := r.client.GetBlockHeaderByHeight(ctx, height) - if err != nil { - err = fmt.Errorf("failed to subscribe for events, the block height %d doesn't exist: %w", height, err) - eventsChan <- models.NewBlockEventsError(err) - return eventsChan - } - blockHeadersChan, errChan, err := r.client.SubscribeBlockHeadersFromStartHeight( ctx, height, From ce83ebb0f271b27da0f4eac808b31c093dae86e0 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Thu, 23 Jan 2025 14:13:12 +0200 Subject: [PATCH 04/19] Improve error handling in RPCBlockTrackingSubscriber subscription --- .../ingestion/block_tracking_subscriber.go | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/services/ingestion/block_tracking_subscriber.go b/services/ingestion/block_tracking_subscriber.go index fc317ffc2..79140745d 100644 --- a/services/ingestion/block_tracking_subscriber.go +++ b/services/ingestion/block_tracking_subscriber.go @@ -117,7 +117,11 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 ) if err != nil { eventsChan <- models.NewBlockEventsError( - fmt.Errorf("failed to subscribe to events by block height: %d, with: %w", height, err), + fmt.Errorf( + "failed to subscribe for finalized block headers on height: %d, with: %w", + height, + err, + ), ) return eventsChan } @@ -146,7 +150,6 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 } var blockEvents flow.BlockEvents - for _, eventType := range blocksFilter(r.chain).EventTypes { evts, err := r.client.GetEventsForHeightRange( ctx, @@ -155,10 +158,24 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 blockHeader.Height, ) if err != nil { + eventsChan <- models.NewBlockEventsError( + fmt.Errorf( + "failed to fetch EVM events for height: %d, with: %w", + blockHeader.Height, + err, + ), + ) return } if len(evts) != 1 { + eventsChan <- models.NewBlockEventsError( + fmt.Errorf( + "received unexpected number of EVM events for height: %d, got: %d, expected: 1", + blockHeader.Height, + len(evts), + ), + ) return } blockEvent := evts[0] @@ -203,7 +220,11 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 ) if err != nil { eventsChan <- models.NewBlockEventsError( - fmt.Errorf("failed to subscribe to events by block height: %d, with: %w", height, err), + fmt.Errorf( + "failed to subscribe for finalized block headers on height: %d, with: %w", + height, + err, + ), ) return } From d1594671e2c93cf489659bd4907b407e67ade004 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Thu, 23 Jan 2025 15:45:53 +0200 Subject: [PATCH 05/19] Simplify RPCBlockTrackingSubscriber by embedding RPCEventSubscriber for function resuse --- .../ingestion/block_tracking_subscriber.go | 336 +----------------- 1 file changed, 19 insertions(+), 317 deletions(-) diff --git a/services/ingestion/block_tracking_subscriber.go b/services/ingestion/block_tracking_subscriber.go index 79140745d..a7de72e74 100644 --- a/services/ingestion/block_tracking_subscriber.go +++ b/services/ingestion/block_tracking_subscriber.go @@ -4,16 +4,12 @@ import ( "context" "errors" "fmt" - "sort" "strings" - "github.com/onflow/cadence/common" "github.com/onflow/flow-evm-gateway/models" errs "github.com/onflow/flow-evm-gateway/models/errors" "github.com/onflow/flow-evm-gateway/services/requester" "github.com/onflow/flow-go-sdk" - "github.com/onflow/flow-go/fvm/evm/events" - "github.com/onflow/flow-go/fvm/systemcontracts" flowGo "github.com/onflow/flow-go/model/flow" "github.com/rs/zerolog" ) @@ -21,15 +17,13 @@ import ( var _ EventSubscriber = &RPCBlockTrackingSubscriber{} type RPCBlockTrackingSubscriber struct { - logger zerolog.Logger + *RPCEventSubscriber + logger zerolog.Logger client *requester.CrossSporkClient chain flowGo.ChainID keyLock requester.KeyLock height uint64 - - recovery bool - recoveredEvents []flow.Event } func NewRPCBlockTrackingSubscriber( @@ -39,14 +33,22 @@ func NewRPCBlockTrackingSubscriber( keyLock requester.KeyLock, startHeight uint64, ) *RPCBlockTrackingSubscriber { + eventSubscriber := NewRPCEventSubscriber( + logger, + client, + chainID, + keyLock, + startHeight, + ) logger = logger.With().Str("component", "subscriber").Logger() - return &RPCBlockTrackingSubscriber{ - logger: logger, - client: client, - chain: chainID, - keyLock: keyLock, - height: startHeight, + return &RPCBlockTrackingSubscriber{ + RPCEventSubscriber: eventSubscriber, + logger: logger, + client: client, + chain: chainID, + keyLock: keyLock, + height: startHeight, } } @@ -179,6 +181,9 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 return } blockEvent := evts[0] + blockEvents.BlockID = blockEvent.BlockID + blockEvents.BlockTimestamp = blockEvent.BlockTimestamp + blockEvents.Height = blockEvent.Height blockEvents.Events = append(blockEvents.Events, blockEvent.Events...) } @@ -238,306 +243,3 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 return eventsChan } - -// backfill returns a channel that is filled with block events from the provided fromCadenceHeight up to the first -// height in the current spork. -func (r *RPCBlockTrackingSubscriber) backfill(ctx context.Context, fromCadenceHeight uint64) <-chan models.BlockEvents { - eventsChan := make(chan models.BlockEvents) - - go func() { - defer func() { - close(eventsChan) - }() - - for { - // check if the current fromCadenceHeight is still in past sporks, and if not return since we are done with backfilling - if !r.client.IsPastSpork(fromCadenceHeight) { - r.logger.Info(). - Uint64("height", fromCadenceHeight). - Msg("completed backfilling") - - return - } - - var err error - fromCadenceHeight, err = r.backfillSporkFromHeight(ctx, fromCadenceHeight, eventsChan) - if err != nil { - r.logger.Error().Err(err).Msg("error backfilling spork") - eventsChan <- models.NewBlockEventsError(err) - return - } - - r.logger.Info(). - Uint64("next-cadence-height", fromCadenceHeight). - Msg("reached the end of spork, checking next spork") - } - }() - - return eventsChan -} - -// / backfillSporkFromHeight will fill the eventsChan with block events from the provided fromHeight up to the first height in the spork that comes -// after the spork of the provided fromHeight. -func (r *RPCBlockTrackingSubscriber) backfillSporkFromHeight(ctx context.Context, fromCadenceHeight uint64, eventsChan chan<- models.BlockEvents) (uint64, error) { - evmAddress := common.Address(systemcontracts.SystemContractsForChain(r.chain).EVMContract.Address) - - blockExecutedEvent := common.NewAddressLocation( - nil, - evmAddress, - string(events.EventTypeBlockExecuted), - ).ID() - - transactionExecutedEvent := common.NewAddressLocation( - nil, - evmAddress, - string(events.EventTypeTransactionExecuted), - ).ID() - - lastHeight, err := r.client.GetLatestHeightForSpork(ctx, fromCadenceHeight) - if err != nil { - eventsChan <- models.NewBlockEventsError(err) - return 0, err - } - - r.logger.Info(). - Uint64("start-height", fromCadenceHeight). - Uint64("last-spork-height", lastHeight). - Msg("backfilling spork") - - for fromCadenceHeight < lastHeight { - r.logger.Debug().Msg(fmt.Sprintf("backfilling [%d / %d] ...", fromCadenceHeight, lastHeight)) - - startHeight := fromCadenceHeight - endHeight := fromCadenceHeight + maxRangeForGetEvents - if endHeight > lastHeight { - endHeight = lastHeight - } - - blocks, err := r.client.GetEventsForHeightRange(ctx, blockExecutedEvent, startHeight, endHeight) - if err != nil { - return 0, fmt.Errorf("failed to get block events: %w", err) - } - - transactions, err := r.client.GetEventsForHeightRange(ctx, transactionExecutedEvent, startHeight, endHeight) - if err != nil { - return 0, fmt.Errorf("failed to get block events: %w", err) - } - - if len(transactions) != len(blocks) { - return 0, fmt.Errorf("transactions and blocks have different length") - } - - // sort both, just in case - sort.Slice(blocks, func(i, j int) bool { - return blocks[i].Height < blocks[j].Height - }) - sort.Slice(transactions, func(i, j int) bool { - return transactions[i].Height < transactions[j].Height - }) - - for i := range transactions { - if transactions[i].Height != blocks[i].Height { - return 0, fmt.Errorf("transactions and blocks have different height") - } - - // append the transaction events to the block events - blocks[i].Events = append(blocks[i].Events, transactions[i].Events...) - - evmEvents := models.NewSingleBlockEvents(blocks[i]) - if evmEvents.Err != nil && errors.Is(evmEvents.Err, errs.ErrMissingBlock) { - evmEvents, err = r.accumulateBlockEvents( - ctx, - blocks[i], - blockExecutedEvent, - transactionExecutedEvent, - ) - if err != nil { - return 0, err - } - eventsChan <- evmEvents - // advance the height - fromCadenceHeight = evmEvents.Events.CadenceHeight() + 1 - break - } - eventsChan <- evmEvents - - // advance the height - fromCadenceHeight = evmEvents.Events.CadenceHeight() + 1 - } - - } - return fromCadenceHeight, nil -} - -// accumulateBlockEvents will keep fetching `EVM.TransactionExecuted` events -// until it finds their `EVM.BlockExecuted` event. -// At that point it will return the valid models.BlockEvents. -func (r *RPCBlockTrackingSubscriber) accumulateBlockEvents( - ctx context.Context, - block flow.BlockEvents, - blockExecutedEventType string, - txExecutedEventType string, -) (models.BlockEvents, error) { - evmEvents := models.NewSingleBlockEvents(block) - currentHeight := block.Height - transactionEvents := make([]flow.Event, 0) - - for evmEvents.Err != nil && errors.Is(evmEvents.Err, errs.ErrMissingBlock) { - blocks, err := r.client.GetEventsForHeightRange( - ctx, - blockExecutedEventType, - currentHeight, - currentHeight+maxRangeForGetEvents, - ) - if err != nil { - return models.BlockEvents{}, fmt.Errorf("failed to get block events: %w", err) - } - - transactions, err := r.client.GetEventsForHeightRange( - ctx, - txExecutedEventType, - currentHeight, - currentHeight+maxRangeForGetEvents, - ) - if err != nil { - return models.BlockEvents{}, fmt.Errorf("failed to get block events: %w", err) - } - - if len(transactions) != len(blocks) { - return models.BlockEvents{}, fmt.Errorf("transactions and blocks have different length") - } - - // sort both, just in case - sort.Slice(blocks, func(i, j int) bool { - return blocks[i].Height < blocks[j].Height - }) - sort.Slice(transactions, func(i, j int) bool { - return transactions[i].Height < transactions[j].Height - }) - - for i := range blocks { - if transactions[i].Height != blocks[i].Height { - return models.BlockEvents{}, fmt.Errorf("transactions and blocks have different height") - } - - // If no EVM.BlockExecuted event found, keep accumulating the incoming - // EVM.TransactionExecuted events, until we find the EVM.BlockExecuted - // event that includes them. - if len(blocks[i].Events) == 0 { - txEvents := transactions[i].Events - // Sort `EVM.TransactionExecuted` events - sort.Slice(txEvents, func(i, j int) bool { - if txEvents[i].TransactionIndex != txEvents[j].TransactionIndex { - return txEvents[i].TransactionIndex < txEvents[j].TransactionIndex - } - return txEvents[i].EventIndex < txEvents[j].EventIndex - }) - transactionEvents = append(transactionEvents, txEvents...) - } else { - blocks[i].Events = append(blocks[i].Events, transactionEvents...) - // We use `models.NewMultiBlockEvents`, as the `transactionEvents` - // are coming from different Flow blocks. - evmEvents = models.NewMultiBlockEvents(blocks[i]) - if evmEvents.Err == nil { - return evmEvents, nil - } - } - - currentHeight = blocks[i].Height + 1 - } - } - return evmEvents, nil -} - -// fetchMissingData is used as a backup mechanism for fetching EVM-related -// events, when the event streaming API returns an inconsistent response. -// An inconsistent response could be an EVM block that references EVM -// transactions which are not present in the response. It falls back -// to using grpc requests instead of streaming. -func (r *RPCBlockTrackingSubscriber) fetchMissingData( - ctx context.Context, - blockEvents flow.BlockEvents, -) models.BlockEvents { - // remove existing events - blockEvents.Events = nil - - for _, eventType := range blocksFilter(r.chain).EventTypes { - recoveredEvents, err := r.client.GetEventsForHeightRange( - ctx, - eventType, - blockEvents.Height, - blockEvents.Height, - ) - if err != nil { - return models.NewBlockEventsError(err) - } - - if len(recoveredEvents) != 1 { - return models.NewBlockEventsError( - fmt.Errorf( - "received %d but expected 1 event for height %d", - len(recoveredEvents), - blockEvents.Height, - ), - ) - } - - blockEvents.Events = append(blockEvents.Events, recoveredEvents[0].Events...) - } - - return models.NewSingleBlockEvents(blockEvents) -} - -// accumulateEventsMissingBlock will keep receiving transaction events until it can produce a valid -// EVM block event containing a block and transactions. At that point it will reset the recovery mode -// and return the valid block events. -func (r *RPCBlockTrackingSubscriber) accumulateEventsMissingBlock(events flow.BlockEvents) models.BlockEvents { - txEvents := events.Events - // Sort `EVM.TransactionExecuted` events - sort.Slice(txEvents, func(i, j int) bool { - if txEvents[i].TransactionIndex != txEvents[j].TransactionIndex { - return txEvents[i].TransactionIndex < txEvents[j].TransactionIndex - } - return txEvents[i].EventIndex < txEvents[j].EventIndex - }) - r.recoveredEvents = append(r.recoveredEvents, txEvents...) - events.Events = r.recoveredEvents - - // We use `models.NewMultiBlockEvents`, as the `transactionEvents` - // are coming from different Flow blocks. - recovered := models.NewMultiBlockEvents(events) - r.recovery = recovered.Err != nil - - if !r.recovery { - r.recoveredEvents = nil - } - - return recovered -} - -// recover tries to recover from an invalid data sent over the event stream. -// -// An invalid data can be a cause of corrupted index or network issue from the source, -// in which case we might miss one of the events (missing transaction), or it can be -// due to a failure from the system transaction which commits an EVM block, which results -// in missing EVM block event but present transactions. -func (r *RPCBlockTrackingSubscriber) recover( - ctx context.Context, - events flow.BlockEvents, - err error, -) models.BlockEvents { - r.logger.Warn().Err(err).Msgf( - "failed to parse EVM block events for Flow height: %d, entering recovery", - events.Height, - ) - - if errors.Is(err, errs.ErrMissingBlock) || r.recovery { - return r.accumulateEventsMissingBlock(events) - } - - if errors.Is(err, errs.ErrMissingTransactions) { - return r.fetchMissingData(ctx, events) - } - - return models.NewBlockEventsError(err) -} From 5e8f388212145367330b6f5b9e968ab0bb4bcab5 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Fri, 24 Jan 2025 14:08:12 +0200 Subject: [PATCH 06/19] Optimize RPCBlockTrackingSubscriber to avoid fetch EVM tx events for empty EVM blocks --- .../ingestion/block_tracking_subscriber.go | 100 ++++++++++++------ 1 file changed, 66 insertions(+), 34 deletions(-) diff --git a/services/ingestion/block_tracking_subscriber.go b/services/ingestion/block_tracking_subscriber.go index a7de72e74..9186533bf 100644 --- a/services/ingestion/block_tracking_subscriber.go +++ b/services/ingestion/block_tracking_subscriber.go @@ -10,7 +10,9 @@ import ( errs "github.com/onflow/flow-evm-gateway/models/errors" "github.com/onflow/flow-evm-gateway/services/requester" "github.com/onflow/flow-go-sdk" + "github.com/onflow/flow-go/fvm/evm/events" flowGo "github.com/onflow/flow-go/model/flow" + "github.com/onflow/go-ethereum/core/types" "github.com/rs/zerolog" ) @@ -151,40 +153,10 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 return } - var blockEvents flow.BlockEvents - for _, eventType := range blocksFilter(r.chain).EventTypes { - evts, err := r.client.GetEventsForHeightRange( - ctx, - eventType, - blockHeader.Height, - blockHeader.Height, - ) - if err != nil { - eventsChan <- models.NewBlockEventsError( - fmt.Errorf( - "failed to fetch EVM events for height: %d, with: %w", - blockHeader.Height, - err, - ), - ) - return - } - - if len(evts) != 1 { - eventsChan <- models.NewBlockEventsError( - fmt.Errorf( - "received unexpected number of EVM events for height: %d, got: %d, expected: 1", - blockHeader.Height, - len(evts), - ), - ) - return - } - blockEvent := evts[0] - blockEvents.BlockID = blockEvent.BlockID - blockEvents.BlockTimestamp = blockEvent.BlockTimestamp - blockEvents.Height = blockEvent.Height - blockEvents.Events = append(blockEvents.Events, blockEvent.Events...) + blockEvents, err := r.evmEventsForHeight(ctx, blockHeader.Height) + if err != nil { + eventsChan <- models.NewBlockEventsError(err) + return } evmEvents := models.NewSingleBlockEvents(blockEvents) @@ -243,3 +215,63 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 return eventsChan } + +func (r *RPCBlockTrackingSubscriber) evmEventsForHeight( + ctx context.Context, + height uint64, +) (flow.BlockEvents, error) { + eventTypes := blocksFilter(r.chain).EventTypes + evmBlockEvent := eventTypes[0] + + evts, err := r.client.GetEventsForHeightRange( + ctx, + evmBlockEvent, + height, + height, + ) + if err != nil { + return flow.BlockEvents{}, err + } + + if len(evts) != 1 && len(evts[0].Events) != 1 { + return flow.BlockEvents{}, fmt.Errorf( + "received unexpected number of EVM events for height: %d, got: %d, expected: 1", + height, + len(evts), + ) + } + + blockEvents := evts[0] + payload, err := events.DecodeBlockEventPayload(blockEvents.Events[0].Value) + if err != nil { + return flow.BlockEvents{}, err + } + + if payload.TransactionHashRoot == types.EmptyTxsHash { + return blockEvents, nil + } + + evmTxEvent := eventTypes[1] + evts, err = r.client.GetEventsForHeightRange( + ctx, + evmTxEvent, + height, + height, + ) + if err != nil { + return flow.BlockEvents{}, err + } + + if len(evts) != 1 && len(evts[0].Events) != 1 { + return flow.BlockEvents{}, fmt.Errorf( + "received unexpected number of EVM events for height: %d, got: %d, expected: 1", + height, + len(evts), + ) + } + txEvents := evts[0] + + blockEvents.Events = append(blockEvents.Events, txEvents.Events...) + + return blockEvents, nil +} From bfe61889f9d71293354569b3576e0839038a2cb7 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Mon, 27 Jan 2025 11:42:51 +0200 Subject: [PATCH 07/19] Improve length checks for EVM related events --- services/ingestion/block_tracking_subscriber.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/services/ingestion/block_tracking_subscriber.go b/services/ingestion/block_tracking_subscriber.go index 9186533bf..e3fa32ad2 100644 --- a/services/ingestion/block_tracking_subscriber.go +++ b/services/ingestion/block_tracking_subscriber.go @@ -233,7 +233,10 @@ func (r *RPCBlockTrackingSubscriber) evmEventsForHeight( return flow.BlockEvents{}, err } - if len(evts) != 1 && len(evts[0].Events) != 1 { + // We are requesting the `EVM.BlockExecuted` events for a single Flow block, + // so we expect the length of `evts` to equal 1. + // The `EVM.BlockExecuted` event should be present for every Flow block. + if len(evts) != 1 || len(evts[0].Events) != 1 { return flow.BlockEvents{}, fmt.Errorf( "received unexpected number of EVM events for height: %d, got: %d, expected: 1", height, @@ -262,7 +265,9 @@ func (r *RPCBlockTrackingSubscriber) evmEventsForHeight( return flow.BlockEvents{}, err } - if len(evts) != 1 && len(evts[0].Events) != 1 { + // We are requesting the `EVM.TransactionExecuted` events for a single + // Flow block, so we expect the length of `evts` to equal 1. + if len(evts) != 1 { return flow.BlockEvents{}, fmt.Errorf( "received unexpected number of EVM events for height: %d, got: %d, expected: 1", height, From 037c23463b7471c60e0529a7d7da77c9b99239e6 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Mon, 27 Jan 2025 11:51:56 +0200 Subject: [PATCH 08/19] Close events channel in RPCBlockTrackingSubscriber --- services/ingestion/block_tracking_subscriber.go | 1 + 1 file changed, 1 insertion(+) diff --git a/services/ingestion/block_tracking_subscriber.go b/services/ingestion/block_tracking_subscriber.go index e3fa32ad2..9d712ba37 100644 --- a/services/ingestion/block_tracking_subscriber.go +++ b/services/ingestion/block_tracking_subscriber.go @@ -127,6 +127,7 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 err, ), ) + close(eventsChan) return eventsChan } lastReceivedHeight := height From 6afde4d1b3899eb557c6a8b757c04a3d5454361f Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Mon, 27 Jan 2025 13:05:04 +0200 Subject: [PATCH 09/19] Improve error handling to use the gRPC status code instead of strings --- services/ingestion/block_tracking_subscriber.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/services/ingestion/block_tracking_subscriber.go b/services/ingestion/block_tracking_subscriber.go index 9d712ba37..b08b8dfba 100644 --- a/services/ingestion/block_tracking_subscriber.go +++ b/services/ingestion/block_tracking_subscriber.go @@ -2,9 +2,7 @@ package ingestion import ( "context" - "errors" "fmt" - "strings" "github.com/onflow/flow-evm-gateway/models" errs "github.com/onflow/flow-evm-gateway/models/errors" @@ -14,6 +12,8 @@ import ( flowGo "github.com/onflow/flow-go/model/flow" "github.com/onflow/go-ethereum/core/types" "github.com/rs/zerolog" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) var _ EventSubscriber = &RPCBlockTrackingSubscriber{} @@ -189,8 +189,7 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 return } - if strings.Contains(errors.Unwrap(err).Error(), "DeadlineExceeded") || - strings.Contains(errors.Unwrap(err).Error(), "unexpected EOF") { + if status.Code(err) == codes.DeadlineExceeded || status.Code(err) == codes.Internal { blockHeadersChan, errChan, err = r.client.SubscribeBlockHeadersFromStartHeight( ctx, lastReceivedHeight+1, From ff927f7c6d78feb8f54ecf6c40eeeb8051bb7d60 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Mon, 27 Jan 2025 13:10:39 +0200 Subject: [PATCH 10/19] Simplify constructor of RPCBlockTrackingSubscriber Co-authored-by: Peter Argue <89119817+peterargue@users.noreply.github.com> --- .../ingestion/block_tracking_subscriber.go | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/services/ingestion/block_tracking_subscriber.go b/services/ingestion/block_tracking_subscriber.go index b08b8dfba..13c169d0b 100644 --- a/services/ingestion/block_tracking_subscriber.go +++ b/services/ingestion/block_tracking_subscriber.go @@ -35,22 +35,14 @@ func NewRPCBlockTrackingSubscriber( keyLock requester.KeyLock, startHeight uint64, ) *RPCBlockTrackingSubscriber { - eventSubscriber := NewRPCEventSubscriber( - logger, - client, - chainID, - keyLock, - startHeight, - ) - logger = logger.With().Str("component", "subscriber").Logger() - return &RPCBlockTrackingSubscriber{ - RPCEventSubscriber: eventSubscriber, - logger: logger, - client: client, - chain: chainID, - keyLock: keyLock, - height: startHeight, + RPCEventSubscriber: NewRPCEventSubscriber( + logger.With().Str("component", "subscriber").Logger(), + client, + chainID, + keyLock, + startHeight, + ) } } From 455526349ceb345e2d7bf3aef3d131ffaa78139e Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Mon, 27 Jan 2025 13:20:25 +0200 Subject: [PATCH 11/19] Remove redundant fields from RPCBlockTrackingSubscriber --- services/ingestion/block_tracking_subscriber.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/services/ingestion/block_tracking_subscriber.go b/services/ingestion/block_tracking_subscriber.go index 13c169d0b..0da7371c9 100644 --- a/services/ingestion/block_tracking_subscriber.go +++ b/services/ingestion/block_tracking_subscriber.go @@ -20,12 +20,6 @@ var _ EventSubscriber = &RPCBlockTrackingSubscriber{} type RPCBlockTrackingSubscriber struct { *RPCEventSubscriber - - logger zerolog.Logger - client *requester.CrossSporkClient - chain flowGo.ChainID - keyLock requester.KeyLock - height uint64 } func NewRPCBlockTrackingSubscriber( @@ -42,7 +36,7 @@ func NewRPCBlockTrackingSubscriber( chainID, keyLock, startHeight, - ) + ), } } From 34dbde58be0875f54d63f7751be53efeebcec583 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Mon, 27 Jan 2025 13:41:15 +0200 Subject: [PATCH 12/19] Make us of GetEventsForBlockIDs method instead of GetEventsForHeightRange --- .../ingestion/block_tracking_subscriber.go | 20 +++++++++---------- services/requester/cross-spork_client.go | 12 +++++++++++ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/services/ingestion/block_tracking_subscriber.go b/services/ingestion/block_tracking_subscriber.go index 0da7371c9..12fa7c77b 100644 --- a/services/ingestion/block_tracking_subscriber.go +++ b/services/ingestion/block_tracking_subscriber.go @@ -140,7 +140,7 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 return } - blockEvents, err := r.evmEventsForHeight(ctx, blockHeader.Height) + blockEvents, err := r.evmEventsForBlockHeader(ctx, blockHeader) if err != nil { eventsChan <- models.NewBlockEventsError(err) return @@ -202,18 +202,17 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 return eventsChan } -func (r *RPCBlockTrackingSubscriber) evmEventsForHeight( +func (r *RPCBlockTrackingSubscriber) evmEventsForBlockHeader( ctx context.Context, - height uint64, + blockHeader flow.BlockHeader, ) (flow.BlockEvents, error) { eventTypes := blocksFilter(r.chain).EventTypes evmBlockEvent := eventTypes[0] - evts, err := r.client.GetEventsForHeightRange( + evts, err := r.client.GetEventsForBlockHeader( ctx, evmBlockEvent, - height, - height, + blockHeader, ) if err != nil { return flow.BlockEvents{}, err @@ -225,7 +224,7 @@ func (r *RPCBlockTrackingSubscriber) evmEventsForHeight( if len(evts) != 1 || len(evts[0].Events) != 1 { return flow.BlockEvents{}, fmt.Errorf( "received unexpected number of EVM events for height: %d, got: %d, expected: 1", - height, + blockHeader.Height, len(evts), ) } @@ -241,11 +240,10 @@ func (r *RPCBlockTrackingSubscriber) evmEventsForHeight( } evmTxEvent := eventTypes[1] - evts, err = r.client.GetEventsForHeightRange( + evts, err = r.client.GetEventsForBlockHeader( ctx, evmTxEvent, - height, - height, + blockHeader, ) if err != nil { return flow.BlockEvents{}, err @@ -256,7 +254,7 @@ func (r *RPCBlockTrackingSubscriber) evmEventsForHeight( if len(evts) != 1 { return flow.BlockEvents{}, fmt.Errorf( "received unexpected number of EVM events for height: %d, got: %d, expected: 1", - height, + blockHeader.Height, len(evts), ) } diff --git a/services/requester/cross-spork_client.go b/services/requester/cross-spork_client.go index 955f3475c..a659a2d58 100644 --- a/services/requester/cross-spork_client.go +++ b/services/requester/cross-spork_client.go @@ -250,6 +250,18 @@ func (c *CrossSporkClient) GetEventsForHeightRange( return client.GetEventsForHeightRange(ctx, eventType, startHeight, endHeight) } +func (c *CrossSporkClient) GetEventsForBlockHeader( + ctx context.Context, + eventType string, + blockHeader flow.BlockHeader, +) ([]flow.BlockEvents, error) { + client, err := c.getClientForHeight(blockHeader.Height) + if err != nil { + return nil, err + } + return client.GetEventsForBlockIDs(ctx, eventType, []flow.Identifier{blockHeader.ID}) +} + func (c *CrossSporkClient) SubscribeBlockHeadersFromStartHeight( ctx context.Context, startHeight uint64, From bf4626c82504b69f0bcc2c9e24a159f95b5e4e37 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Mon, 27 Jan 2025 13:46:08 +0200 Subject: [PATCH 13/19] Add description on RPCBlockTrackingSubscriber type Co-authored-by: Peter Argue <89119817+peterargue@users.noreply.github.com> --- services/ingestion/block_tracking_subscriber.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/services/ingestion/block_tracking_subscriber.go b/services/ingestion/block_tracking_subscriber.go index 12fa7c77b..71405a550 100644 --- a/services/ingestion/block_tracking_subscriber.go +++ b/services/ingestion/block_tracking_subscriber.go @@ -18,6 +18,19 @@ import ( var _ EventSubscriber = &RPCBlockTrackingSubscriber{} +// RPCBlockTrackingSubscriber subscribes to new EVM block events for unsealed finalized blocks. +// This is accomplished by following finalized blocks from the upstream Access node, and using the +// polling endpoint to fetch the events for each finalized block. +// +// IMPORTANT: Since data is downloaded and processed from unsealed blocks, it's possible for the +// data that was downloaded to be incorrect. This subscriber provides no handling or detection for +// cases where the received data differs from the data that was ultimately sealed. The operator must +// handle this manually. +// Since it's not reasonable to expect operators to do this manual tracking, this features should NOT +// be used outside of a limited Proof of Concept. Use at own risk. +// +// A future version of the RPCEventSubscriber will provide this detection and handling functionality +// at which point this subscriber will be removed. type RPCBlockTrackingSubscriber struct { *RPCEventSubscriber } From 1f084be5542acb74baf3cdb874e0cff500a0107b Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Mon, 27 Jan 2025 14:14:19 +0200 Subject: [PATCH 14/19] Refactor function for fetching EVM events on a given block header --- .../ingestion/block_tracking_subscriber.go | 66 ++++++++++--------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/services/ingestion/block_tracking_subscriber.go b/services/ingestion/block_tracking_subscriber.go index 71405a550..0a6359057 100644 --- a/services/ingestion/block_tracking_subscriber.go +++ b/services/ingestion/block_tracking_subscriber.go @@ -3,6 +3,7 @@ package ingestion import ( "context" "fmt" + "strings" "github.com/onflow/flow-evm-gateway/models" errs "github.com/onflow/flow-evm-gateway/models/errors" @@ -153,7 +154,7 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 return } - blockEvents, err := r.evmEventsForBlockHeader(ctx, blockHeader) + blockEvents, err := r.evmEventsForBlock(ctx, blockHeader) if err != nil { eventsChan <- models.NewBlockEventsError(err) return @@ -215,34 +216,18 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 return eventsChan } -func (r *RPCBlockTrackingSubscriber) evmEventsForBlockHeader( +func (r *RPCBlockTrackingSubscriber) evmEventsForBlock( ctx context.Context, blockHeader flow.BlockHeader, ) (flow.BlockEvents, error) { eventTypes := blocksFilter(r.chain).EventTypes - evmBlockEvent := eventTypes[0] - evts, err := r.client.GetEventsForBlockHeader( - ctx, - evmBlockEvent, - blockHeader, - ) + // evm Block events + blockEvents, err := r.getEventsByType(ctx, blockHeader, eventTypes[0]) if err != nil { return flow.BlockEvents{}, err } - // We are requesting the `EVM.BlockExecuted` events for a single Flow block, - // so we expect the length of `evts` to equal 1. - // The `EVM.BlockExecuted` event should be present for every Flow block. - if len(evts) != 1 || len(evts[0].Events) != 1 { - return flow.BlockEvents{}, fmt.Errorf( - "received unexpected number of EVM events for height: %d, got: %d, expected: 1", - blockHeader.Height, - len(evts), - ) - } - - blockEvents := evts[0] payload, err := events.DecodeBlockEventPayload(blockEvents.Events[0].Value) if err != nil { return flow.BlockEvents{}, err @@ -252,28 +237,49 @@ func (r *RPCBlockTrackingSubscriber) evmEventsForBlockHeader( return blockEvents, nil } - evmTxEvent := eventTypes[1] - evts, err = r.client.GetEventsForBlockHeader( + // evm TX events + txEvents, err := r.getEventsByType(ctx, blockHeader, eventTypes[1]) + if err != nil { + return flow.BlockEvents{}, err + } + + // combine block and tx events to be processed together + blockEvents.Events = append(blockEvents.Events, txEvents.Events...) + + return blockEvents, nil +} + +func (r *RPCBlockTrackingSubscriber) getEventsByType( + ctx context.Context, + blockHeader flow.BlockHeader, + eventType string, +) (flow.BlockEvents, error) { + evts, err := r.client.GetEventsForBlockHeader( ctx, - evmTxEvent, + eventType, blockHeader, ) if err != nil { return flow.BlockEvents{}, err } - // We are requesting the `EVM.TransactionExecuted` events for a single - // Flow block, so we expect the length of `evts` to equal 1. if len(evts) != 1 { + // this shouldn't happen and probably indicates a bug on the Access node. return flow.BlockEvents{}, fmt.Errorf( - "received unexpected number of EVM events for height: %d, got: %d, expected: 1", - blockHeader.Height, + "received unexpected number of block events: got: %d, expected: 1", len(evts), ) } - txEvents := evts[0] - blockEvents.Events = append(blockEvents.Events, txEvents.Events...) + // The `EVM.BlockExecuted` event should be present for every Flow block. + if strings.Contains(eventType, string(events.EventTypeBlockExecuted)) { + if len(evts[0].Events) != 1 { + return flow.BlockEvents{}, fmt.Errorf( + "received unexpected number of EVM events in block: got: %d, expected: 1", + len(evts[0].Events), + ) + } + } - return blockEvents, nil + return evts[0], nil } From a8329e4dc7bf4395dc412ef86a1de798630f058c Mon Sep 17 00:00:00 2001 From: Peter Argue <89119817+peterargue@users.noreply.github.com> Date: Tue, 28 Jan 2025 10:48:29 -0800 Subject: [PATCH 15/19] Make PoC configurable --- bootstrap/bootstrap.go | 25 ++++++++++++++++++------- cmd/run/cmd.go | 5 +++++ config/config.go | 4 ++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go index af89179df..84bceb994 100644 --- a/bootstrap/bootstrap.go +++ b/bootstrap/bootstrap.go @@ -142,13 +142,24 @@ func (b *Bootstrap) StartEventIngestion(ctx context.Context) error { chainID := b.config.FlowNetworkID // create event subscriber - subscriber := ingestion.NewRPCBlockTrackingSubscriber( - b.logger, - b.client, - chainID, - b.keystore, - latestCadenceHeight, - ) + var subscriber ingestion.EventSubscriber + if b.config.ExperimentalSoftFinalityEnabled { + subscriber = ingestion.NewRPCEventSubscriber( + b.logger, + b.client, + chainID, + b.keystore, + latestCadenceHeight, + ) + } else { + subscriber = ingestion.NewRPCBlockTrackingSubscriber( + b.logger, + b.client, + chainID, + b.keystore, + latestCadenceHeight, + ) + } callTracerCollector, err := replayer.NewCallTracerCollector(b.logger) if err != nil { diff --git a/cmd/run/cmd.go b/cmd/run/cmd.go index 52aa19dc3..5f40301b8 100644 --- a/cmd/run/cmd.go +++ b/cmd/run/cmd.go @@ -222,6 +222,8 @@ func parseConfigFromFlags() error { return fmt.Errorf("unknown tx state validation: %s", txStateValidation) } + cfg.ExperimentalSoftFinalityEnabled = experimentalSoftFinalityEnabled + return nil } @@ -246,6 +248,8 @@ var ( initHeight, forceStartHeight uint64 + + experimentalSoftFinalityEnabled bool ) func init() { @@ -280,4 +284,5 @@ func init() { Cmd.Flags().StringVar(&cfg.ProfilerHost, "profiler-host", "localhost", "Host for the Profiler server") Cmd.Flags().IntVar(&cfg.ProfilerPort, "profiler-port", 6060, "Port for the Profiler server") Cmd.Flags().StringVar(&txStateValidation, "tx-state-validation", "tx-seal", "Sets the transaction validation mechanism. It can validate using the local state index, or wait for the outer Flow transaction to seal. Available values ('local-index' / 'tx-seal'), defaults to 'tx-seal'.") + Cmd.Flags().BoolVar(&experimentalSoftFinalityEnabled, "experimental-soft-finality-enabled", false, "Sets whether the gateway should use the experimental soft finality feature. WARNING: This may result in incorrect results being returned in certain circumstances. Use only if you know what you are doing.") } diff --git a/config/config.go b/config/config.go index 570258d20..da7051b2d 100644 --- a/config/config.go +++ b/config/config.go @@ -89,4 +89,8 @@ type Config struct { // TxStateValidation sets the transaction validation mechanism. It can validate // using the local state index, or wait for the outer Flow transaction to seal. TxStateValidation string + // ExperimentalSoftFinalityEnabled enables the experimental soft finality feature which syncs + // EVM block and transaction data from the upstream Access node before the block is sealed. + // CAUTION: This feature is experimental and may return incorrect data in certain circumstances. + ExperimentalSoftFinalityEnabled bool } From fa39603f070798a84d6e8657f00ae09c8ee98b91 Mon Sep 17 00:00:00 2001 From: Peter Argue <89119817+peterargue@users.noreply.github.com> Date: Tue, 28 Jan 2025 10:50:26 -0800 Subject: [PATCH 16/19] fix enabled conditional --- bootstrap/bootstrap.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go index 84bceb994..bcff42f6e 100644 --- a/bootstrap/bootstrap.go +++ b/bootstrap/bootstrap.go @@ -144,7 +144,7 @@ func (b *Bootstrap) StartEventIngestion(ctx context.Context) error { // create event subscriber var subscriber ingestion.EventSubscriber if b.config.ExperimentalSoftFinalityEnabled { - subscriber = ingestion.NewRPCEventSubscriber( + subscriber = ingestion.NewRPCBlockTrackingSubscriber( b.logger, b.client, chainID, @@ -152,7 +152,7 @@ func (b *Bootstrap) StartEventIngestion(ctx context.Context) error { latestCadenceHeight, ) } else { - subscriber = ingestion.NewRPCBlockTrackingSubscriber( + subscriber = ingestion.NewRPCEventSubscriber( b.logger, b.client, chainID, From 823e965cda7f7c21e0948285e6ed698dfa40d528 Mon Sep 17 00:00:00 2001 From: Peter Argue <89119817+peterargue@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:55:18 -0800 Subject: [PATCH 17/19] improve error reconnection handling --- .../ingestion/block_tracking_subscriber.go | 62 ++++++++++++------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/services/ingestion/block_tracking_subscriber.go b/services/ingestion/block_tracking_subscriber.go index 0a6359057..0e269486b 100644 --- a/services/ingestion/block_tracking_subscriber.go +++ b/services/ingestion/block_tracking_subscriber.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strings" + "time" "github.com/onflow/flow-evm-gateway/models" errs "github.com/onflow/flow-evm-gateway/models/errors" @@ -114,12 +115,21 @@ func (r *RPCBlockTrackingSubscriber) Subscribe(ctx context.Context) <-chan model func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint64) <-chan models.BlockEvents { eventsChan := make(chan models.BlockEvents) - blockHeadersChan, errChan, err := r.client.SubscribeBlockHeadersFromStartHeight( - ctx, - height, - flow.BlockStatusFinalized, - ) - if err != nil { + var blockHeadersChan <-chan flow.BlockHeader + var errChan <-chan error + + lastReceivedHeight := height + connect := func(height uint64) error { + var err error + blockHeadersChan, errChan, err = r.client.SubscribeBlockHeadersFromStartHeight( + ctx, + height, + flow.BlockStatusFinalized, + ) + return err + } + + if err := connect(lastReceivedHeight); err != nil { eventsChan <- models.NewBlockEventsError( fmt.Errorf( "failed to subscribe for finalized block headers on height: %d, with: %w", @@ -130,7 +140,6 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 close(eventsChan) return eventsChan } - lastReceivedHeight := height go func() { defer func() { @@ -145,6 +154,7 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 case blockHeader, ok := <-blockHeadersChan: if !ok { + // typically we receive an error in the errChan before the channels are closes var err error err = errs.ErrDisconnected if ctx.Err() != nil { @@ -180,6 +190,7 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 case err, ok := <-errChan: if !ok { + // typically we receive an error in the errChan before the channels are closes var err error err = errs.ErrDisconnected if ctx.Err() != nil { @@ -189,26 +200,33 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 return } - if status.Code(err) == codes.DeadlineExceeded || status.Code(err) == codes.Internal { - blockHeadersChan, errChan, err = r.client.SubscribeBlockHeadersFromStartHeight( - ctx, - lastReceivedHeight+1, - flow.BlockStatusFinalized, - ) - if err != nil { - eventsChan <- models.NewBlockEventsError( - fmt.Errorf( - "failed to subscribe for finalized block headers on height: %d, with: %w", - height, - err, - ), - ) + switch status.Code(err) { + case codes.NotFound: + // we can get not found when reconnecting after a disconnect/restart before the + // next block is finalized. just wait briefly and try again + select { + case <-ctx.Done(): return + case <-time.After(200 * time.Millisecond): } - } else { + case codes.DeadlineExceeded, codes.Internal: + // these are sometimes returned when the stream is disconnected by a middleware or the server + default: + // skip reconnect on all other errors eventsChan <- models.NewBlockEventsError(fmt.Errorf("%w: %w", errs.ErrDisconnected, err)) return } + + if err := connect(lastReceivedHeight + 1); err != nil { + eventsChan <- models.NewBlockEventsError( + fmt.Errorf( + "failed to resubscribe for finalized block headers on height: %d, with: %w", + lastReceivedHeight+1, + err, + ), + ) + return + } } } }() From 91b1ec08937541249f31e7ddbf330e67afdaf8b0 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Thu, 30 Jan 2025 18:09:34 +0200 Subject: [PATCH 18/19] Remove redundant start-testnet & start-mainnet make recipes --- Makefile | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/Makefile b/Makefile index b3ff2fa32..ef0e054cd 100644 --- a/Makefile +++ b/Makefile @@ -227,36 +227,3 @@ endif docker run $(MODE) -p $(HOST_PORT):8545 -p $(HOST_METRICS_PORT):8080 $(MOUNT) "$(CONTAINER_REGISTRY)/flow-evm-gateway:$(IMAGE_TAG)" $(CMD_ARGS) -.PHONY: start-testnet -start-testnet: - rm -rf testnet-db-block-headers/ - rm -rf metrics/data/ - go run cmd/main.go run \ - --database-dir=testnet-db-block-headers \ - --access-node-grpc-host=access.devnet.nodes.onflow.org:9000 \ - --access-node-spork-hosts=access-001.devnet51.nodes.onflow.org:9000 \ - --flow-network-id=flow-testnet \ - --init-cadence-height=211176670 \ - --ws-enabled=true \ - --coinbase=FACF71692421039876a5BB4F10EF7A439D8ef61E \ - --coa-address=0x62631c28c9fc5a91 \ - --coa-key=2892fba444f1d5787739708874e3b01160671924610411ac787ac1379d420f49 \ - --gas-price=100 \ - --log-level=info - -.PHONY: start-mainnet -start-mainnet: - rm -rf mainnet-db-block-headers/ - rm -rf metrics/data/ - go run cmd/main.go run \ - --database-dir=mainnet-db-block-headers \ - --access-node-grpc-host=access.mainnet.nodes.onflow.org:9000 \ - --access-node-spork-hosts=access-001.mainnet25.nodes.onflow.org:9000 \ - --flow-network-id=flow-mainnet \ - --init-cadence-height=85981135 \ - --ws-enabled=true \ - --coinbase=FACF71692421039876a5BB4F10EF7A439D8ef61E \ - --coa-address=0xf1ab99c82dee3526 \ - --coa-key=2892fba444f1d5787739708874e3b01160671924610411ac787ac1379d420f49 \ - --gas-price=100 \ - --log-level=info From 1e977a9a9c5c002116ec3342b85b05dc0b7955a7 Mon Sep 17 00:00:00 2001 From: Peter Argue <89119817+peterargue@users.noreply.github.com> Date: Mon, 3 Feb 2025 10:27:57 -0800 Subject: [PATCH 19/19] retry getting events on NotFound and ResourceExhausted --- .../ingestion/block_tracking_subscriber.go | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/services/ingestion/block_tracking_subscriber.go b/services/ingestion/block_tracking_subscriber.go index 0e269486b..9d336b731 100644 --- a/services/ingestion/block_tracking_subscriber.go +++ b/services/ingestion/block_tracking_subscriber.go @@ -204,11 +204,7 @@ func (r *RPCBlockTrackingSubscriber) subscribe(ctx context.Context, height uint6 case codes.NotFound: // we can get not found when reconnecting after a disconnect/restart before the // next block is finalized. just wait briefly and try again - select { - case <-ctx.Done(): - return - case <-time.After(200 * time.Millisecond): - } + time.Sleep(200 * time.Millisecond) case codes.DeadlineExceeded, codes.Internal: // these are sometimes returned when the stream is disconnected by a middleware or the server default: @@ -272,13 +268,26 @@ func (r *RPCBlockTrackingSubscriber) getEventsByType( blockHeader flow.BlockHeader, eventType string, ) (flow.BlockEvents, error) { - evts, err := r.client.GetEventsForBlockHeader( - ctx, - eventType, - blockHeader, - ) - if err != nil { - return flow.BlockEvents{}, err + var evts []flow.BlockEvents + var err error + + // retry until we get the block from an execution node that has the events + for { + evts, err = r.client.GetEventsForBlockHeader( + ctx, + eventType, + blockHeader, + ) + if err != nil { + // retry after a short pause + if status.Code(err) == codes.NotFound || status.Code(err) == codes.ResourceExhausted { + time.Sleep(200 * time.Millisecond) + continue + } + + return flow.BlockEvents{}, err + } + break } if len(evts) != 1 {