Compare commits

..

No commits in common. "master" and "v0.1.1-alpha" have entirely different histories.

52 changed files with 833 additions and 2238 deletions

View File

@ -1,10 +0,0 @@
.github
.deepsource.toml
.goreleaser.yaml
**/.env
**/.git
**/.gitignore
**/docker-compose*
**/Dockerfile*

View File

@ -1,9 +1,5 @@
EVENTS_CHAIN__GRAPHQL_ENDPOINT=
EVENTS_CHAIN__WS_ENDPOINT=
EVENTS_CHAIN__SYSTEM_ADDRESS=
EVENTS_CHAIN__TOKEN_INDEX_ADDRESS=
EVENTS_CHAIN__GAS_FAUCET_ADDRESS=
EVENTS_CHAIN__USER_INDEX_ADDRESS=
EVENTS_SYNCER__INITIAL_LOWER_BOUND=
EVENTS_POSTGRES__DSN=
EVENTS_JETSTREAM__ENDPOINT=
export PG_HOST=localhost
export PG_PORT=5432
export PG_DB=cic_indexer
export PG_USER=postgres
export PG_PASSWORD=postgres

View File

@ -1,76 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '40 16 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

View File

@ -1,100 +1,35 @@
name: release
name: goreleaser
on:
push:
tags:
- 'v*'
- "v*"
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Check out repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Cache Docker layers
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Login to GHCR Docker registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set outputs
run: |
echo "RELEASE_TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV \
&& echo "RELEASE_SHORT_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Build and push image
uses: docker/build-push-action@v2
with:
context: ./
file: ./Dockerfile
platforms: linux/amd64
push: true
build-args: |-
BUILD_COMMIT=${{ env.RELEASE_SHORT_COMMIT }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
tags: |
ghcr.io/grassrootseconomics/cic-chain-events/cic-chain-events:latest
ghcr.io/grassrootseconomics/cic-chain-events/cic-chain-events:${{ env.RELEASE_TAG }}
goreleaser:
runs-on: ubuntu-latest
container:
image: goreleaser/goreleaser-cross
environment: build
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
~/.cache/go-build
~/Library/Caches/go-build
%LocalAppData%\go-build
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Workaround Git Security Warning
run: |
# Workaround a bug in github actions:
# https://github.com/actions/runner-images/issues/6775.
git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v2
with:
go-version: 'stable'
go-version: 1.19.3
- name: Login to Docker Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
args: --parallelism 1 --rm-dist --skip-validate
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

3
.gitignore vendored
View File

@ -1,5 +1,2 @@
covprofile
.env
.env.test
cic-chain-events
profiles

33
.goreleaser Normal file
View File

@ -0,0 +1,33 @@
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- amd64
main: ./cmd
ldflags:
- -s -w
archives:
- format: tar.gz
files:
- LICENSE
- config.toml
- queries.sql
- migrations/*
dockers:
- goos: linux
goarch: amd64
ids:
- cic-chain-events
image_templates:
- "ghcr.io/grassrootseconomics/cic-chain-events/cic-chain-events:latest"
- "ghcr.io/grassrootseconomics/cic-chain-events/cic-chain-events:{{ .Tag }}"
dockerfile: Dockerfile
extra_files:
- LICENSE
- config.toml
- queries.sql
- migrations/*

View File

@ -1,21 +0,0 @@
builds:
- id: linux-amd64
env:
- CGO_ENABLED=1
- CC=x86_64-linux-gnu-gcc
- CXX=x86_64-linux-gnu-g++
goos:
- linux
goarch:
- amd64
main: ./cmd/service
ldflags:
- -X main.build={{.ShortCommit}} -s -w
archives:
- format: tar.gz
files:
- LICENSE
- config.toml
- queries.sql
- migrations/

View File

@ -1,33 +1,15 @@
FROM golang:1-bullseye as build
FROM debian:11-slim
ENV CGO_ENABLED=1
ENV GOOS=linux
ENV GOARCH=amd64
RUN set -x && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
ca-certificates && \
rm -rf /var/lib/apt/lists/*
ENV BUILD_COMMIT=${BUILD_COMMIT}
WORKDIR /cic-chain-events
WORKDIR /build
COPY go.* .
RUN go mod download
COPY . .
RUN go build -o cic-chain-events -ldflags="-X main.build=${BUILD_COMMIT} -s -w" cmd/service/*
FROM debian:bullseye-slim
ENV DEBIAN_FRONTEND=noninteractive
WORKDIR /service
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /build/cic-chain-events .
COPY migrations migrations/
COPY cic-chain-events .
COPY config.toml .
COPY queries.sql .
COPY LICENSE .
EXPOSE 5000
COPY migrations migrations/
CMD ["./cic-chain-events"]

View File

@ -1,17 +0,0 @@
BIN := cic-chain-events
BUILD_CONF := CGO_ENABLED=1 GOOS=linux GOARCH=amd64
BUILD_COMMIT := $(shell git rev-parse --short HEAD 2> /dev/null)
.PHONY: build
clean:
rm ${BIN}
build:
${BUILD_CONF} go build -ldflags="-X main.build=${BUILD_COMMIT} -s -w" -o ${BIN} cmd/service/*.go
run:
${BUILD_CONF} go run cmd/service/*
run-debug:
${BUILD_CONF} go run cmd/service/* -debug

View File

@ -1,18 +0,0 @@
# cic-chain-events
![GitHub release (latest by date)](https://img.shields.io/github/v/release/grassrootseconomics/cic-chain-events)
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/grassrootseconomics/cic-chain-events/release.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/grassrootseconomics/cic-chain-events)](https://goreportcard.com/report/github.com/grassrootseconomics/cic-chain-events)
> CIC Chain Events
Filters live (and past) transactions on Celo and emits relevant events to a NATS JetStream sink for further processing/indexing.
## Documentation
- [Functionality](docs/functionality.md)
- [Usage](docs/usage.md)
## License
[AGPL-3.0](LICENSE).

View File

@ -2,7 +2,6 @@ package main
import (
"github.com/VictoriaMetrics/metrics"
"github.com/grassrootseconomics/cic-chain-events/pkg/echopprof"
"github.com/labstack/echo/v4"
)
@ -18,7 +17,5 @@ func initApiServer() *echo.Echo {
})
}
echopprof.Wrap(server)
return server
}

23
cmd/filters.go Normal file
View File

@ -0,0 +1,23 @@
package main
import (
"github.com/grassrootseconomics/cic-chain-events/internal/filter"
)
func initAddressFilter() filter.Filter {
return filter.NewAddressFilter(filter.AddressFilterOpts{
Logg: lo,
})
}
func initTransferFilter() filter.Filter {
return filter.NewTransferFilter(filter.TransferFilterOpts{
Logg: lo,
})
}
func initNoopFilter() filter.Filter {
return filter.NewNoopFilter(filter.NoopFilterOpts{
Logg: lo,
})
}

85
cmd/init.go Normal file
View File

@ -0,0 +1,85 @@
package main
import (
"strings"
"github.com/alitto/pond"
"github.com/grassrootseconomics/cic-chain-events/internal/fetch"
"github.com/grassrootseconomics/cic-chain-events/internal/pool"
"github.com/grassrootseconomics/cic-chain-events/internal/store"
"github.com/jackc/pgx/v5"
"github.com/knadh/goyesql/v2"
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/toml"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file"
"github.com/zerodha/logf"
)
func initLogger(debug bool) logf.Logger {
loggOpts := logf.Opts{
EnableColor: true,
}
if debug {
loggOpts.Level = logf.DebugLevel
}
return logf.New(loggOpts)
}
func initConfig(configFilePath string) *koanf.Koanf {
var (
ko = koanf.New(".")
)
confFile := file.Provider(configFilePath)
if err := ko.Load(confFile, toml.Parser()); err != nil {
lo.Fatal("could not load config file", "error", err)
}
if err := ko.Load(env.Provider("", ".", func(s string) string {
return strings.ReplaceAll(strings.ToLower(
strings.TrimPrefix(s, "")), "_", ".")
}), nil); err != nil {
lo.Fatal("could not override config from env vars", "error", err)
}
return ko
}
func initQueries(queriesPath string) goyesql.Queries {
queries, err := goyesql.ParseFile(queriesPath)
if err != nil {
lo.Fatal("could not load queries file", "error", err)
}
return queries
}
func initPgStore() (store.Store[pgx.Rows], error) {
pgStore, err := store.NewPostgresStore(store.PostgresStoreOpts{
DSN: ko.MustString("postgres.dsn"),
InitialLowerBound: uint64(ko.MustInt64("indexer.initial_lower_bound")),
Logg: lo,
Queries: q,
})
if err != nil {
return nil, err
}
return pgStore, nil
}
func initWorkerPool() *pond.WorkerPool {
return pool.NewPool(pool.Opts{
ConcurrencyFactor: ko.MustInt("indexer.concurrency"),
PoolQueueSize: ko.MustInt("indexer.queue_size"),
})
}
func initFetcher() fetch.Fetch {
return fetch.NewGraphqlFetcher(fetch.GraphqlOpts{
GraphqlEndpoint: ko.MustString("chain.graphql_endpoint"),
})
}

131
cmd/main.go Normal file
View File

@ -0,0 +1,131 @@
package main
import (
"context"
"flag"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"time"
"github.com/grassrootseconomics/cic-chain-events/internal/api"
"github.com/grassrootseconomics/cic-chain-events/internal/filter"
"github.com/grassrootseconomics/cic-chain-events/internal/pipeline"
"github.com/grassrootseconomics/cic-chain-events/internal/syncer"
"github.com/knadh/goyesql/v2"
"github.com/knadh/koanf"
"github.com/zerodha/logf"
)
var (
confFlag string
debugFlag bool
queriesFlag string
ko *koanf.Koanf
lo logf.Logger
q goyesql.Queries
)
func init() {
flag.StringVar(&confFlag, "config", "config.toml", "Config file location")
flag.BoolVar(&debugFlag, "log", true, "Enable debug logging")
flag.StringVar(&queriesFlag, "queries", "queries.sql", "Queries file location")
flag.Parse()
lo = initLogger(debugFlag)
ko = initConfig(confFlag)
q = initQueries(queriesFlag)
}
func main() {
syncerStats := &syncer.Stats{}
wg := &sync.WaitGroup{}
apiServer := initApiServer()
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
pgStore, err := initPgStore()
if err != nil {
lo.Fatal("error loading pg store", "error", err)
}
workerPool := initWorkerPool()
graphqlFetcher := initFetcher()
pipeline := pipeline.NewPipeline(pipeline.PipelineOpts{
BlockFetcher: graphqlFetcher,
Filters: []filter.Filter{
initAddressFilter(),
initTransferFilter(),
// initNoopFilter(),
},
Logg: lo,
Store: pgStore,
})
headSyncer, err := syncer.NewHeadSyncer(syncer.HeadSyncerOpts{
Logg: lo,
Pipeline: pipeline,
Pool: workerPool,
Stats: syncerStats,
WsEndpoint: ko.MustString("chain.ws_endpoint"),
})
if err != nil {
lo.Fatal("error loading head syncer", "error", err)
}
janitor := syncer.NewJanitor(syncer.JanitorOpts{
BatchSize: uint64(ko.MustInt64("indexer.batch_size")),
HeadBlockLag: uint64(ko.MustInt64("indexer.head_block_lag")),
Logg: lo,
Pipeline: pipeline,
Pool: workerPool,
Stats: syncerStats,
Store: pgStore,
SweepInterval: time.Second * time.Duration(ko.MustInt64("indexer.sweep_interval")),
})
apiServer.GET("/stats", api.StatsHandler(syncerStats, workerPool, lo))
wg.Add(1)
go func() {
defer wg.Done()
if err := headSyncer.Start(ctx); err != nil {
lo.Fatal("head syncer error", "error", err)
}
}()
wg.Add(1)
go func() {
defer wg.Done()
if err := janitor.Start(ctx); err != nil {
lo.Fatal("janitor error", "error", err)
}
}()
wg.Add(1)
go func() {
defer wg.Done()
lo.Info("starting API server")
if err := apiServer.Start(ko.MustString("api.address")); err != nil {
if strings.Contains(err.Error(), "Server closed") {
lo.Info("shutting down server")
} else {
lo.Fatal("could not start api server", "err", err)
}
}
}()
<-ctx.Done()
lo.Info("graceful shutdown triggered")
workerPool.Stop()
if err := apiServer.Shutdown(ctx); err != nil {
lo.Error("could not gracefully shutdown api server", "err", err)
}
wg.Wait()
}

View File

@ -1,107 +0,0 @@
package main
import (
"context"
"fmt"
"math/big"
"strings"
"sync"
"time"
"github.com/celo-org/celo-blockchain/common"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-chain-events/internal/filter"
"github.com/grassrootseconomics/cic-chain-events/internal/pub"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/grassrootseconomics/w3-celo-patch/module/eth"
"github.com/grassrootseconomics/w3-celo-patch/w3types"
)
func initAddressFilter(celoProvider *celoutils.Provider, cache *sync.Map) filter.Filter {
var (
tokenIndexEntryCount big.Int
)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
registryMap, err := celoProvider.RegistryMap(ctx, celoutils.HexToAddress(ko.MustString("chain.registry_address")))
if err != nil {
lo.Fatal("init: critical error creating address filter", "error", err)
}
for k, v := range registryMap {
cache.Store(strings.ToLower(v.Hex()), k)
}
if err := celoProvider.Client.CallCtx(
ctx,
eth.CallFunc(w3.MustNewFunc("entryCount()", "uint256"), registryMap[celoutils.TokenIndex]).Returns(&tokenIndexEntryCount),
); err != nil {
lo.Fatal("init: critical error creating address filter", "error", err)
}
calls := make([]w3types.Caller, tokenIndexEntryCount.Int64())
tokenAddresses := make([]common.Address, tokenIndexEntryCount.Int64())
entrySig := w3.MustNewFunc("entry(uint256 _idx)", "address")
// TODO: There is a 5MB limit to a RPC batch call size.
// Test if 10k entries will raise an error (future proofed for a lot of years)
for i := 0; i < int(tokenIndexEntryCount.Int64()); i++ {
calls[i] = eth.CallFunc(entrySig, registryMap[celoutils.TokenIndex], new(big.Int).SetInt64(int64(i))).Returns(&tokenAddresses[i])
}
if err := celoProvider.Client.CallCtx(
ctx,
calls...,
); err != nil {
lo.Fatal("init: critical error creating address filter", "error", err)
}
for i, v := range tokenAddresses {
cache.Store(strings.ToLower(v.Hex()), fmt.Sprintf("TOKEN_%d", i))
}
return filter.NewAddressFilter(filter.AddressFilterOpts{
Cache: cache,
Logg: lo,
})
}
func initTransferFilter(pub *pub.Pub) filter.Filter {
return filter.NewTransferFilter(filter.TransferFilterOpts{
Pub: pub,
Logg: lo,
})
}
func initGasGiftFilter(pub *pub.Pub) filter.Filter {
return filter.NewGasFilter(filter.GasFilterOpts{
Pub: pub,
Logg: lo,
})
}
func initRegisterFilter(pub *pub.Pub) filter.Filter {
return filter.NewRegisterFilter(filter.RegisterFilterOpts{
Pub: pub,
Logg: lo,
})
}
func initApproveFilter(pub *pub.Pub) filter.Filter {
return filter.NewApproveFilter(filter.ApproveFilterOpts{
Pub: pub,
Logg: lo,
})
}
func initTokenIndexFilter(cache *sync.Map, pub *pub.Pub) filter.Filter {
return filter.NewTokenIndexFilter(filter.TokenIndexFilterOpts{
Cache: cache,
Pub: pub,
Logg: lo,
})
}

View File

@ -1,150 +0,0 @@
package main
import (
"context"
"strings"
"time"
"github.com/alitto/pond"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-chain-events/internal/pool"
"github.com/grassrootseconomics/cic-chain-events/internal/pub"
"github.com/grassrootseconomics/cic-chain-events/internal/store"
"github.com/grassrootseconomics/cic-chain-events/pkg/fetch"
"github.com/jackc/pgx/v5"
"github.com/knadh/goyesql/v2"
"github.com/knadh/koanf/parsers/toml"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/v2"
"github.com/nats-io/nats.go"
"github.com/zerodha/logf"
)
func initLogger() logf.Logger {
loggOpts := logf.Opts{}
if debugFlag {
loggOpts.EnableColor = true
loggOpts.EnableColor = true
loggOpts.Level = logf.DebugLevel
}
return logf.New(loggOpts)
}
func initConfig() *koanf.Koanf {
var (
ko = koanf.New(".")
)
confFile := file.Provider(confFlag)
if err := ko.Load(confFile, toml.Parser()); err != nil {
lo.Fatal("init: could not load config file", "error", err)
}
if err := ko.Load(env.Provider("EVENTS_", ".", func(s string) string {
return strings.ReplaceAll(strings.ToLower(
strings.TrimPrefix(s, "EVENTS_")), "__", ".")
}), nil); err != nil {
lo.Fatal("init: could not override config from env vars", "error", err)
}
if debugFlag {
ko.Print()
}
return ko
}
func initQueries(queriesPath string) goyesql.Queries {
queries, err := goyesql.ParseFile(queriesPath)
if err != nil {
lo.Fatal("init: could not load queries file", "error", err)
}
return queries
}
func initPgStore(migrationsPath string, queries goyesql.Queries) store.Store[pgx.Rows] {
pgStore, err := store.NewPostgresStore(store.PostgresStoreOpts{
MigrationsFolderPath: migrationsPath,
DSN: ko.MustString("postgres.dsn"),
InitialLowerBound: uint64(ko.MustInt64("syncer.initial_lower_bound")),
Logg: lo,
Queries: queries,
})
if err != nil {
lo.Fatal("init: critical error loading chain provider", "error", err)
}
return pgStore
}
func initFetcher() fetch.Fetch {
return fetch.NewGraphqlFetcher(fetch.GraphqlOpts{
GraphqlEndpoint: ko.MustString("chain.graphql_endpoint"),
})
}
func initJanitorWorkerPool(ctx context.Context) *pond.WorkerPool {
return pool.NewPool(ctx, pool.Opts{
Concurrency: ko.MustInt("syncer.janitor_concurrency"),
QueueSize: ko.MustInt("syncer.janitor_queue_size"),
})
}
func initHeadSyncerWorkerPool(ctx context.Context) *pond.WorkerPool {
return pool.NewPool(ctx, pool.Opts{
Concurrency: 1,
QueueSize: 1,
})
}
func initJetStream() (*nats.Conn, nats.JetStreamContext) {
natsConn, err := nats.Connect(ko.MustString("jetstream.endpoint"))
if err != nil {
lo.Fatal("init: critical error connecting to NATS", "error", err)
}
js, err := natsConn.JetStream()
if err != nil {
lo.Fatal("init: bad JetStream opts", "error", err)
}
return natsConn, js
}
func initPub(natsConn *nats.Conn, jsCtx nats.JetStreamContext) *pub.Pub {
pub, err := pub.NewPub(pub.PubOpts{
DedupDuration: time.Duration(ko.MustInt("jetstream.dedup_duration_hrs")) * time.Hour,
JsCtx: jsCtx,
NatsConn: natsConn,
PersistDuration: time.Duration(ko.MustInt("jetstream.persist_duration_hrs")) * time.Hour,
})
if err != nil {
lo.Fatal("init: critical error bootstrapping pub", "error", err)
}
return pub
}
func initCeloProvider() *celoutils.Provider {
providerOpts := celoutils.ProviderOpts{
RpcEndpoint: ko.MustString("chain.rpc_endpoint"),
}
if ko.Bool("chain.testnet") {
providerOpts.ChainId = celoutils.TestnetChainId
} else {
providerOpts.ChainId = celoutils.MainnetChainId
}
provider, err := celoutils.NewProvider(providerOpts)
if err != nil {
lo.Fatal("init: critical error loading chain provider", "error", err)
}
return provider
}

View File

@ -1,145 +0,0 @@
package main
import (
"context"
"flag"
"strings"
"sync"
"time"
"github.com/grassrootseconomics/cic-chain-events/internal/filter"
"github.com/grassrootseconomics/cic-chain-events/internal/pipeline"
"github.com/grassrootseconomics/cic-chain-events/internal/pub"
"github.com/grassrootseconomics/cic-chain-events/internal/syncer"
"github.com/knadh/koanf/v2"
"github.com/labstack/echo/v4"
"github.com/zerodha/logf"
)
type (
internalServicesContainer struct {
apiService *echo.Echo
pub *pub.Pub
}
)
var (
build string
confFlag string
debugFlag bool
migrationsFolderFlag string
queriesFlag string
ko *koanf.Koanf
lo logf.Logger
)
func init() {
flag.StringVar(&confFlag, "config", "config.toml", "Config file location")
flag.BoolVar(&debugFlag, "debug", false, "Enable debug logging")
flag.StringVar(&migrationsFolderFlag, "migrations", "migrations/", "Migrations folder location")
flag.StringVar(&queriesFlag, "queries", "queries.sql", "Queries file location")
flag.Parse()
lo = initLogger()
ko = initConfig()
}
func main() {
lo.Info("main: starting cic-chain-events", "build", build)
parsedQueries := initQueries(queriesFlag)
graphqlFetcher := initFetcher()
pgStore := initPgStore(migrationsFolderFlag, parsedQueries)
natsConn, jsCtx := initJetStream()
jsPub := initPub(natsConn, jsCtx)
celoProvider := initCeloProvider()
cache := &sync.Map{}
pipeline := pipeline.NewPipeline(pipeline.PipelineOpts{
BlockFetcher: graphqlFetcher,
Filters: []filter.Filter{
initAddressFilter(celoProvider, cache),
initGasGiftFilter(jsPub),
initTransferFilter(jsPub),
initRegisterFilter(jsPub),
initApproveFilter(jsPub),
initTokenIndexFilter(cache, jsPub),
},
Logg: lo,
Store: pgStore,
})
internalServices := &internalServicesContainer{
pub: jsPub,
}
syncerStats := &syncer.Stats{}
wg := &sync.WaitGroup{}
signalCh, closeCh := createSigChannel()
defer closeCh()
ctx, cancel := context.WithCancel(context.Background())
headSyncer, err := syncer.NewHeadSyncer(syncer.HeadSyncerOpts{
Logg: lo,
Pipeline: pipeline,
Pool: initHeadSyncerWorkerPool(ctx),
Stats: syncerStats,
WsEndpoint: ko.MustString("chain.ws_endpoint"),
})
if err != nil {
lo.Fatal("main: crticial error loading head syncer", "error", err)
}
janitor := syncer.NewJanitor(syncer.JanitorOpts{
BatchSize: uint64(ko.MustInt64("syncer.janitor_queue_size")),
Logg: lo,
Pipeline: pipeline,
Pool: initJanitorWorkerPool(ctx),
Stats: syncerStats,
Store: pgStore,
SweepInterval: time.Second * time.Duration(ko.MustInt64("syncer.janitor_sweep_interval")),
})
wg.Add(1)
go func() {
defer wg.Done()
if err := headSyncer.Start(ctx); err != nil {
lo.Info("main: starting head syncer")
lo.Fatal("main: critical error starting head syncer", "error", err)
}
}()
wg.Add(1)
go func() {
defer wg.Done()
lo.Info("main: starting janitor")
if err := janitor.Start(ctx); err != nil {
lo.Fatal("main: critical error starting janitor", "error", err)
}
}()
internalServices.apiService = initApiServer()
wg.Add(1)
go func() {
defer wg.Done()
host := ko.MustString("service.address")
lo.Info("main: starting API server", "host", host)
if err := internalServices.apiService.Start(host); err != nil {
if strings.Contains(err.Error(), "Server closed") {
lo.Info("main: shutting down server")
} else {
lo.Fatal("main: critical error shutting down server", "err", err)
}
}
}()
lo.Info("main: graceful shutdown triggered", "signal", <-signalCh)
cancel()
startGracefulShutdown(context.Background(), internalServices)
wg.Wait()
}

View File

@ -1,29 +0,0 @@
package main
import (
"context"
"os"
"os/signal"
"syscall"
"time"
)
func createSigChannel() (chan os.Signal, func()) {
signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
return signalCh, func() {
close(signalCh)
}
}
func startGracefulShutdown(ctx context.Context, internalServices *internalServicesContainer) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
internalServices.pub.Close()
if err := internalServices.apiService.Shutdown(ctx); err != nil {
lo.Fatal("Could not gracefully shutdown api server", "err", err)
}
}

View File

@ -1,38 +1,21 @@
[metrics]
# Exposes Prometheus metrics
go_process = true
# API server
[service]
# Host and port
address = ":5001"
[api]
address = ":8080"
# Geth API endpoints
[chain]
graphql_endpoint = ""
ws_endpoint = ""
rpc_endpoint = ""
testnet = true
registry_address = ""
graphql_endpoint = "https://rpc.celo.grassecon.net/graphql"
ws_endpoint = "wss://ws.celo.grassecon.net"
# Syncer configs
[syncer]
# Maximum number of missing blocks pushed into the worker queue every janitor sweep
janitor_queue_size = 250
# Number of goroutines assigned to the worker pool
janitor_concurrency = 3
# Syncer start block
initial_lower_bound = ""
# Janitor sweep interval, should take into account concurrency and queue_size
janitor_sweep_interval = 10
[indexer]
batch_size = 200
concurrency = 3
head_block_lag = 5
idle_worker_timeout = 1
initial_lower_bound = 17034445
queue_size = 1250
sweep_interval = 10
[postgres]
dsn = ""
# https://docs.nats.io/
[jetstream]
endpoint = ""
# Duration JetStream should keep the message before GC
persist_duration_hrs = 48
# Duration to ignore duplicate transactions (e.g. due to restart)
dedup_duration_hrs = 6
dsn = "postgres://postgres:postgres@localhost:5432/cic_chain_events"

View File

@ -17,17 +17,6 @@ services:
interval: 10s
timeout: 5s
retries: 5
nats:
image: nats:2.9
restart: unless-stopped
command: "-js -sd /nats/data"
volumes:
- cic-indexer-nats:/nats/data
ports:
- '4222:4222'
- '8222:8222'
volumes:
cic-indexer-pg:
driver: local
cic-indexer-nats:
driver: local

View File

@ -1,38 +0,0 @@
## Functionality
## Filters
Filters are initialized in `cmd/filters.go` and implemented in `internal/filters/*.go` folder. You will need to modify these files to suite your indexing needs.
The existing implementation demo's tracking Celo stables transfer events and gives a rough idea on how to write filters. The final filter should always emit an event to NATS JetStream.
## Syncers
### Head syncer
The head syncer processes newely produced blocks independently by connecting to the geth websocket endpoint.
### Janitor
The janitor syncer checks for missing (blocks) gaps in the commited block sequence and queues them for processing. It can also function as a historical syncer to process older blocks.
With the default `config.toml`, The janitor can process around 950-1000 blocks/min.
**Ordering**
Missed/historical blocks are not guaranteed to be processed in order, however a low concurrency setting would somewhat give an "in-order" behaviour (not to be relied upon in any case).
## Block fetchers
The default GraphQL block fetcher is the recommended fetcher. An experimental RPC fetcher implementation is also provided as an example.
## Pipeline
The pipeline fetches a whole block with its full transaction and receipt objects, executes all loaded filters serially and finally commits the block number to the db. Blocks are processed atomically by the pipeline; a failure in one of the filters will trigger the janitor to re-queue the block and process the block again.
## Store
The postgres store keeps track of commited blocks and syncer curosors. Schema:
- The `blocks` table keeps track of processed blocks.
- The `syncer_meta` table keeps track of the lower_bound cursor. Below the lower_bound cursor, all blocks are guarnteed to have been processsed hence it is safe to trim the `blocks` table below that pointer.

View File

@ -1,71 +0,0 @@
## Prerequisites
- Linux OS (amd64) or Docker
- Postgres >= 14
- Celo geth with GraphQL API enabled
- NATS server with JetStream enabled
## Usage
The provided `docker-compose.yaml` is the fastest way to get up and running. Bring up the Postgres and NATS conatiners with `docker-compose up -d`
### 1. Run migrations
Run the SQL migrations inside the `migrations` folder with `psql` or [`tern`](https://github.com/jackc/tern) (recommended).
### 2. Update the config
The base config is described in `config.toml`. Values can be overriden with env variables e.g. to disable metrics, set `METRICS_GO_PROCESS=false`.
### 3. Start the service
**Compiling the binary**
Run `make build` or download pre-compiled binaries from the [releases](https://github.com/grassrootseconomics/cic-chain-events/releases) page.
Then start the service with `./cic-chain-events`
Optional flags:
- `-config` - `config.toml` file path
- `-debug` - Enable/disable debug level logs
- `-queries` - `queries.sql` file path
**Docker**
To pull the pre-built docker image:
`docker pull ghcr.io/grassrootseconomics/cic-chain-events/cic-chain-events:latest`
Or to build it:
`DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 docker-compose -f docker-compose.build.yaml build --progress plain`
### 4. NATS JetStream consumer
A consumer with the following NATS JetStream config is required:
- Durable
- Stream: `CHAIN.*` (See `config.toml` for stream subjects)
[Benthos](https://benthos.dev) (Benthos can act as a JetStream consumer) example.
```yaml
# config.yaml
input:
label: jetstream
nats_jetstream:
urls:
- nats://127.0.0.1:4222
subject: "CHAIN.*"
durable: benthos
deliver: all
output:
stdout:
codec: lines
```
Then run:
`benthos -c config.yaml`

82
go.mod
View File

@ -1,94 +1,56 @@
module github.com/grassrootseconomics/cic-chain-events
go 1.20
go 1.19
require (
github.com/VictoriaMetrics/metrics v1.24.0
github.com/alitto/pond v1.8.3
github.com/celo-org/celo-blockchain v1.7.2
github.com/grassrootseconomics/celoutils v1.4.0
github.com/grassrootseconomics/w3-celo-patch v0.2.0
github.com/jackc/pgx/v5 v5.4.1
github.com/jackc/tern/v2 v2.1.0
github.com/VictoriaMetrics/metrics v1.23.0
github.com/alitto/pond v1.8.2
github.com/celo-org/celo-blockchain v1.6.1
github.com/jackc/pgx/v5 v5.2.0
github.com/knadh/goyesql/v2 v2.2.0
github.com/knadh/koanf v1.5.0
github.com/knadh/koanf/v2 v2.0.1
github.com/labstack/echo/v4 v4.10.2
github.com/nats-io/nats.go v1.27.1
github.com/stretchr/testify v1.8.4
github.com/knadh/koanf v1.4.5
github.com/labstack/echo/v4 v4.2.1
github.com/stretchr/testify v1.8.1
github.com/zerodha/logf v0.5.5
)
require (
filippo.io/edwards25519 v1.0.0-alpha.2 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
github.com/VictoriaMetrics/fastcache v1.6.0 // indirect
github.com/btcsuite/btcd v0.20.1-beta // indirect
github.com/celo-org/celo-bls-go v0.3.3 // indirect
github.com/celo-org/celo-bls-go-android v0.3.2 // indirect
github.com/celo-org/celo-bls-go-ios v0.3.2 // indirect
github.com/celo-org/celo-bls-go-linux v0.3.2 // indirect
github.com/celo-org/celo-bls-go-macos v0.3.2 // indirect
github.com/celo-org/celo-bls-go-other v0.3.2 // indirect
github.com/celo-org/celo-bls-go-windows v0.3.2 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/celo-org/celo-bls-go v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deckarep/golang-set v1.8.0 // indirect
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea // indirect
github.com/deckarep/golang-set/v2 v2.1.0 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-ole/go-ole v1.2.5 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
github.com/hdevalence/ed25519consensus v0.0.0-20201207055737-7fde80a9d5ff // indirect
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
github.com/holiman/uint256 v1.2.1 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/huin/goupnp v1.0.3 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.0 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/klauspost/compress v1.16.5 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jackc/puddle/v2 v2.1.2 // indirect
github.com/labstack/gommon v0.3.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/nats-io/nats-server/v2 v2.9.11 // indirect
github.com/nats-io/nkeys v0.4.4 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/onsi/gomega v1.10.1 // indirect
github.com/pelletier/go-toml v1.7.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/tsdb v0.7.1 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tklauser/go-sysconf v0.3.5 // indirect
github.com/tklauser/numcpus v0.2.2 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fastrand v1.1.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
github.com/valyala/histogram v1.2.0 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
go.uber.org/atomic v1.10.0 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/text v0.6.0 // indirect
golang.org/x/time v0.2.0 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

184
go.sum
View File

@ -37,19 +37,13 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
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=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o=
github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw=
github.com/VictoriaMetrics/metrics v1.24.0 h1:ILavebReOjYctAGY5QU2F9X0MYvkcrG3aEn2RKa1Zkw=
github.com/VictoriaMetrics/metrics v1.24.0/go.mod h1:eFT25kvsTidQFHb6U0oa0rTrDRdz4xTYjpL8+UPohys=
github.com/VictoriaMetrics/metrics v1.23.0 h1:WzfqyzCaxUZip+OBbg1+lV33WChDSu4ssYII3nxtpeA=
github.com/VictoriaMetrics/metrics v1.23.0/go.mod h1:rAr/llLpEnAdTehiNlUxKgnjcOuROSzpw0GvjpEbvFc=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@ -57,9 +51,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alitto/pond v1.8.3 h1:ydIqygCLVPqIX/USe5EaV/aSRXTRXDEI9JwuDdu+/xs=
github.com/alitto/pond v1.8.3/go.mod h1:CmvIIGd5jKLasGI3D87qDkQxjzChdKMmnXMg3fG6M6Q=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
github.com/alitto/pond v1.8.2 h1:k0k3GIE7CFLW/kyMJj5DDKLFg1VH09l8skZqg/yJNng=
github.com/alitto/pond v1.8.2/go.mod h1:CmvIIGd5jKLasGI3D87qDkQxjzChdKMmnXMg3fG6M6Q=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
@ -106,24 +99,13 @@ github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46f
github.com/buraksezer/consistent v0.0.0-20191006190839-693edf70fd72 h1:fUmDBbSvv1uOzo/t8WaxZMVb7BxJ8JECo5lGoR9c5bA=
github.com/buraksezer/consistent v0.0.0-20191006190839-693edf70fd72/go.mod h1:OEE5igu/CDjGegM1Jn6ZMo7R6LlV/JChAkjfQQIRLpg=
github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
github.com/celo-org/celo-blockchain v1.7.2 h1:LjQ+t89inzSK2K9i/twwwW07kIE1ubEVDlrcet+UvLU=
github.com/celo-org/celo-blockchain v1.7.2/go.mod h1:x5HsfXAfUjxKQfTWzroQSb2HljaQEVgf2/mQVDPIMIY=
github.com/celo-org/celo-bls-go v0.3.3 h1:kOkm/BeM0YwHWUTwSLprxhk1IFq/ZjaWVtW6jTZ0+ys=
github.com/celo-org/celo-bls-go v0.3.3/go.mod h1:eoMAORYWgZ5HOo3Z0bIAv/nbPtj/eArIO0nh7XFx7rk=
github.com/celo-org/celo-bls-go-android v0.3.2 h1:pa6T14NUaf5S1vkq+90gya4ckVLefl4YtUhQKi22wvI=
github.com/celo-org/celo-bls-go-android v0.3.2/go.mod h1:cFgtFRH8+6x5b+EyG5SqniXY3aKd03NBSGDgITscX34=
github.com/celo-org/celo-bls-go-ios v0.3.2 h1:xR+3h80nyB1jRZB7GUChHpX93+iDIywH0qeGhWiZ/6w=
github.com/celo-org/celo-bls-go-ios v0.3.2/go.mod h1:eaSoMpx29YV5oF7jXVChzJpNfxeZHnAa8G4PjL5CyW0=
github.com/celo-org/celo-bls-go-linux v0.3.2 h1:je4JIot91S9uPLvh0D2mK3yxbE9VynGhEP56nmer0l4=
github.com/celo-org/celo-bls-go-linux v0.3.2/go.mod h1:DVpJadg22OrxBtMb0ub6iNVdqDBL/r6EDdWVAA0bHa0=
github.com/celo-org/celo-bls-go-macos v0.3.2 h1:S5C3kwXXiPlS4pPRr3t7nDeyJVo5lSFMzhfUEaHlg6I=
github.com/celo-org/celo-bls-go-macos v0.3.2/go.mod h1:mYPuRqGMVxj6yZUeL6Q6ggtP52HPBS1jz+FvBPXQ7QA=
github.com/celo-org/celo-bls-go-other v0.3.2 h1:Onbcv1FkNl/1TfBI/AfedzTCGAlChOOPEcEvPnE99M4=
github.com/celo-org/celo-bls-go-other v0.3.2/go.mod h1:tNxZNfekzyT7TdYQbyNPhkfpcYtA3KCU/IKX5FNxM/U=
github.com/celo-org/celo-bls-go-windows v0.3.2 h1:HpauxEhxeGedvsfZC4aBo8ADlNstihm1gKnVQMmdxuY=
github.com/celo-org/celo-bls-go-windows v0.3.2/go.mod h1:82GC5iJA9Qw5gynhYqR8ht3J+l/MO8eSNzgSTMI8UdA=
github.com/celo-org/celo-blockchain v1.6.1 h1:Kv+OXLXwdORDz3aIrGY1oCuw0aYSlK4KH7o8cGxO8FU=
github.com/celo-org/celo-blockchain v1.6.1/go.mod h1:NdcP5idffWajmOP79Q0PCRfaFNTmTdSKBMbngLDxjpQ=
github.com/celo-org/celo-bls-go v0.2.4 h1:V1y92kM5IRJWQZ6DCwqiKLW7swmUA5y/dPJ9YbU4HfA=
github.com/celo-org/celo-bls-go v0.2.4/go.mod h1:eXUCLXu5F1yfd3M+3VaUk5ZUXaA0sLK2rWdLC1Cfaqo=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@ -145,9 +127,10 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X6GqbFowYdYdI0L9bwxL07jyPZIdepyZ0=
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI=
github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
@ -169,7 +152,6 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@ -182,13 +164,11 @@ github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJ
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
@ -226,7 +206,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@ -247,7 +226,6 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
@ -255,11 +233,9 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I=
github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@ -267,10 +243,6 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
github.com/grassrootseconomics/celoutils v1.4.0 h1:AJNKiOpfnQqZ3kRxeUlhWH/zlDDjhtbs/OzAMb5zU4A=
github.com/grassrootseconomics/celoutils v1.4.0/go.mod h1:Uo5YRy6AGLAHDZj9jaOI+AWoQ1H3L0v79728pPMkm9Q=
github.com/grassrootseconomics/w3-celo-patch v0.2.0 h1:YqibbPzX0tQKmxU1nUGzThPKk/fiYeYZY6Aif3eyu8U=
github.com/grassrootseconomics/w3-celo-patch v0.2.0/go.mod h1:WhBXNzNIvHmS6B2hAeShs56oa9Azb4jQSrOMKuMdBWw=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
@ -301,6 +273,7 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs=
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
@ -312,24 +285,17 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKe
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hdevalence/ed25519consensus v0.0.0-20201207055737-7fde80a9d5ff h1:LeVKjw8pcDQj7WVVnbFvbD7ovcv+r/l15ka1NH6Lswc=
github.com/hdevalence/ed25519consensus v0.0.0-20201207055737-7fde80a9d5ff/go.mod h1:Feit0l8NcNO4g69XNjwvsR0LGcwMMfzI1TF253rOIlQ=
github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs=
github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM=
github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
github.com/holiman/uint256 v1.2.1 h1:XRtyuda/zw2l+Bq/38n5XUoEF72aSOu/77Thd9pPp2o=
github.com/holiman/uint256 v1.2.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huin/goupnp v1.0.2 h1:RfGLP+h3mvisuWEyybxNq5Eft3NWhHLPeUN72kpKZoI=
github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM=
github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ=
github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y=
github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY=
github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI=
@ -344,22 +310,20 @@ github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mq
github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.4.1 h1:oKfB/FhuVtit1bBM3zNRRsZ925ZkMN3HXL+LgLUM9lE=
github.com/jackc/pgx/v5 v5.4.1/go.mod h1:q6iHT8uDNXWiFNOlRqJzBTaSH3+2xCXkokxHZC5qWFY=
github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk=
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jackc/tern/v2 v2.1.0 h1:yqx1rppJY0WfDC7nAmRCLODRrdlLWO8VspuTD88nX7k=
github.com/jackc/tern/v2 v2.1.0/go.mod h1:4cpqN/grjWYeRWcKXah5YGoviJKJuoqNLoORKLumoG0=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgx/v5 v5.2.0 h1:NdPpngX0Y6z6XDFKqmFQaE+bCtkqzvQIOt1wvBlAqs8=
github.com/jackc/pgx/v5 v5.2.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk=
github.com/jackc/puddle/v2 v2.1.2 h1:0f7vaaXINONKTsxYDn4otOAiJanX/BMeAtY//BXqzlg=
github.com/jackc/puddle/v2 v2.1.2/go.mod h1:2lpufsF5mRHO6SuZkm0fNYxM6SWHfvyFj62KwNzgels=
github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJyexcZ3pYbTI9jWx5tHo1Dee/tWbLMfPe2TA=
github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
@ -380,17 +344,13 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/knadh/goyesql/v2 v2.2.0 h1:DNQIzgITmMTXA+z+jDzbXCpgr7fGD6Hp0AJ7ZLEAem4=
github.com/knadh/goyesql/v2 v2.2.0/go.mod h1:is+wK/XQBukYK3DdKfpJRyDH9U/ZTMyX2u6DFijjRnI=
github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs=
github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g=
github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus=
github.com/knadh/koanf v1.4.5 h1:yKWFswTrqFc0u7jBAoERUz30+N1b1yPXU01gAPr8IrY=
github.com/knadh/koanf v1.4.5/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@ -398,15 +358,13 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo/v4 v4.2.1 h1:LF5Iq7t/jrtUuSutNuiEWtB5eiHfZ5gSe2pcu5exjQw=
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
@ -419,7 +377,6 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
@ -431,7 +388,6 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@ -444,7 +400,6 @@ github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4f
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
@ -473,17 +428,7 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
github.com/nats-io/jwt/v2 v2.3.0 h1:z2mA1a7tIf5ShggOFlR1oBPgd6hGqcDYsISxZByUzdI=
github.com/nats-io/nats-server/v2 v2.9.11 h1:4y5SwWvWI59V5mcqtuoqKq6L9NDUydOP3Ekwuwl8cZI=
github.com/nats-io/nats-server/v2 v2.9.11/go.mod h1:b0oVuxSlkvS3ZjMkncFeACGyZohbO4XhSqW1Lt7iRRY=
github.com/nats-io/nats.go v1.27.1 h1:OuYnal9aKVSnOzLQIzf7554OXMCG7KbaTkCSBHRcSoo=
github.com/nats-io/nats.go v1.27.1/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc=
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
@ -492,7 +437,6 @@ github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
@ -559,8 +503,6 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@ -569,22 +511,24 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
@ -599,9 +543,8 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY=
github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
@ -609,7 +552,6 @@ github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6Ut
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
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/zerodha/logf v0.5.5 h1:AhxHlixHNYwhFjvlgTv6uO4VBKYKxx2I6SbHoHtWLBk=
github.com/zerodha/logf v0.5.5/go.mod h1:HWpfKsie+WFFpnUnUxelT6Z0FC6xu9+qt+oXNMPg6y8=
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
@ -620,6 +562,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
@ -637,10 +581,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -671,7 +613,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -700,10 +641,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
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=
@ -719,9 +658,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 h1:ZrnxWX62AgTKOSagEqxvb3ffipvEDX2pl7E1TdqLqIc=
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -775,21 +713,12 @@ golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@ -798,15 +727,14 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE=
golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -839,13 +767,11 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU=
@ -903,7 +829,6 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
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 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
@ -917,7 +842,6 @@ gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0=
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
@ -932,10 +856,8 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -6,6 +6,7 @@ import (
"github.com/alitto/pond"
"github.com/grassrootseconomics/cic-chain-events/internal/syncer"
"github.com/labstack/echo/v4"
"github.com/zerodha/logf"
)
type statsResponse struct {
@ -21,6 +22,7 @@ type statsResponse struct {
func StatsHandler(
syncerStats *syncer.Stats,
poolStats *pond.WorkerPool,
logg logf.Logger,
) func(echo.Context) error {
return func(ctx echo.Context) error {
headCursor := syncerStats.GetHeadCursor()

View File

@ -8,3 +8,8 @@ type okResp struct {
Ok bool `json:"ok"`
Data interface{} `json:"data"`
}
type errResp struct {
Ok bool `json:"ok"`
Error string `json:"error"`
}

View File

@ -1,16 +1,14 @@
package fetch
import "context"
// Fetch defines a block fetcher that must return a full JSON response
type Fetch interface {
Block(ctx context.Context, block uint64) (fetchResponse FetchResponse, err error)
Block(block uint64) (fetchResponse FetchResponse, err error)
}
// Transaction reprsents a JSON object of all important mined transaction information
type Transaction struct {
Block struct {
Number uint64 `json:"number"`
Number uint `json:"number"`
Timestamp string `json:"timestamp"`
} `json:"block"`
Hash string `json:"hash"`
@ -23,8 +21,8 @@ type Transaction struct {
} `json:"to"`
Value string `json:"value"`
InputData string `json:"inputData"`
Status uint64 `json:"status"`
GasUsed uint64 `json:"gasUsed"`
Status uint `json:"status"`
GasUsed uint `json:"gasUsed"`
}
// BlockResponse represents a full fetch JSON response

63
internal/fetch/graphql.go Normal file
View File

@ -0,0 +1,63 @@
package fetch
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
)
const (
contentType = "application/json"
graphqlQuery = `{"query":"{block(number:%d){transactions{block{number,timestamp},hash,index,from{address},to{address},value,inputData,status,gasUsed}}}"}`
)
type GraphqlOpts struct {
GraphqlEndpoint string
}
type Graphql struct {
graphqlEndpoint string
httpClient *http.Client
}
func NewGraphqlFetcher(o GraphqlOpts) Fetch {
return &Graphql{
httpClient: &http.Client{
Timeout: time.Second * 5,
},
graphqlEndpoint: o.GraphqlEndpoint,
}
}
func (f *Graphql) Block(blockNumber uint64) (FetchResponse, error) {
var (
fetchResponse FetchResponse
)
resp, err := f.httpClient.Post(
f.graphqlEndpoint,
contentType,
bytes.NewBufferString(fmt.Sprintf(graphqlQuery, blockNumber)),
)
if err != nil {
return FetchResponse{}, err
}
if resp.StatusCode != http.StatusOK {
return FetchResponse{}, fmt.Errorf("error fetching block %s", resp.Status)
}
defer resp.Body.Close()
out, err := ioutil.ReadAll(resp.Body)
if err != nil {
return FetchResponse{}, nil
}
if err := json.Unmarshal(out, &fetchResponse); err != nil {
return FetchResponse{}, err
}
return fetchResponse, nil
}

View File

@ -0,0 +1,37 @@
package fetch
import (
"testing"
"github.com/stretchr/testify/suite"
)
var (
graphqlEndpoint = "https://rpc.celo.grassecon.net/graphql"
)
type itGraphqlTest struct {
suite.Suite
graphqlFetcher Fetch
}
func TestPipelineSuite(t *testing.T) {
suite.Run(t, new(itGraphqlTest))
}
func (s *itGraphqlTest) SetupSuite() {
s.graphqlFetcher = NewGraphqlFetcher(GraphqlOpts{
GraphqlEndpoint: graphqlEndpoint,
})
}
func (s *itGraphqlTest) Test_E2E_Fetch_Existing_Block() {
resp, err := s.graphqlFetcher.Block(14974600)
s.NoError(err)
s.Len(resp.Data.Block.Transactions, 3)
}
func (s *itGraphqlTest) Test_E2E_Fetch_Non_Existing_Block() {
_, err := s.graphqlFetcher.Block(14974600000)
s.Error(err)
}

View File

@ -1,34 +1,30 @@
package filter
import (
"context"
"sync"
"github.com/grassrootseconomics/cic-chain-events/pkg/fetch"
"github.com/grassrootseconomics/cic-chain-events/internal/fetch"
"github.com/zerodha/logf"
)
type (
AddressFilterOpts struct {
Cache *sync.Map
Logg logf.Logger
}
AddressFilter struct {
cache *sync.Map
logg logf.Logger
}
const (
cUSD = "0x765de816845861e75a25fca122bb6898b8b1282a"
)
type AddressFilterOpts struct {
Logg logf.Logger
}
type AddressFilter struct {
logg logf.Logger
}
func NewAddressFilter(o AddressFilterOpts) Filter {
return &AddressFilter{
cache: o.Cache,
logg: o.Logg,
logg: o.Logg,
}
}
func (f *AddressFilter) Execute(_ context.Context, transaction *fetch.Transaction) (bool, error) {
if _, found := f.cache.Load(transaction.To.Address); found {
func (f *AddressFilter) Execute(transaction fetch.Transaction) (bool, error) {
if transaction.To.Address == cUSD {
return true, nil
}

View File

@ -1,69 +0,0 @@
package filter
import (
"context"
"sync"
"testing"
"github.com/grassrootseconomics/cic-chain-events/pkg/fetch"
"github.com/stretchr/testify/suite"
)
type AddressFilterSuite struct {
suite.Suite
filter Filter
}
func (s *AddressFilterSuite) SetupSuite() {
addressCache := &sync.Map{}
addressCache.Store("0x6914ba1c49d3c3f32a9e65a0661d7656cb292e9f", "")
s.filter = NewAddressFilter(AddressFilterOpts{
Cache: addressCache,
})
}
func (s *AddressFilterSuite) TestAddresses() {
type testCase struct {
transactionData fetch.Transaction
want bool
wantErr bool
}
// Generated with eth-encode
tests := []testCase{
{
transactionData: fetch.Transaction{
To: struct {
Address string "json:\"address\""
}{
Address: "0x6914ba1c49d3c3f32a9e65a0661d7656cb292e9f",
},
},
want: true,
wantErr: false,
},
{
transactionData: fetch.Transaction{
To: struct {
Address string "json:\"address\""
}{
Address: "0x6914ba1c49d3c3f32a9e65a0661d7656cb292e9x",
},
},
want: false,
wantErr: false,
},
}
for _, test := range tests {
next, err := s.filter.Execute(context.Background(), &test.transactionData)
s.NoError(err)
s.Equal(test.want, next)
}
}
func TestAddressFilterSuite(t *testing.T) {
suite.Run(t, new(AddressFilterSuite))
}

View File

@ -1,85 +0,0 @@
package filter
import (
"context"
"math/big"
"github.com/celo-org/celo-blockchain/common"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-chain-events/internal/pub"
"github.com/grassrootseconomics/cic-chain-events/pkg/fetch"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/zerodha/logf"
)
type (
ApproveFilterOpts struct {
Logg logf.Logger
Pub *pub.Pub
}
ApproveFilter struct {
logg logf.Logger
pub *pub.Pub
}
)
const (
approveEventSubject = "CHAIN.approve"
)
var (
approveSig = w3.MustNewFunc("approve(address, uint256)", "bool")
)
func NewApproveFilter(o ApproveFilterOpts) Filter {
return &ApproveFilter{
logg: o.Logg,
pub: o.Pub,
}
}
func (f *ApproveFilter) Execute(_ context.Context, transaction *fetch.Transaction) (bool, error) {
if len(transaction.InputData) < 10 {
return true, nil
}
if transaction.InputData[:10] == "0x095ea7b3" {
var (
address common.Address
value big.Int
)
if err := approveSig.DecodeArgs(w3.B(transaction.InputData), &address, &value); err != nil {
return false, err
}
approveEvent := &pub.MinimalTxInfo{
Block: transaction.Block.Number,
ContractAddress: celoutils.ChecksumAddress(transaction.To.Address),
Timestamp: hexutil.MustDecodeUint64(transaction.Block.Timestamp),
From: celoutils.ChecksumAddress(transaction.From.Address),
To: address.Hex(),
TxHash: transaction.Hash,
TxIndex: transaction.Index,
TXType: "approve",
}
if transaction.Status == 1 {
approveEvent.Success = true
}
if err := f.pub.Publish(
approveEventSubject,
transaction.Hash,
approveEvent,
); err != nil {
return false, err
}
return true, nil
}
return true, nil
}

View File

@ -1,12 +1,8 @@
package filter
import (
"context"
"github.com/grassrootseconomics/cic-chain-events/pkg/fetch"
)
import "github.com/grassrootseconomics/cic-chain-events/internal/fetch"
// Filter defines a read only filter which must return next as true/false or an error
type Filter interface {
Execute(ctx context.Context, inputTransaction *fetch.Transaction) (next bool, err error)
Execute(inputTransaction fetch.Transaction) (next bool, err error)
}

View File

@ -1,80 +0,0 @@
package filter
import (
"context"
"github.com/celo-org/celo-blockchain/common"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-chain-events/internal/pub"
"github.com/grassrootseconomics/cic-chain-events/pkg/fetch"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/zerodha/logf"
)
const (
gasEventSubject = "CHAIN.gas"
)
var (
giveToSig = w3.MustNewFunc("giveTo(address)", "uint256")
)
type (
GasFilterOpts struct {
Logg logf.Logger
Pub *pub.Pub
}
GasFilter struct {
logg logf.Logger
pub *pub.Pub
}
)
func NewGasFilter(o GasFilterOpts) Filter {
return &GasFilter{
logg: o.Logg,
pub: o.Pub,
}
}
func (f *GasFilter) Execute(_ context.Context, transaction *fetch.Transaction) (bool, error) {
if len(transaction.InputData) < 10 {
return true, nil
}
if transaction.InputData[:10] == "0x63e4bff4" {
var address common.Address
if err := giveToSig.DecodeArgs(w3.B(transaction.InputData), &address); err != nil {
return false, err
}
giveToEvent := &pub.MinimalTxInfo{
Block: transaction.Block.Number,
ContractAddress: celoutils.ChecksumAddress(transaction.To.Address),
Timestamp: hexutil.MustDecodeUint64(transaction.Block.Timestamp),
To: address.Hex(),
TxHash: transaction.Hash,
TxIndex: transaction.Index,
TXType: "gas",
}
if transaction.Status == 1 {
giveToEvent.Success = true
}
if err := f.pub.Publish(
gasEventSubject,
transaction.Hash,
giveToEvent,
); err != nil {
return false, err
}
return true, nil
}
return true, nil
}

View File

@ -0,0 +1,25 @@
package filter
import (
"github.com/grassrootseconomics/cic-chain-events/internal/fetch"
"github.com/zerodha/logf"
)
type NoopFilterOpts struct {
Logg logf.Logger
}
type NoopFilter struct {
logg logf.Logger
}
func NewNoopFilter(o NoopFilterOpts) Filter {
return &NoopFilter{
logg: o.Logg,
}
}
func (f *NoopFilter) Execute(transaction fetch.Transaction) (bool, error) {
f.logg.Debug("noop filter", "block", transaction.Block.Number, "index", transaction.Index)
return true, nil
}

View File

@ -1,80 +0,0 @@
package filter
import (
"context"
"github.com/celo-org/celo-blockchain/common"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-chain-events/internal/pub"
"github.com/grassrootseconomics/cic-chain-events/pkg/fetch"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/zerodha/logf"
)
const (
registerEventSubject = "CHAIN.register"
)
var (
registerSig = w3.MustNewFunc("register(address)", "")
)
type (
RegisterFilterOpts struct {
Logg logf.Logger
Pub *pub.Pub
}
RegisterFilter struct {
logg logf.Logger
pub *pub.Pub
}
)
func NewRegisterFilter(o RegisterFilterOpts) Filter {
return &RegisterFilter{
logg: o.Logg,
pub: o.Pub,
}
}
func (f *RegisterFilter) Execute(_ context.Context, transaction *fetch.Transaction) (bool, error) {
if len(transaction.InputData) < 10 {
return true, nil
}
if transaction.InputData[:10] == "0x4420e486" {
var address common.Address
if err := registerSig.DecodeArgs(w3.B(transaction.InputData), &address); err != nil {
return false, err
}
addEvent := &pub.MinimalTxInfo{
Block: transaction.Block.Number,
ContractAddress: celoutils.ChecksumAddress(transaction.To.Address),
Timestamp: hexutil.MustDecodeUint64(transaction.Block.Timestamp),
To: address.Hex(),
TxHash: transaction.Hash,
TxIndex: transaction.Index,
TXType: "register",
}
if transaction.Status == 1 {
addEvent.Success = true
}
if err := f.pub.Publish(
registerEventSubject,
transaction.Hash,
addEvent,
); err != nil {
return false, err
}
return true, nil
}
return true, nil
}

View File

@ -1,84 +0,0 @@
package filter
import (
"context"
"strings"
"sync"
"github.com/celo-org/celo-blockchain/common"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-chain-events/internal/pub"
"github.com/grassrootseconomics/cic-chain-events/pkg/fetch"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/zerodha/logf"
)
const (
tokenIndexFilterEventSubject = "CHAIN.tokenAdded"
)
var (
addSig = w3.MustNewFunc("add(address)", "bool")
)
type (
TokenIndexFilterOpts struct {
Cache *sync.Map
Logg logf.Logger
Pub *pub.Pub
}
TokenIndexFilter struct {
pub *pub.Pub
cache *sync.Map
logg logf.Logger
}
)
func NewTokenIndexFilter(o TokenIndexFilterOpts) Filter {
return &TokenIndexFilter{
cache: o.Cache,
logg: o.Logg,
pub: o.Pub,
}
}
func (f *TokenIndexFilter) Execute(_ context.Context, transaction *fetch.Transaction) (bool, error) {
if len(transaction.InputData) < 10 {
return true, nil
}
if transaction.InputData[:10] == "0x0a3b0a4f" {
var address common.Address
if err := addSig.DecodeArgs(w3.B(transaction.InputData), &address); err != nil {
return false, err
}
f.cache.Store(strings.ToLower(address.Hex()), transaction.Hash)
addEvent := &pub.MinimalTxInfo{
Block: transaction.Block.Number,
ContractAddress: celoutils.ChecksumAddress(transaction.To.Address),
Timestamp: hexutil.MustDecodeUint64(transaction.Block.Timestamp),
To: address.Hex(),
TxHash: transaction.Hash,
TxIndex: transaction.Index,
TXType: "tokenAdd",
}
if transaction.Status == 1 {
addEvent.Success = true
}
if err := f.pub.Publish(
tokenIndexFilterEventSubject,
transaction.Hash,
addEvent,
); err != nil {
return false, err
}
}
return true, nil
}

View File

@ -1,160 +1,33 @@
package filter
import (
"context"
"math/big"
"github.com/celo-org/celo-blockchain/common"
"github.com/celo-org/celo-blockchain/common/hexutil"
"github.com/grassrootseconomics/celoutils"
"github.com/grassrootseconomics/cic-chain-events/internal/pub"
"github.com/grassrootseconomics/cic-chain-events/pkg/fetch"
"github.com/grassrootseconomics/w3-celo-patch"
"github.com/grassrootseconomics/cic-chain-events/internal/fetch"
"github.com/zerodha/logf"
)
const (
transferFilterEventSubject = "CHAIN.transfer"
)
type TransferFilterOpts struct {
Logg logf.Logger
}
var (
transferSig = w3.MustNewFunc("transfer(address, uint256)", "bool")
transferFromSig = w3.MustNewFunc("transferFrom(address, address, uint256)", "bool")
mintToSig = w3.MustNewFunc("mintTo(address, uint256)", "bool")
)
type (
TransferFilterOpts struct {
Logg logf.Logger
Pub *pub.Pub
}
TransferFilter struct {
logg logf.Logger
pub *pub.Pub
}
)
type TransferFilter struct {
logg logf.Logger
}
func NewTransferFilter(o TransferFilterOpts) Filter {
return &TransferFilter{
logg: o.Logg,
pub: o.Pub,
}
}
func (f *TransferFilter) Execute(_ context.Context, transaction *fetch.Transaction) (bool, error) {
if len(transaction.InputData) < 10 {
return true, nil
}
func (f *TransferFilter) Execute(transaction fetch.Transaction) (bool, error) {
switch transaction.InputData[:10] {
case "0xa9059cbb":
var (
to common.Address
value big.Int
)
if err := transferSig.DecodeArgs(w3.B(transaction.InputData), &to, &value); err != nil {
return false, err
}
transferEvent := &pub.MinimalTxInfo{
Block: transaction.Block.Number,
From: celoutils.ChecksumAddress(transaction.From.Address),
Timestamp: hexutil.MustDecodeUint64(transaction.Block.Timestamp),
To: to.Hex(),
ContractAddress: celoutils.ChecksumAddress(transaction.To.Address),
TxHash: transaction.Hash,
TxIndex: transaction.Index,
TXType: "transfer",
Value: value.Uint64(),
}
if transaction.Status == 1 {
transferEvent.Success = true
}
if err := f.pub.Publish(
transferFilterEventSubject,
transaction.Hash,
transferEvent,
); err != nil {
return false, err
}
return true, nil
f.logg.Info("cUSD transfer", "block", transaction.Block.Number, "index", transaction.Index)
case "0x23b872dd":
var (
from common.Address
to common.Address
value big.Int
)
if err := transferFromSig.DecodeArgs(w3.B(transaction.InputData), &from, &to, &value); err != nil {
return false, err
}
transferEvent := &pub.MinimalTxInfo{
Block: transaction.Block.Number,
From: from.Hex(),
Timestamp: hexutil.MustDecodeUint64(transaction.Block.Timestamp),
To: to.Hex(),
ContractAddress: celoutils.ChecksumAddress(transaction.To.Address),
TxHash: transaction.Hash,
TxIndex: transaction.Index,
TXType: "transferFrom",
Value: value.Uint64(),
}
if transaction.Status == 1 {
transferEvent.Success = true
}
if err := f.pub.Publish(
transferFilterEventSubject,
transaction.Hash,
transferEvent,
); err != nil {
return false, err
}
return true, nil
case "0x449a52f8":
var (
to common.Address
value big.Int
)
if err := mintToSig.DecodeArgs(w3.B(transaction.InputData), &to, &value); err != nil {
return false, err
}
transferEvent := &pub.MinimalTxInfo{
Block: transaction.Block.Number,
From: celoutils.ChecksumAddress(transaction.From.Address),
Timestamp: hexutil.MustDecodeUint64(transaction.Block.Timestamp),
To: to.Hex(),
ContractAddress: celoutils.ChecksumAddress(transaction.To.Address),
TxHash: transaction.Hash,
TxIndex: transaction.Index,
TXType: "mintTo",
Value: value.Uint64(),
}
if transaction.Status == 1 {
transferEvent.Success = true
}
if err := f.pub.Publish(
transferFilterEventSubject,
transaction.Hash,
transferEvent,
); err != nil {
return false, err
}
return true, nil
f.logg.Info("cUSD transferFrom", "block", transaction.Block.Number, "index", transaction.Index)
default:
return true, nil
f.logg.Info("cUSD otherMethod", "block", transaction.Block.Number, "index", transaction.Index)
}
return true, nil
}

View File

@ -1,77 +0,0 @@
package filter
import (
"context"
"testing"
"github.com/grassrootseconomics/cic-chain-events/pkg/fetch"
"github.com/stretchr/testify/suite"
"github.com/zerodha/logf"
)
type TransferFilterSuite struct {
suite.Suite
filter Filter
}
func (s *TransferFilterSuite) SetupSuite() {
logg := logf.New(
logf.Opts{
Level: logf.DebugLevel,
},
)
s.filter = NewTransferFilter(TransferFilterOpts{
Logg: logg,
})
}
func (s *TransferFilterSuite) TestTranfserInputs() {
type testCase struct {
transactionData fetch.Transaction
want bool
wantErr bool
}
// Generated with eth-encode
tests := []testCase{
{
transactionData: fetch.Transaction{
InputData: "0xa9059cbb000000000000000000000000000000000000000000000000000000000000dEaD00000000000000000000000000000000000000000000000000000000000003e8",
},
want: true,
wantErr: false,
},
{
transactionData: fetch.Transaction{
InputData: "0x23b872dd000000000000000000000000000000000000000000000000000000000000dEaD000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000003e8",
},
want: true,
wantErr: false,
},
{
transactionData: fetch.Transaction{
InputData: "0x449a52f8000000000000000000000000000000000000000000000000000000000000dEaD00000000000000000000000000000000000000000000000000000000000003e8",
},
want: true,
wantErr: false,
},
{
transactionData: fetch.Transaction{
InputData: "0x8d72ec9d000000000000000000000000000000000000000000000000000000000000dEaD00000000000000000000000000000000000000000000000000000000000003e8",
},
want: false,
wantErr: false,
},
}
for _, test := range tests {
next, err := s.filter.Execute(context.Background(), &test.transactionData)
s.NoError(err)
s.Equal(test.want, next)
}
}
func TestTransferFilterSuite(t *testing.T) {
suite.Run(t, new(TransferFilterSuite))
}

View File

@ -1,30 +1,26 @@
package pipeline
import (
"context"
"github.com/grassrootseconomics/cic-chain-events/internal/fetch"
"github.com/grassrootseconomics/cic-chain-events/internal/filter"
"github.com/grassrootseconomics/cic-chain-events/internal/store"
"github.com/grassrootseconomics/cic-chain-events/pkg/fetch"
"github.com/jackc/pgx/v5"
"github.com/zerodha/logf"
)
type (
PipelineOpts struct {
BlockFetcher fetch.Fetch
Filters []filter.Filter
Logg logf.Logger
Store store.Store[pgx.Rows]
}
type PipelineOpts struct {
BlockFetcher fetch.Fetch
Filters []filter.Filter
Logg logf.Logger
Store store.Store[pgx.Rows]
}
Pipeline struct {
fetch fetch.Fetch
filters []filter.Filter
logg logf.Logger
store store.Store[pgx.Rows]
}
)
type Pipeline struct {
fetch fetch.Fetch
filters []filter.Filter
logg logf.Logger
store store.Store[pgx.Rows]
}
func NewPipeline(o PipelineOpts) *Pipeline {
return &Pipeline{
@ -35,24 +31,19 @@ func NewPipeline(o PipelineOpts) *Pipeline {
}
}
// Run is the task executor which runs in its own goroutine and does the following:
// 1. Fetches the block and all transactional data
// 2. Passes the block through all filters
// 3. Commits the block to store as successfully processed
//
// Note:
// - Blocks are processed atomically, a failure in-between will process the block from the start
// - Therefore, any side effect/event sink in the filter should support dedup
func (md *Pipeline) Run(ctx context.Context, blockNumber uint64) error {
fetchResp, err := md.fetch.Block(ctx, blockNumber)
// Run is the task executor which fetches and processes a block and its transactions through the pipeline filters
func (md *Pipeline) Run(blockNumber uint64) error {
fetchResp, err := md.fetch.Block(blockNumber)
if err != nil {
md.logg.Error("pipeline block fetch error", "error", err)
return err
}
for _, tx := range fetchResp.Data.Block.Transactions {
for _, filter := range md.filters {
next, err := filter.Execute(ctx, &tx)
next, err := filter.Execute(tx)
if err != nil {
md.logg.Error("pipeline run error", "error", err)
return err
}
if !next {
@ -61,9 +52,10 @@ func (md *Pipeline) Run(ctx context.Context, blockNumber uint64) error {
}
}
if err := md.store.CommitBlock(ctx, blockNumber); err != nil {
if err := md.store.CommitBlock(blockNumber); err != nil {
return err
}
md.logg.Debug("successfully commited block", "block", blockNumber)
return nil
}

View File

@ -0,0 +1,136 @@
package pipeline
import (
"errors"
"testing"
"github.com/grassrootseconomics/cic-chain-events/internal/fetch"
"github.com/grassrootseconomics/cic-chain-events/internal/filter"
"github.com/stretchr/testify/suite"
"github.com/zerodha/logf"
)
var (
graphqlEndpoint = "https://rpc.celo.grassecon.net/graphql"
)
type itPipelineTest struct {
suite.Suite
errorPipeline *Pipeline
normalPipeline *Pipeline
earlyExitPipeline *Pipeline
}
func TestPipelineSuite(t *testing.T) {
suite.Run(t, new(itPipelineTest))
}
type errorFilter struct{}
func newErrorFilter() filter.Filter {
return &errorFilter{}
}
func (f *errorFilter) Execute(transaction fetch.Transaction) (bool, error) {
return false, errors.New("crash")
}
type earlyExitFilter struct{}
func newEarlyExitFilter() filter.Filter {
return &earlyExitFilter{}
}
func (f *earlyExitFilter) Execute(transaction fetch.Transaction) (bool, error) {
return false, nil
}
func (s *itPipelineTest) SetupSuite() {
logger := logf.New(
logf.Opts{
Level: logf.DebugLevel,
},
)
fetcher := fetch.NewGraphqlFetcher(fetch.GraphqlOpts{
GraphqlEndpoint: graphqlEndpoint,
})
noopFilter := filter.NewNoopFilter(filter.NoopFilterOpts{
Logg: logger,
})
errFilter := newErrorFilter()
earlyFilter := newEarlyExitFilter()
s.errorPipeline = NewPipeline(PipelineOpts{
Filters: []filter.Filter{
noopFilter,
errFilter,
},
BlockFetcher: fetcher,
Logg: logger,
})
s.normalPipeline = NewPipeline(PipelineOpts{
Filters: []filter.Filter{
noopFilter,
},
BlockFetcher: fetcher,
Logg: logger,
})
s.earlyExitPipeline = NewPipeline(PipelineOpts{
Filters: []filter.Filter{
noopFilter,
earlyFilter,
errFilter,
},
BlockFetcher: fetcher,
Logg: logger,
})
}
func (s *itPipelineTest) Test_E2E_Pipeline_Run_On_Existing_Block_No_Err() {
err := s.normalPipeline.Run(14974600)
s.NoError(err)
}
func (s *itPipelineTest) Test_E2E_Pipeline_Run_On_Non_Existing_Block_No_Err() {
err := s.normalPipeline.Run(14974600000)
s.Error(err)
}
func (s *itPipelineTest) Test_E2E_Pipeline_Run_On_Existing_Block_Early() {
err := s.earlyExitPipeline.Run(14974600)
s.NoError(err)
}
func (s *itPipelineTest) Test_E2E_Pipeline_Run_On_Existing_Block_With_Err() {
err := s.errorPipeline.Run(14974600)
s.Error(err)
}
func (s *itPipelineTest) Test_E2E_Pipeline_Run_On_Non_Existing_Block_With_Err() {
err := s.errorPipeline.Run(14974600000)
s.Error(err)
}
func (s *itPipelineTest) Test_E2E_Pipeline_Run_On_Non_Existing_Block_Early() {
err := s.earlyExitPipeline.Run(14974600000)
s.Error(err)
}
func (s *itPipelineTest) Test_E2E_Pipeline_Run_On_Empty_Block_With_No_Err() {
err := s.normalPipeline.Run(15370320)
s.NoError(err)
}
func (s *itPipelineTest) Test_E2E_Pipeline_Run_On_Empty_Block_With_Err() {
err := s.errorPipeline.Run(15370320)
s.NoError(err)
}
func (s *itPipelineTest) Test_E2E_Pipeline_Run_On_Empty_Block_Early() {
err := s.earlyExitPipeline.Run(15370320)
s.NoError(err)
}

View File

@ -1,28 +1,21 @@
package pool
import (
"context"
"time"
"github.com/alitto/pond"
)
const (
idleTimeout = 1 * time.Second
)
type Opts struct {
Concurrency int
QueueSize int
ConcurrencyFactor int
PoolQueueSize int
}
// NewPool creates a fixed size (and buffered) go routine worker pool.
func NewPool(ctx context.Context, o Opts) *pond.WorkerPool {
func NewPool(o Opts) *pond.WorkerPool {
return pond.New(
o.Concurrency,
o.QueueSize,
pond.MinWorkers(o.Concurrency),
pond.IdleTimeout(idleTimeout),
pond.Context(ctx),
o.ConcurrencyFactor,
o.PoolQueueSize,
pond.MinWorkers(o.ConcurrencyFactor),
pond.IdleTimeout(time.Second*1),
)
}

View File

@ -1,83 +0,0 @@
package pub
import (
"encoding/json"
"time"
"github.com/nats-io/nats.go"
)
const (
streamName string = "CHAIN"
streamSubjects string = "CHAIN.*"
)
type (
PubOpts struct {
DedupDuration time.Duration
JsCtx nats.JetStreamContext
NatsConn *nats.Conn
PersistDuration time.Duration
}
Pub struct {
natsConn *nats.Conn
jsCtx nats.JetStreamContext
}
MinimalTxInfo struct {
Block uint64 `json:"block"`
From string `json:"from"`
To string `json:"to"`
ContractAddress string `json:"contractAddress"`
Success bool `json:"success"`
Timestamp uint64 `json:"timestamp"`
TxHash string `json:"transactionHash"`
TxIndex uint `json:"transactionIndex"`
TXType string `json:"txType"`
Value uint64 `json:"value"`
}
)
func NewPub(o PubOpts) (*Pub, error) {
stream, _ := o.JsCtx.StreamInfo(streamName)
if stream == nil {
_, err := o.JsCtx.AddStream(&nats.StreamConfig{
Name: streamName,
MaxAge: o.PersistDuration,
Storage: nats.FileStorage,
Subjects: []string{streamSubjects},
Duplicates: o.DedupDuration,
})
if err != nil {
return nil, err
}
}
return &Pub{
jsCtx: o.JsCtx,
natsConn: o.NatsConn,
}, nil
}
// Close gracefully shutdowns the JetStream connection.
func (p *Pub) Close() {
if p.natsConn != nil {
p.natsConn.Close()
}
}
// Publish publishes the JSON data to the NATS stream.
func (p *Pub) Publish(subject string, dedupId string, eventPayload interface{}) error {
jsonData, err := json.Marshal(eventPayload)
if err != nil {
return err
}
_, err = p.jsCtx.Publish(subject, jsonData, nats.MsgId(dedupId))
if err != nil {
return err
}
return nil
}

View File

@ -3,37 +3,27 @@ package store
import (
"context"
"fmt"
"os"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/jackc/tern/v2/migrate"
"github.com/knadh/goyesql/v2"
"github.com/zerodha/logf"
)
const (
schemaTable = "schema_version"
)
type queries struct {
CommitBlock string `query:"commit-block"`
GetMissingBlocks string `query:"get-missing-blocks"`
GetSearchBounds string `query:"get-search-bounds"`
InitSyncerMeta string `query:"init-syncer-meta"`
SetSearchLowerBound string `query:"set-search-lower-bound"`
}
type (
queries struct {
CommitBlock string `query:"commit-block"`
GetMissingBlocks string `query:"get-missing-blocks"`
GetSearchBounds string `query:"get-search-bounds"`
InitSyncerMeta string `query:"init-syncer-meta"`
SetSearchLowerBound string `query:"set-search-lower-bound"`
}
PostgresStoreOpts struct {
DSN string
MigrationsFolderPath string
InitialLowerBound uint64
Logg logf.Logger
Queries goyesql.Queries
}
)
type PostgresStoreOpts struct {
DSN string
InitialLowerBound uint64
Logg logf.Logger
Queries goyesql.Queries
}
type PostgresStore struct {
logg logf.Logger
@ -55,34 +45,12 @@ func NewPostgresStore(o PostgresStoreOpts) (Store[pgx.Rows], error) {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
dbPool, err := pgxpool.NewWithConfig(ctx, parsedConfig)
dbPool, err := pgxpool.NewWithConfig(context.Background(), parsedConfig)
if err != nil {
return nil, err
}
conn, err := dbPool.Acquire(ctx)
if err != nil {
return nil, err
}
defer conn.Release()
migrator, err := migrate.NewMigrator(ctx, conn.Conn(), schemaTable)
if err != nil {
return nil, err
}
if err := migrator.LoadMigrations(os.DirFS(o.MigrationsFolderPath)); err != nil {
return nil, err
}
if err := migrator.Migrate(ctx); err != nil {
return nil, err
}
_, err = dbPool.Exec(ctx, postgresStore.queries.InitSyncerMeta, o.InitialLowerBound)
_, err = dbPool.Exec(context.Background(), postgresStore.queries.InitSyncerMeta, o.InitialLowerBound)
if err != nil {
return nil, err
}
@ -92,14 +60,14 @@ func NewPostgresStore(o PostgresStoreOpts) (Store[pgx.Rows], error) {
return postgresStore, nil
}
func (s *PostgresStore) GetSearchBounds(ctx context.Context, batchSize uint64, headCursor uint64, headBlockLag uint64) (uint64, uint64, error) {
func (s *PostgresStore) GetSearchBounds(batchSize uint64, headCursor uint64, headBlockLag uint64) (uint64, uint64, error) {
var (
lowerBound uint64
upperBound uint64
)
if err := s.pool.QueryRow(
ctx,
context.Background(),
s.queries.GetSearchBounds,
batchSize,
headCursor,
@ -112,8 +80,8 @@ func (s *PostgresStore) GetSearchBounds(ctx context.Context, batchSize uint64, h
return lowerBound, upperBound, nil
}
func (s *PostgresStore) GetMissingBlocks(ctx context.Context, lowerBound uint64, upperBound uint64) (pgx.Rows, error) {
rows, err := s.pool.Query(ctx, s.queries.GetMissingBlocks, lowerBound, upperBound)
func (s *PostgresStore) GetMissingBlocks(lowerBound uint64, upperBound uint64) (pgx.Rows, error) {
rows, err := s.pool.Query(context.Background(), s.queries.GetMissingBlocks, lowerBound, upperBound)
if err != nil {
return nil, err
}
@ -121,8 +89,8 @@ func (s *PostgresStore) GetMissingBlocks(ctx context.Context, lowerBound uint64,
return rows, nil
}
func (s *PostgresStore) SetSearchLowerBound(ctx context.Context, newLowerBound uint64) error {
_, err := s.pool.Exec(ctx, s.queries.SetSearchLowerBound, newLowerBound)
func (s *PostgresStore) SetSearchLowerBound(newLowerBound uint64) error {
_, err := s.pool.Exec(context.Background(), s.queries.SetSearchLowerBound, newLowerBound)
if err != nil {
return err
}
@ -130,8 +98,8 @@ func (s *PostgresStore) SetSearchLowerBound(ctx context.Context, newLowerBound u
return nil
}
func (s *PostgresStore) CommitBlock(ctx context.Context, block uint64) error {
_, err := s.pool.Exec(ctx, s.queries.CommitBlock, block)
func (s *PostgresStore) CommitBlock(block uint64) error {
_, err := s.pool.Exec(context.Background(), s.queries.CommitBlock, block)
if err != nil {
return err
}

View File

@ -1,11 +1,10 @@
package store
import "context"
// Store defines all relevant get/set queries against the implemented storage backend.
// Store defines indexer get and set queries.
// GetMissingBlocks returns a generic iterable.
type Store[T any] interface {
GetSearchBounds(ctx context.Context, batchSize uint64, headCursor uint64, headBlockLag uint64) (lowerBound uint64, upperBound uint64, err error)
GetMissingBlocks(ctx context.Context, lowerBound uint64, upperBound uint64) (missingBlocksIterable T, err error)
SetSearchLowerBound(ctx context.Context, newLowerBound uint64) (err error)
CommitBlock(ctx context.Context, block uint64) (err error)
GetSearchBounds(batchSize uint64, headCursor uint64, headBlockLag uint64) (lowerBound uint64, upperBound uint64, err error)
GetMissingBlocks(lowerBound uint64, upperBound uint64) (missingBlocksIterable T, err error)
SetSearchLowerBound(newLowerBound uint64) (err error)
CommitBlock(block uint64) (err error)
}

View File

@ -2,38 +2,29 @@ package syncer
import (
"context"
"time"
"github.com/alitto/pond"
"github.com/celo-org/celo-blockchain/core/types"
"github.com/celo-org/celo-blockchain/ethclient"
"github.com/celo-org/celo-blockchain/event"
"github.com/grassrootseconomics/cic-chain-events/internal/pipeline"
"github.com/zerodha/logf"
)
const (
jobTimeout = 5 * time.Second
resubscribeBackoff = 2 * time.Second
)
type HeadSyncerOpts struct {
Stats *Stats
Pipeline *pipeline.Pipeline
Logg logf.Logger
Pool *pond.WorkerPool
WsEndpoint string
}
type (
HeadSyncerOpts struct {
Logg logf.Logger
Pipeline *pipeline.Pipeline
Pool *pond.WorkerPool
Stats *Stats
WsEndpoint string
}
HeadSyncer struct {
ethClient *ethclient.Client
logg logf.Logger
pipeline *pipeline.Pipeline
pool *pond.WorkerPool
stats *Stats
}
)
type HeadSyncer struct {
stats *Stats
pipeline *pipeline.Pipeline
logg logf.Logger
ethClient *ethclient.Client
pool *pond.WorkerPool
}
func NewHeadSyncer(o HeadSyncerOpts) (*HeadSyncer, error) {
ethClient, err := ethclient.Dial(o.WsEndpoint)
@ -42,45 +33,41 @@ func NewHeadSyncer(o HeadSyncerOpts) (*HeadSyncer, error) {
}
return &HeadSyncer{
ethClient: ethClient,
logg: o.Logg,
pipeline: o.Pipeline,
pool: o.Pool,
stats: o.Stats,
pipeline: o.Pipeline,
logg: o.Logg,
ethClient: ethClient,
pool: o.Pool,
}, nil
}
// Start creates a websocket subscription and actively receives new blocks until stopped
// or a critical error occurs.
func (hs *HeadSyncer) Start(ctx context.Context) error {
headerReceiver := make(chan *types.Header, 1)
headerReceiver := make(chan *types.Header, 10)
sub := event.ResubscribeErr(resubscribeBackoff, func(ctx context.Context, err error) (event.Subscription, error) {
if err != nil {
hs.logg.Error("head syncer: resubscribe error", "error", err)
}
return hs.ethClient.SubscribeNewHead(ctx, headerReceiver)
})
defer sub.Unsubscribe()
sub, err := hs.ethClient.SubscribeNewHead(ctx, headerReceiver)
if err != nil {
return err
}
for {
select {
case <-ctx.Done():
hs.logg.Info("head syncer: shutdown signal received")
return nil
case header := <-headerReceiver:
blockNumber := header.Number.Uint64()
hs.logg.Debug("head syncer: received new block", "block", blockNumber)
hs.stats.UpdateHeadCursor(blockNumber)
hs.pool.Submit(func() {
ctx, cancel := context.WithTimeout(context.Background(), jobTimeout)
defer cancel()
block := header.Number.Uint64()
hs.logg.Debug("head syncer received new block", "block", block)
if err := hs.pipeline.Run(ctx, blockNumber); err != nil {
hs.logg.Error("head syncer: pipeline run error", "error", err)
hs.stats.UpdateHeadCursor(block)
hs.pool.Submit(func() {
if err := hs.pipeline.Run(block); err != nil {
hs.logg.Error("pipeline run error", "error", err)
}
})
case err := <-sub.Err():
hs.logg.Error("head syncer error", "error", err)
return err
case <-ctx.Done():
hs.logg.Debug("head syncer shutdown signnal received")
sub.Unsubscribe()
return nil
}
}
}

View File

@ -11,35 +11,32 @@ import (
"github.com/zerodha/logf"
)
const (
headBlockLag = 5
)
type JanitorOpts struct {
BatchSize uint64
HeadBlockLag uint64
Logg logf.Logger
Pipeline *pipeline.Pipeline
Pool *pond.WorkerPool
Stats *Stats
Store store.Store[pgx.Rows]
SweepInterval time.Duration
}
type (
JanitorOpts struct {
BatchSize uint64
Logg logf.Logger
Pipeline *pipeline.Pipeline
Pool *pond.WorkerPool
Stats *Stats
Store store.Store[pgx.Rows]
SweepInterval time.Duration
}
Janitor struct {
batchSize uint64
pipeline *pipeline.Pipeline
logg logf.Logger
pool *pond.WorkerPool
stats *Stats
store store.Store[pgx.Rows]
sweepInterval time.Duration
}
)
type Janitor struct {
batchSize uint64
headBlockLag uint64
pipeline *pipeline.Pipeline
logg logf.Logger
pool *pond.WorkerPool
stats *Stats
store store.Store[pgx.Rows]
sweepInterval time.Duration
}
func NewJanitor(o JanitorOpts) *Janitor {
return &Janitor{
batchSize: o.BatchSize,
headBlockLag: o.HeadBlockLag,
logg: o.Logg,
pipeline: o.Pipeline,
pool: o.Pool,
@ -50,78 +47,71 @@ func NewJanitor(o JanitorOpts) *Janitor {
}
func (j *Janitor) Start(ctx context.Context) error {
ticker := time.NewTicker(j.sweepInterval)
timer := time.NewTimer(j.sweepInterval)
for {
select {
case <-ctx.Done():
j.logg.Info("janitor: shutdown signal received")
return nil
case <-ticker.C:
ctx, cancel := context.WithTimeout(context.Background(), jobTimeout)
defer cancel()
j.logg.Debug("janitor: starting sweep")
if err := j.QueueMissingBlocks(ctx); err != nil {
j.logg.Error("janitor: queue missing blocks error", "error", err)
case <-timer.C:
j.logg.Debug("janitor starting sweep")
if err := j.QueueMissingBlocks(); err != nil {
j.logg.Error("janitor error", "error", err)
}
timer.Reset(j.sweepInterval)
case <-ctx.Done():
j.logg.Debug("janitor shutdown signal received")
return nil
}
}
}
// QueueMissingBlocks searches for missing block and queues the block for processing.
// It will run twice for a given search range and only after, raise the lower bound.
func (j *Janitor) QueueMissingBlocks(ctx context.Context) error {
func (j *Janitor) QueueMissingBlocks() error {
if j.stats.GetHeadCursor() == 0 {
j.logg.Warn("janitor: (skipping) awaiting head synchronization")
j.logg.Debug("janitor waiting for head synchronization")
return nil
}
if j.pool.WaitingTasks() != 0 {
j.logg.Debug("janitor: (skipping) queue has pending jobs", "pending_jobs", j.pool.WaitingTasks())
if j.pool.WaitingTasks() >= j.batchSize {
j.logg.Debug("janitor skipping potential queue pressure")
return nil
}
lowerBound, upperBound, err := j.store.GetSearchBounds(
ctx,
j.batchSize,
j.stats.GetHeadCursor(),
headBlockLag,
j.headBlockLag,
)
if err != nil {
return err
}
j.logg.Debug("janitor search bounds", "lower_bound", lowerBound, "upper_bound", upperBound)
rows, err := j.store.GetMissingBlocks(ctx, lowerBound, upperBound)
rows, err := j.store.GetMissingBlocks(lowerBound, upperBound)
if err != nil {
return err
}
missingBlockCountReport := j.stats.GetHeadCursor() - lowerBound
if missingBlockCountReport > 10 {
j.logg.Info("janitor: missing blocks", "count", missingBlockCountReport)
}
rowsProcessed := 0
for rows.Next() {
var blockNumber uint64
if err := rows.Scan(&blockNumber); err != nil {
var n uint64
if err := rows.Scan(&n); err != nil {
return err
}
j.pool.Submit(func() {
if err := j.pipeline.Run(ctx, blockNumber); err != nil {
j.logg.Error("janitor: pipeline run error", "error", err)
if err := j.pipeline.Run(n); err != nil {
j.logg.Error("pipeline run error", "error", err)
}
})
rowsProcessed++
}
j.logg.Debug("janitor: missing blocks to be processed", "count", rowsProcessed)
j.logg.Debug("janitor missing block count", "count", rowsProcessed)
if rowsProcessed == 0 {
j.logg.Info("janitor: no gap! rasing lower bound", "new_lower_bound", upperBound)
j.logg.Debug("no missing blocks, rasing lower bound")
j.stats.UpdateLowerBound(upperBound)
j.store.SetSearchLowerBound(ctx, upperBound)
j.store.SetSearchLowerBound(upperBound)
}
if rows.Err() != nil {

View File

@ -4,7 +4,8 @@ import (
"sync/atomic"
)
// Stats synchronizes block cursors values across the head and janitor.
// Stats synchronize syncer values across the head and janitor.
// could also be used to expose Prom gauges
type Stats struct {
headCursor atomic.Uint64
lowerBound atomic.Uint64

View File

@ -5,3 +5,8 @@ CREATE TABLE IF NOT EXISTS blocks (
CREATE TABLE syncer_meta (
lower_bound INT
);
---- create above / drop below ----
DROP TABLE syncer_meta;
DROP TABLE blocks;

View File

@ -1,158 +0,0 @@
// MIT License
// Copyright (c) 2017 Leslie Zheng
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package echopprof
import (
"net/http/pprof"
"strings"
"github.com/labstack/echo/v4"
)
// Wrap adds several routes from package `net/http/pprof` to *echo.Echo object.
func Wrap(e *echo.Echo) {
WrapGroup("", e.Group("/debug/pprof"))
}
// Wrapper make sure we are backward compatible.
var Wrapper = Wrap
// WrapGroup adds several routes from package `net/http/pprof` to *echo.Group object.
func WrapGroup(prefix string, g *echo.Group) {
routers := []struct {
Method string
Path string
Handler echo.HandlerFunc
}{
{"GET", "", IndexHandler()},
{"GET", "/", IndexHandler()},
{"GET", "/heap", HeapHandler()},
{"GET", "/goroutine", GoroutineHandler()},
{"GET", "/block", BlockHandler()},
{"GET", "/threadcreate", ThreadCreateHandler()},
{"GET", "/cmdline", CmdlineHandler()},
{"GET", "/profile", ProfileHandler()},
{"GET", "/symbol", SymbolHandler()},
{"POST", "/symbol", SymbolHandler()},
{"GET", "/trace", TraceHandler()},
{"GET", "/mutex", MutexHandler()},
{"GET", "/allocs", AllocsHandler()},
}
for _, r := range routers {
switch r.Method {
case "GET":
g.GET(strings.TrimPrefix(r.Path, prefix), r.Handler)
case "POST":
g.POST(strings.TrimPrefix(r.Path, prefix), r.Handler)
}
}
}
// IndexHandler will pass the call from /debug/pprof to pprof.
func IndexHandler() echo.HandlerFunc {
return func(ctx echo.Context) error {
pprof.Index(ctx.Response().Writer, ctx.Request())
return nil
}
}
// HeapHandler will pass the call from /debug/pprof/heap to pprof.
func HeapHandler() echo.HandlerFunc {
return func(ctx echo.Context) error {
pprof.Handler("heap").ServeHTTP(ctx.Response(), ctx.Request())
return nil
}
}
// GoroutineHandler will pass the call from /debug/pprof/goroutine to pprof.
func GoroutineHandler() echo.HandlerFunc {
return func(ctx echo.Context) error {
pprof.Handler("goroutine").ServeHTTP(ctx.Response().Writer, ctx.Request())
return nil
}
}
// BlockHandler will pass the call from /debug/pprof/block to pprof.
func BlockHandler() echo.HandlerFunc {
return func(ctx echo.Context) error {
pprof.Handler("block").ServeHTTP(ctx.Response().Writer, ctx.Request())
return nil
}
}
// ThreadCreateHandler will pass the call from /debug/pprof/threadcreate to pprof.
func ThreadCreateHandler() echo.HandlerFunc {
return func(ctx echo.Context) error {
pprof.Handler("threadcreate").ServeHTTP(ctx.Response().Writer, ctx.Request())
return nil
}
}
// CmdlineHandler will pass the call from /debug/pprof/cmdline to pprof.
func CmdlineHandler() echo.HandlerFunc {
return func(ctx echo.Context) error {
pprof.Cmdline(ctx.Response().Writer, ctx.Request())
return nil
}
}
// ProfileHandler will pass the call from /debug/pprof/profile to pprof.
func ProfileHandler() echo.HandlerFunc {
return func(ctx echo.Context) error {
pprof.Profile(ctx.Response().Writer, ctx.Request())
return nil
}
}
// SymbolHandler will pass the call from /debug/pprof/symbol to pprof.
func SymbolHandler() echo.HandlerFunc {
return func(ctx echo.Context) error {
pprof.Symbol(ctx.Response().Writer, ctx.Request())
return nil
}
}
// TraceHandler will pass the call from /debug/pprof/trace to pprof.
func TraceHandler() echo.HandlerFunc {
return func(ctx echo.Context) error {
pprof.Trace(ctx.Response().Writer, ctx.Request())
return nil
}
}
// MutexHandler will pass the call from /debug/pprof/mutex to pprof.
func MutexHandler() echo.HandlerFunc {
return func(ctx echo.Context) error {
pprof.Handler("mutex").ServeHTTP(ctx.Response().Writer, ctx.Request())
return nil
}
}
// AllocsHandler will pass the call from /debug/pprof/allocs to pprof.
func AllocsHandler() echo.HandlerFunc {
return func(ctx echo.Context) error {
pprof.Handler("allocs").ServeHTTP(ctx.Response().Writer, ctx.Request())
return nil
}
}

View File

@ -1,69 +0,0 @@
package fetch
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
const (
graphqlQuery = `{"query":"{block(number:%d){transactions{block{number,timestamp},hash,index,from{address},to{address},value,inputData,status,gasUsed}}}"}`
)
type GraphqlOpts struct {
GraphqlEndpoint string
}
type Graphql struct {
graphqlEndpoint string
httpClient *http.Client
}
func NewGraphqlFetcher(o GraphqlOpts) Fetch {
return &Graphql{
httpClient: &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 60 * time.Second,
},
Timeout: time.Second * 5,
},
graphqlEndpoint: o.GraphqlEndpoint,
}
}
func (f *Graphql) Block(ctx context.Context, blockNumber uint64) (FetchResponse, error) {
fetchResponse := FetchResponse{}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, f.graphqlEndpoint, bytes.NewBufferString(fmt.Sprintf(graphqlQuery, blockNumber)))
if err != nil {
return fetchResponse, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := f.httpClient.Do(req)
if err != nil {
return fetchResponse, err
}
if resp.StatusCode >= http.StatusBadRequest {
return fetchResponse, fmt.Errorf("error fetching block %s", resp.Status)
}
out, err := io.ReadAll(resp.Body)
_ = resp.Body.Close()
if err != nil {
return fetchResponse, nil
}
if err := json.Unmarshal(out, &fetchResponse); err != nil {
return fetchResponse, err
}
return fetchResponse, nil
}

View File

@ -1,39 +0,0 @@
package fetch
import (
"context"
"os"
"testing"
"github.com/stretchr/testify/suite"
)
var (
graphqlEndpoint = os.Getenv("TEST_GRAPHQL_ENDPOINT")
)
type GraphQlTestSuite struct {
suite.Suite
fetch Fetch
}
func (s *GraphQlTestSuite) SetupSuite() {
s.fetch = NewGraphqlFetcher(GraphqlOpts{
GraphqlEndpoint: graphqlEndpoint,
})
}
func (s *GraphQlTestSuite) Test_E2E_Fetch_Existing_Block() {
resp, err := s.fetch.Block(context.Background(), 14974600)
s.NoError(err)
s.Len(resp.Data.Block.Transactions, 3)
}
func (s *GraphQlTestSuite) Test_E2E_Fetch_Non_Existing_Block() {
_, err := s.fetch.Block(context.Background(), 14974600000)
s.Error(err)
}
func TestGraphQlSuite(t *testing.T) {
suite.Run(t, new(GraphQlTestSuite))
}