diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..39bbd2681d --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "image": "mcr.microsoft.com/devcontainers/universal:2", + "features": {} +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e1203c2f3f..b42e99bb92 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: - name: Check out code into the Go module directory uses: actions/checkout@v3 - - name: Set up Go 1.21 + - name: Set up Go 1.23 uses: actions/setup-go@v4 with: go-version-file: './go.mod' @@ -36,7 +36,6 @@ jobs: - name: Get dependencies run: | - go install github.com/swaggo/swag/cmd/swag@v1.7.0 && swag init --parseDependency --parseInternal --parseDepth 1 -g handlers/api.go go get -v -t -d ./... # - name: Test diff --git a/.github/workflows/publish-dencun-images.yml b/.github/workflows/publish-dencun-images.yml deleted file mode 100644 index 668ba7f1b2..0000000000 --- a/.github/workflows/publish-dencun-images.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Publish Docker dencun images - -on: - # Trigger the workflow on push or pull request, - # but only for the staging branch - push: - branches: - - dencun - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: Publish to Dockerhub Registry - uses: elgohr/Publish-Docker-Github-Action@master - with: - name: gobitfly/eth2-beaconchain-explorer - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - tags: "dencun" diff --git a/.github/workflows/publish-docker-images-tagged.yml b/.github/workflows/publish-docker-images-tagged.yml deleted file mode 100644 index 38d7330c28..0000000000 --- a/.github/workflows/publish-docker-images-tagged.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Publish Docker images - -on: - # Trigger the workflow on push or pull request, - # but only for the master branch - push: - branches: - - publish-docker - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - id: pre-step - shell: bash - run: echo "release-version=$(TZ=UTC0 git show --quiet --date='format-local:%Y%m%d%H%M%S' --format="%cd" $GITHUB_SHA)-$(git describe $GITHUB_SHA --always --tags)" >> $GITHUB_OUTPUT - - - name: Publish to Dockerhub Registry - uses: elgohr/Publish-Docker-Github-Action@v5 - with: - name: gobitfly/eth2-beaconchain-explorer - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - tags: "${{ steps.pre-step.outputs.release-version }}" - diff --git a/.github/workflows/publish-docker-images.yml b/.github/workflows/publish-docker-images.yml index 7e34053352..9da92ed1b3 100644 --- a/.github/workflows/publish-docker-images.yml +++ b/.github/workflows/publish-docker-images.yml @@ -6,15 +6,21 @@ on: push: branches: - master + - staging + - publish-docker jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@master + - id: pre-step + shell: bash + run: echo "release-version=$(TZ=UTC0 git show --quiet --date='format-local:%Y%m%d%H%M%S' --format="%cd" $GITHUB_SHA)-$(git describe $GITHUB_SHA --always --tags)" >> $GITHUB_OUTPUT; echo "release-branch=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_OUTPUT - name: Publish to Dockerhub Registry uses: elgohr/Publish-Docker-Github-Action@master with: name: gobitfly/eth2-beaconchain-explorer username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} \ No newline at end of file + password: ${{ secrets.DOCKER_PASSWORD }} + tags: "${{ steps.pre-step.outputs.release-branch }},${{ steps.pre-step.outputs.release-version }}" \ No newline at end of file diff --git a/.github/workflows/staticcheck.yml b/.github/workflows/staticcheck.yml index 4320a2f607..e9d763f815 100644 --- a/.github/workflows/staticcheck.yml +++ b/.github/workflows/staticcheck.yml @@ -13,24 +13,22 @@ on: jobs: ci: name: "Run CI" - strategy: - fail-fast: false - matrix: - os: ["ubuntu-latest"] - go: ["1.21.x"] - runs-on: ${{ matrix.os }} + runs-on: "ubuntu-latest" steps: - - uses: actions/checkout@v1 + - name: Check out code into the Go module directory + uses: actions/checkout@v3 + - name: Set up Go 1.23 + uses: actions/setup-go@v4 with: - fetch-depth: 1 - - uses: WillAbides/setup-go-faster@v1.14.0 - with: - go-version: ${{ matrix.go }} + go-version-file: './go.mod' + id: go + - name: Get dependencies + run: | + go get -v -t -d ./... - run: make all - run: "go test ./..." - run: "go vet ./..." - uses: dominikh/staticcheck-action@v1.3.1 with: - version: "2023.1.7" - install-go: false - cache-key: ${{ matrix.go }} \ No newline at end of file + version: "2025.1" # see https://github.com/dominikh/go-tools/releases + install-go: false \ No newline at end of file diff --git a/.gitignore b/.gitignore index f59ca519b7..fffd008eaf 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ docker-compose-private.yml static/bundle/css static/bundle/js docs +static/swagger node_modules .vscode webassembly diff --git a/Dockerfile b/Dockerfile index 4cf1a6b039..b13ff5dfb2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # The dockerfile is currently still WIP and might be broken -FROM golang:1.21.12 AS build-env +FROM golang:1.23.5 AS build-env COPY go.mod go.sum /src/ WORKDIR /src RUN go mod download diff --git a/Makefile b/Makefile index d4a533c8a4..f23bde636d 100644 --- a/Makefile +++ b/Makefile @@ -19,8 +19,13 @@ test: explorer: rm -rf bin/ mkdir -p bin/ + echo "Creating frontend asset bundle..." go run cmd/bundle/main.go - go install github.com/swaggo/swag/cmd/swag@v1.8.3 && swag init --exclude bin,_gitignore,.vscode,.idea --parseDepth 1 -g ./handlers/api.go + echo "Generating swagger docs..." + mkdir -p static/swagger/ + go install github.com/swaggo/swag/cmd/swag@v1.8.3 + swag init --exclude bin,_gitignore,.vscode,.idea --parseDepth 1 -g ./handlers/api.go --outputTypes json --output static/swagger/ --parseDependency --parseInternal + echo "Building explorer..." CGO_CFLAGS=${CGO_CFLAGS} CGO_CFLAGS_ALLOW=${CGO_CFLAGS_ALLOW} go build --ldflags=${LDFLAGS} -o bin/explorer cmd/explorer/main.go stats: diff --git a/cmd/eth1indexer/main.go b/cmd/eth1indexer/main.go index 875890e9b8..5fccdf9369 100644 --- a/cmd/eth1indexer/main.go +++ b/cmd/eth1indexer/main.go @@ -106,6 +106,14 @@ func main() { }() } + if utils.Config.Chain.PectraWithdrawalRequestContractAddress == "" { + utils.LogFatal(nil, "missing config pectraWithdrawalRequestContractAddress, please provide via explorer config", 0) + } + + if utils.Config.Chain.PectraConsolidationRequestContractAddress == "" { + utils.LogFatal(nil, "missing config pectraConsolidationRequestContractAddress, please provide via explorer config", 0) + } + db.MustInitDB(&types.DatabaseConfig{ Username: cfg.WriterDatabase.Username, Password: cfg.WriterDatabase.Password, @@ -199,7 +207,9 @@ func main() { bt.TransformUncle, bt.TransformWithdrawals, bt.TransformEnsNameRegistered, - bt.TransformContract) + bt.TransformContract, + bt.TransformConsolidationRequests, + bt.TransformWithdrawalRequests) cache := freecache.NewCache(100 * 1024 * 1024) // 100 MB limit diff --git a/cmd/explorer/main.go b/cmd/explorer/main.go index 24fa87d4af..72d5d3ca6a 100644 --- a/cmd/explorer/main.go +++ b/cmd/explorer/main.go @@ -7,6 +7,7 @@ import ( "errors" "flag" "fmt" + "log" "math/big" "net/http" "strings" @@ -28,14 +29,10 @@ import ( "github.com/gobitfly/eth2-beaconchain-explorer/utils" "github.com/gobitfly/eth2-beaconchain-explorer/version" - httpSwagger "github.com/swaggo/http-swagger" - "github.com/sirupsen/logrus" _ "net/http/pprof" - _ "github.com/gobitfly/eth2-beaconchain-explorer/docs" - "github.com/gorilla/csrf" "github.com/gorilla/mux" _ "github.com/jackc/pgx/v5/stdlib" @@ -57,6 +54,7 @@ func initStripe(http *mux.Router) error { func init() { gob.Register(types.DataTableSaveState{}) + gob.Register(types.UserV1Notification(0)) } var frontendHttpServer *http.Server @@ -71,6 +69,7 @@ func main() { fmt.Println(version.GoVersion) return } + log.SetFlags(log.LstdFlags | log.Lshortfile) cfg := &types.Config{} err := utils.ReadConfig(cfg, *configPath) @@ -275,7 +274,7 @@ func main() { router := mux.NewRouter() apiV1Router := router.PathPrefix("/api/v1").Subrouter() - router.PathPrefix("/api/v1/docs/").Handler(httpSwagger.WrapHandler) + apiV1Router.HandleFunc("/docs", handlers.ApiDocs).Methods("GET") apiV1Router.HandleFunc("/latestState", handlers.ApiLatestState).Methods("GET", "OPTIONS") apiV1Router.HandleFunc("/epoch/{epoch}", handlers.ApiEpoch).Methods("GET", "OPTIONS") @@ -288,6 +287,9 @@ func main() { apiV1Router.HandleFunc("/slot/{slot}/proposerslashings", handlers.ApiSlotProposerSlashings).Methods("GET", "OPTIONS") apiV1Router.HandleFunc("/slot/{slot}/voluntaryexits", handlers.ApiSlotVoluntaryExits).Methods("GET", "OPTIONS") apiV1Router.HandleFunc("/slot/{slot}/withdrawals", handlers.ApiSlotWithdrawals).Methods("GET", "OPTIONS") + apiV1Router.HandleFunc("/slot/{slot}/consolidation_requests", handlers.ApiSlotConsolidationRequests).Methods("GET", "OPTIONS") + apiV1Router.HandleFunc("/slot/{slot}/switch_to_compounding_requests", handlers.ApiSlotSwitchToCompoundingRequests).Methods("GET", "OPTIONS") + apiV1Router.HandleFunc("/slot/{slot}/deposit_requests", handlers.ApiSlotDepositRequests).Methods("GET", "OPTIONS") // deprecated, use slot equivalents apiV1Router.HandleFunc("/block/{slotOrHash}", handlers.ApiSlots).Methods("GET", "OPTIONS") @@ -296,6 +298,9 @@ func main() { apiV1Router.HandleFunc("/block/{slot}/attesterslashings", handlers.ApiSlotAttesterSlashings).Methods("GET", "OPTIONS") apiV1Router.HandleFunc("/block/{slot}/proposerslashings", handlers.ApiSlotProposerSlashings).Methods("GET", "OPTIONS") apiV1Router.HandleFunc("/block/{slot}/voluntaryexits", handlers.ApiSlotVoluntaryExits).Methods("GET", "OPTIONS") + apiV1Router.HandleFunc("/block/{slot}/consolidation_requests", handlers.ApiSlotConsolidationRequests).Methods("GET", "OPTIONS") + apiV1Router.HandleFunc("/block/{slot}/switch_to_compounding_requests", handlers.ApiSlotSwitchToCompoundingRequests).Methods("GET", "OPTIONS") + apiV1Router.HandleFunc("/block/{slot}/deposit_requests", handlers.ApiSlotDepositRequests).Methods("GET", "OPTIONS") apiV1Router.HandleFunc("/sync_committee/{period}", handlers.ApiSyncCommittee).Methods("GET", "OPTIONS") apiV1Router.HandleFunc("/eth1deposit/{txhash}", handlers.ApiEth1Deposit).Methods("GET", "OPTIONS") @@ -310,6 +315,8 @@ func main() { apiV1Router.HandleFunc("/validator/{indexOrPubkey}/execution/performance", handlers.ApiValidatorExecutionPerformance).Methods("GET", "OPTIONS") apiV1Router.HandleFunc("/validator/{indexOrPubkey}/attestations", handlers.ApiValidatorAttestations).Methods("GET", "OPTIONS") apiV1Router.HandleFunc("/validator/{indexOrPubkey}/proposals", handlers.ApiValidatorProposals).Methods("GET", "OPTIONS") + apiV1Router.HandleFunc("/validator/{indexOrPubkey}/consolidation_requests", handlers.ApiValidatorConsolidationRequests).Methods("GET", "OPTIONS") + apiV1Router.HandleFunc("/validator/{indexOrPubkey}/switch_to_compounding_requests", handlers.ApiValidatorSwitchToCompoundingRequests).Methods("GET", "OPTIONS") apiV1Router.HandleFunc("/validator/{indexOrPubkey}/deposits", handlers.ApiValidatorDeposits).Methods("GET", "OPTIONS") apiV1Router.HandleFunc("/validator/{indexOrPubkey}/attestationefficiency", handlers.ApiValidatorAttestationEfficiency).Methods("GET", "OPTIONS") apiV1Router.HandleFunc("/validator/{indexOrPubkey}/attestationeffectiveness", handlers.ApiValidatorAttestationEffectiveness).Methods("GET", "OPTIONS") @@ -322,8 +329,6 @@ func main() { apiV1Router.HandleFunc("/chart/{chart}", handlers.ApiChart).Methods("GET", "OPTIONS") apiV1Router.HandleFunc("/user/token", handlers.APIGetToken).Methods("POST", "OPTIONS") apiV1Router.HandleFunc("/dashboard/data/allbalances", handlers.DashboardDataBalanceCombined).Methods("GET", "OPTIONS") // consensus & execution - apiV1Router.HandleFunc("/dashboard/data/balances", handlers.DashboardDataBalance).Methods("GET", "OPTIONS") // new app versions - apiV1Router.HandleFunc("/dashboard/data/balance", handlers.APIDashboardDataBalance).Methods("GET", "OPTIONS") // old app versions apiV1Router.HandleFunc("/dashboard/data/proposals", handlers.DashboardDataProposals).Methods("GET", "OPTIONS") apiV1Router.HandleFunc("/stripe/webhook", handlers.StripeWebhook).Methods("POST") apiV1Router.HandleFunc("/stats/{apiKey}/{machine}", handlers.ClientStatsPostOld).Methods("POST", "OPTIONS") @@ -472,7 +477,6 @@ func main() { router.HandleFunc("/validator/{pubkey}/name", handlers.SaveValidatorName).Methods("POST") router.HandleFunc("/watchlist/add", handlers.UsersModalAddValidator).Methods("POST") router.HandleFunc("/validator/{pubkey}/remove", handlers.UserValidatorWatchlistRemove).Methods("POST") - router.HandleFunc("/validator/{index}/stats", handlers.ValidatorStatsTable).Methods("GET") router.HandleFunc("/validators", handlers.Validators).Methods("GET") router.HandleFunc("/validators/data", handlers.ValidatorsData).Methods("GET") router.HandleFunc("/validators/slashings", handlers.ValidatorsSlashings).Methods("GET") @@ -490,18 +494,10 @@ func main() { router.HandleFunc("/validators/included-deposits", handlers.Eth2Deposits).Methods("GET") // deprecated, will redirect to /validators/deposits router.HandleFunc("/validators/included-deposits/data", handlers.Eth2DepositsData).Methods("GET") - router.HandleFunc("/heatmap", handlers.Heatmap).Methods("GET") - - router.HandleFunc("/dashboard", handlers.Dashboard).Methods("GET") router.HandleFunc("/dashboard/save", handlers.UserDashboardWatchlistAdd).Methods("POST") router.HandleFunc("/dashboard/data/allbalances", handlers.DashboardDataBalanceCombined).Methods("GET") - router.HandleFunc("/dashboard/data/proposals", handlers.DashboardDataProposals).Methods("GET") - router.HandleFunc("/dashboard/data/proposalshistory", handlers.DashboardDataProposalsHistory).Methods("GET") - router.HandleFunc("/dashboard/data/validators", handlers.DashboardDataValidators).Methods("GET") - router.HandleFunc("/dashboard/data/withdrawal", handlers.DashboardDataWithdrawals).Methods("GET") - router.HandleFunc("/dashboard/data/effectiveness", handlers.DashboardDataEffectiveness).Methods("GET") - router.HandleFunc("/dashboard/data/earnings", handlers.DashboardDataEarnings).Methods("GET") + router.HandleFunc("/graffitiwall", handlers.Graffitiwall).Methods("GET") router.HandleFunc("/calculator", handlers.StakingCalculator).Methods("GET") router.HandleFunc("/search", handlers.Search).Methods("POST") @@ -536,9 +532,6 @@ func main() { // confirming the email update should not require auth router.HandleFunc("/settings/email/{hash}", handlers.UserConfirmUpdateEmail).Methods("GET") router.HandleFunc("/gitcoinfeed", handlers.GitcoinFeed).Methods("GET") - router.HandleFunc("/rewards", handlers.ValidatorRewards).Methods("GET") - router.HandleFunc("/rewards/hist", handlers.RewardsHistoricalData).Methods("GET") - router.HandleFunc("/rewards/hist/download", handlers.DownloadRewardsHistoricalData).Methods("GET") router.HandleFunc("/notifications/unsubscribe", handlers.UserNotificationsUnsubscribeByHash).Methods("GET") @@ -600,10 +593,6 @@ func main() { authRouter.HandleFunc("/subscriptions/data", handlers.UserSubscriptionsData).Methods("GET") authRouter.HandleFunc("/generateKey", handlers.GenerateAPIKey).Methods("POST") authRouter.HandleFunc("/ethClients", handlers.EthClientsServices).Methods("GET") - authRouter.HandleFunc("/rewards", handlers.ValidatorRewards).Methods("GET") - authRouter.HandleFunc("/rewards/subscribe", handlers.RewardNotificationSubscribe).Methods("POST") - authRouter.HandleFunc("/rewards/unsubscribe", handlers.RewardNotificationUnsubscribe).Methods("POST") - authRouter.HandleFunc("/rewards/subscriptions/data", handlers.RewardGetUserSubscriptions).Methods("POST") authRouter.HandleFunc("/webhooks", handlers.NotificationWebhookPage).Methods("GET") authRouter.HandleFunc("/webhooks/add", handlers.UsersAddWebhook).Methods("POST") authRouter.HandleFunc("/webhooks/{webhookID}/update", handlers.UsersEditWebhook).Methods("POST") diff --git a/cmd/misc/main.go b/cmd/misc/main.go index fff12ecb58..007ed557cd 100644 --- a/cmd/misc/main.go +++ b/cmd/misc/main.go @@ -17,6 +17,7 @@ import ( "time" "firebase.google.com/go/v4/messaging" + "go.uber.org/atomic" "github.com/gobitfly/eth2-beaconchain-explorer/cmd/misc/commands" "github.com/gobitfly/eth2-beaconchain-explorer/db" @@ -124,6 +125,14 @@ func main() { wg := &sync.WaitGroup{} wg.Add(5) + if utils.Config.Chain.PectraWithdrawalRequestContractAddress == "" { + utils.LogFatal(nil, "missing config pectraWithdrawalRequestContractAddress, please provide via explorer config", 0) + } + + if utils.Config.Chain.PectraConsolidationRequestContractAddress == "" { + utils.LogFatal(nil, "missing config pectraConsolidationRequestContractAddress, please provide via explorer config", 0) + } + go func() { defer wg.Done() var err error @@ -594,10 +603,13 @@ func fixInternalTxsFromNode(startBlock, endBlock, batchSize, concurrency uint64, func fixEns(erigonClient *rpc.ErigonClient) error { logrus.WithField("dry", opts.DryRun).Infof("command: fix-ens") addrs := []struct { - Address []byte `db:"address"` - EnsName string `db:"ens_name"` + Address []byte `db:"address"` + EnsName string `db:"ens_name"` + NameHash []byte `db:"name_hash"` + IsPrimaryName bool `db:"is_primary_name"` + ValidTo time.Time `db:"valid_to"` }{} - err := db.WriterDb.Select(&addrs, `select address, ens_name from ens where is_primary_name = true`) + err := db.WriterDb.Select(&addrs, `select ens_name, name_hash, address, is_primary_name, valid_to from ens`) if err != nil { return err } @@ -620,6 +632,22 @@ func fixEns(erigonClient *rpc.ErigonClient) error { for _, addr := range batch { addr := addr g.Go(func() error { + + logFields := logrus.Fields{ + "db.addr": fmt.Sprintf("%#x", addr.Address), + "db.name": addr.EnsName, + "db.hash": fmt.Sprintf("%#x", addr.NameHash), + "db.valid_to": addr.ValidTo, + } + + deleteEntry := false + deleteEntryReasons := []string{} + + normalizedName, err := go_ens.Normalize(addr.EnsName) + if err != nil { + deleteEntry = true + deleteEntryReasons = append(deleteEntryReasons, fmt.Sprintf("failed normalize: %v", err.Error())) + } ensAddr, err := go_ens.Resolve(erigonClient.GetNativeClient(), addr.EnsName) if err != nil { if err.Error() == "unregistered name" || @@ -628,51 +656,63 @@ func fixEns(erigonClient *rpc.ErigonClient) error { err.Error() == "abi: attempting to unmarshall an empty string while arguments are expected" || strings.Contains(err.Error(), "execution reverted") || err.Error() == "invalid jump destination" { - logrus.WithFields(logrus.Fields{"addr": fmt.Sprintf("%#x", addr.Address), "name": addr.EnsName, "reason": fmt.Sprintf("failed resolve: %v", err.Error())}).Warnf("deleting ens entry") - if !opts.DryRun { - _, err = db.WriterDb.Exec(`delete from ens where address = $1 and ens_name = $2`, addr.Address, addr.EnsName) - if err != nil { - return err - } - } - return nil + deleteEntry = true + deleteEntryReasons = append(deleteEntryReasons, fmt.Sprintf("failed resolve: %v", err.Error())) } - return err } dbAddr := common.BytesToAddress(addr.Address) if dbAddr.Cmp(ensAddr) != 0 { - logrus.WithFields(logrus.Fields{"addr": fmt.Sprintf("%#x", addr.Address), "name": addr.EnsName, "reason": fmt.Sprintf("dbAddr != resolved ensAddr: %#x != %#x", addr.Address, ensAddr.Bytes())}).Warnf("deleting ens entry") - if !opts.DryRun { - _, err = db.WriterDb.Exec(`delete from ens where address = $1 and ens_name = $2`, addr.Address, addr.EnsName) - if err != nil { - return err - } - } + deleteEntry = true + deleteEntryReasons = append(deleteEntryReasons, fmt.Sprintf("dbAddr != resolved ensAddr: %#x != %#x", addr.Address, ensAddr.Bytes())) } + isPrimaryName := false reverseName, err := go_ens.ReverseResolve(erigonClient.GetNativeClient(), dbAddr) if err != nil { if err.Error() == "not a resolver" || err.Error() == "no resolution" { - logrus.WithFields(logrus.Fields{"addr": dbAddr, "name": addr.EnsName, "reason": fmt.Sprintf("failed reverse-resolve: %v", err.Error())}).Warnf("updating ens entry: is_primary_name = false") - if !opts.DryRun { - _, err = db.WriterDb.Exec(`update ens set is_primary_name = false where address = $1 and ens_name = $2`, addr.Address, addr.EnsName) - if err != nil { - return err - } - } - return nil + isPrimaryName = false } - return err } + logFields["reverseName"] = reverseName - if reverseName != addr.EnsName { - logrus.WithFields(logrus.Fields{"addr": fmt.Sprintf("%#x", addr.Address), "name": addr.EnsName, "reason": fmt.Sprintf("resolved != reverseResolved: %v != %v", addr.EnsName, reverseName)}).Warnf("updating ens entry: is_primary_name = false") + if reverseName == addr.EnsName { + isPrimaryName = true + } + + if deleteEntry { + logFields["delReasons"] = strings.Join(deleteEntryReasons, ", ") if !opts.DryRun { - _, err = db.WriterDb.Exec(`update ens set is_primary_name = false where address = $1 and ens_name = $2`, addr.Address, addr.EnsName) + logrus.WithFields(logFields).Warnf("deleting ens entry") + _, err = db.WriterDb.Exec(`delete from ens where name_hash = $1`, addr.NameHash) if err != nil { return err } + } else { + logrus.WithFields(logFields).Warnf("WOULD deleting ens entry") + } + } else if normalizedName != addr.EnsName || isPrimaryName != addr.IsPrimaryName { + updateEntry := false + updateEntryReasons := []string{} + if normalizedName != addr.EnsName { + updateEntryReasons = append(updateEntryReasons, fmt.Sprintf("normalizedName != addr.EnsName: %v != %v", normalizedName, addr.EnsName)) + updateEntry = true + } + if isPrimaryName != addr.IsPrimaryName { + updateEntryReasons = append(updateEntryReasons, fmt.Sprintf("isPrimaryName != addr.IsPrimaryName: %v != %v", isPrimaryName, addr.IsPrimaryName)) + updateEntry = true + } + if updateEntry { + logFields["updateReasons"] = strings.Join(updateEntryReasons, ", ") + if !opts.DryRun { + logrus.WithFields(logFields).Infof("updating ens entry") + _, err = db.WriterDb.Exec(`update ens set ens_name = $1, is_primary_name = $2 where name_hash = $3`, normalizedName, isPrimaryName, addr.NameHash) + if err != nil { + return err + } + } else { + logrus.WithFields(logFields).Infof("WOULD updating ens entry") + } } } @@ -891,7 +931,7 @@ func migrateAppPurchases(appStoreSecret string) error { return errors.Wrap(err, "error verifying receipt") } - if resp.LatestReceiptInfo == nil || len(resp.LatestReceiptInfo) == 0 { + if len(resp.LatestReceiptInfo) == 0 { logrus.Infof("no receipt info for purchase id %v", receipt.ID) if receipt.Active && receipt.ValidateRemotely { // sanity, if there is an active subscription without receipt info we cam't delete it. return fmt.Errorf("no receipt info for active purchase id %v", receipt.ID) @@ -1682,11 +1722,34 @@ func reIndexBlocks(start uint64, end uint64, bt *db.Bigtable, client *rpc.Erigon } }) + type Report struct { + Time time.Time + Slot int64 + } + currSlot := atomic.NewInt64(int64(start)) + lastReport := atomic.NewPointer(&Report{Time: time.Now(), Slot: currSlot.Load()}) + + go func() { + t := time.NewTicker(10 * time.Second) + for { + newReport := &Report{Time: time.Now(), Slot: currSlot.Load()} + oldReport := lastReport.Swap(newReport) + blocksPerSecond := float64(newReport.Slot-oldReport.Slot) / newReport.Time.Sub(oldReport.Time).Seconds() + logrus.Infof("indexed %d blocks in %.2fs (%.2f b/s, curr_block: %d, last_block: %d, blocks_left: %d, est_time_left: %s)", newReport.Slot-oldReport.Slot, newReport.Time.Sub(oldReport.Time).Seconds(), blocksPerSecond, newReport.Slot, end, int64(end)-newReport.Slot, time.Duration(float64(int64(end)-newReport.Slot)/blocksPerSecond)*time.Second) + select { + case <-t.C: + case <-quit: + return + } + } + }() + var errs []error var mu sync.Mutex for i := start; i <= end; i = i + batchSize { height := int64(i) readGroup.Go(func() error { + currSlot.Swap(height) heightEnd := height + int64(batchSize) - 1 if heightEnd > int64(end) { heightEnd = int64(end) @@ -1728,7 +1791,7 @@ func getTransformers(transformerFlag string, bt *db.Bigtable) ([]db.TransformFun logrus.Infof("transformerFlag: %v", transformerFlag) transformerList := strings.Split(transformerFlag, ",") if transformerFlag == "all" { - transformerList = []string{"TransformBlock", "TransformTx", "TransformBlobTx", "TransformItx", "TransformERC20", "TransformERC721", "TransformERC1155", "TransformWithdrawals", "TransformUncle", "TransformEnsNameRegistered", "TransformContract"} + transformerList = []string{"TransformBlock", "TransformTx", "TransformBlobTx", "TransformItx", "TransformERC20", "TransformERC721", "TransformERC1155", "TransformWithdrawals", "TransformUncle", "TransformEnsNameRegistered", "TransformContract", "TransformConsolidationRequests", "TransformWithdrawalRequests"} } else if len(transformerList) == 0 { utils.LogError(nil, "no transformer functions provided", 0) return nil, false, fmt.Errorf("no transformer functions provided") @@ -1761,6 +1824,10 @@ func getTransformers(transformerFlag string, bt *db.Bigtable) ([]db.TransformFun importENSChanges = true case "TransformContract": transforms = append(transforms, bt.TransformContract) + case "TransformConsolidationRequests": + transforms = append(transforms, bt.TransformConsolidationRequests) + case "TransformWithdrawalRequests": + transforms = append(transforms, bt.TransformWithdrawalRequests) default: return nil, false, fmt.Errorf("invalid transformer flag %v", t) } diff --git a/cmd/notification-collector/main.go b/cmd/notification-collector/main.go index c30e1bfc20..07aa702298 100644 --- a/cmd/notification-collector/main.go +++ b/cmd/notification-collector/main.go @@ -19,8 +19,6 @@ import ( _ "net/http/pprof" - _ "github.com/gobitfly/eth2-beaconchain-explorer/docs" - _ "github.com/jackc/pgx/v5/stdlib" ) diff --git a/cmd/notification-sender/main.go b/cmd/notification-sender/main.go index f00b492caa..2b838d68a5 100644 --- a/cmd/notification-sender/main.go +++ b/cmd/notification-sender/main.go @@ -18,8 +18,6 @@ import ( _ "net/http/pprof" - _ "github.com/gobitfly/eth2-beaconchain-explorer/docs" - _ "github.com/jackc/pgx/v5/stdlib" ) diff --git a/cmd/user-service/main.go b/cmd/user-service/main.go index d3e1ae43ff..063bf16ec7 100644 --- a/cmd/user-service/main.go +++ b/cmd/user-service/main.go @@ -17,8 +17,6 @@ import ( _ "net/http/pprof" - _ "github.com/gobitfly/eth2-beaconchain-explorer/docs" - _ "github.com/jackc/pgx/v5/stdlib" ) diff --git a/config/config.go b/config/config.go index 5512664bc3..b6b3f92e09 100644 --- a/config/config.go +++ b/config/config.go @@ -25,3 +25,15 @@ var GnosisChainYml string //go:embed holesky.chain.yml var HoleskyChainYml string + +//go:embed hoodi.chain.yml +var HoodiChainYml string + +//go:embed mekong.chain.yml +var MekongChainYml string + +//go:embed pectra-devnet-5.chain.yml +var PectraDevnet5ChainYml string + +//go:embed pectra-devnet-6.chain.yml +var PectraDevnet6ChainYml string diff --git a/config/hoodi.chain.yml b/config/hoodi.chain.yml new file mode 100644 index 0000000000..5cca1cd037 --- /dev/null +++ b/config/hoodi.chain.yml @@ -0,0 +1,163 @@ +# Extends the mainnet preset +PRESET_BASE: mainnet +CONFIG_NAME: hoodi + +# Genesis +# --------------------------------------------------------------- +# `2**14` (= 16,384) +MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 16384 +# 2025-Mar-17 12:00:00 PM UTC +MIN_GENESIS_TIME: 1742212800 +GENESIS_FORK_VERSION: 0x10000910 +GENESIS_DELAY: 600 + + +# Forking +# --------------------------------------------------------------- +# Some forks are disabled for now: +# - These may be re-assigned to another fork-version later +# - Temporarily set to max uint64 value: 2**64 - 1 + +# Altair +ALTAIR_FORK_VERSION: 0x20000910 +ALTAIR_FORK_EPOCH: 0 +# Merge +BELLATRIX_FORK_VERSION: 0x30000910 +BELLATRIX_FORK_EPOCH: 0 +TERMINAL_TOTAL_DIFFICULTY: 0 +TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000 +TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615 + +# Capella +CAPELLA_FORK_VERSION: 0x40000910 +CAPELLA_FORK_EPOCH: 0 + +# DENEB +DENEB_FORK_VERSION: 0x50000910 +DENEB_FORK_EPOCH: 0 + +# Electra +ELECTRA_FORK_VERSION: 0x60000910 +ELECTRA_FORK_EPOCH: 2048 + +# Fulu +FULU_FORK_VERSION: 0x70000910 +FULU_FORK_EPOCH: 18446744073709551615 + + +# Time parameters +# --------------------------------------------------------------- +# 12 seconds +SECONDS_PER_SLOT: 12 +# 14 (estimate from Eth1 mainnet) +SECONDS_PER_ETH1_BLOCK: 12 +# 2**8 (= 256) epochs ~27 hours +MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256 +# 2**8 (= 256) epochs ~27 hours +SHARD_COMMITTEE_PERIOD: 256 +# 2**11 (= 2,048) Eth1 blocks ~8 hours +ETH1_FOLLOW_DISTANCE: 2048 + +# Validator cycle +# --------------------------------------------------------------- +# 2**2 (= 4) +INACTIVITY_SCORE_BIAS: 4 +# 2**4 (= 16) +INACTIVITY_SCORE_RECOVERY_RATE: 16 +# 2**4 * 10**9 (= 16,000,000,000) Gwei +EJECTION_BALANCE: 16000000000 +# 2**2 (= 4) +MIN_PER_EPOCH_CHURN_LIMIT: 4 +# 2**16 (= 65,536) +CHURN_LIMIT_QUOTIENT: 65536 +# [New in Deneb:EIP7514] 2**3 (= 8) +MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8 + +# Fork choice +# --------------------------------------------------------------- +# 40% +PROPOSER_SCORE_BOOST: 40 +# 20% +REORG_HEAD_WEIGHT_THRESHOLD: 20 +# 160% +REORG_PARENT_WEIGHT_THRESHOLD: 160 +# `2` epochs +REORG_MAX_EPOCHS_SINCE_FINALIZATION: 2 + +# Deposit contract +# --------------------------------------------------------------- +DEPOSIT_CHAIN_ID: 560048 +DEPOSIT_NETWORK_ID: 560048 +DEPOSIT_CONTRACT_ADDRESS: 0x00000000219ab540356cBB839Cbe05303d7705Fa + +# Networking +# --------------------------------------------------------------- +# `10 * 2**20` (= 10485760, 10 MiB) +MAX_PAYLOAD_SIZE: 10485760 +# `2**10` (= 1024) +MAX_REQUEST_BLOCKS: 1024 +# `2**8` (= 256) +EPOCHS_PER_SUBNET_SUBSCRIPTION: 256 +# `MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2` (= 33024, ~5 months) +MIN_EPOCHS_FOR_BLOCK_REQUESTS: 33024 +# 5s +TTFB_TIMEOUT: 5 +# 10s +RESP_TIMEOUT: 10 +ATTESTATION_PROPAGATION_SLOT_RANGE: 32 +# 500ms +MAXIMUM_GOSSIP_CLOCK_DISPARITY: 500 +MESSAGE_DOMAIN_INVALID_SNAPPY: 0x00000000 +MESSAGE_DOMAIN_VALID_SNAPPY: 0x01000000 +# 2 subnets per node +SUBNETS_PER_NODE: 2 +# 2**8 (= 64) +ATTESTATION_SUBNET_COUNT: 64 +ATTESTATION_SUBNET_EXTRA_BITS: 0 +# ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS +ATTESTATION_SUBNET_PREFIX_BITS: 6 + +# Deneb +# `2**7` (=128) +MAX_REQUEST_BLOCKS_DENEB: 128 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK +MAX_REQUEST_BLOB_SIDECARS: 768 +# `2**12` (= 4096 epochs, ~18 days) +MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 +# `6` +BLOB_SIDECAR_SUBNET_COUNT: 6 +## `uint64(6)` +MAX_BLOBS_PER_BLOCK: 6 + +# Electra +# 2**7 * 10**9 (= 128,000,000,000) +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 +# 2**8 * 10**9 (= 256,000,000,000) +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 +# `9` +BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9 +# `uint64(6)` +TARGET_BLOBS_PER_BLOCK_ELECTRA: 6 +# `uint64(9)` +MAX_BLOBS_PER_BLOCK_ELECTRA: 9 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA +MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 1152 + +# Whisk +# `Epoch(2**8)` +WHISK_EPOCHS_PER_SHUFFLING_PHASE: 256 +# `Epoch(2)` +WHISK_PROPOSER_SELECTION_GAP: 2 + +# Fulu +NUMBER_OF_COLUMNS: 128 +NUMBER_OF_CUSTODY_GROUPS: 128 +DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128 +MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 +SAMPLES_PER_SLOT: 8 +CUSTODY_REQUIREMENT: 4 +MAX_BLOBS_PER_BLOCK_FULU: 12 +MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 + +# EIP7732 +MAX_REQUEST_PAYLOADS: 128 diff --git a/config/mekong.chain.yml b/config/mekong.chain.yml new file mode 100644 index 0000000000..eed63477c0 --- /dev/null +++ b/config/mekong.chain.yml @@ -0,0 +1,153 @@ +# Extends the mainnet preset +PRESET_BASE: mainnet +CONFIG_NAME: mekong # needs to exist because of Prysm. Otherwise it conflicts with mainnet genesis + +# Genesis +# --------------------------------------------------------------- +# `2**14` (= 16,384) +MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 100000 +# 2024-Nov-05 03:59:00 PM UTC +MIN_GENESIS_TIME: 1730822340 +GENESIS_FORK_VERSION: 0x10637624 +GENESIS_DELAY: 60 + + +# Forking +# --------------------------------------------------------------- +# Some forks are disabled for now: +# - These may be re-assigned to another fork-version later +# - Temporarily set to max uint64 value: 2**64 - 1 + +# Altair +ALTAIR_FORK_VERSION: 0x20637624 +ALTAIR_FORK_EPOCH: 0 +# Merge +BELLATRIX_FORK_VERSION: 0x30637624 +BELLATRIX_FORK_EPOCH: 0 +TERMINAL_TOTAL_DIFFICULTY: 0 +TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000 +TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615 + +# Capella +CAPELLA_FORK_VERSION: 0x40637624 +CAPELLA_FORK_EPOCH: 0 + +# DENEB +DENEB_FORK_VERSION: 0x50637624 +DENEB_FORK_EPOCH: 0 + +# Electra +ELECTRA_FORK_VERSION: 0x60637624 +ELECTRA_FORK_EPOCH: 256 + +# Fulu +FULU_FORK_VERSION: 0x70000000 +FULU_FORK_EPOCH: 99999 + +# EIP7594 - Peerdas +EIP7594_FORK_VERSION: 0x70000000 +EIP7594_FORK_EPOCH: 99999 + +# Time parameters +# --------------------------------------------------------------- +# 12 seconds +SECONDS_PER_SLOT: 12 +# 14 (estimate from Eth1 mainnet) +SECONDS_PER_ETH1_BLOCK: 12 +# 2**8 (= 256) epochs ~27 hours +MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 2 +# 2**8 (= 256) epochs ~27 hours +SHARD_COMMITTEE_PERIOD: 256 +# 2**11 (= 2,048) Eth1 blocks ~8 hours +ETH1_FOLLOW_DISTANCE: 2048 + +# Validator cycle +# --------------------------------------------------------------- +# 2**2 (= 4) +INACTIVITY_SCORE_BIAS: 4 +# 2**4 (= 16) +INACTIVITY_SCORE_RECOVERY_RATE: 16 +# 2**4 * 10**9 (= 16,000,000,000) Gwei +EJECTION_BALANCE: 30000000000 +# 2**2 (= 4) +MIN_PER_EPOCH_CHURN_LIMIT: 4 +# 2**16 (= 65,536) +CHURN_LIMIT_QUOTIENT: 128 +# [New in Deneb:EIP7514] 2**3 (= 8) +MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8 + +# Fork choice +# --------------------------------------------------------------- +# 40% +PROPOSER_SCORE_BOOST: 40 +# 20% +REORG_HEAD_WEIGHT_THRESHOLD: 20 +# 160% +REORG_PARENT_WEIGHT_THRESHOLD: 160 +# `2` epochs +REORG_MAX_EPOCHS_SINCE_FINALIZATION: 2 + +# Deposit contract +# --------------------------------------------------------------- +DEPOSIT_CHAIN_ID: 7078815900 +DEPOSIT_NETWORK_ID: 7078815900 +DEPOSIT_CONTRACT_ADDRESS: 0x4242424242424242424242424242424242424242 + +# Networking +# --------------------------------------------------------------- +# `10 * 2**20` (= 10485760, 10 MiB) +GOSSIP_MAX_SIZE: 10485760 +# `2**10` (= 1024) +MAX_REQUEST_BLOCKS: 1024 +# `2**8` (= 256) +EPOCHS_PER_SUBNET_SUBSCRIPTION: 256 +# `MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2` (= 33024, ~5 months) +MIN_EPOCHS_FOR_BLOCK_REQUESTS: 33024 +# `10 * 2**20` (=10485760, 10 MiB) +MAX_CHUNK_SIZE: 10485760 +# 5s +TTFB_TIMEOUT: 5 +# 10s +RESP_TIMEOUT: 10 +ATTESTATION_PROPAGATION_SLOT_RANGE: 32 +# 500ms +MAXIMUM_GOSSIP_CLOCK_DISPARITY: 500 +MESSAGE_DOMAIN_INVALID_SNAPPY: 0x00000000 +MESSAGE_DOMAIN_VALID_SNAPPY: 0x01000000 +# 2 subnets per node +SUBNETS_PER_NODE: 2 +# 2**8 (= 64) +ATTESTATION_SUBNET_COUNT: 64 +ATTESTATION_SUBNET_EXTRA_BITS: 0 +# ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS +ATTESTATION_SUBNET_PREFIX_BITS: 6 + +# Deneb +# `2**7` (=128) +MAX_REQUEST_BLOCKS_DENEB: 128 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK +MAX_REQUEST_BLOB_SIDECARS: 768 +# `2**12` (= 4096 epochs, ~18 days) +MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 +# `6` +BLOB_SIDECAR_SUBNET_COUNT: 6 +## `uint64(6)` +MAX_BLOBS_PER_BLOCK: 6 + +# Whisk +# `Epoch(2**8)` +WHISK_EPOCHS_PER_SHUFFLING_PHASE: 256 +# `Epoch(2)` +WHISK_PROPOSER_SELECTION_GAP: 2 + +# EIP7594 +NUMBER_OF_COLUMNS: 128 +MAX_CELLS_IN_EXTENDED_MATRIX: 768 +DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128 +MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 +SAMPLES_PER_SLOT: 8 +CUSTODY_REQUIREMENT: 4 + +# [New in Electra:EIP7251] +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 # 2**7 * 10**9 (= 128,000,000,000) +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 # 2**8 * 10**9 (= 256,000,000,000) \ No newline at end of file diff --git a/config/pectra-devnet-5.chain.yml b/config/pectra-devnet-5.chain.yml new file mode 100644 index 0000000000..d5b6b1ca20 --- /dev/null +++ b/config/pectra-devnet-5.chain.yml @@ -0,0 +1,164 @@ +# Extends the mainnet preset +PRESET_BASE: mainnet +CONFIG_NAME: testnet # needs to exist because of Prysm. Otherwise it conflicts with mainnet genesis + +# Genesis +# --------------------------------------------------------------- +# `2**14` (= 16,384) +MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 11300 +# 2025-Jan-16 01:30:00 PM UTC +MIN_GENESIS_TIME: 1737034200 +GENESIS_FORK_VERSION: 0x10710240 +GENESIS_DELAY: 60 + + +# Forking +# --------------------------------------------------------------- +# Some forks are disabled for now: +# - These may be re-assigned to another fork-version later +# - Temporarily set to max uint64 value: 2**64 - 1 + +# Altair +ALTAIR_FORK_VERSION: 0x20710240 +ALTAIR_FORK_EPOCH: 0 +# Merge +BELLATRIX_FORK_VERSION: 0x30710240 +BELLATRIX_FORK_EPOCH: 0 +TERMINAL_TOTAL_DIFFICULTY: 0 +TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000 +TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615 + +# Capella +CAPELLA_FORK_VERSION: 0x40710240 +CAPELLA_FORK_EPOCH: 0 + +# DENEB +DENEB_FORK_VERSION: 0x50710240 +DENEB_FORK_EPOCH: 0 + +# Electra +ELECTRA_FORK_VERSION: 0x60710240 +ELECTRA_FORK_EPOCH: 4 + +# Fulu +FULU_FORK_VERSION: 0x70710240 +FULU_FORK_EPOCH: 999999 + + +# Time parameters +# --------------------------------------------------------------- +# 12 seconds +SECONDS_PER_SLOT: 12 +# 14 (estimate from Eth1 mainnet) +SECONDS_PER_ETH1_BLOCK: 12 +# 2**8 (= 256) epochs ~27 hours +MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 2 +# 2**8 (= 256) epochs ~27 hours +SHARD_COMMITTEE_PERIOD: 256 +# 2**11 (= 2,048) Eth1 blocks ~8 hours +ETH1_FOLLOW_DISTANCE: 2048 + +# Validator cycle +# --------------------------------------------------------------- +# 2**2 (= 4) +INACTIVITY_SCORE_BIAS: 4 +# 2**4 (= 16) +INACTIVITY_SCORE_RECOVERY_RATE: 16 +# 2**4 * 10**9 (= 16,000,000,000) Gwei +EJECTION_BALANCE: 16000000000 +# 2**2 (= 4) +MIN_PER_EPOCH_CHURN_LIMIT: 4 +# 2**16 (= 65,536) +CHURN_LIMIT_QUOTIENT: 128 +# [New in Deneb:EIP7514] 2**3 (= 8) +MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8 + +# Fork choice +# --------------------------------------------------------------- +# 40% +PROPOSER_SCORE_BOOST: 40 +# 20% +REORG_HEAD_WEIGHT_THRESHOLD: 20 +# 160% +REORG_PARENT_WEIGHT_THRESHOLD: 160 +# `2` epochs +REORG_MAX_EPOCHS_SINCE_FINALIZATION: 2 + +# Deposit contract +# --------------------------------------------------------------- +DEPOSIT_CHAIN_ID: 7088110746 +DEPOSIT_NETWORK_ID: 7088110746 +DEPOSIT_CONTRACT_ADDRESS: 0x4242424242424242424242424242424242424242 + +# Networking +# --------------------------------------------------------------- +# `10 * 2**20` (= 10485760, 10 MiB) +GOSSIP_MAX_SIZE: 10485760 +# `2**10` (= 1024) +MAX_REQUEST_BLOCKS: 1024 +# `2**8` (= 256) +EPOCHS_PER_SUBNET_SUBSCRIPTION: 256 +# `MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2` (= 33024, ~5 months) +MIN_EPOCHS_FOR_BLOCK_REQUESTS: 33024 +# `10 * 2**20` (=10485760, 10 MiB) +MAX_CHUNK_SIZE: 10485760 +# 5s +TTFB_TIMEOUT: 5 +# 10s +RESP_TIMEOUT: 10 +ATTESTATION_PROPAGATION_SLOT_RANGE: 32 +# 500ms +MAXIMUM_GOSSIP_CLOCK_DISPARITY: 500 +MESSAGE_DOMAIN_INVALID_SNAPPY: 0x00000000 +MESSAGE_DOMAIN_VALID_SNAPPY: 0x01000000 +# 2 subnets per node +SUBNETS_PER_NODE: 2 +# 2**8 (= 64) +ATTESTATION_SUBNET_COUNT: 64 +ATTESTATION_SUBNET_EXTRA_BITS: 0 +# ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS +ATTESTATION_SUBNET_PREFIX_BITS: 6 + +# Deneb +# `2**7` (=128) +MAX_REQUEST_BLOCKS_DENEB: 128 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK +MAX_REQUEST_BLOB_SIDECARS: 768 +# `2**12` (= 4096 epochs, ~18 days) +MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 +# `6` +BLOB_SIDECAR_SUBNET_COUNT: 6 +## `uint64(6)` +MAX_BLOBS_PER_BLOCK: 6 + +# Electra +# 2**7 * 10**9 (= 128,000,000,000) +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 +# 2**8 * 10**9 (= 256,000,000,000) +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 +# `9` +BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9 +# `uint64(6)` +TARGET_BLOBS_PER_BLOCK_ELECTRA: 6 +# `uint64(9)` +MAX_BLOBS_PER_BLOCK_ELECTRA: 9 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA +MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 1152 + +# Whisk +# `Epoch(2**8)` +WHISK_EPOCHS_PER_SHUFFLING_PHASE: 256 +# `Epoch(2)` +WHISK_PROPOSER_SELECTION_GAP: 2 + +# Fulu +NUMBER_OF_COLUMNS: 128 +MAX_CELLS_IN_EXTENDED_MATRIX: 768 +DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128 +MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 +SAMPLES_PER_SLOT: 8 +CUSTODY_REQUIREMENT: 4 +TARGET_BLOBS_PER_BLOCK_FULU: 9 +MAX_BLOBS_PER_BLOCK_FULU: 12 +# `MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_FULU` +MAX_REQUEST_BLOB_SIDECARS_FULU: 1536 diff --git a/config/pectra-devnet-6.chain.yml b/config/pectra-devnet-6.chain.yml new file mode 100644 index 0000000000..36931cb9b4 --- /dev/null +++ b/config/pectra-devnet-6.chain.yml @@ -0,0 +1,172 @@ +# Extends the mainnet preset +PRESET_BASE: mainnet +CONFIG_NAME: testnet # needs to exist because of Prysm. Otherwise it conflicts with mainnet genesis + +# Genesis +# --------------------------------------------------------------- +# `2**14` (= 16,384) +MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 71000 +# 2025-Feb-03 05:30:00 PM UTC +MIN_GENESIS_TIME: 1738603800 +GENESIS_FORK_VERSION: 0x10585557 +GENESIS_DELAY: 60 + + +# Forking +# --------------------------------------------------------------- +# Some forks are disabled for now: +# - These may be re-assigned to another fork-version later +# - Temporarily set to max uint64 value: 2**64 - 1 + +# Altair +ALTAIR_FORK_VERSION: 0x20585557 +ALTAIR_FORK_EPOCH: 0 +# Merge +BELLATRIX_FORK_VERSION: 0x30585557 +BELLATRIX_FORK_EPOCH: 0 +TERMINAL_TOTAL_DIFFICULTY: 0 +TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000 +TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615 + +# Capella +CAPELLA_FORK_VERSION: 0x40585557 +CAPELLA_FORK_EPOCH: 0 + +# DENEB +DENEB_FORK_VERSION: 0x50585557 +DENEB_FORK_EPOCH: 0 + +# Electra +ELECTRA_FORK_VERSION: 0x60585557 +ELECTRA_FORK_EPOCH: 10 + +# Fulu +FULU_FORK_VERSION: 0x70585557 +FULU_FORK_EPOCH: 999999 + +# EIP7732 +EIP7732_FORK_VERSION: 0x80000000 +EIP7732_FORK_EPOCH: 99999 + +# EIP7805 +EIP7805_FORK_VERSION: 0x90000000 +EIP7805_FORK_EPOCH: 99999 + +# Time parameters +# --------------------------------------------------------------- +# 12 seconds +SECONDS_PER_SLOT: 12 +# 14 (estimate from Eth1 mainnet) +SECONDS_PER_ETH1_BLOCK: 12 +# 2**8 (= 256) epochs ~27 hours +MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 2 +# 2**8 (= 256) epochs ~27 hours +SHARD_COMMITTEE_PERIOD: 256 +# 2**11 (= 2,048) Eth1 blocks ~8 hours +ETH1_FOLLOW_DISTANCE: 2048 + +# Validator cycle +# --------------------------------------------------------------- +# 2**2 (= 4) +INACTIVITY_SCORE_BIAS: 4 +# 2**4 (= 16) +INACTIVITY_SCORE_RECOVERY_RATE: 16 +# 2**4 * 10**9 (= 16,000,000,000) Gwei +EJECTION_BALANCE: 16000000000 +# 2**2 (= 4) +MIN_PER_EPOCH_CHURN_LIMIT: 4 +# 2**16 (= 65,536) +CHURN_LIMIT_QUOTIENT: 128 +# [New in Deneb:EIP7514] 2**3 (= 8) +MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8 + +# Fork choice +# --------------------------------------------------------------- +# 40% +PROPOSER_SCORE_BOOST: 40 +# 20% +REORG_HEAD_WEIGHT_THRESHOLD: 20 +# 160% +REORG_PARENT_WEIGHT_THRESHOLD: 160 +# `2` epochs +REORG_MAX_EPOCHS_SINCE_FINALIZATION: 2 + +# Deposit contract +# --------------------------------------------------------------- +DEPOSIT_CHAIN_ID: 7072151312 +DEPOSIT_NETWORK_ID: 7072151312 +DEPOSIT_CONTRACT_ADDRESS: 0x4242424242424242424242424242424242424242 + +# Networking +# --------------------------------------------------------------- +# `10 * 2**20` (= 10485760, 10 MiB) +GOSSIP_MAX_SIZE: 10485760 +# `2**10` (= 1024) +MAX_REQUEST_BLOCKS: 1024 +# `2**8` (= 256) +EPOCHS_PER_SUBNET_SUBSCRIPTION: 256 +# `MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2` (= 33024, ~5 months) +MIN_EPOCHS_FOR_BLOCK_REQUESTS: 33024 +# `10 * 2**20` (=10485760, 10 MiB) +MAX_CHUNK_SIZE: 10485760 +# 5s +TTFB_TIMEOUT: 5 +# 10s +RESP_TIMEOUT: 10 +ATTESTATION_PROPAGATION_SLOT_RANGE: 32 +# 500ms +MAXIMUM_GOSSIP_CLOCK_DISPARITY: 500 +MESSAGE_DOMAIN_INVALID_SNAPPY: 0x00000000 +MESSAGE_DOMAIN_VALID_SNAPPY: 0x01000000 +# 2 subnets per node +SUBNETS_PER_NODE: 2 +# 2**8 (= 64) +ATTESTATION_SUBNET_COUNT: 64 +ATTESTATION_SUBNET_EXTRA_BITS: 0 +# ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS +ATTESTATION_SUBNET_PREFIX_BITS: 6 + +# Deneb +# `2**7` (=128) +MAX_REQUEST_BLOCKS_DENEB: 128 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK +MAX_REQUEST_BLOB_SIDECARS: 768 +# `2**12` (= 4096 epochs, ~18 days) +MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 +# `6` +BLOB_SIDECAR_SUBNET_COUNT: 6 +## `uint64(6)` +MAX_BLOBS_PER_BLOCK: 6 + +# Electra +# 2**7 * 10**9 (= 128,000,000,000) +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 +# 2**8 * 10**9 (= 256,000,000,000) +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 +# `9` +BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9 +# `uint64(6)` +TARGET_BLOBS_PER_BLOCK_ELECTRA: 6 +# `uint64(9)` +MAX_BLOBS_PER_BLOCK_ELECTRA: 9 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA +MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 1152 + +# Whisk +# `Epoch(2**8)` +WHISK_EPOCHS_PER_SHUFFLING_PHASE: 256 +# `Epoch(2)` +WHISK_PROPOSER_SELECTION_GAP: 2 + +# Fulu +NUMBER_OF_COLUMNS: 128 +NUMBER_OF_CUSTODY_GROUPS: 128 +DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128 +MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 +SAMPLES_PER_SLOT: 8 +CUSTODY_REQUIREMENT: 4 +MAX_BLOBS_PER_BLOCK_FULU: 12 +MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 + +# EIP7732 +MAX_REQUEST_PAYLOADS: 128 \ No newline at end of file diff --git a/contracts/ens/ens.go b/contracts/ens/ens.go index 85331c039b..f4c49cb2cd 100644 --- a/contracts/ens/ens.go +++ b/contracts/ens/ens.go @@ -26,6 +26,8 @@ var ENSCrontractAddressesSepolia = map[string]string{ "0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5": "OldEnsRegistrarController", } +// TODO hoodi + var ENSRegistryParsedABI, ENSBaseRegistrarParsedABI, ENSOldRegistrarControllerParsedABI, ENSPublicResolverParsedABI, ENSETHRegistrarControllerParsedABI *abi.ABI var ENSRegistryContract, ENSBaseRegistrarContract, ENSOldRegistrarControllerContract, ENSPublicResolverContract, ENSETHRegistrarControllerContract *bind.BoundContract diff --git a/db/bigtable.go b/db/bigtable.go index ed07398aec..aa25dde76c 100644 --- a/db/bigtable.go +++ b/db/bigtable.go @@ -101,10 +101,12 @@ func InitBigtable(project, instance, chainId, redisAddress string) (*Bigtable, e return nil, err } - return InitBigtableWithCache(ctx, project, instance, chainId, rdc) + return InitBigtableWithCache(project, instance, chainId, rdc) } -func InitBigtableWithCache(ctx context.Context, project, instance, chainId string, rdc RedisClient) (*Bigtable, error) { +func InitBigtableWithCache(project, instance, chainId string, rdc RedisClient) (*Bigtable, error) { + ctx := context.Background() + if utils.Config.Bigtable.Emulator { if utils.Config.Bigtable.EmulatorHost == "" { utils.Config.Bigtable.EmulatorHost = "127.0.0.1" @@ -571,15 +573,18 @@ func (bigtable *Bigtable) SaveValidatorBalances(epoch uint64, validators []*type for _, validator := range validators { - if validator.Balance > 0 && validator.Index > highestActiveIndex { + if validator.Index > highestActiveIndex { highestActiveIndex = validator.Index } balanceEncoded := make([]byte, 8) binary.LittleEndian.PutUint64(balanceEncoded, validator.Balance) - effectiveBalanceEncoded := uint8(validator.EffectiveBalance / 1e9) // we can encode the effective balance in 1 byte as it is capped at 32ETH and only decrements in 1 ETH steps - combined := append(balanceEncoded, effectiveBalanceEncoded) + effectiveBalanceEncoded := make([]byte, 8) + binary.LittleEndian.PutUint64(effectiveBalanceEncoded, validator.EffectiveBalance) + + combined := append(balanceEncoded, effectiveBalanceEncoded...) + mut := &gcp_bigtable.Mutation{} mut.Set(VALIDATOR_BALANCES_FAMILY, "b", ts, combined) key := fmt.Sprintf("%s:%s:%s:%s", bigtable.chainId, bigtable.validatorIndexToKey(validator.Index), VALIDATOR_BALANCES_FAMILY, epochKey) diff --git a/db/bigtable_eth1.go b/db/bigtable_eth1.go index 3ceaa2c472..051e92063c 100644 --- a/db/bigtable_eth1.go +++ b/db/bigtable_eth1.go @@ -32,7 +32,7 @@ import ( "github.com/coocood/freecache" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/common/hexutil" eth_types "github.com/ethereum/go-ethereum/core/types" "github.com/go-redis/redis/v8" "github.com/sirupsen/logrus" @@ -701,9 +701,10 @@ func (bigtable *Bigtable) IndexEventsWithTransformers(start, end int64, transfor if err != nil { logrus.WithError(err).Errorf("error transforming block [%v]", block.Number) } - bulkMutsData.Keys = append(bulkMutsData.Keys, mutsData.Keys...) - bulkMutsData.Muts = append(bulkMutsData.Muts, mutsData.Muts...) - + if mutsData != nil { + bulkMutsData.Keys = append(bulkMutsData.Keys, mutsData.Keys...) + bulkMutsData.Muts = append(bulkMutsData.Muts, mutsData.Muts...) + } if mutsMetadataUpdate != nil { bulkMutsMetadataUpdate.Keys = append(bulkMutsMetadataUpdate.Keys, mutsMetadataUpdate.Keys...) bulkMutsMetadataUpdate.Muts = append(bulkMutsMetadataUpdate.Muts, mutsMetadataUpdate.Muts...) @@ -785,15 +786,17 @@ func (bigtable *Bigtable) IndexBlocksWithTransformers(blocks []*types.Eth1Block, if err != nil { logrus.WithError(err).Errorf("error transforming block [%v]", block.Number) } - bulkMutsData.Keys = append(bulkMutsData.Keys, mutsData.Keys...) - bulkMutsData.Muts = append(bulkMutsData.Muts, mutsData.Muts...) + if mutsData != nil { + bulkMutsData.Keys = append(bulkMutsData.Keys, mutsData.Keys...) + bulkMutsData.Muts = append(bulkMutsData.Muts, mutsData.Muts...) + } if mutsMetadataUpdate != nil { bulkMutsMetadataUpdate.Keys = append(bulkMutsMetadataUpdate.Keys, mutsMetadataUpdate.Keys...) bulkMutsMetadataUpdate.Muts = append(bulkMutsMetadataUpdate.Muts, mutsMetadataUpdate.Muts...) } - if len(mutsData.Keys) > 0 { + if mutsData != nil && len(mutsData.Keys) > 0 { metaKeys := strings.Join(bulkMutsData.Keys, ",") // save block keys in order to be able to handle chain reorgs key, mut := bigtable.blockKeysMutation(block.Number, block.Hash, metaKeys) bulkMutsMetadataUpdate.Keys = append(bulkMutsMetadataUpdate.Keys, key) @@ -906,7 +909,7 @@ func (bigtable *Bigtable) TransformBlock(block *types.Eth1Block, cache *freecach txFee := new(big.Int).Mul(new(big.Int).SetBytes(t.GasPrice), big.NewInt(int64(t.GasUsed))) if len(block.BaseFee) > 0 { - effectiveGasPrice := math.BigMin(new(big.Int).Add(new(big.Int).SetBytes(t.MaxPriorityFeePerGas), new(big.Int).SetBytes(block.BaseFee)), new(big.Int).SetBytes(t.MaxFeePerGas)) + effectiveGasPrice := bigMin(new(big.Int).Add(new(big.Int).SetBytes(t.MaxPriorityFeePerGas), new(big.Int).SetBytes(block.BaseFee)), new(big.Int).SetBytes(t.MaxFeePerGas)) proposerGasPricePart := new(big.Int).Sub(effectiveGasPrice, new(big.Int).SetBytes(block.BaseFee)) if proposerGasPricePart.Cmp(big.NewInt(0)) >= 0 { @@ -1002,13 +1005,20 @@ func CalculateTxFeesFromBlock(block *types.Eth1Block) *big.Int { return txFees } +func bigMin(x, y *big.Int) *big.Int { + if x.Cmp(y) > 0 { + return y + } + return x +} + func CalculateTxFeeFromTransaction(tx *types.Eth1Transaction, blockBaseFee *big.Int) *big.Int { // calculate tx fee depending on tx type txFee := new(big.Int).SetUint64(tx.GasUsed) switch tx.Type { case 0, 1: txFee.Mul(txFee, new(big.Int).SetBytes(tx.GasPrice)) - case 2, 3: + case 2, 3, 4: // multiply gasused with min(baseFee + maxpriorityfee, maxfee) if normalGasPrice, maxGasPrice := new(big.Int).Add(blockBaseFee, new(big.Int).SetBytes(tx.MaxPriorityFeePerGas)), new(big.Int).SetBytes(tx.MaxFeePerGas); normalGasPrice.Cmp(maxGasPrice) <= 0 { txFee.Mul(txFee, normalGasPrice) @@ -1016,7 +1026,7 @@ func CalculateTxFeeFromTransaction(tx *types.Eth1Transaction, blockBaseFee *big. txFee.Mul(txFee, maxGasPrice) } default: - logger.Errorf("unknown tx type %v", tx.Type) + utils.LogError(fmt.Errorf("unknown tx type %v", tx.Type), "error calculating tx fee", 0) } return txFee } @@ -2097,6 +2107,210 @@ func (bigtable *Bigtable) TransformWithdrawals(block *types.Eth1Block, cache *fr return bulkData, bulkMetadataUpdates, nil } +type BridgeQueueRequest struct { + TxHash []byte + TxIndex int + ItxIndex int + BlockNumber uint64 + BlockTimestamp time.Time + From []byte + Fee uint64 +} + +func (bigtable *Bigtable) TransformConsolidationRequests(blk *types.Eth1Block, cache *freecache.Cache) (bulkData *types.BulkMutations, bulkMetadataUpdates *types.BulkMutations, err error) { + consolidationContractAddress := hexutil.MustDecode(utils.Config.Chain.PectraConsolidationRequestContractAddress) + startTime := time.Now() + defer func() { + metrics.TaskDuration.WithLabelValues("bt_transform_consolidation_requests").Observe(time.Since(startTime).Seconds()) + }() + + var queueRequests []BridgeQueueRequest + for i, tx := range blk.GetTransactions() { + if i >= TX_PER_BLOCK_LIMIT { + return nil, nil, fmt.Errorf("unexpected number of transactions in block expected at most %d but got: %v, tx: %x", TX_PER_BLOCK_LIMIT-1, i, tx.GetHash()) + } + + var revertSource string + for j, itx := range tx.GetItx() { + if j > ITX_PER_TX_LIMIT { + return nil, nil, fmt.Errorf("unexpected number of internal transactions in block expected at most %d but got: %v, tx: %x", ITX_PER_TX_LIMIT, j, tx.GetHash()) + } + // check for error before skipping, otherwise we loose track of cascading reverts + if itx.ErrorMsg != "" { + if revertSource == "" || !strings.HasPrefix(itx.Path, revertSource) { + revertSource = strings.TrimSuffix(itx.Path, "]") + } + continue + } + if revertSource != "" && strings.HasPrefix(itx.Path, revertSource) { + continue + } + + if !bytes.Equal(itx.To, consolidationContractAddress) { + continue + } + + if itx.Type == "staticcall" { + continue + } + + if bytes.Equal(itx.Value, []byte{0x0}) { + continue + } + + queueRequests = append(queueRequests, BridgeQueueRequest{ + Fee: new(big.Int).SetBytes(itx.Value).Uint64(), + TxHash: tx.Hash, + TxIndex: i, + ItxIndex: j, + BlockNumber: blk.Number, + BlockTimestamp: blk.Time.AsTime(), + From: tx.From, + }) + } + } + + var requestIndex int + for _, tx := range blk.GetTransactions() { + for _, log := range tx.GetLogs() { + if bytes.Equal(log.Address, consolidationContractAddress) { + // we have found a consolidation event + // now slice out the data + // source_address: Bytes20 + // source_pubkey: Bytes48 + // target_pubkey: Bytes48 + + elData := types.ElConsolidationRequestData(log.Data) + sourceAddress, _ := elData.GetSourceAddressBytes() + sourcePubkey, _ := elData.GetSourceValidatorPubkey() + targetPubkey, _ := elData.GetTargetValidatorPubkey() + + if sourceAddress == nil || sourcePubkey == nil || targetPubkey == nil { + logger.Warnf("error parsing consolidation event: %x %x %d", sourceAddress, sourcePubkey, targetPubkey) + continue + } + + logger.Infof("consolidation event: %x %x %x", sourceAddress, sourcePubkey, targetPubkey) + + request := queueRequests[requestIndex] + _, err := WriterDb.Exec(` + INSERT INTO eth1_consolidation_requests (tx_hash, tx_index, itx_index, block_number, block_ts, from_address, fee, source_address, source_pubkey, target_pubkey) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) + ON CONFLICT (tx_hash, tx_index, itx_index) DO UPDATE + SET block_number = EXCLUDED.block_number, block_ts = EXCLUDED.block_ts, from_address = EXCLUDED.from_address, fee = EXCLUDED.fee, source_address = EXCLUDED.source_address, source_pubkey = EXCLUDED.source_pubkey, target_pubkey = EXCLUDED.target_pubkey + `, request.TxHash, request.TxIndex, request.ItxIndex, request.BlockNumber, request.BlockTimestamp, request.From, request.Fee, sourceAddress, sourcePubkey, targetPubkey) + + if err != nil { + return nil, nil, err + } + requestIndex++ + } + } + } + + if requestIndex != len(queueRequests) { + logger.Errorf("unexpected number of consolidation requests for block %d", blk.Number) + } + + return bulkData, bulkMetadataUpdates, nil +} + +func (bigtable *Bigtable) TransformWithdrawalRequests(blk *types.Eth1Block, cache *freecache.Cache) (bulkData *types.BulkMutations, bulkMetadataUpdates *types.BulkMutations, err error) { + withdrawalContractAddress := hexutil.MustDecode(utils.Config.Chain.PectraWithdrawalRequestContractAddress) + startTime := time.Now() + defer func() { + metrics.TaskDuration.WithLabelValues("bt_transform_withdrawal_requests").Observe(time.Since(startTime).Seconds()) + }() + + var queueRequests []BridgeQueueRequest + for i, tx := range blk.GetTransactions() { + if i >= TX_PER_BLOCK_LIMIT { + return nil, nil, fmt.Errorf("unexpected number of transactions in block expected at most %d but got: %v, tx: %x", TX_PER_BLOCK_LIMIT-1, i, tx.GetHash()) + } + + var revertSource string + for j, itx := range tx.GetItx() { + if j > ITX_PER_TX_LIMIT { + return nil, nil, fmt.Errorf("unexpected number of internal transactions in block expected at most %d but got: %v, tx: %x", ITX_PER_TX_LIMIT, j, tx.GetHash()) + } + // check for error before skipping, otherwise we loose track of cascading reverts + if itx.ErrorMsg != "" { + if revertSource == "" || !strings.HasPrefix(itx.Path, revertSource) { + revertSource = strings.TrimSuffix(itx.Path, "]") + } + continue + } + if revertSource != "" && strings.HasPrefix(itx.Path, revertSource) { + continue + } + + if !bytes.Equal(itx.To, withdrawalContractAddress) { + continue + } + + if itx.Type == "staticcall" { + continue + } + + if bytes.Equal(itx.Value, []byte{0x0}) { + continue + } + + queueRequests = append(queueRequests, BridgeQueueRequest{ + Fee: new(big.Int).SetBytes(itx.Value).Uint64(), + TxHash: tx.Hash, + TxIndex: i, + ItxIndex: j, + BlockNumber: blk.Number, + BlockTimestamp: blk.Time.AsTime(), + From: tx.From, + }) + } + } + + var requestIndex int + for _, tx := range blk.GetTransactions() { + for _, log := range tx.GetLogs() { + if bytes.Equal(log.Address, withdrawalContractAddress) { + // we have found a withdrawal event + // now slice out the data + // source_address: Bytes20 + // validator_pubkey: Bytes48 + // amount: uint64 + + elData := types.ElWithdrawalRequestData(log.Data) + sourceAddress, _ := elData.GetSourceAddressBytes() + validatorPubkey, _ := elData.GetValidatorPubkey() + amount, _ := elData.GetAmountUint64() + + if sourceAddress == nil || validatorPubkey == nil { + logger.Warnf("error parsing withdrawal event: %x %x %d", sourceAddress, validatorPubkey, amount) + continue + } + + logger.Infof("withdrawal event: %x %x %d", sourceAddress, validatorPubkey, amount) + + request := queueRequests[requestIndex] + _, err := WriterDb.Exec(` + INSERT INTO eth1_withdrawal_requests (tx_hash, tx_index, itx_index, block_number, block_ts, from_address, fee, source_address, validator_pubkey, amount) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) + ON CONFLICT (tx_hash, tx_index, itx_index) DO UPDATE + SET block_number = EXCLUDED.block_number, block_ts = EXCLUDED.block_ts, from_address = EXCLUDED.from_address, fee = EXCLUDED.fee, source_address = EXCLUDED.source_address, validator_pubkey = EXCLUDED.validator_pubkey, amount = EXCLUDED.amount + `, request.TxHash, request.TxIndex, request.ItxIndex, request.BlockNumber, request.BlockTimestamp, request.From, request.Fee, sourceAddress, validatorPubkey, amount) + + if err != nil { + return nil, nil, err + } + requestIndex++ + } + } + } + + if requestIndex != len(queueRequests) { + logger.Errorf("unexpected number of withdrawal requests for block %d", blk.Number) + } + + return bulkData, bulkMetadataUpdates, nil +} + type IndexKeys struct { indexes []string keys []string @@ -2933,13 +3147,15 @@ func (bigtable *Bigtable) GetAddressInternalTableData(address []byte, pageToken return data, nil } -func (bigtable *Bigtable) GetInternalTransfersForTransaction(transaction []byte, from []byte, parityTrace []*rpc.ParityTraceResult, currency string) ([]types.ITransaction, error) { +func (bigtable *Bigtable) GetInternalTransfersForTransaction(transaction []byte, from []byte, traces []*rpc.GethTraceCallResult, currency string, blockNumber *big.Int) ([]types.ITransaction, error) { + if len(traces) == 0 { + return nil, nil + } names := make(map[string]string) - for _, trace := range parityTrace { - from, to, _, _ := trace.ConvertFields() - names[string(from)] = "" - names[string(to)] = "" + for _, trace := range traces { + names[trace.From.String()] = "" + names[trace.To.String()] = "" } err := bigtable.GetAddressNames(names) @@ -2947,34 +3163,32 @@ func (bigtable *Bigtable) GetInternalTransfersForTransaction(transaction []byte, return nil, err } - contractInteractionTypes, err := BigtableClient.GetAddressContractInteractionsAtParityTraces(parityTrace) + contractInteractionTypes, err := BigtableClient.GetAddressContractInteractionsAtTraces(traces, blockNumber) if err != nil { utils.LogError(err, "error getting contract states", 0) } - data := make([]types.ITransaction, 0, len(parityTrace)-1) - var revertSource []int64 - for i := 1; i < len(parityTrace); i++ { - var reverted bool - if parityTrace[i].Error != "" { - reverted = true - // only save the highest root revert - if !isSubset(parityTrace[i].TraceAddress, revertSource) { - revertSource = parityTrace[i].TraceAddress - } - } - if isSubset(parityTrace[i].TraceAddress, revertSource) { - reverted = true + paths := make(map[*rpc.GethTraceCallResult]string) + data := make([]types.ITransaction, 0, len(traces)-1) + revertedTraces := make(map[*rpc.GethTraceCallResult]struct{}) + for i := 1; i < len(traces); i++ { + for index, call := range traces[i].Calls { + paths[call] = fmt.Sprintf("%s %d", paths[traces[i]], index) } - from, to, value, tx_type := parityTrace[i].ConvertFields() + reverted := isReverted(traces[i], revertedTraces) + + from := traces[i].From.Bytes() + to := traces[i].To.Bytes() + value := common.FromHex(traces[i].Value) + tx_type := traces[i].Type if tx_type == "suicide" { // erigon's "suicide" might be misleading for users tx_type = "selfdestruct" } input := make([]byte, 0) - if len(parityTrace[i].Action.Input) > 2 { - input, err = hex.DecodeString(parityTrace[i].Action.Input[2:]) + if len(traces[i].Input) > 2 { + input, err = hex.DecodeString(traces[i].Input[2:]) if err != nil { utils.LogError(err, "can't convert hex string", 0) } @@ -2993,18 +3207,18 @@ func (bigtable *Bigtable) GetInternalTransfersForTransaction(transaction []byte, From: utils.FormatAddress(from, nil, fromName, false, from_contractInteraction != types.CONTRACT_NONE, true), To: utils.FormatAddress(to, nil, toName, false, to_contractInteraction != types.CONTRACT_NONE, true), Amount: utils.FormatElCurrency(value, currency, 8, true, false, false, true), - TracePath: utils.FormatTracePath(tx_type, parityTrace[i].TraceAddress, !reverted, bigtable.GetMethodLabel(input, from_contractInteraction)), + TracePath: utils.FormatGethTracePath(tx_type, paths[traces[i]], !reverted, bigtable.GetMethodLabel(input, from_contractInteraction)), Advanced: tx_type == "delegatecall" || string(value) == "\x00", Reverted: reverted, } - gaslimit, err := strconv.ParseUint(parityTrace[i].Action.Gas, 0, 0) + gaslimit, err := strconv.ParseUint(traces[i].Gas, 0, 0) if err == nil { itx.Gas.Limit = gaslimit } data = append(data, itx) - // gasusage, err := strconv.ParseUint(parityTrace[i].Result.GasUsed, 0, 0) + // gasusage, err := strconv.ParseUint(traces[i].Result.GasUsed, 0, 0) // if err == nil { // itx.Gas.Usage = gasusage // } @@ -3012,6 +3226,22 @@ func (bigtable *Bigtable) GetInternalTransfersForTransaction(transaction []byte, return data, nil } +func isReverted(internal *rpc.GethTraceCallResult, revertedTraces map[*rpc.GethTraceCallResult]struct{}) bool { + if _, exist := revertedTraces[internal]; exist { + return true + } + var reverted bool + if internal.Error != "" { + reverted = true + calls := internal.Calls + for len(calls) != 0 { + revertedTraces[calls[0]] = struct{}{} + calls = append(calls[1:], calls[0].Calls...) + } + } + return reverted +} + // currently only erc20 func (bigtable *Bigtable) GetArbitraryTokenTransfersForTransaction(transaction []byte) ([]*types.Transfer, error) { @@ -3051,7 +3281,7 @@ func (bigtable *Bigtable) GetArbitraryTokenTransfersForTransaction(transaction [ return true }, gcp_bigtable.LimitRows(256)) if err != nil { - return nil, err + return nil, fmt.Errorf("error reading rows: %w", err) } names := make(map[string]string) @@ -3068,7 +3298,7 @@ func (bigtable *Bigtable) GetArbitraryTokenTransfersForTransaction(transaction [ g.Go(func() error { err := bigtable.GetAddressNames(names) if err != nil { - return err + return fmt.Errorf("error getting address names: %w", err) } return nil }) @@ -3078,7 +3308,7 @@ func (bigtable *Bigtable) GetArbitraryTokenTransfersForTransaction(transaction [ g.Go(func() error { metadata, err := bigtable.GetERC20MetadataForAddress([]byte(address)) if err != nil { - return err + return fmt.Errorf("error getting erc20 metadata for address %v: %w", address, err) } mux.Lock() tokensToAdd[address] = metadata @@ -3088,7 +3318,7 @@ func (bigtable *Bigtable) GetArbitraryTokenTransfersForTransaction(transaction [ } err = g.Wait() if err != nil { - return nil, err + return nil, fmt.Errorf("error getting token metadata: %w", err) } for k, v := range tokensToAdd { @@ -3710,7 +3940,7 @@ func (bigtable *Bigtable) GetBalanceForAddress(address []byte, token []byte) (*t return nil, nil } if val, ok := row[ACCOUNT_METADATA_FAMILY]; ok { - if val == nil || len(val) < 1 { + if len(val) < 1 { return nil, fmt.Errorf("ReadItem is empty or nil") } @@ -4131,20 +4361,19 @@ func (bigtable *Bigtable) GetAddressContractInteractionsAtITransactions(itransac return resultPairs, nil } -// convenience function to get contract interaction status per parity trace -func (bigtable *Bigtable) GetAddressContractInteractionsAtParityTraces(traces []*rpc.ParityTraceResult) ([][2]types.ContractInteractionType, error) { +// convenience function to get contract interaction status per trace +func (bigtable *Bigtable) GetAddressContractInteractionsAtTraces(traces []*rpc.GethTraceCallResult, blockNumber *big.Int) ([][2]types.ContractInteractionType, error) { requests := make([]contractInteractionAtRequest, 0, len(traces)*2) for i, itx := range traces { - from, to, _, _ := itx.ConvertFields() requests = append(requests, contractInteractionAtRequest{ - address: fmt.Sprintf("%x", from), - block: int64(itx.BlockNumber), + address: itx.From.String(), + block: blockNumber.Int64(), txIdx: int64(itx.TransactionPosition), traceIdx: int64(i), }) requests = append(requests, contractInteractionAtRequest{ - address: fmt.Sprintf("%x", to), - block: int64(itx.BlockNumber), + address: itx.To.String(), + block: blockNumber.Int64(), txIdx: int64(itx.TransactionPosition), traceIdx: int64(i), }) diff --git a/db/bigtable_eth1_test.go b/db/bigtable_eth1_test.go new file mode 100644 index 0000000000..0198c68a61 --- /dev/null +++ b/db/bigtable_eth1_test.go @@ -0,0 +1,58 @@ +package db + +import ( + "testing" + + "github.com/gobitfly/eth2-beaconchain-explorer/rpc" +) + +func TestName(t *testing.T) { + traces := &rpc.GethTraceCallResult{ + Input: "source", + Calls: []*rpc.GethTraceCallResult{ + { + // child 1 and its children are reverted + Input: "child 1", + Error: "error", + Calls: []*rpc.GethTraceCallResult{ + { + Input: "child 1-1", + }, + }, + }, + { + Input: "child 2", + }, + }, + } + tests := []struct { + name string + internals *rpc.GethTraceCallResult + expected bool + total int + }{ + { + name: "head", + internals: traces, + expected: false, + total: 0, + }, { + name: "child 1", + internals: traces.Calls[0], + expected: true, + total: 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + revertedTraces := make(map[*rpc.GethTraceCallResult]struct{}) + reverted := isReverted(tt.internals, revertedTraces) + if got, want := reverted, tt.expected; got != want { + t.Errorf("got %v want %v", got, want) + } + if got, want := len(revertedTraces), tt.total; got != want { + t.Errorf("got %v want %v", got, want) + } + }) + } +} diff --git a/db/db.go b/db/db.go index 6b3d873203..b52b77e1ca 100644 --- a/db/db.go +++ b/db/db.go @@ -8,6 +8,7 @@ import ( "encoding/hex" "errors" "fmt" + "math" "math/big" "net" "regexp" @@ -25,8 +26,8 @@ import ( "github.com/jmoiron/sqlx" "github.com/lib/pq" "github.com/pressly/goose/v3" - prysm_deposit "github.com/prysmaticlabs/prysm/v3/contracts/deposit" - ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" + prysm_deposit "github.com/prysmaticlabs/prysm/v5/contracts/deposit" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/sirupsen/logrus" "github.com/gobitfly/eth2-beaconchain-explorer/rpc" @@ -592,6 +593,23 @@ func GetValidatorIndex(publicKey []byte) (uint64, error) { return index, err } +func GetNextPendingDeposit(pubkey []byte) (*types.PendingDeposit, error) { + entry, err := GetPendingDeposits(pubkey, 1) + if len(entry) == 0 { + return &types.PendingDeposit{}, sql.ErrNoRows + } + return &entry[0], err +} + +func GetPendingDeposits(pubkey []byte, limit int) ([]types.PendingDeposit, error) { + var pendingDeposit []types.PendingDeposit + err := ReaderDb.Select(&pendingDeposit, `SELECT id, est_clear_epoch, amount, withdrawal_credentials, signature FROM pending_deposits_queue WHERE pubkey = $1 ORDER BY id asc LIMIT $2`, pubkey, limit) + if err != nil && err != sql.ErrNoRows { + return nil, err + } + return pendingDeposit, err +} + // GetValidatorDeposits will return eth1- and eth2-deposits for a public key from the database func GetValidatorDeposits(publicKey []byte) (*types.ValidatorDeposits, error) { deposits := &types.ValidatorDeposits{} @@ -631,11 +649,45 @@ func GetValidatorDeposits(publicKey []byte) (*types.ValidatorDeposits, error) { blocks_deposits.signature FROM blocks_deposits INNER JOIN blocks ON (blocks_deposits.block_root = blocks.blockroot AND blocks.status = '1') OR (blocks_deposits.block_slot = 0 AND blocks_deposits.block_slot = blocks.slot AND blocks_deposits.publickey = $1) - WHERE blocks_deposits.publickey = $1`, publicKey) + WHERE blocks_deposits.publickey = $1 + UNION ALL + SELECT + blocks_deposit_requests_v2.slot_processed as block_slot, + blocks_deposit_requests_v2.index_processed as request_index, + blocks_deposit_requests_v2.block_processed_root as block_root, + null, + blocks_deposit_requests_v2.pubkey, + blocks_deposit_requests_v2.withdrawal_credentials, + blocks_deposit_requests_v2.amount, + blocks_deposit_requests_v2.signature + FROM blocks_deposit_requests_v2 + INNER JOIN blocks ON (blocks_deposit_requests_v2.block_processed_root = blocks.blockroot AND blocks.status = '1') OR (blocks_deposit_requests_v2.slot_processed = 0 AND blocks_deposit_requests_v2.slot_processed = blocks.slot AND blocks_deposit_requests_v2.pubkey = $1) + WHERE blocks_deposit_requests_v2.pubkey = $1 + AND blocks_deposit_requests_v2.status = 'completed' + ORDER BY block_slot DESC, block_index DESC + `, publicKey) if err != nil { return nil, err } - return deposits, nil + + pendingDeposits, err := GetPendingDeposits(publicKey, 10) + if err == nil { + deposits.PendingEth2Deposits = make([]types.Eth2Deposit, 0, len(pendingDeposits)) + for _, deposit := range pendingDeposits { + deposits.PendingEth2Deposits = append(deposits.PendingEth2Deposits, types.Eth2Deposit{ + BlockSlot: deposit.EstClearEpoch * utils.Config.ClConfig.SlotsPerEpoch, + BlockIndex: 0, + BlockRoot: nil, + Proof: nil, + Publickey: publicKey, + Withdrawalcredentials: deposit.WithdrawalCredentials, + Amount: deposit.Amount, + Signature: deposit.Signature, + }) + } + } + + return utils.FixELDepositValidity(deposits), nil } // UpdateCanonicalBlocks will update the blocks for an epoch range in the database @@ -1835,6 +1887,7 @@ func GetValidatorNames(validators []uint64) (map[uint64]string, error) { } // GetPendingValidatorCount queries the pending validators currently in the queue +// @deprecated after pectra, use services.LatestQueueData instead func GetPendingValidatorCount() (uint64, error) { count := uint64(0) err := ReaderDb.Get(&count, "SELECT entering_validators_count FROM queue ORDER BY ts DESC LIMIT 1") @@ -1848,7 +1901,7 @@ func GetTotalEligibleEther() (uint64, error) { var total uint64 err := ReaderDb.Get(&total, ` - SELECT eligibleether FROM epochs ORDER BY epoch DESC LIMIT 1 + SELECT (eligibleether / 1e9)::int FROM epochs ORDER BY epoch DESC LIMIT 1 `) if err == sql.ErrNoRows { return 0, nil @@ -1856,7 +1909,7 @@ func GetTotalEligibleEther() (uint64, error) { if err != nil { return 0, err } - return total / 1e9, nil + return total, nil } // GetValidatorsGotSlashed returns the validators that got slashed after `epoch` either by an attestation violation or a proposer violation @@ -2332,10 +2385,14 @@ func GetAddressWithdrawalTableData(address []byte, pageToken string, currency st } } + // Note that withdrawalindex can be negative in the table starting with pectra. + // To keep API compatability we use the GREATEST function. + // pagination should not be affected as there is only one withdrawal for a given address at a time. + err = ReaderDb.Select(&withdrawals, ` SELECT w.block_slot as slot, - w.withdrawalindex as index, + GREATEST(withdrawalindex, 0) as index, w.validatorindex, w.address, w.amount @@ -2441,7 +2498,7 @@ func GetValidatorsWithdrawals(validators []uint64, fromEpoch uint64, toEpoch uin w.amount FROM blocks_withdrawals w INNER JOIN blocks b ON b.blockroot = w.block_root AND b.status = '1' - WHERE validatorindex = ANY($1) + WHERE validatorindex = ANY($1) AND w.address <> ''::bytea AND (w.block_slot / $4) >= $2 AND (w.block_slot / $4) <= $3 ORDER BY w.withdrawalindex`, pq.Array(validators), fromEpoch, toEpoch, utils.Config.Chain.ClConfig.SlotsPerEpoch) if err != nil { @@ -2993,6 +3050,10 @@ func GetValidatorsBLSChange(validators []uint64) ([]*types.ValidatorsBLSChange, change := make([]*types.ValidatorsBLSChange, 0, len(validators)) err := ReaderDb.Select(&change, ` + WITH d AS (SELECT ROW_NUMBER() OVER (PARTITION BY publickey ORDER BY block_slot) AS rn, withdrawalcredentials, publickey FROM blocks_deposits d + INNER JOIN blocks b ON b.blockroot = d.block_root AND b.status = '1' + WHERE d.publickey IN (SELECT pubkey FROM validators WHERE validatorindex = ANY($1)) + ) SELECT bls.block_slot AS slot, bls.block_root, @@ -3004,15 +3065,12 @@ func GetValidatorsBLSChange(validators []uint64) ([]*types.ValidatorsBLSChange, FROM blocks_bls_change bls INNER JOIN blocks b ON b.blockroot = bls.block_root AND b.status = '1' LEFT JOIN validators v ON v.validatorindex = bls.validatorindex - LEFT JOIN ( - SELECT ROW_NUMBER() OVER (PARTITION BY publickey ORDER BY block_slot) AS rn, withdrawalcredentials, publickey, block_root FROM blocks_deposits d - INNER JOIN blocks b ON b.blockroot = d.block_root AND b.status = '1' - ) AS d ON d.publickey = v.pubkey AND rn = 1 + LEFT JOIN d ON d.publickey = v.pubkey AND rn = 1 WHERE bls.validatorindex = ANY($1) ORDER BY bls.block_slot DESC `, pq.Array(validators)) if err != nil { - if err == sql.ErrNoRows { + if errors.Is(err, sql.ErrNoRows) { return nil, nil } return nil, fmt.Errorf("error getting validators blocks_bls_change: %w", err) @@ -3038,7 +3096,13 @@ func GetWithdrawableValidatorCount(epoch uint64) (uint64, error) { WHERE DAY = (SELECT COALESCE(MAX(day), 0) FROM validator_stats_status)) as stats ON stats.validatorindex = validators.validatorindex WHERE - validators.withdrawalcredentials LIKE '\x01' || '%'::bytea AND ((stats.end_effective_balance = $1 AND stats.end_balance > $1) OR (validators.withdrawableepoch <= $2 AND stats.end_balance > 0));`, utils.Config.Chain.ClConfig.MaxEffectiveBalance, epoch) + ( + (get_byte(validators.withdrawalcredentials, 0) = 1 AND stats.end_effective_balance = $1 AND stats.end_balance > $1) + OR + (get_byte(validators.withdrawalcredentials, 0) = 2 AND stats.end_effective_balance = $3 AND stats.end_balance > $3) + OR + (validators.withdrawableepoch <= $2 AND stats.end_balance > 0) + )`, utils.Config.Chain.ClConfig.MaxEffectiveBalance, epoch, utils.Config.Chain.ClConfig.MaxEffectiveBalanceElectra) if err != nil { if err == sql.ErrNoRows { return 0, nil @@ -3102,15 +3166,168 @@ func GetValidatorIncomePerformance(validators []uint64, incomePerformance *types WHERE validatorindex = ANY($1)`, validatorsPQArray) } -func GetTotalValidatorDeposits(validators []uint64, totalDeposits *uint64) error { +type SlotRange struct { + StartSlot uint64 + EndSlot uint64 +} + +type GetValidatorDepositsAndIncomingConsolidationsResult struct { + ValidatorIndex uint64 `db:"validatorindex"` + Deposits uint64 `db:"deposits"` + DepositsAmount uint64 `db:"deposits_amount"` +} + +func GetValidatorDepositsAndIncomingConsolidations(slotRange *SlotRange, validators []uint64) ([]*GetValidatorDepositsAndIncomingConsolidationsResult, error) { + if validators == nil { + validators = []uint64{} + } validatorsPQArray := pq.Array(validators) - return ReaderDb.Get(totalDeposits, ` - SELECT - COALESCE(SUM(amount), 0) - FROM blocks_deposits d - INNER JOIN blocks b ON b.blockroot = d.block_root AND b.status = '1' - WHERE publickey IN (SELECT pubkey FROM validators WHERE validatorindex = ANY($1)) - `, validatorsPQArray) + + if slotRange == nil { + slotRange = &SlotRange{ + StartSlot: 0, + EndSlot: uint64(math.MaxInt32), + } + } + + ret := []*GetValidatorDepositsAndIncomingConsolidationsResult{} + + depositsQry := ` + SELECT + validatorindex, + COUNT(*) AS deposits, + SUM(amount) AS deposits_amount + FROM ( + SELECT + validators.validatorindex, + amount + FROM + blocks_deposits + INNER JOIN validators ON blocks_deposits.publickey = validators.pubkey + INNER JOIN blocks ON blocks_deposits.block_root = blocks.blockroot + where + blocks.slot >= $1 + AND blocks.slot <= $2 + AND ( + blocks.status = '1' + OR blocks.slot = 0 + ) + AND blocks_deposits.valid_signature + AND (cardinality($3::int[]) = 0 OR validators.validatorindex = ANY($3)) + UNION ALL + SELECT + validators.validatorindex, + amount + FROM + blocks_deposit_requests_v2 + INNER JOIN validators ON blocks_deposit_requests_v2.pubkey = validators.pubkey + INNER JOIN blocks ON blocks_deposit_requests_v2.block_processed_root = blocks.blockroot + WHERE + blocks.slot >= $1 + AND blocks.slot <= $2 + AND ( + blocks.status = '1' + OR blocks.slot = 0 + ) + AND blocks_deposit_requests_v2.status = 'completed' + AND (cardinality($3::int[]) = 0 OR validators.validatorindex = ANY($3)) + UNION ALL + SELECT + validators.validatorindex as target_index, + amount_consolidated + FROM + blocks_consolidation_requests_v2 + INNER JOIN validators ON blocks_consolidation_requests_v2.target_pubkey = validators.pubkey + INNER JOIN blocks ON blocks_consolidation_requests_v2.block_processed_root = blocks.blockroot + WHERE + blocks.slot >= $1 + AND blocks.slot <= $2 + AND ( + blocks.status = '1' + OR blocks.slot = 0 + ) + AND blocks_consolidation_requests_v2.status = 'completed' + AND (cardinality($3::int[]) = 0 OR validators.validatorindex = ANY($3)) + ) AS a + GROUP BY validatorindex + ORDER BY 2 DESC;` + + err := WriterDb.Select(&ret, depositsQry, slotRange.StartSlot, slotRange.EndSlot, validatorsPQArray) + + if err != nil { + return nil, fmt.Errorf("error getting validator deposits and incoming consolidations: %w", err) + } + return ret, nil +} + +type GetValidatorWithdrawalsAndOutgoingConsolidationsResult struct { + ValidatorIndex uint64 `db:"validatorindex"` + Withdrawals uint64 `db:"withdrawals"` + WithdrawalsAmount uint64 `db:"withdrawals_amount"` +} + +func GetValidatorWithdrawalsAndOutgoingConsolidations(slotRange *SlotRange, validators []uint64) ([]*GetValidatorWithdrawalsAndOutgoingConsolidationsResult, error) { + if validators == nil { + validators = []uint64{} + } + validatorsPQArray := pq.Array(validators) + + if slotRange == nil { + slotRange = &SlotRange{ + StartSlot: 0, + EndSlot: uint64(math.MaxInt32), + } + } + + ret := []*GetValidatorWithdrawalsAndOutgoingConsolidationsResult{} + + query := ` + SELECT + validatorindex, + COUNT(*) AS withdrawals, + SUM(amount) AS withdrawals_amount + FROM + ( + SELECT + validatorindex, + amount + FROM + blocks_withdrawals + INNER JOIN blocks on blocks_withdrawals.block_root = blocks.blockroot + WHERE + block_slot >= $1 + AND block_slot <= $2 + AND blocks.status = '1' + AND (cardinality($3::int[]) = 0 OR blocks_withdrawals.validatorindex = ANY($3)) + UNION ALL + SELECT + validators.validatorindex as source_index, + amount_consolidated + FROM + blocks_consolidation_requests_v2 + INNER JOIN validators ON blocks_consolidation_requests_v2.source_pubkey = validators.pubkey + INNER JOIN blocks ON blocks_consolidation_requests_v2.block_processed_root = blocks.blockroot + WHERE + slot_processed >= $1 + AND slot_processed <= $2 + AND blocks_consolidation_requests_v2.amount_consolidated > 0 + AND ( + blocks.status = '1' + OR blocks.slot = 0 + ) + AND blocks_consolidation_requests_v2.status = 'completed' + AND (cardinality($3::int[]) = 0 OR validators.validatorindex = ANY($3)) + ) AS a + GROUP BY + validatorindex; + ` + + err := WriterDb.Select(&ret, query, slotRange.StartSlot, slotRange.EndSlot, validatorsPQArray) + + if err != nil { + return nil, fmt.Errorf("error getting validator withdrawals and outgoing consolidations: %w", err) + } + return ret, nil } func GetFirstActivationEpoch(validators []uint64, firstActivationEpoch *uint64) error { @@ -3124,17 +3341,6 @@ func GetFirstActivationEpoch(validators []uint64, firstActivationEpoch *uint64) `, validatorsPQArray) } -func GetValidatorDepositsForSlots(validators []uint64, fromSlot uint64, toSlot uint64, deposits *uint64) error { - validatorsPQArray := pq.Array(validators) - return ReaderDb.Get(deposits, ` - SELECT - COALESCE(SUM(amount), 0) - FROM blocks_deposits d - INNER JOIN blocks b ON b.blockroot = d.block_root AND b.status = '1' and b.slot >= $2 and b.slot <= $3 - WHERE publickey IN (SELECT pubkey FROM validators WHERE validatorindex = ANY($1)) - `, validatorsPQArray, fromSlot, toSlot) -} - func GetValidatorWithdrawalsForSlots(validators []uint64, fromSlot uint64, toSlot uint64, withdrawals *uint64) error { validatorsPQArray := pq.Array(validators) return ReaderDb.Get(withdrawals, ` diff --git a/db/db_test.go b/db/db_test.go index 60d3107415..c1f653d4ab 100644 --- a/db/db_test.go +++ b/db/db_test.go @@ -51,7 +51,7 @@ func TestTxRevertTransformer(t *testing.T) { } ReaderDb = noSQLReaderDb{} - bt, err := InitBigtableWithCache(context.Background(), "test", "instanceTest", "1", noRedis{}) + bt, err := InitBigtableWithCache("test", "instanceTest", "1", noRedis{}) if err != nil { t.Fatal(err) } diff --git a/db/ens.go b/db/ens.go index ec2440236b..d47a410c8e 100644 --- a/db/ens.go +++ b/db/ens.go @@ -91,6 +91,7 @@ func (bigtable *Bigtable) TransformEnsNameRegistered(blk *types.Eth1Block, cache ensCrontractAddresses = ensContracts.ENSCrontractAddressesHolesky case "11155111": ensCrontractAddresses = ensContracts.ENSCrontractAddressesSepolia + // TODO hoodi default: return bulkData, bulkMetadataUpdates, nil } @@ -382,8 +383,13 @@ func (bigtable *Bigtable) ImportEnsUpdates(client *ethclient.Client, readBatchSi } g.Go(func() error { - if name != "" { - err := validateEnsName(client, name, &alreadyChecked) + normalizedName, err := go_ens.Normalize(name) + if err != nil { + utils.LogWarn(err, fmt.Sprintf("error normalizing name [%v]", name), 0) + return nil + } + if normalizedName != "" { + err := validateEnsName(client, normalizedName, &alreadyChecked) if err != nil { return fmt.Errorf("error validating new name [%v]: %w", name, err) } diff --git a/db/migrations/20250409140914_pending_deposits_queue.sql b/db/migrations/20250409140914_pending_deposits_queue.sql new file mode 100644 index 0000000000..7caf74f9f8 --- /dev/null +++ b/db/migrations/20250409140914_pending_deposits_queue.sql @@ -0,0 +1,26 @@ +-- +goose Up +-- +goose StatementBegin + +CREATE TABLE IF NOT EXISTS pending_deposits_queue ( + id INT NOT NULL, + validator_index BIGINT, -- nullable, null = deposit, not null = topup + pubkey BYTEA NOT NULL, + withdrawal_credentials BYTEA NOT NULL, + amount BIGINT NOT NULL, --gwei + signature BYTEA NOT NULL, + slot BIGINT NOT NULL, + queued_balance_ahead BIGINT NOT NULL, --gwei + est_clear_epoch BIGINT NOT NULL +); + +CREATE INDEX idx_pending_deposits_queue_pubkey ON pending_deposits_queue (pubkey); +CREATE INDEX idx_pending_deposits_queue_id ON pending_deposits_queue (id); + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +DROP TABLE pending_deposits_queue; + +-- +goose StatementEnd diff --git a/db/migrations/20250818071414_add_blocks_bls_change_validatorindex_index.sql b/db/migrations/20250818071414_add_blocks_bls_change_validatorindex_index.sql new file mode 100644 index 0000000000..d34f4ebc67 --- /dev/null +++ b/db/migrations/20250818071414_add_blocks_bls_change_validatorindex_index.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_blocks_bls_change_validatorindex ON blocks_bls_change (validatorindex); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP INDEX CONCURRENTLY IF EXISTS idx_blocks_bls_change_validatorindex; +-- +goose StatementEnd diff --git a/db/statistics.go b/db/statistics.go index dea0eb9a11..f3bc94b119 100644 --- a/db/statistics.go +++ b/db/statistics.go @@ -79,19 +79,20 @@ func WriteValidatorStatisticsForDay(day uint64, client rpc.Client) error { return fmt.Errorf("cannot export day %v as day %v has not yet been exported yet", day, int64(day)-1) } - maxValidatorIndex, err := BigtableClient.GetMaxValidatorindexForEpoch(lastEpoch) + state, err := client.GetValidatorState(lastEpoch) if err != nil { - return err + return fmt.Errorf("error retrieving validator state for epoch %v: %w", lastEpoch, err) } - validators := make([]uint64, 0, maxValidatorIndex) - validatorData := make([]*types.ValidatorStatsTableDbRow, 0, maxValidatorIndex) + + validators := make([]uint64, 0, len(state.Data)) + validatorData := make([]*types.ValidatorStatsTableDbRow, 0, len(state.Data)) validatorDataMux := &sync.Mutex{} - logger.Infof("processing statistics for validators 0-%d", maxValidatorIndex) - for i := uint64(0); i <= maxValidatorIndex; i++ { - validators = append(validators, i) + logger.Infof("processing statistics for validators 0-%d", len(state.Data)) + for i := 0; i < len(state.Data); i++ { + validators = append(validators, uint64(state.Data[i].Index)) validatorData = append(validatorData, &types.ValidatorStatsTableDbRow{ - ValidatorIndex: i, + ValidatorIndex: uint64(state.Data[i].Index), Day: int64(day), }) } @@ -839,25 +840,10 @@ func gatherValidatorDepositWithdrawals(day uint64, data []*types.ValidatorStatsT logger.Infof("gathering deposits + withdrawals") - type resRowDeposits struct { - ValidatorIndex uint64 `db:"validatorindex"` - Deposits uint64 `db:"deposits"` - DepositsAmount uint64 `db:"deposits_amount"` - } - resDeposits := make([]*resRowDeposits, 0, 1024) - depositsQry := ` - select validators.validatorindex, count(*) AS deposits, sum(amount) AS deposits_amount - from blocks_deposits - inner join validators on blocks_deposits.publickey = validators.pubkey - inner join blocks on blocks_deposits.block_root = blocks.blockroot - where blocks.slot >= $1 and blocks.slot <= $2 and (blocks.status = '1' OR blocks.slot = 0) and blocks_deposits.valid_signature - group by validators.validatorindex` - - err := WriterDb.Select(&resDeposits, depositsQry, firstSlot, lastSlot) + resDeposits, err := GetValidatorDepositsAndIncomingConsolidations(&SlotRange{StartSlot: firstSlot, EndSlot: lastSlot}, nil) if err != nil { return fmt.Errorf("error retrieving deposits for day [%v], firstSlot [%v] and lastSlot [%v]: %w", day, firstSlot, lastSlot, err) } - mux.Lock() for _, r := range resDeposits { data[r.ValidatorIndex].Deposits = int64(r.Deposits) @@ -865,19 +851,7 @@ func gatherValidatorDepositWithdrawals(day uint64, data []*types.ValidatorStatsT } mux.Unlock() - type resRowWithdrawals struct { - ValidatorIndex uint64 `db:"validatorindex"` - Withdrawals uint64 `db:"withdrawals"` - WithdrawalsAmount uint64 `db:"withdrawals_amount"` - } - resWithdrawals := make([]*resRowWithdrawals, 0, 1024) - - withdrawalsQuery := `select validatorindex, count(*) AS withdrawals, sum(amount) AS withdrawals_amount - from blocks_withdrawals - inner join blocks on blocks_withdrawals.block_root = blocks.blockroot - where block_slot >= $1 and block_slot <= $2 and blocks.status = '1' - group by validatorindex;` - err = WriterDb.Select(&resWithdrawals, withdrawalsQuery, firstSlot, lastSlot) + resWithdrawals, err := GetValidatorWithdrawalsAndOutgoingConsolidations(&SlotRange{StartSlot: firstSlot, EndSlot: lastSlot}, nil) if err != nil { return fmt.Errorf("error retrieving withdrawals for day [%v], firstSlot [%v] and lastSlot [%v]: %w", day, firstSlot, lastSlot, err) } @@ -1077,7 +1051,8 @@ func gatherValidatorMissedAttestationsStatisticsForDay(validators []uint64, day completedEpochData := epochParticipation[completedEpoch] if completedEpochData == nil { - return fmt.Errorf("logic error, did not retrieve data for epoch %v", completedEpoch) + logger.Errorf("logic error, did not retrieve data for epoch %v (maybe epoch had not slots)", completedEpoch) + continue } mux.Lock() @@ -1296,7 +1271,14 @@ func GetValidatorIncomeHistory(validatorIndices []uint64, lowerBoundDay uint64, var lastDeposits uint64 g.Go(func() error { - return GetValidatorDepositsForSlots(validatorIndices, firstSlot, lastSlot, &lastDeposits) + deposits, err := GetValidatorDepositsAndIncomingConsolidations(&SlotRange{StartSlot: firstSlot, EndSlot: lastSlot}, validatorIndices) + if err != nil { + return err + } + for _, deposit := range deposits { + lastDeposits += deposit.DepositsAmount + } + return nil }) var lastWithdrawals uint64 diff --git a/db/utils.go b/db/utils.go index 165ee09bcf..1541255cb7 100644 --- a/db/utils.go +++ b/db/utils.go @@ -1,16 +1,63 @@ package db -func isSubset[E comparable](big []E, short []E) bool { - if len(short) == 0 { - return false - } - if len(big) < len(short) { - return false +import ( + "context" + "fmt" + + pgxdecimal "github.com/jackc/pgx-shopspring-decimal" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/stdlib" + "github.com/jmoiron/sqlx" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func ClearAndCopyToTable[T []any](db *sqlx.DB, tableName string, columns []string, data []T) error { + conn, err := db.Conn(context.Background()) + if err != nil { + return fmt.Errorf("error retrieving raw sql connection: %w", err) } - for i := 0; i < len(short); i++ { - if big[i] != short[i] { - return false + defer conn.Close() + err = conn.Raw(func(driverConn interface{}) error { + conn := driverConn.(*stdlib.Conn).Conn() + + pgxdecimal.Register(conn.TypeMap()) + tx, err := conn.Begin(context.Background()) + + if err != nil { + return err + } + defer func() { + err := tx.Rollback(context.Background()) + if err != nil && !errors.Is(err, pgx.ErrTxClosed) { + logrus.Error(err, "error rolling back transaction", 0) + } + }() + + // clear + _, err = tx.Exec(context.Background(), fmt.Sprintf("TRUNCATE TABLE %s", tableName)) + if err != nil { + return errors.Wrap(err, "failed to truncate table") + } + + // copy + _, err = tx.CopyFrom(context.Background(), pgx.Identifier{tableName}, columns, + pgx.CopyFromSlice(len(data), func(i int) ([]interface{}, error) { + return data[i], nil + })) + + if err != nil { + return err + } + + err = tx.Commit(context.Background()) + if err != nil { + return err } + return nil + }) + if err != nil { + return fmt.Errorf("error copying data to %s: %w", tableName, err) } - return true + return nil } diff --git a/eth1data/eth1data.go b/eth1data/eth1data.go index 0b491f04bc..965fb5b7e2 100644 --- a/eth1data/eth1data.go +++ b/eth1data/eth1data.go @@ -91,7 +91,7 @@ func GetEth1Transaction(hash common.Hash, currency string) (*types.Eth1TxData, e txPageData.BlockNumber = header.Number.Int64() txPageData.Timestamp = time.Unix(int64(header.Time), 0) - msg, err := core.TransactionToMessage(tx, geth_types.NewCancunSigner(tx.ChainId()), header.BaseFee) + msg, err := core.TransactionToMessage(tx, geth_types.NewPragueSigner(tx.ChainId()), header.BaseFee) if err != nil { return nil, fmt.Errorf("error getting sender of tx: %w", err) } @@ -135,12 +135,12 @@ func GetEth1Transaction(hash common.Hash, currency string) (*types.Eth1TxData, e } } - data, err := rpc.CurrentErigonClient.TraceParityTx(tx.Hash().Hex()) + data, err := rpc.CurrentErigonClient.TraceGethTx(tx.Hash().Hex()) if err != nil { return nil, fmt.Errorf("failed to get parity trace for revert reason: %w", err) } if receipt.Status != 1 { - errorMsg, err := abi.UnpackRevert(utils.MustParseHex(data[0].Result.Output)) + errorMsg, err := abi.UnpackRevert(utils.MustParseHex(data[0].Output)) if err == nil { txPageData.ErrorMsg = errorMsg } @@ -150,7 +150,7 @@ func GetEth1Transaction(hash common.Hash, currency string) (*types.Eth1TxData, e return nil, fmt.Errorf("error loading token transfers from tx: %w", err) } } - txPageData.InternalTxns, err = db.BigtableClient.GetInternalTransfersForTransaction(tx.Hash().Bytes(), msg.From.Bytes(), data, currency) + txPageData.InternalTxns, err = db.BigtableClient.GetInternalTransfersForTransaction(tx.Hash().Bytes(), msg.From.Bytes(), data, currency, receipt.BlockNumber) if err != nil { return nil, fmt.Errorf("error loading internal transfers from tx: %w", err) } @@ -288,6 +288,47 @@ func GetEth1Transaction(hash common.Hash, currency string) (*types.Eth1TxData, e } } + // pectra + for _, v := range txPageData.Events { + if v.Address == common.HexToAddress(utils.Config.Chain.PectraWithdrawalRequestContractAddress) { + var d types.WithdrawalRequestInteraction + elData := types.ElWithdrawalRequestData(v.Data) + d.SourceAddress, _ = elData.GetSourceAddress() + d.ValidatorPubkey, _ = elData.GetValidatorPubkey() + d.Amount, _ = elData.GetAmount() + if d.IsExitRequest() { + v.Name = "ExitRequest" + } else { + v.Name = "WithdrawalRequest" + } + + if d.SourceAddress.Bytes() == nil || d.ValidatorPubkey == nil || d.Amount == nil { + logger.Warnf("error decoding ElWithdrawalRequestData for event [%v]: %v", v.Name, err) + continue + } + + txPageData.Withdrawals = append(txPageData.Withdrawals, d) + } else if v.Address == common.HexToAddress(utils.Config.Chain.PectraConsolidationRequestContractAddress) { + var d types.ConsolidationRequestInteraction + elData := types.ElConsolidationRequestData(v.Data) + d.SourceAddress, _ = elData.GetSourceAddress() + d.SourceValidatorPubkey, _ = elData.GetSourceValidatorPubkey() + d.TargetValidatorPubkey, _ = elData.GetTargetValidatorPubkey() + if d.IsMoveToCompounding() { + v.Name = "CompoundingRequest" + } else { + v.Name = "ConsolidationRequest" + } + + if d.SourceAddress.Bytes() == nil || d.TargetValidatorPubkey == nil || d.SourceValidatorPubkey == nil { + logger.Warnf("error decoding ElConsolidationRequestData for event [%v]: %v", v.Name, err) + continue + } + + txPageData.Consolidations = append(txPageData.Consolidations, d) + } + } + err = cache.TieredCache.Set(cacheKey, txPageData, utils.Day) if err != nil { return nil, fmt.Errorf("error writing data for tx to cache: %w", err) diff --git a/eth1data/eth1data_test.go b/eth1data/eth1data_test.go index 35ea332c6d..5992e34994 100644 --- a/eth1data/eth1data_test.go +++ b/eth1data/eth1data_test.go @@ -57,7 +57,7 @@ func TestGetEth1Transaction(t *testing.T) { price.Init(1, node, "ETH", "ETH") - bt, err := db.InitBigtableWithCache(context.Background(), "test", "instanceTest", "1", noRedis{}) + bt, err := db.InitBigtableWithCache("test", "instanceTest", "1", noRedis{}) if err != nil { t.Fatal(err) } diff --git a/ethClients/ethClients.go b/ethClients/ethClients.go index b7ab5ccb08..0e457c5b7a 100644 --- a/ethClients/ethClients.go +++ b/ethClients/ethClients.go @@ -246,7 +246,7 @@ func updateEthClient() { ethClients.Lighthouse.ClientReleaseVersion, ethClients.Lighthouse.ClientReleaseDate = prepareEthClientData("/sigp/lighthouse", "Lighthouse", curTime) ethClients.Lodestar.ClientReleaseVersion, ethClients.Lodestar.ClientReleaseDate = prepareEthClientData("/chainsafe/lodestar", "Lodestar", curTime) - ethClients.RocketpoolSmartnode.ClientReleaseVersion, ethClients.RocketpoolSmartnode.ClientReleaseDate = prepareEthClientData("/rocket-pool/smartnode-install", "Rocketpool", curTime) + ethClients.RocketpoolSmartnode.ClientReleaseVersion, ethClients.RocketpoolSmartnode.ClientReleaseDate = prepareEthClientData("/rocket-pool/smartnode", "Rocketpool", curTime) ethClients.MevBoost.ClientReleaseVersion, ethClients.MevBoost.ClientReleaseDate = prepareEthClientData("/flashbots/mev-boost", "MEV-Boost", curTime) ethClients.LastUpdate = curTime diff --git a/exporter/eth1.go b/exporter/eth1.go index 0f106ecba1..69d0df2571 100644 --- a/exporter/eth1.go +++ b/exporter/eth1.go @@ -17,16 +17,16 @@ import ( gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" gethRPC "github.com/ethereum/go-ethereum/rpc" - "github.com/prysmaticlabs/prysm/v3/contracts/deposit" - "github.com/prysmaticlabs/prysm/v3/crypto/hash" - "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" - ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/contracts/deposit" + "github.com/prysmaticlabs/prysm/v5/crypto/hash" + "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/sirupsen/logrus" ) var eth1LookBack = uint64(100) var eth1MaxFetch = uint64(1000) -var eth1DepositEventSignature = hash.HashKeccak256([]byte("DepositEvent(bytes,bytes,bytes,bytes,bytes)")) +var eth1DepositEventSignature = hash.Keccak256([]byte("DepositEvent(bytes,bytes,bytes,bytes,bytes)")) var eth1DepositContractFirstBlock uint64 var eth1DepositContractAddress common.Address var eth1Client *ethclient.Client @@ -229,7 +229,7 @@ func fetchEth1Deposits(fromBlock, toBlock uint64) (depositsToSave []*types.Eth1D if chainID == nil { return depositsToSave, fmt.Errorf("error getting tx-chainId for eth1-deposit") } - signer := gethTypes.NewCancunSigner(chainID) + signer := gethTypes.NewPragueSigner(chainID) sender, err := signer.Sender(tx) if err != nil { return depositsToSave, fmt.Errorf("error getting sender for eth1-deposit (txHash: %x, chainID: %v): %w", d.TxHash, chainID, err) diff --git a/exporter/exporter.go b/exporter/exporter.go index 5eb072f014..fed357c8db 100644 --- a/exporter/exporter.go +++ b/exporter/exporter.go @@ -23,6 +23,7 @@ func Start(client rpc.Client) { go checkSubscriptions() go syncCommitteesExporter(client) go syncCommitteesCountExporter() + go NewPendingQueueIndexer(client).Start() if utils.Config.SSVExporter.Enabled { go ssvExporter() } diff --git a/exporter/queues.go b/exporter/queues.go new file mode 100644 index 0000000000..d000c09c6d --- /dev/null +++ b/exporter/queues.go @@ -0,0 +1,314 @@ +// Copyright (C) 2025 Bitfly GmbH +// +// This file is part of Beaconchain Dashboard. +// +// Beaconchain Dashboard is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Beaconchain Dashboard is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Beaconchain Dashboard. If not, see . + +package exporter + +import ( + "database/sql" + "fmt" + "sync" + "time" + + "github.com/gobitfly/eth2-beaconchain-explorer/db" + "github.com/gobitfly/eth2-beaconchain-explorer/rpc" + "github.com/gobitfly/eth2-beaconchain-explorer/types" + "github.com/gobitfly/eth2-beaconchain-explorer/utils" + "github.com/gobitfly/eth2-beaconchain-explorer/version" + "github.com/jmoiron/sqlx" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +type PendingQueueIndexer struct { + running bool + runningMu *sync.Mutex + lc rpc.Client + db *sqlx.DB +} + +func NewPendingQueueIndexer(client rpc.Client) *PendingQueueIndexer { + indexer := &PendingQueueIndexer{ + running: false, + lc: client, + db: db.WriterDb, + runningMu: &sync.Mutex{}, + } + return indexer +} + +func (qi *PendingQueueIndexer) Start() { + qi.runningMu.Lock() + if qi.running { + qi.runningMu.Unlock() + return + } + qi.running = true + qi.runningMu.Unlock() + + logrus.WithFields(logrus.Fields{"version": version.Version}).Infof("starting pending queue indexer") + for { + err := qi.Index() + if err != nil { + logrus.WithFields(logrus.Fields{"error": err}).Errorf("failed indexing pending queue") + } + logrus.Infof("pending queue indexer finished indexing, sleeping for 10 minutes") + time.Sleep(time.Minute * 10) // interval MUST be longer than one epoch + // Background: A freshly exported validator will have an eligible epoch of max uint64, by keeping the pending deposits + // a bit longer in the db, we can rely on the pending deposits table to still get us an estimate for eligibility + } +} + +func (qi *PendingQueueIndexer) Index() error { + head, err := qi.lc.GetChainHead() + if err != nil { + return errors.Wrap(err, "failed to get chain head") + } + epoch := head.HeadEpoch + + deposits, err := qi.lc.GetPendingDeposits() + if err != nil { + return errors.Wrap(err, "failed to get pending deposits") + } + + validators, err := qi.lc.GetValidatorState(epoch) + if err != nil { + return errors.Wrap(err, "failed to get validator state") + } + + type MiniState struct { + Index uint64 + ExitEpoch uint64 + WithdrawableEpoch uint64 + } + + totalActiveEffectiveBalance := uint64(0) + pubkeyToIndexMap := make(map[string]*MiniState) + + for _, v := range validators.Data { + pubkeyToIndexMap[v.Validator.Pubkey] = &MiniState{ + Index: uint64(v.Index), + ExitEpoch: uint64(v.Validator.ExitEpoch), + WithdrawableEpoch: uint64(v.Validator.WithdrawableEpoch), + } + if epoch >= uint64(v.Validator.ActivationEpoch) && epoch < uint64(v.Validator.ExitEpoch) { + totalActiveEffectiveBalance += uint64(v.Validator.EffectiveBalance) + } + } + + etherChurnByEpoch := utils.GetActivationExitChurnLimit(totalActiveEffectiveBalance) + count := 0 + balanceAhead := uint64(0) + clearEpoch := head.HeadEpoch + 1 + + // transition period + // pre electra system will keep going for follow distance until every deposit of the last system is converted to the new system + // before the new system starts + electraQueueDelay := uint64(utils.Config.ClConfig.Eth1FollowDistance/utils.Config.ClConfig.SlotsPerEpoch + utils.Config.ClConfig.EpochsPerEth1VotingPeriod) + if clearEpoch < utils.Config.ClConfig.ElectraForkEpoch+electraQueueDelay { + clearEpoch = utils.Config.ClConfig.ElectraForkEpoch + electraQueueDelay + } + + depositsList := make([]types.PendingDeposit, 0) + + // spec vars (in snake_case) + next_deposit_index := uint64(0) + max_pending_deposits_per_epoch := utils.Config.ClConfig.MaxPendingDepositsPerEpoch + if max_pending_deposits_per_epoch == 0 { // eth mainnet spec default + max_pending_deposits_per_epoch = uint64(16) + } + processed_amount := uint64(0) + state_deposit_balance_to_consume := uint64(0) + + pending_deposits := deposits.Data + depositsToPostpone := []types.PendingDeposit{} // est differently than the spec as we just set these to the same clearEpoch as the "normal" last entry. Not snake case to highlight the different handling to spec + + // emulate spec based on current view in time (approx estimation) + // https://github.com/ethereum/consensus-specs/blob/dev/specs/electra/beacon-chain.md#new-process_pending_deposits + for { + next_epoch := clearEpoch + 1 + available_for_processing := state_deposit_balance_to_consume + etherChurnByEpoch + processed_amount = 0 + next_deposit_index = 0 + + is_churn_limit_reached := false + finalized_slot := next_epoch * utils.Config.ClConfig.SlotsPerEpoch // first slot of next epoch is finalized + // potential improvement: utils.GetActivationExitChurnLimit(totalActiveEffectiveBalance + balanceAhead - withdrawalsAhead) + + for _, deposit := range pending_deposits { + if deposit.Slot > finalized_slot { + break + } + + if next_deposit_index >= max_pending_deposits_per_epoch { + break + } + + miniState, found := pubkeyToIndexMap[deposit.Pubkey.String()] + var is_validator_exited bool + var is_validator_withdrawn bool + + if found { + is_validator_exited = miniState.ExitEpoch < 100_000_000_000 + is_validator_withdrawn = miniState.WithdrawableEpoch < next_epoch + } + + getPendingDeposit := func() types.PendingDeposit { + pendingDeposit := types.PendingDeposit{ + ID: count, + Pubkey: deposit.Pubkey, + WithdrawalCredentials: deposit.WithdrawalCredentials, + Amount: deposit.Amount, + Signature: deposit.Signature, + Slot: deposit.Slot, + ValidatorIndex: sql.NullInt64{}, + QueuedBalanceAhead: balanceAhead, + EstClearEpoch: clearEpoch, + } + + if found { + pendingDeposit.ValidatorIndex = sql.NullInt64{ + Int64: int64(miniState.Index), + Valid: true, + } + } + return pendingDeposit + } + + if is_validator_withdrawn { // do not consume churn + depositsList = append(depositsList, getPendingDeposit()) + } else if is_validator_exited { // do not consume churn + depositsToPostpone = append(depositsToPostpone, getPendingDeposit()) + } else { + is_churn_limit_reached = processed_amount+deposit.Amount > available_for_processing + if is_churn_limit_reached { + break + } + processed_amount += deposit.Amount + depositsList = append(depositsList, getPendingDeposit()) + } + + next_deposit_index++ + + // out of spec + balanceAhead += deposit.Amount + count++ + } + + pending_deposits = pending_deposits[next_deposit_index:] + + if len(pending_deposits) == 0 { + break + } + + if is_churn_limit_reached { + state_deposit_balance_to_consume = available_for_processing - processed_amount + } else { + state_deposit_balance_to_consume = 0 + } + + clearEpoch++ + } + + // treat postpones deposits differently, set to last epoch of "normal" deposits + // since we can't accurately predict them anyway if they are that far out where there are no "normal" deposits with current state + if len(depositsList) > 0 { + lastEntry := depositsList[len(depositsList)-1] + for i := range depositsToPostpone { + depositsToPostpone[i].EstClearEpoch = lastEntry.EstClearEpoch + depositsToPostpone[i].QueuedBalanceAhead = lastEntry.QueuedBalanceAhead + } + depositsList = append(depositsList, depositsToPostpone...) + } + + return qi.save(depositsList) +} + +func (*PendingQueueIndexer) matchDepositRequests(tx *sql.Tx) error { + // matching will be wrong for postponed system-deposits + // but likelihood to ever occur for one pubkey, amount, slot combo is effectively 0 + query := ` + WITH pdq_ranked AS ( + SELECT *, ROW_NUMBER() OVER ( + PARTITION BY pubkey, amount, slot ORDER BY id + ) AS rn + FROM pending_deposits_queue + ), + bdr_ranked AS ( + SELECT *, ROW_NUMBER() OVER ( + PARTITION BY pubkey, amount, slot_queued ORDER BY index_queued ASC + ) AS rn + FROM blocks_deposit_requests_v2 + WHERE status = 'queued' OR status = 'postponed' + ), + matches AS ( + SELECT pdq.id AS pdq_id, bdr.id AS bdr_id + FROM pdq_ranked pdq + JOIN bdr_ranked bdr + ON pdq.pubkey = bdr.pubkey + AND pdq.amount = bdr.amount + AND ( + pdq.slot = bdr.slot_queued AND pdq.rn = bdr.rn OR + (pdq.slot = 0 AND bdr.index_queued < 0) + ) + ) + UPDATE pending_deposits_queue + SET request_id = matches.bdr_id + FROM matches + WHERE pending_deposits_queue.id = matches.pdq_id;` + + _, err := tx.Exec(query) + if err != nil && err != sql.ErrNoRows { + return err + } + return nil +} + +func (qi *PendingQueueIndexer) save(pendingDeposits []types.PendingDeposit) error { + tx, err := qi.db.Begin() + if err != nil { + return errors.Wrap(err, "failed to start db transaction") + } + + defer tx.Rollback() + + // prepare data for bulk insert + dat := make([][]interface{}, len(pendingDeposits)) + for i, r := range pendingDeposits { + dat[i] = []interface{}{r.ID, r.ValidatorIndex, encodeToHex(r.Pubkey), encodeToHex(r.WithdrawalCredentials), r.Amount, encodeToHex(r.Signature), r.Slot, r.QueuedBalanceAhead, r.EstClearEpoch} + } + + err = db.ClearAndCopyToTable(qi.db, "pending_deposits_queue", []string{"id", "validator_index", "pubkey", "withdrawal_credentials", "amount", "signature", "slot", "queued_balance_ahead", "est_clear_epoch"}, dat) + if err != nil { + return fmt.Errorf("error copying data to pending_deposits_queue table: %w", err) + } + + err = qi.matchDepositRequests(tx) + if err != nil { + return fmt.Errorf("error matching data with blocks_deposit_requests_v2 table: %w", err) + } + + if err := tx.Commit(); err != nil { + return errors.Wrap(err, "failed to commit transaction") + } + + return nil +} + +func encodeToHex(data []byte) string { + return fmt.Sprintf("\\x%x", data) +} diff --git a/exporter/rocketpool.go b/exporter/rocketpool.go index bbf77cbe21..564ec7b328 100644 --- a/exporter/rocketpool.go +++ b/exporter/rocketpool.go @@ -13,6 +13,7 @@ import ( "github.com/gobitfly/eth2-beaconchain-explorer/db" "github.com/gobitfly/eth2-beaconchain-explorer/utils" + "github.com/pkg/errors" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" @@ -51,6 +52,7 @@ var firstBlockOfRedstone = map[string]uint64{ "mainnet": 15451165, "prater": 7287326, "holesky": 0, + "hoodi": 0, // TODO: dont know } var leb16, _ = big.NewInt(0).SetString("16000000000000000000", 10) @@ -1919,7 +1921,7 @@ func DownloadRewardsFile(fileName string, interval uint64, cid string, isDaemon } } - return nil, fmt.Errorf(errBuilder.String()) + return nil, errors.New(errBuilder.String()) } diff --git a/funding.json b/funding.json new file mode 100644 index 0000000000..0effdef184 --- /dev/null +++ b/funding.json @@ -0,0 +1,5 @@ +{ + "opRetro": { + "projectId": "0xdd81f0fbb21c949e5d67176b22a39cfdc7c774349214a450df78eb31174413db" + } +} diff --git a/go.mod b/go.mod index f0d7fc46be..0f07a31cd3 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,12 @@ module github.com/gobitfly/eth2-beaconchain-explorer -go 1.21 +go 1.23.1 -toolchain go1.22.0 +toolchain go1.23.3 require ( - cloud.google.com/go/bigtable v1.33.0 - cloud.google.com/go/secretmanager v1.14.0 + cloud.google.com/go/bigtable v1.35.0 + cloud.google.com/go/secretmanager v1.14.2 firebase.google.com/go/v4 v4.14.1 github.com/ClickHouse/clickhouse-go/v2 v2.30.0 github.com/Gurpartap/storekit-go v0.0.0-20201205024111-36b6cd5c6a21 @@ -19,23 +19,24 @@ require ( github.com/aybabtme/uniplot v0.0.0-20151203143629-039c559e5e7e github.com/carlmjohnson/requests v0.23.4 github.com/davecgh/go-spew v1.1.1 - github.com/ethereum/go-ethereum v1.13.10 + github.com/ethereum/go-ethereum v1.14.6-0.20250124151602-75526bb8e01b github.com/evanw/esbuild v0.8.23 github.com/go-redis/redis/v8 v8.11.5 github.com/gobitfly/eth-rewards v0.1.2-0.20230403064929-411ddc40a5f7 - github.com/gobitfly/eth.store v0.0.0-20240312111708-b43f13990280 + github.com/gobitfly/eth.store v0.0.0-20250125090903-cce1f5e601a4 github.com/gobitfly/scs/v2 v2.0.0-20240516120302-8754831e6b9b github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/golang-jwt/jwt/v4 v4.5.0 + github.com/golang-jwt/jwt/v4 v4.5.1 github.com/golang/protobuf v1.5.4 github.com/gomodule/redigo v1.8.0 github.com/google/go-cmp v0.6.0 github.com/gorilla/context v1.1.1 github.com/gorilla/csrf v1.7.0 github.com/gorilla/mux v1.8.0 - github.com/gorilla/websocket v1.5.1 + github.com/gorilla/websocket v1.5.3 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/jackc/pgx-shopspring-decimal v0.0.0-20220624020537-1d36b5a1853e + github.com/jackc/pgx/v4 v4.18.1 github.com/jackc/pgx/v5 v5.4.3 github.com/jmoiron/sqlx v1.2.0 github.com/juliangruber/go-intersect v1.1.0 @@ -45,16 +46,17 @@ require ( github.com/lib/pq v1.10.7 github.com/mailgun/mailgun-go/v4 v4.1.3 github.com/mitchellh/mapstructure v1.5.0 + github.com/montanaflynn/stats v0.7.1 github.com/mssola/user_agent v0.5.2 github.com/mvdan/xurls v1.1.0 github.com/phyber/negroni-gzip v0.0.0-20180113114010-ef6356a5d029 github.com/pkg/errors v0.9.1 github.com/pressly/goose/v3 v3.10.0 - github.com/prometheus/client_golang v1.18.0 - github.com/protolambda/zrnt v0.30.0 - github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 + github.com/prometheus/client_golang v1.20.0 + github.com/protolambda/zrnt v0.32.2 + github.com/prysmaticlabs/go-bitfield v0.0.0-20240328144219-a1caa50c3a1e github.com/prysmaticlabs/go-ssz v0.0.0-20210121151755-f6208871c388 - github.com/prysmaticlabs/prysm/v3 v3.2.2 + github.com/prysmaticlabs/prysm/v5 v5.2.0 github.com/rocket-pool/rocketpool-go v1.8.3-0.20240618173422-783b8668f5b4 github.com/rocket-pool/smartnode v1.13.6 github.com/shopspring/decimal v1.4.0 @@ -62,43 +64,42 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skygeario/go-confusable-homoglyphs v0.0.0-20191212061114-e2b2a60df110 github.com/stripe/stripe-go/v72 v72.50.0 - github.com/swaggo/http-swagger v1.3.0 - github.com/swaggo/swag v1.8.3 + github.com/swaggo/swag v1.16.4 github.com/urfave/negroni v1.0.0 github.com/wealdtech/go-ens/v3 v3.6.0 github.com/wealdtech/go-eth2-types/v2 v2.8.1 github.com/wealdtech/go-eth2-util v1.8.1 github.com/zesik/proxyaddr v0.0.0-20161218060608-ec32c535184d - golang.org/x/crypto v0.28.0 - golang.org/x/sync v0.8.0 - golang.org/x/text v0.19.0 - golang.org/x/time v0.6.0 - google.golang.org/api v0.197.0 - google.golang.org/grpc v1.66.2 - google.golang.org/protobuf v1.34.2 + golang.org/x/crypto v0.32.0 + golang.org/x/sync v0.10.0 + golang.org/x/text v0.21.0 + golang.org/x/time v0.9.0 + google.golang.org/api v0.217.0 + google.golang.org/grpc v1.69.4 + google.golang.org/protobuf v1.36.3 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 - golang.org/x/exp v0.0.0-20240213143201-ec583247a57a + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa ) require ( - cel.dev/expr v0.16.0 // indirect - cloud.google.com/go v0.115.1 // indirect - cloud.google.com/go/auth v0.9.3 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect - cloud.google.com/go/compute/metadata v0.5.0 // indirect - cloud.google.com/go/iam v1.2.1 // indirect - cloud.google.com/go/longrunning v0.6.1 // indirect - cloud.google.com/go/monitoring v1.21.1 // indirect + cel.dev/expr v0.16.2 // indirect + cloud.google.com/go v0.118.0 // indirect + cloud.google.com/go/auth v0.14.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect + cloud.google.com/go/compute/metadata v0.6.0 // indirect + cloud.google.com/go/iam v1.3.1 // indirect + cloud.google.com/go/longrunning v0.6.4 // indirect + cloud.google.com/go/monitoring v1.22.1 // indirect github.com/ClickHouse/ch-go v0.61.5 // indirect - github.com/DataDog/zstd v1.5.2 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect github.com/MicahParks/keyfunc v1.9.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/VictoriaMetrics/fastcache v1.12.1 // indirect + github.com/VictoriaMetrics/fastcache v1.12.2 // indirect github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/alessio/shellescape v1.4.1 // indirect github.com/alexedwards/scs/v2 v2.5.0 // indirect @@ -113,43 +114,43 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4 // indirect github.com/aws/smithy-go v1.15.0 // indirect - github.com/bits-and-blooms/bitset v1.11.0 // indirect + github.com/bits-and-blooms/bitset v1.17.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect - github.com/cncf/xds/go v0.0.0-20240822171458-6449f94b4d59 // indirect - github.com/cockroachdb/errors v1.9.1 // indirect - github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 // indirect - github.com/cockroachdb/redact v1.1.3 // indirect - github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect - github.com/consensys/bavard v0.1.13 // indirect - github.com/consensys/gnark-crypto v0.12.1 // indirect + github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect + github.com/consensys/bavard v0.1.22 // indirect + github.com/consensys/gnark-crypto v0.14.0 // indirect github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 // indirect - github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 // indirect - github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect - github.com/deckarep/golang-set/v2 v2.5.0 // indirect + github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect + github.com/crate-crypto/go-kzg-4844 v1.1.0 // indirect + github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/doug-martin/goqu v5.0.0+incompatible // indirect + github.com/doug-martin/goqu/v9 v9.19.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/envoyproxy/go-control-plane v0.13.0 // indirect + github.com/envoyproxy/go-control-plane v0.13.1 // indirect github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect - github.com/ethereum/c-kzg-4844 v0.4.0 // indirect + github.com/ethereum/c-kzg-4844 v1.0.0 // indirect + github.com/ethereum/go-verkle v0.2.2 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect - github.com/getsentry/sentry-go v0.18.0 // indirect github.com/glendc/go-external-ip v0.1.0 // indirect github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/errors v0.7.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.20.2 // indirect + github.com/go-openapi/jsonreference v0.20.4 // indirect + github.com/go-openapi/spec v0.20.14 // indirect + github.com/go-openapi/swag v0.22.9 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/s2a-go v0.1.8 // indirect + github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/holiman/uint256 v1.2.4 // indirect + github.com/holiman/uint256 v1.3.2 // indirect github.com/huandu/go-clone v1.6.0 // indirect github.com/ipfs/bbloom v0.0.4 // indirect github.com/ipfs/boxo v0.8.0 // indirect @@ -166,13 +167,15 @@ require ( github.com/ipfs/go-metrics-interface v0.0.1 // indirect github.com/ipld/go-codec-dagpb v1.6.0 // indirect github.com/ipld/go-ipld-prime v0.20.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.2 // indirect + github.com/jackc/pgx v3.6.2+incompatible // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jbenet/goprocess v0.1.4 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/kr/text v0.2.0 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/mattn/go-sqlite3 v1.11.0 // indirect + github.com/mattn/go-sqlite3 v1.14.7 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect @@ -181,6 +184,7 @@ require ( github.com/multiformats/go-multibase v0.2.0 // indirect github.com/multiformats/go-multihash v0.2.3 // indirect github.com/multiformats/go-varint v0.0.7 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/paulmach/orb v0.11.1 // indirect @@ -189,10 +193,9 @@ require ( github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/polydawn/refmt v0.89.0 // indirect github.com/protolambda/zssz v0.1.5 // indirect - github.com/prysmaticlabs/fastssz v0.0.0-20221107182844-78142813af44 // indirect - github.com/prysmaticlabs/gohashtree v0.0.4-beta // indirect + github.com/prysmaticlabs/fastssz v0.0.0-20241008181541-518c4ce73516 // indirect + github.com/prysmaticlabs/gohashtree v0.0.4-beta.0.20240624100937-73632381301b // indirect github.com/rivo/uniseg v0.4.4 // indirect - github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/wealdtech/go-bytesutil v1.2.1 // indirect @@ -203,50 +206,43 @@ require ( github.com/yusufpapurcu/wmi v1.2.3 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect + go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/sdk v1.31.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect + golang.org/x/tools v0.29.0 // indirect google.golang.org/appengine/v2 v2.0.2 // indirect - google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - lukechampine.com/blake3 v1.2.1 // indirect + google.golang.org/genproto v0.0.0-20241216192217-9240e9c98484 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + lukechampine.com/blake3 v1.3.0 // indirect rsc.io/binaryregexp v0.2.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) require ( - cloud.google.com/go/firestore v1.16.0 // indirect + cloud.google.com/go/firestore v1.17.0 // indirect cloud.google.com/go/storage v1.43.0 // indirect - github.com/BurntSushi/toml v1.2.1 // indirect - github.com/KyleBanks/depth v1.2.1 // indirect + github.com/BurntSushi/toml v1.3.2 // indirect github.com/attestantio/go-eth2-client v0.19.9 github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coocood/freecache v1.2.3 - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/fatih/color v1.16.0 // indirect github.com/ferranbt/fastssz v0.1.3 // indirect github.com/go-chi/chi v4.0.2+incompatible // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.20.2 // indirect - github.com/go-openapi/jsonreference v0.20.4 // indirect - github.com/go-openapi/spec v0.20.14 // indirect - github.com/go-openapi/swag v0.22.9 // indirect github.com/goccy/go-yaml v1.10.0 // indirect - github.com/golang/glog v1.2.1 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/glog v1.2.2 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/uuid v1.6.0 - github.com/googleapis/gax-go/v2 v2.13.0 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 // indirect github.com/hashicorp/go-version v1.6.0 github.com/herumi/bls-eth-go-binary v1.29.1 // indirect github.com/jackc/pgio v1.0.0 // indirect @@ -254,36 +250,32 @@ require ( github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgtype v1.14.0 github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.17.7 - github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/klauspost/compress v1.17.9 + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/minio/highwayhash v1.0.2 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect - github.com/prometheus/client_model v0.6.0 // indirect - github.com/prometheus/common v0.47.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/r3labs/sse/v2 v2.10.0 // indirect github.com/rs/zerolog v1.29.1 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect - github.com/supranational/blst v0.3.11 // indirect - github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect + github.com/supranational/blst v0.3.14 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e // indirect github.com/tklauser/go-sysconf v0.3.13 // indirect github.com/tklauser/numcpus v0.7.0 // indirect - go.opencensus.io v0.24.0 // indirect - golang.org/x/net v0.30.0 - golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + golang.org/x/net v0.34.0 + golang.org/x/oauth2 v0.25.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/client-go v0.25.0 // indirect ) replace github.com/json-iterator/go => github.com/prestonvanloon/go v1.1.7-0.20190722034630-4f2e55fcf87b @@ -291,7 +283,7 @@ replace github.com/json-iterator/go => github.com/prestonvanloon/go v1.1.7-0.201 // See https://github.com/prysmaticlabs/grpc-gateway/issues/2 replace github.com/grpc-ecosystem/grpc-gateway/v2 => github.com/prysmaticlabs/grpc-gateway/v2 v2.3.1-0.20210702154020-550e1cd83ec1 -replace github.com/prysmaticlabs/prysm/v3 => github.com/gobitfly/prysm/v3 v3.0.0-20230216184552-2f3f1e8190d5 +// replace github.com/prysmaticlabs/prysm/v3 => github.com/gobitfly/prysm/v3 v3.0.0-20230216184552-2f3f1e8190d5 replace github.com/wealdtech/go-merkletree v1.0.1-0.20190605192610-2bb163c2ea2a => github.com/rocket-pool/go-merkletree v1.0.1-0.20220406020931-c262d9b976dd diff --git a/go.sum b/go.sum index 72b373c73c..b7347fc61e 100644 --- a/go.sum +++ b/go.sum @@ -1,95 +1,51 @@ -cel.dev/expr v0.16.0 h1:yloc84fytn4zmJX2GU3TkXGsaieaV7dQ057Qs4sIG2Y= -cel.dev/expr v0.16.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ= -cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc= -cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U= -cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk= -cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= -cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/bigtable v1.33.0 h1:2BDaWLRAwXO14DJL/u8crbV2oUbMZkIa2eGq8Yao1bk= -cloud.google.com/go/bigtable v1.33.0/go.mod h1:HtpnH4g25VT1pejHRtInlFPnN5sjTxbQlsYBjh9t5l0= -cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= -cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/firestore v1.16.0 h1:YwmDHcyrxVRErWcgxunzEaZxtNbc8QoFYA/JOEwDPgc= -cloud.google.com/go/firestore v1.16.0/go.mod h1:+22v/7p+WNBSQwdSwP57vz47aZiY+HrDkrOsJNhk7rg= -cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU= -cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g= -cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc= -cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= -cloud.google.com/go/monitoring v1.21.1 h1:zWtbIoBMnU5LP9A/fz8LmWMGHpk4skdfeiaa66QdFGc= -cloud.google.com/go/monitoring v1.21.1/go.mod h1:Rj++LKrlht9uBi8+Eb530dIrzG/cU/lB8mt+lbeFK1c= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/secretmanager v1.14.0 h1:P2RRu2NEsQyOjplhUPvWKqzDXUKzwejHLuSUBHI8c4w= -cloud.google.com/go/secretmanager v1.14.0/go.mod h1:q0hSFHzoW7eRgyYFH8trqEFavgrMeiJI4FETNN78vhM= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cel.dev/expr v0.16.2 h1:RwRhoH17VhAu9U5CMvMhH1PDVgf0tuz9FT+24AfMLfU= +cel.dev/expr v0.16.2/go.mod h1:gXngZQMkWJoSbE8mOzehJlXQyubn/Vg0vR9/F3W7iw8= +cloud.google.com/go v0.118.0 h1:tvZe1mgqRxpiVa3XlIGMiPcEUbP1gNXELgD4y/IXmeQ= +cloud.google.com/go v0.118.0/go.mod h1:zIt2pkedt/mo+DQjcT4/L3NDxzHPR29j5HcclNH+9PM= +cloud.google.com/go/auth v0.14.0 h1:A5C4dKV/Spdvxcl0ggWwWEzzP7AZMJSEIgrkngwhGYM= +cloud.google.com/go/auth v0.14.0/go.mod h1:CYsoRL1PdiDuqeQpZE0bP2pnPrGqFcOkI0nldEQis+A= +cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= +cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= +cloud.google.com/go/bigtable v1.35.0 h1:UEacPwaejN2mNbz67i1Iy3G812rxtgcs6ePj1TAg7dw= +cloud.google.com/go/bigtable v1.35.0/go.mod h1:EabtwwmTcOJFXp+oMZAT/jZkyDIjNwrv53TrS4DGrrM= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/firestore v1.17.0 h1:iEd1LBbkDZTFsLw3sTH50eyg4qe8eoG6CjocmEXO9aQ= +cloud.google.com/go/firestore v1.17.0/go.mod h1:69uPx1papBsY8ZETooc71fOhoKkD70Q1DwMrtKuOT/Y= +cloud.google.com/go/iam v1.3.1 h1:KFf8SaT71yYq+sQtRISn90Gyhyf4X8RGgeAVC8XGf3E= +cloud.google.com/go/iam v1.3.1/go.mod h1:3wMtuyT4NcbnYNPLMBzYRFiEfjKfJlLVLrisE7bwm34= +cloud.google.com/go/longrunning v0.6.4 h1:3tyw9rO3E2XVXzSApn1gyEEnH2K9SynNQjMlBi3uHLg= +cloud.google.com/go/longrunning v0.6.4/go.mod h1:ttZpLCe6e7EXvn9OxpBRx7kZEB0efv8yBO6YnVMfhJs= +cloud.google.com/go/monitoring v1.22.1 h1:KQbnAC4IAH+5x3iWuPZT5iN9VXqKMzzOgqcYB6fqPDE= +cloud.google.com/go/monitoring v1.22.1/go.mod h1:AuZZXAoN0WWWfsSvET1Cpc4/1D8LXq8KRDU87fMS6XY= +cloud.google.com/go/secretmanager v1.14.2 h1:2XscWCfy//l/qF96YE18/oUaNJynAx749Jg3u0CjQr8= +cloud.google.com/go/secretmanager v1.14.2/go.mod h1:Q18wAPMM6RXLC/zVpWTlqq2IBSbbm7pKBlM3lCKsmjw= cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= -contrib.go.opencensus.io/exporter/jaeger v0.2.1 h1:yGBYzYMewVL0yO9qqJv3Z5+IRhPdU7e9o/2oKpX4YvI= -contrib.go.opencensus.io/exporter/jaeger v0.2.1/go.mod h1:Y8IsLgdxqh1QxYxPC5IgXVmBaeLUeQFfBeBi9PbeZd0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= firebase.google.com/go/v4 v4.14.1 h1:4qiUETaFRWoFGE1XP5VbcEdtPX93Qs+8B/7KvP2825g= firebase.google.com/go/v4 v4.14.1/go.mod h1:fgk2XshgNDEKaioKco+AouiegSI9oTWVqRaBdTTGBoM= -github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4= github.com/ClickHouse/ch-go v0.61.5/go.mod h1:s1LJW/F/LcFs5HJnuogFMta50kKDO0lf9zzfrbl0RQg= github.com/ClickHouse/clickhouse-go/v2 v2.30.0 h1:AG4D/hW39qa58+JHQIFOSnxyL46H6h2lrmGGk17dhFo= github.com/ClickHouse/clickhouse-go/v2 v2.30.0/go.mod h1:i9ZQAojcayW3RsdCb3YR+n+wC2h65eJsZCscZ1Z1wyo= -github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= -github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= -github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= -github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= +github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Gurpartap/storekit-go v0.0.0-20201205024111-36b6cd5c6a21 h1:HcdvlzaQ4CJfH7xbfJZ3ZHN//BTEpId46iKEMuP3wHE= github.com/Gurpartap/storekit-go v0.0.0-20201205024111-36b6cd5c6a21/go.mod h1:7PODFS++oNZ6khojmPBvkrDeFO/hrc3jmvWvQAOXorw= -github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o= github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= -github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= -github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= +github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= @@ -103,11 +59,6 @@ github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKS github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/attestantio/go-eth2-client v0.19.9 h1:g5LLX3X7cLC0KS0oai/MtxBOZz3U3QPIX5qryYMxgVE= @@ -149,39 +100,25 @@ github.com/aws/smithy-go v1.15.0 h1:PS/durmlzvAFpQHDs4wi4sNNP9ExsqZh6IlfdHXgKK8= github.com/aws/smithy-go v1.15.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aybabtme/uniplot v0.0.0-20151203143629-039c559e5e7e h1:dSeuFcs4WAJJnswS8vXy7YY1+fdlbVPuEVmDAfqvFOQ= github.com/aybabtme/uniplot v0.0.0-20151203143629-039c559e5e7e/go.mod h1:uh71c5Vc3VNIplXOFXsnDy21T1BepgT32c5X/YPrOyc= -github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= -github.com/bazelbuild/rules_go v0.23.2 h1:Wxu7JjqnF78cKZbsBsARLSXx/jlGaSLCnUV3mTlyHvM= -github.com/bazelbuild/rules_go v0.23.2/go.mod h1:MC23Dc/wkXEyk3Wpq6lCqz0ZAYOZDw2DR5y3N1q2i7M= +github.com/bazelbuild/rules_go v0.49.0 h1:5vCbuvy8Q11g41lseGJDc5vxhDjJtfxr6nM/IC4VmqM= +github.com/bazelbuild/rules_go v0.49.0/go.mod h1:Dhcz716Kqg1RHNWos+N6MlXNkjNP2EwZQ0LukRKJfMs= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bits-and-blooms/bitset v1.11.0 h1:RMyy2mBBShArUAhfVRZJ2xyBO58KCBCtZFShw3umo6k= -github.com/bits-and-blooms/bitset v1.11.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bits-and-blooms/bitset v1.17.0 h1:1X2TS7aHz1ELcC0yU1y2stUs/0ig5oMU6STFZGrhvHI= +github.com/bits-and-blooms/bitset v1.17.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/btcsuite/btcd v0.23.4 h1:IzV6qqkfwbItOS/sg/aDfPDsjPP8twrCOE2R93hxMlQ= -github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= -github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/bufbuild/buf v0.37.0/go.mod h1:lQ1m2HkIaGOFba6w/aC3KYBHhKEOESP3gaAEpS3dAFM= github.com/carlmjohnson/requests v0.23.4 h1:AxcvapfB9RPXLSyvAHk9YJoodQ43ZjzNHj6Ft3tQGdg= github.com/carlmjohnson/requests v0.23.4/go.mod h1:Qzp6tW4DQyainPP+tGwiJTzwxvElTIKm0B191TgTtOA= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU= github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -190,57 +127,41 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20240822171458-6449f94b4d59 h1:fLZ97KE86ELjEYJCEUVzmbhfzDxHHGwBrDVMd4XL6Bs= -github.com/cncf/xds/go v0.0.0-20240822171458-6449f94b4d59/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= -github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= -github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= +github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= -github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593/go.mod h1:6hk1eMY/u5t+Cf18q5lFMUA1Rc+Sm5I6Ra1QuPyxXCo= -github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= -github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/pebble v1.1.2 h1:CUh2IPtR4swHlEj48Rhfzw6l/d0qA31fItcIszQVIsA= +github.com/cockroachdb/pebble v1.1.2/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= -github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= -github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= -github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= -github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= -github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/consensys/bavard v0.1.22 h1:Uw2CGvbXSZWhqK59X0VG/zOjpTFuOMcPLStrp1ihI0A= +github.com/consensys/bavard v0.1.22/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= +github.com/consensys/gnark-crypto v0.14.0 h1:DDBdl4HaBtdQsq/wfMwJvZNE80sHidrK3Nfrefatm0E= +github.com/consensys/gnark-crypto v0.14.0/go.mod h1:CU4UijNPsHawiVGNxe9co07FkzCeWHHrb1li/n1XoU0= github.com/coocood/freecache v1.2.3 h1:lcBwpZrwBZRZyLk/8EMyQVXRiFl663cCuMOrjCALeto= github.com/coocood/freecache v1.2.3/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg= github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE= -github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= -github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= -github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= -github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= +github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= +github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= +github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= +github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis= github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= @@ -248,42 +169,40 @@ github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkE 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/v2 v2.5.0 h1:hn6cEZtQ0h3J8kFrHR/NrzyOoTnjgW1+FmNJzQ7y/sA= -github.com/deckarep/golang-set/v2 v2.5.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= +github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 h1:C7t6eeMaEQVy6e8CarIhscYQlNmw5e3G36y7l7Y21Ao= github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0/go.mod h1:56wL82FO0bfMU5RvfXoIwSOP2ggqqxT+tAfNEIyxuHw= +github.com/doug-martin/goqu v5.0.0+incompatible h1:C7O6xQYoWpSGX32C1faMJWe1s82Ktr2jjWf2joReiSQ= +github.com/doug-martin/goqu v5.0.0+incompatible/go.mod h1:4xBntUHXkdIh+CnYd+I2kdHgUTq1kJ+p7OBCngg7RrY= +github.com/doug-martin/goqu/v9 v9.19.0 h1:PD7t1X3tRcUiSdc5TEyOFKujZA5gs3VSA7wxSvBx7qo= +github.com/doug-martin/goqu/v9 v9.19.0/go.mod h1:nf0Wc2/hV3gYK9LiyqIrzBEVGlI8qW3GuDCEobC4wBQ= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.13.0 h1:HzkeUz1Knt+3bK+8LG1bxOO/jzWZmdxpwC51i202les= -github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE= +github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= -github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= -github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/ethereum/go-ethereum v1.13.10 h1:Ppdil79nN+Vc+mXfge0AuUgmKWuVv4eMqzoIVSdqZek= -github.com/ethereum/go-ethereum v1.13.10/go.mod h1:sc48XYQxCzH3fG9BcrXCOOgQk2JfZzNAmIKnceogzsA= +github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= +github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/go-ethereum v1.14.6-0.20250124151602-75526bb8e01b h1:G8jJ0o196aZUEwRE9+FUcllEFwXnKDK3esCzmldjGrI= +github.com/ethereum/go-ethereum v1.14.6-0.20250124151602-75526bb8e01b/go.mod h1:4q+4t48P2C03sjqGvTXix5lEOplf5dz4CTosbjt5tGs= +github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= +github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= github.com/evanw/esbuild v0.8.23 h1:eRRG1fNtQ9KPG3lM62EUYagLVMSuxSTBEgukqY0et3w= github.com/evanw/esbuild v0.8.23/go.mod h1:y2AFBAGVelPqPodpdtxWWqe6n2jYf5FrsJbligmRmuw= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= @@ -292,18 +211,17 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= -github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/ferranbt/fastssz v0.1.3 h1:ZI+z3JH05h4kgmFXdHuR1aWYsgrg7o+Fw7/NCzM16Mo= github.com/ferranbt/fastssz v0.1.3/go.mod h1:0Y9TEd/9XuFlh7mskMPfXiI2Dkw4Ddg9EyXt1W7MRvE= -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/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= +github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= +github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= @@ -312,44 +230,24 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= -github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= -github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= -github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= -github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= -github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= -github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= -github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= +github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= +github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/glendc/go-external-ip v0.1.0 h1:iX3xQ2Q26atAmLTbd++nUce2P5ht5P4uD4V7caSY/xg= github.com/glendc/go-external-ip v0.1.0/go.mod h1:CNx312s2FLAJoWNdJWZ2Fpf5O4oLsMFwuYviHjS4uJE= -github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-chi/chi v4.0.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= -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-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -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/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -363,152 +261,106 @@ github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZC github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= -github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-playground/validator/v10 v10.13.0 h1:cFRQdfaSMCOSfGCCLB20MHvuoHb/s5G8L5pu2ppK5AQ= +github.com/go-playground/validator/v10 v10.13.0/go.mod h1:dwu7+CG8/CtBiJFZDz4e+5Upb6OLw04gtBYw0mcG/z4= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/gobitfly/eth-rewards v0.1.2-0.20230403064929-411ddc40a5f7 h1:qAEBlj4j25sJjsj5CQraL94rY9jyPIjCZ+hlu84n5l0= github.com/gobitfly/eth-rewards v0.1.2-0.20230403064929-411ddc40a5f7/go.mod h1:JrFv2uq5i+p6yEoRSHralEV139HkPV0wBjZZHWb4he8= -github.com/gobitfly/eth.store v0.0.0-20240312111708-b43f13990280 h1:zHl4a19bwoa3ccAS3fdUEBC0TkEj5r0spPiVJAzURRc= -github.com/gobitfly/eth.store v0.0.0-20240312111708-b43f13990280/go.mod h1:1PLeTVRw8Rpmi0o/kRuoJEXOXecZRqSjoAxEMbj+usA= -github.com/gobitfly/prysm/v3 v3.0.0-20230216184552-2f3f1e8190d5 h1:8kVoXCPhDwSjaGlKzBVQeE8n49k6jZumBGiP26FHNy0= -github.com/gobitfly/prysm/v3 v3.0.0-20230216184552-2f3f1e8190d5/go.mod h1:+v+em7rOykPs93APGWCX/95/3uxU8bSVmbZ4+YNJzdA= +github.com/gobitfly/eth.store v0.0.0-20250125090903-cce1f5e601a4 h1:JwvY/hmo0XU6q9kAWHcNPjly59Q45iOZuY9S+tt5cUs= +github.com/gobitfly/eth.store v0.0.0-20250125090903-cce1f5e601a4/go.mod h1:iv7lkVudj8l/ZNOU8kUisTAK1EWfYr1Q86vWoH7u2ZE= github.com/gobitfly/scs/v2 v2.0.0-20240516120302-8754831e6b9b h1:Svh+MoUvmbie+1EUexn5p5UbDIdBWpP9rLe4q1/9188= github.com/gobitfly/scs/v2 v2.0.0-20240516120302-8754831e6b9b/go.mod h1:BJs6oUWumklexTEBnf6sgirUTagmYe6e9y36MaFimPg= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.10.0 h1:rBi+5HGuznOxx0JZ+60LDY85gc0dyIJCIMvsMJTKSKQ= github.com/goccy/go-yaml v1.10.0/go.mod h1:h/18Lr6oSQ3mvmqFoWmQ47KChOgpfHpTyIHl3yVmpiY= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= -github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY= +github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 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/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/gomodule/redigo v1.8.0 h1:OXfLQ/k8XpYF8f8sZKd2Df4SDyzbLeC35OsBsB11rYg= github.com/gomodule/redigo v1.8.0/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -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/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= -github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= -github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/csrf v1.7.0 h1:mMPjV5/3Zd460xCavIkppUdvnl5fPXMpv2uz2Zyg7/Y= @@ -517,51 +369,24 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -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/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.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/herumi/bls-eth-go-binary v1.29.1 h1:XcNSHYTyNjEUVfWDCE2gtG5r95biTwd7MJUJF09LtSE= github.com/herumi/bls-eth-go-binary v1.29.1/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= -github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 h1:3JQNjnMRil1yD0IfZKHF9GxxWKDJGj8I0IqOUol//sw= -github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= 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/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= -github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= -github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= +github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= @@ -571,11 +396,7 @@ github.com/huandu/go-clone/generic v1.6.0 h1:Wgmt/fUZ28r16F2Y3APotFD59sHk1p78K0X github.com/huandu/go-clone/generic v1.6.0/go.mod h1:xgd9ZebcMsBWWcBx5mVMCoqMX24gLWr5lQicr+nVXNs= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= -github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= github.com/ipfs/boxo v0.8.0 h1:UdjAJmHzQHo/j3g3b1bAcAXCj/GM6iTwvSlBDvPBNBs= @@ -629,12 +450,6 @@ github.com/ipld/go-codec-dagpb v1.6.0/go.mod h1:ANzFhfP2uMJxRBr8CE+WQWs5UsNa0pYt github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= github.com/ipld/go-ipld-prime v0.20.0 h1:Ud3VwE9ClxpO2LkCYP7vWPc0Fo+dYdYzgxUJZ3uRG4g= github.com/ipld/go-ipld-prime v0.20.0/go.mod h1:PzqZ/ZR981eKbgdr3y2DJYeD/8bgMawdGVlJDE8kK+M= -github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= -github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= -github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= -github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= -github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -651,10 +466,10 @@ github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= 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/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= @@ -673,6 +488,8 @@ github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrU github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= +github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jackc/pgx-shopspring-decimal v0.0.0-20220624020537-1d36b5a1853e h1:i3gQ/Zo7sk4LUVbsAjTNeC4gIjoPNIZVzs4EXstssV4= github.com/jackc/pgx-shopspring-decimal v0.0.0-20220624020537-1d36b5a1853e/go.mod h1:zUHglCZ4mpDUPgIwqEKoba6+tcUQzRdb1+DPTuYe9pI= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= @@ -686,67 +503,50 @@ github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSlj github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= 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/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= +github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= +github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= -github.com/jhump/protoreflect v1.8.1/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= 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 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juliangruber/go-intersect v1.1.0 h1:sc+y5dCjMMx0pAdYk/N6KBm00tD/f3tq+Iox7dYDUrY= github.com/juliangruber/go-intersect v1.1.0/go.mod h1:WMau+1kAmnlQnKiikekNJbtGtfmILU/mMU6H7AgKbWQ= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5 h1:PJr+ZMXIecYc1Ey2zucXdR73SMBtgjPgwa31099IMv0= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= github.com/kataras/i18n v0.0.5 h1:X9EQHxDhjpN0zh+Ry0PZvi0ODi9lf5mo4wiXWtOYhlY= github.com/kataras/i18n v0.0.5/go.mod h1:U0aKF7ANqGmFVs4WCexDTYGf8wg7Rb3mLJCmr/OuDoo= -github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= -github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= -github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= -github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= -github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= -github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 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.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8= -github.com/koron/go-ssdp v0.0.3/go.mod h1:b2MxI6yh02pKrsyNoQUsk4+YNikaGhe4894J+Q5lDvA= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= +github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -756,59 +556,48 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= -github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= +github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA= +github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= -github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= -github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= -github.com/libp2p/go-libp2p v0.26.3 h1:6g/psubqwdaBqNNoidbRKSTBEYgaOuKBhHl8Q5tO+PM= -github.com/libp2p/go-libp2p v0.26.3/go.mod h1:x75BN32YbwuY0Awm2Uix4d4KOz+/4piInkp4Wr3yOo8= -github.com/libp2p/go-libp2p-asn-util v0.2.0 h1:rg3+Os8jbnO5DxkC7K/Utdi+DkY3q/d1/1q+8WeNAsw= -github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= +github.com/libp2p/go-libp2p v0.36.5 h1:DoABsaHO0VXwH6pwCs2F6XKAXWYjFMO4HFBoVxTnF9g= +github.com/libp2p/go-libp2p v0.36.5/go.mod h1:CpszAtXxHYOcyvB7K8rSHgnNlh21eKjYbEfLoMerbEI= +github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= +github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= -github.com/libp2p/go-nat v0.1.0 h1:MfVsH6DLcpa04Xr+p8hmVRG4juse0s3J8HyNWYHffXg= -github.com/libp2p/go-nat v0.1.0/go.mod h1:X7teVkwRHNInVNWQiO/tAiAVRwSr5zoRz4YSTC3uRBM= +github.com/libp2p/go-nat v0.2.0 h1:Tyz+bUFAYqGyJ/ppPPymMGbIgNRH+WqC5QrT5fKrrGk= +github.com/libp2p/go-nat v0.2.0/go.mod h1:3MJr+GRpRkyT65EpVPBstXLvOlAPzUVlG6Pwg9ohLJk= github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailgun/mailgun-go/v4 v4.1.3 h1:KLa5EZaOMMeyvY/lfAhWxv9ealB3mtUsMz0O9XmTtP0= github.com/mailgun/mailgun-go/v4 v4.1.3/go.mod h1:R9kHUQBptF4iSEjhriCQizplCDwrnDShy8w/iPiOfaM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/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.12/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-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 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= @@ -820,31 +609,19 @@ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= -github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= -github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= +github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= -github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= @@ -852,17 +629,15 @@ github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8oh github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= @@ -876,18 +651,18 @@ github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYg github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= -github.com/multiformats/go-multiaddr v0.8.0 h1:aqjksEcqK+iD/Foe1RRFsGZh8+XFiGo7FgUCZlpv3LU= -github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= -github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= -github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= +github.com/multiformats/go-multiaddr v0.13.0 h1:BCBzs61E3AGHcYYTv8dqRH43ZfyrqM8RXVPT8t13tLQ= +github.com/multiformats/go-multiaddr v0.13.0/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII= +github.com/multiformats/go-multiaddr-dns v0.4.0 h1:P76EJ3qzBXpUXZ3twdCDx/kvagMsNo0LMFXpyms/zgU= +github.com/multiformats/go-multiaddr-dns v0.4.0/go.mod h1:7hfthtB4E4pQwirrz+J0CcDUfbWzTqEzVyYKKIKpgkc= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= -github.com/multiformats/go-multicodec v0.8.1 h1:ycepHwavHafh3grIbR1jIXnKCsFm0fqsfEOsJ8NtKE8= -github.com/multiformats/go-multicodec v0.8.1/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= +github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= +github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= @@ -895,41 +670,38 @@ github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUj github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg= github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= -github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= -github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= +github.com/multiformats/go-multistream v0.5.0 h1:5htLSLl7lvJk3xx3qT/8Zm9J4K8vEOf/QGkvOGQAyiE= +github.com/multiformats/go-multistream v0.5.0/go.mod h1:n6tMZiwiP2wUsR8DgfDWw1dydlEqV3l6N3/GBsX6ILA= github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mvdan/xurls v1.1.0 h1:OpuDelGQ1R1ueQ6sSryzi6P+1RtBpfQHM8fJwlE45ww= github.com/mvdan/xurls v1.1.0/go.mod h1:tQlNn3BED8bE/15hnSL2HLkDeLWpNPAwtw7wkEq44oU= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.20.0 h1:PE84V2mHqoT1sglvHc8ZdQtPcwmvvt29WLEEO3xmdZw= +github.com/onsi/ginkgo/v2 v2.20.0/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= @@ -937,19 +709,49 @@ github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/En github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/phyber/negroni-gzip v0.0.0-20180113114010-ef6356a5d029 h1:d6HcSW4ZoNlUWrPyZtBwIu8yv4WAWIU3R/jorwVkFtQ= github.com/phyber/negroni-gzip v0.0.0-20180113114010-ef6356a5d029/go.mod h1:94RTq2fypdZCze25ZEZSjtbAQRT3cL/8EuRUqAZC/+w= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= -github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pion/datachannel v1.5.8 h1:ph1P1NsGkazkjrvyMfhRBUAWMxugJjq2HfQifaOoSNo= +github.com/pion/datachannel v1.5.8/go.mod h1:PgmdpoaNBLX9HNzNClmdki4DYW5JtI7Yibu8QzbL3tI= +github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= +github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= +github.com/pion/ice/v2 v2.3.34 h1:Ic1ppYCj4tUOcPAp76U6F3fVrlSw8A9JtRXLqw6BbUM= +github.com/pion/ice/v2 v2.3.34/go.mod h1:mBF7lnigdqgtB+YHkaY/Y6s6tsyRyo4u4rPGRuOjUBQ= +github.com/pion/interceptor v0.1.30 h1:au5rlVHsgmxNi+v/mjOPazbW1SHzfx7/hYOEYQnUcxA= +github.com/pion/interceptor v0.1.30/go.mod h1:RQuKT5HTdkP2Fi0cuOS5G5WNymTjzXaGF75J4k7z2nc= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8= +github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/rtcp v1.2.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE= +github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4= +github.com/pion/rtp v1.8.9 h1:E2HX740TZKaqdcPmf4pw6ZZuG8u5RlMMt+l3dxeu6Wk= +github.com/pion/rtp v1.8.9/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= +github.com/pion/sctp v1.8.33 h1:dSE4wX6uTJBcNm8+YlMg7lw1wqyKHggsP5uKbdj+NZw= +github.com/pion/sctp v1.8.33/go.mod h1:beTnqSzewI53KWoG3nqB282oDMGrhNxBdb+JZnkCwRM= +github.com/pion/sdp/v3 v3.0.9 h1:pX++dCHoHUwq43kuwf3PyJfHlwIj4hXA7Vrifiq0IJY= +github.com/pion/sdp/v3 v3.0.9/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M= +github.com/pion/srtp/v2 v2.0.20 h1:HNNny4s+OUmG280ETrCdgFndp4ufx3/uy85EawYEhTk= +github.com/pion/srtp/v2 v2.0.20/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA= +github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= +github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8= +github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0= +github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ= +github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= +github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= +github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM= +github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= +github.com/pion/turn/v2 v2.1.6 h1:Xr2niVsiPTB0FPtt+yAWKFUkU1eotQbGgpTIld4x1Gc= +github.com/pion/turn/v2 v2.1.6/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= +github.com/pion/webrtc/v3 v3.3.0 h1:Rf4u6n6U5t5sUxhYPQk/samzU/oDv7jk6BA5hyO2F9I= +github.com/pion/webrtc/v3 v3.3.0/go.mod h1:hVmrDJvwhEertRWObeb1xzulzHGeVUoPlWvxdGzcfU0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -958,48 +760,40 @@ github.com/polydawn/refmt v0.0.0-20190221155625-df39d6c2d992/go.mod h1:uIp+gprXx github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pressly/goose/v3 v3.10.0 h1:Gn5E9CkPqTtWvfaDVqtJqMjYtsrZ9K5mU/8wzTsvg04= github.com/pressly/goose/v3 v3.10.0/go.mod h1:c5D3a7j66cT0fhRPj7KsXolfduVrhLlxKZjmCVSey5w= github.com/prestonvanloon/go v1.1.7-0.20190722034630-4f2e55fcf87b h1:Bt5PzQCqfP4xiLXDSrMoqAfj6CBr3N9DAyyq8OiIWsc= github.com/prestonvanloon/go v1.1.7-0.20190722034630-4f2e55fcf87b/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= -github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpjL1k= -github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/protolambda/bls12-381-util v0.0.0-20210720105258-a772f2aac13e/go.mod h1:MPZvj2Pr0N8/dXyTPS5REeg2sdLG7t8DRzC1rLv925w= -github.com/protolambda/messagediff v1.4.0/go.mod h1:LboJp0EwIbJsePYpzh5Op/9G1/4mIztMRYzzwR0dR2M= -github.com/protolambda/zrnt v0.30.0 h1:pHEn69ZgaDFGpLGGYG1oD7DvYI7RDirbMBPfbC+8p4g= -github.com/protolambda/zrnt v0.30.0/go.mod h1:qcdX9CXFeVNCQK/q0nswpzhd+31RHMk2Ax/2lMsJ4Jw= +github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI= +github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/protolambda/zrnt v0.32.2 h1:KZ48T+3UhsPXNdtE/5QEvGc9DGjUaRI17nJaoznoIaM= +github.com/protolambda/zrnt v0.32.2/go.mod h1:A0fezkp9Tt3GBLATSPIbuY4ywYESyAuc/FFmPKg8Lqs= github.com/protolambda/zssz v0.1.5 h1:7fjJjissZIIaa2QcvmhS/pZISMX21zVITt49sW1ouek= github.com/protolambda/zssz v0.1.5/go.mod h1:a4iwOX5FE7/JkKA+J/PH0Mjo9oXftN6P8NZyL28gpag= -github.com/protolambda/ztyp v0.2.2/go.mod h1:9bYgKGqg3wJqT9ac1gI2hnVb0STQq7p/1lapqrqY1dU= -github.com/prysmaticlabs/fastssz v0.0.0-20221107182844-78142813af44 h1:c3p3UzV4vFA7xaCDphnDWOjpxcadrQ26l5b+ypsvyxo= -github.com/prysmaticlabs/fastssz v0.0.0-20221107182844-78142813af44/go.mod h1:MA5zShstUwCQaE9faGHgCGvEWUbG87p4SAXINhmCkvg= -github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 h1:0tVE4tdWQK9ZpYygoV7+vS6QkDvQVySboMVEIxBJmXw= -github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7/go.mod h1:wmuf/mdK4VMD+jA9ThwcUKjg3a2XWM9cVfFYjDyY4j4= +github.com/prysmaticlabs/fastssz v0.0.0-20241008181541-518c4ce73516 h1:xuVAdtz5ShYblG2sPyb4gw01DF8InbOI/kBCQjk7NiM= +github.com/prysmaticlabs/fastssz v0.0.0-20241008181541-518c4ce73516/go.mod h1:h2OlIZD/M6wFvV3YMZbW16lFgh3Rsye00G44J2cwLyU= +github.com/prysmaticlabs/go-bitfield v0.0.0-20240328144219-a1caa50c3a1e h1:ATgOe+abbzfx9kCPeXIW4fiWyDdxlwHw07j8UGhdTd4= +github.com/prysmaticlabs/go-bitfield v0.0.0-20240328144219-a1caa50c3a1e/go.mod h1:wmuf/mdK4VMD+jA9ThwcUKjg3a2XWM9cVfFYjDyY4j4= github.com/prysmaticlabs/go-ssz v0.0.0-20210121151755-f6208871c388 h1:4bD+ujqGfY4zoDUF3q9MhdmpPXzdp03DYUIlXeQ72kk= github.com/prysmaticlabs/go-ssz v0.0.0-20210121151755-f6208871c388/go.mod h1:VecIJZrewdAuhVckySLFt2wAAHRME934bSDurP8ftkc= -github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkqOOVnWapUyeWro4= -github.com/prysmaticlabs/gohashtree v0.0.4-beta/go.mod h1:BFdtALS+Ffhg3lGQIHv9HDWuHS8cTvHZzrHWxwOtGOs= -github.com/prysmaticlabs/grpc-gateway/v2 v2.3.1-0.20210702154020-550e1cd83ec1 h1:xcu59yYL6AWWTl6jtejBfE0y8uF35fArCBeZjRlvJss= -github.com/prysmaticlabs/grpc-gateway/v2 v2.3.1-0.20210702154020-550e1cd83ec1/go.mod h1:IOyTYjcIO0rkmnGBfJTL0NJ11exy/Tc2QEuv7hCXp24= -github.com/prysmaticlabs/protoc-gen-go-cast v0.0.0-20211014160335-757fae4f38c6 h1:+jhXLjEYVW4qU2z5SOxlxN+Hv/A9FDf0HpfDurfMEz0= -github.com/prysmaticlabs/protoc-gen-go-cast v0.0.0-20211014160335-757fae4f38c6/go.mod h1:ZVEbRdnMkGhp/pu35zq4SXxtvUwWK0J1MATtekZpH2Y= +github.com/prysmaticlabs/gohashtree v0.0.4-beta.0.20240624100937-73632381301b h1:VK7thFOnhxAZ/5aolr5Os4beiubuD08WiuiHyRqgwks= +github.com/prysmaticlabs/gohashtree v0.0.4-beta.0.20240624100937-73632381301b/go.mod h1:HRuvtXLZ4WkaB1MItToVH2e8ZwKwZPY5/Rcby+CvvLY= +github.com/prysmaticlabs/protoc-gen-go-cast v0.0.0-20230228205207-28762a7b9294 h1:q9wE0ZZRdTUAAeyFP/w0SwBEnCqlVy2+on6X2/e+eAU= +github.com/prysmaticlabs/protoc-gen-go-cast v0.0.0-20230228205207-28762a7b9294/go.mod h1:ZVEbRdnMkGhp/pu35zq4SXxtvUwWK0J1MATtekZpH2Y= +github.com/prysmaticlabs/prysm/v5 v5.2.0 h1:JqKKK5aqehZN9GiSOSSw4M57NpbvG0nIxqFK5KpPnRw= +github.com/prysmaticlabs/prysm/v5 v5.2.0/go.mod h1:cQc+NIMKaHjPvY566HsYcuni763nzuUWnDsDbk45bbs= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= +github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= +github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 h1:4WFk6u3sOT6pLa1kQ50ZVdm8BQFgJNA117cepZxtLIg= +github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66/go.mod h1:Vp72IJajgeOL6ddqrAhmp7IM9zbTcgkQxD/YdxrVwMw= github.com/r3labs/sse/v2 v2.10.0 h1:hFEkLLFY4LDifoHdiCN/LlGBAdVJYsANaLqNYa1l/v0= github.com/r3labs/sse/v2 v2.10.0/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktEmkNJ7I= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= @@ -1013,14 +807,9 @@ github.com/rocket-pool/rocketpool-go v1.8.3-0.20240618173422-783b8668f5b4 h1:X2c github.com/rocket-pool/rocketpool-go v1.8.3-0.20240618173422-783b8668f5b4/go.mod h1:f2TVsMOYmCwaJOhshG2zRoX89PZmvCkCD7UYJ9waRkI= github.com/rocket-pool/smartnode v1.13.6 h1:dZCDEb5+ZFN7iU5/Qzxv16efS1zk/fLf6HiewNzjBfY= github.com/rocket-pool/smartnode v1.13.6/go.mod h1:mW/gljU+C02+JhrXN3dQfftaLnEqw8mIkTtkKOFIyJk= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= @@ -1029,19 +818,12 @@ github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= @@ -1049,7 +831,6 @@ github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= 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.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -1065,21 +846,8 @@ github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa/go.mod h1:2 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.0.1-0.20201006035406-b97b5ead31f7/go.mod h1:yk5b0mALVusDL5fMM6Rd1wgnoO5jUPhwsQ6LQAJTidQ= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= -github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= 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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -1099,15 +867,10 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stripe/stripe-go/v72 v72.50.0 h1:oy+EsSKMrFS3zzayb8Ic+2LZ04Ux0vJ4990/7psaYsc= github.com/stripe/stripe-go/v72 v72.50.0/go.mod h1:QwqJQtduHubZht9mek5sds9CtQcKFdsykV9ZepRWwo0= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= -github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= -github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc= -github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= -github.com/swaggo/http-swagger v1.3.0 h1:1+6M4qRorIbdyTWTsGrwnb0r9jGK5dcWN82O6oY/yHQ= -github.com/swaggo/http-swagger v1.3.0/go.mod h1:9glekdg40lwclrrKNRGgj/IMDxpNPZ3kzab4oPcF8EM= -github.com/swaggo/swag v1.8.3 h1:3pZSSCQ//gAH88lfmxM3Cd1+JCsxV8Md6f36b9hrZ5s= -github.com/swaggo/swag v1.8.3/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg= +github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= +github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= +github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e h1:cR8/SYRgyQCt5cNCMniB/ZScMkhI9nk8U5C7SbISXjo= @@ -1117,29 +880,14 @@ github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08 github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/twitchtv/twirp v7.1.0+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= -github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= -github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= -github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U= -github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/umbracle/gohashtree v0.0.2-alpha.0.20230207094856-5b775a815c10 h1:CQh33pStIp/E30b7TxDlXfM0145bn2e8boI30IxAhTg= github.com/umbracle/gohashtree v0.0.2-alpha.0.20230207094856-5b775a815c10/go.mod h1:x/Pa0FF5Te9kdrlZKJK82YmAkvL8+f989USgz6Jiw7M= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8= -github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= -github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI= +github.com/urfave/cli/v2 v2.26.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/warpfork/go-testmark v0.11.0 h1:J6LnV8KpceDvo7spaNU4+DauH2n1x+6RaO2rJrmpQ9U= github.com/warpfork/go-testmark v0.11.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= @@ -1163,57 +911,44 @@ github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa h1:EyA027ZA github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8= +github.com/wlynxg/anet v0.0.4 h1:0de1OFQxnNqAu+x2FAKKCVIrnfGKQbs7FQz++tB0+Uw= +github.com/wlynxg/anet v0.0.4/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/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/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zesik/proxyaddr v0.0.0-20161218060608-ec32c535184d h1:Gsw/uTjNB2vkIEhBO3NAXjKo6QRY6D5B0GzMv980ses= github.com/zesik/proxyaddr v0.0.0-20161218060608-ec32c535184d/go.mod h1:PXKs8dwJ+2BKBQxMlKg+eaO27VucEN/jrrymnRB5tvs= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= +go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -1224,6 +959,8 @@ go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0 go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= @@ -1238,297 +975,147 @@ go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/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-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= -golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 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.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= -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= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -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/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-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-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/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-20220715151400-c0bba94af5f8/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.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -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-20191024005414-555d28b269f0/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.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1538,169 +1125,71 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.197.0 h1:x6CwqQLsFiA5JKAiGyGBjc2bNtHtLddhJCE2IKuhhcQ= -google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/api v0.217.0 h1:GYrUtD289o4zl1AhiTZL0jvQGa2RDLyC+kX1N/lfGOU= +google.golang.org/api v0.217.0/go.mod h1:qMc2E8cBAbQlRypBTBWHklNJlaZZJBwDv81B1Iu8oSI= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/appengine/v2 v2.0.2 h1:MSqyWy2shDLwG7chbwBJ5uMyw6SNqJzhJHNDwYB0Akk= google.golang.org/appengine/v2 v2.0.2/go.mod h1:PkgRUWz4o1XOvbqtWTkBtCitEJ5Tp4HoVEdMMYQR/8E= -google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210426193834-eac7f76ac494/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU= -google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.35.0-dev.0.20201218190559-666aea1fb34c/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= -google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/genproto v0.0.0-20241216192217-9240e9c98484 h1:a/U5otbGrI6mYIO598WriFB1172i6Ktr6FGcatZD3Yw= +google.golang.org/genproto v0.0.0-20241216192217-9240e9c98484/go.mod h1:Gmd/M/W9fEyf6VSu/mWLnl+9Be51B9CLdxdsKokYq7Y= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.25.1-0.20201208041424-160c7477e0e8/go.mod h1:hFxJC2f0epmp1elRCiEGJTKAWbwxZ2nvqZdHl3FQXCY= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y= gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.61.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 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/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -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= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU= -k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= -k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E= -k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= -k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= -k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= -k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= -lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= +k8s.io/apimachinery v0.30.4 h1:5QHQI2tInzr8LsT4kU/2+fSeibH1eIHswNx480cqIoY= +k8s.io/apimachinery v0.30.4/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/client-go v0.30.4 h1:eculUe+HPQoPbixfwmaSZGsKcOf7D288tH6hDAdd+wY= +k8s.io/client-go v0.30.4/go.mod h1:IBS0R/Mt0LHkNHF4E6n+SUDPG7+m2po6RZU7YHeOpzc= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= +lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= @@ -1723,13 +1212,11 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/handlers/api.go b/handlers/api.go index 3e1d5fccd8..c5b3cdca16 100644 --- a/handlers/api.go +++ b/handlers/api.go @@ -22,7 +22,6 @@ import ( "github.com/gobitfly/eth2-beaconchain-explorer/db" "github.com/gobitfly/eth2-beaconchain-explorer/exporter" "github.com/gobitfly/eth2-beaconchain-explorer/metrics" - "github.com/gobitfly/eth2-beaconchain-explorer/price" "github.com/gobitfly/eth2-beaconchain-explorer/services" "github.com/gobitfly/eth2-beaconchain-explorer/types" "github.com/gobitfly/eth2-beaconchain-explorer/utils" @@ -39,50 +38,65 @@ import ( "google.golang.org/protobuf/proto" ) -// @title beaconcha.in Ethereum API Documentation +// @title beaconcha.in API Documentation // @version 1.1 -// @description High performance API for querying information about Ethereum -// @description The API is currently free to use. A fair use policy applies. Calls are rate limited to -// @description 10 requests / 1 minute / IP. All API results are cached for 1 minute. -// @description If you required a higher usage plan please checkout https://beaconcha.in/pricing. -// @description The API key can be provided in the Header or as a query string parameter. +// @description ## Introduction +// @description **Advanced and reliable API for accessing comprehensive Ethereum blockchain data.** // @description -// @description Key as a query string parameter: `curl https://beaconcha.in/api/v1/slot/1?apikey=` +// @description - **Free Usage Policy:** The API is free to use under a fair use policy, with rate limits of 10 requests per minute per IP. +// @description - **Caching:** All responses are cached for 1 minute. +// @description - **Higher Usage Plans:** For higher usage plans, visit: [https://beaconcha.in/pricing](https://beaconcha.in/pricing). An API key is required to use these plans. // @description -// @description Key in a request header: `curl -H 'apikey: ' https://beaconcha.in/api/v1/slot/1` +// @description ### API Key Usage +// @description API keys can be obtained at [/user/settings](https://beaconcha.in/user/settings) and must be included in requests either as a query string parameter or in the request header. +// @description +// @description #### Example: Query String Parameter +// @description ```bash +// @description curl https://beaconcha.in/api/v1/slot/1?apikey= +// @description ``` +// @description +// @description #### Example: Request Header +// @description ```bash +// @description curl -H 'apikey: ' https://beaconcha.in/api/v1/slot/1 +// @description ``` +// @x-tagGroups [{"name":"Consensus Layer","tags":["Epochs", "Slots", "Validators", "Rewards", "Sync Committees", "Rocketpool", "ETH.Store®"]},{"name":"Execution Layer","tags":["Validator Deposits", "Blocks", "Addresses", "Gas"]},{"name":"Other","tags":["Network", "User", "Misc"]}] + // @tag.name Epoch -// @tag.description Consensus layer information about epochs -// @tag.docs.url https://example.com -// @tag.name Slot -// @tag.description Consensus layer information about slots -// @tag.name Validator -// @tag.description Consensus layer information about validators -// @tag.name SyncCommittee -// @tag.name Execution -// @tag.description layer information about addresses, blocks and transactions -// @tag.name ETH.STORE® -// @tag.description is the transparent Ethereum staking reward reference rate. -// @tag.docs.url https://staking.ethermine.org/statistics -// @tag.docs.description More info +// @tag.description Data related to consensus layer epochs +// @tag.name Slots +// @tag.description Data related to consensus layer slots +// @tag.name Validators +// @tag.description Data related to consensus layer validators +// @tag.name Rewards +// @tag.description Data related to validator rewards +// @tag.name Sync Committees +// @tag.description Data related to sync committees // @tag.name Rocketpool -// @tag.description validator statistics -// @tag.docs.url https://rocketpool.net -// @tag.docs.description More info +// @tag.description Data related to the rocketpool protocol +// @tag.name ETH.Store® +// @tag.description Data related to the ETH.Store® metric + +// @tag.name Validator deposits +// @tag.description Data related to execution layer validator deposits +// @tag.name Blocks +// @tag.description Data related to execution layer blocks +// @tag.name Gas +// @tag.description Data related to gas prices +// @tag.name Address +// @tag.description Data related to ethereum addresses + +// @tag.name Network +// @tag.description Network data // @tag.name Misc // @tag.name User -// @tag.description provided for Oauth applications (public OAuth support is a work in progress). -// @securitydefinitions.oauth2.accessCode OAuthAccessCode -// @tokenurl https://beaconcha.in/user/token -// @authorizationurl https://beaconcha.in/user/authorize -// @securitydefinitions.apikey ApiKeyAuth -// @in header -// @name Authorization // ApiHealthz godoc -// @Summary Health of the explorer // @Tags Misc -// @Description Health endpoint for monitoring if the explorer is in sync -// @Produce text/plain +// @Summary Get explorer Health +// @Description Provides the health status of all modules of the explorer. This endpoint is useful for monitoring the availability and functionality of the explorer's components. +// @Description - **Modules Monitored:** Includes monitoring of services such as `monitoring_app`, `monitoring_el_data`, `monitoring_services`, `monitoring_cl_data`, `monitoring_api`, `monitoring_redis`. +// @Description - **Response Details:** Returns the status of each module. If all modules are operational, the response will indicate success. Otherwise, it will return an error with details about the failing modules. +// @Produce text/plain // @Success 200 {object} types.ApiResponse // @Router /api/healthz [get] func ApiHealthz(w http.ResponseWriter, r *http.Request) { @@ -152,10 +166,10 @@ func ApiHealthz(w http.ResponseWriter, r *http.Request) { } } -// ApiHealthzLoadbalancer godoc -// @Summary Health of the explorer-api regarding having a healthy connection to the database +// healthz-loadbalancer godoc // @Tags Misc -// @Description Health endpoint for montitoring if the explorer-api +// @Summary Get explorer Availability +// @Description Health endpoint to monitor the operational status of the explorer (used for load balancer health checks) // @Produce text/plain // @Success 200 {object} types.ApiResponse // @Router /api/healthz-loadbalancer [get] @@ -185,8 +199,8 @@ func ApiHealthzLoadbalancer(w http.ResponseWriter, r *http.Request) { } // ApiEthStoreDay godoc -// @Summary Get ETH.STORE® reference rate for a specified beaconchain-day or the latest day -// @Tags ETH.STORE® +// @Tags ETH.Store® +// @Summary Get ETH.Store® day // @Description ETH.STORE® represents the average financial return validators on the Ethereum network have achieved in a 24-hour period. // @Description For each 24-hour period the datapoint is denoted by the number of days that have passed since genesis for that period (= beaconchain-day) // @Description See https://github.com/gobitfly/eth.store for further information. @@ -194,6 +208,7 @@ func ApiHealthzLoadbalancer(w http.ResponseWriter, r *http.Request) { // @Param day path string true "The beaconchain-day (periods of <(24 * 60 * 60) // SlotsPerEpoch // SecondsPerSlot> epochs) to get the the ETH.STORE® for. Must be a number or the string 'latest'." // @Success 200 {object} types.ApiResponse // @Failure 400 {object} types.ApiResponse +// @Failure 500 {object} types.ApiResponse // @Router /api/v1/ethstore/{day} [get] func ApiEthStoreDay(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -260,10 +275,11 @@ func ApiEthStoreDay(w http.ResponseWriter, r *http.Request) { } // ApiLatestState godoc -// @Summary Get the latest state of the network // @Tags Network +// @Summary Get network state // @Description Returns information on the current state of the network // @Produce json +// @Success 200 {object} types.LatestState // @Failure 400 {object} types.ApiResponse "Failure" // @Failure 500 {object} types.ApiResponse "Server Error" // @Router /api/v1/latestState [get] @@ -288,8 +304,8 @@ func ApiLatestState(w http.ResponseWriter, r *http.Request) { } // ApiEpoch godoc -// @Summary Get epoch by number, latest, finalized -// @Tags Epoch +// @Tags Epochs +// @Summary Get epoch // @Description Returns information for a specified epoch by the epoch number or an epoch tag (can be latest or finalized) // @Produce json // @Param epoch path string true "Epoch number, the string latest or the string finalized" @@ -349,8 +365,8 @@ func ApiEpoch(w http.ResponseWriter, r *http.Request) { } // ApiEpochSlots godoc -// @Summary Get epoch blocks by epoch number, latest or finalized -// @Tags Epoch +// @Tags Epochs +// @Summary Get epoch slots // @Description Returns all slots for a specified epoch // @Produce json // @Param epoch path string true "Epoch number, the string latest or string finalized" @@ -396,8 +412,8 @@ func ApiEpochSlots(w http.ResponseWriter, r *http.Request) { } // ApiSlots godoc -// @Summary Get a slot by its slot number or root hash. Alternatively get the latest slot or the slot containing the head block. -// @Tags Slot +// @Tags Slots +// @Summary Get slot // @Description Returns a slot by its slot number or root hash, the latest slot with string latest or the slot containing the head block with string head // @Produce json // @Param slotOrHash path string true "Slot or root hash or the string latest or head" @@ -504,8 +520,8 @@ func ApiSlots(w http.ResponseWriter, r *http.Request) { } // ApiSlotAttestations godoc -// @Summary Get the attestations included in a specific slot -// @Tags Slot +// @Tags Slots +// @Summary Get slot attestations // @Description Returns the attestations included in a specific slot // @Produce json // @Param slot path string true "Slot" @@ -550,8 +566,8 @@ func ApiSlotAttestations(w http.ResponseWriter, r *http.Request) { } // ApiSlotAttesterSlashings godoc -// @Summary Get the attester slashings included in a specific slot -// @Tags Slot +// @Tags Slots +// @Summary Get slot attestation slashings // @Description Returns the attester slashings included in a specific slot // @Produce json // @Param slot path string true "Slot" @@ -580,8 +596,8 @@ func ApiSlotAttesterSlashings(w http.ResponseWriter, r *http.Request) { } // ApiSlotDeposits godoc -// @Summary Get the deposits included in a specific block -// @Tags Slot +// @Tags Slots +// @Summary Get slot deposits // @Description Returns the deposits included in a specific block // @Produce json // @Param slot path string true "Block slot" @@ -635,8 +651,8 @@ func ApiSlotDeposits(w http.ResponseWriter, r *http.Request) { } // ApiSlotProposerSlashings godoc -// @Summary Get the proposer slashings included in a specific slot -// @Tags Slot +// @Tags Slots +// @Summary Get slot proposer slashings // @Description Returns the proposer slashings included in a specific slot // @Produce json // @Param slot path string true "Slot" @@ -667,8 +683,8 @@ func ApiSlotProposerSlashings(w http.ResponseWriter, r *http.Request) { } // ApiSlotVoluntaryExits godoc -// @Summary Get the voluntary exits included in a specific slot -// @Tags Slot +// @Tags Slots +// @Summary Get slot voluntary exits // @Description Returns the voluntary exits included in a specific slot // @Produce json // @Param slot path string true "Slot" @@ -699,8 +715,8 @@ func ApiSlotVoluntaryExits(w http.ResponseWriter, r *http.Request) { } // ApiSlotWithdrawals godoc -// @Summary Get the withdrawals included in a specific slot -// @Tags Slot +// @Tags Slots +// @Summary Get slot withdrawals // @Description Returns the withdrawals included in a specific slot // @Produce json // @Param slot path string true "Block slot" @@ -717,7 +733,7 @@ func ApiSlotWithdrawals(w http.ResponseWriter, r *http.Request) { return } - rows, err := db.ReaderDb.Query("SELECT block_slot, withdrawalindex, validatorindex, address, amount FROM blocks_withdrawals WHERE block_slot = $1 ORDER BY withdrawalindex", slot) + rows, err := db.ReaderDb.Query("SELECT block_slot, GREATEST(withdrawalindex, 0) AS withdrawalindex, validatorindex, address, amount FROM blocks_withdrawals WHERE block_slot = $1 AND address <> ''::bytea ORDER BY withdrawalindex", slot) if err != nil { logger.WithError(err).Error("error getting blocks_withdrawals") SendBadRequestResponse(w, r.URL.String(), "could not retrieve db results") @@ -727,10 +743,184 @@ func ApiSlotWithdrawals(w http.ResponseWriter, r *http.Request) { returnQueryResults(rows, w, r) } -// ApiBlockVoluntaryExits godoc +func ApiSlotConsolidationRequests(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + vars := mux.Vars(r) + q := r.URL.Query() + + limitQuery := q.Get("limit") + offsetQuery := q.Get("offset") + + offset, err := strconv.ParseInt(offsetQuery, 10, 64) + if err != nil { + offset = 0 + } + + limit, err := strconv.ParseInt(limitQuery, 10, 64) + if err != nil { + limit = 100 + offset + } + + if offset < 0 { + offset = 0 + } + + if limit > (100+offset) || limit <= 0 || limit <= offset { + limit = 100 + offset + } + + slot, err := strconv.ParseInt(vars["slot"], 10, 64) + if err != nil { + SendBadRequestResponse(w, r.URL.String(), "invalid block slot provided") + return + } + + rows, err := db.ReaderDb.Query(` + SELECT + slot_processed as block_slot, + block_processed_root as block_root, + index_processed as request_index, + amount_consolidated, + sv.validatorindex as source_index, + tv.validatorindex as target_index + FROM blocks_consolidation_requests_v2 + INNER JOIN validators sv ON (sv.pubkey = source_pubkey) + INNER JOIN validators tv ON (tv.pubkey = target_pubkey) + WHERE slot_processed = $1 + AND blocks_consolidation_requests_v2.status = 'completed' + ORDER BY slot_processed DESC, index_processed DESC + limit $2 offset $3`, slot, limit, offset) + if err != nil { + logger.WithError(err).Error("could not retrieve db results") + SendBadRequestResponse(w, r.URL.String(), "could not retrieve db results") + return + } + defer rows.Close() + + returnQueryResultsAsArray(rows, w, r) +} + +func ApiSlotSwitchToCompoundingRequests(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + vars := mux.Vars(r) + q := r.URL.Query() + + limitQuery := q.Get("limit") + offsetQuery := q.Get("offset") + + offset, err := strconv.ParseInt(offsetQuery, 10, 64) + if err != nil { + offset = 0 + } + + limit, err := strconv.ParseInt(limitQuery, 10, 64) + if err != nil { + limit = 100 + offset + } + + if offset < 0 { + offset = 0 + } + + if limit > (100+offset) || limit <= 0 || limit <= offset { + limit = 100 + offset + } + + slot, err := strconv.ParseInt(vars["slot"], 10, 64) + if err != nil { + SendBadRequestResponse(w, r.URL.String(), "invalid block slot provided") + return + } + + // TODO: remove v1 table dependency once eth1id resolving is available + // See https://bitfly1.atlassian.net/browse/BEDS-1522 + rows, err := db.ReaderDb.Query(` + SELECT + slot_processed as block_slot, + block_processed_root as block_root, + index_processed as request_index, + v.validatorindex as validator_index, + COALESCE(v1.address, decode('0000000000000000000000000000000000000000', 'hex')) as address + FROM blocks_switch_to_compounding_requests_v2 + INNER JOIN validators v ON (v.pubkey = validator_pubkey) + LEFT JOIN blocks_switch_to_compounding_requests v1 ON (blocks_switch_to_compounding_requests_v2.slot_processed = v1.block_slot AND blocks_switch_to_compounding_requests_v2.block_processed_root = v1.block_root AND blocks_switch_to_compounding_requests_v2.index_processed = v1.request_index) + WHERE slot_processed = $1 + AND blocks_switch_to_compounding_requests_v2.status = 'completed' + ORDER BY slot_processed DESC, index_processed DESC + limit $2 offset $3`, slot, limit, offset) + if err != nil { + logger.WithError(err).Error("could not retrieve db results") + SendBadRequestResponse(w, r.URL.String(), "could not retrieve db results") + return + } + defer rows.Close() + + returnQueryResultsAsArray(rows, w, r) +} + +func ApiSlotDepositRequests(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + vars := mux.Vars(r) + q := r.URL.Query() + + limitQuery := q.Get("limit") + offsetQuery := q.Get("offset") + + offset, err := strconv.ParseInt(offsetQuery, 10, 64) + if err != nil { + offset = 0 + } + + limit, err := strconv.ParseInt(limitQuery, 10, 64) + if err != nil { + limit = 100 + offset + } + + if offset < 0 { + offset = 0 + } + + if limit > (100+offset) || limit <= 0 || limit <= offset { + limit = 100 + offset + } + + slot, err := strconv.ParseInt(vars["slot"], 10, 64) + if err != nil { + SendBadRequestResponse(w, r.URL.String(), "invalid block slot provided") + return + } + + rows, err := db.ReaderDb.Query(` + SELECT + slot_processed as block_slot, + block_processed_root as block_root, + index_processed as request_index, + pubkey, + withdrawal_credentials, + amount, + signature + FROM blocks_deposit_requests_v2 + WHERE slot_processed = $1 + AND type = 'account' + AND blocks_deposit_requests_v2.status = 'completed' + ORDER BY slot_processed DESC, index_processed DESC + limit $2 offset $3`, slot, limit, offset) + if err != nil { + logger.WithError(err).Error("could not retrieve db results") + SendBadRequestResponse(w, r.URL.String(), "could not retrieve db results") + return + } + defer rows.Close() + + returnQueryResultsAsArray(rows, w, r) +} + // ApiSyncCommittee godoc -// @Summary Get the sync-committee for a sync-period -// @Tags SyncCommittee +// @Tags Sync Committees +// @Summary Get sync committee // @Description Returns the sync-committee for a sync-period. Validators are sorted by sync-committee-index. // @Description Sync committees where introduced in the Altair hardfork. Peroids before the hardfork do not contain sync-committees. // @Description For mainnet sync-committes first started after epoch 74240 (period 290) and each sync-committee is active for 256 epochs. @@ -770,8 +960,8 @@ func ApiSyncCommittee(w http.ResponseWriter, r *http.Request) { } // ApiValidatorQueue godoc -// @Summary Get the current validator queue -// @Tags Validator +// @Tags Validators +// @Summary Get validator queue // @Description Returns the current number of validators entering and exiting the beacon chain // @Produce json // @Success 200 {object} types.ApiResponse{data=types.ApiValidatorQueueResponse} @@ -780,18 +970,58 @@ func ApiSyncCommittee(w http.ResponseWriter, r *http.Request) { func ApiValidatorQueue(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - rows, err := db.ReaderDb.Query("SELECT e.validatorscount, q.entering_validators_count as beaconchain_entering, q.exiting_validators_count as beaconchain_exiting FROM epochs e, queue q ORDER BY e.epoch DESC, q.ts DESC LIMIT 1") - if err != nil { - SendBadRequestResponse(w, r.URL.String(), "could not retrieve db results") - return + epoch := services.LatestEpoch() + + var queueData *types.QueuesEstimate + var indexData *types.IndexPageData + + var respondWithElectra bool = false + if utils.ElectraHasHappened(epoch) { + queueData = services.LatestQueueData() + indexData = services.LatestIndexPageData() + + respondWithElectra = queueData != nil || utils.ElectraHasHappened(epoch-2) // allow fall back to pre electra in the first 2 epochs after fork if queue data is not available } - defer rows.Close() - returnQueryResults(rows, w, r) + if respondWithElectra { + if queueData == nil { + SendBadRequestResponse(w, r.URL.String(), "queue data not available") + return + } + if indexData == nil { + SendBadRequestResponse(w, r.URL.String(), "index data not available") + return + } + + j := json.NewEncoder(w) + SendOKResponse(j, r.URL.String(), []interface{}{struct { + Entering uint64 `json:"beaconchain_entering"` + Exiting uint64 `json:"beaconchain_exiting"` + ValidatorCount uint64 `json:"validatorscount"` + EnteringBalance uint64 `json:"beaconchain_entering_balance"` + LeavingBalance uint64 `json:"beaconchain_exiting_balance"` + }{ + Entering: queueData.EnteringNewValidatorsCount, // do not break compatibility with old API even though this might be a useless stat now + Exiting: queueData.LeavingValidatorCount, + ValidatorCount: indexData.ActiveValidators, + EnteringBalance: queueData.EnteringTotalEthAmount, + LeavingBalance: queueData.LeavingEthAmount, + }}) + } else { + rows, err := db.ReaderDb.Query("SELECT e.validatorscount, q.entering_validators_count as beaconchain_entering, q.exiting_validators_count as beaconchain_exiting FROM epochs e, queue q ORDER BY e.epoch DESC, q.ts DESC LIMIT 1") + if err != nil { + SendBadRequestResponse(w, r.URL.String(), "could not retrieve db results") + return + } + defer rows.Close() + + returnQueryResults(rows, w, r) + } } // ApiRocketpoolStats godoc -// @Summary Get global rocketpool network statistics +// @Summary Get rocketpool statistics +// @Description Returns statistics about the Rocketpool protocol // @Tags Rocketpool // @Produce json // @Success 200 {object} types.ApiResponse{data=types.APIRocketpoolStatsResponse} @@ -814,8 +1044,9 @@ func ApiRocketpoolStats(w http.ResponseWriter, r *http.Request) { } // ApiRocketpoolValidators godoc -// @Summary Get rocketpool specific data for given validators -// @Tags Rocketpool +// @Summary Get rocketpool validators +// @Description Returns information about Rocketpool validators +// @Tags Validators // @Param indexOrPubkey path string true "Up to 100 validator indicesOrPubkeys, comma separated" // @Produce json // @Success 200 {object} types.ApiResponse{data=types.ApiRocketpoolValidatorResponse} @@ -1085,124 +1316,12 @@ func getSyncCommitteeStatistics(validators []uint64, epoch uint64) (*SyncCommitt return &SyncCommitteesInfo{}, nil } - expectedSlots, err := getExpectedSyncCommitteeSlots(validators, epoch) - if err != nil { - return nil, err - } - stats, err := getSyncCommitteeSlotsStatistics(validators, epoch) if err != nil { return nil, err } - return &SyncCommitteesInfo{SyncCommitteesStats: stats, ExpectedSlots: expectedSlots}, nil -} - -func getExpectedSyncCommitteeSlots(validators []uint64, epoch uint64) (expectedSlots uint64, err error) { - if epoch < utils.Config.Chain.ClConfig.AltairForkEpoch { - // no sync committee duties before altair fork - return 0, nil - } - - lastFinalizedEpoch := services.LatestFinalizedEpoch() - if epoch > lastFinalizedEpoch { - epoch = lastFinalizedEpoch - } - - // retrieve activation and exit epochs from database per validator - type ValidatorInfo struct { - Id int64 `db:"validatorindex"` - ActivationEpoch uint64 `db:"activationepoch"` - ExitEpoch uint64 `db:"exitepoch"` - FirstPossibleSyncCommittee uint64 // calculated - LastPossibleSyncCommittee uint64 // calculated - } - - var validatorsInfoFromDb = []ValidatorInfo{} - query, args, err := sqlx.In(`SELECT validatorindex, activationepoch, exitepoch FROM validators WHERE validatorindex IN (?) ORDER BY validatorindex ASC`, validators) - if err != nil { - return 0, err - } - - err = db.ReaderDb.Select(&validatorsInfoFromDb, db.ReaderDb.Rebind(query), args...) - if err != nil { - return 0, err - } - - // only check validators that are/have been active and that did not exit before altair - const noEpoch = uint64(9223372036854775807) - var validatorsInfo = make([]ValidatorInfo, 0, len(validatorsInfoFromDb)) - for _, v := range validatorsInfoFromDb { - if v.ActivationEpoch != noEpoch && v.ActivationEpoch < epoch && (v.ExitEpoch == noEpoch || v.ExitEpoch >= utils.Config.Chain.ClConfig.AltairForkEpoch) { - validatorsInfo = append(validatorsInfo, v) - } - } - - if len(validatorsInfo) == 0 { - // no validators relevant for sync duties - return 0, nil - } - - // we need all related and unique timeframes (activation and exit sync period) for all validators - uniquePeriods := make(map[uint64]bool) - for i := range validatorsInfo { - // first epoch (activation epoch or Altair if Altair was later as there were no sync committees pre Altair) - firstSyncEpoch := validatorsInfo[i].ActivationEpoch - if validatorsInfo[i].ActivationEpoch < utils.Config.Chain.ClConfig.AltairForkEpoch { - firstSyncEpoch = utils.Config.Chain.ClConfig.AltairForkEpoch - } - validatorsInfo[i].FirstPossibleSyncCommittee = utils.SyncPeriodOfEpoch(firstSyncEpoch) - uniquePeriods[validatorsInfo[i].FirstPossibleSyncCommittee] = true - - // last epoch (exit epoch or current epoch if not exited yet) - lastSyncEpoch := epoch - if validatorsInfo[i].ExitEpoch != noEpoch && validatorsInfo[i].ExitEpoch <= epoch { - lastSyncEpoch = validatorsInfo[i].ExitEpoch - } - validatorsInfo[i].LastPossibleSyncCommittee = utils.SyncPeriodOfEpoch(lastSyncEpoch) - uniquePeriods[validatorsInfo[i].LastPossibleSyncCommittee] = true - } - - // transform map to slice; this will be used to query sync_committees_count_per_validator - periodSlice := make([]uint64, 0, len(uniquePeriods)) - for period := range uniquePeriods { - periodSlice = append(periodSlice, period) - } - - // get aggregated count for all relevant committees from sync_committees_count_per_validator - var countStatistics []struct { - Period uint64 `db:"period"` - CountSoFar float64 `db:"count_so_far"` - } - - query, args, errs := sqlx.In(`SELECT period, count_so_far FROM sync_committees_count_per_validator WHERE period IN (?) ORDER BY period ASC`, periodSlice) - if errs != nil { - return 0, errs - } - err = db.ReaderDb.Select(&countStatistics, db.ReaderDb.Rebind(query), args...) - if err != nil { - return 0, err - } - if len(countStatistics) != len(periodSlice) { - return 0, fmt.Errorf("unable to retrieve all sync committee count statistics, required %v entries but got %v entries (epoch: %v)", len(periodSlice), len(countStatistics), epoch) - } - - // transform query result to map for easy access - periodInfoMap := make(map[uint64]float64) - for _, pl := range countStatistics { - periodInfoMap[pl.Period] = pl.CountSoFar - } - - // calculate expected committies for every single validator and aggregate them - expectedCommitties := 0.0 - for _, vi := range validatorsInfo { - expectedCommitties += periodInfoMap[vi.LastPossibleSyncCommittee] - periodInfoMap[vi.FirstPossibleSyncCommittee] - } - - // transform committees to slots - expectedSlots = uint64(expectedCommitties * float64(utils.SlotsPerSyncCommittee())) - - return expectedSlots, nil + return &SyncCommitteesInfo{SyncCommitteesStats: stats, ExpectedSlots: 0}, nil } func getSyncCommitteeSlotsStatistics(validators []uint64, epoch uint64) (types.SyncCommitteesStats, error) { @@ -1555,9 +1674,9 @@ func getEpoch(epoch int64) ([]interface{}, error) { } // ApiValidator godoc -// @Summary Get up to 100 validators -// @Tags Validator -// @Description Searching for too many validators based on their pubkeys will lead to a "URI too long" error +// @Tags Validators +// @Summary Get validator +// @Description Retrieve validator information by index or pubkey (up to 100). Use the POST endpoint if you get URL too long errors. // @Produce json // @Param indexOrPubkey path string true "Up to 100 validator indicesOrPubkeys, comma separated" // @Success 200 {object} types.ApiResponse{data=[]types.APIValidatorResponse} @@ -1568,9 +1687,9 @@ func ApiValidatorGet(w http.ResponseWriter, r *http.Request) { } // ApiValidator godoc -// @Summary Get up to 100 validators -// @Tags Validator -// @Description This POST endpoint exists because the GET endpoint can lead to a "URI too long" error when searching for too many validators based on their pubkeys. +// @Tags Validators +// @Summary Get validator +// @Description Retrieve validator information by index or pubkey (up to 100). // @Produce json // @Param indexOrPubkey body types.DashboardRequest true "Up to 100 validator indicesOrPubkeys, comma separated" // @Success 200 {object} types.ApiResponse{data=[]types.APIValidatorResponse} @@ -1731,8 +1850,9 @@ type ApiValidatorResponse struct { } // ApiValidatorDailyStats godoc -// @Summary Get the daily validator stats by the validator index -// @Tags Validator +// @Summary Get validator statistics +// @Description: Retrieve daily stats for a validator by index +// @Tags Validators // @Produce json // @Param index path string true "Validator index" // @Param end_day query string false "End day (default: latest day)" @@ -1833,8 +1953,9 @@ func ApiValidatorDailyStats(w http.ResponseWriter, r *http.Request) { } // ApiValidatorByEth1Address godoc -// @Summary Get all validators that belong to an eth1 address -// @Tags Validator +// @Summary Get validator information by eth1 address +// @Description Retrieve validator information by eth1 address +// @Tags Validators // @Produce json // @Param eth1address path string true "Eth1 address from which the validator deposits were sent". It can also be a valid ENS name. // @Param limit query string false "Limit the number of results (default: 2000)" @@ -1886,8 +2007,9 @@ func ApiValidatorByEth1Address(w http.ResponseWriter, r *http.Request) { } // ApiValidator godoc -// @Summary Get the income detail history of up to 100 validators -// @Tags Validator +// @Summary Get validator income detail history +// @Description Retrieve validator income detail history by index or pubkey (up to 100). +// @Tags Validators // @Produce json // @Param indexOrPubkey path string true "Up to 100 validator indicesOrPubkeys, comma separated" // @Param latest_epoch query int false "The latest epoch to consider in the query" @@ -2021,8 +2143,9 @@ func getIncomeDetailsHistoryQueryParameters(q url.Values) (uint64, uint64, error } // ApiValidatorWithdrawals godoc -// @Summary Get the withdrawal history of up to 100 validators for the last 100 epochs. To receive older withdrawals modify the epoch paraum -// @Tags Validator +// @Summary Get validator withdrawal history +// @Description Retrieve validator withdrawal history by index or pubkey (up to 100). +// @Tags Validators // @Produce json // @Param indexOrPubkey path string true "Up to 100 validator indicesOrPubkeys, comma separated" // @Param epoch query int false "the start epoch for the withdrawal history (default: latest epoch)" @@ -2067,10 +2190,11 @@ func ApiValidatorWithdrawals(w http.ResponseWriter, r *http.Request) { dataFormatted := make([]*types.ApiValidatorWithdrawalResponse, 0, len(data)) for _, w := range data { + index := uint64(max(w.Index, 0)) dataFormatted = append(dataFormatted, &types.ApiValidatorWithdrawalResponse{ Epoch: w.Slot / utils.Config.Chain.ClConfig.SlotsPerEpoch, Slot: w.Slot, - Index: w.Index, + Index: index, ValidatorIndex: w.ValidatorIndex, Amount: w.Amount, BlockRoot: fmt.Sprintf("0x%x", w.BlockRoot), @@ -2091,8 +2215,9 @@ func ApiValidatorWithdrawals(w http.ResponseWriter, r *http.Request) { } // ApiValidatorBlsChange godoc -// @Summary Gets the BLS withdrawal address change for up to 100 validators -// @Tags Validator +// @Description Retrieve validator BLS change history by index or pubkey (up to 100). +// @Tags Validators +// @Summary Get validator bls change history // @Produce json // @Param indexOrPubkey path string true "Up to 100 validator indicesOrPubkeys, comma separated" // @Success 200 {object} types.ApiResponse{data=[]types.ApiValidatorBlsChangeResponse} @@ -2133,7 +2258,8 @@ func ApiValidatorBlsChange(w http.ResponseWriter, r *http.Request) { Address: fmt.Sprintf("0x%x", d.Address), Signature: fmt.Sprintf("0x%x", d.Signature), WithdrawalCredentialsOld: fmt.Sprintf("0x%x", d.WithdrawalCredentialsOld), - WithdrawalCredentialsNew: fmt.Sprintf("0x"+utils.BeginningOfSetWithdrawalCredentials+"%x", d.Address), + // BLS change is always 0x00 => 0x01 + WithdrawalCredentialsNew: fmt.Sprintf("0x"+utils.BeginningOfSetWithdrawalCredentials(1)+"%x", d.Address), }) } @@ -2150,8 +2276,9 @@ func ApiValidatorBlsChange(w http.ResponseWriter, r *http.Request) { } // ApiValidator godoc -// @Summary Get the balance history of up to 100 validators -// @Tags Validator +// @Summary Get validator balance history +// @Description Retrieve the validator balance history by index or pubkey (up to 100). +// @Tags Validators // @Produce json // @Param indexOrPubkey path string true "Up to 100 validator indicesOrPubkeys, comma separated" // @Param latest_epoch query int false "The latest epoch to consider in the query" @@ -2262,8 +2389,9 @@ func getBalanceHistoryQueryParameters(q url.Values) (uint64, uint64, error) { } // ApiValidatorPerformance godoc -// @Summary Get the current consensus reward performance of up to 100 validators -// @Tags Validator +// @Summary Get validator consensus layer rewards +// @Description Retrieve validator consensus layer rewards by index or pubkey (up to 100). +// @Tags Rewards // @Produce json // @Param indexOrPubkey path string true "Up to 100 validator indicesOrPubkeys, comma separated" // @Success 200 {object} types.ApiResponse{data=[]types.ApiValidatorPerformanceResponse} @@ -2374,8 +2502,9 @@ func ApiValidatorPerformance(w http.ResponseWriter, r *http.Request) { } // ApiValidatorExecutionPerformance godoc -// @Summary Get the current execution reward performance of up to 100 validators. If block was produced via mev relayer, this endpoint will use the relayer data as block reward instead of the normal block reward. -// @Tags Validator +// @Summary Get validator execution layer rewards +// @Description Retrieve validator execution rewards by index or pubkey (up to 100). +// @Tags Rewards // @Produce json // @Param indexOrPubkey path string true "Up to 100 validator indicesOrPubkeys, comma separated" // @Success 200 {object} types.ApiResponse{data=[]types.ApiValidatorExecutionPerformanceResponse} @@ -2404,14 +2533,6 @@ func ApiValidatorExecutionPerformance(w http.ResponseWriter, r *http.Request) { SendOKResponse(j, r.URL.String(), []any{result}) } -// ApiValidatorAttestationEffectiveness godoc -// @Summary DEPRECIATED - USE /attestationefficiency (Get the current performance of up to 100 validators) -// @Tags Validator -// @Produce json -// @Param indexOrPubkey path string true "Up to 100 validator indicesOrPubkeys, comma separated" -// @Success 200 {object} types.ApiResponse -// @Failure 400 {object} types.ApiResponse -// @Router /api/v1/validator/{indexOrPubkey}/attestationeffectiveness [get] func ApiValidatorAttestationEffectiveness(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -2447,8 +2568,9 @@ func ApiValidatorAttestationEffectiveness(w http.ResponseWriter, r *http.Request } // ApiValidatorAttestationEfficiency godoc -// @Summary Get the current performance of up to 100 validators -// @Tags Validator +// @Summary Get validator attestation efficiency +// @Description Retrieve validator attestation efficiency by index or pubkey (up to 100). +// @Tags Validators // @Produce json // @Param indexOrPubkey path string true "Up to 100 validator indicesOrPubkeys, comma separated" // @Success 200 {object} types.ApiResponse @@ -2488,27 +2610,10 @@ func ApiValidatorAttestationEfficiency(w http.ResponseWriter, r *http.Request) { } } -// func getAttestationEfficiencyQuery(epoch int64, queryIndices []uint64) (*sql.Rows, error) { -// return db.ReaderDb.Query(` -// SELECT aa.validatorindex, validators.pubkey, COALESCE( -// AVG(1 + inclusionslot - COALESCE(( -// SELECT MIN(slot) -// FROM blocks -// WHERE slot > aa.attesterslot AND blocks.status = '1' -// ), 0) -// ), 0)::float AS attestation_efficiency -// FROM attestation_assignments_p aa -// INNER JOIN blocks ON blocks.slot = aa.inclusionslot AND blocks.status <> '3' -// INNER JOIN validators ON validators.validatorindex = aa.validatorindex -// WHERE aa.week >= $1 / 1575 AND aa.epoch > $1 AND (validators.validatorindex = ANY($2)) AND aa.inclusionslot > 0 -// GROUP BY aa.validatorindex, validators.pubkey -// ORDER BY aa.validatorindex -// `, epoch, pq.Array(queryIndices)) -// } - // ApiValidatorLeaderboard godoc -// @Summary Get the current top 100 performing validators (using the income over the last 7 days) -// @Tags Validator +// @Summary Get validator leaderboard +// @Description Get the current top 100 performing validators (using the income over the last 7 days) +// @Tags Rewards // @Produce json // @Success 200 {object} types.ApiResponse{data=[]types.ApiValidatorPerformanceResponse} // @Failure 400 {object} types.ApiResponse @@ -2539,8 +2644,9 @@ func ApiValidatorLeaderboard(w http.ResponseWriter, r *http.Request) { } // ApiValidatorDeposits godoc -// @Summary Get all eth1 deposits for up to 100 validators -// @Tags Validator +// @Summary Get validator execution layer deposits +// @Description Get all eth1 deposits for up to 100 validators +// @Tags Validators // @Produce json // @Param indexOrPubkey path string true "Up to 100 validator indicesOrPubkeys, comma separated" // @Success 200 {object} types.ApiResponse{data=[]types.ApiValidatorDepositsResponse} @@ -2573,10 +2679,14 @@ func ApiValidatorDeposits(w http.ResponseWriter, r *http.Request) { } // ApiValidatorAttestations godoc -// @Summary Get all attestations during the last 100 epochs for up to 100 validators -// @Tags Validator +// @Summary Get validator attestations +// @Description Get all attestations during the last 100 epochs for up to 100 validators +// @Tags Validators // @Produce json // @Param indexOrPubkey path string true "Up to 100 validator indicesOrPubkeys, comma separated" +// @Param startEpoch query int false "Start epoch for the query (default: latest epoch - 99)" +// @Param endEpoch query int false "End epoch for the query (default: latest epoch)" +// @Param slim query bool false "If true, drops rarely used week and committee index fields from the response" // @Success 200 {object} types.ApiResponse{[]types.ApiValidatorAttestationsResponse} // @Failure 400 {object} types.ApiResponse // @Router /api/v1/validator/{indexOrPubkey}/attestations [get] @@ -2585,8 +2695,10 @@ func ApiValidatorAttestations(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") j := json.NewEncoder(w) + q := r.URL.Query() vars := mux.Vars(r) maxValidators := getUserPremium(r).MaxValidators + latestEpoch := services.LatestEpoch() queryIndices, err := parseApiValidatorParamToIndices(vars["indexOrPubkey"], maxValidators) if err != nil { @@ -2594,12 +2706,37 @@ func ApiValidatorAttestations(w http.ResponseWriter, r *http.Request) { return } - history, err := db.BigtableClient.GetValidatorAttestationHistory(queryIndices, services.LatestEpoch()-99, services.LatestEpoch()) + startEpoch := max(latestEpoch-99, 0) + endEpoch := latestEpoch + + if q.Has("startEpoch") { + userStartEpoch, err := strconv.ParseUint(q.Get("startEpoch"), 10, 64) + // must be within the pre-calculated range + if err != nil || userStartEpoch < startEpoch || userStartEpoch > endEpoch { + SendBadRequestResponse(w, r.URL.String(), "invalid start epoch parameter") + return + } + logger.Tracef("user start epoch: %d, default start epoch: %d", userStartEpoch, startEpoch) + startEpoch = max(startEpoch, userStartEpoch) // max not needed, but just to be sure + } + + if q.Has("endEpoch") { + userEndEpoch, err := strconv.ParseUint(q.Get("endEpoch"), 10, 64) + if err != nil || userEndEpoch < startEpoch || userEndEpoch > endEpoch { + SendBadRequestResponse(w, r.URL.String(), "invalid end epoch parameter") + return + } + logger.Tracef("user end epoch: %d, default end epoch: %d", userEndEpoch, endEpoch) + endEpoch = min(endEpoch, userEndEpoch) // ditto + } + + history, err := db.BigtableClient.GetValidatorAttestationHistory(queryIndices, startEpoch, endEpoch) if err != nil { SendBadRequestResponse(w, r.URL.String(), "could not retrieve db results") return } + // following over-allocates if the user passes a custom startEpoch and endEpoch, but thats fine responseData := make([]*types.ApiValidatorAttestationsResponse, 0, len(history)*100) epochsPerWeek := utils.EpochsPerDay() * 7 @@ -2630,7 +2767,23 @@ func ApiValidatorAttestations(w http.ResponseWriter, r *http.Request) { response := &types.ApiResponse{} response.Status = "OK" - response.Data = responseData + if q.Has("slim") && q.Get("slim") == "true" { + // if slim is true, drop the week and committee index fields + slimmedResponseData := make([]*types.ApiValidatorAttestationsResponseSlim, 0, len(responseData)) + for _, attestation := range responseData { + slimmedResponseData = append(slimmedResponseData, &types.ApiValidatorAttestationsResponseSlim{ + AttesterSlot: attestation.AttesterSlot, + Epoch: attestation.Epoch, + InclusionSlot: attestation.InclusionSlot, + Status: attestation.Status, + ValidatorIndex: attestation.ValidatorIndex, + }) + } + response.Data = slimmedResponseData + } else { + // otherwise, keep the full response data + response.Data = responseData + } err = j.Encode(response) @@ -2641,8 +2794,9 @@ func ApiValidatorAttestations(w http.ResponseWriter, r *http.Request) { } // ApiValidatorProposals godoc -// @Summary Get all proposed blocks during the last 100 epochs for up to 100 validators. Optionally set the epoch query parameter to look back further. -// @Tags Validator +// @Summary Get validator proposals +// @Description Get all proposed blocks during the last 100 epochs for up to 100 validators. Optionally set the epoch query parameter to look back further. +// @Tags Validators // @Produce json // @Param indexOrPubkey path string true "Up to 100 validator indicesOrPubkeys, comma separated" // @Param epoch query string false "Page the result by epoch" @@ -2729,9 +2883,129 @@ func ApiValidatorProposals(w http.ResponseWriter, r *http.Request) { returnQueryResultsAsArray(rows, w, r) } +func ApiValidatorConsolidationRequests(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + vars := mux.Vars(r) + q := r.URL.Query() + + limitQuery := q.Get("limit") + offsetQuery := q.Get("offset") + + offset, err := strconv.ParseInt(offsetQuery, 10, 64) + if err != nil { + offset = 0 + } + + limit, err := strconv.ParseInt(limitQuery, 10, 64) + if err != nil { + limit = 100 + offset + } + + if offset < 0 { + offset = 0 + } + + if limit > (100+offset) || limit <= 0 || limit <= offset { + limit = 100 + offset + } + + maxValidators := getUserPremium(r).MaxValidators + queryIndices, err := parseApiValidatorParamToIndices(vars["indexOrPubkey"], maxValidators) + if err != nil { + SendBadRequestResponse(w, r.URL.String(), err.Error()) + return + } + + rows, err := db.ReaderDb.Query(` + SELECT + slot_processed as block_slot, + block_processed_root as block_root, + index_processed as request_index, + amount_consolidated, + sv.validatorindex as source_index, + tv.validatorindex as target_index + FROM blocks_consolidation_requests_v2 + INNER JOIN validators sv ON (sv.pubkey = source_pubkey) + INNER JOIN validators tv ON (tv.pubkey = target_pubkey) + WHERE sv.validatorindex = ANY($1) OR tv.validatorindex = ANY($1) + AND blocks_consolidation_requests_v2.status = 'completed' + ORDER BY slot_processed DESC, index_processed DESC + limit $2 offset $3 + `, pq.Array(queryIndices), limit, offset) + if err != nil { + logger.WithError(err).Error("could not retrieve db results") + SendBadRequestResponse(w, r.URL.String(), "could not retrieve db results") + return + } + defer rows.Close() + + returnQueryResultsAsArray(rows, w, r) +} + +func ApiValidatorSwitchToCompoundingRequests(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + vars := mux.Vars(r) + q := r.URL.Query() + + limitQuery := q.Get("limit") + offsetQuery := q.Get("offset") + + offset, err := strconv.ParseInt(offsetQuery, 10, 64) + if err != nil { + offset = 0 + } + + limit, err := strconv.ParseInt(limitQuery, 10, 64) + if err != nil { + limit = 100 + offset + } + + if offset < 0 { + offset = 0 + } + + if limit > (100+offset) || limit <= 0 || limit <= offset { + limit = 100 + offset + } + + maxValidators := getUserPremium(r).MaxValidators + queryIndices, err := parseApiValidatorParamToIndices(vars["indexOrPubkey"], maxValidators) + if err != nil { + SendBadRequestResponse(w, r.URL.String(), err.Error()) + return + } + + // TODO: remove v1 table dependency once eth1id resolving is available + // See https://bitfly1.atlassian.net/browse/BEDS-1522 + rows, err := db.ReaderDb.Query(` + SELECT + slot_processed as block_slot, + block_processed_root as block_root, + index_processed as request_index, + v.validatorindex as validator_index, + COALESCE(v1.address, decode('0000000000000000000000000000000000000000', 'hex')) as address + FROM blocks_switch_to_compounding_requests_v2 + INNER JOIN validators v ON (v.pubkey = validator_pubkey) + LEFT JOIN blocks_switch_to_compounding_requests v1 ON (blocks_switch_to_compounding_requests_v2.slot_processed = v1.block_slot AND blocks_switch_to_compounding_requests_v2.block_processed_root = v1.block_root AND blocks_switch_to_compounding_requests_v2.index_processed = v1.request_index) + WHERE v.validatorindex = ANY($1) + AND blocks_switch_to_compounding_requests_v2.status = 'completed' + ORDER BY slot_processed DESC, index_processed DESC + limit $2 offset $3`, pq.Array(queryIndices), limit, offset) + if err != nil { + logger.WithError(err).Error("could not retrieve db results") + SendBadRequestResponse(w, r.URL.String(), "could not retrieve db results") + return + } + defer rows.Close() + + returnQueryResultsAsArray(rows, w, r) +} + // ApiGraffitiwall godoc -// @Summary Get the most recent pixels that have been painted. // @Tags Misc +// @Summary Get graffiti wall // @Description Returns the most recent pixels that have been painted during the last 10000 slots. // @Description Optionally set the slot query parameter to look back further. // @Description Boundary coordinates are included. @@ -2839,7 +3113,8 @@ func ApiGraffitiwall(w http.ResponseWriter, r *http.Request) { } // ApiChart godoc -// @Summary Returns charts from the page https://beaconcha.in/charts as PNG +// @Summary Get chart +// @Description Returns charts from the page https://beaconcha.in/charts as PNG // @Tags Misc // @Produce json // @Param chart path string true "Chart name (see https://github.com/gobitfly/eth2-beaconchain-explorer/blob/master/services/charts_updater.go#L20 for all available names)" @@ -2869,7 +3144,8 @@ func ApiChart(w http.ResponseWriter, r *http.Request) { } // APIGetToken godoc -// @Summary Exchange your oauth code for an access token or refresh your access token +// @Summary Get OAUTH API token +// @Description Exchange your oauth code for an access token or refresh your access token // @Tags User // @Produce json // @Param grant_type formData string true "grant_type use authorization_code for oauth code or refresh_token if you wish to refresh an token" @@ -3050,7 +3326,8 @@ func getDeviceNameFromUA(userAgent string) string { } // MobileNotificationUpdatePOST godoc -// @Summary Register or update your mobile notification token +// @Summary Change mobile notification token +// @Description Register or update your mobile notification token // @Tags User // @Produce json // @Param token body string true "Your device`s firebase notification token" @@ -3424,7 +3701,8 @@ func GetMobileWidgetStats(w http.ResponseWriter, r *http.Request, indexOrPubkey } // MobileDeviceSettings godoc -// @Summary Get your device settings, currently only whether to enable mobile notifcations or not +// @Summary Get device settings +// @Description Get your device settings, currently only whether to enable mobile notifcations or not // @Tags User // @Produce json // @Success 200 {object} types.ApiResponse{data=types.MobileSettingsData} @@ -3450,7 +3728,8 @@ func MobileDeviceSettings(w http.ResponseWriter, r *http.Request) { } // MobileDeviceSettingsPOST godoc -// @Summary Changing your devices mobile settings +// @Summary Update device settings +// @Description Update your device settings, currently only whether to enable mobile notifcations or not. // @Tags User // @Produce json // @Param notify_enabled body bool true "Whether to enable mobile notifications for this device or not" @@ -3503,7 +3782,8 @@ func MobileDeviceSettingsPOST(w http.ResponseWriter, r *http.Request) { } // MobileTagedValidators godoc -// @Summary Get all your tagged validators +// @Summary Get tagged validators +// @Description Get all your tagged validators // @Tags User // @Produce json // @Success 200 {object} types.ApiResponse{data=[]types.MinimalTaggedValidators} @@ -3552,7 +3832,8 @@ func parseUintWithDefault(input string, defaultValue uint64) uint64 { } // ClientStats godoc -// @Summary Get your client submitted stats +// @Summary Get client stats +// @Description Get your client submitted stats // @Tags User // @Produce json // @Param offset path int false "Data offset, default 0" default(0) @@ -3609,7 +3890,8 @@ func ClientStats(w http.ResponseWriter, r *http.Request) { } // ClientStatsPost godoc -// @Summary Used in eth2 clients to submit stats to your beaconcha.in account. This data can be accessed by the app or the user stats api call. +// @Summary Submit client stats +// @Description Used in consensus layer clients to submit stats to your beaconcha.in account. This data can be accessed by the app or the user stats api call. // @Tags User // @Produce json // @Param apikey query string true "User API key, can be found on https://beaconcha.in/user/settings" @@ -3810,8 +4092,8 @@ func insertStats(userData *types.UserWithPremium, machine string, body *map[stri } // ApiWithdrawalCredentialsValidators godoc -// @Summary Get validator indexes and pubkeys of a withdrawal credential or eth1 address -// @Tags Validator +// @Summary Get validators by credentials or address +// @Tags Validators // @Description Returns the validator indexes and pubkeys of a withdrawal credential or eth1 address // @Produce json // @Param withdrawalCredentialsOrEth1address path string true "Provide a withdrawal credential or an eth1 address with an optional 0x prefix". It can also be a valid ENS name. @@ -3840,7 +4122,7 @@ func ApiWithdrawalCredentialsValidators(w http.ResponseWriter, r *http.Request) credentials, err := utils.AddressToWithdrawalCredentials(credentialsOrAddress) if err != nil { // Input is not an address so it must already be withdrawal credentials - credentials = credentialsOrAddress + credentials = [][]byte{credentialsOrAddress} } limitQuery := q.Get("limit") @@ -3864,7 +4146,7 @@ func ApiWithdrawalCredentialsValidators(w http.ResponseWriter, r *http.Request) validatorindex, pubkey FROM validators - WHERE withdrawalcredentials = $1 + WHERE withdrawalcredentials = ANY($1) ORDER BY validatorindex ASC LIMIT $2 OFFSET $3 @@ -3888,8 +4170,8 @@ func ApiWithdrawalCredentialsValidators(w http.ResponseWriter, r *http.Request) } // ApiProposalLuck godoc -// @Summary Get the proposal luck of a validator or a list of validators -// @Tags Validator +// @Summary Get validator proposal luck +// @Tags Validators // @Description Returns the proposal luck of a validator or a list of validators // @Produce json // @Param validators query string true "Provide a comma separated list of validator indices or pubkeys" @@ -3966,19 +4248,33 @@ func getProposalLuckStats(indices []uint64) (*types.ApiProposalLuckResponse, err ORDER BY slot ASC`, pq.Array(indices)) }) + var effectiveBalanceSumEth uint64 = 0 + g.Go(func() error { + var err error + balances, err := db.BigtableClient.GetValidatorBalanceHistory(indices, services.LatestEpoch(), services.LatestEpoch()) + if err != nil { + return fmt.Errorf("error in GetValidatorBalanceHistory: %w", err) + } + + for _, balance := range balances { + effectiveBalanceSumEth += balance[0].EffectiveBalance / 1e9 + } + return nil + }) + err := g.Wait() if err != nil { return nil, err } - proposalLuck, proposalTimeFrame := getProposalLuck(slots, len(indices), firstActivationEpoch) + proposalLuck, proposalTimeFrame := getProposalLuck(slots, effectiveBalanceSumEth, firstActivationEpoch) if proposalLuck > 0 { data.ProposalLuck = &proposalLuck timeframeName := getProposalTimeframeName(proposalTimeFrame) data.TimeFrameName = &timeframeName } - avgProposalInterval := getAvgSlotInterval(len(indices)) + avgProposalInterval := getAvgSlotInterval(effectiveBalanceSumEth) data.AverageProposalInterval = avgProposalInterval var estimateLowerBoundSlot *uint64 @@ -4011,86 +4307,6 @@ func DecodeMapStructure(input interface{}, output interface{}) error { return decoder.Decode(input) } -// TODO Replace app code to work with new income balance dashboard -// Meanwhile keep old code from Feb 2021 to be app compatible -func APIDashboardDataBalance(w http.ResponseWriter, r *http.Request) { - currency := GetCurrency(r) - - w.Header().Set("Content-Type", "application/json") - - q := r.URL.Query() - - queryValidatorIndices, queryValidatorPubkeys, err := parseValidatorsFromQueryString(q.Get("validators"), 100) - if err != nil || len(queryValidatorPubkeys) > 0 { - logger.WithError(err).WithField("route", r.URL.String()).Error("error parsing validators from query string") - http.Error(w, "Invalid query", http.StatusBadRequest) - return - } - if len(queryValidatorIndices) < 1 { - http.Error(w, "Invalid query", http.StatusBadRequest) - return - } - // queryValidatorsArr := pq.Array(queryValidators) - - // get data from one week before latest epoch - latestEpoch := services.LatestEpoch() - oneWeekEpochs := uint64(3600 * 24 * 7 / float64(utils.Config.Chain.ClConfig.SecondsPerSlot*utils.Config.Chain.ClConfig.SlotsPerEpoch)) - queryOffsetEpoch := uint64(0) - if latestEpoch > oneWeekEpochs { - queryOffsetEpoch = latestEpoch - oneWeekEpochs - } - - if len(queryValidatorIndices) == 0 { - SendBadRequestResponse(w, r.URL.String(), "no or invalid validator indicies provided") - } - - balances, err := db.BigtableClient.GetValidatorBalanceHistory(queryValidatorIndices, latestEpoch-queryOffsetEpoch, latestEpoch) - if err != nil { - logger.WithError(err).WithField("route", r.URL.String()).Errorf("error retrieving validator balance history") - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - dataMap := make(map[uint64]*types.DashboardValidatorBalanceHistory) - - for _, balanceHistory := range balances { - for _, history := range balanceHistory { - if dataMap[history.Epoch] == nil { - dataMap[history.Epoch] = &types.DashboardValidatorBalanceHistory{} - } - dataMap[history.Epoch].Balance += history.Balance - dataMap[history.Epoch].EffectiveBalance += history.EffectiveBalance - dataMap[history.Epoch].Epoch = history.Epoch - dataMap[history.Epoch].ValidatorCount++ - } - } - - data := make([]*types.DashboardValidatorBalanceHistory, 0, len(dataMap)) - - for _, e := range dataMap { - data = append(data, e) - } - - sort.Slice(data, func(i, j int) bool { - return data[i].Epoch < data[j].Epoch - }) - - balanceHistoryChartData := make([][4]float64, len(data)) - clPrice := price.GetPrice(utils.Config.Frontend.ClCurrency, currency) - for i, item := range data { - balanceHistoryChartData[i][0] = float64(utils.EpochToTime(item.Epoch).Unix() * 1000) - balanceHistoryChartData[i][1] = item.ValidatorCount - balanceHistoryChartData[i][2] = float64(item.Balance) / 1e9 * clPrice - balanceHistoryChartData[i][3] = float64(item.EffectiveBalance) / 1e9 * clPrice - } - - err = json.NewEncoder(w).Encode(balanceHistoryChartData) - if err != nil { - logger.WithError(err).WithField("route", r.URL.String()).Error("error enconding json response") - sendServerErrorResponse(w, r.URL.String(), "could not serialize data results") - return - } -} - func getAuthClaims(r *http.Request) *utils.CustomClaims { middleWare := gorillacontext.Get(r, utils.MobileAuthorizedKey) if middleWare == nil { diff --git a/handlers/api_docs.go b/handlers/api_docs.go new file mode 100644 index 0000000000..551fbe710a --- /dev/null +++ b/handlers/api_docs.go @@ -0,0 +1,26 @@ +package handlers + +import ( + "net/http" + + "github.com/gobitfly/eth2-beaconchain-explorer/templates" + "github.com/gobitfly/eth2-beaconchain-explorer/types" + "github.com/gobitfly/eth2-beaconchain-explorer/utils" +) + +func ApiDocs(w http.ResponseWriter, r *http.Request) { + templateFiles := append(layoutTemplateFiles, "api_docs.html") + var advertisewithusTemplate = templates.GetTemplate(templateFiles...) + + w.Header().Set("Content-Type", "text/html") + + data := InitPageData(w, r, "api_docs", "/api_docs", "API Documentation", templateFiles) + + pageData := &types.AdvertiseWithUsPageData{} + pageData.RecaptchaKey = utils.Config.Frontend.RecaptchaSiteKey + + data.Data = pageData + if handleTemplateError(w, r, "api_docs.go.go", "AdvertiseWithUs", "", advertisewithusTemplate.ExecuteTemplate(w, "layout", data)) != nil { + return // an error has occurred and was processed + } +} diff --git a/handlers/api_eth1.go b/handlers/api_eth1.go index 3fbf73a569..fbc958674a 100644 --- a/handlers/api_eth1.go +++ b/handlers/api_eth1.go @@ -26,8 +26,8 @@ import ( ) // ApiEth1Deposit godoc -// @Summary Get an eth1 deposit by its eth1 transaction hash -// @Tags Execution +// @Description Get the deposit information for a given eth1 transaction hash. +// @Tags Validator deposits // @Produce json // @Param txhash path string true "Eth1 transaction hash" // @Success 200 {object} types.ApiResponse @@ -56,12 +56,12 @@ func ApiEth1Deposit(w http.ResponseWriter, r *http.Request) { } // ApiETH1ExecBlocks godoc -// @Summary Get execution blocks -// @Tags Execution +// @Tags Blocks +// @Summary Get blocks by number // @Description Get execution blocks by execution block number // @Produce json // @Param blockNumber path string true "Provide one or more execution block numbers. Coma separated up to max 100. " -// @Success 200 {object} types.ApiResponse +// @Success 200 {object} []types.ExecutionBlockApiResponse // @Failure 400 {object} types.ApiResponse // @Router /api/v1/execution/block/{blockNumber} [get] func ApiETH1ExecBlocks(w http.ResponseWriter, r *http.Request) { @@ -117,8 +117,8 @@ func ApiETH1ExecBlocks(w http.ResponseWriter, r *http.Request) { } // ApiETH1AccountProposedBlocks godoc -// @Summary Get proposed or mined blocks -// @Tags Execution +// @Tags Blocks +// @Summary Get proposed blocks by address, proposer index or proposer pubkey // @Description Get a list of proposed or mined blocks from a given fee recipient address, proposer index or proposer pubkey. // @Description Mixed use of recipient addresses and proposer indexes or proposer pubkeys with an offset is discouraged as it can lead to skipped entries. // @Produce json @@ -126,7 +126,7 @@ func ApiETH1ExecBlocks(w http.ResponseWriter, r *http.Request) { // @Param offset query int false "Offset" default(0) // @Param limit query int false "Limit the amount of entries you wish to receive (Maximum: 100)" default(10) maximum(100) // @Param sort query string false "Sort via the block number either by 'asc' or 'desc'" default(desc) -// @Success 200 {object} types.ApiResponse +// @Success 200 {object} []types.ExecutionBlockApiResponse // @Failure 400 {object} types.ApiResponse // @Router /api/v1/execution/{addressIndexOrPubkey}/produced [get] func ApiETH1AccountProducedBlocks(w http.ResponseWriter, r *http.Request) { @@ -154,8 +154,8 @@ func ApiETH1AccountProducedBlocks(w http.ResponseWriter, r *http.Request) { return } - var offset uint64 = 0 - var limit uint64 = 10 + var offset uint64 + var limit uint64 var isSortAsc bool = false offsetString := r.URL.Query().Get("offset") @@ -251,11 +251,11 @@ func ApiETH1AccountProducedBlocks(w http.ResponseWriter, r *http.Request) { } // ApiETH1GasNowData godoc -// @Summary Gets the current estimation for gas prices in GWei. -// @Tags Execution -// @Description The response is split into four estimated inclusion speeds rapid (15 seconds), fast (1 minute), standard (3 minutes) and slow (> 10 minutes). +// @Tags Gas +// @Summary Get current gas prices +// @Description Gets the current estimation for gas prices in GWei.. The response is split into four estimated inclusion speeds rapid (15 seconds), fast (1 minute), standard (3 minutes) and slow (> 10 minutes). // @Produce json -// @Success 200 {object} types.ApiResponse +// @Success 200 {object} types.GasNowPageData // @Failure 400 {object} types.ApiResponse // @Router /api/v1/execution/gasnow [get] func ApiEth1GasNowData(w http.ResponseWriter, r *http.Request) { @@ -283,13 +283,13 @@ func ApiEth1GasNowData(w http.ResponseWriter, r *http.Request) { } // ApiEth1Address godoc -// @Summary Gets information about an Ethereum address. -// @Tags Execution +// @Tags Addresses +// @Summary Get address balances // @Description Returns the ether balance and any token balances for a given Ethereum address. Amount of different ECR20 tokens is limited to 200. If you need more, use the /execution/address/{address}/erc20tokens endpoint. // @Produce json // @Param address path string true "provide an Ethereum address consists of an optional 0x prefix followed by 40 hexadecimal characters". It can also be a valid ENS name. // @Param token query string false "filter for a specific token by providing a ethereum token contract address" -// @Success 200 {object} types.ApiResponse +// @Success 200 {object} types.ApiEth1AddressResponse // @Failure 400 {object} types.ApiResponse // @Router /api/v1/execution/address/{address} [get] func ApiEth1Address(w http.ResponseWriter, r *http.Request) { @@ -346,14 +346,14 @@ func ApiEth1Address(w http.ResponseWriter, r *http.Request) { } // ApiEth1AddressERC20Tokens godoc -// @Summary Returns the ERC20 token balances for a given Ethereum address. -// @Tags Execution +// @Tags Addresses +// @Summary Get ERC20 token balances for address // @Description Returns the ERC20 token balances for a given Ethereum address. Supports pagination. // @Produce json // @Param address path string true "provide an Ethereum address consists of an optional 0x prefix followed by 40 hexadecimal characters". It can also be a valid ENS name. // @Param offset query int false "data offset" default(0) // @Param limit query int false "data limit (ranging from 1 to 200)" default(200) -// @Success 200 {object} types.ApiResponse +// @Success 200 {object} []types.ApiEth1AddressERC20TokenResponse // @Failure 400 {object} types.ApiResponse // @Router /api/v1/execution/address/{address}/erc20tokens [get] func ApiEth1AddressERC20Tokens(w http.ResponseWriter, r *http.Request) { @@ -758,7 +758,7 @@ func getAddressesOrIndicesFromAddressIndexOrPubkey(search string, max int) ([][] resultAddresses = append(resultAddresses, addInPub.Address) } else if len(addInPub.Pubkey) > 0 { pubkeys = append(pubkeys, addInPub.Pubkey) - } else if addInPub.Index > 0 && addInPub.Index < db.MaxSqlInteger { + } else if addInPub.Index < db.MaxSqlInteger { indices = append(indices, addInPub.Index) } } diff --git a/handlers/common.go b/handlers/common.go index 0d899859a6..cce7672a48 100644 --- a/handlers/common.go +++ b/handlers/common.go @@ -63,6 +63,7 @@ func GetValidatorEarnings(validators []uint64, currency string) (*types.Validato totalBalance := uint64(0) g := errgroup.Group{} + incomeForApr := types.ValidatorIncomePerformance{} g.Go(func() error { latestBalances, err := db.BigtableClient.GetValidatorBalanceHistory(validators, latestFinalizedEpoch, latestFinalizedEpoch) if err != nil { @@ -83,7 +84,18 @@ func GetValidatorEarnings(validators []uint64, currency string) (*types.Validato totalBalance += balance[0].Balance } - return nil + + // get performance for all validators with an effective balance, used for apr + // If we would use ${income} for APR, validators that have exited would be included in the performance but + // not on the divisor side as the effective balance is now zero, which would result in a higher/wrong APR. + // Hence we ignore exited validators in the APR calculation + indicesWithBalances := make([]uint64, 0) + for index := range balancesMap { + if balancesMap[index].EffectiveBalance > 0 { + indicesWithBalances = append(indicesWithBalances, index) + } + } + return db.GetValidatorIncomePerformance(indicesWithBalances, &incomeForApr) }) income := types.ValidatorIncomePerformance{} @@ -93,7 +105,14 @@ func GetValidatorEarnings(validators []uint64, currency string) (*types.Validato var totalDeposits uint64 g.Go(func() error { - return db.GetTotalValidatorDeposits(validators, &totalDeposits) + deposits, err := db.GetValidatorDepositsAndIncomingConsolidations(nil, validators) + if err != nil { + return err + } + for _, deposit := range deposits { + totalDeposits += deposit.DepositsAmount + } + return nil }) var firstActivationEpoch uint64 @@ -116,11 +135,22 @@ func GetValidatorEarnings(validators []uint64, currency string) (*types.Validato return err } } - err := db.GetValidatorDepositsForSlots(validators, firstSlot, lastSlot, &lastDeposits) + deposits, err := db.GetValidatorDepositsAndIncomingConsolidations(&db.SlotRange{StartSlot: firstSlot, EndSlot: lastSlot}, validators) if err != nil { return err } - return db.GetValidatorWithdrawalsForSlots(validators, firstSlot, lastSlot, &lastWithdrawals) + for _, deposit := range deposits { + lastDeposits += deposit.DepositsAmount + } + + withdrawals, err := db.GetValidatorWithdrawalsAndOutgoingConsolidations(&db.SlotRange{StartSlot: firstSlot, EndSlot: lastSlot}, validators) + if err != nil { + return err + } + for _, withdrawal := range withdrawals { + lastWithdrawals += withdrawal.WithdrawalsAmount + } + return nil }) proposals := []types.ValidatorProposalInfo{} @@ -135,11 +165,19 @@ func GetValidatorEarnings(validators []uint64, currency string) (*types.Validato clElPrice := price.GetPrice(utils.Config.Frontend.ClCurrency, utils.Config.Frontend.ElCurrency) - if totalDeposits == 0 { - totalDeposits = utils.Config.Chain.ClConfig.MaxEffectiveBalance * uint64(len(validators)) + totalEB := decimal.NewFromInt(0) + for _, v := range balancesMap { + totalEB = totalEB.Add(decimal.NewFromInt(int64(v.EffectiveBalance))) + } + // convert totalEB to el currency needed for el apr (fe gnosis) + totalEBInEl := totalEB.Mul(decimal.NewFromFloat(clElPrice)) + + if totalEB.IsZero() { + totalEB = decimal.NewFromInt(math.MaxInt64) // if all validators have exited, make all aprs zero by dividing by max int + totalEBInEl = decimal.NewFromInt(math.MaxInt64) } - clApr7d := income.ClIncomeWei7d.DivRound(decimal.NewFromInt(1e9), 18).DivRound(decimal.NewFromInt(int64(totalDeposits)), 18).Mul(decimal.NewFromInt(365)).Div(decimal.NewFromInt(7)).InexactFloat64() + clApr7d := incomeForApr.ClIncomeWei7d.DivRound(decimal.NewFromInt(1e9), 18).DivRound(totalEB, 18).Mul(decimal.NewFromInt(365)).Div(decimal.NewFromInt(7)).InexactFloat64() if clApr7d < float64(-1) { clApr7d = float64(-1) } @@ -147,7 +185,7 @@ func GetValidatorEarnings(validators []uint64, currency string) (*types.Validato clApr7d = float64(0) } - elApr7d := income.ElIncomeWei7d.DivRound(decimal.NewFromInt(1e9), 18).DivRound(decimal.NewFromInt(int64(totalDeposits)), 18).Mul(decimal.NewFromInt(365)).Div(decimal.NewFromInt(7)).InexactFloat64() + elApr7d := incomeForApr.ElIncomeWei7d.DivRound(decimal.NewFromInt(1e9), 18).DivRound(totalEBInEl, 18).Mul(decimal.NewFromInt(365)).Div(decimal.NewFromInt(7)).InexactFloat64() if elApr7d < float64(-1) { elApr7d = float64(-1) } @@ -155,7 +193,7 @@ func GetValidatorEarnings(validators []uint64, currency string) (*types.Validato elApr7d = float64(0) } - clApr31d := income.ClIncomeWei31d.DivRound(decimal.NewFromInt(1e9), 18).DivRound(decimal.NewFromInt(int64(totalDeposits)), 18).Mul(decimal.NewFromInt(365)).Div(decimal.NewFromInt(31)).InexactFloat64() + clApr31d := incomeForApr.ClIncomeWei31d.DivRound(decimal.NewFromInt(1e9), 18).DivRound(totalEB, 18).Mul(decimal.NewFromInt(365)).Div(decimal.NewFromInt(31)).InexactFloat64() if clApr31d < float64(-1) { clApr31d = float64(-1) } @@ -163,7 +201,7 @@ func GetValidatorEarnings(validators []uint64, currency string) (*types.Validato clApr31d = float64(0) } - elApr31d := income.ElIncomeWei31d.DivRound(decimal.NewFromInt(1e9), 18).DivRound(decimal.NewFromInt(int64(totalDeposits)), 18).Mul(decimal.NewFromInt(365)).Div(decimal.NewFromInt(31)).InexactFloat64() + elApr31d := incomeForApr.ElIncomeWei31d.DivRound(decimal.NewFromInt(1e9), 18).DivRound(totalEBInEl, 18).Mul(decimal.NewFromInt(365)).Div(decimal.NewFromInt(31)).InexactFloat64() if elApr31d < float64(-1) { elApr31d = float64(-1) } @@ -171,7 +209,7 @@ func GetValidatorEarnings(validators []uint64, currency string) (*types.Validato elApr31d = float64(0) } - clApr365d := income.ClIncomeWei365d.DivRound(decimal.NewFromInt(1e9), 18).DivRound(decimal.NewFromInt(int64(totalDeposits)), 18).InexactFloat64() + clApr365d := incomeForApr.ClIncomeWei365d.DivRound(decimal.NewFromInt(1e9), 18).DivRound(totalEB, 18).InexactFloat64() if clApr365d < float64(-1) { clApr365d = float64(-1) } @@ -179,7 +217,7 @@ func GetValidatorEarnings(validators []uint64, currency string) (*types.Validato clApr365d = float64(0) } - elApr365d := income.ElIncomeWei365d.DivRound(decimal.NewFromInt(1e9), 18).DivRound(decimal.NewFromInt(int64(totalDeposits)), 18).InexactFloat64() + elApr365d := incomeForApr.ElIncomeWei365d.DivRound(decimal.NewFromInt(1e9), 18).DivRound(totalEBInEl, 18).InexactFloat64() if elApr365d < float64(-1) { elApr365d = float64(-1) } @@ -229,8 +267,9 @@ func GetValidatorEarnings(validators []uint64, currency string) (*types.Validato } } - validatorProposalData.ProposalLuck, _ = getProposalLuck(slots, len(validators), firstActivationEpoch) - avgSlotInterval := uint64(getAvgSlotInterval(len(validators))) + ebEth := totalEB.DivRound(decimal.NewFromInt(1e9), 0).BigInt().Uint64() + validatorProposalData.ProposalLuck, _ = getProposalLuck(slots, ebEth, firstActivationEpoch) + avgSlotInterval := uint64(getAvgSlotInterval(ebEth)) avgSlotIntervalAsDuration := time.Duration(utils.Config.Chain.ClConfig.SecondsPerSlot*avgSlotInterval) * time.Second validatorProposalData.AvgSlotInterval = &avgSlotIntervalAsDuration if len(slots) > 0 { @@ -339,15 +378,15 @@ const year = utils.Year // given the blocks proposed by the validators and the number of validators // // precondition: slots is sorted by ascending block number -func getProposalLuck(slots []uint64, validatorsCount int, fromEpoch uint64) (float64, time.Duration) { +func getProposalLuck(slots []uint64, validatorEbEth uint64, fromEpoch uint64) (float64, time.Duration) { // Return 0 if there are no proposed blocks or no validators - if len(slots) == 0 || validatorsCount == 0 { + if len(slots) == 0 || validatorEbEth == 0 { return 0, 0 } - activeValidatorsCount := *services.GetLatestStats().ActiveValidatorCount + activeValidatorEbEth := *services.GetLatestStats().ActiveValidatorEbEth // Calculate the expected number of slot proposals for 30 days - expectedSlotProposals := calcExpectedSlotProposals(oneMonth, validatorsCount, activeValidatorsCount) + expectedSlotProposals := calcExpectedSlotProposals(oneMonth, validatorEbEth, activeValidatorEbEth) // Get the timeframe for which we should consider qualified proposals var proposalTimeFrame time.Duration @@ -383,7 +422,7 @@ func getProposalLuck(slots []uint64, validatorsCount int, fromEpoch uint64) (flo } // Recalculate expected slot proposals for the new timeframe - expectedSlotProposals = calcExpectedSlotProposals(proposalTimeFrame, validatorsCount, activeValidatorsCount) + expectedSlotProposals = calcExpectedSlotProposals(proposalTimeFrame, validatorEbEth, activeValidatorEbEth) if expectedSlotProposals == 0 { return 0, 0 } @@ -429,25 +468,25 @@ func getProposalTimeframeName(proposalTimeframe time.Duration) string { } // calcExpectedSlotProposals calculates the expected number of slot proposals for a certain time frame and validator count -func calcExpectedSlotProposals(timeframe time.Duration, validatorCount int, activeValidatorsCount uint64) float64 { - if validatorCount == 0 || activeValidatorsCount == 0 { +func calcExpectedSlotProposals(timeframe time.Duration, validatorsEbEth uint64, activeValidatorsEbEth uint64) float64 { + if validatorsEbEth == 0 || activeValidatorsEbEth == 0 { return 0 } slotsInTimeframe := timeframe.Seconds() / float64(utils.Config.Chain.ClConfig.SecondsPerSlot) - return (slotsInTimeframe / float64(activeValidatorsCount)) * float64(validatorCount) + return (slotsInTimeframe / float64(activeValidatorsEbEth)) * float64(validatorsEbEth) } // getAvgSlotInterval will return the average block interval for a certain number of validators // // result of the function should be interpreted as "1 in every X slots will be proposed by this amount of validators on avg." -func getAvgSlotInterval(validatorsCount int) float64 { +func getAvgSlotInterval(validatorsEbEth uint64) float64 { // don't estimate if there are no proposed blocks or no validators - activeValidatorsCount := *services.GetLatestStats().ActiveValidatorCount - if activeValidatorsCount == 0 { + activeValidatorsEbEth := *services.GetLatestStats().ActiveValidatorEbEth + if activeValidatorsEbEth == 0 { return 0 } - probability := float64(validatorsCount) / float64(activeValidatorsCount) + probability := float64(validatorsEbEth) / float64(activeValidatorsEbEth) // in a geometric distribution, the expected value of the number of trials needed until first success is 1/p // you can think of this as the average interval of blocks until you get a proposal return 1 / probability @@ -456,13 +495,13 @@ func getAvgSlotInterval(validatorsCount int) float64 { // getAvgSyncCommitteeInterval will return the average sync committee interval for a certain number of validators // // result of the function should be interpreted as "there will be one validator included in every X committees, on average" -func getAvgSyncCommitteeInterval(validatorsCount int) float64 { - activeValidatorsCount := *services.GetLatestStats().ActiveValidatorCount - if activeValidatorsCount == 0 { +func getAvgSyncCommitteeInterval(validatorsEbEth uint64) float64 { + activeValidatorsEbEth := *services.GetLatestStats().ActiveValidatorEbEth + if activeValidatorsEbEth == 0 { return 0 } - probability := (float64(utils.Config.Chain.ClConfig.SyncCommitteeSize) / float64(activeValidatorsCount)) * float64(validatorsCount) + probability := (float64(utils.Config.Chain.ClConfig.SyncCommitteeSize) / float64(activeValidatorsEbEth)) * float64(validatorsEbEth) // in a geometric distribution, the expected value of the number of trials needed until first success is 1/p // you can think of this as the average interval of sync committees until you expect to have been part of one return 1 / probability diff --git a/handlers/dashboard.go b/handlers/dashboard.go index 84fe844092..6107a9f1d6 100644 --- a/handlers/dashboard.go +++ b/handlers/dashboard.go @@ -3,29 +3,21 @@ package handlers import ( "bytes" "context" - "encoding/hex" "encoding/json" "errors" "fmt" - "html/template" - "math" - "math/rand" "net/http" - "sort" - "time" "github.com/gobitfly/eth2-beaconchain-explorer/db" - "github.com/gobitfly/eth2-beaconchain-explorer/price" "github.com/gobitfly/eth2-beaconchain-explorer/services" - "github.com/gobitfly/eth2-beaconchain-explorer/templates" "github.com/gobitfly/eth2-beaconchain-explorer/types" "github.com/gobitfly/eth2-beaconchain-explorer/utils" + "github.com/lib/pq" "strconv" "strings" "github.com/ethereum/go-ethereum/common" - "github.com/lib/pq" "golang.org/x/sync/errgroup" ) @@ -199,251 +191,48 @@ func checkValidatorsQuery(validatorIndices []uint64, validatorPubkeys [][]byte) return nil } -func Heatmap(w http.ResponseWriter, r *http.Request) { - templateFiles := append(layoutTemplateFiles, "heatmap.html") - var heatmapTemplate = templates.GetTemplate(templateFiles...) - - w.Header().Set("Content-Type", "text/html") - validatorLimit := getUserPremium(r).MaxValidators - - heatmapData := types.HeatmapData{} - heatmapData.ValidatorLimit = validatorLimit - - min := 1 - max := 400000 - - validatorCount := 100 - count, err := strconv.Atoi(r.URL.Query().Get("count")) - if err == nil && count > 0 && count <= 1000 { - validatorCount = count - } - - validatorMap := make(map[uint64]bool) - for len(validatorMap) < validatorCount { - validatorMap[uint64(rand.Intn(max-min)+min)] = true - } - validators := make([]uint64, 0, len(validatorMap)) - for key := range validatorMap { - validators = append(validators, key) - } - sort.Slice(validators, func(i, j int) bool { return validators[i] < validators[j] }) +func DashboardDataProposals(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") - validatorsCatagoryMap := make(map[uint64]int) - for index, validator := range validators { - validatorsCatagoryMap[validator] = index + filterArr, _, redirect, err := handleValidatorsQuery(w, r, true) + if err != nil || redirect { + return } - heatmapData.Validators = validators - endEpoch := services.LatestFinalizedEpoch() - epochs := make([]uint64, 0, 100) - epochsCatagoryMap := make(map[uint64]int) - for e := endEpoch - 99; e <= endEpoch; e++ { - epochs = append(epochs, e) - epochsCatagoryMap[e] = len(epochs) - 1 + filter := pq.Array(filterArr) - } - heatmapData.Epochs = epochs + proposals := []struct { + Slot uint64 + Status uint64 + }{} errFieldMap := map[string]interface{}{"route": r.URL.String()} - start := time.Now() - if len(validators) == 0 { - http.Error(w, "Error: No validators provided", http.StatusBadRequest) - return - } - incomeData, err := db.BigtableClient.GetValidatorIncomeDetailsHistory(validators, endEpoch-100, endEpoch) + err = db.ReaderDb.Select(&proposals, ` + SELECT slot, status + FROM blocks + WHERE proposer = ANY($1) + ORDER BY slot`, filter) if err != nil { - utils.LogError(err, "error loading validator income history data", 0, errFieldMap) + utils.LogError(err, "error retrieving block-proposals", 0, errFieldMap) http.Error(w, "Internal server error", http.StatusInternalServerError) return } - heatmapData.IncomeData = make([][3]int64, 0, validatorCount*100) - for validator, epochs := range incomeData { - for epoch, income := range epochs { - income := int64(income.AttestationHeadReward+income.AttestationSourceReward+income.AttestationTargetReward) - int64(income.AttestationSourcePenalty+income.AttestationTargetPenalty) - if income > heatmapData.MaxIncome { - heatmapData.MaxIncome = income - } - if income < heatmapData.MinIncome { - heatmapData.MinIncome = income - } - heatmapData.IncomeData = append(heatmapData.IncomeData, [3]int64{int64(epochsCatagoryMap[epoch]), int64(validatorsCatagoryMap[validator]), income}) - } - } - sort.Slice(heatmapData.IncomeData, func(i, j int) bool { - if heatmapData.IncomeData[i][0] != heatmapData.IncomeData[j][0] { - return heatmapData.IncomeData[i][0] < heatmapData.IncomeData[j][0] - } - return heatmapData.IncomeData[i][1] < heatmapData.IncomeData[j][1] - }) - - logger.Infof("retrieved income history of %v validators in %v", len(incomeData), time.Since(start)) - - data := InitPageData(w, r, "dashboard", "/heatmap", "Validator Heatmap", templateFiles) - data.Data = heatmapData - - if handleTemplateError(w, r, "dashboard.go", "Heatmap", "", heatmapTemplate.ExecuteTemplate(w, "layout", data)) != nil { - return // an error has occurred and was processed - } -} - -func Dashboard(w http.ResponseWriter, r *http.Request) { - templateFiles := append(layoutTemplateFiles, "dashboard.html", "dashboard/tables.html") - var dashboardTemplate = templates.GetTemplate(templateFiles...) - - w.Header().Set("Content-Type", "text/html") - - _, _, redirect, err := handleValidatorsQuery(w, r, false) - if err != nil || redirect { - return - } - - dashboardData := types.DashboardData{} - dashboardData.ValidatorLimit = getUserPremium(r).MaxValidators - - epoch := services.LatestEpoch() - dashboardData.CappellaHasHappened = epoch >= (utils.Config.Chain.ClConfig.CappellaForkEpoch) - - data := InitPageData(w, r, "dashboard", "/dashboard", "Dashboard", templateFiles) - data.Data = dashboardData - - if handleTemplateError(w, r, "dashboard.go", "Dashboard", "", dashboardTemplate.ExecuteTemplate(w, "layout", data)) != nil { - return // an error has occurred and was processed - } -} - -func getNextWithdrawalRow(queryValidators []uint64, currency string) ([][]interface{}, error) { - if len(queryValidators) == 0 { - return nil, nil - } - - stats := services.GetLatestStats() - if stats == nil || stats.LatestValidatorWithdrawalIndex == nil || stats.TotalValidatorCount == nil { - return nil, errors.New("stats not available") - } - - epoch := services.LatestEpoch() - - // find subscribed validators that are active and have valid withdrawal credentials (balance will be checked later as it will be queried from bigtable) - // order by validator index to ensure that "last withdrawal" cursor handling works - var validatorsDb []*types.Validator - err := db.ReaderDb.Select(&validatorsDb, ` - SELECT - validatorindex, - withdrawalcredentials, - withdrawableepoch - FROM validators - WHERE - activationepoch <= $1 AND exitepoch > $1 AND - withdrawalcredentials LIKE '\x01' || '%'::bytea AND - validatorindex = ANY($2) - ORDER BY validatorindex ASC`, epoch, pq.Array(queryValidators)) - - if err != nil { - return nil, err - } - - if len(validatorsDb) == 0 { - return nil, nil - } - - // GetValidatorBalanceHistory only takes uint64 slice - var validatorIds = make([]uint64, 0, len(validatorsDb)) - for _, v := range validatorsDb { - validatorIds = append(validatorIds, v.Index) - } - - // retrieve up2date balances for all valid validators from bigtable - balances, err := db.BigtableClient.GetValidatorBalanceHistory(validatorIds, epoch, epoch) - if err != nil { - return nil, err - } - - // find the first withdrawable validator by matching validators and balances - var nextValidator *types.Validator - for _, v := range validatorsDb { - balance, ok := balances[v.Index] - if !ok { - continue - } - if len(balance) == 0 { - continue - } - - if (balance[0].Balance > 0 && v.WithdrawableEpoch <= epoch) || - (balance[0].EffectiveBalance == utils.Config.Chain.ClConfig.MaxEffectiveBalance && balance[0].Balance > utils.Config.Chain.ClConfig.MaxEffectiveBalance) { - // this validator is eligible for withdrawal, check if it is the next one - if nextValidator == nil || v.Index > *stats.LatestValidatorWithdrawalIndex { - nextValidator = v - nextValidator.Balance = balance[0].Balance - if nextValidator.Index > *stats.LatestValidatorWithdrawalIndex { - // the first validator after the cursor has to be the next validator - break - } - } + proposalsResult := make([][]uint64, len(proposals)) + for i, b := range proposals { + proposalsResult[i] = []uint64{ + uint64(utils.SlotToTime(b.Slot).Unix()), + b.Status, } } - if nextValidator == nil { - return nil, nil - } - - lastWithdrawnEpochs, err := db.GetLastWithdrawalEpoch([]uint64{nextValidator.Index}) - if err != nil { - return nil, err - } - lastWithdrawnEpoch := lastWithdrawnEpochs[nextValidator.Index] - - distance, err := GetWithdrawableCountFromCursor(epoch, nextValidator.Index, *stats.LatestValidatorWithdrawalIndex) - if err != nil { - return nil, err - } - - timeToWithdrawal := utils.GetTimeToNextWithdrawal(distance) - - // it normally takes two epochs to finalize - latestFinalized := services.LatestFinalizedEpoch() - if timeToWithdrawal.Before(utils.EpochToTime(epoch + (epoch - latestFinalized))) { - return nil, nil - } - - var withdrawalCredentialsTemplate template.HTML - address, err := utils.WithdrawalCredentialsToAddress(nextValidator.WithdrawalCredentials) + err = json.NewEncoder(w).Encode(proposalsResult) if err != nil { - // warning only as "N/A" will be displayed - logger.Warn("invalid withdrawal credentials") - } - if address != nil { - withdrawalCredentialsTemplate = template.HTML(fmt.Sprintf(`%s`, address, utils.FormatAddress(address, nil, "", false, false, true))) - } else { - withdrawalCredentialsTemplate = `N/A` - } - - var withdrawalAmount uint64 - if nextValidator.WithdrawableEpoch <= epoch { - // full withdrawal - withdrawalAmount = nextValidator.Balance - } else { - // partial withdrawal - withdrawalAmount = nextValidator.Balance - utils.Config.Chain.ClConfig.MaxEffectiveBalance - } - - if lastWithdrawnEpoch == epoch || nextValidator.Balance < utils.Config.Chain.ClConfig.MaxEffectiveBalance { - withdrawalAmount = 0 + utils.LogError(err, "error enconding json response", 0, errFieldMap) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return } - - nextData := make([][]interface{}, 0, 1) - nextData = append(nextData, []interface{}{ - utils.FormatValidator(nextValidator.Index), - template.HTML(fmt.Sprintf(`~ %s`, utils.FormatEpoch(uint64(utils.TimeToEpoch(timeToWithdrawal))))), - template.HTML(fmt.Sprintf(`~ %s`, utils.FormatBlockSlot(utils.TimeToSlot(uint64(timeToWithdrawal.Unix()))))), - template.HTML(fmt.Sprintf(`~ %s`, utils.FormatTimestamp(timeToWithdrawal.Unix()))), - withdrawalCredentialsTemplate, - template.HTML(fmt.Sprintf(` %s`, utils.FormatClCurrency(withdrawalAmount, currency, 6, true, false, false, true))), - }) - - return nextData, nil } // Dashboard Chart that combines balance data and @@ -518,636 +307,3 @@ func DashboardDataBalanceCombined(w http.ResponseWriter, r *http.Request) { return } } - -// DashboardDataBalance retrieves the income history of a set of validators -func DashboardDataBalance(w http.ResponseWriter, r *http.Request) { - currency := GetCurrency(r) - errFieldMap := map[string]interface{}{"route": r.URL.String()} - - w.Header().Set("Content-Type", "application/json") - - q := r.URL.Query() - validatorLimit := getUserPremium(r).MaxValidators - queryValidatorIndices, queryValidatorPubkeys, err := parseValidatorsFromQueryString(q.Get("validators"), validatorLimit) - if err != nil || len(queryValidatorPubkeys) > 0 { - utils.LogError(err, "error parsing validators from query string", 0, errFieldMap) - http.Error(w, "Invalid query", http.StatusBadRequest) - return - } - if len(queryValidatorIndices) < 1 { - http.Error(w, "Invalid query", http.StatusBadRequest) - return - } - - incomeHistoryChartData, err := db.GetValidatorIncomeHistoryChart(queryValidatorIndices, currency, services.LatestFinalizedEpoch(), 0) - if err != nil { - utils.LogError(err, "failed to genereate income history chart data for dashboard view", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - err = json.NewEncoder(w).Encode(incomeHistoryChartData) - if err != nil { - utils.LogError(err, "error enconding json response", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } -} - -func DashboardDataProposals(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - filterArr, _, redirect, err := handleValidatorsQuery(w, r, true) - if err != nil || redirect { - return - } - - filter := pq.Array(filterArr) - - proposals := []struct { - Slot uint64 - Status uint64 - }{} - - errFieldMap := map[string]interface{}{"route": r.URL.String()} - - err = db.ReaderDb.Select(&proposals, ` - SELECT slot, status - FROM blocks - WHERE proposer = ANY($1) - ORDER BY slot`, filter) - if err != nil { - utils.LogError(err, "error retrieving block-proposals", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - proposalsResult := make([][]uint64, len(proposals)) - for i, b := range proposals { - proposalsResult[i] = []uint64{ - uint64(utils.SlotToTime(b.Slot).Unix()), - b.Status, - } - } - - err = json.NewEncoder(w).Encode(proposalsResult) - if err != nil { - utils.LogError(err, "error enconding json response", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } -} - -func DashboardDataWithdrawals(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - reqCurrency := GetCurrency(r) - q := r.URL.Query() - - validatorIndices, _, redirect, err := handleValidatorsQuery(w, r, true) - if err != nil || redirect { - return - } - - draw, err := strconv.ParseUint(q.Get("draw"), 10, 64) - if err != nil { - logger.Warnf("error converting datatables draw parameter from string to int: %v", err) - http.Error(w, "Error: Missing or invalid parameter draw", http.StatusBadRequest) - return - } - start, err := strconv.ParseUint(q.Get("start"), 10, 64) - if err != nil { - logger.Warnf("error converting datatables start parameter from string to int: %v", err) - http.Error(w, "Error: Missing or invalid parameter start", http.StatusBadRequest) - return - } - - orderColumn := q.Get("order[0][column]") - orderByMap := map[string]string{ - "0": "validatorindex", - "1": "block_slot", - "2": "block_slot", - "3": "withdrawalindex", - "4": "address", - "5": "amount", - } - orderBy, exists := orderByMap[orderColumn] - if !exists { - orderBy = "validatorindex" - } - orderDir := q.Get("order[0][dir]") - if orderDir != "asc" { - orderDir = "desc" - } - - length := uint64(10) - - errFieldMap := map[string]interface{}{"route": r.URL.String()} - - withdrawalCount, err := db.GetTotalWithdrawalsCount(validatorIndices) - if err != nil { - utils.LogError(err, "error retrieving dashboard validator withdrawals count", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - withdrawals, err := db.GetDashboardWithdrawals(validatorIndices, length, start, orderBy, orderDir) - if err != nil { - utils.LogError(err, "error retrieving validator withdrawals", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - var tableData [][]interface{} - - // check if there is a NextWithdrawal and append - NextWithdrawalRow, err := getNextWithdrawalRow(validatorIndices, reqCurrency) - if err != nil { - utils.LogError(err, "error calculating next withdrawal row", 0, errFieldMap) - tableData = make([][]interface{}, 0, len(withdrawals)) - } else { - if NextWithdrawalRow == nil { - tableData = make([][]interface{}, 0, len(withdrawals)) - } else { - // make the array +1 larger to append the NextWithdrawal row - tableData = make([][]interface{}, 0, len(withdrawals)+1) - tableData = append(NextWithdrawalRow, tableData...) - } - } - - for _, w := range withdrawals { - tableData = append(tableData, []interface{}{ - utils.FormatValidator(w.ValidatorIndex), - utils.FormatEpoch(utils.EpochOfSlot(w.Slot)), - utils.FormatBlockSlot(w.Slot), - utils.FormatTimestamp(utils.SlotToTime(w.Slot).Unix()), - utils.FormatAddress(w.Address, nil, "", false, false, true), - utils.FormatClCurrency(w.Amount, reqCurrency, 6, true, false, false, true), - }) - } - - data := &types.DataTableResponse{ - Draw: draw, - RecordsTotal: withdrawalCount, - RecordsFiltered: withdrawalCount, - Data: tableData, - } - - err = json.NewEncoder(w).Encode(data) - if err != nil { - utils.LogError(err, "error enconding json response", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } -} - -func DashboardDataValidators(w http.ResponseWriter, r *http.Request) { - currency := GetCurrency(r) - - w.Header().Set("Content-Type", "application/json") - - validatorIndexArr, validatorPubkeyArr, redirect, err := handleValidatorsQuery(w, r, true) - if err != nil || redirect { - return - } - - errFieldMap := map[string]interface{}{"route": r.URL.String()} - - filter := pq.Array(validatorIndexArr) - validatorLimit := getUserPremium(r).MaxValidators - - var validatorsByIndex []*types.ValidatorsData - err = db.ReaderDb.Select(&validatorsByIndex, ` - SELECT - validators.validatorindex, - validators.pubkey, - validators.withdrawableepoch, - validators.slashed, - validators.activationeligibilityepoch, - validators.activationepoch, - validators.exitepoch, - (SELECT COUNT(*) FROM blocks WHERE proposer = validators.validatorindex AND status = '1') as executedproposals, - (SELECT COUNT(*) FROM blocks WHERE proposer = validators.validatorindex AND status = '2') as missedproposals, - COALESCE(validator_performance.cl_performance_7d, 0) as performance7d, - COALESCE(validator_names.name, '') AS name, - validators.status AS state - FROM validators - LEFT JOIN validator_names ON validators.pubkey = validator_names.publickey - LEFT JOIN validator_performance ON validators.validatorindex = validator_performance.validatorindex - WHERE validators.validatorindex = ANY($1) - LIMIT $2`, filter, validatorLimit) - - if err != nil { - utils.LogError(err, "error retrieving validator data", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - validatorsByIndexPubKeys := make([][]byte, len(validatorsByIndex)) - for idx := range validatorsByIndex { - validatorsByIndexPubKeys[idx] = validatorsByIndex[idx].PublicKey - } - pubkeyFilter := pq.ByteaArray(validatorsByIndexPubKeys) - - validatorsDeposits := []struct { - Pubkey []byte `db:"publickey"` - Address []byte `db:"from_address"` - }{} - err = db.ReaderDb.Select(&validatorsDeposits, ` - SELECT - publickey, - from_address - FROM eth1_deposits - WHERE publickey = ANY($1)`, pubkeyFilter) - if err != nil { - utils.LogError(err, "error retrieving validator deposists", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - validatorsDepositsMap := make(map[string][]string) - for _, deposit := range validatorsDeposits { - key := hex.EncodeToString(deposit.Pubkey) - if _, ok := validatorsDepositsMap[key]; !ok { - validatorsDepositsMap[key] = make([]string, 0) - } - validatorsDepositsMap[key] = append(validatorsDepositsMap[key], fmt.Sprintf("%#x", deposit.Address)) - } - - latestEpoch := services.LatestEpoch() - - stats := services.GetLatestStats() - activationChurnRate := stats.ValidatorActivationChurnLimit - if activationChurnRate == nil { - utils.LogError(fmt.Errorf("activation churn rate not available"), "error retrieving validator activation churn rate", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - if len(validatorIndexArr) > 0 { - balances, err := db.BigtableClient.GetValidatorBalanceHistory(validatorIndexArr, latestEpoch, latestEpoch) - if err != nil { - utils.LogError(err, "error retrieving validator balance data", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - for _, validator := range validatorsByIndex { - for balanceIndex, balance := range balances { - if len(balance) == 0 { - continue - } - if validator.ValidatorIndex == balanceIndex { - validator.CurrentBalance = balance[0].Balance - validator.EffectiveBalance = balance[0].EffectiveBalance - } - } - } - - lastAttestationSlots, err := db.BigtableClient.GetLastAttestationSlots(validatorIndexArr) - if err != nil { - utils.LogError(err, "error retrieving validator last attestation slot data", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - for _, validator := range validatorsByIndex { - validator.LastAttestationSlot = int64(lastAttestationSlots[validator.ValidatorIndex]) - } - } - - validatorsByPubkey := make([]*types.ValidatorsData, len(validatorPubkeyArr)) - for i := range validatorsByPubkey { - // Validators without an index don't have activation, exit and withdrawable epochs yet. - // Show them as pending even if they are still in the state "Deposited". - validatorsByPubkey[i] = &types.ValidatorsData{ - PublicKey: validatorPubkeyArr[i], - ActivationEpoch: math.MaxInt64, - ExitEpoch: math.MaxInt64, - WithdrawableEpoch: math.MaxInt64, - State: "pending_deposited", - } - } - - validators := append(validatorsByIndex, validatorsByPubkey...) - - tableData := make([][]interface{}, len(validators)) - for i, v := range validators { - indexInfo := fmt.Sprintf("%v", v.ValidatorIndex) - if i >= len(validatorsByIndex) { - // If the validator does not have an index yet show custom text that is like the state - indexInfo = "Pending" - } - var queueAhead uint64 - var estimatedActivationTs time.Time - if v.State == "pending" { - if v.ActivationEpoch > 100_000_000 { - queueAhead, err = db.GetQueueAheadOfValidator(v.ValidatorIndex) - if err != nil { - utils.LogError(err, fmt.Sprintf("failed to retrieve queue ahead of validator %v for dashboard", v.ValidatorIndex), 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - epochsToWait := queueAhead / *activationChurnRate - // calculate dequeue epoch - estimatedActivationEpoch := latestEpoch + epochsToWait + 1 - // add activation offset - estimatedActivationEpoch += utils.Config.Chain.ClConfig.MaxSeedLookahead + 1 - estimatedActivationTs = utils.EpochToTime(estimatedActivationEpoch) - } else { - queueAhead = 0 - estimatedActivationTs = utils.EpochToTime(v.ActivationEpoch) - } - } - - tableData[i] = []interface{}{ - fmt.Sprintf("%x", v.PublicKey), - indexInfo, - []interface{}{ - fmt.Sprintf("%.4f %v", float64(v.CurrentBalance)/float64(1e9)*price.GetPrice(utils.Config.Frontend.ClCurrency, currency), currency), - fmt.Sprintf("%.1f %v", float64(v.EffectiveBalance)/float64(1e9)*price.GetPrice(utils.Config.Frontend.ClCurrency, currency), currency), - }, - []interface{}{ - v.ValidatorIndex, - v.State, - queueAhead + 1, - estimatedActivationTs.Unix()}, - } - - if v.ActivationEpoch != math.MaxInt64 { - tableData[i] = append(tableData[i], []interface{}{ - v.ActivationEpoch, - utils.EpochToTime(v.ActivationEpoch).Unix(), - }) - } else { - tableData[i] = append(tableData[i], nil) - } - - if v.ExitEpoch != math.MaxInt64 { - tableData[i] = append(tableData[i], []interface{}{ - v.ExitEpoch, - utils.EpochToTime(v.ExitEpoch).Unix(), - }) - } else { - tableData[i] = append(tableData[i], nil) - } - - if v.WithdrawableEpoch != math.MaxInt64 { - tableData[i] = append(tableData[i], []interface{}{ - v.WithdrawableEpoch, - utils.EpochToTime(v.WithdrawableEpoch).Unix(), - }) - } else { - tableData[i] = append(tableData[i], nil) - } - - if v.LastAttestationSlot != 0 { - tableData[i] = append(tableData[i], []interface{}{ - v.LastAttestationSlot, - utils.FormatTimestamp(utils.SlotToTime(uint64(v.LastAttestationSlot)).Unix()), - //utils.SlotToTime(uint64(*v.LastAttestationSlot)).Unix(), - }) - } else { - tableData[i] = append(tableData[i], nil) - } - - tableData[i] = append(tableData[i], []interface{}{ - v.ExecutedProposals, - v.MissedProposals, - }) - - tableData[i] = append(tableData[i], utils.FormatIncome(v.Performance7d, currency, true)) - - validatorDeposits := validatorsDepositsMap[hex.EncodeToString(v.PublicKey)] - if validatorDeposits != nil { - tableData[i] = append(tableData[i], validatorDeposits) - } else { - tableData[i] = append(tableData[i], nil) - } - - } - - type dataType struct { - LatestEpoch uint64 `json:"latestEpoch"` - Data [][]interface{} `json:"data"` - } - data := &dataType{ - LatestEpoch: latestEpoch, - Data: tableData, - } - - err = json.NewEncoder(w).Encode(data) - if err != nil { - utils.LogError(err, "error enconding json response", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } -} - -func DashboardDataEarnings(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - errFieldMap := map[string]interface{}{"route": r.URL.String()} - - queryValidatorIndices, _, redirect, err := handleValidatorsQuery(w, r, true) - if err != nil || redirect { - return - } - - earnings, _, err := GetValidatorEarnings(queryValidatorIndices, GetCurrency(r)) - if err != nil { - utils.LogError(err, "error retrieving validator earnings", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - } - - if earnings == nil { - earnings = &types.ValidatorEarnings{} - } - - err = json.NewEncoder(w).Encode(earnings) - if err != nil { - utils.LogError(err, "error enconding json response", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } -} - -func DashboardDataEffectiveness(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - filterArr, _, redirect, err := handleValidatorsQuery(w, r, true) - if err != nil || redirect { - return - } - - errFieldMap := map[string]interface{}{"route": r.URL.String()} - - filter := pq.Array(filterArr) - - var activeValidators []uint64 - err = db.ReaderDb.Select(&activeValidators, ` - SELECT validatorindex FROM validators where validatorindex = ANY($1) and activationepoch < $2 AND exitepoch > $2 - `, filter, services.LatestEpoch()) - if err != nil { - utils.LogError(err, "error retrieving active validators", 0, errFieldMap) - } - - if len(activeValidators) == 0 { - // valid 200 response with empty data - w.Write([]byte(`{}`)) - return - } - - var avgIncDistance []float64 - - epoch := services.LatestEpoch() - if epoch > 0 { - epoch = epoch - 1 - } - - effectiveness, err := db.BigtableClient.GetValidatorEffectiveness(activeValidators, epoch) - if err != nil { - utils.LogError(err, "error retrieving validator effectiveness", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - for _, e := range effectiveness { - avgIncDistance = append(avgIncDistance, e.AttestationEfficiency) - } - - err = json.NewEncoder(w).Encode(avgIncDistance) - if err != nil { - utils.LogError(err, "error enconding json response", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } -} - -func DashboardDataProposalsHistory(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - errFieldMap := map[string]interface{}{"route": r.URL.String()} - - lastDay, err := db.GetLastExportedStatisticDay() - if err != nil && err != db.ErrNoStats { - utils.LogError(err, "error retrieving last exported statistic day", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - dayStart, err := strconv.Atoi(r.URL.Query().Get("start_day")) - if err != nil { - dayStart = 0 - } - dayEnd, err := strconv.Atoi(r.URL.Query().Get("end_day")) - if err != nil { - dayEnd = int(lastDay) - } - - filterArr, _, redirect, err := handleValidatorsQuery(w, r, true) - if err != nil || redirect { - return - } - - allowedDayRange := utils.GetMaxAllowedDayRangeValidatorStats(len(filterArr)) - - if dayEnd < dayStart { - http.Error(w, "Error: Invalid day range", http.StatusBadRequest) - return - } - - if dayEnd-dayStart > allowedDayRange { - dayStart = dayEnd - allowedDayRange - } - - filter := pq.Array(filterArr) - - proposals := []struct { - ValidatorIndex uint64 `db:"validatorindex"` - Day int64 `db:"day"` - Proposed *uint64 `db:"proposed_blocks"` - Missed *uint64 `db:"missed_blocks"` - Orphaned *uint64 `db:"orphaned_blocks"` - }{} - todaysProposals := proposals - - dayFilter := "day >= $2 AND day <= $3" - args := []interface{}{filter, dayStart, dayEnd} - if allowedDayRange == 0 { - dayFilter = "day = $2" - args = []interface{}{filter, dayStart} - } - - err = db.ReaderDb.Select(&proposals, fmt.Sprintf(` - SELECT validatorindex, day, proposed_blocks, missed_blocks, orphaned_blocks - FROM validator_stats - WHERE validatorindex = ANY($1) - AND (proposed_blocks > 0 OR missed_blocks > 0 OR orphaned_blocks > 0) - AND %v - ORDER BY day DESC`, dayFilter), args...) - if err != nil { - utils.LogError(err, "error retrieving validator_stats", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - if uint64(dayEnd) > lastDay { - _, lastExportedEpoch := utils.GetFirstAndLastEpochForDay(lastDay) - - err = db.ReaderDb.Select(&todaysProposals, ` - SELECT - proposer as validatorindex, - SUM(CASE WHEN status = '1' THEN 1 ELSE 0 END) as proposed_blocks, - SUM(CASE WHEN status = '2' THEN 1 ELSE 0 END) as missed_blocks, - SUM(CASE WHEN status = '3' THEN 1 ELSE 0 END) as orphaned_blocks - FROM blocks - WHERE proposer = ANY($1) AND epoch > $2 - group by proposer`, filter, lastExportedEpoch) - if err != nil { - utils.LogError(err, "error retrieving validator_stats", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - for i := range todaysProposals { - todaysProposals[i].Day = int64(lastDay + 1) - } - - proposals = append(todaysProposals, proposals...) - } - - proposalsHistResult := make([][]uint64, len(proposals)) - for i, proposal := range proposals { - var proposed, missed, orphaned uint64 = 0, 0, 0 - if proposal.Proposed != nil { - proposed = *proposal.Proposed - } - if proposal.Missed != nil { - missed = *proposal.Missed - } - if proposal.Orphaned != nil { - orphaned = *proposal.Orphaned - } - proposalsHistResult[i] = []uint64{ - proposal.ValidatorIndex, - uint64(utils.DayToTime(proposal.Day).Unix()), - proposed, - missed, - orphaned, - } - } - - responseStruct := struct { - StartDay int64 `json:"start_day"` - EndDay int64 `json:"end_day"` - Data [][]uint64 `json:"data"` - }{int64(dayStart), int64(dayEnd), proposalsHistResult} - - err = json.NewEncoder(w).Encode(responseStruct) - if err != nil { - utils.LogError(err, "error enconding json response", 0, errFieldMap) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } -} diff --git a/handlers/ens.go b/handlers/ens.go index eb8f046069..a8abc93139 100644 --- a/handlers/ens.go +++ b/handlers/ens.go @@ -16,12 +16,13 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/gorilla/mux" + go_ens "github.com/wealdtech/go-ens/v3" ) // ApiEnsLookup godoc -// @Summary Get the address for an ens name and vice versa -// @Tags Ens -// @Description Returns and object with the ens name and address - if found. +// @Tags ENS +// @Summary Resolve an ens name or address +// @Description Get the address for an ens name and vice versa. Returns and object with the ens name and address - if found. // @Produce json // @Param domain path string true "domain can either be an ens name or an etherum address" // @Success 200 {object} types.ApiResponse @@ -48,11 +49,18 @@ func GetEnsDomain(search string) (*types.EnsDomainResponse, error) { data := &types.EnsDomainResponse{} var returnError error + var err error + search, err = go_ens.Normalize(search) + if err != nil { + return nil, fmt.Errorf("error normalizing ENS name: %w", err) + } + if utils.IsValidEnsDomain(search) { cacheKey := fmt.Sprintf("%d:ens:address:%v", utils.Config.Chain.ClConfig.DepositChainID, search) if address, err := cache.TieredCache.GetStringWithLocalTimeout(cacheKey, time.Minute); err == nil && len(address) > 0 { data.Address = address + data.Domain = search return data, nil } diff --git a/handlers/eth1Block.go b/handlers/eth1Block.go index 8485173833..b6d6bb7b40 100644 --- a/handlers/eth1Block.go +++ b/handlers/eth1Block.go @@ -29,6 +29,10 @@ func Eth1Block(w http.ResponseWriter, r *http.Request) { "slot/attesterSlashing.html", "slot/proposerSlashing.html", "slot/exits.html", + "slot/consolidationRequests.html", + "slot/compoundingRequests.html", + "slot/withdrawalRequests.html", + "slot/depositRequests.html", "components/timestamp.html", "slot/overview.html", "slot/execTransactions.html", @@ -230,12 +234,8 @@ func GetExecutionBlockPageData(number uint64, limit int) (*types.Eth1BlockPageDa }) } - if limit > 0 { - if len(txs) > limit { - txs = txs[:limit] - } else { - txs = txs[:0] - } + if limit > 0 && len(txs) > limit { + txs = txs[:limit] } blobGasPrice := eip4844.CalcBlobFee(block.ExcessBlobGas) diff --git a/handlers/eth1Deposits.go b/handlers/eth1Deposits.go index 096da0a763..8bcc441aeb 100644 --- a/handlers/eth1Deposits.go +++ b/handlers/eth1Deposits.go @@ -93,10 +93,6 @@ func Eth1DepositsData(w http.ResponseWriter, r *http.Request) { tableData := make([][]interface{}, len(deposits)) for i, d := range deposits { - valid := "❌" - if d.ValidSignature { - valid = "✅" - } tableData[i] = []interface{}{ utils.FormatEth1Address(d.FromAddress), utils.FormatPublicKey(d.PublicKey), @@ -106,7 +102,6 @@ func Eth1DepositsData(w http.ResponseWriter, r *http.Request) { utils.FormatTimestamp(d.BlockTs.Unix()), utils.FormatEth1Block(d.BlockNumber), utils.FormatValidatorStatus(d.State), - valid, } } diff --git a/handlers/index.go b/handlers/index.go index 5db2c7b3a8..133c002334 100644 --- a/handlers/index.go +++ b/handlers/index.go @@ -5,6 +5,7 @@ import ( "fmt" "math" "net/http" + "time" "github.com/gobitfly/eth2-beaconchain-explorer/services" "github.com/gobitfly/eth2-beaconchain-explorer/templates" @@ -97,19 +98,49 @@ func getSlotVizData(currentEpoch uint64) *types.SlotVizPageData { } func calculateChurn(page *types.IndexPageData) { - limit := services.GetLatestStats().ValidatorActivationChurnLimit - pending_validators := services.GetLatestStats().PendingValidatorCount - // calculate daily new validators - limit_per_day := *limit * uint64(225) - // calculate how long it will take for a new deposit to be processed - time := float64(*pending_validators) / float64((limit_per_day)) - const hoursPerDay = 24 - wholeDays, fractionalDays := math.Modf(time) - - hours := int(fractionalDays * hoursPerDay) - - time_as_days := fmt.Sprintf("%d days and %d hours", int(wholeDays), hours) - page.NewDepositProcessAfter = time_as_days - page.ValidatorsPerEpoch = *limit - page.ValidatorsPerDay = limit_per_day + if utils.ElectraHasHappened(page.CurrentEpoch) { + data := services.LatestQueueData() + if data == nil { + logger.Warn("error getting queue data") + return + } + duration := data.EnteringQueueTime + + if duration < 0 { + page.NewDepositProcessAfter = "a couple minutes" + } else { + days := duration / (24 * time.Hour) + hours := (duration % (24 * time.Hour)) / time.Hour + page.NewDepositProcessAfter = fmt.Sprintf("%d days and %d hours", days, hours) + } + page.ValidatorsPerEpoch = data.EnteringBalancePerEpoch + page.ValidatorsPerDay = data.EnteringBalancePerDay + } else { + stats := services.GetLatestStats() + if stats == nil || + stats.ValidatorActivationChurnLimit == nil || + stats.PendingValidatorCount == nil { + logger.Warn("calculateChurn: missing or invalid cached stats; skipping churn calculation") + return + } + limit := *stats.ValidatorActivationChurnLimit + if limit == 0 { + logger.Warn("calculateChurn: churn limit is zero; skipping churn calculation") + return + } + pending_validators := *stats.PendingValidatorCount + // calculate daily new validators + limit_per_day := limit * uint64(225) + // calculate how long it will take for a new deposit to be processed + daysFloat := float64(pending_validators) / float64((limit_per_day)) + const hoursPerDay = 24 + wholeDays, fractionalDays := math.Modf(daysFloat) + + hours := int(fractionalDays * hoursPerDay) + + time_as_days := fmt.Sprintf("%d days and %d hours", int(wholeDays), hours) + page.NewDepositProcessAfter = time_as_days + page.ValidatorsPerEpoch = limit + page.ValidatorsPerDay = limit_per_day + } } diff --git a/handlers/pageData.go b/handlers/pageData.go index e5bdd355ac..42251a7460 100644 --- a/handlers/pageData.go +++ b/handlers/pageData.go @@ -30,7 +30,10 @@ func InitPageData(w http.ResponseWriter, r *http.Request, active, path, title st } isMainnet := utils.Config.Chain.ClConfig.ConfigName == "mainnet" - user := getUser(r) + + user, session, _ := getUserSession(r) + user = checkForV1Notifications(r.Context(), user, session) + data := &types.PageData{ Meta: &types.Meta{ Title: fullTitle, @@ -64,7 +67,7 @@ func InitPageData(w http.ResponseWriter, r *http.Request, active, path, title st ShowSyncingMessage: services.IsSyncing(), GlobalNotification: services.GlobalNotificationMessage(), AvailableCurrencies: price.GetAvailableCurrencies(), - MainMenuItems: createMenuItems(active, isMainnet), + MainMenuItems: createMenuItems(active, isMainnet, user.HasV1Notifications), TermsOfServiceUrl: utils.Config.Frontend.Legal.TermsOfServiceUrl, PrivacyPolicyUrl: utils.Config.Frontend.Legal.PrivacyPolicyUrl, } @@ -108,6 +111,27 @@ func InitPageData(w http.ResponseWriter, r *http.Request, active, path, title st return data } +func checkForV1Notifications(ctx context.Context, user *types.User, session *utils.CustomSession) *types.User { + if !user.Authenticated || user.UserID <= 0 || user.HasV1Notifications != types.UserV1Notification_Unknown { + return user + } + + hasV1Notifications, err := hasUserV1NotificationSubscriptions(ctx, user.UserID) + if err != nil { + logger.Warnf("error checking v1 notifications for user %v: %v", user.UserID, err) + return user + } + + if hasV1Notifications { + user.HasV1Notifications = types.UserV1Notification_True + } else { + user.HasV1Notifications = types.UserV1Notification_False + } + session.SetValue("has_v1_notifications", user.HasV1Notifications) + + return user +} + func SetPageDataTitle(pageData *types.PageData, title string) { if title == "" { pageData.Meta.Title = fmt.Sprintf("%v - beaconcha.in - %v", utils.Config.Frontend.SiteName, time.Now().Year()) @@ -164,6 +188,11 @@ func getUserSession(r *http.Request) (*types.User, *utils.CustomSession, error) u.UserGroup = "" return u, session, nil } + u.HasV1Notifications, ok = session.GetValue("has_v1_notifications").(types.UserV1Notification) + if !ok { + u.HasV1Notifications = types.UserV1Notification_Unknown + } + return u, session, nil } @@ -186,9 +215,26 @@ func purgeAllSessionsForUser(ctx context.Context, userId uint64) error { } -func createMenuItems(active string, isMain bool) []types.MainMenuItem { +func createMenuItems(active string, isMain bool, hasV1Notifications types.UserV1Notification) []types.MainMenuItem { + notificationItems := []types.MainMenuItem{} + + v2NotificationText := "Notifications" + if hasV1Notifications == types.UserV1Notification_True { + notificationItems = append(notificationItems, types.MainMenuItem{ + Label: "v1 Notifications", + IsActive: false, + Path: "/user/notifications", + }) + v2NotificationText = "v2 Notifications" + } + notificationItems = append(notificationItems, types.MainMenuItem{ + Label: v2NotificationText, + IsActive: false, + Path: utils.Config.V2NotificationURL, + }) + if utils.Config.Chain.Name == "gnosis" { - return createMenuItemsGnosis(active, isMain) + return createMenuItemsGnosis(active, isMain, notificationItems) } hiddenFor := []string{"confirmation", "login", "register"} @@ -196,7 +242,8 @@ func createMenuItems(active string, isMain bool) []types.MainMenuItem { if utils.SliceContains(hiddenFor, active) { return []types.MainMenuItem{} } - return []types.MainMenuItem{ + + composed := []types.MainMenuItem{ { Label: "Blockchain", IsActive: active == "blockchain", @@ -286,184 +333,172 @@ func createMenuItems(active string, isMain bool) []types.MainMenuItem { IsActive: active == "dashboard", Path: "/dashboard", }, - { - Label: "Notifications", - IsActive: false, - Path: "/user/notifications", - }, - { - Label: "More", - IsActive: active == "more", - HasBigGroups: true, - Groups: []types.NavigationGroup{ - { - Label: "Staking Pools", - Links: []types.NavigationLink{ - { - Label: "Run a Validator!", - Path: "https://ethpool.org/", - CustomIcon: "ethermine_staking_logo_svg", - IsHighlighted: true, - }, - { - Label: "ETH.STORE®", - Path: "/ethstore", - CustomIcon: "ethermine_stake_logo_svg", - }, - { - Label: "Staking Services", - Path: "/stakingServices", - Icon: "fa-drumstick-bite", - }, - { - Label: "Pool Benchmarks", - Path: "/pools", - Icon: "fa-chart-pie", - }, - { - Label: "Rocket Pool Stats", - Path: "/pools/rocketpool", - Icon: "fa-rocket", - }, + } + + composed = append(composed, notificationItems...) + + composed = append(composed, types.MainMenuItem{ + Label: "More", + IsActive: active == "more", + HasBigGroups: true, + Groups: []types.NavigationGroup{ + { + Label: "Staking Pools", + Links: []types.NavigationLink{ + { + Label: "Run a Validator!", + Path: "https://ethpool.org/", + CustomIcon: "ethermine_staking_logo_svg", + IsHighlighted: true, + }, + { + Label: "ETH.STORE®", + Path: "/ethstore", + CustomIcon: "ethermine_stake_logo_svg", + }, + { + Label: "Staking Services", + Path: "/stakingServices", + Icon: "fa-drumstick-bite", + }, + { + Label: "Pool Benchmarks", + Path: "/pools", + Icon: "fa-chart-pie", + }, + { + Label: "Rocket Pool Stats", + Path: "/pools/rocketpool", + Icon: "fa-rocket", }, }, - { - Label: "Stats", - Links: []types.NavigationLink{ - { - Label: "Charts", - Path: "/charts", - Icon: "fa-chart-bar", - }, - { - Label: "Income History", - Path: "/rewards", - Icon: "fa-money-bill-alt", - }, - { - Label: "Profit Calculator", - Path: "/calculator", - Icon: "fa-calculator", - }, - { - Label: "Block Viz", - Path: "/vis", - Icon: "fa-project-diagram", - }, - { - Label: "Relays", - Path: "/relays", - Icon: "fa-robot", - }, - { - Label: "EIP-1559 Burn", - Path: "/burn", - Icon: "fa-burn", - }, - { - Label: "Correlations", - Path: "/correlations", - Icon: "fa-chart-line", - IsHidden: !isMain, - }, + }, + { + Label: "Stats", + Links: []types.NavigationLink{ + { + Label: "Charts", + Path: "/charts", + Icon: "fa-chart-bar", }, - }, { - Label: "Tools", - Links: []types.NavigationLink{ - { - Label: "beaconcha.in App", - Path: "/mobile", - Icon: "fa-mobile-alt", - }, - { - Label: "beaconcha.in Premium", - Path: "/premium", - Icon: "fa-gem", - }, - { - Label: "Webhooks", - Path: "/user/webhooks", - CustomIcon: "webhook_logo_svg", - }, - { - Label: "API Docs", - Path: "/api/v1/docs/index.html", - Icon: "fa-book-reader", - }, - { - Label: "API Pricing", - Path: "/pricing", - Icon: "fa-laptop-code", - }, - { - Label: "Unit Converter", - Path: "/tools/unitConverter", - Icon: "fa-sync", - }, - { - Label: "GasNow", - Path: "/gasnow", - Icon: "fa-gas-pump", - }, - { - Label: "Broadcast Signed Messages", - Path: "/tools/broadcast", - Icon: "fa-bullhorn", - }, + { + Label: "Profit Calculator", + Path: "/calculator", + Icon: "fa-calculator", }, - }, { - Label: "Services", - Links: []types.NavigationLink{ - { - Label: "Eversteel", - Path: "https://eversteel.io/", - CustomIcon: "eversteel_logo_svg", - IsHighlighted: true, - }, - { - Label: "Knowledge Base", - Path: "https://kb.beaconcha.in", - Icon: "fa-external-link-alt", - }, - { - Label: "Notifications", - Path: "/user/notifications", - Icon: "fa-bell", - }, - { - Label: "Graffiti Wall", - Path: "/graffitiwall", - Icon: "fa-paint-brush", - }, - { - Label: "Ethereum Clients", - Path: "/ethClients", - Icon: "fa-desktop", - }, - { - Label: "Slot Finder", - Path: "/slots/finder", - Icon: "fa-cube", - }, - { - Label: "Report a scam", - Path: "https://www.chainabuse.com/report?source=bitfly", - Icon: "fa-flag", - }, + { + Label: "Block Viz", + Path: "/vis", + Icon: "fa-project-diagram", + }, + { + Label: "Relays", + Path: "/relays", + Icon: "fa-robot", + }, + { + Label: "EIP-1559 Burn", + Path: "/burn", + Icon: "fa-burn", + }, + { + Label: "Correlations", + Path: "/correlations", + Icon: "fa-chart-line", + IsHidden: !isMain, + }, + }, + }, { + Label: "Tools", + Links: []types.NavigationLink{ + { + Label: "beaconcha.in App", + Path: "/mobile", + Icon: "fa-mobile-alt", + }, + { + Label: "beaconcha.in Premium", + Path: "/premium", + Icon: "fa-gem", + }, + { + Label: "Webhooks", + Path: "/user/webhooks", + CustomIcon: "webhook_logo_svg", + }, + { + Label: "API Docs", + Path: "/api/v1/docs", + Icon: "fa-book-reader", + }, + { + Label: "API Pricing", + Path: "/pricing", + Icon: "fa-laptop-code", + }, + { + Label: "Unit Converter", + Path: "/tools/unitConverter", + Icon: "fa-sync", + }, + { + Label: "GasNow", + Path: "/gasnow", + Icon: "fa-gas-pump", + }, + { + Label: "Broadcast Signed Messages", + Path: "/tools/broadcast", + Icon: "fa-bullhorn", + }, + }, + }, { + Label: "Services", + Links: []types.NavigationLink{ + { + Label: "Knowledge Base", + Path: "https://kb.beaconcha.in", + Icon: "fa-external-link-alt", + }, + { + Label: "Notifications", + Path: "/user/notifications", + Icon: "fa-bell", + }, + { + Label: "Graffiti Wall", + Path: "/graffitiwall", + Icon: "fa-paint-brush", + }, + { + Label: "Ethereum Clients", + Path: "/ethClients", + Icon: "fa-desktop", + }, + { + Label: "Slot Finder", + Path: "/slots/finder", + Icon: "fa-cube", + }, + { + Label: "Report a scam", + Path: "https://www.chainabuse.com/report?source=bitfly", + Icon: "fa-flag", }, }, }, }, - } + }) + return composed } -func createMenuItemsGnosis(active string, isMain bool) []types.MainMenuItem { +func createMenuItemsGnosis(active string, isMain bool, notificationItems []types.MainMenuItem) []types.MainMenuItem { hiddenFor := []string{"confirmation", "login", "register"} if utils.SliceContains(hiddenFor, active) { return []types.MainMenuItem{} } - return []types.MainMenuItem{ + composed := []types.MainMenuItem{ { Label: "Blockchain", IsActive: active == "blockchain", @@ -553,104 +588,90 @@ func createMenuItemsGnosis(active string, isMain bool) []types.MainMenuItem { IsActive: active == "dashboard", Path: "/dashboard", }, - { - Label: "Notifications", - IsActive: false, - Path: "/user/notifications", - }, - { - Label: "More", - IsActive: active == "more", - HasBigGroups: true, - Groups: []types.NavigationGroup{ - { - Label: "Stats", - Links: []types.NavigationLink{ - { - Label: "Charts", - Path: "/charts", - Icon: "fa-chart-bar", - }, - { - Label: "Income History", - Path: "/rewards", - Icon: "fa-money-bill-alt", - }, - { - Label: "Block Viz", - Path: "/vis", - Icon: "fa-project-diagram", - }, - { - Label: "Correlations", - Path: "/correlations", - Icon: "fa-chart-line", - IsHidden: !isMain, - }, + } + composed = append(composed, notificationItems...) + composed = append(composed, types.MainMenuItem{ + Label: "More", + IsActive: active == "more", + HasBigGroups: true, + Groups: []types.NavigationGroup{ + { + Label: "Stats", + Links: []types.NavigationLink{ + { + Label: "Charts", + Path: "/charts", + Icon: "fa-chart-bar", + }, + { + Label: "Block Viz", + Path: "/vis", + Icon: "fa-project-diagram", + }, + { + Label: "Correlations", + Path: "/correlations", + Icon: "fa-chart-line", + IsHidden: !isMain, }, }, - { - Label: "Tools", - Links: []types.NavigationLink{ - { - Label: "beaconcha.in App", - Path: "/mobile", - Icon: "fa-mobile-alt", - }, - { - Label: "beaconcha.in Premium", - Path: "/premium", - Icon: "fa-gem", - }, - { - Label: "Webhooks", - Path: "/user/webhooks", - CustomIcon: "webhook_logo_svg", - }, - { - Label: "API Docs", - Path: "/api/v1/docs/index.html", - Icon: "fa-book-reader", - }, - { - Label: "API Pricing", - Path: "/pricing", - Icon: "fa-laptop-code", - }, - { - Label: "Broadcast Signed Messages", - Path: "/tools/broadcast", - Icon: "fa-bullhorn", - }, + }, + { + Label: "Tools", + Links: []types.NavigationLink{ + { + Label: "beaconcha.in App", + Path: "/mobile", + Icon: "fa-mobile-alt", + }, + { + Label: "beaconcha.in Premium", + Path: "/premium", + Icon: "fa-gem", + }, + { + Label: "Webhooks", + Path: "/user/webhooks", + CustomIcon: "webhook_logo_svg", + }, + { + Label: "API Docs", + Path: "/api/v1/docs", + Icon: "fa-book-reader", + }, + { + Label: "API Pricing", + Path: "/pricing", + Icon: "fa-laptop-code", + }, + { + Label: "Broadcast Signed Messages", + Path: "/tools/broadcast", + Icon: "fa-bullhorn", }, }, - { - Label: "Services", - Links: []types.NavigationLink{ - { - Label: "Eversteel", - Path: "https://eversteel.io/", - CustomIcon: "eversteel_logo_svg", - IsHighlighted: true, - }, - { - Label: "Knowledge Base", - Path: "https://kb.beaconcha.in", - Icon: "fa-external-link-alt", - }, - { - Label: "Notifications", - Path: "/user/notifications", - Icon: "fa-bell", - }, - { - Label: "Graffiti Wall", - Path: "/graffitiwall", - Icon: "fa-paint-brush", - }, + }, + { + Label: "Services", + Links: []types.NavigationLink{ + { + Label: "Knowledge Base", + Path: "https://kb.beaconcha.in", + Icon: "fa-external-link-alt", + }, + { + Label: "Notifications", + Path: "/user/notifications", + Icon: "fa-bell", + }, + { + Label: "Graffiti Wall", + Path: "/graffitiwall", + Icon: "fa-paint-brush", }, }, }, }, - } + }) + return composed } diff --git a/handlers/search.go b/handlers/search.go index 94847e4809..bd604dc5e5 100644 --- a/handlers/search.go +++ b/handlers/search.go @@ -271,15 +271,37 @@ func SearchAhead(w http.ResponseWriter, r *http.Request) { lowerStrippedSearch = strings.ToLower(strings.Replace(ensData.Address, "0x", "", -1)) } } + var candidateCreds []string if len(lowerStrippedSearch) == 40 { // when the user gives an address (that validators might withdraw to) we transform the address into credentials - lowerStrippedSearch = utils.BeginningOfSetWithdrawalCredentials + lowerStrippedSearch + // We don't know if the address corresponds to a 0x01 or 0x02 withdrawal credentials, so we check both. + cred1 := utils.BeginningOfSetWithdrawalCredentials(1) + lowerStrippedSearch + cred2 := utils.BeginningOfSetWithdrawalCredentials(2) + lowerStrippedSearch + + if utils.IsValidWithdrawalCredentials(cred1) { + candidateCreds = append(candidateCreds, cred1) + } + if utils.IsValidWithdrawalCredentials(cred2) { + candidateCreds = append(candidateCreds, cred2) + } + } else if utils.IsValidWithdrawalCredentials(lowerStrippedSearch) { // Otherwise, assume the user supplied the full withdrawal credentials. + candidateCreds = append(candidateCreds, lowerStrippedSearch) } - if !utils.IsValidWithdrawalCredentials(lowerStrippedSearch) { + + if len(candidateCreds) == 0 { break } - decodedCredential, decodeErr := hex.DecodeString(lowerStrippedSearch) - if decodeErr != nil { + + var decodedCreds [][]byte + for _, cred := range candidateCreds { + decoded, decodeErr := hex.DecodeString(cred) + if decodeErr != nil { + continue + } + decodedCreds = append(decodedCreds, decoded) + } + + if len(decodedCreds) == 0 { break } // find validators per withdrawal credential @@ -288,9 +310,12 @@ func SearchAhead(w http.ResponseWriter, r *http.Request) { Count uint64 `db:"count"` }{} err = db.ReaderDb.Select(&dbFinding, ` - SELECT withdrawalcredentials, COUNT(*) FROM validators - WHERE withdrawalcredentials = $1 - GROUP BY withdrawalcredentials`, decodedCredential) + SELECT withdrawalcredentials, COUNT(*) + FROM validators + WHERE withdrawalcredentials = ANY($1) + GROUP BY withdrawalcredentials`, + pq.ByteaArray(decodedCreds), + ) if err == nil { res := make([]struct { EncodedCredential string `json:"withdrawalcredentials"` diff --git a/handlers/slot.go b/handlers/slot.go index 0e0f1133c6..8807477f8b 100644 --- a/handlers/slot.go +++ b/handlers/slot.go @@ -43,6 +43,10 @@ func Slot(w http.ResponseWriter, r *http.Request) { "slot/proposerSlashing.html", "slot/exits.html", "slot/blobs.html", + "slot/consolidationRequests.html", + "slot/compoundingRequests.html", + "slot/withdrawalRequests.html", + "slot/depositRequests.html", "components/timestamp.html", "slot/overview.html", "slot/execTransactions.html") @@ -157,10 +161,10 @@ func Slot(w http.ResponseWriter, r *http.Request) { } } -func getAttestationsData(slot uint64, onlyFirst bool) ([]*types.BlockPageAttestation, error) { - limit := ";" - if onlyFirst { - limit = " LIMIT 1;" +func getAttestationsData(slot uint64, offset int64) ([]*types.BlockPageAttestation, error) { + limit := " LIMIT 1" + if offset > 0 { + limit += fmt.Sprintf(" OFFSET %d", offset) } var attestations []*types.BlockPageAttestation @@ -173,6 +177,7 @@ func getAttestationsData(slot uint64, onlyFirst bool) ([]*types.BlockPageAttesta signature, slot, committeeindex, + committeebits, beaconblockroot, source_epoch, source_root, @@ -198,6 +203,7 @@ func getAttestationsData(slot uint64, onlyFirst bool) ([]*types.BlockPageAttesta &attestation.Signature, &attestation.Slot, &attestation.CommitteeIndex, + &attestation.CommitteeBits, &attestation.BeaconBlockRoot, &attestation.SourceEpoch, &attestation.SourceRoot, @@ -279,7 +285,7 @@ func GetSlotPageData(blockSlot uint64) (*types.BlockPageData, error) { slotPageData.NextSlot = slotPageData.Slot + 1 slotPageData.PreviousSlot = slotPageData.Slot - 1 - slotPageData.Attestations, err = getAttestationsData(slotPageData.Slot, true) + slotPageData.Attestations, err = getAttestationsData(slotPageData.Slot, 0) if err != nil { return nil, err } @@ -314,8 +320,28 @@ func GetSlotPageData(blockSlot uint64) (*types.BlockPageData, error) { } slotPageData.VotingValidatorsCount = uint64(len(votesPerValidator)) slotPageData.VotesCount = uint64(votesCount) + err = db.ReaderDb.Select(&slotPageData.VoluntaryExits, ` + SELECT + validatorindex, + signature, + 'Consensus Layer' as triggered_via, + 'completed' as status + FROM blocks_voluntaryexits + WHERE block_slot = $1 + + UNION ALL - err = db.ReaderDb.Select(&slotPageData.VoluntaryExits, "SELECT validatorindex, signature FROM blocks_voluntaryexits WHERE block_slot = $1", slotPageData.Slot) + SELECT + validatorindex, + E'\\x'::bytea as signature, + 'Execution Layer' as triggered_via, + blocks_exit_requests.status + FROM blocks_exit_requests + INNER JOIN validators AS validatorindex_pubkey ON blocks_exit_requests.validator_pubkey = validatorindex_pubkey.pubkey + WHERE blocks_exit_requests.slot_processed = $1 + + ORDER BY validatorindex + `, slotPageData.Slot) if err != nil { return nil, fmt.Errorf("error retrieving block deposit data: %v", err) } @@ -371,6 +397,88 @@ func GetSlotPageData(blockSlot uint64) (*types.BlockPageData, error) { if err != nil { return nil, fmt.Errorf("error retrieving sync-committee of block %v: %v", slotPageData.Slot, err) } + // TODO: remove v1 table dependency once eth1id resolving is available + // See https://bitfly1.atlassian.net/browse/BEDS-1522 + err = db.ReaderDb.Select(&slotPageData.MoveToCompoundingRequests, ` + SELECT + slot_processed as block_slot, + block_processed_root as block_root, + index_processed as request_index, + v.validatorindex as validator_index, + COALESCE(v1.address, decode('0000000000000000000000000000000000000000', 'hex')) as address + FROM blocks_switch_to_compounding_requests_v2 + INNER JOIN validators v ON (v.pubkey = validator_pubkey) + LEFT JOIN blocks_switch_to_compounding_requests v1 ON (blocks_switch_to_compounding_requests_v2.slot_processed = v1.block_slot AND blocks_switch_to_compounding_requests_v2.block_processed_root = v1.block_root AND blocks_switch_to_compounding_requests_v2.index_processed = v1.request_index) + WHERE slot_processed = $1 AND block_processed_root = $2 + AND blocks_switch_to_compounding_requests_v2.status = 'completed' + ORDER BY index_processed`, slotPageData.Slot, slotPageData.BlockRoot) + if err != nil { + return nil, fmt.Errorf("error retrieving blocks_switch_to_compounding_requests_v2 of slot %v: %v", slotPageData.Slot, err) + } + + err = db.ReaderDb.Select(&slotPageData.ConsolidationRequests, ` + SELECT + slot_processed as block_slot, + block_processed_root as block_root, + index_processed as request_index, + sv.validatorindex as source_index, + tv.validatorindex as target_index, + amount_consolidated + FROM blocks_consolidation_requests_v2 + INNER JOIN validators sv ON (sv.pubkey = source_pubkey) + INNER JOIN validators tv ON (tv.pubkey = target_pubkey) + WHERE slot_processed = $1 AND block_processed_root = $2 + AND blocks_consolidation_requests_v2.status = 'completed' + ORDER BY index_processed`, slotPageData.Slot, slotPageData.BlockRoot) + if err != nil { + return nil, fmt.Errorf("error retrieving blocks_consolidation_requests of block %v: %v", slotPageData.Slot, err) + } + + // TODO: remove v1 table dependency once eth1id resolving is available + // See https://bitfly1.atlassian.net/browse/BEDS-1522 + err = db.ReaderDb.Select(&slotPageData.WithdrawalRequests, ` + SELECT + slot_processed as block_slot, + block_processed_root as block_root, + index_processed as request_index, + COALESCE(v1.source_address, decode('0000000000000000000000000000000000000000', 'hex')) as source_address, + blocks_withdrawal_requests_v2.validator_pubkey, + COALESCE((SELECT validatorindex FROM validators WHERE pubkey = blocks_withdrawal_requests_v2.validator_pubkey), -1) as validator_index, + blocks_withdrawal_requests_v2.amount + FROM blocks_withdrawal_requests_v2 + LEFT JOIN blocks_withdrawal_requests v1 ON (blocks_withdrawal_requests_v2.slot_processed = v1.block_slot AND blocks_withdrawal_requests_v2.block_processed_root = v1.block_root AND blocks_withdrawal_requests_v2.index_processed = v1.request_index) + WHERE slot_processed = $1 AND block_processed_root = $2 + AND blocks_withdrawal_requests_v2.status = 'completed' + ORDER BY index_processed`, slotPageData.Slot, slotPageData.BlockRoot) + if err != nil { + return nil, fmt.Errorf("error retrieving blocks_withdrawal_requests_v2 of block %v: %v", slotPageData.Slot, err) + } + + for _, wr := range slotPageData.WithdrawalRequests { + if wr.Amount == 0 { + wr.Type = "Exit" + } else { + wr.Type = "Withdrawal" + } + } + + err = db.ReaderDb.Select(&slotPageData.DepositRequests, ` + SELECT + slot_processed as block_slot, + block_processed_root as block_root, + index_processed as request_index, + pubkey, + withdrawal_credentials, + COALESCE((SELECT validatorindex FROM validators WHERE validators.pubkey = blocks_deposit_requests_v2.pubkey), -1) as validator_index, + amount, + signature + FROM blocks_deposit_requests_v2 + WHERE slot_processed = $1 AND block_processed_root = $2 + AND blocks_deposit_requests_v2.status = 'completed' + ORDER BY index_processed`, slotPageData.Slot, slotPageData.BlockRoot) + if err != nil { + return nil, fmt.Errorf("error retrieving blocks_deposit_requests_v2 of block %v: %v", slotPageData.Slot, err) + } return &slotPageData, nil } @@ -707,6 +815,7 @@ type attestationsData struct { BlockIndex uint64 `json:"BlockIndex"` Slot uint64 `json:"Slot"` CommitteeIndex uint64 `json:"CommitteeIndex"` + CommitteeBits template.HTML `json:"CommitteeBits"` AggregationBits template.HTML `json:"AggregationBits"` Validators template.HTML `json:"Validators"` BeaconBlockRoot string `json:"BeaconBlockRoot"` @@ -729,7 +838,19 @@ func SlotAttestationsData(w http.ResponseWriter, r *http.Request) { return } - attestations, err := getAttestationsData(slot, false) + offset := uint64(0) + + offsetStr := r.URL.Query().Get("offset") + if offsetStr != "" { + offsetParsed, err := strconv.ParseUint(offsetStr, 10, 64) + if err != nil || offsetParsed > 30 { + http.Error(w, "Error: Invalid parameter offset.", http.StatusBadRequest) + return + } + offset = offsetParsed + } + + attestations, err := getAttestationsData(slot, int64(offset)) if err != nil { logger.Errorf("error retrieving attestations data for slot %v, err: %v", slot, err) http.Error(w, "Internal server error", http.StatusInternalServerError) @@ -746,6 +867,7 @@ func SlotAttestationsData(w http.ResponseWriter, r *http.Request) { BlockIndex: v.BlockIndex, Slot: v.Slot, CommitteeIndex: v.CommitteeIndex, + CommitteeBits: utils.FormatCommitteeBitList(v.CommitteeBits), AggregationBits: utils.FormatBitlist(v.AggregationBits), Validators: validators, BeaconBlockRoot: fmt.Sprintf("%x", v.BeaconBlockRoot), @@ -785,7 +907,7 @@ func SlotWithdrawalData(w http.ResponseWriter, r *http.Request) { tableData = append(tableData, []interface{}{ template.HTML(fmt.Sprintf("%v", w.Index)), utils.FormatValidator(w.ValidatorIndex), - utils.FormatAddress(w.Address, nil, "", false, false, true), + utils.FormatWithdrawalAddress(w.Address, nil, "", false, false, true), utils.FormatClCurrency(w.Amount, currency, 6, true, false, false, true), }) } diff --git a/handlers/user.go b/handlers/user.go index c632304241..f0c330fbf5 100644 --- a/handlers/user.go +++ b/handlers/user.go @@ -13,6 +13,7 @@ import ( "strings" "time" + "github.com/doug-martin/goqu/v9" "github.com/gobitfly/eth2-beaconchain-explorer/db" "github.com/gobitfly/eth2-beaconchain-explorer/mail" "github.com/gobitfly/eth2-beaconchain-explorer/ratelimit" @@ -20,6 +21,7 @@ import ( "github.com/gobitfly/eth2-beaconchain-explorer/templates" "github.com/gobitfly/eth2-beaconchain-explorer/types" "github.com/gobitfly/eth2-beaconchain-explorer/utils" + "github.com/jmoiron/sqlx" ctxt "context" @@ -380,18 +382,31 @@ func RemoveAllValidatorsAndUnsubscribe(w http.ResponseWriter, r *http.Request) { // UserNotificationsCenter renders the notificationsCenter template func UserNotificationsCenter(w http.ResponseWriter, r *http.Request) { - var notificationsCenterTemplate = templates.GetTemplate(notificationCenterParts...) - w.Header().Set("Content-Type", "text/html") - userNotificationsCenterData := &types.UserNotificationsCenterPageData{} - data := InitPageData(w, r, "user", "/user", "", notificationCenterParts) user := getUser(r) + // only allow accessing the v1 notification center if user has v1 notifications + hasV1Notifications, err := hasUserV1NotificationSubscriptions(r.Context(), user.UserID) + if err != nil { + logger.Errorf("error checking v1 notifications for user %v: %v", user.UserID, err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + if !hasV1Notifications { + // prohibited, forward to v2 notifications + http.Redirect(w, r, utils.Config.V2NotificationURL, http.StatusSeeOther) + return + } + + var notificationsCenterTemplate = templates.GetTemplate(notificationCenterParts...) + userNotificationsCenterData := &types.UserNotificationsCenterPageData{} + data := InitPageData(w, r, "user", "/user", "", notificationCenterParts) + userNotificationsCenterData.Flashes = utils.GetFlashes(w, r, authSessionName) userNotificationsCenterData.CsrfField = csrf.TemplateField(r) var watchlistPubkeys [][]byte - err := db.FrontendWriterDB.Select(&watchlistPubkeys, ` + err = db.FrontendWriterDB.Select(&watchlistPubkeys, ` SELECT validator_publickey FROM users_validators_tags WHERE user_id = $1 and tag = $2 @@ -729,6 +744,51 @@ func UserNotificationsCenter(w http.ResponseWriter, r *http.Request) { } } +// copied from v2 and adjusted event names for the v1 repo +func hasUserV1NotificationSubscriptions(ctx ctxt.Context, userId uint64) (bool, error) { + events := []types.EventName{ + types.ValidatorIsOfflineEventName, + types.ValidatorMissedProposalEventName, + types.ValidatorExecutedProposalEventName, + types.ValidatorMissedAttestationEventName, + types.ValidatorReceivedWithdrawalEventName, + types.ValidatorGotSlashedEventName, + types.ValidatorDidSlashEventName, + types.SyncCommitteeSoon, + types.ValidatorReceivedDepositEventName, + } + for i, event := range events { + events[i] = types.EventName(fmt.Sprintf("%s:%s", utils.GetNetwork(), event)) + } + ds := goqu.Dialect("postgres"). + Select(goqu.COUNT(goqu.I("id"))). + From(goqu.T("users_subscriptions")). + Where( + goqu.I("user_id").Eq(userId), + goqu.L("event_name IN ?", events), + goqu.I("event_filter").NotLike("vdb:%"), + ) + + count, err := runQuery[int](ctx, db.FrontendReaderDB, ds) + return count > 0, err +} + +func runQuery[T any](ctx ctxt.Context, db *sqlx.DB, ds *goqu.SelectDataset) (T, error) { + query, _, err := ds.Prepared(false).ToSQL() + if err != nil { + var zero T + return zero, fmt.Errorf("error preparing query: %w", err) + } + + var result T + err = db.GetContext(ctx, &result, query) + if err != nil { + return result, fmt.Errorf("error executing query: %w", err) + } + + return result, nil +} + func UserNotificationsData(w http.ResponseWriter, r *http.Request) { currency := GetCurrency(r) w.Header().Set("Content-Type", "application/json") @@ -893,8 +953,6 @@ func UserSubscriptionsData(w http.ResponseWriter, r *http.Request) { } else { pubkey = utils.FormatPublicKey(h) } - } else if sub.EventName == string(types.TaxReportEventName) { - pubkey = template.HTML(`report`) } else if strings.HasPrefix(string(sub.EventName), "monitoring_") { pubkey = utils.FormatMachineName(sub.EventFilter) } @@ -1394,7 +1452,8 @@ func UserConfirmUpdateEmail(w http.ResponseWriter, r *http.Request) { } // UserValidatorWatchlistAdd godoc -// @Summary subscribes a user to get notifications from a specific validator +// @Summary Add validator subscription +// @Description Subscribes a user to get notifications from a specific validator // @Tags User // @Produce json // @Param pubKey query string true "Public Key of validator you want to subscribe to" @@ -1508,7 +1567,8 @@ func UserValidatorWatchlistAdd(w http.ResponseWriter, r *http.Request) { } // UserDashboardWatchlistAdd godoc -// @Summary subscribes a user to get notifications from a specific validator via index +// @Summary Save validator subscription +// @Description Subscribes a user to get notifications from a specific validator via index // @Tags User // @Produce json // @Param pubKey body []string true "Index of validator you want to subscribe to" @@ -1572,7 +1632,8 @@ func UserDashboardWatchlistAdd(w http.ResponseWriter, r *http.Request) { } // UserDashboardWatchlistRemove godoc -// @Summary unsubscribes a user from a specific validator via index from both watchlist and notification events +// @Summary Remove validator subscription +// @Description Unsubscribes a user from a specific validator via index from both watchlist and notification events // @Tags User // @Produce json // @Param pubKey body []string true "Index of validator you want to unsubscribe from" @@ -1628,7 +1689,8 @@ func UserDashboardWatchlistRemove(w http.ResponseWriter, r *http.Request) { } // UserValidatorWatchlistRemove godoc -// @Summary unsubscribes a user from a specific validator +// @Summary Remove validator subscription +// @Description Unsubscribes a user from a specific validator // @Tags User // @Produce json // @Param pubKey query string true "Public Key of validator you want to subscribe to" @@ -2169,7 +2231,7 @@ func UserNotificationsUnsubscribe(w http.ResponseWriter, r *http.Request) { } func isValidSubscriptionFilter(userID uint64, eventName types.EventName, filter string) (bool, error) { - ethClients := []string{"geth", "nethermind", "besu", "erigon", "teku", "prysm", "nimbus", "lighthouse", "lodestar", "rocketpool", "mev-boost"} + ethClients := []string{"geth", "nethermind", "besu", "erigon", "teku", "prysm", "nimbus", "lighthouse", "lodestar", "rocketpool", "mev-boost", "reth"} isPkey := searchPubkeyExactRE.MatchString(filter) @@ -2286,7 +2348,8 @@ type UsersNotificationsRequest struct { } // UserNotificationsSubscribed godoc -// @Summary Get a set of events a user is subscribed to +// @Summary Get user subscriptions +// @Description Get a set of events a user is subscribed to // @Tags User // @Param requestFilter body types.UsersNotificationsRequest false "An object that filters through the active subscriptions" // @Produce json diff --git a/handlers/validator.go b/handlers/validator.go index a10db320e3..a80a05a5a2 100644 --- a/handlers/validator.go +++ b/handlers/validator.go @@ -2,9 +2,11 @@ package handlers import ( "bytes" + "context" "database/sql" "encoding/hex" "encoding/json" + "errors" "fmt" "html/template" "math" @@ -19,6 +21,7 @@ import ( "github.com/gobitfly/eth2-beaconchain-explorer/templates" "github.com/gobitfly/eth2-beaconchain-explorer/types" "github.com/gobitfly/eth2-beaconchain-explorer/utils" + "github.com/sirupsen/logrus" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/crypto" @@ -40,7 +43,7 @@ func Validator(w http.ResponseWriter, r *http.Request) { validatorTemplateFiles := append(layoutTemplateFiles, "validator/validator.html", "validator/heading.html", - "validator/tables.html", + "validator/tables/*.html", "validator/modals.html", "modals.html", "validator/overview.html", @@ -93,38 +96,55 @@ func Validator(w http.ResponseWriter, r *http.Request) { futureSyncDutyEpoch := uint64(0) stats := services.GetLatestStats() - churnRate := stats.ValidatorChurnLimit - if churnRate == nil { - churnRate = new(uint64) - } + epoch := services.LatestEpoch() + latestState := services.LatestState() - if *churnRate == 0 { - *churnRate = 4 - logger.Warning("Churn rate not set in config using 4 as default") + var finalityDelay uint64 = 0 + if latestState != nil { + finalityDelay = uint64(math.Max(1, float64(latestState.FinalityDelay)-2)) } - validatorPageData.ChurnRate = *churnRate - - activationChurnRate := stats.ValidatorActivationChurnLimit - if activationChurnRate == nil { - activationChurnRate = new(uint64) + if finalityDelay < 1 { + finalityDelay = 1 } - if *activationChurnRate == 0 { - *activationChurnRate = 4 - logger.Warning("Activation Churn rate not set in config using 4 as default") - } + validatorPageData.ElectraHasHappened = utils.ElectraHasHappened(epoch) + + var activationChurnRate *uint64 + if utils.ElectraHasHappened(epoch) { + queueData := services.LatestQueueData() + + validatorPageData.ChurnRate = queueData.EnteringBalancePerEpoch + } else { + + churnRate := stats.ValidatorChurnLimit + if churnRate == nil { + churnRate = new(uint64) + } - pendingCount := stats.PendingValidatorCount - if pendingCount == nil { - pendingCount = new(uint64) + if *churnRate == 0 { + *churnRate = 4 + logger.Warning("Churn rate not set in config using 4 as default") + } + validatorPageData.ChurnRate = *churnRate + + activationChurnRate = stats.ValidatorActivationChurnLimit + if activationChurnRate == nil { + activationChurnRate = new(uint64) + } + + if *activationChurnRate == 0 { + *activationChurnRate = 4 + logger.Warning("Activation Churn rate not set in config using 4 as default") + } } - validatorPageData.PendingCount = *pendingCount validatorPageData.InclusionDelay = int64((utils.Config.Chain.ClConfig.Eth1FollowDistance*utils.Config.Chain.ClConfig.SecondsPerEth1Block+utils.Config.Chain.ClConfig.SecondsPerSlot*utils.Config.Chain.ClConfig.SlotsPerEpoch*utils.Config.Chain.ClConfig.EpochsPerEth1VotingPeriod)/3600) + 1 data := InitPageData(w, r, "validators", "/validators", "", validatorTemplateFiles) validatorPageData.NetworkStats = services.LatestIndexPageData() validatorPageData.User = data.User + validatorPageData.ConsolidationTargetIndex = -1 + validatorPageData.Epoch = latestEpoch validatorPageData.FlashMessage, err = utils.GetFlash(w, r, validatorEditFlash) if err != nil { @@ -252,6 +272,50 @@ func Validator(w http.ResponseWriter, r *http.Request) { } } + g := errgroup.Group{} + g.Go(func() error { + if utils.ElectraHasHappened(validatorPageData.Epoch) { + // deposit processing queue + pendingDeposit, err := db.GetNextPendingDeposit(validatorPageData.PublicKey) + if err != nil { + if err == sql.ErrNoRows { + var lastPendingDeposit types.PendingDeposit + err := db.ReaderDb.Get(&lastPendingDeposit, `SELECT id, est_clear_epoch, amount FROM pending_deposits_queue WHERE id = (select max(id) from pending_deposits_queue)`) + if err != nil { + logrus.Warnf("error getting pending deposits for validator %v: %v", validatorPageData.PublicKey, err) + return nil + } + // no queue position as deposit is too fresh, show estimates of last entry in the queue as rough estimate + validatorPageData.EstimatedActivationEpoch = lastPendingDeposit.EstClearEpoch + 1 + finalityDelay + utils.Config.Chain.ClConfig.MaxSeedLookahead + 1 + estimatedDequeueTs := utils.EpochToTime(validatorPageData.EstimatedActivationEpoch) + validatorPageData.EstimatedActivationTs = estimatedDequeueTs + validatorPageData.PendingDepositAboveMinActivation = true // assume for now + } else { + logrus.Warnf("error getting pending deposits for validator %v: %v", validatorPageData.PublicKey, err) + return nil + } + + return nil // can happen if the deposit is fresh and has not yet picked up by the beaconchain deposit queue + } + + validatorPageData.QueuePosition = uint64(pendingDeposit.ID) + 1 + validatorPageData.EstimatedActivationEpoch = pendingDeposit.EstClearEpoch + 1 + finalityDelay + utils.Config.Chain.ClConfig.MaxSeedLookahead + 1 + estimatedDequeueTs := utils.EpochToTime(validatorPageData.EstimatedActivationEpoch) + validatorPageData.EstimatedActivationTs = estimatedDequeueTs + validatorPageData.EstimatedIndexEpoch = pendingDeposit.EstClearEpoch + validatorPageData.EstimatedIndexTs = utils.EpochToTime(validatorPageData.EstimatedIndexEpoch) + validatorPageData.PendingDepositAboveMinActivation = pendingDeposit.Amount >= utils.Config.Chain.ClConfig.MinActivationBalance + } + return nil + }) + + err = g.Wait() + if err != nil { + utils.LogError(err, "error getting pending deposits for validator", 0, errFields) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + data.Data = validatorPageData if utils.IsApiRequest(r) { w.Header().Set("Content-Type", "application/json") @@ -339,7 +403,6 @@ func Validator(w http.ResponseWriter, r *http.Request) { validatorPageData.RankPercentage = float64(validatorPageData.Rank7d) / float64(validatorPageData.RankCount) } - validatorPageData.Epoch = latestEpoch validatorPageData.Index = index if data.User.Authenticated { @@ -365,18 +428,21 @@ func Validator(w http.ResponseWriter, r *http.Request) { validatorPageData.ExitTs = utils.EpochToTime(validatorPageData.ExitEpoch) validatorPageData.WithdrawableTs = utils.EpochToTime(validatorPageData.WithdrawableEpoch) - avgSyncInterval := uint64(getAvgSyncCommitteeInterval(1)) - avgSyncIntervalAsDuration := time.Duration( - utils.Config.Chain.ClConfig.SecondsPerSlot* - utils.SlotsPerSyncCommittee()* - avgSyncInterval) * time.Second - validatorPageData.AvgSyncInterval = &avgSyncIntervalAsDuration + validatorWithdrawalAddress, err := utils.WithdrawalCredentialsToAddress(validatorPageData.WithdrawCredentials) + if err != nil { + if len(validatorPageData.WithdrawCredentials) <= 0 || !bytes.Equal(validatorPageData.WithdrawCredentials[:1], []byte{0x00}) { + utils.LogWarn(err, "error converting withdrawal credentials to address", 0, errFields) + } + } var lowerBoundDay uint64 if lastStatsDay > 30 { lowerBoundDay = lastStatsDay - 30 } - g := errgroup.Group{} + gCtx, cancel := context.WithCancel(context.Background()) + defer cancel() // ensure resources cleaned up in case g.Wait is skipped + g, ctx := errgroup.WithContext(gCtx) + g.Go(func() error { start := time.Now() defer func() { @@ -411,12 +477,15 @@ func Validator(w http.ResponseWriter, r *http.Request) { return nil }) + currentBalanceCh := make(chan uint64, 1) + var avgSyncInterval uint64 g.Go(func() error { // those functions need to be executed sequentially as both require the CurrentBalance value start := time.Now() defer func() { timings.Earnings = time.Since(start) }() + defer close(currentBalanceCh) earnings, balances, err := GetValidatorEarnings([]uint64{index}, currency) if err != nil { return fmt.Errorf("error getting validator earnings: %w", err) @@ -433,16 +502,24 @@ func Validator(w http.ResponseWriter, r *http.Request) { if ok { validatorPageData.CurrentBalance = vbalance.Balance validatorPageData.EffectiveBalance = vbalance.EffectiveBalance + currentBalanceCh <- vbalance.Balance + + avgSyncInterval = uint64(getAvgSyncCommitteeInterval(validatorPageData.EffectiveBalance / 1e9)) + avgSyncIntervalAsDuration := time.Duration( + utils.Config.Chain.ClConfig.SecondsPerSlot* + utils.SlotsPerSyncCommittee()* + avgSyncInterval) * time.Second + validatorPageData.AvgSyncInterval = &avgSyncIntervalAsDuration } - if bytes.Equal(validatorPageData.WithdrawCredentials[:1], []byte{0x01}) { + credentialsPrefixBytes := validatorPageData.WithdrawCredentials[:1] + if bytes.Equal(credentialsPrefixBytes, []byte{0x01}) || bytes.Equal(credentialsPrefixBytes, []byte{0x02}) { // validators can have 0x01 credentials even before the cappella fork validatorPageData.IsWithdrawableAddress = true } if validatorPageData.CappellaHasHappened { // if we are currently past the cappella fork epoch, we can calculate the withdrawal information - validatorSlice := []uint64{index} withdrawalsCount, err := db.GetTotalWithdrawalsCount(validatorSlice) if err != nil { @@ -461,14 +538,15 @@ func Validator(w http.ResponseWriter, r *http.Request) { } validatorPageData.BLSChange = blsChange - if bytes.Equal(validatorPageData.WithdrawCredentials[:1], []byte{0x00}) && blsChange != nil { + if bytes.Equal(credentialsPrefixBytes, []byte{0x00}) && blsChange != nil { // blsChanges are only possible afters cappeala validatorPageData.IsWithdrawableAddress = true } // only calculate the expected next withdrawal if the validator is eligible + maxEB := utils.GetMaxEffectiveBalanceByWithdrawalCredentials(validatorPageData.WithdrawCredentials) isFullWithdrawal := validatorPageData.CurrentBalance > 0 && validatorPageData.WithdrawableEpoch <= validatorPageData.Epoch - isPartialWithdrawal := validatorPageData.EffectiveBalance == utils.Config.Chain.ClConfig.MaxEffectiveBalance && validatorPageData.CurrentBalance > utils.Config.Chain.ClConfig.MaxEffectiveBalance + isPartialWithdrawal := validatorPageData.EffectiveBalance == maxEB && validatorPageData.CurrentBalance > maxEB if stats != nil && stats.LatestValidatorWithdrawalIndex != nil && stats.TotalValidatorCount != nil && validatorPageData.IsWithdrawableAddress && (isFullWithdrawal || isPartialWithdrawal) { distance, err := GetWithdrawableCountFromCursor(validatorPageData.Epoch, validatorPageData.Index, *stats.LatestValidatorWithdrawalIndex) if err != nil { @@ -498,7 +576,7 @@ func Validator(w http.ResponseWriter, r *http.Request) { if isFullWithdrawal { withdrawalAmount = validatorPageData.CurrentBalance } else { - withdrawalAmount = validatorPageData.CurrentBalance - utils.Config.Chain.ClConfig.MaxEffectiveBalance + withdrawalAmount = validatorPageData.CurrentBalance - maxEB } if latestEpoch == lastWithdrawalsEpoch { @@ -507,7 +585,7 @@ func Validator(w http.ResponseWriter, r *http.Request) { tableData = append(tableData, []interface{}{ template.HTML(fmt.Sprintf(`~ %s`, utils.FormatEpoch(uint64(utils.TimeToEpoch(timeToWithdrawal))))), template.HTML(fmt.Sprintf(`~ %s`, utils.FormatBlockSlot(utils.TimeToSlot(uint64(timeToWithdrawal.Unix()))))), - template.HTML(fmt.Sprintf(`~ %s`, utils.FormatTimestamp(timeToWithdrawal.Unix()))), + template.HTML(fmt.Sprintf(` ~ %s`, utils.FormatTimestamp(timeToWithdrawal.Unix()))), withdrawalCredentialsTemplate, template.HTML(fmt.Sprintf(` %s`, utils.FormatClCurrency(withdrawalAmount, currency, 6, true, false, false, true))), }) @@ -557,26 +635,72 @@ func Validator(w http.ResponseWriter, r *http.Request) { } validatorPageData.ShowMultipleWithdrawalCredentialsWarning = hasMultipleWithdrawalCredentials(validatorPageData.Deposits) - return nil }) g.Go(func() error { - // we only need to get the queue information if we don't have an activation epoch but we have an eligibility epoch - if validatorPageData.ActivationEpoch > 100_000_000 && validatorPageData.ActivationEligibilityEpoch < 100_000_000 { - queueAhead, err := db.GetQueueAheadOfValidator(validatorPageData.Index) - if err != nil { - return fmt.Errorf("failed to retrieve queue ahead of validator %v: %w", validatorPageData.ValidatorIndex, err) + if utils.ElectraHasHappened(validatorPageData.Epoch) { + var isPendingActivation = false + + theoreticalActivationEligibilityEpoch := validatorPageData.ActivationEligibilityEpoch // does not consider if deposit actually is above min activation balance + if validatorPageData.ActivationEpoch > 100_000_000 && validatorPageData.ActivationEligibilityEpoch < 100_000_000 { + isPendingActivation = true + validatorPageData.PendingDepositAboveMinActivation = true // already has eligibility + } else if validatorPageData.ActivationEpoch > 100_000_000 { + // Validator either has just processed a deposit and gets their eligibility in the next epoch or + // validator is missing balance for activation + pendingDeposit, err := db.GetNextPendingDeposit(validatorPageData.PublicKey) + if err != nil { + logrus.Warnf("error getting pending deposits for validator %v: %v", validatorPageData.PublicKey, err) + return nil + } + + select { + case currentBalance := <-currentBalanceCh: + validatorPageData.PendingDepositAboveMinActivation = currentBalance+pendingDeposit.Amount >= utils.Config.Chain.ClConfig.MinActivationBalance + case <-ctx.Done(): + return ctx.Err() + } + + theoreticalActivationEligibilityEpoch = pendingDeposit.EstClearEpoch + 1 + if !validatorPageData.PendingDepositAboveMinActivation { + // countdown to when the deposit will be processed if it won't become eligible + validatorPageData.EstimatedActivationTs = utils.EpochToTime(pendingDeposit.EstClearEpoch) + } else { + validatorPageData.ActivationEligibilityEpoch = theoreticalActivationEligibilityEpoch + } + + isPendingActivation = true + } + + if isPendingActivation { + validatorPageData.QueuePosition = 0 + validatorPageData.EstimatedActivationEpoch = theoreticalActivationEligibilityEpoch + finalityDelay + utils.Config.Chain.ClConfig.MaxSeedLookahead + 1 + if validatorPageData.EstimatedActivationTs.IsZero() { + validatorPageData.EstimatedActivationTs = utils.EpochToTime(validatorPageData.EstimatedActivationEpoch) + } + + validatorPageData.EstimatedIndexEpoch = theoreticalActivationEligibilityEpoch - 1 + validatorPageData.EstimatedIndexTs = utils.EpochToTime(validatorPageData.EstimatedIndexEpoch) + } + } else { + + // we only need to get the queue information if we don't have an activation epoch but we have an eligibility epoch + if validatorPageData.ActivationEpoch > 100_000_000 && validatorPageData.ActivationEligibilityEpoch < 100_000_000 { + queueAhead, err := db.GetQueueAheadOfValidator(validatorPageData.Index) + if err != nil { + return fmt.Errorf("failed to retrieve queue ahead of validator %v: %w", validatorPageData.ValidatorIndex, err) + } + validatorPageData.QueuePosition = queueAhead + 1 + epochsToWait := queueAhead / *activationChurnRate + // calculate dequeue epoch + estimatedActivationEpoch := validatorPageData.Epoch + epochsToWait + 1 + // add activation offset + estimatedActivationEpoch += utils.Config.Chain.ClConfig.MaxSeedLookahead + 1 + validatorPageData.EstimatedActivationEpoch = estimatedActivationEpoch + estimatedDequeueTs := utils.EpochToTime(estimatedActivationEpoch) + validatorPageData.EstimatedActivationTs = estimatedDequeueTs } - validatorPageData.QueuePosition = queueAhead + 1 - epochsToWait := queueAhead / *activationChurnRate - // calculate dequeue epoch - estimatedActivationEpoch := validatorPageData.Epoch + epochsToWait + 1 - // add activation offset - estimatedActivationEpoch += utils.Config.Chain.ClConfig.MaxSeedLookahead + 1 - validatorPageData.EstimatedActivationEpoch = estimatedActivationEpoch - estimatedDequeueTs := utils.EpochToTime(estimatedActivationEpoch) - validatorPageData.EstimatedActivationTs = estimatedDequeueTs } return nil }) @@ -780,13 +904,6 @@ func Validator(w http.ResponseWriter, r *http.Request) { // sync luck if len(allSyncPeriods) > 0 { maxPeriod := allSyncPeriods[0].Period - expectedSyncCount, err := getExpectedSyncCommitteeSlots([]uint64{index}, lastFinalizedEpoch) - if err != nil { - return fmt.Errorf("error getting expected sync committee slots: %w", err) - } - if expectedSyncCount != 0 { - validatorPageData.SyncLuck = float64(validatorPageData.ParticipatedSyncCountSlots+validatorPageData.MissedSyncCountSlots) / float64(expectedSyncCount) - } nextEstimate := utils.EpochToTime(utils.FirstEpochOfSyncPeriod(maxPeriod + avgSyncInterval)) validatorPageData.SyncEstimate = &nextEstimate } @@ -840,6 +957,203 @@ func Validator(w http.ResponseWriter, r *http.Request) { return nil }) + g.Go(func() error { + err = db.ReaderDb.Select(&validatorPageData.ConsolidationRequests, ` + SELECT + slot_processed as block_slot, + block_processed_root as block_root, + index_processed as request_index, + sv.validatorindex as source_index, + tv.validatorindex as target_index, + COALESCE(amount_consolidated, 0) AS amount_consolidated + FROM blocks_consolidation_requests_v2 + INNER JOIN blocks ON blocks_consolidation_requests_v2.block_processed_root = blocks.blockroot AND blocks.status = '1' + INNER JOIN validators sv ON (sv.pubkey = source_pubkey) + INNER JOIN validators tv ON (tv.pubkey = target_pubkey) + WHERE + ( source_pubkey = (select pubkey from validators where validatorindex = $1) + OR target_pubkey = (select pubkey from validators where validatorindex = $1) + ) + AND blocks_consolidation_requests_v2.status = 'completed' + ORDER BY slot_processed DESC, index_processed`, index) + if err != nil { + return fmt.Errorf("error retrieving blocks_consolidation_requests_v2 of validator %v: %v", validatorPageData.Index, err) + } + + // find the consolidation target index + for _, cr := range validatorPageData.ConsolidationRequests { + if cr.SourceIndex == int64(validatorPageData.Index) { + validatorPageData.ConsolidationTargetIndex = cr.TargetIndex + break + } + } + return nil + }) + + g.Go(func() error { + validatorPageData.MoveToCompoundingRequest = &types.FrontendMoveToCompoundingRequest{} + // TODO: remove v1 table dependency once eth1id resolving is available + // See https://bitfly1.atlassian.net/browse/BEDS-1522 + err = db.ReaderDb.Get(validatorPageData.MoveToCompoundingRequest, ` + SELECT + slot_processed as block_slot, + block_processed_root as block_root, + index_processed as request_index, + v.validatorindex as validator_index, + COALESCE(v1.address, decode('0000000000000000000000000000000000000000', 'hex')) as address + FROM blocks_switch_to_compounding_requests_v2 + INNER JOIN blocks ON blocks_switch_to_compounding_requests_v2.block_processed_root = blocks.blockroot AND blocks.status = '1' + INNER JOIN validators v ON (v.pubkey = validator_pubkey) + LEFT JOIN blocks_switch_to_compounding_requests v1 ON (blocks_switch_to_compounding_requests_v2.slot_processed = v1.block_slot AND blocks_switch_to_compounding_requests_v2.block_processed_root = v1.block_root AND blocks_switch_to_compounding_requests_v2.index_processed = v1.request_index) + WHERE v.validatorindex = $1 + AND blocks_switch_to_compounding_requests_v2.status = 'completed' + ORDER BY slot_processed DESC, index_processed LIMIT 1`, index) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + validatorPageData.MoveToCompoundingRequest = nil + return nil + } + return fmt.Errorf("error retrieving blocks_switch_to_compounding_requests of validator %v: %v", validatorPageData.Index, err) + } + return nil + }) + + g.Go(func() error { + err = db.ReaderDb.Select(&validatorPageData.ConsensusElExits, ` + SELECT + slot_processed AS slot, + block_processed_root AS block_root, + blocks_exit_requests.status, + COALESCE(reject_reason, '') AS reject_reason, + validator_pubkey, + 'Execution Layer' as triggered_via + FROM blocks_exit_requests + INNER JOIN blocks ON blocks_exit_requests.block_processed_root = blocks.blockroot AND blocks.status = '1' + WHERE blocks_exit_requests.validator_pubkey = $1 + + UNION ALL + + SELECT + block_slot AS slot, + block_root, + 'completed' AS status, + '' AS reject_reason, + validatorindex_pubkey.pubkey AS validator_pubkey, + 'Consensus Layer' as triggered_via + FROM blocks_voluntaryexits + INNER JOIN validators AS validatorindex_pubkey ON blocks_voluntaryexits.validatorindex = validatorindex_pubkey.validatorindex + INNER JOIN blocks ON blocks_voluntaryexits.block_root = blocks.blockroot AND blocks.status = '1' + WHERE validatorindex_pubkey.pubkey = $1 + ORDER BY slot DESC + `, validatorPageData.PublicKey) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + validatorPageData.ConsensusElExits = nil + return nil + } + return fmt.Errorf("error retrieving blocks_exit_requests/blocks_voluntaryexits of validator %v: %v", validatorPageData.Index, err) + } + return nil + }) + + g.Go(func() error { + err = db.ReaderDb.Select(&validatorPageData.ExecutionWithdrawals, ` + SELECT + source_address, + tx_hash, + block_number, + extract(epoch from block_ts)::int as block_ts, + validatorindex as validator_index, + amount + FROM eth1_withdrawal_requests + INNER JOIN validators ON eth1_withdrawal_requests.validator_pubkey = validators.pubkey + WHERE validatorindex = $1 + ORDER BY block_number DESC, tx_index desc, itx_index desc`, index) + if err != nil { + return fmt.Errorf("error retrieving eth1_withdrawal_requests of validator %v: %v", validatorPageData.Index, err) + } + + for _, ew := range validatorPageData.ExecutionWithdrawals { + if !bytes.Equal(ew.SourceAddress.Bytes(), validatorWithdrawalAddress) { + ew.WrongSourceAddress = true + } + } + return nil + }) + + g.Go(func() error { + err = db.ReaderDb.Select(&validatorPageData.ExecutionConsolidations, ` + SELECT + ecr.source_address, + ecr.tx_hash, + ecr.block_number, + extract(epoch from ecr.block_ts)::int as block_ts, + s.validatorindex AS source_validator_index, + t.validatorindex AS target_validator_index, + s.withdrawalcredentials AS source_withdrawalcredentials + FROM eth1_consolidation_requests ecr + INNER JOIN validators s ON ecr.source_pubkey = s.pubkey + INNER JOIN validators t ON ecr.target_pubkey = t.pubkey + WHERE source_pubkey = (select pubkey from validators where validatorindex = $1) + OR target_pubkey = (select pubkey from validators where validatorindex = $1) + ORDER BY ecr.block_number DESC, ecr.tx_index DESC, ecr.itx_index DESC + `, index) + if err != nil { + return fmt.Errorf("error retrieving eth1_consolidation_requests of validator %v: %v", validatorPageData.Index, err) + } + + for _, ew := range validatorPageData.ExecutionConsolidations { + sourceWithdrawalAddress, err := utils.WithdrawalCredentialsToAddress(ew.SourceWithdrawalCredentials) + if err != nil { + logger.Warnf("error converting withdrawal credentials to address: %v", err) + continue + } + if !bytes.Equal(ew.SourceAddress.Bytes(), sourceWithdrawalAddress) { + ew.WrongSourceAddress = true + } + } + return nil + }) + + g.Go(func() error { + // TODO: remove v1 table dependency once eth1id resolving is available + // See https://bitfly1.atlassian.net/browse/BEDS-1522 + err = db.ReaderDb.Select(&validatorPageData.WithdrawalRequests, ` + SELECT + slot_processed as block_slot, + block_processed_root as block_root, + index_processed as request_index, + COALESCE(v1.source_address, decode('0000000000000000000000000000000000000000', 'hex')) as source_address, + blocks_withdrawal_requests_v2.amount + FROM blocks_withdrawal_requests_v2 + INNER JOIN blocks ON blocks_withdrawal_requests_v2.block_processed_root = blocks.blockroot AND blocks.status = '1' + LEFT JOIN blocks_withdrawal_requests v1 ON (blocks_withdrawal_requests_v2.slot_processed = v1.block_slot AND blocks_withdrawal_requests_v2.block_processed_root = v1.block_root AND blocks_withdrawal_requests_v2.index_processed = v1.request_index) + WHERE blocks_withdrawal_requests_v2.validator_pubkey = $1 + AND blocks_withdrawal_requests_v2.status = 'completed' + ORDER BY slot_processed DESC, index_processed`, validatorPageData.PublicKey) + if err != nil { + return fmt.Errorf("error retrieving blocks_withdrawal_requests_v2 of validator %v: %v", validatorPageData.Index, err) + } + + for _, wr := range validatorPageData.WithdrawalRequests { + if wr.Amount == 0 { + wr.Type = "Exit" + } else { + wr.Type = "Withdrawal" + } + } + return nil + }) + + var withdrawalCount uint64 + g.Go(func() error { + withdrawalCount, err = db.GetTotalWithdrawalsCount([]uint64{validatorPageData.Index}) + if err != nil { + return fmt.Errorf("error getting withdrawal count: %w", err) + } + return nil + }) + err = g.Wait() if err != nil { utils.LogError(err, "error getting validator data", 0) @@ -847,6 +1161,7 @@ func Validator(w http.ResponseWriter, r *http.Request) { return } + validatorPageData.EnableWithdrawalsTab = validatorPageData.CappellaHasHappened && (withdrawalCount > 0 || len(validatorPageData.ExecutionWithdrawals) > 0 || len(validatorPageData.ConsensusElExits) > 0) validatorPageData.FutureDutiesEpoch = protomath.MaxU64(futureProposalEpoch, futureSyncDutyEpoch) data.Data = validatorPageData @@ -865,35 +1180,41 @@ func Validator(w http.ResponseWriter, r *http.Request) { // Returns true if there are more than one different withdrawal credentials within both Eth1Deposits and Eth2Deposits func hasMultipleWithdrawalCredentials(deposits *types.ValidatorDeposits) bool { - if deposits == nil { - return false - } - - credential := make([]byte, 0) - - if deposits == nil { - return false - } - - // check Eth1Deposits - for _, deposit := range deposits.Eth1Deposits { - if len(credential) == 0 { - credential = deposit.WithdrawalCredentials - } else if !bytes.Equal(credential, deposit.WithdrawalCredentials) { - return true - } - } - - // check Eth2Deposits - for _, deposit := range deposits.Eth2Deposits { - if len(credential) == 0 { - credential = deposit.Withdrawalcredentials - } else if !bytes.Equal(credential, deposit.Withdrawalcredentials) { - return true - } - } - + // temporarily disable this check as defined in ticket BEDS-1252 return false + /* + if deposits == nil { + return false + } + + credential := make([]byte, 0) + + if deposits == nil { + return false + } + + // check Eth1Deposits + + for _, deposit := range deposits.Eth1Deposits { + if len(credential) == 0 { + credential = deposit.WithdrawalCredentials + } else if !bytes.Equal(credential, deposit.WithdrawalCredentials) { + return true + } + } + + // check Eth2Deposits + + for _, deposit := range deposits.Eth2Deposits { + if len(credential) == 0 { + credential = deposit.Withdrawalcredentials + } else if !bytes.Equal(credential, deposit.Withdrawalcredentials) { + return true + } + } + + return false + */ } // ValidatorDeposits returns a validator's deposits in json @@ -1302,7 +1623,7 @@ func ValidatorWithdrawals(w http.ResponseWriter, r *http.Request) { utils.FormatEpoch(utils.EpochOfSlot(w.Slot)), utils.FormatBlockSlot(w.Slot), utils.FormatTimestamp(utils.SlotToTime(w.Slot).Unix()), - utils.FormatAddress(w.Address, nil, "", false, false, true), + utils.FormatWithdrawalAddress(w.Address, nil, "", false, false, true), utils.FormatClCurrency(w.Amount, reqCurrency, 6, true, false, false, true), }) } @@ -1868,112 +2189,6 @@ func incomeToTableData(epoch uint64, income *itypes.ValidatorEpochIncome, withdr } } -// Validator returns validator data using a go template -func ValidatorStatsTable(w http.ResponseWriter, r *http.Request) { - templateFiles := append(layoutTemplateFiles, "validator_stats_table.html") - var validatorStatsTableTemplate = templates.GetTemplate(templateFiles...) - - w.Header().Set("Content-Type", "text/html") - vars := mux.Vars(r) - - var index uint64 - var err error - - errFields := map[string]interface{}{ - "route": r.URL.String()} - - data := InitPageData(w, r, "validators", "/validators", "", templateFiles) - - // Request came with a hash - if utils.IsHash(vars["index"]) { - pubKey, err := hex.DecodeString(strings.TrimPrefix(vars["index"], "0x")) - if err != nil { - utils.LogError(err, "error decoding validator pubkey", 0, errFields) - validatorNotFound(data, w, r, vars, "/stats") - return - } - index, err = db.GetValidatorIndex(pubKey) - if err != nil { - if err != sql.ErrNoRows { - errFields["pubkey"] = pubKey - utils.LogError(err, "error getting validator index from db", 0, errFields) - } - validatorNotFound(data, w, r, vars, "/stats") - return - } - } else { - // Request came with a validator index number - index, err = strconv.ParseUint(vars["index"], 10, 31) - - if err != nil { - // Request is not a valid index number - logger.Warnf("error parsing validator index: %v", err) - validatorNotFound(data, w, r, vars, "/stats") - return - } - } - - errFields["index"] = index - - // Check if validator index is available - var doesIndexExist bool - err = db.ReaderDb.Get(&doesIndexExist, "SELECT EXISTS (SELECT validatorindex FROM validators WHERE validatorindex = $1)", index) - if err != nil || !doesIndexExist { - if err != nil { - utils.LogError(err, "error checking for index in validators table", 0, errFields) - } - validatorNotFound(data, w, r, vars, "/stats") - return - } - - SetPageDataTitle(data, fmt.Sprintf("Validator %v Daily Statistics", index)) - data.Meta.Path = fmt.Sprintf("/validator/%v/stats", index) - - validatorStatsTablePageData := &types.ValidatorStatsTablePageData{ - ValidatorIndex: index, - Rows: make([]*types.ValidatorStatsTableRow, 0), - } - - err = db.ReaderDb.Select(&validatorStatsTablePageData.Rows, ` - SELECT - validatorindex, - day, - start_balance, - end_balance, - min_balance, - max_balance, - start_effective_balance, - end_effective_balance, - min_effective_balance, - max_effective_balance, - COALESCE(missed_attestations, 0) AS missed_attestations, - COALESCE(proposed_blocks, 0) AS proposed_blocks, - COALESCE(missed_blocks, 0) AS missed_blocks, - COALESCE(orphaned_blocks, 0) AS orphaned_blocks, - COALESCE(attester_slashings, 0) AS attester_slashings, - COALESCE(proposer_slashings, 0) AS proposer_slashings, - COALESCE(deposits, 0) AS deposits, - COALESCE(deposits_amount, 0) AS deposits_amount, - COALESCE(participated_sync, 0) AS participated_sync, - COALESCE(missed_sync, 0) AS missed_sync, - COALESCE(orphaned_sync, 0) AS orphaned_sync, - COALESCE(cl_rewards_gwei, 0) AS cl_rewards_gwei - FROM validator_stats - WHERE validatorindex = $1 - ORDER BY day DESC`, index) - - if err != nil { - utils.LogError(err, "error getting validator stats history from db", 0, errFields) - validatorNotFound(data, w, r, vars, "/stats") - return - } - - data.Data = validatorStatsTablePageData - if handleTemplateError(w, r, "validator.go", "ValidatorStatsTable", "", validatorStatsTableTemplate.ExecuteTemplate(w, "layout", data)) != nil { - return // an error has occurred and was processed - } -} - // ValidatorSync retrieves one page of sync duties of a specific validator for DataTable. func ValidatorSync(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") diff --git a/handlers/validatorRewards.go b/handlers/validatorRewards.go index 90a8503f76..4974abc00f 100644 --- a/handlers/validatorRewards.go +++ b/handlers/validatorRewards.go @@ -1,369 +1,4 @@ package handlers -import ( - "encoding/json" - "fmt" - "html/template" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "github.com/gobitfly/eth2-beaconchain-explorer/db" - "github.com/gobitfly/eth2-beaconchain-explorer/services" - "github.com/gobitfly/eth2-beaconchain-explorer/templates" - "github.com/gobitfly/eth2-beaconchain-explorer/types" - "github.com/gobitfly/eth2-beaconchain-explorer/utils" - - "github.com/gorilla/csrf" -) - // var supportedCurrencies = []string{"eur", "usd", "gbp", "cny", "cad", "jpy", "rub", "aud"} const USER_SUBSCRIPTION_LIMIT = 8 - -type rewardsResp struct { - Currencies []string - CsrfField template.HTML - ShowSubscriptions bool - MinDateTimestamp uint64 -} - -func ValidatorRewards(w http.ResponseWriter, r *http.Request) { - templateFiles := append(layoutTemplateFiles, "validatorRewards.html") - var validatorRewardsServicesTemplate = templates.GetTemplate(templateFiles...) - - var err error - - w.Header().Set("Content-Type", "text/html") - - data := InitPageData(w, r, "services", "/rewards", "Ethereum Validator Rewards", templateFiles) - - var supportedCurrencies []string - err = db.ReaderDb.Select(&supportedCurrencies, - `select column_name - from information_schema.columns - where table_name = 'price'`) - if err != nil { - logger.Errorf("error getting eth1-deposits-distribution for stake pools: %v", err) - } - - var minTime time.Time - err = db.ReaderDb.Get(&minTime, - `select ts from price order by ts asc limit 1`) - if err != nil { - logger.Errorf("error getting min ts: %v", err) - } - - data.Data = rewardsResp{Currencies: supportedCurrencies, CsrfField: csrf.TemplateField(r), MinDateTimestamp: uint64(minTime.Unix()), ShowSubscriptions: data.User.Authenticated} - - if handleTemplateError(w, r, "validatorRewards.go", "ValidatorRewards", "", validatorRewardsServicesTemplate.ExecuteTemplate(w, "layout", data)) != nil { - return // an error has occurred and was processed - } -} - -func getUserRewardSubscriptions(uid uint64) [][]string { - var dbResp []types.Subscription - - err := db.FrontendWriterDB.Select(&dbResp, - `select id, user_id, event_name, event_filter, last_sent_ts, last_sent_epoch, created_ts, created_epoch, event_threshold, unsubscribe_hash, internal_state from users_subscriptions where event_name=$1 AND user_id=$2`, strings.ToLower(utils.GetNetwork())+":"+string(types.TaxReportEventName), uid) - if err != nil { - logger.Errorf("error getting prices: %v", err) - } - - res := make([][]string, len(dbResp)) - for i, item := range dbResp { - q, err := url.ParseQuery(item.EventFilter) - if err != nil { - continue - } - res[i] = []string{ - fmt.Sprintf("%v", item.CreatedTime), - q.Get("currency"), - q.Get("validators"), - item.EventFilter, - } - } - - return res -} - -func isValidCurrency(currency string) bool { - var count uint64 - err := db.ReaderDb.Get(&count, - `select count(column_name) - from information_schema.columns - where table_name = 'price' AND column_name=$1;`, currency) - if err != nil { - logger.Errorf("error checking currency: %v", err) - return false - } - - if count > 0 { - return true - } - - return false -} - -func RewardsHistoricalData(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - q := r.URL.Query() - - validatorIndexArr, _, redirect, err := handleValidatorsQuery(w, r, true) - if err != nil || redirect { - return - } - - currency := q.Get("currency") - - // Set the default start and end time to the first day - t := time.Unix(int64(utils.Config.Chain.GenesisTimestamp), 0) - startGenesisDay := uint64(time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC).Unix()) - var start uint64 = startGenesisDay - var end uint64 = startGenesisDay - - dateRange := strings.Split(q.Get("days"), "-") - if len(dateRange) == 2 { - start, err = strconv.ParseUint(dateRange[0], 10, 32) //Limit to uint32 for postgres - if err != nil || start < startGenesisDay { - logger.Warnf("error parsing days range: %v", err) - http.Error(w, "Error: Invalid parameter days.", http.StatusBadRequest) - return - } - end, err = strconv.ParseUint(dateRange[1], 10, 32) //Limit to uint32 for postgres - if err != nil || end < startGenesisDay { - logger.Warnf("error parsing days range: %v", err) - http.Error(w, "Error: Invalid parameter days.", http.StatusBadRequest) - return - } - } - - data := services.GetValidatorHist(validatorIndexArr, currency, start, end) - - err = json.NewEncoder(w).Encode(data) - if err != nil { - logger.WithError(err).WithField("route", r.URL.String()).Error("error encoding json response") - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - -} - -func DownloadRewardsHistoricalData(w http.ResponseWriter, r *http.Request) { - q := r.URL.Query() - - validatorIndexArr, _, redirect, err := handleValidatorsQuery(w, r, true) - if err != nil || redirect { - return - } - - currency := q.Get("currency") - - // Set the default start and end time to the first day - t := time.Unix(int64(utils.Config.Chain.GenesisTimestamp), 0) - startGenesisDay := uint64(time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC).Unix()) - var start uint64 = startGenesisDay - var end uint64 = startGenesisDay - - dateRange := strings.Split(q.Get("days"), "-") - if len(dateRange) == 2 { - start, err = strconv.ParseUint(dateRange[0], 10, 32) //Limit to uint32 for postgres - if err != nil || start < startGenesisDay { - logger.Warnf("error parsing days range: %v", err) - http.Error(w, "Error: Invalid parameter days.", http.StatusBadRequest) - return - } - end, err = strconv.ParseUint(dateRange[1], 10, 32) //Limit to uint32 for postgres - if err != nil || end < startGenesisDay { - logger.Warnf("error parsing days range: %v", err) - http.Error(w, "Error: Invalid parameter days.", http.StatusBadRequest) - return - } - } - - hist := services.GetValidatorHist(validatorIndexArr, currency, start, end) - - if len(hist.History) == 0 { - w.Write([]byte("No data available")) - return - } - - s := time.Unix(int64(start), 0) - e := time.Unix(int64(end), 0) - - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=income_history_%v_%v.pdf", s.Format("20060102"), e.Format("20060102"))) - w.Header().Set("Content-Type", "text/csv") - - _, err = w.Write(services.GeneratePdfReport(hist, currency)) - if err != nil { - logger.WithError(err).WithField("route", r.URL.String()).Error("error writing response") - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - -} - -func RewardNotificationSubscribe(w http.ResponseWriter, r *http.Request) { - SetAutoContentType(w, r) - user := getUser(r) - if !user.Authenticated { - http.Error(w, "User Not Authenticated", http.StatusUnauthorized) - return - } - q := r.URL.Query() - - validatorArr := q.Get("validators") - currency := q.Get("currency") - validatorLimit := getUserPremium(r).MaxValidators - - errFields := map[string]interface{}{ - "route": r.URL.String(), - "user_id": user.UserID, - "validators_query": validatorArr, - "currency": currency, - "validator_limit": validatorLimit, - } - - var count uint64 - err := db.FrontendWriterDB.Get(&count, - `select count(event_name) - from users_subscriptions - where user_id=$1 AND event_name=$2;`, user.UserID, strings.ToLower(utils.GetNetwork())+":"+string(types.TaxReportEventName)) - if err != nil { - utils.LogError(err, "Failed to get User Subscriptions Count", 0, errFields) - http.Error(w, "Internal Server Error: failed to get user subscriptions count", http.StatusInternalServerError) - return - } - - if count >= USER_SUBSCRIPTION_LIMIT { - http.Error(w, "Conflicting Request: user subscription limit reached", http.StatusConflict) - return - } - - // don't allow passing validator pubkeys in the query string - _, queryValidatorPubkeys, err := parseValidatorsFromQueryString(validatorArr, validatorLimit) - if err != nil || len(queryValidatorPubkeys) > 0 { - http.Error(w, "Bad Request: validators could not be parsed or should be specified using Indices", http.StatusBadRequest) - return - } - - if validatorArr == "" || !isValidCurrency(currency) { - http.Error(w, "Bad Request: no validators or invalid currency given", http.StatusBadRequest) - return - } - - err = db.AddSubscription(user.UserID, - utils.Config.Chain.ClConfig.ConfigName, - types.TaxReportEventName, - fmt.Sprintf("validators=%s&days=30¤cy=%s", validatorArr, currency), 0) - - if err != nil { - utils.LogError(err, "Failed to add entry to user subscriptions", 0, errFields) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - return - } - - err = json.NewEncoder(w).Encode(struct { - Msg string `json:"msg"` - }{Msg: "Subscription Updated"}) - - if err != nil { - utils.LogError(err, "error encoding json response", 0, errFields) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - return - } - -} - -func RewardNotificationUnsubscribe(w http.ResponseWriter, r *http.Request) { - SetAutoContentType(w, r) - user := getUser(r) - if !user.Authenticated { - http.Error(w, "User Not Authenticated", http.StatusUnauthorized) - return - } - - q := r.URL.Query() - - validatorArr := q.Get("validators") - currency := q.Get("currency") - validatorLimit := getUserPremium(r).MaxValidators - - errFields := map[string]interface{}{ - "route": r.URL.String(), - "user_id": user.UserID, - "validators_query": validatorArr, - "currency": currency, - "validator_limit": validatorLimit, - } - - // don't allow passing validator pubkeys in the query string - _, queryValidatorPubkeys, err := parseValidatorsFromQueryString(validatorArr, validatorLimit) - if err != nil || len(queryValidatorPubkeys) > 0 { - http.Error(w, "Bad Request: validators could not be parsed or should be specified using Indices", http.StatusBadRequest) - return - } - - if validatorArr == "" || !isValidCurrency(currency) { - http.Error(w, "Bad Request: no validators or invalid currency given", http.StatusBadRequest) - return - } - - err = db.DeleteSubscription(user.UserID, - utils.GetNetwork(), - types.TaxReportEventName, - fmt.Sprintf("validators=%s&days=30¤cy=%s", validatorArr, currency)) - - if err != nil { - utils.LogError(err, "Failed to delete entry from user subscriptions", 0, errFields) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - err = json.NewEncoder(w).Encode(struct { - Msg string `json:"msg"` - }{Msg: "Subscription Deleted"}) - - if err != nil { - utils.LogError(err, "error encoding json response", 0, errFields) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } -} - -func RewardGetUserSubscriptions(w http.ResponseWriter, r *http.Request) { - SetAutoContentType(w, r) - user := getUser(r) - if !user.Authenticated { - logger.WithField("route", r.URL.String()).Error("User not Authenticated") - http.Error(w, "Internal server error, User Not Authenticated", http.StatusUnauthorized) - return - } - - var count uint64 - err := db.FrontendWriterDB.Get(&count, - `select count(event_name) - from users_subscriptions - where user_id=$1 AND event_name=$2;`, user.UserID, strings.ToLower(utils.GetNetwork())+":"+string(types.TaxReportEventName)) - - if err != nil { - logger.WithField("route", r.URL.String()).Error("Failed to get User Subscriptions Count") - http.Error(w, "Internal server error, Failed to get User Subscriptions Count", http.StatusInternalServerError) - return - } - - data := getUserRewardSubscriptions(user.UserID) - - err = json.NewEncoder(w).Encode(struct { - Data [][]string `json:"data"` - Count uint64 `json:"count"` - }{Data: data, Count: count}) - - if err != nil { - logger.WithError(err).WithField("route", r.URL.String()).Error("error encoding json response") - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } -} diff --git a/handlers/validators_slashings.go b/handlers/validators_slashings.go index 54d10c30a2..6c810e6e49 100644 --- a/handlers/validators_slashings.go +++ b/handlers/validators_slashings.go @@ -105,7 +105,7 @@ func ValidatorsSlashingsData(w http.ResponseWriter, r *http.Request) { for _, slashing := range slashings { validatorsForNameSearch = append(validatorsForNameSearch, slashing.Proposer) if slashing.Type == "Attestation Violation" { - inter := intersect.Simple(slashing.Attestestation1Indices, slashing.Attestestation2Indices) + inter := intersect.Hash(slashing.Attestestation1Indices, slashing.Attestestation2Indices) if len(inter) == 0 { logger.Warningf("No intersection found for attestation violation, proposer: %v, slot: %v", slashing.Proposer, slashing.Slot) } @@ -130,7 +130,7 @@ func ValidatorsSlashingsData(w http.ResponseWriter, r *http.Request) { slashedValidators := []uint64{} if row.Type == "Attestation Violation" { - inter := intersect.Simple(row.Attestestation1Indices, row.Attestestation2Indices) + inter := intersect.Hash(row.Attestestation1Indices, row.Attestestation2Indices) if len(inter) == 0 { logger.Warningf("No intersection found for attestation violation, proposer: %v, slot: %v", row.Proposer, row.Slot) } diff --git a/handlers/withdrawals.go b/handlers/withdrawals.go index 791ad0e270..c7e8a121b8 100644 --- a/handlers/withdrawals.go +++ b/handlers/withdrawals.go @@ -200,7 +200,7 @@ func WithdrawalsTableData(draw uint64, search string, length, start uint64, orde tableData[i] = []interface{}{ utils.FormatEpoch(utils.EpochOfSlot(w.Slot)), utils.FormatBlockSlot(w.Slot), - template.HTML(fmt.Sprintf("%v", w.Index)), + template.HTML(fmt.Sprintf("%v", w.Index)), // show negative index as is utils.FormatValidator(w.ValidatorIndex), utils.FormatTimestamp(utils.SlotToTime(w.Slot).Unix()), utils.FormatAddressWithLimits(w.Address, names[string(w.Address)], false, "address", visibleDigitsForHash+5, 18, true), diff --git a/hooks/commit-msg b/hooks/commit-msg deleted file mode 100755 index c2b68febe9..0000000000 --- a/hooks/commit-msg +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -if ! grep -iqE "(\(BIDS-[1-9][0-9]*|NOBIDS)\)" "$1"; then - echo "Aborting commit! You must use (BIDS-xxx), or (NOBIDS) if no JIRA Issue available, anywhere in your commit message." >&2 - exit 1 -fi diff --git a/hooks/prepare-commit-msg b/hooks/prepare-commit-msg deleted file mode 100755 index 77f94db404..0000000000 --- a/hooks/prepare-commit-msg +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python2 - -import sys, os, re -from subprocess import check_output - -# Collect the parameters -commit_msg_filepath = sys.argv[1] -if len(sys.argv) > 2: - commit_type = sys.argv[2] -else: - commit_type = '' -if len(sys.argv) > 3: - commit_hash = sys.argv[3] -else: - commit_hash = '' - -print "prepare-commit-msg: File: %s\nType: %s\nHash: %s" % (commit_msg_filepath, commit_type, commit_hash) - -# Figure out which branch we're on -try: - branch = check_output(['git', 'symbolic-ref', '--short', 'HEAD']).strip() -except: - print "prepare-commit-msg: No head found, exiting." - sys.exit(0) -print "prepare-commit-msg: On branch '%s'" % branch - -# Populate the commit message with the issue #, if there is one -if branch.startswith('BIDS-'): - print "prepare-commit-msg: Oh hey, it's an issue branch." - result = re.match('BIDS-(.*)/', branch) - issue_number = result.group(1) - - with open(commit_msg_filepath, 'r+') as f: - content = f.read() - if not content.startswith('(BIDS-%s) ' % (issue_number)): - f.seek(0, 0) - f.write("(BIDS-%s) %s" % (issue_number, content)) -else: - with open(commit_msg_filepath, 'r+') as f: - content = f.read() - if not content.startswith('(NOBIDS) '): - f.seek(0, 0) - f.write("(NOBIDS) %s" % (content)) \ No newline at end of file diff --git a/rpc/erigon.go b/rpc/erigon.go index 6513d0b1f4..aca850006a 100644 --- a/rpc/erigon.go +++ b/rpc/erigon.go @@ -78,6 +78,7 @@ func NewErigonClient(endpoint string) (*ErigonClient, error) { Transport: db2.NewWithFallback(roundTripper, http.DefaultTransport), })) client.rawStore = rawStore + logger.Infof("using bigtable %s/%s for erigon client", project, instance) } } @@ -144,7 +145,7 @@ func (client *ErigonClient) GetBlock(number int64, traceMode string) (*types.Eth g.Go(func() error { b, err := client.ethClient.BlockByNumber(ctx, big.NewInt(number)) if err != nil { - return err + return fmt.Errorf("error getting block %v details from client, error: %s", number, err) } mu.Lock() timings.Headers = time.Since(start) @@ -345,9 +346,11 @@ func (client *ErigonClient) GetBlock(number int64, traceMode string) (*types.Eth } func (client *ErigonClient) GetBlocks(start, end int64, traceMode string) ([]*types.Eth1Block, error) { - _, err := client.rawStore.ReadBlocksByNumber(client.chainID.Uint64(), start, end) - if err != nil { - return nil, err + if client.rawStore != nil { + _, err := client.rawStore.ReadBlocksByNumber(client.chainID.Uint64(), start, end) + if err != nil { + return nil, err + } } blocks := make([]*types.Eth1Block, end-start+1) for i := start; i <= end; i++ { @@ -371,7 +374,7 @@ func (client *ErigonClient) GetBlockNumberByHash(hash string) (uint64, error) { block, err := client.ethClient.BlockByHash(ctx, common.HexToHash(hash)) if err != nil { - return 0, err + return 0, fmt.Errorf("error getting block by hash %s, error: %s", hash, err) } return block.NumberU64(), nil } @@ -436,11 +439,15 @@ func (client *ErigonClient) TraceGeth(blockNumber *big.Int) ([]*GethTraceCallRes err := client.rpcClient.Call(&res, "debug_traceBlockByNumber", hexutil.EncodeBig(blockNumber), gethTracerArg) if err != nil { - return nil, err + return nil, fmt.Errorf("error while calling geth for block %v traces, error: %s", blockNumber.Uint64(), err) } data := make([]*GethTraceCallResult, 0, 20) for i, r := range res { + if r.Result == nil { + return nil, fmt.Errorf("trace call %v result is nil for block %v", i, blockNumber.Uint64()) + } + r.Result.TransactionPosition = i extractCalls(r.Result, &data) } @@ -510,7 +517,7 @@ func (client *ErigonClient) TraceParity(blockNumber uint64) ([]*ParityTraceResul err := client.rpcClient.Call(&res, "trace_block", blockNumber) if err != nil { - return nil, err + return nil, fmt.Errorf("error while calling parity for block %v traces, error: %s", blockNumber, err) } return res, nil @@ -521,12 +528,26 @@ func (client *ErigonClient) TraceParityTx(txHash string) ([]*ParityTraceResult, err := client.rpcClient.Call(&res, "trace_transaction", txHash) if err != nil { - return nil, err + return nil, fmt.Errorf("error while calling parity for tx %s traces, error: %s", txHash, err) } return res, nil } +func (client *ErigonClient) TraceGethTx(txHash string) ([]*GethTraceCallResult, error) { + var res *GethTraceCallResult + + err := client.rpcClient.Call(&res, "debug_traceTransaction", txHash, gethTracerArg) + if err != nil { + return nil, fmt.Errorf("error while calling geth for tx %s traces, error: %s", txHash, err) + } + + data := make([]*GethTraceCallResult, 0, 20) + extractCalls(res, &data) + + return data, nil +} + func (client *ErigonClient) GetBalances(pairs []*types.Eth1AddressBalance, addressIndex, tokenIndex int) ([]*types.Eth1AddressBalance, error) { startTime := time.Now() defer func() { @@ -1065,7 +1086,7 @@ func (client *ErigonClient) processBlockResult(block BlockResponseWithUncles) *t for _, tx := range txs { var from []byte - sender, err := geth_types.Sender(geth_types.NewCancunSigner(tx.ChainId()), tx) + sender, err := geth_types.Sender(geth_types.NewPragueSigner(tx.ChainId()), tx) if err != nil { from, _ = hex.DecodeString("abababababababababababababababababababab") logrus.Errorf("error converting tx %v to msg: %v", tx.Hash(), err) diff --git a/rpc/geth.go b/rpc/geth.go index 0a43234525..774cd304b2 100644 --- a/rpc/geth.go +++ b/rpc/geth.go @@ -155,7 +155,7 @@ func (client *GethClient) GetBlock(number int64) (*types.Eth1Block, *types.GetBl for _, tx := range txs { var from []byte - sender, err := geth_types.Sender(geth_types.NewCancunSigner(tx.ChainId()), tx) + sender, err := geth_types.Sender(geth_types.NewPragueSigner(tx.ChainId()), tx) if err != nil { from, _ = hex.DecodeString("abababababababababababababababababababab") logrus.Errorf("error converting tx %v to msg: %v", tx.Hash(), err) diff --git a/rpc/interfaces.go b/rpc/interfaces.go index de5c6d68a0..499677e569 100644 --- a/rpc/interfaces.go +++ b/rpc/interfaces.go @@ -21,6 +21,7 @@ type Client interface { GetBalancesForEpoch(epoch int64) (map[uint64]uint64, error) GetValidatorState(epoch uint64) (*StandardValidatorsResponse, error) GetBlockHeader(slot uint64) (*StandardBeaconHeaderResponse, error) + GetPendingDeposits() (*StandardBeaconPendingDepositsResponse, error) } type Eth1Client interface { diff --git a/rpc/lighthouse.go b/rpc/lighthouse.go index 313b45786d..76f03d93c3 100644 --- a/rpc/lighthouse.go +++ b/rpc/lighthouse.go @@ -19,6 +19,7 @@ import ( "github.com/gobitfly/eth2-beaconchain-explorer/utils" "github.com/donovanhide/eventsource" + "github.com/ethereum/go-ethereum/common/hexutil" gtypes "github.com/ethereum/go-ethereum/core/types" "golang.org/x/sync/errgroup" @@ -41,7 +42,7 @@ type LighthouseClient struct { // NewLighthouseClient is used to create a new Lighthouse client func NewLighthouseClient(endpoint string, chainID *big.Int) (*LighthouseClient, error) { - signer := gtypes.NewCancunSigner(chainID) + signer := gtypes.NewPragueSigner(chainID) client := &LighthouseClient{ endpoint: endpoint, assignmentsCacheMux: &sync.Mutex{}, @@ -100,6 +101,22 @@ func (lc *LighthouseClient) GetNewBlockChan() chan *types.Block { return blkCh } +// /eth/v1/beacon/states/%v/pending_deposits +func (lc *LighthouseClient) GetPendingDeposits() (*StandardBeaconPendingDepositsResponse, error) { + headResp, err := lc.get(fmt.Sprintf("%s/eth/v1/beacon/states/head/pending_deposits", lc.endpoint)) + if err != nil { + return nil, fmt.Errorf("error retrieving pending deposits: %w", err) + } + + var parsedHead StandardBeaconPendingDepositsResponse + err = json.Unmarshal(headResp, &parsedHead) + if err != nil { + return nil, fmt.Errorf("error parsing pending deposits: %w", err) + } + + return &parsedHead, nil +} + // GetChainHead gets the chain head from Lighthouse func (lc *LighthouseClient) GetChainHead() (*types.ChainHead, error) { headResp, err := lc.get(fmt.Sprintf("%s/eth/v1/beacon/headers/head", lc.endpoint)) @@ -169,12 +186,17 @@ func (lc *LighthouseClient) GetValidatorQueue() (*types.ValidatorQueue, error) { // TODO: maybe track more status counts in the future? statusMap := make(map[string]uint64) + exitingBalance := uint64(0) for _, validator := range parsedValidators.Data { statusMap[validator.Status] += 1 + if validator.Status == "active_exiting" || validator.Status == "active_slashed" { + exitingBalance += uint64(validator.Validator.EffectiveBalance) + } } return &types.ValidatorQueue{ - Activating: statusMap["pending_queued"], - Exiting: statusMap["active_exiting"] + statusMap["active_slashed"], + Activating: statusMap["pending_queued"], + Exiting: statusMap["active_exiting"] + statusMap["active_slashed"], + ExitingBalance: exitingBalance, }, nil } @@ -1007,7 +1029,7 @@ func (lc *LighthouseClient) blockFromResponse(parsedHeaders *StandardBeaconHeade withdrawals := make([]*types.Withdrawals, 0, len(payload.Withdrawals)) for _, w := range payload.Withdrawals { withdrawals = append(withdrawals, &types.Withdrawals{ - Index: uint64(w.Index), + Index: int64(w.Index), ValidatorIndex: uint64(w.ValidatorIndex), Address: w.Address, Amount: uint64(w.Amount), @@ -1374,6 +1396,20 @@ type StandardBeaconHeaderResponse struct { Finalized bool `json:"finalized"` } +type StandardBeaconPendingDepositsResponse struct { + ExecutionOptimistic bool `json:"execution_optimistic"` + Finalized bool `json:"finalized"` + Data []StandardBeaconPendingDepositsData `json:"data"` +} + +type StandardBeaconPendingDepositsData struct { + Pubkey hexutil.Bytes `json:"pubkey"` + WithdrawalCredentials hexutil.Bytes `json:"withdrawal_credentials"` + Amount uint64 `json:"amount,string"` + Signature hexutil.Bytes `json:"signature"` + Slot uint64 `json:"slot,string"` +} + type StandardBeaconHeadersResponse struct { Data []struct { Root string `json:"root"` diff --git a/services/notifications.go b/services/notifications.go index 741bd53f56..19029a31c9 100644 --- a/services/notifications.go +++ b/services/notifications.go @@ -375,13 +375,6 @@ func collectUserDbNotifications(epoch uint64) (map[uint64]map[types.EventName][] return nil, fmt.Errorf("error collecting Eth client notifications: %v", err) } - //Tax Report - err = collectTaxReportNotificationNotifications(notificationsByUserID, types.TaxReportEventName) - if err != nil { - metrics.Errors.WithLabelValues("notifications_collect_tax_report").Inc() - return nil, fmt.Errorf("error collecting tax report notifications: %v", err) - } - return notificationsByUserID, nil } @@ -2100,7 +2093,7 @@ func (n *ethClientNotification) GetInfo(includeUrl bool) string { case "Erigon": url = "https://github.com/erigontech/erigon/releases" case "Rocketpool": - url = "https://github.com/rocket-pool/smartnode-install/releases" + url = "https://github.com/rocket-pool/smartnode/releases" case "MEV-Boost": url = "https://github.com/flashbots/mev-boost/releases" case "Lodestar": @@ -2140,7 +2133,7 @@ func (n *ethClientNotification) GetInfoMarkdown() string { case "Erigon": url = "https://github.com/erigontech/erigon/releases" case "Rocketpool": - url = "https://github.com/rocket-pool/smartnode-install/releases" + url = "https://github.com/rocket-pool/smartnode/releases" case "MEV-Boost": url = "https://github.com/flashbots/mev-boost/releases" case "Lodestar": @@ -2488,146 +2481,6 @@ func (n *monitorMachineNotification) GetInfoMarkdown() string { return n.GetInfo(false) } -type taxReportNotification struct { - SubscriptionID uint64 - UserID uint64 - Epoch uint64 - EventFilter string - UnsubscribeHash sql.NullString -} - -func (n *taxReportNotification) GetLatestState() string { - return "" -} - -func (n *taxReportNotification) GetUnsubscribeHash() string { - if n.UnsubscribeHash.Valid { - return n.UnsubscribeHash.String - } - return "" -} - -func (n *taxReportNotification) GetEmailAttachment() *types.EmailAttachment { - tNow := time.Now() - lastDay := time.Date(tNow.Year(), tNow.Month(), 1, 0, 0, 0, 0, time.UTC) - firstDay := lastDay.AddDate(0, -1, 0) - - q, err := url.ParseQuery(n.EventFilter) - - if err != nil { - logger.Warn("Failed to parse rewards report eventfilter") - return nil - } - - currency := q.Get("currency") - - validators := []uint64{} - valSlice := strings.Split(q.Get("validators"), ",") - if len(valSlice) > 0 { - for _, val := range valSlice { - v, err := strconv.ParseUint(val, 10, 64) - if err != nil { - continue - } - validators = append(validators, v) - } - } else { - logger.Warn("Validators Not found in rewards report eventfilter") - return nil - } - - pdf := GetPdfReport(validators, currency, uint64(firstDay.Unix()), uint64(lastDay.Unix())) - - return &types.EmailAttachment{Attachment: pdf, Name: fmt.Sprintf("income_history_%v_%v.pdf", firstDay.Format("20060102"), lastDay.Format("20060102"))} -} - -func (n *taxReportNotification) GetSubscriptionID() uint64 { - return n.SubscriptionID -} - -func (n *taxReportNotification) GetEpoch() uint64 { - return n.Epoch -} - -func (n *taxReportNotification) GetEventName() types.EventName { - return types.TaxReportEventName -} - -func (n *taxReportNotification) GetInfo(includeUrl bool) string { - generalPart := `Please find attached the income history of your selected validators.` - return generalPart -} - -func (n *taxReportNotification) GetTitle() string { - return "Income Report" -} - -func (n *taxReportNotification) GetEventFilter() string { - return n.EventFilter -} - -func (n *taxReportNotification) GetInfoMarkdown() string { - return n.GetInfo(false) -} - -func collectTaxReportNotificationNotifications(notificationsByUserID map[uint64]map[types.EventName][]types.Notification, eventName types.EventName) error { - lastStatsDay, err := LatestExportedStatisticDay() - - if err != nil { - return err - } - //Check that the last day of the month is already exported - tNow := time.Now() - firstDayOfMonth := time.Date(tNow.Year(), tNow.Month(), 1, 0, 0, 0, 0, time.UTC) - if utils.TimeToDay(uint64(firstDayOfMonth.Unix())) > lastStatsDay { - return nil - } - - var dbResult []struct { - SubscriptionID uint64 `db:"id"` - UserID uint64 `db:"user_id"` - Epoch uint64 `db:"created_epoch"` - EventFilter string `db:"event_filter"` - UnsubscribeHash sql.NullString `db:"unsubscribe_hash"` - } - - name := string(eventName) - if utils.Config.Chain.ClConfig.ConfigName != "" { - name = utils.Config.Chain.ClConfig.ConfigName + ":" + name - } - - err = db.FrontendWriterDB.Select(&dbResult, ` - SELECT us.id, us.user_id, us.created_epoch, us.event_filter, ENCODE(us.unsubscribe_hash, 'hex') AS unsubscribe_hash - FROM users_subscriptions AS us - WHERE us.event_name=$1 AND (us.last_sent_ts < $2 OR (us.last_sent_ts IS NULL AND us.created_ts < $2)); - `, - name, firstDayOfMonth) - - if err != nil { - return err - } - - for _, r := range dbResult { - n := &taxReportNotification{ - SubscriptionID: r.SubscriptionID, - UserID: r.UserID, - Epoch: r.Epoch, - EventFilter: r.EventFilter, - UnsubscribeHash: r.UnsubscribeHash, - } - if _, exists := notificationsByUserID[r.UserID]; !exists { - notificationsByUserID[r.UserID] = map[types.EventName][]types.Notification{} - } - if _, exists := notificationsByUserID[r.UserID][n.GetEventName()]; !exists { - notificationsByUserID[r.UserID][n.GetEventName()] = []types.Notification{} - } - notificationsByUserID[r.UserID][n.GetEventName()] = append(notificationsByUserID[r.UserID][n.GetEventName()], n) - metrics.NotificationsCollected.WithLabelValues(string(n.GetEventName())).Inc() - } - - return nil -} - type networkNotification struct { SubscriptionID uint64 UserID uint64 diff --git a/services/queue.go b/services/queue.go new file mode 100644 index 0000000000..171ded64d4 --- /dev/null +++ b/services/queue.go @@ -0,0 +1,138 @@ +// Copyright (C) 2025 Bitfly GmbH +// +// This file is part of Beaconchain Dashboard. +// +// Beaconchain Dashboard is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Beaconchain Dashboard is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Beaconchain Dashboard. If not, see . + +package services + +import ( + "fmt" + "sync" + "time" + + "github.com/gobitfly/eth2-beaconchain-explorer/cache" + "github.com/gobitfly/eth2-beaconchain-explorer/db" + "github.com/gobitfly/eth2-beaconchain-explorer/rpc" + "github.com/gobitfly/eth2-beaconchain-explorer/types" + "github.com/gobitfly/eth2-beaconchain-explorer/utils" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func queueEstimateUpdater(wg *sync.WaitGroup) { + firstRun := true + + for { + data, err := getQueuesEstimate() + if err != nil { + logger.Warnf("error retrieving queue data: %v", err) + time.Sleep(time.Minute) + continue + } + + err = cache.TieredCache.Set(getQueueCacheKey(), data, utils.Day) + if err != nil { + logger.Errorf("error caching queue data: %v", err) + } + + if firstRun { + logrus.Info("initialized queue updater") + wg.Done() + firstRun = false + } + + ReportStatus("queueEstimateUpdater", "Running", nil) + time.Sleep(1 * time.Minute) + } +} + +func LatestQueueData() *types.QueuesEstimate { + wanted := &types.QueuesEstimate{} + if wanted, err := cache.TieredCache.GetWithLocalTimeout(getQueueCacheKey(), time.Minute, wanted); err == nil { + return wanted.(*types.QueuesEstimate) + } else { + logger.Errorf("error retrieving mempool data from cache: %v", err) + } + return wanted +} + +func getQueueCacheKey() string { + return fmt.Sprintf("%d:frontend:queues", utils.Config.Chain.ClConfig.DepositChainID) +} + +func getQueuesEstimate() (*types.QueuesEstimate, error) { + queue, err := rpc.CurrentClient.GetValidatorQueue() + if err != nil { + return nil, errors.Wrap(err, "failed to get validator state") + } + + type Result struct { + EstClearEpoch uint64 `db:"est_clear_epoch"` + QueuedBalanceAhead uint64 `db:"queued_balance_ahead"` + TopUpAmount uint64 `db:"topup_amount"` + TopUpCount uint64 `db:"topup_count"` + EnteringNewValidators uint64 `db:"entering_new_validators"` + TotalEffectiveBalance uint64 `db:"eligibleether"` + } + var result Result + + err = db.WriterDb.Get(&result, + ` + WITH + last_deposit AS ( + SELECT * + FROM pending_deposits_queue + WHERE id = (SELECT max(id) FROM pending_deposits_queue) + ), + deduplicated AS ( + SELECT DISTINCT pubkey, validator_index + FROM pending_deposits_queue + ) + SELECT + COALESCE((SELECT est_clear_epoch FROM last_deposit),0) AS est_clear_epoch, + COALESCE((SELECT queued_balance_ahead FROM last_deposit),0) AS queued_balance_ahead, + (SELECT COALESCE(count(*),0) FROM deduplicated WHERE validator_index IS NULL) AS entering_new_validators, + (SELECT COALESCE(count(*),0) FROM deduplicated WHERE validator_index IS NOT NULL) AS topup_count, + (SELECT COALESCE(sum(amount),0) FROM pending_deposits_queue WHERE validator_index IS NOT NULL) AS topup_amount, + (SELECT eligibleether FROM epochs WHERE epoch = (SELECT max(epoch) FROM epochs)) + + `) + if err != nil { + logger.Errorf("error getting pending queue data: %v", err) + } + + depositQueueTime := time.Until(utils.EpochToTime(result.EstClearEpoch)) + etherChurnByEpoch := utils.GetActivationExitChurnLimit(result.TotalEffectiveBalance) + if etherChurnByEpoch == 0 { + return nil, errors.New("etherChurnByEpoch is 0") + } + + etherChurnByDay := etherChurnByEpoch * utils.EpochsPerDay() + re := &types.QueuesEstimate{ + EnteringNewValidatorsCount: result.EnteringNewValidators, + EnteringNewValidatorsEthAmount: max(result.QueuedBalanceAhead-result.TopUpAmount, 0), + EnteringTopUpEthAmount: result.TopUpAmount, + EnteringTotalEthAmount: result.QueuedBalanceAhead, + EnteringQueueTime: depositQueueTime, + EnteringTopUpCount: result.TopUpCount, + TotalActiveEffectiveBalance: result.TotalEffectiveBalance, + LeavingValidatorCount: queue.Exiting, + LeavingEthAmount: queue.ExitingBalance, + EnteringBalancePerDay: etherChurnByDay, + EnteringBalancePerEpoch: etherChurnByEpoch, + } + + return re, nil +} diff --git a/services/rewards.go b/services/rewards.go deleted file mode 100644 index 62708a9e94..0000000000 --- a/services/rewards.go +++ /dev/null @@ -1,365 +0,0 @@ -package services - -import ( - "bytes" - "fmt" - "sort" - "strings" - "time" - - "github.com/gobitfly/eth2-beaconchain-explorer/db" - "github.com/gobitfly/eth2-beaconchain-explorer/types" - "github.com/gobitfly/eth2-beaconchain-explorer/utils" - - "github.com/jung-kurt/gofpdf" - "github.com/lib/pq" - "golang.org/x/text/language" - "golang.org/x/text/message" -) - -type rewardHistory struct { - History [][]string `json:"history"` - TotalETH string `json:"total_eth"` - TotalCurrency string `json:"total_currency"` - Validators []uint64 `json:"validators"` -} - -func GetValidatorHist(validatorArr []uint64, currency string, start uint64, end uint64) rewardHistory { - var err error - - var pricesDb []types.Price - // we get prices with a 1 day buffer to so we have no problems in different time zones - var oneDay = uint64(24 * 60 * 60) - - if start == end { // no date range was provided, use the current day as ending boundary - end = uint64(time.Now().Unix()) - } - err = db.WriterDb.Select(&pricesDb, - `select ts, eur, usd, gbp, cad, jpy, cny, rub, aud from price where ts >= TO_TIMESTAMP($1) and ts <= TO_TIMESTAMP($2) order by ts desc`, start-oneDay, end+oneDay) - if err != nil { - logger.Errorf("error getting prices: %v", err) - } - - lowerBound := utils.TimeToDay(start) - upperBound := utils.TimeToDay(end) - - // As the genesis timestamp is in the middle of the day and we get timestamps from the ui from the start of the day we add one to get the correct day, - // except for the beaconchain day where we get a timestamp lower then the genesis day. The TimeToDay function still would transform it to 0 (and not -1) so we don't need to add one. - if start > utils.Config.Chain.GenesisTimestamp { - lowerBound++ - } - - income, err := db.GetValidatorIncomeHistory(validatorArr, lowerBound, upperBound, LatestFinalizedEpoch()) - if err != nil { - logger.Errorf("error getting income history for validator hist: %v", err) - } - - prices := map[string]float64{} - for _, item := range pricesDb { - date := fmt.Sprintf("%v", item.TS) - date = strings.Split(date, " ")[0] - switch currency { - case "eur": - prices[date] = item.EUR - case "usd": - prices[date] = item.USD - case "gbp": - prices[date] = item.GBP - case "cad": - prices[date] = item.CAD - case "cny": - prices[date] = item.CNY - case "jpy": - prices[date] = item.JPY - case "rub": - prices[date] = item.RUB - case "aud": - prices[date] = item.AUD - default: - prices[date] = item.USD - currency = "usd" - } - } - - data := make([][]string, len(income)) - tETH := 0.0 - tCur := 0.0 - - for i, item := range income { - key := fmt.Sprintf("%v", utils.DayToTime(item.Day)) - key = strings.Split(key, " ")[0] - iETH := float64(item.ClRewards) / 1e9 - tETH += iETH - iCur := iETH * prices[key] - tCur += iCur - data[i] = []string{ - key, - addCommas(float64(item.EndBalance.Int64)/1e9, "%.5f"), // end of day balance - addCommas(iETH, "%.5f"), // income of day ETH - fmt.Sprintf("%s %s", strings.ToUpper(currency), addCommas(prices[key], "%.2f")), //price will default to 0 if key does not exist - fmt.Sprintf("%s %s", strings.ToUpper(currency), addCommas(iCur, "%.2f")), // income of day Currency - } - } - - return rewardHistory{ - History: data, - TotalETH: addCommas(tETH, "%.5f"), - TotalCurrency: fmt.Sprintf("%s %s", strings.ToUpper(currency), addCommas(tCur, "%.2f")), - Validators: validatorArr, - } -} - -func addCommas(balance float64, decimals string) string { - p := message.NewPrinter(language.English) - rb := []rune(p.Sprintf(decimals, balance)) - // remove trailing zeros - if rb[len(rb)-2] == '.' || rb[len(rb)-3] == '.' { - for rb[len(rb)-1] == '0' { - rb = rb[:len(rb)-1] - } - if rb[len(rb)-1] == '.' { - rb = rb[:len(rb)-1] - } - } - - return string(rb) -} - -func GeneratePdfReport(hist rewardHistory, currency string) []byte { - - data := hist.History - - if !(len(data) > 0) { - logger.Warn("Can't generate PDF for Empty Slice") - return []byte{} - } - - sort.Slice(data, func(p, q int) bool { - i, err := time.Parse("2006-01-02", data[p][0]) - if err != nil { - return false - } - - i2, err := time.Parse("2006-01-02", data[q][0]) - if err != nil { - return false - } - return i2.Before(i) - }) - - validators := hist.Validators - - pdf := gofpdf.New("P", "mm", "A4", "") - pdf.SetTopMargin(15) - pdf.SetHeaderFuncMode(func() { - pdf.SetY(5) - pdf.SetFont("Arial", "B", 12) - pdf.Cell(80, 0, "") - pdf.CellFormat(30, 10, fmt.Sprintf("Beaconcha.in Income History (%s - %s)", data[len(data)-1][0], data[0][0]), "", 0, "C", false, 0, "") - // pdf.Ln(-1) - }, true) - - pdf.AddPage() - pdf.SetFont("Times", "", 9) - - // generating the table - const ( - colCount = 5 - colWd = 40.0 - marginH = 5.0 - lineHt = 5.5 - maxHt = 5 - ) - - pdf.SetTextColor(24, 24, 24) - pdf.SetFillColor(255, 255, 255) - // pdf.Ln(-1) - pdf.CellFormat(0, maxHt, fmt.Sprintf("Income For Timeframe %s | %s", hist.TotalETH, hist.TotalCurrency), "", 0, "CM", true, 0, "") - - header := [colCount]string{"Date", "Balance", "Income", "ETH Value", fmt.Sprintf("Income (%v)", currency)} - - // pdf.SetMargins(marginH, marginH, marginH) - pdf.Ln(10) - pdf.SetTextColor(224, 224, 224) - pdf.SetFillColor(64, 64, 64) - pdf.Cell(-5, 0, "") - for col := 0; col < colCount; col++ { - pdf.CellFormat(colWd, maxHt, header[col], "1", 0, "CM", true, 0, "") - } - pdf.Ln(-1) - pdf.SetTextColor(24, 24, 24) - pdf.SetFillColor(255, 255, 255) - - // Rows - y := pdf.GetY() - - for i, row := range data { - pdf.SetTextColor(24, 24, 24) - pdf.SetFillColor(255, 255, 255) - x := marginH - if i%47 == 0 && i != 0 { - pdf.AddPage() - y = pdf.GetY() - } - for col := 0; col < colCount; col++ { - if i%2 != 0 { - pdf.SetFillColor(191, 191, 191) - } - pdf.Rect(x, y, colWd, maxHt, "D") - cellY := y - pdf.SetXY(x, cellY) - pdf.CellFormat(colWd, maxHt, row[col], "", 0, - "LM", true, 0, "") - cellY += lineHt - x += colWd - } - y += maxHt - } - - // adding a footer - pdf.AliasNbPages("") - pdf.SetFooterFunc(func() { - pdf.SetY(-15) - pdf.SetFont("Arial", "I", 8) - pdf.CellFormat(0, 10, fmt.Sprintf("Page %d/{nb}", pdf.PageNo()), - "", 0, "C", false, 0, "") - }) - - pdf.AddPage() - pdf.SetTextColor(24, 24, 24) - pdf.SetFillColor(255, 255, 255) - // pdf.Ln(10) - pdf.SetFont("Arial", "B", 12) - pdf.CellFormat(0, maxHt, "Validators", "", 0, "CM", true, 0, "") - pdf.Ln(10) - pdf.SetFont("Times", "", 9) - - const ( - vColCount = 4 - vColWd = 50.0 - ) - vHeader := [vColCount]string{"Index", "Activation Balance", "Balance", "Last Attestation"} - - // pdf.SetMargins(marginH, marginH, marginH) - // pdf.Ln(10) - pdf.SetTextColor(224, 224, 224) - pdf.SetFillColor(64, 64, 64) - pdf.Cell(-5, 0, "") - for col := 0; col < vColCount; col++ { - pdf.CellFormat(vColWd, maxHt, vHeader[col], "1", 0, "CM", true, 0, "") - } - pdf.Ln(-1) - pdf.SetTextColor(24, 24, 24) - pdf.SetFillColor(255, 255, 255) - - y = pdf.GetY() - - for i, row := range getValidatorDetails(validators) { - pdf.SetTextColor(24, 24, 24) - pdf.SetFillColor(255, 255, 255) - x := marginH - - if i%47 == 0 && i != 0 { - pdf.AddPage() - y = pdf.GetY() - } - - for col := 0; col < vColCount; col++ { - if i%2 != 0 { - pdf.SetFillColor(191, 191, 191) - } - pdf.Rect(x, y, vColWd, maxHt, "D") - cellY := y - pdf.SetXY(x, cellY) - pdf.CellFormat(vColWd, maxHt, row[col], "", 0, - "LM", true, 0, "") - cellY += lineHt - x += vColWd - } - y += maxHt - } - - // adding a footer - pdf.AliasNbPages("") - pdf.SetFooterFunc(func() { - pdf.SetY(-15) - pdf.SetFont("Arial", "I", 8) - pdf.CellFormat(0, 10, fmt.Sprintf("Page %d/{nb}", pdf.PageNo()), - "", 0, "C", false, 0, "") - }) - - buf := new(bytes.Buffer) - pdf.Output(buf) - - return buf.Bytes() - -} - -func GetPdfReport(validatorArr []uint64, currency string, start uint64, end uint64) []byte { - hist := GetValidatorHist(validatorArr, currency, start, end) - return GeneratePdfReport(hist, currency) -} - -func getValidatorDetails(validators []uint64) [][]string { - validatorFilter := pq.Array(validators) - var data []types.ValidatorPageData - err := db.WriterDb.Select(&data, - `SELECT validatorindex, balanceactivation - FROM validators - WHERE validatorindex = ANY($1) - ORDER BY validatorindex ASC`, validatorFilter) - if err != nil { - utils.LogError(err, "error getting validators data", 0, map[string]interface{}{"validators": validators}) - return [][]string{} - } - - latestEpoch := LatestEpoch() - balances, err := db.BigtableClient.GetValidatorBalanceHistory(validators, latestEpoch, latestEpoch) - if err != nil { - utils.LogError(err, "error getting validator balance history", 0, map[string]interface{}{ - "validators": validators, - "latestEpoch": latestEpoch, - }) - return [][]string{} - } - - lastAttestationSlots, err := db.BigtableClient.GetLastAttestationSlots(validators) - if err != nil { - utils.LogError(err, "error getting validator balance history", 0, map[string]interface{}{ - "validators": validators, - "latestEpoch": latestEpoch, - }) - return [][]string{} - } - - for i, validator := range data { - validator.LastAttestationSlot = lastAttestationSlots[validator.ValidatorIndex] - for balanceIndex, balance := range balances { - if len(balance) == 0 { - continue - } - if validator.ValidatorIndex == balanceIndex { - validator.CurrentBalance = balance[0].Balance - validator.EffectiveBalance = balance[0].EffectiveBalance - } - } - data[i] = validator - } - - result := [][]string{} - for _, item := range data { - la_date := "N/a" - if item.LastAttestationSlot > 0 { - la_time := utils.SlotToTime(item.LastAttestationSlot) - la_date = la_time.Format(time.RFC822) - } - result = append(result, []string{ - fmt.Sprintf("%d", item.ValidatorIndex), - addCommas(float64(item.BalanceActivation)/float64(1e9), "%.5f"), - addCommas(float64(item.CurrentBalance)/float64(1e9), "%.5f"), - la_date, - }) - } - - return result -} diff --git a/services/services.go b/services/services.go index 9dc6ca9f09..21611459de 100644 --- a/services/services.go +++ b/services/services.go @@ -20,6 +20,7 @@ import ( "github.com/gobitfly/eth2-beaconchain-explorer/ratelimit" "github.com/gobitfly/eth2-beaconchain-explorer/types" "github.com/gobitfly/eth2-beaconchain-explorer/utils" + "github.com/montanaflynn/stats" itypes "github.com/gobitfly/eth-rewards/types" "github.com/shopspring/decimal" @@ -87,6 +88,9 @@ func Init() { ready.Add(1) go latestExportedStatisticDayUpdater(ready) + ready.Add(1) + go queueEstimateUpdater(ready) + if utils.Config.RatelimitUpdater.Enabled { go ratelimit.DBUpdater() } @@ -665,6 +669,7 @@ func getIndexPageData() (*types.IndexPageData, error) { } data.CurrentEpoch = epoch + data.ElectraHasHappened = utils.ElectraHasHappened(epoch) cutoffSlot := utils.TimeToSlot(uint64(time.Now().Add(time.Second * 10).Unix())) // If we are before the genesis block show the first 20 slots by default @@ -799,7 +804,7 @@ func getIndexPageData() (*types.IndexPageData, error) { latestFinalizedEpoch := LatestFinalizedEpoch() var epochs []*types.IndexPageDataEpochs - err = db.ReaderDb.Select(&epochs, `SELECT epoch, finalized , eligibleether, globalparticipationrate, votedether FROM epochs ORDER BY epochs DESC LIMIT 15`) + err = db.ReaderDb.Select(&epochs, `SELECT epoch, finalized , eligibleether, globalparticipationrate, votedether FROM epochs ORDER BY epoch DESC LIMIT 15`) if err != nil { return nil, fmt.Errorf("error retrieving index epoch data: %v", err) } @@ -902,16 +907,30 @@ func getIndexPageData() (*types.IndexPageData, error) { for _, block := range data.Blocks { block.Ts = utils.SlotToTime(block.Slot) } - queueCount := struct { - EnteringValidators uint64 `db:"entering_validators_count"` - ExitingValidators uint64 `db:"exiting_validators_count"` - }{} - err = db.ReaderDb.Get(&queueCount, "SELECT entering_validators_count, exiting_validators_count FROM queue ORDER BY ts DESC LIMIT 1") - if err != nil && err != sql.ErrNoRows { - return nil, fmt.Errorf("error retrieving validator queue count: %v", err) + + if utils.ElectraHasHappened(epoch) { + queueData := LatestQueueData() + if queueData != nil { + total := queueData.EnteringNewValidatorsEthAmount + queueData.EnteringTopUpEthAmount + data.EnteringBalance = fmt.Sprintf("%.0f", float64(total)/1e9) + data.EnteringValidatorsBalance = fmt.Sprintf("%.0f", float64(queueData.EnteringNewValidatorsEthAmount)/1e9) + data.EnteringValidatorTopup = fmt.Sprintf("%.0f %s", float64(queueData.EnteringTopUpEthAmount)/1e9, utils.Config.Frontend.ClCurrency) + data.ExitingValidatorsBalance = fmt.Sprintf("%.0f %s", float64(queueData.LeavingEthAmount)/1e9, utils.Config.Frontend.ClCurrency) + data.EnteringValidators = total + data.ExitingValidators = queueData.LeavingValidatorCount + } + } else { + queueCount := struct { + EnteringValidators uint64 `db:"entering_validators_count"` + ExitingValidators uint64 `db:"exiting_validators_count"` + }{} + err = db.ReaderDb.Get(&queueCount, "SELECT entering_validators_count, exiting_validators_count FROM queue ORDER BY ts DESC LIMIT 1") + if err != nil && err != sql.ErrNoRows { + return nil, fmt.Errorf("error retrieving validator queue count: %v", err) + } + data.EnteringValidators = queueCount.EnteringValidators + data.ExitingValidators = queueCount.ExitingValidators } - data.EnteringValidators = queueCount.EnteringValidators - data.ExitingValidators = queueCount.ExitingValidators var epochLowerBound uint64 if epochLowerBound = 0; epoch > 1600 { @@ -963,6 +982,10 @@ func LatestEpoch() uint64 { return 0 } +func GetMaxEffectiveBalance() uint64 { + return utils.GetMaxEffectiveBalance(LatestEpoch()) +} + func LatestNodeEpoch() uint64 { cacheKey := fmt.Sprintf("%d:frontend:latestNodeEpoch", utils.Config.Chain.ClConfig.DepositChainID) @@ -1105,7 +1128,8 @@ func LatestGasNowData() *types.GasNowPageData { if wanted, err := cache.TieredCache.GetWithLocalTimeout(cacheKey, time.Second*5, wanted); err == nil { return wanted.(*types.GasNowPageData) } else { - logger.Errorf("error retrieving gasNow from cache: %v", err) + // TODO: uncomment + //logger.Errorf("error retrieving gasNow from cache: %v", err) } return nil @@ -1291,117 +1315,165 @@ func gasNowUpdater(wg *sync.WaitGroup) { } } -func getGasNowData() (*types.GasNowPageData, error) { - gpoData := &types.GasNowPageData{} - gpoData.Code = 200 - gpoData.Data.Timestamp = time.Now().UnixNano() / 1e6 +// BlockWrapper is a simple wrapper for Ethereum block data. +type BlockWrapper struct { + Header *geth_types.Header + Transactions []rpcTransaction +} - client, err := geth_rpc.Dial(utils.Config.Eth1GethEndpoint) - if err != nil { - return nil, err - } +func GetBlock(client *geth_rpc.Client, blockType string) (*BlockWrapper, error) { var raw json.RawMessage - err = client.Call(&raw, "eth_getBlockByNumber", "pending", true) + err := client.Call(&raw, "eth_getBlockByNumber", blockType, true) if err != nil { - return nil, fmt.Errorf("error retrieving pending block data: %.1000s", err) // limit error message to 1000 characters + return nil, fmt.Errorf("error retrieving block data for %s: %w", blockType, err) } - - // var res map[string]interface{} - // err = json.Unmarshal(raw, &res) - // if err != nil { - // return nil, err - // } - var header *geth_types.Header var body rpcBlock - - err = json.Unmarshal(raw, &header) - if err != nil { + if err = json.Unmarshal(raw, &header); err != nil { return nil, err } - err = json.Unmarshal(raw, &body) - if err != nil { + if err = json.Unmarshal(raw, &body); err != nil { return nil, err } - txs := body.Transactions - - sort.Slice(txs, func(i, j int) bool { - return txs[i].tx.GasPrice().Cmp(txs[j].tx.GasPrice()) > 0 - }) - if len(txs) > 1 { - medianGasPrice := txs[len(txs)/2].tx.GasPrice() - tailGasPrice := txs[len(txs)-1].tx.GasPrice() - - gpoData.Data.Rapid = medianGasPrice - gpoData.Data.Fast = tailGasPrice - } else { - gpoData.Data.Rapid = new(big.Int) - gpoData.Data.Fast = new(big.Int) + if header.BaseFee == nil { + return nil, fmt.Errorf("block header for %s has nil BaseFee", blockType) } + return &BlockWrapper{ + Header: header, + Transactions: body.Transactions, + }, nil +} - err = client.Call(&raw, "txpool_content") +func getGasNowData() (*types.GasNowPageData, error) { + // Connect to the ETH node. + client, err := geth_rpc.Dial(utils.Config.Eth1GethEndpoint) if err != nil { - return nil, fmt.Errorf("error getting raw json data from txpool_content: %w", err) + return nil, err } + defer client.Close() - txPoolContent := &TxPoolContent{} - err = json.Unmarshal(raw, txPoolContent) + block, err := GetBlock(client, "pending") if err != nil { - return nil, fmt.Errorf("unmarshal txpoolcontent json error: %w", err) + return nil, fmt.Errorf("error retrieving pending block: %w", err) } - pendingTxs := make([]*TxPoolContentTransaction, 0, len(txPoolContent.Pending)) + baseFee := block.Header.BaseFee - for _, account := range txPoolContent.Pending { - lowestNonce := 9223372036854775807 - for n := range account { - if n < int(lowestNonce) { - lowestNonce = n - } + // Handle edgecase when there's too few txs in the pending block + // Use latest instead + if len(block.Transactions) < 10 { // 10 chosen to make percentiles estimates somewhat work with low data set + // Do not update baseFee as this is still the target block we suggest gas prices for + block, err = GetBlock(client, "latest") + if err != nil { + return nil, fmt.Errorf("error retrieving latest block: %w", err) } + } - pendingTxs = append(pendingTxs, account[lowestNonce]) + // ------------------------- + // (1) Build our “tip” samples. + // ------------------------- + pendingTips := make([]*big.Int, 0, len(block.Transactions)) + for _, tx := range block.Transactions { + tip := tx.tx.GasTipCap() + if tip.Sign() < 0 { + tip = big.NewInt(0) + } + pendingTips = append(pendingTips, tip) } - sort.Slice(pendingTxs, func(i, j int) bool { - return pendingTxs[i].GetGasPrice().Cmp(pendingTxs[j].GetGasPrice()) > 0 - }) - standardIndex := int(math.Max(float64(2*len(txs)), 500)) + gpoData := suggestGasPrices(block.Header.GasUsed, block.Header.GasLimit, baseFee, pendingTips) - slowIndex := int(math.Max(float64(5*len(txs)), 1000)) - if standardIndex < len(pendingTxs) { - gpoData.Data.Standard = pendingTxs[standardIndex].GetGasPrice() + // not available in unit test mode + if db.BigtableClient != nil { + // Log or store historical data. + if err = db.BigtableClient.SaveGasNowHistory(gpoData.Data.Slow, gpoData.Data.Standard, gpoData.Data.Fast, gpoData.Data.Rapid); err != nil { + logrus.WithError(err).Error("error updating gas now history") + } + + // Get fiat conversion data. + gpoData.Data.Price = price.GetPrice(utils.Config.Frontend.ElCurrency, "USD") + gpoData.Data.Currency = "USD" } else { - gpoData.Data.Standard = header.BaseFee + logrus.Error("error saving gas now history: bigtable client not initialized") } - if gpoData.Data.Standard.Cmp(header.BaseFee) < 0 { - gpoData.Data.Standard = header.BaseFee - } + return gpoData, nil +} - if slowIndex < len(pendingTxs) { - gpoData.Data.Slow = pendingTxs[slowIndex].GetGasPrice() - } else { - gpoData.Data.Slow = header.BaseFee +func suggestGasPrices(gasUsed uint64, gasLimit uint64, baseFee *big.Int, pendingTips []*big.Int) *types.GasNowPageData { + gpoData := &types.GasNowPageData{} + gpoData.Code = 200 + gpoData.Data.Timestamp = time.Now().UnixNano() / 1e6 + + // ------------------------- + // (2) Compute tip percentiles. + // Replace custom percentile calculation with stats.Percentile from https://pkg.go.dev/github.com/montanaflynn/stats#Percentile + // Convert pendingTips to a slice of float64 as required by the library. + tipsFloats := make([]float64, len(pendingTips)) + for i, tip := range pendingTips { + tipsFloats[i] = float64(tip.Int64()) } - if gpoData.Data.Slow.Cmp(header.BaseFee) < 0 { - gpoData.Data.Slow = header.BaseFee + // How full a block is [0, 1], protocol targets 50% usage + gasUsage := float64(gasUsed) / float64(gasLimit) + + // Dictates the aggressiveness to scale the percentiles for depending on the block usage + // This shift of percentiles is to better consider the usage of the current block. + // Imagine a 30% used block, there's still plenty of room to be filled with even a slow tip. + // So we should scale the percentiles down to suggest better value tips of the user. + // And vice versa where we want the users tx to stay competitive when the usage is high. + cappedGasUsage := gasUsage + if cappedGasUsage > 0.80 { + cappedGasUsage = 0.80 + } else if cappedGasUsage < 0.10 { + cappedGasUsage = 0.10 } - err = db.BigtableClient.SaveGasNowHistory(gpoData.Data.Slow, gpoData.Data.Standard, gpoData.Data.Fast, gpoData.Data.Rapid) + // The target percentiles assume a block usage of 50% as targeted by the protocol. + // So a percentile of 100 scaled with 50% cappedGasUsage usage will target the 50th percentile. + // While the cap prevents edgecases around 100% and 0% usage + + // Use stats.Percentile to compute percentiles + rapidTipFloat, err := stats.Percentile(tipsFloats, 100*cappedGasUsage) // target 50th percentile + if err != nil { + logrus.Warnf("error computing rapid tip percentile: %v", err) + rapidTipFloat = 0 + } + fastTipFloat, err := stats.Percentile(tipsFloats, 70*cappedGasUsage) // target 35th percentile if err != nil { - logrus.WithError(err).Error("error updating gas now history") + logrus.Warnf("error computing fast tip percentile: %v", err) + fastTipFloat = 0 + } + normalTipFloat, err := stats.Percentile(tipsFloats, 35*cappedGasUsage) // target 17.5th percentile + if err != nil { + logrus.Warnf("error computing normal tip percentile: %v", err) + normalTipFloat = 0 + } + slowTipFloat, err := stats.Percentile(tipsFloats, 10*cappedGasUsage) // target 5th percentile + if err != nil { + logrus.Warnf("error computing slow tip percentile: %v", err) + slowTipFloat = 0 } - gpoData.Data.Price = price.GetPrice(utils.Config.Frontend.ElCurrency, "USD") - gpoData.Data.Currency = "USD" + // Convert the float64 percentiles back to big.Int. + rapidTip := big.NewInt(int64(math.Round(rapidTipFloat))) + fastTip := big.NewInt(int64(math.Round(fastTipFloat))) + normalTip := big.NewInt(int64(math.Round(normalTipFloat))) + slowTip := big.NewInt(int64(math.Round(slowTipFloat))) - // gpoData.RapidUSD = gpoData.Rapid * 21000 * params.GWei / params.Ether * usd - // gpoData.FastUSD = gpoData.Fast * 21000 * params.GWei / params.Ether * usd - // gpoData.StandardUSD = gpoData.Standard * 21000 * params.GWei / params.Ether * usd - // gpoData.SlowUSD = gpoData.Slow * 21000 * params.GWei / params.Ether * usd - return gpoData, nil + // Now compute the final suggestion as: + // suggested gas price = baseFee + effective tip + rapidSuggestion := new(big.Int).Add(baseFee, rapidTip) + fastSuggestion := new(big.Int).Add(baseFee, fastTip) + normalSuggestion := new(big.Int).Add(baseFee, normalTip) + slowSuggestion := new(big.Int).Add(baseFee, slowTip) + + gpoData.Data.Rapid = rapidSuggestion + gpoData.Data.Fast = fastSuggestion + gpoData.Data.Standard = normalSuggestion + gpoData.Data.Slow = slowSuggestion + + return gpoData } type TxPoolContent struct { diff --git a/services/services_test.go b/services/services_test.go new file mode 100644 index 0000000000..2fd54c85c4 --- /dev/null +++ b/services/services_test.go @@ -0,0 +1,185 @@ +package services + +import ( + _ "embed" + "encoding/json" + "math" + "math/big" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/gobitfly/eth2-beaconchain-explorer/types" + "github.com/gobitfly/eth2-beaconchain-explorer/utils" +) + +//go:embed services_test_block.json +var pendingBlock string + +// checkSuggestion compares a suggestion value with an expected value. +func checkSuggestion(t *testing.T, name string, got, expected int64) { + if got != expected { + t.Errorf("expected %s suggestion %d, got %d", name, expected, got) + } +} + +func newFakeRPCServer(pendingBlock, latestBlock string) *httptest.Server { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var req struct { + ID interface{} `json:"id"` + Method string `json:"method"` + Params []interface{} `json:"params"` + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "bad request", http.StatusBadRequest) + return + } + + var block json.RawMessage // Use RawMessage to handle JSON correctly + // Choose the block based on the first parameter. + if req.Method == "eth_getBlockByNumber" && len(req.Params) > 0 { + if param, ok := req.Params[0].(string); ok { + if param == "pending" { + block = json.RawMessage(pendingBlock) + } else if param == "latest" { + block = json.RawMessage(latestBlock) + } + } + } + + resp := map[string]interface{}{ + "jsonrpc": "2.0", + "id": req.ID, + "result": block, + } + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(resp) + }) + return httptest.NewServer(handler) +} + +// TestSuggestGasPrices_TableDriven consolidates various test cases into one table-driven test. +func TestSuggestGasPrices_TableDriven(t *testing.T) { + // Helper to create 50 dummy transactions with tip values 1..50. + makeDummyTips := func() []*big.Int { + tips := make([]*big.Int, 50) + for i := 0; i < 50; i++ { + tips[i] = big.NewInt(int64(i + 1)) + } + return tips + } + + testCases := []struct { + name string + tips []*big.Int + baseFee *big.Int + gasUsed, gasLimit uint64 + expectedRapid int64 + expectedFast int64 + expectedStandard int64 + expectedSlow int64 + }{ + { + name: "SufficientTxs", + tips: makeDummyTips(), + baseFee: big.NewInt(100), + gasUsed: 10_000_000, + gasLimit: 20_000_000, + expectedRapid: 125, + expectedFast: 118, + expectedStandard: 109, + expectedSlow: 103, + }, + { + name: "EmptyTxs", + tips: []*big.Int{}, // no transactions + baseFee: big.NewInt(100), + gasUsed: 10_000_000, + gasLimit: 20_000_000, + expectedRapid: 100, + expectedFast: 100, + expectedStandard: 100, + expectedSlow: 100, + }, + { + name: "LowGasUsage", + tips: makeDummyTips(), + baseFee: big.NewInt(100), + gasUsed: 1_000_000, // 0.05 usage, clamped to 0.10 + gasLimit: 20_000_000, + expectedRapid: 105, + expectedFast: 104, + expectedStandard: 102, + expectedSlow: 100, + }, + { + name: "HighGasUsage", + tips: makeDummyTips(), + baseFee: big.NewInt(100), + gasUsed: 18_000_000, // 0.9 usage, clamped to 0.80 + gasLimit: 20_000_000, + expectedRapid: 140, + expectedFast: 129, + expectedStandard: 115, + expectedSlow: 104, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := suggestGasPrices(tc.gasUsed, tc.gasLimit, tc.baseFee, tc.tips) + + // Basic checks. + if result.Code != 200 { + t.Errorf("expected Code 200, got %d", result.Code) + } + if result.Data.Timestamp == 0 { + t.Error("expected nonzero Timestamp") + } + + // Validate the suggestions. + checkSuggestion(t, "Rapid", result.Data.Rapid.Int64(), tc.expectedRapid) + checkSuggestion(t, "Fast", result.Data.Fast.Int64(), tc.expectedFast) + checkSuggestion(t, "Standard", result.Data.Standard.Int64(), tc.expectedStandard) + checkSuggestion(t, "Slow", result.Data.Slow.Int64(), tc.expectedSlow) + }) + } +} + +// TestSuggestGasPrices_Timestamp checks that the timestamp is set and roughly "now". +func TestSuggestGasPrices_Timestamp(t *testing.T) { + tips := []*big.Int{big.NewInt(10)} + baseFee := big.NewInt(100) + gasUsed := uint64(10_000_000) + gasLimit := uint64(20_000_000) + + result := suggestGasPrices(gasUsed, gasLimit, baseFee, tips) + now := time.Now().UnixNano() / 1e6 + if math.Abs(float64(result.Data.Timestamp-now)) > 1000 { + t.Errorf("expected timestamp close to now, got %d (now=%d)", result.Data.Timestamp, now) + } +} + +func TestViaFakeRPC(t *testing.T) { + cfg := &types.Config{} + utils.Config = cfg + + server := newFakeRPCServer(pendingBlock, "") + defer server.Close() + utils.Config.Eth1GethEndpoint = server.URL + + result, err := getGasNowData() + if err != nil { + t.Errorf("Error: %v", err) + } + if result == nil { + t.Errorf("Error: data is nil") + return + } + + checkSuggestion(t, "Rapid", result.Data.Rapid.Int64(), 3935619558) + checkSuggestion(t, "Fast", result.Data.Fast.Int64(), 3511919489) + checkSuggestion(t, "Standard", result.Data.Standard.Int64(), 3123069597) + checkSuggestion(t, "Slow", result.Data.Slow.Int64(), 3073069597) +} diff --git a/services/services_test_block.json b/services/services_test_block.json new file mode 100644 index 0000000000..73dcd6bd13 --- /dev/null +++ b/services/services_test_block.json @@ -0,0 +1 @@ +{"baseFeePerGas":"0xb72b521d","blobGasUsed":"0x0","difficulty":"0x0","excessBlobGas":"0x4f40000","extraData":"0x6265617665726275696c642e6f7267","gasLimit":"0x2255100","gasUsed":"0xcee1de","hash":null,"logsBloom":"0x1529517723264c9482810a28b810100944cc2d4a20136760b28880415411a18281508805ff4b0212c4400946399e8130931f84689a80282276d324481caf20c61c04d090c7560318e8017b0a0cc008ec0c12521910458c16240047258c482813140b51018e603264a013f01809833891022b0ca010cb3f06fa205816099d97140b200c4b980060281d988142837162645809bca76b41a008105a6674e494028b87c9116220c028e2049415ec55e80ac81ecf083a080e63d489f009830c4c5370ab12004aa09a2a70236940b25b550c06c194b70e074e1090944719060808636e165ba421250080050001d245803018405120e644623a8ac12001f8001034cfce","miner":null,"mixHash":"0xd5f7787d768a10185e4e8d09131eb34d970909adf8be7994bbc2591301933057","nonce":null,"number":"0x14d1d15","parentBeaconBlockRoot":"0x2e25458d086aee62caaedb25f4d62f2f65540d561702ed4d49c459560e57768a","parentHash":"0xfbf7ae733d5f09ed7fad67c744d1335092795b1b6c63b28132eaca89becbbf4e","receiptsRoot":"0xfe9903d10e61836f789e777da165ed3a08b8d277228dc83c951670cf6d8a6710","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x27624","stateRoot":"0xed564f2d7032a5489e5da2540413beb0396017e0c2919765796432a5885a80a9","timestamp":"0x67acacd7","totalDifficulty":"0xc70d815d562d3cfa955","transactions":[{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xa10f5dc7d17ad60131bdc7501271011c38cc4c66","gas":"0x6a6ff","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x155c85f1c","hash":"0xb5ac01e62204b1403194cd7d9c5b7c1c34c422e626bc7ed314f19f8414c4e5fd","input":"0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000067acb3cd00000000000000000000000000000000000000000000000000000000000000040a00060400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000001600000000000000000000000007fc66500c84a76ad7e9c93437bfc5ac33e2ddae9000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000067d439ba000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066a9893cc07d91d95644aedd05d03f95e1dba8af0000000000000000000000000000000000000000000000000000000067acb3c200000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000004110c681a4518d3a8ac74b796647a1fd20e7c55e74660230c6045a5a25d70322141dcc62c84da3419972ba2c82bcc87ba3accf20518b537104bea7940ba7fecdff1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000010da5215273dcf715d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000427fc66500c84a76ad7e9c93437bfc5ac33e2ddae9000bb8c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000027213e28d7fda5c57fe9e5dd923818dbccf71c4700000000000000000000000000000000000000000000000000000000000000190000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a10f5dc7d17ad60131bdc7501271011c38cc4c6600000000000000000000000000000000000000000000000000000010e8ebedf40c","nonce":"0x3f9","to":"0x66a9893cc07d91d95644aedd05d03f95e1dba8af","transactionIndex":"0x0","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x3a99e110e62ce10f5b7bcbfb74fe2a19cf77cc10947f4fd312915365be057e0d","s":"0x5160f8931da88f5a359af00d5dd1c228d7ffdeb34e73bf9ba40b5f6b4b90fd47"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x029f159b23828ca769f3431463383a36525ed120","gas":"0x155cc0","gasPrice":"0xb72b521d","maxPriorityFeePerGas":"0x0","maxFeePerGas":"0xf52eecb5","hash":"0x272b85804154808be681c436f7591550a72bca6cc9581fc2c38d25950acab6f1","input":"0xca8bd1f90614010001f427104585fe77225b41b697c938b018e2ac67ac5a20c0c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2130100271027105ee5bf7ae06d1be5997a1a72006fe6c607ec6de82260fac5e5542a773aa44fbcfedf7c193bc2c59911000127102710494077b316b40ec770eaa45b0655fe4235ef29965ee5bf7ae06d1be5997a1a72006fe6c607ec6de81300012710271023878914efe38d27c4d67ab83ed1b93a74d4086a23878914efe38d27c4d67ab83ed1b93a74d4086a1f0201000327101116898dda4015ed8ddefb84b6e8bc24528af2d8dac17f958d2ee523a2206206994597c13d831ec714000101f4271088e6a0c2ddd26feeb64f039a2c41296fcb3f5640a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48070faa229a7f934b0901cc6c4f238905e000014501450145020702070407040704a904a904a904a904a900201f0000000000000000000000000000000000000000000000000000000000000001000002000387870bca3f3fd6335c3f4ce8392d69350b4fa4e20080e8eda9df0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000494077b316b40ec770eaa45b0655fe4235ef2996a852b2ee0c8e48986f63928c095a214e4c329fdbcfca03e708590193fae4cde4ccb3b79a6c66661fab7f48cf0000000000000000000000006c2a355929ee1262305e385ad49b84fe5f5a4777000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023878914efe38d27c4d67ab83ed1b93a74d4086a0000000000000000000000005ee5bf7ae06d1be5997a1a72006fe6c607ec6de8000000000000000000000000000000000000000000000000000018f02893ea0000000000000000000000000000000000000000000000000000000006b4bf9e1844000000000000000000000000000000330067b5e6ae00000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004134f6dfa065d091511cbded466f26c510d99641887b53248fca411010f6c85cf8d8391cd068b41a6158814b12139a107118f0874a15c717b187f5abb00b6407761c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000201f0000000000000000000000000000000000000000000000000000000000000000000002000387870bca3f3fd6335c3f4ce8392d69350b4fa4e2006069328dec000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012c00000000","nonce":"0x2502","to":"0x9d6b911199b891c55a93e4bc635bf59e33d002d8","transactionIndex":"0x1","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x255c938b4e1646f547e10e9af614faed10bdc5404627b306d968a544deb5219d","s":"0x690209d33ff14b14d36c37147771094cfefb52f7c84c5143f884356d3a89cf98"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x029f159b23828ca769f3431463383a36525ed120","gas":"0x155cc0","gasPrice":"0xb72b521d","maxPriorityFeePerGas":"0x0","maxFeePerGas":"0xf52eecb5","hash":"0x339ee00bf63e8df38e2232e1a396febaab616dbda6ef3b7f1628d788138c5c26","input":"0xca8bd1f90514010001f427109a772018fbd77fcd2d25657e5c547baff3fd7d16a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48130100271027105ee5bf7ae06d1be5997a1a72006fe6c607ec6de82260fac5e5542a773aa44fbcfedf7c193bc2c59911000127102710494077b316b40ec770eaa45b0655fe4235ef29965ee5bf7ae06d1be5997a1a72006fe6c607ec6de81300012710271023878914efe38d27c4d67ab83ed1b93a74d4086a23878914efe38d27c4d67ab83ed1b93a74d4086a1301000000271031373595f40ea48a7aab6cbcb0d377c6066e2dcadac17f958d2ee523a2206206994597c13d831ec703898dd70510c802c607010a010a010a01cc01cc03cc03cc046e046e065000201f0000000000000000000000000000000000000000000000000000000000000001000002000387870bca3f3fd6335c3f4ce8392d69350b4fa4e20080e8eda9df0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000494077b316b40ec770eaa45b0655fe4235ef2996a852b2ee0c8e48986f63928c095a214e4c329fdbcfca03e708590193fae4cde4ccb3b79a6c66661fab7f48cf0000000000000000000000006c2a355929ee1262305e385ad49b84fe5f5a4777000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023878914efe38d27c4d67ab83ed1b93a74d4086a0000000000000000000000005ee5bf7ae06d1be5997a1a72006fe6c607ec6de8000000000000000000000000000000000000000000000000000018f02893ea0000000000000000000000000000000000000000000000000000000006b4bf9e1844000000000000000000000000000000330067b5e6ae00000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004134f6dfa065d091511cbded466f26c510d99641887b53248fca411010f6c85cf8d8391cd068b41a6158814b12139a107118f0874a15c717b187f5abb00b6407761c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000201f0000000000000000000000000000000000000000000000000000000000000000000002000387870bca3f3fd6335c3f4ce8392d69350b4fa4e2006069328dec000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c01e00000000228ee3f2ccc605477745f04600ad9b91c04aabdb9700a049b5931100000000000000000000000031373595f40ea48a7aab6cbcb0d377c6066e2dca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb14b640000002000131373595f40ea48a7aab6cbcb0d377c6066e2dca01003eece7db0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb14b600000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001012c00000000","nonce":"0x2503","to":"0x9d6b911199b891c55a93e4bc635bf59e33d002d8","transactionIndex":"0x2","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x310efc09118b39b95e90617c0ee906517d8813ca010b49bb36cf05b6413f2b73","s":"0x71b89251820cd5ed2f0dfee8fdf167a4a4d9890076df02a2752da8693513ab52"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x029f159b23828ca769f3431463383a36525ed120","gas":"0x155cc0","gasPrice":"0xb72b521d","maxPriorityFeePerGas":"0x0","maxFeePerGas":"0xf52eecb5","hash":"0xa7f5d6731b157c87458def98f439dcd2b904817f09891b08697186883b6b8f72","input":"0xca8bd1f90514010001f427104585fe77225b41b697c938b018e2ac67ac5a20c0c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2130100271027105ee5bf7ae06d1be5997a1a72006fe6c607ec6de82260fac5e5542a773aa44fbcfedf7c193bc2c59911000127102710494077b316b40ec770eaa45b0655fe4235ef29965ee5bf7ae06d1be5997a1a72006fe6c607ec6de81300012710271023878914efe38d27c4d67ab83ed1b93a74d4086a23878914efe38d27c4d67ab83ed1b93a74d4086a14010000642710c7bbec68d12a0d1830360f8ec58fa599ba1b0e9bdac17f958d2ee523a2206206994597c13d831ec7070bb429661405da0855414ff9be89100001110111011101d301d303d303d304750475047500201f0000000000000000000000000000000000000000000000000000000000000001000002000387870bca3f3fd6335c3f4ce8392d69350b4fa4e20080e8eda9df0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000494077b316b40ec770eaa45b0655fe4235ef2996a852b2ee0c8e48986f63928c095a214e4c329fdbcfca03e708590193fae4cde4ccb3b79a6c66661fab7f48cf0000000000000000000000006c2a355929ee1262305e385ad49b84fe5f5a4777000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023878914efe38d27c4d67ab83ed1b93a74d4086a0000000000000000000000005ee5bf7ae06d1be5997a1a72006fe6c607ec6de8000000000000000000000000000000000000000000000000000018f02893ea0000000000000000000000000000000000000000000000000000000006b4bf9e1844000000000000000000000000000000330067b5e6ae00000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004134f6dfa065d091511cbded466f26c510d99641887b53248fca411010f6c85cf8d8391cd068b41a6158814b12139a107118f0874a15c717b187f5abb00b6407761c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000201f0000000000000000000000000000000000000000000000000000000000000000000002000387870bca3f3fd6335c3f4ce8392d69350b4fa4e2006069328dec000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012c00000000","nonce":"0x2504","to":"0x9d6b911199b891c55a93e4bc635bf59e33d002d8","transactionIndex":"0x3","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x956da38355f833c9557e5c23a477f5c1bbec5762947f07ff8fff256681725a07","s":"0x31e8f93291dbef47701cc4385e29e4788abf2ee9195ec5b938679f406f47b78c"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x061979019711fd0c9ef93510a996f770a9fc7b60","gas":"0x43584","gasPrice":"0xb72b521d","maxPriorityFeePerGas":"0x0","maxFeePerGas":"0x112c0fb2b","hash":"0x0ee3c5d4889614ea8184e48f401b3289c02f885c08b161a324d37a4283da5508","input":"0x78e111f6000000000000000000000000a1d4de0ecd7481fd1366df26b3a928713e286be4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c42f1c6b500000000000000000000000000000000000000000000000000000000007d27b5f000000000000000000000000000000000000000bfb9b3abe8522000000000000000000000000000000000000000000000000001ed1d7036f14c7c09f0d00cde70000000000000000000000000000000000000004a25e810e8b264000000000000000000000000000000000000000000000000000000000000000000067acacd7ff80000000000000000000000000000000000000000000000000000000011cf700000000000000000000000000000000000000000000000000000000","nonce":"0x17b5","to":"0xa69babef1ca67a37ffaf7a485dfff3382056e78c","transactionIndex":"0x4","value":"0x4e04","type":"0x2","accessList":[{"address":"0x9a772018fbd77fcd2d25657e5c547baff3fd7d16","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000004","0x0000000000000000000000000000000000000000000000000000000000000001","0x2d88733daa8d83575c1c435b1a4140007e667f8dfbe2e5678ffec947a513e5cd","0x000000000000000000000000000000000000000000000000000000000000002d","0x0000000000000000000000000000000000000000000000000000000000000000"]},{"address":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","storageKeys":["0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b","0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3","0x0000000000000000000000000000000000000000000000000000000000000001","0x24c09d9cd41e893582ed9b685c2f61008d4f103548177a791173648a3b141ab7","0xf56408d23e6790fec5453738cf042a4a3ef7ec36e9ceae8978e4ffce8e903bc3"]},{"address":"0x43506849d7c04f9138d1a2050bbf3a0c054402dd","storageKeys":[]},{"address":"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599","storageKeys":["0x8c2f16f235092052b401051eef9e019dac94bb7b8f7c80be2557b939537bec39","0x0000000000000000000000000000000000000000000000000000000000000005","0x99713ceb4322a7b2d063a2b1e90a212070b8c507ea9c7afebed78f66997ae15e"]},{"address":"0xa1d4de0ecd7481fd1366df26b3a928713e286be4","storageKeys":[]}],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xdd9bd93c77f42f046fcc1d00a096956d6f26eb6f3c03aff685423f2129c905d8","s":"0x53790ef3a829ed0f410a1eb36251f87ff73b9e1fa43e3908b396cd282639cce6"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xfc9928f6590d853752824b0b403a6ae36785e535","gas":"0xf97d0","gasPrice":"0xd03b6d27","maxPriorityFeePerGas":"0x19101b0a","maxFeePerGas":"0x1072ed296","hash":"0x64ba8166ead858e521181c4cf72ab0b748ddbda7b7f07440f72a7f6056b0aefd","input":"0x152c490056534741cd8b152df6d48adf7ac51f75169a83b203143fa30084000000000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e2617ba0370000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c59900000000000000000000000000000000000000000000000000000000010ae05000000000000000000000000000000000009e50a7ddb7a7b0e2ee6604fd120e490000000000000000000000000000000000000000000000000000000000000000404b01c0095a214e4c329fdbcfca03e708590193fae4cde4ccb3b79a6c66661fab7f48cf0000000000000000000000006c2a355929ee1262305e385ad49b84fe5f5a4777000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023878914efe38d27c4d67ab83ed1b93a74d4086a0000000000000000000000005ee5bf7ae06d1be5997a1a72006fe6c607ec6de8000000000000000000000000000000000000000000000000000018f02893ea0000000000000000000000000000000000000000000000000000000006b4bf9e1844000000000000000000000000000000330067b5e6ae0000000000000000000034f6dfa065d091511cbded466f26c510d99641887b53248fca411010f6c85cf8d8391cd068b41a6158814b12139a107118f0874a15c717b187f5abb00b64077600000000000000000000000000000000000000000000000000000000010ae050200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000003fa30064000000000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e269328dec000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000003e072298000000000000000000000000056534741cd8b152df6d48adf7ac51f75169a83b2006d216a0a16d0cd184585fe77225b41b697c938b018e2ac67ac5a20c02cde0000000000000000000000001e33042e31042260fac5e5542a773aa44fbcfedf7c193bc2c599006d","nonce":"0x336c0","to":"0x00000000009e50a7ddb7a7b0e2ee6604fd120e49","transactionIndex":"0x5","value":"0x10afe83","type":"0x2","accessList":[{"address":"0xfdfd9c85ad200c506cf9e21f1fd8dd01932fbb23","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000005"]},{"address":"0xf4030086522a5beea4988f8ca5b36dbc97bee88c","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000005","0x0000000000000000000000000000000000000000000000000000000000000002"]},{"address":"0x6df1c1e379bc5a00a7b4c6e67a203333772f45a8","storageKeys":["0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","0xac6acc773282ba7a764f2b799791dd7d7105566f74ce055188fd558a19461488","0x000000000000000000000000000000000000000000000000000000000000003a"]},{"address":"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599","storageKeys":["0x8641bb179b9aa9ed8dee118e2d357643a656a679df1d0d9f26c18bd21be43bc6","0xa5b61b44e045dc21fc5c207ab1008f38dbd81b3b5f415dc8aae55ebb505b5a4d","0xa9054b5579bdf7aede053a0b3bc6d413048dcb3abecc36589756f2d6c9bfe7e0","0xe1895f1d4a5bd2b3df7a639e575fcbd7de014b6c8ea7d177285000226e39db32","0xdc276a4f120117ad5ae6415d1c724b4f3a0e81f0ee6466e1392ca121b63123f2","0x0000000000000000000000000000000000000000000000000000000000000005"]},{"address":"0x2b22e425c1322fba0dbf17bb1da25d71811ee7ba","storageKeys":[]},{"address":"0x40aabef1aa8f0eec637e0e7d92fbffb2f26a8b7b","storageKeys":["0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","0x000000000000000000000000000000000000000000000000000000000000003a","0xac6acc773282ba7a764f2b799791dd7d7105566f74ce055188fd558a19461488"]},{"address":"0x2f39d218133afab8f2b819b1066c7e434ad94e9e","storageKeys":["0x740f710666bd7a12af42df98311e541e47f7fd33d382d11602457a6d540cbd63"]},{"address":"0x4a3411ac2948b33c69666b35cc6d055b27ea84f1","storageKeys":["0xa7b66874f3370752e3dde84f4673d050865edbef4ca32be3f9ac25ddcdb07333","0x000000000000000000000000000000000000000000000000000000000000000b","0x514afcf618258271a4b4c88684fbb4115a4875fbfa083cad0ffc950ca5a525fe"]},{"address":"0x3e7d1eab13ad0104d2750b8863b489d65364e32d","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000005","0x0000000000000000000000000000000000000000000000000000000000000002"]},{"address":"0xef434e4573b90b6ecd4a00f4888381e4d0cc5ccd","storageKeys":[]},{"address":"0xe7b67f44ea304dd7f6d215b13686637ff64cd2b2","storageKeys":[]},{"address":"0x111111125421ca6dc452d289314280a0f8842a65","storageKeys":["0xa621da3ffb62fc451da224cc8a3d1f372d8ccbb352c2bfa3e720da3c48059fb9","0x0000000000000000000000000000000000000000000000000000000000000003","0x1fcc635fcae3eaec7a08f959d90c47a1d6af4ae043d176775119851b0671c061"]},{"address":"0x018008bfb33d285247a21d44e50697654f754e63","storageKeys":["0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","0xf2cb6213b5623d708dfcd67c36c2ed4f83c2078e94c384263ce971f211d9d648"]},{"address":"0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2","storageKeys":["0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411ee","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb58244d","0xac6acc773282ba7a764f2b799791dd7d7105566f74ce055188fd558a19461488","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb582454","0x49d58ea9a5daf69ab79ed6bff1f0de709220367fa3b259710db785f85d2077eb","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb58244e","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb582451","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb582453","0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411e6","0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411e7","0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411e8","0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411ec","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb582450","0x4c0bd942d17410ca1f6d3278a62feef7078602605466e37de958808f1454efbd","0xf9633c392965ec10b6d4101c7112e9abfd80429f2fc6dd2da927a5187bae9ab6","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb582455","0xf81d8d79f42adb4c73cc3aa0c78e25d3343882d0313c0b80ece3d3a103ef1ec2","0x5e14560e314427eb9d0c466a6058089f672317c8e26719a770a709c3f2481e48","0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411e9","0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411ed","0xf81d8d79f42adb4c73cc3aa0c78e25d3343882d0313c0b80ece3d3a103ef1ebf","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb58244f","0x000000000000000000000000000000000000000000000000000000000000003b","0x4cb2b152c1b54ce671907a93c300fd5aa72383a9d4ec19a81e3333632ae92e00","0x5e14560e314427eb9d0c466a6058089f672317c8e26719a770a709c3f2481e4c","0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411eb","0xa545cd6a8c740ef334da397e20b86fa8bb31c74c08fdf896e40bc8c552c9905e","0x5e14560e314427eb9d0c466a6058089f672317c8e26719a770a709c3f2481e4b","0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411e5","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb58244c","0xf81d8d79f42adb4c73cc3aa0c78e25d3343882d0313c0b80ece3d3a103ef1ec3","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb582452","0xf81d8d79f42adb4c73cc3aa0c78e25d3343882d0313c0b80ece3d3a103ef1ec0","0x5e14560e314427eb9d0c466a6058089f672317c8e26719a770a709c3f2481e49","0x05725f7419f52ac606bc65a60e5ab85095522694ed898882d2777964ee382600","0x985014169254cc329811c4c974decec8f96d7f90c48c0e7f6e26e89ddaab437f"]},{"address":"0xac725cb59d16c81061bdea61041a8a5e73da9ec6","storageKeys":[]},{"address":"0x8164cc65827dcfe994ab23944cbc90e0aa80bfcb","storageKeys":["0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","0x6c4136660c1c260b1ec9c4485c4d931e4953db6f21a8914a5100594d89ef14c8","0x3e242f8047bc39c2cfb431bd4b619e1048762bfea0daadf894db34c68e8f158f"]},{"address":"0x23878914efe38d27c4d67ab83ed1b93a74d4086a","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000039","0x0000000000000000000000000000000000000000000000000000000000000036","0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","0x04d23ab21c8669e4e7c5544edbd4cc24028df50b98be1c556b07f810fc1767cd","0x000000000000000000000000000000000000000000000000000000000000003d","0xf2cb6213b5623d708dfcd67c36c2ed4f83c2078e94c384263ce971f211d9d648","0x74dbf4189bcab344d89607d6b17b2848bc68fe24fad0016dfb3bc17af55c9610"]},{"address":"0xa5e3a55cea42b86560a5215094981c300899199d","storageKeys":["0x135aaba77790b4180afb1a9f93acda9d4f0234d000a4253208141351b8ec8af6","0x000000000000000000000000000000000000000000000000000000000000000b","0x2948a3f38cb7990a74a21abb6c64c11c74154bde9e17fc360a729ca5ee051ccc"]},{"address":"0xaed0c38402a5d19df6e4c03f4e2dced6e29c1ee9","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000005","0x0000000000000000000000000000000000000000000000000000000000000002"]},{"address":"0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000005","0x0000000000000000000000000000000000000000000000000000000000000002"]},{"address":"0x230e0321cf38f09e247e50afc7801ea2351fe56f","storageKeys":[]},{"address":"0x0d5f4aadf3fde31bbb55db5f42c080f18ad54df5","storageKeys":["0xc72832eeeb6c954f2d21e989302b29df1947780c65be799f4fd90fe3c616336b","0x000000000000000000000000000000000000000000000000000000000000000b","0x2948a3f38cb7990a74a21abb6c64c11c74154bde9e17fc360a729ca5ee051ccc"]},{"address":"0x4585fe77225b41b697c938b018e2ac67ac5a20c0","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000001","0x1975ccc81ee1682a2388ee8f1648952db59b6b82687a671ccb527af6f1447fc2","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000004"]},{"address":"0x9ec6f08190dea04a54f8afc53db96134e5e3fdfb","storageKeys":["0xf0b998666205e50af945dcd5198861491334a6162cc24264d9a801634ee07ec8","0xa934b07068f5d95a11413ed6d08a4a1122dc4b8c14a6ab2d94f8b279dac63042"]},{"address":"0xc26d4a1c46d884cff6de9800b6ae7a8cf48b4ff8","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000002"]},{"address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","storageKeys":["0x9ede93be0d8fc6a5eb9cf1c7345a85b7519d8487a727aef0c2f00ab966aa7716","0x7bc913c661f71064cc80d5dff39efa8510e3bbf72c891a4fa1cbfe224edf9c35"]},{"address":"0x56534741cd8b152df6d48adf7ac51f75169a83b2","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000002","0x2d88733daa8d83575c1c435b1a4140007e667f8dfbe2e5678ffec947a513e5cd","0x0000000000000000000000000000000000000000000000000000000000000008","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000004"]},{"address":"0x5ee5bf7ae06d1be5997a1a72006fe6c607ec6de8","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000036","0x74dbf4189bcab344d89607d6b17b2848bc68fe24fad0016dfb3bc17af55c9610","0x0000000000000000000000000000000000000000000000000000000000000039","0xf2cb6213b5623d708dfcd67c36c2ed4f83c2078e94c384263ce971f211d9d648","0x161c01e67459f18cf4fe037dd969a8ef581f66a1267703f26cd6f3645d5d5389","0x000000000000000000000000000000000000000000000000000000000000003d","0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"]},{"address":"0x7d4e742018fb52e48b08be73d041c18b21de6fb5","storageKeys":["0xfb44bcdd0398172ec04229ecdf2731caab3b9195751a90735b5969e03b3bac03","0x000000000000000000000000000000000000000000000000000000000000000b","0xb45839bf6fce2fb07c81ea76cb27c9dbd2f4deebae83d8422941d9480347336e"]},{"address":"0x4d5f47fa6a74757f35c14fd3a6ef8e3c9bc514e8","storageKeys":["0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","0xf2cb6213b5623d708dfcd67c36c2ed4f83c2078e94c384263ce971f211d9d648"]},{"address":"0x709783ab12b65fd6cd948214eee6448f3bdd72a3","storageKeys":["0xcbd2373947cdd2067fc191106b14727bfb217748a108af3839f40f49c2be1a42","0x000000000000000000000000000000000000000000000000000000000000000b","0x542112b5e73cf836debcc9cb7a7cbdbd24d45f9fb8ce10af09a6afc836a4329b"]},{"address":"0xdac17f958d2ee523a2206206994597c13d831ec7","storageKeys":["0x000000000000000000000000000000000000000000000000000000000000000a","0x6be4a3a1db72c2857f39712ccf07fd67bf3b6aa64d1de0802b5b26256efcce4e","0x0000000000000000000000000000000000000000000000000000000000000000","0x109e9d476717a00634f187393736a420d364a36f857f58442b0af80253671507","0x0000000000000000000000000000000000000000000000000000000000000003","0x0000000000000000000000000000000000000000000000000000000000000004","0xa27f738c934b6e8f6085bdff1bbecb6e2e3b918d793a01ea7cd2519ed4ec4de8"]},{"address":"0x7effd7b47bfd17e52fb7559d3f924201b9dbff3d","storageKeys":[]},{"address":"0x54586be62e3c3580375ae3723c145253060ca0c2","storageKeys":["0x2a11cb67ca5c7e99dba99b50e02c11472d0f19c22ed5af42a1599a7f57e1c7a4","0xf0b998666205e50af945dcd5198861491334a6162cc24264d9a801634ee07ec8","0x5306b8fbe80b30a74098357ee8e26fad8dc069da9011cca5f0870a0a5982e541","0xa934b07068f5d95a11413ed6d08a4a1122dc4b8c14a6ab2d94f8b279dac63042"]},{"address":"0xaeb897e1dc6bbdced3b9d551c71a8cf172f27ac4","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000002"]}],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xcb2b48211d105d181d135be3e9d9aefa2105f4b6273a856856203a051e2784a6","s":"0x66dc51ca52fdbb6f6ec968415b731973026b8e918cbd4e223b13ed34ddba15db"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xcf351e3b6fc104d9906897b16f2043ed593bea25","gas":"0x520be","gasPrice":"0xb72b521d","maxPriorityFeePerGas":"0x0","maxFeePerGas":"0x112c0fb2b","hash":"0xaaee46dc77982e4b1f358b973b8aa480e48a37f12ff50c1a91dbce42fda2561b","input":"0x78e111f6000000000000000000000000a07e649e9392233640736dfc30458e890d7d60b0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c42f1c6b500000000000000000000000000000000000000000000000000000000001c23058000000000000000000000000000000000000000bfb7adbe10e5b000000000000000000000000000000000000000000000000001ed1ad62797d759e79d97019360000000000000000000000000000000000000004a25e810e8b264000000000000000000000000000000000000000000000000000000000000000000067acacd7ff8000000000000000000000000000000000000000000000000000000001479700000000000000000000000000000000000000000000000000000000","nonce":"0x12f4","to":"0xa69babef1ca67a37ffaf7a485dfff3382056e78c","transactionIndex":"0x6","value":"0x13304","type":"0x2","accessList":[{"address":"0xa07e649e9392233640736dfc30458e890d7d60b0","storageKeys":[]},{"address":"0x56534741cd8b152df6d48adf7ac51f75169a83b2","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000004","0x0000000000000000000000000000000000000000000000000000000000000001","0x2d88733daa8d83575c1c435b1a4140007e667f8dfbe2e5678ffec947a513e5cd","0x0000000000000000000000000000000000000000000000000000000000000008"]},{"address":"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599","storageKeys":["0x8641bb179b9aa9ed8dee118e2d357643a656a679df1d0d9f26c18bd21be43bc6","0x0000000000000000000000000000000000000000000000000000000000000005","0x99713ceb4322a7b2d063a2b1e90a212070b8c507ea9c7afebed78f66997ae15e"]},{"address":"0xdac17f958d2ee523a2206206994597c13d831ec7","storageKeys":["0xb3eb08f8908b50793538019be1a1d12936600f3a4243ae0d6e12252bb716f802","0x000000000000000000000000000000000000000000000000000000000000000a","0x0000000000000000000000000000000000000000000000000000000000000003","0x0000000000000000000000000000000000000000000000000000000000000004","0x6be4a3a1db72c2857f39712ccf07fd67bf3b6aa64d1de0802b5b26256efcce4e","0x35d7fb7665514f774d2c2df607e197eb8674b6e63d2638472758647a2e67406a","0x0000000000000000000000000000000000000000000000000000000000000000"]}],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x67a70a6f5454e46d5d02f2dec7dd257dbdd68a30a2a1fe9c8cd16d493e44197e","s":"0x1dcb9a319118a77906dc5ac871ccfe44812f2fe0b34bd057b8299779d3ea7351"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x26bce6ecb5b10138e4bf14ac0ffcc8727fef3b2e","gas":"0xc9878","gasPrice":"0xb72b521d","maxPriorityFeePerGas":"0x0","maxFeePerGas":"0x112c0fb2b","hash":"0x33624ceff621391e2aa539df1ee1143a7901707ca1e3e6a05bf6d604b6810dc8","input":"0x78e111f60000000000000000000000000aa1a9ecdfd4cbe8b0c0a4f5f9b461f263c76208000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001245d022402000000000000000000000000d51a44d3fae010294c616388b506acda1bfaae460000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000006bc5c8000000000000000000000000000000000000000bfb00b206c0f20000000000000000000000000000000000000000000000000004a2727d56e1c28000000000000000000000000000000000000000000000000000000000000000000067acacd7000000000000000000000000000000000000000000000000000000018f58dc40ff0100000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000","nonce":"0x3d8e5","to":"0xa69babef1ca67a37ffaf7a485dfff3382056e78c","transactionIndex":"0x7","value":"0x21b00","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xd4fccaf9b11ff7be095c98e81a9cc836be500d44712108c6e3f65da4a90c9674","s":"0x26bd9c205cf415bee4dc9b0a23abacc0663f7d43879a327e889249eeee10a1b6"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x26fd09c8b44af53df38a9bad41d5abc55a1786af","gas":"0x493e2","gasPrice":"0x22f972b81","maxPriorityFeePerGas":"0x1786bd964","maxFeePerGas":"0x2610bd398","hash":"0x77d8604ef41b2c1c7efadf84a36fc6ed936ccecd41e514e0137b2f12912a41b2","input":"0x2a0aad11000000000000000000000000256bd88baf707eaad1b73dc8b2b8a5d599b455840000000000000000000000000000000000000000000000000224be57327ccbc0000000000000000000000000000000000000000001ee3b8f0073b90000000000000000000000000000000000000000000000000001efb14eeb3a07d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000abd69d0fac4b0851dafe100979df808eb7fb81a9","nonce":"0x3c4be","to":"0x7176f0f071379fee51668eb6387dda9129e5ca6b","transactionIndex":"0x8","value":"0x0","type":"0x2","accessList":[{"address":"0x256bd88baf707eaad1b73dc8b2b8a5d599b45584","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000018","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000004","0x0000000000000000000000000000000000000000000000000000000000000002","0x8a9a513b9791a18d5272680de0b8170980d584db74d5cc608d89e97f4ae4c892"]},{"address":"0x42bbfa2e77757c645eeaad1655e0911a7553efbc","storageKeys":["0xf27b278bd390940e5444b9ff8465eeadb932469dfcd3345055299c69b81e88b7","0x19749baa9b5f41a18bf6763543aee4a4a882414c6c9f9a18c6ba06236a88fbfd","0x6dd1e314d51182173bd3eed45af7924b7f7fcf754666a6ec9a1115ba0311c559","0x90ebb1668edef652c56b31f473a4b85566d73da8d8d2a3f54dce93966d2cc3f4"]},{"address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","storageKeys":["0x51fb2fd50c243063b9224fed37fda98cf4c57371234145e268ea69f278776679","0x0e172d98b879678a3ee0f92abe00ac1abb5704e2202ab541abde74b1c29fa6f2"]},{"address":"0x7176f0f071379fee51668eb6387dda9129e5ca6b","storageKeys":["0xf27b278bd390940e5444b9ff8465eeadb932469dfcd3345055299c69b81e88b8"]}],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x7be5ec4811b6545056fbeba00098eec44804dfd86d599701042d0f0f984c9156","s":"0x3369470be2b4b3734539a71d8240ea07cbc154a27d4d1dceba385cdf846b9f19"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xae2fc483527b8ef99eb5d9b44875f005ba1fae13","gas":"0xe6b97","gasPrice":"0xfdab27f8","maxPriorityFeePerGas":"0xfdab27f8","maxFeePerGas":"0xfdab27f8","hash":"0x1748be3b6d73b8b9442fc5a05f4f4c7d0488ec06e16ac49d5f16d5067be3de45","input":"0x2b6656534741cd8b152df6d48adf7ac51f75169a83b25f03e873dae0010d25002260fac5e5542a773aa44fbcfedf7c193bc2c599dac17f958d2ee523a2206206994597c13d831ec701f4d02260fac5e5542a773aa44fbcfedf7c193bc2c5990044095ea7b300000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e200000000000000000000000000000000000000000000000000000000010d25aed087870bca3f3fd6335c3f4ce8392d69350b4fa4e20084e8eda9df0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c59900000000000000000000000000000000000000000000000000000000010d25ae0000000000000000000000001f2f10d1c40777ae1da742455c65828ff36df3870000000000000000000000000000000000000000000000000000000000000000d05ee5bf7ae06d1be5997a1a72006fe6c607ec6de80044095ea7b3000000000000000000000000111111125421ca6dc452d289314280a0f8842a6500000000000000000000000000000000000000000000000000000000010d25aed0111111125421ca6dc452d289314280a0f8842a6501849fda64bd095a214e4c329fdbcfca03e708590193fae4cde4ccb3b79a6c66661fab7f48cf0000000000000000000000006c2a355929ee1262305e385ad49b84fe5f5a4777000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023878914efe38d27c4d67ab83ed1b93a74d4086a0000000000000000000000005ee5bf7ae06d1be5997a1a72006fe6c607ec6de8000000000000000000000000000000000000000000000000000018f02893ea0000000000000000000000000000000000000000000000000000000006b4bf9e1844000000000000000000000000000000330067b5e6ae0000000000000000000034f6dfa065d091511cbded466f26c510d99641887b53248fca411010f6c85cf8d8391cd068b41a6158814b12139a107118f0874a15c717b187f5abb00b64077600000000000000000000000000000000000000000000000000000000010d25ae2000000000000000000000000000000000000000000000000000000000000000d087870bca3f3fd6335c3f4ce8392d69350b4fa4e2006469328dec000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000003e8e41a000000000000000000000000001f2f10d1c40777ae1da742455c65828ff36df387c6dac17f958d2ee523a2206206994597c13d831ec7d41f39e30006","nonce":"0x403426","to":"0x1f2f10d1c40777ae1da742455c65828ff36df387","transactionIndex":"0x9","value":"0xd7","type":"0x2","accessList":[{"address":"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599","storageKeys":["0xe94405623db486c7f4c8462fa7dbfea438c5682245bec746197eff60e9447631","0xe1895f1d4a5bd2b3df7a639e575fcbd7de014b6c8ea7d177285000226e39db32","0x0000000000000000000000000000000000000000000000000000000000000005","0x8641bb179b9aa9ed8dee118e2d357643a656a679df1d0d9f26c18bd21be43bc6","0x395aaa0e57aeb06cd2b291563efa05f86115ef2122aeedb5d3ab91c884c66a4c"]},{"address":"0x40aabef1aa8f0eec637e0e7d92fbffb2f26a8b7b","storageKeys":["0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","0x000000000000000000000000000000000000000000000000000000000000003a","0xac6acc773282ba7a764f2b799791dd7d7105566f74ce055188fd558a19461488"]},{"address":"0x8164cc65827dcfe994ab23944cbc90e0aa80bfcb","storageKeys":["0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","0x6c4136660c1c260b1ec9c4485c4d931e4953db6f21a8914a5100594d89ef14c8","0x3e242f8047bc39c2cfb431bd4b619e1048762bfea0daadf894db34c68e8f158f"]},{"address":"0x2f39d218133afab8f2b819b1066c7e434ad94e9e","storageKeys":["0x740f710666bd7a12af42df98311e541e47f7fd33d382d11602457a6d540cbd63"]},{"address":"0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000005","0x0000000000000000000000000000000000000000000000000000000000000002"]},{"address":"0xf4030086522a5beea4988f8ca5b36dbc97bee88c","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000005","0x0000000000000000000000000000000000000000000000000000000000000002"]},{"address":"0xe7b67f44ea304dd7f6d215b13686637ff64cd2b2","storageKeys":[]},{"address":"0x54586be62e3c3580375ae3723c145253060ca0c2","storageKeys":["0x2a11cb67ca5c7e99dba99b50e02c11472d0f19c22ed5af42a1599a7f57e1c7a4","0xf0b998666205e50af945dcd5198861491334a6162cc24264d9a801634ee07ec8","0x5306b8fbe80b30a74098357ee8e26fad8dc069da9011cca5f0870a0a5982e541","0xa934b07068f5d95a11413ed6d08a4a1122dc4b8c14a6ab2d94f8b279dac63042"]},{"address":"0x230e0321cf38f09e247e50afc7801ea2351fe56f","storageKeys":[]},{"address":"0xfdfd9c85ad200c506cf9e21f1fd8dd01932fbb23","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000005","0x0000000000000000000000000000000000000000000000000000000000000002"]},{"address":"0xa5e3a55cea42b86560a5215094981c300899199d","storageKeys":["0x2948a3f38cb7990a74a21abb6c64c11c74154bde9e17fc360a729ca5ee051ccc","0x135aaba77790b4180afb1a9f93acda9d4f0234d000a4253208141351b8ec8af6","0x000000000000000000000000000000000000000000000000000000000000000b"]},{"address":"0x3e7d1eab13ad0104d2750b8863b489d65364e32d","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000005","0x0000000000000000000000000000000000000000000000000000000000000002"]},{"address":"0xdac17f958d2ee523a2206206994597c13d831ec7","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000004","0x0ea7755f27060b9d82f676cf25a477664d65f7bac7d3ece2236c3850fdee0eed","0x000000000000000000000000000000000000000000000000000000000000000a","0x6be4a3a1db72c2857f39712ccf07fd67bf3b6aa64d1de0802b5b26256efcce4e","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000003","0xd737923fb16067a2e1e9b30eca49ccd8347f7eb28ba62969799fa11819076676","0x109e9d476717a00634f187393736a420d364a36f857f58442b0af80253671507","0xa27f738c934b6e8f6085bdff1bbecb6e2e3b918d793a01ea7cd2519ed4ec4de8"]},{"address":"0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2","storageKeys":["0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb582453","0x49d58ea9a5daf69ab79ed6bff1f0de709220367fa3b259710db785f85d2077eb","0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411ee","0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411ed","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb582450","0x5e14560e314427eb9d0c466a6058089f672317c8e26719a770a709c3f2481e4b","0x05725f7419f52ac606bc65a60e5ab85095522694ed898882d2777964ee382600","0xa6bce43f0b9f1dc4ef0c58a41e23e01961bae58c0e8b7eed567fe69cd8089a5f","0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411e9","0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411e7","0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411e5","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb58244c","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb582451","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb582455","0xf81d8d79f42adb4c73cc3aa0c78e25d3343882d0313c0b80ece3d3a103ef1ebf","0x5e14560e314427eb9d0c466a6058089f672317c8e26719a770a709c3f2481e48","0x5e14560e314427eb9d0c466a6058089f672317c8e26719a770a709c3f2481e49","0x5e14560e314427eb9d0c466a6058089f672317c8e26719a770a709c3f2481e4c","0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411eb","0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411e6","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb58244f","0xf81d8d79f42adb4c73cc3aa0c78e25d3343882d0313c0b80ece3d3a103ef1ec2","0xf81d8d79f42adb4c73cc3aa0c78e25d3343882d0313c0b80ece3d3a103ef1ec0","0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","0x4cb2b152c1b54ce671907a93c300fd5aa72383a9d4ec19a81e3333632ae92e00","0x4c0bd942d17410ca1f6d3278a62feef7078602605466e37de958808f1454efbd","0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411e8","0xe6b784ffd76cc7c0acf50d71024872850effd718b7fd8c76147ebf73b768cb46","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb582454","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb58244d","0x000000000000000000000000000000000000000000000000000000000000003b","0xac6acc773282ba7a764f2b799791dd7d7105566f74ce055188fd558a19461488","0xa545cd6a8c740ef334da397e20b86fa8bb31c74c08fdf896e40bc8c552c9905e","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb58244e","0xca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb582452","0xf81d8d79f42adb4c73cc3aa0c78e25d3343882d0313c0b80ece3d3a103ef1ec3","0xd0684f4db7c2f2e52cb69cf1a0bfb094fb45c257ee5dcc06511d6d99da0411ec"]},{"address":"0xef434e4573b90b6ecd4a00f4888381e4d0cc5ccd","storageKeys":[]},{"address":"0x23878914efe38d27c4d67ab83ed1b93a74d4086a","storageKeys":["0x000000000000000000000000000000000000000000000000000000000000003d","0xf2cb6213b5623d708dfcd67c36c2ed4f83c2078e94c384263ce971f211d9d648","0x57a3719049ad85436e9ae52fbc627306957c42b02d9edb227af828619c2097f3","0x0000000000000000000000000000000000000000000000000000000000000039","0x0000000000000000000000000000000000000000000000000000000000000036","0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","0x04d23ab21c8669e4e7c5544edbd4cc24028df50b98be1c556b07f810fc1767cd"]},{"address":"0xc26d4a1c46d884cff6de9800b6ae7a8cf48b4ff8","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000002"]},{"address":"0x0d5f4aadf3fde31bbb55db5f42c080f18ad54df5","storageKeys":["0xc72832eeeb6c954f2d21e989302b29df1947780c65be799f4fd90fe3c616336b","0x000000000000000000000000000000000000000000000000000000000000000b","0x2948a3f38cb7990a74a21abb6c64c11c74154bde9e17fc360a729ca5ee051ccc"]},{"address":"0xac725cb59d16c81061bdea61041a8a5e73da9ec6","storageKeys":[]},{"address":"0x7effd7b47bfd17e52fb7559d3f924201b9dbff3d","storageKeys":[]},{"address":"0x111111125421ca6dc452d289314280a0f8842a65","storageKeys":["0xa621da3ffb62fc451da224cc8a3d1f372d8ccbb352c2bfa3e720da3c48059fb9","0x0000000000000000000000000000000000000000000000000000000000000003","0x1fcc635fcae3eaec7a08f959d90c47a1d6af4ae043d176775119851b0671c061"]},{"address":"0x7d4e742018fb52e48b08be73d041c18b21de6fb5","storageKeys":["0xfb44bcdd0398172ec04229ecdf2731caab3b9195751a90735b5969e03b3bac03","0x000000000000000000000000000000000000000000000000000000000000000b","0xb45839bf6fce2fb07c81ea76cb27c9dbd2f4deebae83d8422941d9480347336e"]},{"address":"0x4d5f47fa6a74757f35c14fd3a6ef8e3c9bc514e8","storageKeys":["0xf2cb6213b5623d708dfcd67c36c2ed4f83c2078e94c384263ce971f211d9d648","0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"]},{"address":"0x4a3411ac2948b33c69666b35cc6d055b27ea84f1","storageKeys":["0xa7b66874f3370752e3dde84f4673d050865edbef4ca32be3f9ac25ddcdb07333","0x000000000000000000000000000000000000000000000000000000000000000b","0x514afcf618258271a4b4c88684fbb4115a4875fbfa083cad0ffc950ca5a525fe"]},{"address":"0xaed0c38402a5d19df6e4c03f4e2dced6e29c1ee9","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000005","0x0000000000000000000000000000000000000000000000000000000000000002"]},{"address":"0x9ec6f08190dea04a54f8afc53db96134e5e3fdfb","storageKeys":["0xf0b998666205e50af945dcd5198861491334a6162cc24264d9a801634ee07ec8","0xa934b07068f5d95a11413ed6d08a4a1122dc4b8c14a6ab2d94f8b279dac63042"]},{"address":"0xaeb897e1dc6bbdced3b9d551c71a8cf172f27ac4","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000002"]},{"address":"0x709783ab12b65fd6cd948214eee6448f3bdd72a3","storageKeys":["0xcbd2373947cdd2067fc191106b14727bfb217748a108af3839f40f49c2be1a42","0x000000000000000000000000000000000000000000000000000000000000000b","0x542112b5e73cf836debcc9cb7a7cbdbd24d45f9fb8ce10af09a6afc836a4329b"]},{"address":"0x56534741cd8b152df6d48adf7ac51f75169a83b2","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000004","0x0000000000000000000000000000000000000000000000000000000000000002","0x2d88733daa8d83575c1c435b1a4140007e667f8dfbe2e5678ffec947a513e5cd","0x0000000000000000000000000000000000000000000000000000000000000008"]},{"address":"0x6df1c1e379bc5a00a7b4c6e67a203333772f45a8","storageKeys":["0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","0xac6acc773282ba7a764f2b799791dd7d7105566f74ce055188fd558a19461488","0x000000000000000000000000000000000000000000000000000000000000003a"]},{"address":"0x2b22e425c1322fba0dbf17bb1da25d71811ee7ba","storageKeys":[]},{"address":"0x5ee5bf7ae06d1be5997a1a72006fe6c607ec6de8","storageKeys":["0x000000000000000000000000000000000000000000000000000000000000003d","0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","0x0000000000000000000000000000000000000000000000000000000000000036","0x57a3719049ad85436e9ae52fbc627306957c42b02d9edb227af828619c2097f3","0x0000000000000000000000000000000000000000000000000000000000000039","0x8aae3feef13c70d16e32464c7cdab28ee5d9be819e571cee8306e467d963b8d2","0xf2cb6213b5623d708dfcd67c36c2ed4f83c2078e94c384263ce971f211d9d648"]},{"address":"0x018008bfb33d285247a21d44e50697654f754e63","storageKeys":["0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","0xf2cb6213b5623d708dfcd67c36c2ed4f83c2078e94c384263ce971f211d9d648"]}],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xba029de4e3f8e8ddcf1f2428fd4e597c597bf78b863ac3a21e2b3a2d1e680a33","s":"0x5d6986afff92e544ac367b4350cbca99d6793de99e8bec87b4206ec031ff63"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x95480d3f27658e73b2785d30beb0c847d78294c7","gas":"0x1450a0","gasPrice":"0xf2c61c1d","maxPriorityFeePerGas":"0x3b9aca00","maxFeePerGas":"0x352fa0df4","hash":"0x5b82506ff9f433c59334369ade1a354ab5623ec045cea9a5d752eb269357e147","input":"0x13d79a0b0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000ae0000000000000000000000000000000000000000000000000000000000000000b0000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca000000000000000000000000093ed3fbe21207ec2e8f2d3c3de6e058cb73bc04d000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000093ed3fbe21207ec2e8f2d3c3de6e058cb73bc04d0000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca0000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000249cc221145b2200ea910000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de53e7460ef7afa000000000000000000000000000000000000000000001e8442b782b0e11bf0130000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000505f1ccef3e8e4000000000000000000000000000000000000000000000000004350621babbdd2c00000000000000000000000000000000000000000000000000000000ee7cf06400000000000000000000000000000000000000000000000000000000ee6b280000000000000000000000000000000000000000000000000004307432fd8ef281000000000000000000000000000000000000000000000b0d5837493075000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000b4fbdbc8371a1a3ad3b92012c7a3cdad807b66410000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000a59a8846b8bc80000000000000000000000000000000000000000000000000000000000067acba6601bb7e5e78d2fcc20efd41f959e9d6387dc980ea6c0b746d60b6d354cfc386130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000004350621babbdd2c00000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000041ee173322cf8a41ab918a1c07f314c70d3bb7a3265452b3fa4237304c660ca5405bb59e4c714c3d3ef02bb922a2813a0393db0ebe6608ce7ffed3cf0ba31a86b41b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000800000000000000000000000040521fdd68cc3b1c9fad151e28ed9c5d59cc994700000000000000000000000000000000000000000000000000000000ee6b280000000000000000000000000000000000000000000000000000000000ee71428000000000000000000000000000000000000000000000000000000000698e2758f3ef4e1477de213e3c0a9dcc25112f3c62d65283db00bab8de61147b78f9a07b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ee6b2800000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000416feb4eafc0ed0774990d3a0db586f616e59e43efd56f281406a885a229d9bf7d1045f27334af5b2976fb9406c0b2e3bf913f53ce885349ef40f866ce394564391b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b0d5837493075000000000000000000000000000000000000000000000000000000042f50a43f474b800000000000000000000000000000000000000000000000000000000067acadaf362e5182440b52aa8fffe70a251550fbbcbca424740fe5a14f59bf0c1b06fe1d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000b0d583749307500000000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000194a7401066570960894a12b403f461cb61e8804b7b00000000000000000000000093ed3fbe21207ec2e8f2d3c3de6e058cb73bc04d0000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b0d5837493075000000000000000000000000000000000000000000000000000000042f50a43f474b800000000000000000000000000000000000000000000000000000000067acadaf362e5182440b52aa8fffe70a251550fbbcbca424740fe5a14f59bf0c1b06fe1d0000000000000000000000000000000000000000000000000000000000000000f3b277728b3fee749481eb3e0b3b48980dbbab78658fc419025cb16eee34677500000000000000000000000000000000000000000000000000000000000000005a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc95a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc900000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000072000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a7401066570960894a12b403f461cb61e8804b7b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024f14fcbc823c2b6610eed16861acffcbe51a2a90ab82850832e3cc2c0fca00338efe537d6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000480000000000000000000000000bbbbbbb520d69a9775e85b458c58c648259fad5f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002644dcebcba0000000000000000000000000000000000000000000000000000000067acacea0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab4100000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f00000000000000000000000000000000000000000000000000000329a8df2e4c000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000fa27373800000000000000000000000000000000000000000000000016886ddc8ebf346d0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab410000000000000000000000000000000000000000000000000000000000000000ea34fe1b3f7f11ec5e4decb7269561c40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000ee3ad9a3000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000041b2ba54ed17fac2e4ac220766b0584f296ad1c2f75a9f8dc2ec42802b1bdb3d3014b66e8dad81c8ea2da145d7e01a6b2b9c6e69befd525eea6e03f224b750151d1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000111111125421ca6dc452d289314280a0f8842a6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000008483800a8e00000000000000000000000093ed3fbe21207ec2e8f2d3c3de6e058cb73bc04d000000000000000000000000000000000000000000000b0d583749307500000000000000000000000000000000000000000000000000000004e33a8a8ceb4bc0008000000000000000000000343fd171caf4f0287ae6b87d75a8964dc44516ab00000000000000000000000000000000000000000000000000000000000000000000000000000000111111125421ca6dc452d289314280a0f8842a6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000008483800a8e000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000001575696f0548fd1000000000000000000000000000000000000000000000000000000000ecfdb315208000000000000000000000c7bbec68d12a0d1830360f8ec58fa599ba1b0e9b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009b8153","nonce":"0x8c3d","to":"0x9008d19f58aabd9ed0d60971565aa8510560ab41","transactionIndex":"0xa","value":"0x0","type":"0x2","accessList":[{"address":"0x111111125421ca6dc452d289314280a0f8842a65","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000003"]},{"address":"0x2c4c28ddbdac9c5e7055b4c863b72ea0149d8afe","storageKeys":["0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","0xb78fd7e1706dee4e251f6f3c52649313d4e032077a25f6c26ef8fbf58f357651"]},{"address":"0x343fd171caf4f0287ae6b87d75a8964dc44516ab","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000006","0x0000000000000000000000000000000000000000000000000000000000000007","0x0000000000000000000000000000000000000000000000000000000000000008","0x0000000000000000000000000000000000000000000000000000000000000009","0x000000000000000000000000000000000000000000000000000000000000000a","0x000000000000000000000000000000000000000000000000000000000000000c"]},{"address":"0x43506849d7c04f9138d1a2050bbf3a0c054402dd","storageKeys":[]},{"address":"0x51c72848c68a965f66fa7a88855f9f7784502a7f","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000000000000000000000000000000000000000000d","0x855fecc19a5b2954f79669b9159a26db2e4cb0d7267f46524e2c2c1f977ca7c9"]},{"address":"0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0","storageKeys":["0x3e08ff1ef5fd53d1a27fd282800ab79194dd4a6648f815358d353ea4157efc7a","0x618059526d7d1bb5608c8e3a0740d1f656fa8a764ecca600a8e0e3e0c313ce66","0x8e21cdc9dd0a66ed2d985c19e514a6541f735aad7ca1ea4003a35b3ef3cb6f60","0xa9fdaeb08f8d5a1b2586be857cbdadf862078cd37ba782d8c67dfff817d80d51"]},{"address":"0x9008d19f58aabd9ed0d60971565aa8510560ab41","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000001","0x86b36b0ef98917e826d119d2c3d7df4c38281de1547af50438862b3bd9fce496","0xc2d6a1a58a5a5363f6cd4d7c9a3938205e064ebbecd404ade0303aa3a2974d15","0xcf81bf28dd09222d16e7b2d24f1ba3af61b0430f1fc24926c74f2890a822032c"]},{"address":"0x93ed3fbe21207ec2e8f2d3c3de6e058cb73bc04d","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000006","0x000000000000000000000000000000000000000000000000000000000000000b","0x3a134e700e83652fee30ca28dc6c280083027769738a1cfe30d44f21f646d4e3","0x432ef227c4605f8e06ecbd81a1ca0141f51456d749b895c56390f4d37cd43424","0x432ef227c4605f8e06ecbd81a1ca0141f51456d749b895c56390f4d37cd4f48b","0x432ef227c4605f8e06ecbd81a1ca0141f51456d749b895c56390f4d37cd4f48c","0x4d75a5e67977ca93832f8e62a109d447c715d1eb2febef9940b0d54801616d29","0x64702e325ebfa8b5d0f0faa55e253d188d8b28e2a617a8d734ae2832a43885da","0x64702e325ebfa8b5d0f0faa55e253d188d8b28e2a617a8d734ae2832a438888e","0x64702e325ebfa8b5d0f0faa55e253d188d8b28e2a617a8d734ae2832a438888f","0x7b595f6faf06fce0371919606877ae2d18ffbb2944ff890faef56c54fbe3236d","0xc26da970a9e7aa1155862afe22cc68a5baaa04d377cfe80e4a6ead3881f907f6","0xf52800f42992003eb2d6dc4b25c118c160e654ad432622efb31ad00a3b501231","0xf5e8dce8926b9688ff3a01a258683e50267b2de8274e6d1ab3852b3d0554f3cf","0xf5e8dce8926b9688ff3a01a258683e50267b2de8274e6d1ab3852b3d0554f4aa","0xf5e8dce8926b9688ff3a01a258683e50267b2de8274e6d1ab3852b3d0554f4ab"]},{"address":"0x988b3a538b618c7a603e1c11ab82cd16dbe28069","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000009","0x0ae6c4a77b893b11bbba97c6e6bd97abd0b02cdf5a7d906ec6d44823a098ceba","0x0ae6c4a77b893b11bbba97c6e6bd97abd0b02cdf5a7d906ec6d44823a098cebb","0xe8d408941fff9b8763fc0b689b4d79f9586be7573fcfa4add61085b973ba93e3","0xe8d408941fff9b8763fc0b689b4d79f9586be7573fcfa4add61085b973ba93e4"]},{"address":"0x9e7ae8bdba9aa346739792d219a808884996db67","storageKeys":[]},{"address":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000001","0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b","0x659f5b53123b3e7a886575e106645d4d7c5af120440f3f409542b3987fa1ea07","0x6a549e065c450a8ad8c8481058e2aba2529ffb7fb907fb556ef68260818834f1","0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3","0x77d5014beb071d1c3dabbdbdba61f9a5cc3ffedca11c102ef7e2fae619d04e12","0x88afba62e6432e4d0a3e39a2be587d39c0b93368d66cedb0531bb5292040a552","0xb219bc7532d79ffbe5d5bf758f23240deb6e2ea97104394a6f0637e9abee010a","0xcc236083e86ee3df0f3160002f381f1404bd44c4dec1322196f34d52548202f5","0xef9aa6e78baadd9fa11efc775ebe24781e6dd67d28095a72c3297161bfcd60c2"]},{"address":"0xa7401066570960894a12b403f461cb61e8804b7b","storageKeys":["0x0874b03f1c43eb2919e751d2f7e043ad91b39bcbd9af4d485b720c9cf98a1def","0x0874b03f1c43eb2919e751d2f7e043ad91b39bcbd9af4d485b720c9cf98a1df0","0x0874b03f1c43eb2919e751d2f7e043ad91b39bcbd9af4d485b720c9cf98a1df1","0x0b65d7cdec2e50646adc85a69fb30bdc1100ed74b3440fddc7024215648b59c9","0x0b65d7cdec2e50646adc85a69fb30bdc1100ed74b3440fddc7024215648b59ca","0x0b65d7cdec2e50646adc85a69fb30bdc1100ed74b3440fddc7024215648b59cb"]},{"address":"0xbbbbbbb520d69a9775e85b458c58c648259fad5f","storageKeys":["0x6db65019675678cb243c6305f4ecf14258fa83793bcf42e5492a45c932c05c06"]},{"address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","storageKeys":["0x2a306d7595987ab10375e6e054d7489cc80c1cf5351587e21ffbd126573f06c6","0x32304771d801f02c7aed64e1eb2c0f928ee3122c7c305f5d04c5a559b1a49ce5","0x345249aca4b4faee8e09da154afb988b7e2b7457ff02d5c39daf56f89173566d","0x3a3aec5717af62dfc0f5ede964a3e44cef7c5ba4453963ee6e3545ea9708690f","0x51fedae7ddec141eede6f408daddc5420277496f3e48141418e4c0bc014e581e","0x9d98752c354deebddd53535455198eacf8cfb934237d3523207f70386be5e3dc","0xeea86b983718bb88d27139abc6ecb136484560a140dc32e3cc3e191af9d73f7e"]},{"address":"0xc7bbec68d12a0d1830360f8ec58fa599ba1b0e9b","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000004","0x000000000000000000000000000000000000000000000000000000000000002c","0xb07ee67653e3f0ef45943fd8833b7913887edad0894fe900377f1651656686a0"]},{"address":"0xc92e8bdf79f0507f65a392b0ab4667716bfe0110","storageKeys":[]},{"address":"0xdac17f958d2ee523a2206206994597c13d831ec7","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000003","0x0000000000000000000000000000000000000000000000000000000000000004","0x000000000000000000000000000000000000000000000000000000000000000a","0x31adef62206227419133dd9a6b4041532c22595206a596cf74f19493bfc8f368","0x7a464961c125fc990109c53fc6324f805cc788adf74934778792f1f7cfd292ba","0xbcdfe241f132b38477ee35d4e497c725e4d20778d490eecefb1940b28dbce0ca","0xd06ef82a07326a6e964ce1700825547c428fbc96deb2e33e7e20498001080d7d","0xef1b20f9de18ac869a7b33e8c6e51cdf084ae4e663bce14d9f251b619f74b281"]}],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xbbe9f7b3cbbda0de73d935b587ae12d7d06f695d26d29091760132b3dadf34bf","s":"0x4e613726d2c345e93e57b0355144f9919e27c7e7bff32fa8a5686d1f0d852509"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x448166a91e7bc50d0ac720c2fbed29e0963f5af8","gas":"0x3b0e6","gasPrice":"0x1c3deb527","maxPriorityFeePerGas":"0x10cb3630a","maxFeePerGas":"0x1c3deb527","hash":"0xcb1148e60c0fbda91cde4a180005d091ecf5beeaefbe681488112609bece49cd","input":"0xa000000000000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f564000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007676540735247f8d0000000000000000000000000000000000000000000000000000000522b53ae2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","nonce":"0x4ec7d","to":"0x68d3a973e7272eb388022a5c6518d9b2a2e66fbf","transactionIndex":"0xb","value":"0x14d1d15","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x13293439aa0809b798ac16f99c122ffc865f1b89800bd9901e28c9ccc2013a97","s":"0x54b7bf712f6fb15d19eef809bd5981787688927d4c8d6c059415bd21fd224313"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x3d9aae030b9661e3605b3acb5d0385ede221a0cc","gas":"0x4fec0","gasPrice":"0x1685ee529","maxPriorityFeePerGas":"0xb133930c","maxFeePerGas":"0x1685ee529","hash":"0x003ba082e30ee324e607e4ac6f2c7508000fdb24f16c343fbe5189c03ffb1e5c","input":"0xa00000000000000000000000000000006ca298d2983ab03aa1da7679389d955a4efee15c0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000303bfcaa3bed4d7b00000000000000000000000000000000000000000000000000000002177cd613000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","nonce":"0x293ea","to":"0x68d3a973e7272eb388022a5c6518d9b2a2e66fbf","transactionIndex":"0xc","value":"0x14d1d15","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x7c1affcb10e596b9d251f9eae353131be2b17d3d64becd1e372c1267c8efd2fd","s":"0x50baee656049384fd8604a16cf30bc7557cd6f29171c715ef52fc04d254cfca3"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xdbbebe8b8b7649d5a48e7c3216a21bc03991ca10","gas":"0x42750","gasPrice":"0x169fbb01d","maxPriorityFeePerGas":"0xb2d05e00","maxFeePerGas":"0x1a6ebfb19","hash":"0xb8092730a5725fe1972fddd24f564bb86156cd7ab909a541863defba97169305","input":"0x088890dc000000000000000000000000000000000000000000000000084dff05a364d1b600000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000dbbebe8b8b7649d5a48e7c3216a21bc03991ca100000000000000000000000000000000000000000000000000000000067acacd80000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000d3c27b9b6ab458c43fb90e9fe1f5f6d9ab27f856","nonce":"0x20d","to":"0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e","transactionIndex":"0xd","value":"0x25bf6196bd10000","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xced2b40cfb028e0fa57b912d00db86351dfef71060031a49a17d84fae15b0f1b","s":"0x85ecd7b5548755d9148b7b29bfd05baa7455106542176f2e30841b81a675af0"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x3c7e525fe0f285f0c12690c3bd29414bb9da32d9","gas":"0x43c19","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x155c85f1c","hash":"0xeecdae20ed5858867514a779a7db385e9cdc0b47d60e42e16065885fd0e3a026","input":"0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000067acb3d300000000000000000000000000000000000000000000000000000000000000040a08060c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000160000000000000000000000000df35988d795d90711e785b488bb2127692e6f956000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000067d439a9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066a9893cc07d91d95644aedd05d03f95e1dba8af0000000000000000000000000000000000000000000000000000000067acb3b100000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000004153d3b8993e7b444ab85a97945c9a2fcb2d8ddfb07b2fc1a482fd152fc53ce8341f7e51abaf1d1c0a5d629b86a780cc4fdc98a25d37628325c98ef1f10cf471b71c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000009a8603c564fb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000df35988d795d90711e785b488bb2127692e6f956000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000fee13a103a10d593b9ae06b3e05f2e7e1c000000000000000000000000000000000000000000000000000000000000001900000000000000000000000000000000000000000000000000000000000000400000000000000000000000003c7e525fe0f285f0c12690c3bd29414bb9da32d90000000000000000000000000000000000000000000000000029d7f4c13c86eb0c","nonce":"0x614","to":"0x66a9893cc07d91d95644aedd05d03f95e1dba8af","transactionIndex":"0xe","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xac0750441805ba729a2d6a431cd6220a3867f4fd4efd607ab3bf22faaff1a92b","s":"0x38faf7f5d42742c4162ffdb0fcae6ab6d9281c4e72fad72e6a0b774ad72f948d"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x4027c8029cee51e7377e76f5fc1c6cd4dfa69ec5","gas":"0xb41d","gasPrice":"0x2540be400","hash":"0xa2e968cfadf0c1f0984e8081456287c7b9bc6c52f71e1ba49af1499b2d9ec874","input":"0xa9059cbb00000000000000000000000069e60cf54b3349df5cad5029fcd3b1bfc09af1cd00000000000000000000000000000000000000000000000000000001fd0042a0","nonce":"0x150","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0xf","value":"0x0","type":"0x0","chainId":"0x1","v":"0x26","r":"0x786889a4b78d5117c8150d413d9f353fc2c6bf2a19c20da3e4549fbd4d70f5e4","s":"0x6ce5e3b76ec90d865ee8ad30bb2a53de1dde659e5e81d68570cddbd6aed22f63"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x08f7791f5b0eb332def302513f04ce59c05c97e8","gas":"0xc482","gasPrice":"0x1fb1a2f35","hash":"0xabdf54de29229e5c6f6779755a9ac543fa594a2295d0c1cf4af0da9c2263d0e0","input":"0xa9059cbb0000000000000000000000001ca5aa5b1dd8d948bb0971a5fb1762fe172e004000000000000000000000000000000000000000000000000000000000ee6b2800","nonce":"0x4","to":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","transactionIndex":"0x10","value":"0x0","type":"0x0","chainId":"0x1","v":"0x25","r":"0xf5f70d68efec3da3ff644983797dafd3687b8bd8f18c18e8c1404567c1e8f540","s":"0x5c478fe1cf3fb02fbc1547f7c31fe7139d12065d98eff9254269e4f0cd0858ae"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x2703983059c7b60515b4fab6fb8b301f7781f5c6","gas":"0x5208","gasPrice":"0x1fb1a2f35","hash":"0xe8e76c20a0e77d0bfe69dffae6d9d546bd7b1963d670dbf2b73a5f8ba8822565","input":"0x","nonce":"0x155f8","to":"0xa4760495c4ea1c3b2c64793b165c506351906801","transactionIndex":"0x11","value":"0x153ffb5a54600","type":"0x0","chainId":"0x1","v":"0x25","r":"0x6d482dead73fce0e9fc8fd2ff13967ac602fbcb2f4bb5285f869d6c23c6ed29c","s":"0x4e57ad7816dfad739477f05939216561d7971225f4c02ac4816d31bce0724052"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x6d5c7752f6e5f1cdae3b4718a8e15b0f9416b510","gas":"0xc692","gasPrice":"0x1fb1a2f35","hash":"0x58167cf16529bab53c19a191ce3475d12294e1574976c29ea0015ac180bef90d","input":"0xa9059cbb0000000000000000000000001ca5aa5b1dd8d948bb0971a5fb1762fe172e0040000000000000000000000000000000000000000000000000000000000ebde1f5","nonce":"0x1","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x12","value":"0x0","type":"0x0","chainId":"0x1","v":"0x26","r":"0x2d88f8a1cfe7b505e1e6775b337007b0bd60393682266e4e998a97c819092447","s":"0x7672b9080bdc8b48c81b7db9b80244a5fe387f3528d24abf0883792c165f5334"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xe8832a868c091263ed190a9f4be304a03895dd91","gas":"0xc350","gasPrice":"0x177b6d189","hash":"0x43ab9a98d1d58e192f5fd7184e5863ae551d469066cc65e4756a3acf6f27345f","input":"0x","nonce":"0x32582","to":"0x7f68e335facdaa3576352f6a9ec606602a3db531","transactionIndex":"0x13","value":"0x18de76816d8000","type":"0x0","chainId":"0x1","v":"0x25","r":"0x7e83c3344666c5f111ce33391845bb12dd362279c978b636d8309d90d55867a2","s":"0x9f324494bcc17f34a531e318f34b9ad02921e4f9a90ea84a7881e2ef4ab68b9"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xe8832a868c091263ed190a9f4be304a03895dd91","gas":"0xc350","gasPrice":"0x177b6d189","hash":"0xdb96be5aa92ad0a8e35aa8f609097ae31159d85c5cb1b339d901dc4c9e69e99a","input":"0x","nonce":"0x32583","to":"0x12086e6efa2cd247a68f625bc277ee78d807a6ee","transactionIndex":"0x14","value":"0x18de76816d8000","type":"0x0","chainId":"0x1","v":"0x25","r":"0xed3db90a65cbe9869e7c1a600fcfd69a0871c1adbc95f40b57dce7a0d6b58bb0","s":"0x47b85edc638be87aa29d57931bfdf79d2c4d9c441f0acbd0b0d9dfeb49585702"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xcdd37ada79f589c15bd4f8fd2083dc88e34a2af2","gas":"0xb5a7","gasPrice":"0x169fbb01d","maxPriorityFeePerGas":"0xb2d05e00","maxFeePerGas":"0x3afa08644","hash":"0xa41cde38b4fcbdc0c0b6e643b5288610262de8ce993c7404bd490506fc1bb876","input":"0xa9059cbb000000000000000000000000a80fd831bf63248715bf75f4cb8e1b05c6c78709000000000000000000000000000000000000000000000000000000034a825cc9","nonce":"0x87137","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x15","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x218a4bd2a6b3495699564ed7ec29ea69a238726f98833802cabc741d5e5d7e5a","s":"0x5e1716f7a94bcf408c0892ec937dda8fb9528ba5ee4da344081ab659e0ba344a"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xf89d7b9c864f589bbf53a82105107622b35eaa40","gas":"0x15f90","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x2e90edd000","hash":"0x07e5ec11dbb801c5f30121a37db5571310b32f3d2d5261343fa4b4c782b6b06d","input":"0x","nonce":"0x54e6b8","to":"0x54620b9a8a2c43aa8ed028450a7ce656a9c69feb","transactionIndex":"0x16","value":"0x555738c62c8fc00","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x4bf26d1cc54fd35d0a4b83bb253c9dbb75087467ecdc4b89a3e8b0d7e0f9d560","s":"0x31c1b0d457666ae7ede45204844c276c069b57064218bbabbd29b01926da2394"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x2e5658ccaeb82f0a517d30d1b29df417c3148fea","gas":"0xa498","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x2cb417800","hash":"0x8f4da8ecc9f0390e1467b54366ad1974e078c3a7ecc051442dd1b4caa1addc9e","input":"0xa9059cbb0000000000000000000000004b2afc6f2531f4d738153bac598731edf5fe5b0100000000000000000000000000000000000000000000021e23d2bdc4a3262932","nonce":"0x10e","to":"0x6b175474e89094c44da98b954eedeac495271d0f","transactionIndex":"0x17","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x20d08882264ba33d2a6109cc08e193f31e5cee84d4d16a0e712d6e352914f947","s":"0x7cd4fea1d36f129a0542575fc676b223c32713d29ce8b2cec9e3fe37a08cf6c6"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x0ab3fbc9025ece0ea4e0f9d29fbaa94b70923e37","gas":"0x5208","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x9502f900","maxFeePerGas":"0x12e60e61d","hash":"0x3adf8f3f289cce48cd386b284aa3ac95fca2a69c2315ad12d24d550197bde34c","input":"0x","nonce":"0xe1ed","to":"0x05a88e389a183fe3bb215d8dd7fefdaf0820d48f","transactionIndex":"0x18","value":"0x361282657b4000","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xb0943b83e6b637281913f7035e343324d7c34eda61d997f891660914fe72a4b1","s":"0x5c1e00bdb4237c3962eb18526d2b038463789179e595926f7cdd551d895a3465"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xe76818357440e3d60657614ba1a96755b3f93561","gas":"0xfc61","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x155c85f1c","hash":"0xe93910d48b1025162f5da09adae1a8e632933a468365736d6db432918733764e","input":"0xa9059cbb000000000000000000000000583e5481f07f0a545608e79ad67546f081d62457000000000000000000000000000000000000000000000000002386f26fc10000","nonce":"0x227","to":"0x470003716682791bf2b0e1548ab0a1ffbafb9556","transactionIndex":"0x19","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xeb4f07bff9b3af5879c66b26591fb5489f725320e52ad7f6ea14f70d02eb56a0","s":"0x7cedeb81d879b8cc846281ebfd1036c289f16b96a04a7188bfd78e9481a89257"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xae2d4617c862309a3d75a0ffb358c7a5009c673f","gas":"0xe9e5","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x383a44302","hash":"0x4fa5f6cff758de4b12280a3c0164f17805171de5449f674ad09491c5e12675c8","input":"0x23b872dd000000000000000000000000ccdb2a452866852526b9df2cf193c68c5c905acf000000000000000000000000ae2d4617c862309a3d75a0ffb358c7a5009c673f0000000000000000000000000000000000000000000000000000000000ec04c8","nonce":"0x1390a6","to":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","transactionIndex":"0x1a","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xedfa9644b0e9da17ff0b856b564faf82df60a7fa7c777195ea76b750c7c1ae4b","s":"0x7c5d87a46b4772a9ca52594f8a873ea7b09c505cbe17bc5483f59bfc3e9bd8ba"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xa1280ba719747c88eab200f6d9b5f34b95c64ea8","gas":"0x5208","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x1e58c383a","hash":"0x69677026a0d9f21657ed8a0ad2f546419ac238ded912d7649bca31eef13a1d15","input":"0x","nonce":"0x230fb","to":"0x17e80b6f8f26578352a9f075296d52d8c2a8dcc4","transactionIndex":"0x1b","value":"0xd4dabbdc0801","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xc80c8d20f71896a7d334a87c92469ce237b79dfca063f403472ea9394cb93436","s":"0x61cb301276825b89a16e3d62d0aff9b515180aae207d15987a78ee476596b7b1"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x0ded44218b741a47bfdee8eeb0223fb924936fc8","gas":"0x5208","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x12e60e61d","hash":"0xe22ce2fa01a047f95cec4b95cc9d1bd738be0155a6583d38d89e5ebfdc5e0ea5","input":"0x","nonce":"0x39","to":"0xa1280ba719747c88eab200f6d9b5f34b95c64ea8","transactionIndex":"0x1c","value":"0xb141d7b2088518","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x362cfef9463dd9aa4bae86712b736bf72602a28cfeb8f030f59070e778f23840","s":"0x44396558ba70131450c0e4125a34be095466959dc8d895fd750b070ecd5799ee"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x877ccbf3f1e824a902b51edb02b0b9dcfaebd2eb","gas":"0xea60","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x165a0bc00","hash":"0x1206b4f0f44c40495356073d797157a88f48fbfc99c4867d4f6f4530948c02c3","input":"0x","nonce":"0x76c3","to":"0xa03400e098f4421b34a3a44a1b4e571419517687","transactionIndex":"0x1d","value":"0x29d8ae6cf010cf8","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x85af5aecbc57714d1b3221afe7a5ff84e5120fcfdba27a78e0c0347abbdfd10b","s":"0x743c5ce0b92b1301f6126db53668db5d0736888bc59030b82ee687142e83bd4e"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x63c79fccd0a21e4a4d87056a0efe3b85d8c373d4","gas":"0x186a0","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x2a600b9c00","hash":"0x6847992c292a6cc9dae439ee7f21be121bc14d8d53f14831adf132117c39cdf0","input":"0x23b872dd0000000000000000000000002f65eb145cd8210b903894b0292327cb228f25d3000000000000000000000000089e290a97cb858930cc97fafcf544b2579d4f60000000000000000000000000000000000000000000000000000000000717cbc0","nonce":"0x665c","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x1e","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xd562589d89ab9ac07f4b48d265ef85deb3aabe1875537e27729edcb1722d4e38","s":"0x2a549171acfee238a09bfa2d0843b0ddb48a5630787c9eaccfea9f4a6ffe5ee"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x3b4e467cd0429e8b53f5479d614ced5a7ef5326d","gas":"0xcd7f","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x155c85f1c","hash":"0x14a4d8b9662bcce13477bc72e1f232d257f8c57457e7a82c95997864460ec578","input":"0xa9059cbb0000000000000000000000004d5a7700b5802c7abcc0da6158f5f5f890e81dad00000000000000000000000000000000000000000000000000000000184c3ae0","nonce":"0x1b","to":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","transactionIndex":"0x1f","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x7be4c82d491b5616d59b54f8a212267c34880e157fb567da69ef408600ca5ced","s":"0x2c239372b01b912fe7318d637c98c57ec074d98040673221250b7ccd68c38696"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xae2d4617c862309a3d75a0ffb358c7a5009c673f","gas":"0xe8b8","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x383a44302","hash":"0x7eef336c391810e98f691d8de7d66000bcb228c67cde1ecdd8688e75d861c6fa","input":"0x23b872dd000000000000000000000000a9f48e9a932b621d7d16c19f42ec6c7067647908000000000000000000000000ae2d4617c862309a3d75a0ffb358c7a5009c673f000000000000000000000000000000000000000000000000000000035b7fbd00","nonce":"0x1390a7","to":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","transactionIndex":"0x20","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xe6742fa28544c921b52f6764aa89fa0dcabfa940be2ca941da5068f192cff1c3","s":"0x370260a0f95b3952a6e71238a0e23bae474d033128dc6123ddbfb2e1cdecbe79"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xe9818dc6aa29e1c223d2bf0aeb6dc89c4e751711","gas":"0x5208","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x12e60e61d","hash":"0x4ac413924dd80bfc02e5a3e6b925f84f1a1ccd754bfafe67b0cff9814c1e7146","input":"0x","nonce":"0x0","to":"0x9a10da8ce77f26231860764a2caab36e70584c4b","transactionIndex":"0x21","value":"0x7e7d8dea30b118","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x949645e8925bd02b419813c222c48949a391489ffdcd26434759a007ac5baa36","s":"0x128ad4b7d58b26e847373918eb32dfc83bbddf391344a817bc7f6bf2dbe7be88"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x21a31ee1afc51d94c2efccaa2092ad1028285549","gas":"0x32918","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x17bfac7c00","hash":"0xf89de9f9518c8cff6a6a7826b5efc6cdbbc332c436757e5c02bde8c1eba98201","input":"0x","nonce":"0xac94bf","to":"0xef9dbd375755993ed977f40aa1f375674ae1d209","transactionIndex":"0x22","value":"0xdde9370cc895000","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x281e17d2fda918a034622a598923e88539669a9d7561d6235430449e9abc480d","s":"0x5e8bd2520514ef3d41b5dcd18ca3a56018f8bb9b0edbccc8b12b7f186e00f3d6"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x28c6c06298d514db089934071355e5743bf21d60","gas":"0x32918","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x17bfac7c00","hash":"0xbe6cb5293e347610f0acb65ffa9e50c004864e00b7c0f514fc36295bae274493","input":"0xa9059cbb000000000000000000000000f5213a6a2f0890321712520b8048d9886c1a99000000000000000000000000000000000000000000000000000000005166cc0e40","nonce":"0xb1e2d8","to":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","transactionIndex":"0x23","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x402391a1e196ff33a950ef115ed256d805193a1997d54511d20d7715256572c8","s":"0x380b2133ccf18e808b4c2221224b25026feb56eae32aa49854f589167438c03e"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x56eddb7aa87536c09ccc2793473599fd21a8b17f","gas":"0x35d14","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x17bfac7c00","hash":"0x6c953dced74e5a95934816cfd6522876433b19204df3d510f61189da2ff1e40e","input":"0xa9059cbb000000000000000000000000d16e47e4007d9d07986562dbd098cb199ece431a0000000000000000000000000000000000000000000000000000000005fac300","nonce":"0x73cc48","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x24","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x26a78e870af4b8a5146c4eafcc6fdbe9f5f83bf6d9a6389affbbaadab6aff6f9","s":"0x342573513e082138dfce509d60df256fa29a9edc9a7c6cc23a65a8d8ddfd1fc7"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x4976a4a02f38326660d17bf34b431dc6e2eb2327","gas":"0x32918","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x17bfac7c00","hash":"0xc21a316961a3bc568e46cf8312a7f57d8c5338ef65db14fcf16530ce7d796b2c","input":"0x","nonce":"0x48c55b","to":"0xd960893054080fee6a0682a1a0b199b6b7c91f51","transactionIndex":"0x25","value":"0x7fd7c0311e11000","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x6dfc748cabef3c3a3aef2db98a5ac741f4ced22370c7c74292ebc0af4bd2e176","s":"0x62724651b9637748cc251dc7074c22b7e9fbd13144d26589b2ce2f8358c78f13"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x9696f59e4d72e237be84ffd425dcad154bf96976","gas":"0x32918","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x17bfac7c00","hash":"0x1b1675025e7d9a53920b7c30a3a6d0c756e7d8c0fe1bb5adee3b7c89bb5e8857","input":"0x","nonce":"0x70d5f5","to":"0x16994ed5acb6e0511fb4d74610f1fae6bdebd326","transactionIndex":"0x26","value":"0xf1a39c0c12000","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xd39716eaccc592e34a5a5286ff8076afdef23e4a46ef4c4d3e4ad12f2b63a7fc","s":"0x1a2639c1e8e1ade401ca1ebc69acd0a4820c5ef76ef653c6e990a6d7f838f298"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x28c6c06298d514db089934071355e5743bf21d60","gas":"0x32918","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x17bfac7c00","hash":"0x7d194f8d4791a3a94e12d6f4a1d6387b9a80a56c5bf5c703ae07f8f7578d811a","input":"0x","nonce":"0xb1e2d9","to":"0xb0d7ec2c3582ca2b346cb52253d80b4ee54bf36e","transactionIndex":"0x27","value":"0xa366b9f0df8400","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xee12541e650366415e29274d4fff1ded171cb17636db4c78a656f409078edd16","s":"0x2a730a743d6e87f9cc0ea40c10e3c40325179dcc6e7e3fafcf7bd26c2c849b16"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xae2d4617c862309a3d75a0ffb358c7a5009c673f","gas":"0x7a120","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x383a44302","hash":"0xaa786fa0458239668906f3a56edc86227139e905fa7d8941f7105c9367ca9f1b","input":"0x","nonce":"0x1390a8","to":"0x2de9da5294777fc8096f00cb2b4eb8b69b3949b6","transactionIndex":"0x28","value":"0x1993e2c8b06b8","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xe7f8bff0816caf4b797409886f92d992e08c61ccbbbe11562416bcfab182f81a","s":"0x3a842a72cbfeee05d11654ef970525df6cef1e0bdd9b2eebd9b3324cd38c9642"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xccdb2a452866852526b9df2cf193c68c5c905acf","gas":"0xdd65","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x165ae490d","hash":"0xc03043d32ff40574a8ba7d98801de5c0a002bf0e4b878a3de82ed5d793b137e9","input":"0x095ea7b300000000000000000000000089e51fa8ca5d66cd220baed62ed01e8951aa7c40ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","nonce":"0x2","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x29","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x27d5ba54761eae138407c78ecb735190c210d9a4c21dcb40d8950880a649239d","s":"0x7c2b39e969f422f3f6bd3de6cc53e6c0747d19dda29b1bb9a761c5bfce930cc5"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x98c75294f194ae3003757e4b446ef89a85958964","gas":"0x15f90","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x2e90edd000","hash":"0x22ef7ab64d4b9899fcd97b32da0aff3b08091e20695e8a0ca00b8992941e4840","input":"0xa9059cbb000000000000000000000000f89d7b9c864f589bbf53a82105107622b35eaa40000000000000000000000000000000000000000000000000000000019efb5e4b","nonce":"0x3","to":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","transactionIndex":"0x2a","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x77111d50c2cd0af243750efd1491467ce1ce18328392fe8543fa1d38fbf74b83","s":"0x6f977db60275e6690df3301560c7b7570f1c67279cd4e9790aac8c9da08c0d92"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x77fecdb2385b9b043f4ae2fbcd28316f07c250ca","gas":"0x15f90","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x2e90edd000","hash":"0x23be535268a689559835fa0a8c1d123104255a575fb1d41404a5da0dbf1ed538","input":"0xa9059cbb000000000000000000000000f89d7b9c864f589bbf53a82105107622b35eaa40000000000000000000000000000000000000000000013da329b6336471800000","nonce":"0x176","to":"0x5d285f735998f36631f678ff41fb56a10a4d0429","transactionIndex":"0x2b","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xd1be30e16903e8aacdb5c9f632f0699c60e4634387f2fd45d0c3f8bc10d7618b","s":"0x7ab2c06dc09b4de17b72028f587a3598dcc7679ddef22997aeb82e67a1325354"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x13f27352234fafd2573e8d367a9bc3adff3de7d5","gas":"0x15f90","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x2e90edd000","hash":"0x9b63af4021612f68de031580ccc965f1b1a83eaaba6a2b2dc9d10f4d878e1cec","input":"0xa9059cbb000000000000000000000000f89d7b9c864f589bbf53a82105107622b35eaa4000000000000000000000000000000000000000000185a83b50dd42ac8e800000","nonce":"0x1df1","to":"0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce","transactionIndex":"0x2c","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xb13981326c42384c3b820da606362b53e3d0b9d1ea28a632c4af4e65de2c81b8","s":"0x1f0e9f991350aeb2668e0e8ffdd1184e2b6a61af7100053d03db5c6aad77bf44"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x4177161b2ba338bf13afa054279bd09d9614741d","gas":"0x15f90","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x2e90edd000","hash":"0xf368894f1355efe1727ba9f114200511ee6fc163d76f38be32d5e54f71b47761","input":"0xa9059cbb000000000000000000000000f89d7b9c864f589bbf53a82105107622b35eaa400000000000000000000000000000000000000000000000c3e7db66366efea000","nonce":"0x37","to":"0x5afe3855358e112b5647b952709e6165e1c1eeee","transactionIndex":"0x2d","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x6fda94a4fd2704d20c17f28ef7d85dbdba79a26f8f063b4971d6f1a670244ae0","s":"0x4f6c318a62d11c954330d81d464097d966b51b797595b385d00e5768672a9e7b"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x0ec9bacaf78cdd55ea34ee5dfac40508d500badc","gas":"0x15f90","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x2e90edd000","hash":"0x82be118c6c293aaa374008cec19c9a5e0731e0fc8be04a64b32e02734add784b","input":"0xa9059cbb000000000000000000000000f89d7b9c864f589bbf53a82105107622b35eaa4000000000000000000000000000000000000000000000000000000000e52383f0","nonce":"0x1e","to":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","transactionIndex":"0x2e","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x49f13d2920854327a180f53d52133a258d420b0eefbcf0de9cdd0182afa2b838","s":"0x309755060eef025473e594cf2f1a34842f6f866560de251a7f53d57a232bc669"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x12439d5f859382cc8e96f0489dc331fdedee7046","gas":"0x15f90","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x2e90edd000","hash":"0x090b168d130aae4b731265a8b5daf3011f541624d073c4d273c443aeab3bab81","input":"0xa9059cbb000000000000000000000000f89d7b9c864f589bbf53a82105107622b35eaa40000000000000000000000000000000000000000000000000000000001b7a4d40","nonce":"0x3","to":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","transactionIndex":"0x2f","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x3841ae133a8326e3fe03c7300f29fb98cf3540deaf3c03923b9977ca04129cab","s":"0x2235960e8225af149940ddcedd94629963def9a8b9b58ebc24d442b0eece802e"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xff54a5840c1eacc3a8f9754332fc5d779626a4ab","gas":"0x15f90","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x2e90edd000","hash":"0x5b4d24581847b4de674e7f3678fb095b9d94df034ec460b377a7577b128d0634","input":"0xa9059cbb000000000000000000000000f89d7b9c864f589bbf53a82105107622b35eaa4000000000000000000000000000000000000000000000584da091a8253d360000","nonce":"0x59","to":"0x3506424f91fd33084466f402d5d97f05f8e3b4af","transactionIndex":"0x30","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x5e3858325222504cf447637fb8aa29235cb5508d7efad49ce115712649c2acb8","s":"0x61fe5db81cadb1c7cdd60e9a9df83043c24dce2ac8a41fc9fbfc5b99363aef66"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xb62b3e0b34db4d62d6156365a2bdb0656353a200","gas":"0x15f90","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x2e90edd000","hash":"0x14279d2244f31b25738fcc83fea5680760cbd9b1cf62b842952060fd514c779c","input":"0xa9059cbb000000000000000000000000f89d7b9c864f589bbf53a82105107622b35eaa400000000000000000000000000000000000000000000000000000000615782880","nonce":"0x1","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x31","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xcdbf5e57016750833b4959c44e75bbb956578d6839be3f672e0dc9d274878226","s":"0x1e744968c12d66cdd3b85140e139154855b060695e2a8d79e546990ca0aa1945"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x91048879e83820fe5615d213236e592371b520d3","gas":"0x15f90","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x2e90edd000","hash":"0x39e65638a79b07a9b175a2c6113b07ffd05729c4b027d2cc763b7fda80b5c962","input":"0xa9059cbb000000000000000000000000f89d7b9c864f589bbf53a82105107622b35eaa40000000000000000000000000000000000000000000000000000000000682bdb0","nonce":"0x4","to":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","transactionIndex":"0x32","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x7db5c5ba66e1a7b159afc052d571f2723c1e9de0a32b411334d974def8b19efa","s":"0x30ea43f4cc267fe8e588c8c1cb710ffd060ed1c30729ecc3cfa8bb2d922b120d"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xdbab4734f636613e2dcc0181e46b04effa1028f3","gas":"0x15f90","gasPrice":"0x12e60e61d","maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0x2e90edd000","hash":"0xdbada6d6b73e44e385a466a6ae0c27e722b35fb8f96eb0b9606c4824fef79b29","input":"0xa9059cbb000000000000000000000000f89d7b9c864f589bbf53a82105107622b35eaa400000000000000000000000000000000000000000000000000000000642b6e6c3","nonce":"0x49","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x33","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x399771cf8a523a0f7192aa5fa25478e3a11b60113a94ffc5672ec230b28cf8e2","s":"0x6f0ad28e202a0accb01c030ffc0b5935d084357d1c87aca0cff047c2c6acf72f"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x02e93fc60a245855dcba6e0ed553df40a217ced5","gas":"0x7a120","gasPrice":"0x12a90da52","maxPriorityFeePerGas":"0x73658835","maxFeePerGas":"0x25521b4a4","hash":"0x2476dbdf23c798a1924d2e933b8e38ad136042065ff16d0160f5ef98ca42d1fb","input":"0xa090949a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000002e4f497df75e26b997745d66cacc694e8c0cd7600d41ad8a51ba138eba6372baf5b02e9f669000000000000000000000000da4e9aee5402aa9eee97db31cf5959bf2aadbac10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000022457fa000000000000000000000000000000000000000000000000000000000000926938a000000000000000000000012540000020067acada3000000000000000000000674d712969051fb8ce005cb5acc8ca5df4549b46f24d6f0971928a6b652f0b1843ebfa9d5a05a41b889b779587f2e9646772acfcf16e7118dda0fa88414bbe30000000000000000000000000000000000000000000000000000000022457fa08000011d00000000000000000000000000000000000000000000000000093b1a00000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000011d000000fd000000540000005400000054000000540000002a0000000000000000fb2809a5314473e1165f6b58018e20ed8f07b840007f3600000b3867acace30000b401564a007f3600b4fb2809a5314473e1165f6b58018e20ed8f07b840007f3600000b3867acace30000b401564a007f3600b4fb2809a5314473e1165f6b58018e20ed8f07b84067acacbf6de5e0e428ac771d77b50000b09498030ae3416b66dc00240cf7a62884e542b3bddd0000ade19567bb538035ed360000d18bd45f0b94f54a968f0000d61b892b2ad624901185000095770895ad27ad6b0d950000339fb574bdc56763f9950000617556ed277ab32233780000b5636af8f99b8e85dc9f000026813bd1b091ea6bedbd00000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000","nonce":"0x2ad6","to":"0xb02f39e382c90160eb816de5e0e428ac771d77b5","transactionIndex":"0x34","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xa6da11b62df1e650ecd8515c14f1feb81a70decd7c53740c3263da3f03b05314","s":"0x171e5c7ac729f58b87a7949692af76f5b07e6c70ec8c769cba108a19c2fa2294"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x971e6d5da6a34994f3f43ba52bf0f5de9cc9491c","gas":"0xbd11","gasPrice":"0x11093811d","maxPriorityFeePerGas":"0x59682f00","maxFeePerGas":"0x1763e6e5e","hash":"0x8df5fd7a2951c59fd98b96b21c16c22df9ad26eb6c3ec113386a6ce241090fa9","input":"0xa9059cbb00000000000000000000000035653ec71b8e3aabf047a2e188a84b8685015a7b0000000000000000000000000000000000000000000000000000000017d78400","nonce":"0xa","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x35","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x8cc5bd6c4b67f66dd53d89bff454e2df38ebea2599105fc5f87e33b9733d6cf9","s":"0x56431ff0be38ead9e590724d61af3cbce449b2c064a03d93a328b0bba4a34b60"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x063ed6f59bd44d8bc99c3b170a3d52b49dcbcfff","gas":"0x7b0c","gasPrice":"0x10fd18191","maxPriorityFeePerGas":"0x10fd18191","maxFeePerGas":"0x10fd18191","hash":"0xcc569f9bf9c1d8b9fd5d1b8e20d41addad87350d627537d5224de80473497781","input":"0x","nonce":"0x3f4ab","to":"0x8569169cc04a98f062020c96f8e515b8e359be69","transactionIndex":"0x36","value":"0x2aee2fa708620","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x3bf8504f69ec9a25a14bc1e4aa9e2d4593d5c0bc67ce21a5219c2c161bbf4d59","s":"0x7266855357008f89911e61a88d19280e1cb2d2e1f7544800abc7afbcdf5ff1b5"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x4ee18d1a88cf1a04747af91dd258484202e00951","gas":"0xb491","gasPrice":"0x100998a71","hash":"0x33c81772e3d1471a73b2d6cb887fa47b30c13bafb0dd93b94e2abe08423e3113","input":"0xa9059cbb000000000000000000000000abe511c2bb9a16fcf849bf301a8853f0942cb2db0000000000000000000000000000000000000000000000000000000097fde980","nonce":"0x0","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x37","value":"0x0","type":"0x0","chainId":"0x1","v":"0x26","r":"0xf7c67305640a782116e24dafdc30fec928b2666fa320fba042c7ad71ea1437ad","s":"0x5be04c5e9abbfed7051a2df1680e686e6bdee49be839a942d3e7e3ca3054a57b"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x8069ff4a258cb78cf0ec79a05d067cdde3dbb346","gas":"0x15f90","gasPrice":"0xf44bfea0","maxPriorityFeePerGas":"0x3d20ac83","maxFeePerGas":"0x1f609ad84","hash":"0x7c20abccc80b7fbac50804dabd2f998b9e1425998e6340343d825ed5e47b93e0","input":"0xa9059cbb000000000000000000000000286d41d5ff2760101fd1913fac307d261fc3a9c10000000000000000000000000000000000000000000000000000000002dc6c00","nonce":"0x88f","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x38","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x20f204469f26bb89017ebdf924c039ef509cad78cd40950d3c1d261852af36ec","s":"0x2b67e6e1ef812149ec9ac82d5e76a63dae0fcfcfc34abfdf7d8367ac161dbe61"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x8069ff4a258cb78cf0ec79a05d067cdde3dbb346","gas":"0x15f90","gasPrice":"0x11093811d","maxPriorityFeePerGas":"0x59682f00","maxFeePerGas":"0x22ba865fa","hash":"0x6501d40a16abc6e8a0dd2c455922a34729a19468762387b06489a76080ebba1c","input":"0xa9059cbb000000000000000000000000286d41d5ff2760101fd1913fac307d261fc3a9c100000000000000000000000000000000000000000000000000000000188e9f00","nonce":"0x890","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x39","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xaab7e5fe8c060164c1e644074e17b8eda4436d863195167e38095e5a4c82fd15","s":"0x427b4229a0d90c9b57dcc05f9deff30200443434939d6b521740666f7034afaf"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x963737c550e70ffe4d59464542a28604edb2ef9a","gas":"0x44013","gasPrice":"0xf0fc589d","maxPriorityFeePerGas":"0x39d10680","maxFeePerGas":"0x113664168","hash":"0x3453ef2d365a22ed9e5d34aae5e7ca10c05c613b34dd2d51674c0f1de2e82b99","input":"0x0007e9640000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000a2cd3d43c775978a96bdbf12d733d5a1ed94fb18000000000000000000000000d1d2eb1b1e90b638588728b4130137d262c87cae000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be3000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000c270ec75370c3a7fdbc3cec20cf4b6390831198800000000000000000000000009fa72f08b2163d24b35a1e7131278a99775c297000000000000000000000000b03818026dca6127a00e96991991200bd4e3efaa000000000000000000000000d192ad2c6fb6f7e88ed3c6e68ae0909aadb8fd08000000000000000000000000c0524353632909e349c3cf4e0c1eb4313556a897000000000000000000000000857daab2baf72854638e3a290a853d1129b0fb39000000000000000000000000c4e50eb5cac21733cf06461e0a6e97a076c6f8260000000000000000000000003b929f46584e92a1c0ab2ede2dd7841e01702f150000000000000000000000003b929f46584e92a1c0ab2ede2dd7841e01702f15000000000000000000000000c999cfa58a13cfe9e287d443e1288e45308563c7000000000000000000000000597962063799e201fb365933f1347644f7484032000000000000000000000000f9e51508ad9703ede9764b13fa200a8bfb8081c3000000000000000000000000f9e51508ad9703ede9764b13fa200a8bfb8081c3000000000000000000000000000000000000000000000000000000000000000d0000000000000000000000000000000000000000000000000000001bb8a1fc8200000000000000000000000000000000000000000000000000000002217c5819000000000000000000000000000000000000000000000a6762852bdbfd206800000000000000000000000000000000000000000000000000000000f940a6980e00000000000000000000000000000000000000000000000723c70e1e13937c0000000000000000000000000000000000000000000000000000000000027ce24a00000000000000000000000000000000000000000000000000000000167353cf000000000000000000000000000000000000000000000000000000000684ed480000000000000000000000000000000000000000000000000000000010d563240000000000000000000000000000000000000000000000000000000088d03fb3000000000000000000000000000000000000000000015dacf3cc4a2fd5df000000000000000000000000000000000000000000000000010852ebd46bef4b4c0000000000000000000000000000000000000000000000009fb7dc50e31c645800","nonce":"0xe651d","to":"0xa3222357a0eccf60c73606170be6c99adecb59b3","transactionIndex":"0x3a","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xaacb247e347e44c0e9785c3dc4fba0be78161bf5dd43d6ffe01f339b5bec5a91","s":"0x3045a2386b0224f54200f1a9c585f54fc4669f021b29415ebeb9227dfe0e5720"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xf2233384e0f6a404df1c5353772fb066088a5973","gas":"0x11ebc","gasPrice":"0xf0fc589d","maxPriorityFeePerGas":"0x39d10680","maxFeePerGas":"0x120fd0fae","hash":"0x0c56dd1658928a3fb1a6ba05be8d01d88365b35e0192f2f82aac33315ec848c5","input":"0x095ea7b300000000000000000000000040aa958dd87fc8305b97f2ba922cddca374bcd7f00000000000000000000000000000000000000000000000000000000004895c7","nonce":"0x4","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x3b","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x92beb3249226324137e7f8b4fd0fc6c6d24c54c4538423847f20e99cf7c10ee3","s":"0x724178f5033160ff0b5b9fee294e1786a65a9af61d1f78fda2de3b12b00647d7"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xe6d83a8058bc835e60f157a14f44eced4381df67","gas":"0x1740a","gasPrice":"0xefd4535d","maxPriorityFeePerGas":"0x38a90140","maxFeePerGas":"0x1377239fe","hash":"0xf1d329b6a24562a9c5a1967c51e70158b6ebb9439fdedbe2668ed6315c827578","input":"0xa9059cbb0000000000000000000000005f7e2c45519a12e186391b7fb6e4a5fda9aed16400000000000000000000000000000000000000000000000000000000a21fae28","nonce":"0x14a","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x3c","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xed4b7882177d94bf3fb068c6fe6bb730e008ff6df1cc3ecabc5cdd0adb429693","s":"0x74ca2de68441a0dda7efc143c0ed67a2c10b6e874c4e8f711ea8df9d8bd6e88"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x963737c550e70ffe4d59464542a28604edb2ef9a","gas":"0x5208","gasPrice":"0xefd4535d","maxPriorityFeePerGas":"0x38a90140","maxFeePerGas":"0x114f22c44","hash":"0xed2f1b9efbb9c566a8de5025c6c84db6f55198b39b21bace67e23f464d93e365","input":"0x","nonce":"0xe651e","to":"0x94d052034e2bc8e111badaf68cba288921e004a6","transactionIndex":"0x3d","value":"0x6c9a2b3533160400","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x74dcd5ed824e52f1f2826bf5e01fa55b42e6d27c6d34745d4b18f3e97c8fa43a","s":"0x6bc81650d8e513a01ee7dc667b17b87482737182b85f4b7427a33652071f88dd"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xdd3d72c53ff982ff59853da71158bf1538b3ceee","gas":"0xf0ca","gasPrice":"0xee6b2800","hash":"0xe4e95d0d06a6f2e3820b640692b681e193f8335fbfa36c6bcacb96d0b0a53070","input":"0xa9059cbb0000000000000000000000006e349f20a803bf4660c0ac6d9fa0909f926d4fcc0000000000000000000000000000000000000000000000c35970949583156400","nonce":"0x379dd","to":"0xa2cd3d43c775978a96bdbf12d733d5a1ed94fb18","transactionIndex":"0x3e","value":"0x0","type":"0x0","chainId":"0x1","v":"0x25","r":"0x5dc5a184e14addf25b9c13673808f4cabeace8513475c4923fcf772ff3dbe887","s":"0x1a0656644ddbf337c2000ec28767f32d025a73576073c371b75fe2ca6eafa3fa"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x1835d30ccde46008b6d5e541f6272d0478855be9","gas":"0x5208","gasPrice":"0xeb6018eb","maxPriorityFeePerGas":"0x3434c6ce","maxFeePerGas":"0x109175189","hash":"0xfb47c58f3b1bc6e8d7f545987d74aa3501d5ea0a5c82c9015c00972162855119","input":"0x","nonce":"0x0","to":"0x85784020315f2c7ca628e102ebcb0ce6ec286f39","transactionIndex":"0x3f","value":"0x4563918244f40000","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x7328b13af5f5898f16bbcbb6af63f7a887d6eef62238d43a74be7ae0ff4c98cd","s":"0x5203eec659bd86b8fe3a2861001c3df4f5c0361638deb8064f26a7f67238f24d"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x8ee6c4131597832f1dca3cf6048eefec37b81e33","gas":"0xf50d","gasPrice":"0xeacf03e6","maxPriorityFeePerGas":"0x33a3b1c9","maxFeePerGas":"0x10006fb7c","hash":"0x0f4ee61e19f59d61212413bcf77a4175a735d43bfa045da5d3f589b213da195c","input":"0xa9059cbb00000000000000000000000042675e47991b4778b7bb146b4e2edcc70d92491d0000000000000000000000000000000000000000000000000000000077359400","nonce":"0x15","to":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","transactionIndex":"0x40","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x7ee79f6ec3d3ad051af4e578d0c9eef5274c6b308b3beb049a620b752fa6f729","s":"0x3e1e3b0c37e8ec185c3d66b0c9957387a9bee7988c3a6c0721b5182def0a0854"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x309fada487d25fb477875d0e77a66140fca9aae7","gas":"0x1b89c","gasPrice":"0xeacf03e6","maxPriorityFeePerGas":"0x33a3b1c9","maxFeePerGas":"0x10006fb7c","hash":"0x01afd23baff88529ecf72ce7e924a2a24c776d59f9182808d1cb8b59c31cd867","input":"0xb88a802f","nonce":"0xa6","to":"0x996913c8c08472f584ab8834e925b06d0eb1d813","transactionIndex":"0x41","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x544c5ee679011f4312f6d4a62b1b6fd995bcfb793c01070b23b87c8d0857e058","s":"0x7f8e863a0cef4b51e70626d1f7bb37609490c243391f0a62774da2ffe278d9aa"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x6269ad46c7cf19e71ddca82814416d9ef11cd31c","gas":"0x35778","gasPrice":"0xea5a8fe6","maxPriorityFeePerGas":"0x332f3dc9","maxFeePerGas":"0x142baa329","hash":"0xc46984c9885c9a82014aaf719b45987aadff6131a80a2e6524bc985dadf6b515","input":"0xb930370100000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000194fa82e67300000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000007615000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014c669918cc28000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000014c0358c030b6297000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000001a00000000000000000000000006269ad46c7cf19e71ddca82814416d9ef11cd31c00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000142170ed0880ac9a755fd29b2688956bd959f933f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000146269ad46c7cf19e71ddca82814416d9ef11cd31c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000146269ad46c7cf19e71ddca82814416d9ef11cd31c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000014555ce236c0220695b68341bc48c68d52210cc35b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004201010000006905a35307f3010000000000000000000000000097620b038c35c014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","nonce":"0x28e","to":"0xef4fb24ad0916217251f553c0596f8edc630eb66","transactionIndex":"0x42","value":"0x14c9f71031890000","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x528b60289273fdfe308f25ed0e4c29cfaf3fa97a587780b731d584b1171256d1","s":"0x7d0c4c477dc2275cd36485eac3ecef1effe706b8e3290fab6eb2e388770696bf"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x7e5a68f14c7590011fd2df4066b81aea6783d42e","gas":"0x29478","gasPrice":"0xea5a8fe6","maxPriorityFeePerGas":"0x332f3dc9","maxFeePerGas":"0x1449af006","hash":"0xe41a8be97cb11df47ec7f48ec222c16e98db2dd433fe0971a47436f913e420db","input":"0x161ac21f000000000000000000000000d89c9d33f71472242da78417aa5bd6ae6870dab00000000000000000000000000000a26b00c1f0df003000390027140000faa71900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001360c6ebe","nonce":"0x5","to":"0x00005ea00ac477b1030ce78506496e8c2de24bf5","transactionIndex":"0x43","value":"0xaa87bee538000","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xb3309da76c21a71b35fccb3b019c6b7163a8c683949208e7d2537fe614a3a067","s":"0x59ec073c51a4e2634ae16b0cff3c67d58bfbf215294320157a74c8fbaa8207b7"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x1fcb671b1c6025c90ee12f9ffdf5a917f910dec0","gas":"0x1128f","gasPrice":"0xea5a8fe6","maxPriorityFeePerGas":"0x332f3dc9","maxFeePerGas":"0x1449af006","hash":"0xefbe0a193866480d72cc75657f7be19da54ff53386309e107a09e4312e2edd26","input":"0xa9059cbb000000000000000000000000c2e335cba46bf1bc31dd42b065ddc5c651b6c76d0000000000000000000000000000000000000000000001976924ee91dddc3a49","nonce":"0x91","to":"0xc4441c2be5d8fa8126822b9929ca0b81ea0de38e","transactionIndex":"0x44","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x2d682520f9f4ab78560da123e6a9cc21440f5993f5d98c4df658bce6c5fafb2","s":"0x5cc1f7c1ff77c9ae4cc6b1e1bff67dba934c9aa74721cde7497775c35675c885"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x2b3fed49557bd88f78b898684f82fbb355305dbb","gas":"0x5208","gasPrice":"0xe283ebf9","maxPriorityFeePerGas":"0xe283ebf9","maxFeePerGas":"0xe283ebf9","hash":"0xa0c7ff023d7233a6940f544315cdd6c8b88f71f4e8aa2770d2338e0641edc9bb","input":"0x","nonce":"0xacdbf","to":"0xdf24e05f93f3238e163b3a985b773d635f074982","transactionIndex":"0x45","value":"0x829c6aeb44b400","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x718eef65e93b51f861f37f56a5db882889095372f057f2ecdea8bc760d953cf7","s":"0x5e577c086e082959241a47b33acf7a3002c112c7143814b4eec0d7ff4deb433e"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xae5ebe08f75f1f1e760236a05880003c609bb0e4","gas":"0x14820","gasPrice":"0xde846871","maxPriorityFeePerGas":"0xde846871","maxFeePerGas":"0xde846871","hash":"0xc9bc8f430c6032231b8bc72b4d1c1d8f08c821b7140c0fe3619651c1de204504","input":"0xa9059cbb000000000000000000000000f7c8da79da4cb294c4f55dfebb1b404e3e38d9210000000000000000000000000000000000000000000000000000000029b92700","nonce":"0x1a","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x46","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xc4149cf31b2b31f20fe0ca7b40e03b9c4b436d0316cc126e75e4bdc876fa26e9","s":"0x6da78115b8c6ff30e744378c948552ee0a3b4ee624a648962384243512d042c5"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xe32e3bd2ec560513cab4757564ca50d13caf9f7f","gas":"0x1a019","gasPrice":"0xdc898500","maxPriorityFeePerGas":"0xdc898500","maxFeePerGas":"0xdc898500","hash":"0xb7d18aba1a6de1d6e7c6f38e4301895d2a593c923ed9c440efbe88d817cb8e75","input":"0x0dcb8c1a0000000000000000000000000000000000000000000000000000000000000085000000000000000000000000000000000000000000000001bc16d674ec800000000000000000000000000000e32e3bd2ec560513cab4757564ca50d13caf9f7f","nonce":"0xa9b","to":"0xd664b74274dfeb538d9bac494f3a4760828b02b0","transactionIndex":"0x47","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x5fb8c9c37b15159bc2215623f3c643bf3121b10ef57635928eeca5c4939a9390","s":"0x694e8fc06a28395c7c256b058c4660e612e79ade05b917fe873bad037832875"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xcfc0f98f30742b6d880f90155d4ebb885e55ab33","gas":"0x5208","gasPrice":"0xdbcb13d7","maxPriorityFeePerGas":"0x249fc1ba","maxFeePerGas":"0xff310e42","hash":"0xf8265939cf1f48cab0af24d73cc3c8a75796f90d938282ef8f3a3945e84ac5e4","input":"0x","nonce":"0x18ab55","to":"0x561c58bbaa2ce2f3448202923e9b00cbc1457ef0","transactionIndex":"0x48","value":"0x432d58ec70a5e8","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x15d6811af8822cfcb80b1e09b12efcb5854587c639b2258b11c0866ec31faa5f","s":"0x6fe4d93537f141caeef74611503873df8ac81a3f6aa9c31167d7defdc509169f"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x8216874887415e2650d12d53ff53516f04a74fd7","gas":"0x5208","gasPrice":"0xdbcb13d7","maxPriorityFeePerGas":"0x249fc1ba","maxFeePerGas":"0xff310e42","hash":"0x4c7ff71c89a91481c5b0d9e04df49ad9bbae55c3bce0933b81e3eeb55e044e37","input":"0x","nonce":"0x154db7","to":"0x4e15720b345398e5d00d88e5a7d29531868de012","transactionIndex":"0x49","value":"0x19ef4fb2dc4000","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x4c007887cb98c216be05cce5554825a252f234f494ab803c2396975456017418","s":"0x4a34c1ca42bd0e5d0a093b8fa0938faace594b08717b8175afcc88ccdd4457c6"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xd79feee22c0580fc57bd5e42a01d04eff8af22c6","gas":"0x45dff","gasPrice":"0xda91506c","maxPriorityFeePerGas":"0x2365fe4f","maxFeePerGas":"0x10581364b","hash":"0x9ddb1381f39dcfe85c4048b91d4d3b6694e76856f1a74ba158fed57b1de72b01","input":"0x07ed23790000000000000000000000005141b82f5ffda4c6fe1e372978f1c5427640a190000000000000000000000000a2cd3d43c775978a96bdbf12d733d5a1ed94fb18000000000000000000000000340d2bde5eb28c1eed91b2f790723e3b160613b70000000000000000000000005141b82f5ffda4c6fe1e372978f1c5427640a190000000000000000000000000d79feee22c0580fc57bd5e42a01d04eff8af22c60000000000000000000000000000000000000000000000008ac7230489e800000000000000000000000000000000000000000000000000004640b578d958274700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018600000000000000000000000000000000000000000000000000016800004e00a0744c8c09a2cd3d43c775978a96bdbf12d733d5a1ed94fb18b1f05c103cdd519e9f9785cda23c03635a598be4000000000000000000000000000000000000000000000000017896703a31000000a007e5c0d20000000000000000000000000000000000000000000000000000f600008f0c20a2cd3d43c775978a96bdbf12d733d5a1ed94fb18859f7092f56c43bb48bb46de7119d9c799716cdf6ae40711b8002dc6c0859f7092f56c43bb48bb46de7119d9c799716cdf0e8cc25eac180d68ff214bb9d4b2a043af764a28000000000000000000000000000000000000000000000000000045853ac4ec10a2cd3d43c775978a96bdbf12d733d5a1ed94fb1800206ae4071138002dc6c00e8cc25eac180d68ff214bb9d4b2a043af764a28111111125421ca6dc452d289314280a0f8842a650000000000000000000000000000000000000000000000004640b578d9582747c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000002df1ec3e","nonce":"0x5","to":"0x111111125421ca6dc452d289314280a0f8842a65","transactionIndex":"0x4a","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xfd91ccd786ad12970da9fc0955a4253571646152d2a05b83747dc96adc291e7f","s":"0x4c59d716fe9f52a93767a7f28dacc4d4f1498a6885887617603a239bab1e68d6"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x21760504f5c884c02ca395c11e5cef36cd5ebdb4","gas":"0xfa1f","gasPrice":"0xd9c42b9a","maxPriorityFeePerGas":"0x2298d97d","maxFeePerGas":"0xf37a3b28","hash":"0x11f23ca86ff1e0d74a9fad5e16fbeac85e6f89675568de55f9c215811538b344","input":"0xa9059cbb0000000000000000000000004e83ee47c2cac0c03852c91ac12baf20b0dd9b8d000000000000000000000000000000000000000000000000000000003b9aca00","nonce":"0x24","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x4b","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xdc4de3bb18950fb4d6b0022e86fbfab02e979bb29d3c47884697a473df4c5c0c","s":"0x120c58d32c1db0e306376c1a37759b40df3afca3a085a8f25e44d9240f612e96"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xec1627d9ac6a7524055b37d1c898bfa925d862e2","gas":"0x5208","gasPrice":"0xd954b9a7","maxPriorityFeePerGas":"0x2229678a","maxFeePerGas":"0xf16d0587","hash":"0x3eab2a1dd427afadd77b32b99c9010a750095d3ed6115ae1181a1ae249b32a0f","input":"0x","nonce":"0x92","to":"0xf99741ebe479f37027b6f85943ae7ff727605a98","transactionIndex":"0x4c","value":"0x9e3ba4cb1d805","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x4ac1e31ba13be59850dec480e0a5c26c01fdec3a7299d8144fe4e37a06d01221","s":"0x4a2d8b81c59f13f5f69793b281b24f3989e28951b5f41d4a3d784b2695c5bf52"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x07ae8551be970cb1cca11dd7a11f47ae82e70e67","gas":"0x1b42f","gasPrice":"0xd4f8b71d","maxPriorityFeePerGas":"0x1dcd6500","maxFeePerGas":"0x1386f6afb","hash":"0xc47b8b6b429b5386053839b870b4ff5197ed8c804113e948059a0b26f62a2126","input":"0x2e378115000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000001341c0b5586bf11b8069a5651eb953fa8f25de190000000000000000000000001341c0b5586bf11b8069a5651eb953fa8f25de19000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000017be827eb6f8e830000000000000000000000000000000000000000000000000179c31c68dce0fd000000000000000000000000000000000000000000000000000000000000a4b100000000000000000000000000000000000000000000000000000000002eb2130000000000000000000000000000000000000000000000000000000067accfe1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000","nonce":"0xf5f6","to":"0x5c7bcd6e7de5423a257d81b442095a1a6ced35c5","transactionIndex":"0x4d","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xc8f02c5a24241dde12ccc1ef896ebf00dc455b2241bcea609a4ba7ee17524ca0","s":"0x1ed536f881d7c47e661867f8c22100bee24b9ace5dc682e18417620adc7514f7"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x07ae8551be970cb1cca11dd7a11f47ae82e70e67","gas":"0x1b354","gasPrice":"0xd4f8b71d","maxPriorityFeePerGas":"0x1dcd6500","maxFeePerGas":"0x1386f6afb","hash":"0xe33d278a724d24b86e3416ff065387ea0a48ba875fa452eea89bc2da2c745b06","input":"0x2e3781150000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000008202df12451d056584a3b7760da37f08c3fafbe00000000000000000000000008202df12451d056584a3b7760da37f08c3fafbe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000004200000000000000000000000000000000000006000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000a48f9ed379820040000000000000000000000000000000000000000000000000a46672933018e5100000000000000000000000000000000000000000000000000000000000021050000000000000000000000000000000000000000000000000000000000303e570000000000000000000000000000000000000000000000000000000067accfcd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000","nonce":"0xf5f7","to":"0x5c7bcd6e7de5423a257d81b442095a1a6ced35c5","transactionIndex":"0x4e","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xb76a20a41ed7d94ca8c5539a2c90cbf2499bd5b6b978bce04ccff536f73883c8","s":"0x3a12aeaa2c8bf92c01838f61e28454d063f455fb5e18f962c1f4496ca4185ac9"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x07ae8551be970cb1cca11dd7a11f47ae82e70e67","gas":"0x1b33b","gasPrice":"0xd4f8b71d","maxPriorityFeePerGas":"0x1dcd6500","maxFeePerGas":"0x1308e602b","hash":"0x45ca7563a9cd8e761fa67ce7ba5077ac3acd52f28959fd9eff71953e330390fc","input":"0x2e37811500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d676f0ead2b02c5c4d0597b36f876dacdf9e6c6d000000000000000000000000d676f0ead2b02c5c4d0597b36f876dacdf9e6c6d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000004200000000000000000000000000000000000006000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000105da92d154a71000000000000000000000000000000000000000000000000000e543899adff7600000000000000000000000000000000000000000000000000000000000021050000000000000000000000000000000000000000000000000000000000303e590000000000000000000000000000000000000000000000000000000067accfec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000","nonce":"0xf5f8","to":"0x5c7bcd6e7de5423a257d81b442095a1a6ced35c5","transactionIndex":"0x4f","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x4448490462dbad4e86f336ccb1e5a34151177b52857645b91bddad8de63446f9","s":"0x2fa8196f14e7574e87096f9e45545583eac60987240a01359f72047a86886633"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x526e447c2b5a800a724b788c9e749d4d5b74aed3","gas":"0x29246","gasPrice":"0xd153a381","maxPriorityFeePerGas":"0x1a285164","maxFeePerGas":"0x198906586","hash":"0xa14825277bb114b74712dfad55547f925467322f3832ba0e4abbfc46708a2bf1","input":"0x2da03409000000000000000000000000db908ab4adda767a2937034a282119db77f6d426000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7","nonce":"0x14fe","to":"0x83ab368bea540d07690b46456d28fb92ae8bbf2e","transactionIndex":"0x50","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x3b6de267388482613e3f391a3769a2f9a708f912416bf6f4546d6c0444b54fb3","s":"0x7a42c5a9d016fa7c603337a651e4276a5b84cabc8c0eb3d52b6aa5a294f5458a"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x00bdb5699745f5b860228c8f939abf1b9ae374ed","gas":"0x2a332","gasPrice":"0xd153a381","maxPriorityFeePerGas":"0x1a285164","maxFeePerGas":"0x198906586","hash":"0xb1180149636b9e26062017348d5d2e48fcdb5582e815ad3dbaacf035f10c292e","input":"0x2da03409000000000000000000000000725d507898e0534ffd74b06648ff4c485561a2a7000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7","nonce":"0x1f1ee9","to":"0x1522900b6dafac587d499a862861c0869be6e428","transactionIndex":"0x51","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x8067b7cd350b1cbe98b599a46a3fea2519d31095332825ccc32866012005e751","s":"0x607e5233ad4b2592d21420050e74566ba487622b1b61bc828fe09b3c1ea53b64"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xd7e1d07b30c8417d0d305a6bbc60a1d793c555d8","gas":"0x5208","gasPrice":"0xd1143d34","hash":"0xb7f2abeee11c0943e164c55c6f6893b58a7ac86cc152e91b2d5e36bbcbfdd949","input":"0x","nonce":"0x17","to":"0x35f9d37a63af9fa18564ea72b77a0520ec7824ea","transactionIndex":"0x52","value":"0x4db732547630000","type":"0x0","chainId":"0x1","v":"0x25","r":"0xaf83f6abeee1d25db3c13d4131cdbd08941bc174f0d24a9cac9ff3e3e2bdbffa","s":"0x524c9793f5e932ad479236ec7064f1ea4ae257be328c9fee24c901048860d683"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x397be73c6160e5e66408924da4ad32ea5e9ea9db","gas":"0x5208","gasPrice":"0xc6085cb9","hash":"0x1ec4d1566ad8b3c4db0ebec2e405b6b5a1e1ce00db40f62d83dc98da91d6562f","input":"0x","nonce":"0x1c1d4","to":"0x4ec59752de4aba4d7d4347d9c4c1cf130f4b78ff","transactionIndex":"0x53","value":"0x12d6d02bf4e88","type":"0x0","chainId":"0x1","v":"0x25","r":"0x1989806a714d19ef99b0aa38602b6e1e5eb9ce0f326cdf2d92dbf6970b143fc3","s":"0x2a56e135f6556cf0002d220e65abf060f4488760acc36b06d75e874ff096615b"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xfb19ffd1ff9316b7f5bba076ef4b78e4bbedf4e1","gas":"0xf798","gasPrice":"0xc6085cb9","hash":"0x3b33b86bfb5b98a038a8489186611a7466b74a889d21ef1402a104cdf21ba045","input":"0xa9059cbb000000000000000000000000c8e35a45bcbfe532c49780d18e086571236cd85800000000000000000000000000000000000000000000000000000003536e5d17","nonce":"0x3428f","to":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","transactionIndex":"0x54","value":"0x0","type":"0x0","chainId":"0x1","v":"0x25","r":"0xcf9668d9b4c3ec7e9f3ec88e373bd02d27d943fd87deb32228a270debc76ff2e","s":"0x5d389e6e22095b8df227324617253f5289c4d52e1f4151f43367e3487cb98bf1"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x8be891689037c58c425edc76dbf3b62451304dd1","gas":"0x7a120","gasPrice":"0xc0728bee","hash":"0x60bdea511dda223c44b7d4fd9602912d82feaccd843c77fd9937a6c0bfcc4429","input":"0x095ea7b30000000000000000000000006d303cee7959f814042d31e0624fb88ec6fbcc1d00000000000000000000000000000000000000000000000000000003f8ee03d0","nonce":"0x2c4","to":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","transactionIndex":"0x55","value":"0x0","type":"0x0","chainId":"0x1","v":"0x25","r":"0x4a5425cb84a4798a041aa18f0fe08fcc806beeb3a7035f263ee6caef4ead0763","s":"0x434ba377246b2a42bae37c0166986cc3d531db62d62fb69330ecd0dd64fe4adf"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xca74f404e0c7bfa35b13b511097df966d5a65597","gas":"0x25494","gasPrice":"0xbd21331d","maxPriorityFeePerGas":"0x5f5e100","maxFeePerGas":"0x123916b20","hash":"0xcf9bf038f60c225ce9fc7cd8d778480f5c5559ef90be208644fba230bdd3a0f0","input":"0xcc713a04f5fd4cbd1dd2d9adbb4a5cea5a50872580610b3a3f6895fd61d23aa21112edf1000000000000000000000000807cf9a772d5a3f9cefbc1192e939d62f0d9bd3800000000000000000000000000000000000000000000000000000000000000000000000000000000000000007fc66500c84a76ad7e9c93437bfc5ac33e2ddae9000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000980db37df37bf5a00000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000004164f0067acacfc0000000000000000000000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000de0b6b3a7640000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000411344275b597b5e936cc274f269d7cd7a204076421d2b9af73143cfd70b90fd551c641bfb67187d26271431436a3afa3c845b591871902c4a601a2c29f24f7e6a1b00000000000000000000000000000000000000000000000000000000000000fb39cfb5","nonce":"0x4db02","to":"0x111111125421ca6dc452d289314280a0f8842a65","transactionIndex":"0x56","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xe7531c5314b82790233d5edb3985f814a2384572cd3f3d0fd59fd6f52e446a93","s":"0x66293f81b8b675d48ce75e1c5571a0baf133a4cd90b5e3abb9f1c734fff3ea34"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x420e721ed86ba031e34262aef32e9a0eeb914775","gas":"0xcb75","gasPrice":"0xbd21331d","maxPriorityFeePerGas":"0x5f5e100","maxFeePerGas":"0xc8db7c5d","hash":"0x98f13f70b0ade5d92005dfafa1f36e541c22a9f1ac417ed845f810d7dfad8490","input":"0x095ea7b30000000000000000000000000000000000001ff3684f28c67538d4d072c22734000000000000000000000000000000000000000007a5ee11997f9ddba16b1800","nonce":"0x1ca","to":"0x32b86b99441480a7e5bd3a26c124ec2373e3f015","transactionIndex":"0x57","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xe28afa5e9afe976222e718848efa984846f6999963e75be28fe4f2625d22d68a","s":"0x32f3d71c0216b9b8962983e156048b6567357a4d5f4e7bec010bee2da9416e73"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x4cda9e9f67552e4c3407e842b874329e580fb43d","gas":"0x7b39d","gasPrice":"0xbc9a3fee","hash":"0x05ba59871b3a940a5531e545b4db64807a24e9f4ef75ad0664e536f16d9bd0c5","input":"0xe1fcde8e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000b33d999469a7e6b9ebc25a3a05248287b855ed46000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000063554fba294265800000000000000000000000000000000000000000000000000000000000000000724b3474174bc02d32af162add2daa39fb6177c81788bb17a2a5eb90653df96cabd33105c1d000000000000000000000000a21636070280298e1bde98dd9f190d2d53e3c638000000000000000000000000000000000000000000626573742d77616c6c65740000000000000000000000004cda9e9f67552e4c3407e842b874329e580fb43d000000000000000000000000000000000000000000000000002cbc3064a84ec400000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000d3f64baa732061f8b3626ee44bab354f854877ac000000000000000000000000d3f64baa732061f8b3626ee44bab354f854877ac000000000000000000000000b33d999469a7e6b9ebc25a3a05248287b855ed46000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063554fba2942658000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000005045a5031b9000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000006aa981bff95edfea36bdae98c26b274ffcafe8d3000000000000000000000000b33d999469a7e6b9ebc25a3a05248287b855ed46000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061be71447e9d7e80000000000000000000000000000000000000000000000000000002c9c0d2f48500b000000000000000000000000000000000000000000000000002d851d2092ee69000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000027060000000000000000000000000000000000000000000000000000000000000079000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000005727562696300000000000000000000000000000000000000000000000000000000000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45000000000000000000000000b33d999469a7e6b9ebc25a3a05248287b855ed46000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061a56b85b4c2b67000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000244ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104b858183f0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000008000000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc4500000000000000000000000000000000000000000000061a56b85b4c2b670000000000000000000000000000000000000000000000000000002c9c0d2f48500b000000000000000000000000000000000000000000000000000000000000002bb33d999469a7e6b9ebc25a3a05248287b855ed46000bb8c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004449404b7c000000000000000000000000000000000000000000000000002c9c0d2f48500b0000000000000000000000006aa981bff95edfea36bdae98c26b274ffcafe8d300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","nonce":"0xd","to":"0x3335733c454805df6a77f825f266e136fb4a3333","transactionIndex":"0x58","value":"0x0","type":"0x0","chainId":"0x1","v":"0x26","r":"0xeb3a4d02a12e46470cceb9d3ca82f337e01ce2624a89c1c1592090e0db8b9f8","s":"0x497946e7a4dd73ba68866c193880c7ff7769816d91883b829b623b3854e36a1c"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xd79813c45bc9194f7ee2b2f059ce1f0d286c105d","gas":"0x1b735","gasPrice":"0xbc9a3fee","hash":"0xb4e40f11cc3920d21d42bfcc31688000349ed010c4f40acac9ec69e3b0d5e668","input":"0xb07da958000000000000000000000000765ab02ef7f2edcc3b5595eaff5ab526746210fe000000000000000000000000000000000000000000000001158e460913d000000000000000000000000000000000000000000000000000000000000001e13380","nonce":"0x207","to":"0xcc90105d4a2aa067ee768120ada19886021df422","transactionIndex":"0x59","value":"0x0","type":"0x0","chainId":"0x1","v":"0x25","r":"0xa2dd9455e0d57b486eceb3abecd571e78f6d26a7aa073e4ad514bd2446bffd2c","s":"0x4be696736bb27b724a9cb794f8cc6f36cbd7c9d9ef17f98d131f0a46dca1a3ac"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x2b6e0ef89f3d3e80f76ef9ff97d06a1ee9556dc7","gas":"0x5208","gasPrice":"0xbc795a5d","maxPriorityFeePerGas":"0x54e0840","maxFeePerGas":"0xc332327f","hash":"0xe86edf97629a328d936f682dd9ea8135f78bdf1f2e874f5e62e1a08baeb6de00","input":"0x","nonce":"0x7","to":"0xf48b08caa8824e08249ba0727af1b277cfac2e3e","transactionIndex":"0x5a","value":"0xdb7a665f13800","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xbad860c2621a020b305b41ada1129a8633e2bcc9674f6ad3905685d44202285c","s":"0x74edf65cd14cef9002365b49a3070c862dc1ebfd1f6d550f80769846c2be8115"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x03f0e4f23fabfab735690020ed9809d51e6090f8","gas":"0x5208","gasPrice":"0xbc795a5d","maxPriorityFeePerGas":"0x54e0840","maxFeePerGas":"0xdaeeb7c6","hash":"0x7674d8d877b70e0ad65a28a44c2997187f2ac603745c40c1831b00010f72264c","input":"0x","nonce":"0x0","to":"0xf1b8af64046c0574ee1e54fb6172aaf7968970d4","transactionIndex":"0x5b","value":"0x1c6bf52634000","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xb3716388e1e02ab078af1a50e110001148f9a07e85c01a0be09e56229222620f","s":"0x47ee9f4ed8c859d5a6692b2c8d5f71f6f5687eedd7e19e233e5cc6bafd728f96"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xd5ba3a3378ebdbb64a401c6e4289a90fcc30971b","gas":"0xc7d1","gasPrice":"0xbc795a5d","maxPriorityFeePerGas":"0x54e0840","maxFeePerGas":"0xdaeeb7c6","hash":"0x809134a16c6e22b8e355331b3d9c2aec61e99fd0aff263ec69f513f4bef20cef","input":"0xa9059cbb000000000000000000000000ddb25f35269bfc3963764b5cd4e9b68f66d201960000000000000000000000000000000000000000000000000000000243446390","nonce":"0x4c","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x5c","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xd6fb8757f0ab33168be90209ebfff4201f1b30d8bca43519dfcb0695dc41e502","s":"0x6cf3f7f1a427a0918c125e47b9b0628d23ea61b7c0bc7e687a0c019f74868179"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xb6921af873a9e171734c3367757ef002a566a96d","gas":"0x390f9","gasPrice":"0xbbe0c3dd","maxPriorityFeePerGas":"0x4b571c0","maxFeePerGas":"0x180733080","hash":"0xa15d9a024640d0234dcb8b425d3b1e6f3d2e78ec652e681ebe9f625a98f7389a","input":"0x2213bc0b0000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008e1bc9bf0400000000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000004e41fff991f000000000000000000000000b6921af873a9e171734c3367757ef002a566a96d0000000000000000000000006bea7cfef803d1e3d5f7c0103f7ded065644e19700000000000000000000000000000000000000000000008b0367321eb719151000000000000000000000000000000000000000000000000000000000000000a0ce78b03fe1fb823504d12f9e2068d400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000002710000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000024d0e30db00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e48d68a1560000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002cc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000bb86bea7cfef803d1e3d5f7c0103f7ded065644e197000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012438c9c1470000000000000000000000006bea7cfef803d1e3d5f7c0103f7ded065644e19700000000000000000000000000000000000000000000000000000000000000550000000000000000000000006bea7cfef803d1e3d5f7c0103f7ded065644e197000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000007afa9d836d2fccf172b66622625e56404e465dbd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","nonce":"0xa","to":"0x0000000000001ff3684f28c67538d4d072c22734","transactionIndex":"0x5d","value":"0x8e1bc9bf040000","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xb36bc90a482df5aeb39df553225e914517e313a5c2f5509b80aeebd554cf4d73","s":"0x407d099dfc63729322267bf4f43056bf1852249cf317408cec0e2fcaa1dfc753"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xdde7f25f8422d307dd9fcea4edf4efbe50019bdf","gas":"0x352ef","gasPrice":"0xbbe0c3dd","maxPriorityFeePerGas":"0x4b571c0","maxFeePerGas":"0x180733080","hash":"0xf36f2532a2b13326957b63a8bfaa8183214b624224b56f18b4ffb8fd79f0ad04","input":"0x2213bc0b0000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000021702e400000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000006841fff991f000000000000000000000000dde7f25f8422d307dd9fcea4edf4efbe50019bdf000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000002f33799a4b872a000000000000000000000000000000000000000000000000000000000000000a0fc892d715bac3a8d2732ab572068d400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000004c000000000000000000000000000000000000000000000000000000000000000e4c1fb425e0000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000021702e4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000067acadf100000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a4d92aadfb0000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000302f160a70c0f25fa632588b803e12979740364b0e3e9ccfdf3cdec4f8a65d526c3db673198abfa00000000000000000000000000000000000000000000000000000194fa877554000000000000000000000000a8020ecbc321e0c8cea26b3507f207482d0100c20000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000021702e400000000000000000000000000000000000000000000000000000000000000041e0e43fd4c4599adcf34f517a089ef632364094660a040b4a7a19f5aa55094f7928c2707f880d27039d0f1efe412df9084f2ca7258a5881375b047d0df5e1afef1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000002710000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000242e1a7d4d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c438c9c147000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000000550000000000000000000000007afa9d836d2fccf172b66622625e56404e465dbd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","nonce":"0xa0","to":"0x0000000000001ff3684f28c67538d4d072c22734","transactionIndex":"0x5e","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xaf6f403ed58a7c3ca491656e3dc6536c7afc1d491daaa609fe4cd183fa18ac63","s":"0x7986f7af0a4920a0894985d95201a24bd8aaa8a396d41c345ca155c647ca658d"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xc683adc1e9a0010bb5b6854b80c8192fac98c8a9","gas":"0x4cbd9","gasPrice":"0xbbe0c3dd","maxPriorityFeePerGas":"0x4b571c0","maxFeePerGas":"0x182d58a80","hash":"0x620ddba5f347c471f840e863cae62cf7fef4e2fb180edd5abc9450947166560f","input":"0x2213bc0b0000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aa87bee5380000000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000004c41fff991f000000000000000000000000c683adc1e9a0010bb5b6854b80c8192fac98c8a90000000000000000000000003301ee63fb29f863f2333bd4466acb46cd8323e60000000000000000000000000000000000000000005c7c00d3774f27938e570000000000000000000000000000000000000000000000000000000000000000a045fefdd072fa4c94f4453f4d2068d400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000002710000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000024d0e30db00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c4103b48be0000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000002710000000000000000000000000da3a20aad0c34fa742bd9813d45bbf67c787ae0b0000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012438c9c1470000000000000000000000003301ee63fb29f863f2333bd4466acb46cd8323e600000000000000000000000000000000000000000000000000000000000000550000000000000000000000003301ee63fb29f863f2333bd4466acb46cd8323e6000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000007afa9d836d2fccf172b66622625e56404e465dbd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","to":"0x0000000000001ff3684f28c67538d4d072c22734","transactionIndex":"0x5f","value":"0xaa87bee538000","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xd5b0af08770f1f312f248a0b95758a69493a0398d00b7fc290f6d27cf054430b","s":"0x210509b3edd8748a25a794ed45b3eec575d9dcac96efe02191160a299f06602b"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xc17e8fda1b4871870a0280cc6418eec57e546ce3","gas":"0x3d58d","gasPrice":"0xbbe0c3dd","maxPriorityFeePerGas":"0x4b571c0","maxFeePerGas":"0x173564180","hash":"0x25832b051778554c1fd1463bad830d2088f406b34ad14d99d50fb1d16c359482","input":"0x2213bc0b0000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001d80e301ef4ac00000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000004c41fff991f000000000000000000000000c17e8fda1b4871870a0280cc6418eec57e546ce3000000000000000000000000607b75f3dd862f2ac858641e088a4d72d89acddd000000000000000000000000000000000000000bf9e2b36a3df1b1fab42d4bd200000000000000000000000000000000000000000000000000000000000000a0f6fcc5d7cd323249a85ac12c2068d400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000002710000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000024d0e30db00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c4103b48be0000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000002710000000000000000000000000a976eef88aee55485c34b594d137ee6bce774fe60000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012438c9c147000000000000000000000000607b75f3dd862f2ac858641e088a4d72d89acddd0000000000000000000000000000000000000000000000000000000000000055000000000000000000000000607b75f3dd862f2ac858641e088a4d72d89acddd000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000007afa9d836d2fccf172b66622625e56404e465dbd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","to":"0x0000000000001ff3684f28c67538d4d072c22734","transactionIndex":"0x60","value":"0x1d80e301ef4ac0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x747d320726efee6c9b97c9f02527bfc747a94515c3f23c663977b01121837b6f","s":"0x47b6551c0c7d4cf4ef54307d4e723b95c3d808954b4c4f8deadaa353fd6debf2"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x00f4d759c49d9c1912a156743bc0eff14659cae7","gas":"0xc7c4","gasPrice":"0xbbad997c","maxPriorityFeePerGas":"0x54e0840","maxFeePerGas":"0xbbad997c","hash":"0xd752ea76b37b2f43993dcd72f848eb8f066774b995e7ce30ed01251cc150c26f","input":"0xa9059cbb0000000000000000000000007287dcb6aa70f21cda4560216ac3e7f3b6ccc80b0000000000000000000000000000000000000000000000000000000004ab66ab","nonce":"0x137b","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x61","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xb5afcfd488d627ae7cd1c216693942c1ef61d2745ace71a2a1fcdab56b78140","s":"0x34da36a7da4600af676cb5006dedab69524845be0b6e7af93458349035e1aa5c"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x6edf968da408a9640b8865826429a977a11c5048","gas":"0x14820","gasPrice":"0xba26429d","maxPriorityFeePerGas":"0x2faf080","maxFeePerGas":"0xdd8c3d08","hash":"0x7452b5cd384981d5e1bdba79cb4f2e6c365213c7ec92ce6e5ccf20c052687685","input":"0xa9059cbb00000000000000000000000058dafbab88a5e2e6dde9c34ea45a624dc31e6edd000000000000000000000000000000000000000000000000000000001515eb05","nonce":"0x1b76b","to":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","transactionIndex":"0x62","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x2b606048c5be888663c7dc890667516788e3eabd34a49b1415f733b50fd963cc","s":"0x338bf814b78ebfe1a9c578f617cbcdea0058044e1d5fbd43395ce325e6a1ace9"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x22fff189c37302c02635322911c3b64f80ce7203","gas":"0x5208","gasPrice":"0xba26429d","maxPriorityFeePerGas":"0x2faf080","maxFeePerGas":"0xcc263d12","hash":"0x85ae2eced0388eb01e4b0c6f07082ecdf8ee642351c4f17ca7f1cdea104de717","input":"0x","nonce":"0x23f15","to":"0x6c5919c40cf594fa93a1c6e7c7274370129c33e7","transactionIndex":"0x63","value":"0x2556a4cfd8a000","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x90d649bb7f1930513b852a67ab314817d0e9dfe0cf5e69f33c7a8d735f7f71a8","s":"0x334497140893d24b42f2df23b1305a09bbe09e8d510e86a32a0fe7755651c1a6"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xc1b634853cb333d3ad8663715b08f41a3aec47cc","gas":"0x1d0d34","gasPrice":"0xba26429d","maxPriorityFeePerGas":"0x2faf080","maxFeePerGas":"0x795d5c9aa","hash":"0x1070fba9cfb3f6c5ca44b92c01cfcf24a0e945878579af2ece8b8aada7d3552c","input":"0x8f111f3c00000000000000000000000000000000000000000000000000000000000c71f800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000001c89e4000000000000000000000000e64a54e2533fd126c2e452c5fab544d80e2e4eb50000000000000000000000000000000000000000000000000000000010dfd8590000000000000000000000000000000000000000000000000000000010dfd8d00000000000000000000000000000000000000000000000000000000000018361005b238634d9b80650270300b8a89d5f9bddf4ac60c75e7037d582243d014451ca49e76cfccf85cc8955c6e39ba49073f4a9542726b69db32ef6986a7bff7fed119a9ca2f5e7f9b9fd39f7de177bdb58b091636498c1c0460c8c46a7a26863258361a31fbf08dab30a89346754e317bf59c36280ec76ffc61a11194508217366ec91b1f3bc35f346659c156247bc8c8f9d3157c68cac0138e782c927ac67f1096faa2d6b5f6fbeb16ea777f70419c6e58422b80552f3fff7aae6f67fa881922243875e4887a9ae0df6deb4f940d3471f9627a06c1fd3518a74068c41191cc38d5fc8b749ca3386a414511317ba4ad6344c977d12f9e1f3ba7dfe138b10a650a47da8328d9a469a534a933c45c54d722a09a4501491d245cba57ef37ffe697297497c1a272234ad00f42807b25368a7b4262ba56d9365cedec96e33d3b5744991274d3eb3d8cc629f5696c972dbfc9e53013888f72f97b1162db05880122c42a9af14b0b095b0d184dfffd5a78e8e0d0dd2eb99640759b68ed5b3f7b355b203c67096cade42c19c50598ddb08569f70c47e77a8f19fd0c4ca22fe07c0b23ff3ab9e95876f27003dccc2300d28e67f4918699860cc4318d26c7e36eda569901da02fe934acb50563d9bfa1d3c81864ac4d3a22815e19459fa060782c0cfc59a7ed27ec0ab153bd697381703abaab89968aa282599e4eae6ff0e4663ff3365ea9ef7fbd5249f7e7d0dbfb21ba17da9aa0acb4b2205d649715a419a9da3105faaf77dffadc8b4928ea3f7f980dbe32f8e079680a52bbc36e8059113946f369a443656579347a24e43d033192201ea080f423582b3ab49a354514d7e1c322d1248e51f097f03f1125b49f8f509b06c28513829be98ce5dfd136c38b62d0f499dbf1d9d591e8f282e9fa6a002898814eb541772457fe8800b63a1c1e724c01fc70aad2a480803384c92502488132510153cc88d54b597914c4ad27c23629fff9344ba40055d460839397aac3c3be8436ebfc0977f942167bcc0f3b11e8165a5903d889236a94e542e3b136db32da08f42fb4a5b5255bea047b5dc7ba3b04456dd1172c3871224f4adaa28a5cf54b151bf6810c5fcc77221c6bf0eaa45411a590a21a277af19d3a118c1b6f9fb3b36cd7a8f7ef90546d6cd222e42e013e4c6e314ac550a020605b8d193d50eaf7fe104af7c9168aef7f9152b1e94a137797eff242d763b6ba18240cb627145cb89d9758cbe72fc0c29b7e2cf1c15dab87cf3f00f1131fedc981237d0129d0a1d5481c515c17811b068b4463af02e12cac4844a08a956092a3bc91066289a1f625c52b1325f22a58737131454fd56689e3fe856376bb21899d2ce9eca3473f8adf50261bf20172b4b032aa89370a9ed3ac495ca8dfea4eb7f922e81db567c508bf916e241e0a060696c72cd1d4ff039b779fdd001ae53bd6f922860b3bb2184dc729ff1377f14b41c36ec8a2b0fd705839e6ae4f85bd941da9e5b9f6a33fde4fa40a33f166a8b51b2ff4850ceac88b28aea30a8e44a3db01e0da3f318816b3e567b9610604032a5a8a5f0027d1914caf4c58b31c8e79e90f80a2f4f8d3c0ca7c7bdf599103360dfc8df0297bbdf9132b16266c4566f05a07a2606027f550b335c3449f499015b5b70bb03a7cf384b75ed142a17e16977663a3dc1e74057b4895134e8e7efa4e6a5beacc5c4f6df95245e793b4a035f9ff243324d40478d1c963377ba0f04eb26c38ddff19c403b64bdfb0a912c3504ed4e1564001ea67fe2012d62c578e91f38bd2824c7335ce9c693e5cb99cd4c1979d022f09e45d0056102b2a478313d2812931c5c323d315168358f3e89f8fada4e8baa374d0fb30d37e165f1520417aac3689da3da5896262048273d632b90199fa6338ad2dd90a51f2dab7a1b6199ce52064e8b784c80ac99a856f179cc2137aab107c539b4d7711738ad6d44677145182fc064cf6043024a478b4eb8527c00dfa00e2ee3e3aa278a45b164d6e9ed475966304c3f1c45a06d0f9943d409c7b0974973c8e65d31c77bb4597db2d2314e7dc1501d406d2ace2a29def16af095bcacbc5aa7ff09f8f289df34eba9e461670f0637d6c5e1b4121a21427d0f22d4505c171947008a0e48b6530560c5396a7a08a7c01fe73a08ad15dde94f412d48daff1c1c4fd2d3e95233e4b70ac85dfe59f4dd39d1d78d184cdea38dcd5b3703857638c48ae41023584f784cc474fefd7390cb92d2b43a11ba6b3dab33038688044b438fbf342cdc84f14460cc78dae8984702eefc1dfd39c211265b1ff5d98bd0bcd948f125b8cd524082db3c2cd24412b1e80f98a70b11823044b921208233b4314f06ac375fffdf0d6fdfd88e42c26b3a52a240a10ac4630b0110826a3324e00002627632a3de4cc0fbe13ead2f0b4304b0af56f37b936d893c19d4fc25dcf9f9da1cd6075a691910950cef9c7b77e31a461bcd444fa47ad13d9eade69e84c0a299a0a5e6d9986156a62a17ebb5853d5c72febc54313dcd2b1b8ecf3db0eea01fbaff6d7e191ca16a3e69ae68c3e3485cedeece8958f2ef305cf1d46e3b1c07b92ab34070f6aa71c8e8a4593fe13e616ef20ba4be37f165423e30da87e345a1a824b589cf154abc16836aef7a89424551baafa35dea8864ec3f1da3213e2cddcef61b436cc788c45998f9371d4fb88eaaf1b5f0e018717b2465b3d06231043150c1688654e9e18a685936612ca0f2f454c557792adfa35826a35b1ac476d149356a2be94e02c07861b21ea8e25baf48fce1736e659d1eb17705ae73f75908f9e66a3c5a4658190cd3066205fde677df9db9f9228ede76f24ca5338e7fa81ae429bd04fbb656cafbe765774694d0c9ecdef6f0c13044cf1cef159a01394673a905a51633936c6bb6613432bd30cf7abf0ec29e9eba7d8aa031996fd083448b93fc0a08e89f199af8d41f330f82da4014e1ab884f8dc7e24a9bb77a4ef7fd5e1915457b2b2d97ef4391672bb9b70fc909810868281b961c7068f677e6406e8bcf4b95350712f724ec9cb86d0884629ca84d94c3c421b97e8ac747507aaf1f83fada85af0e96760b6659781986d73600b0e6258c8a30d89ce0d50ccea5132f45b4a6485eac1e1db0bbcf0fe5b1684817ea97a32f586081b468bbd9757e62b3a0eb64618125a5c278bae1a83b1907ce2baa28d9375b3f2a815b1be1dd3764a5413edcdb4f3584a62ec6445f3a80a826504f12c541561d17137741798eaee777ab16fcdbe9ce922f926a2f1068ea43a3fd5ef71903d8794dcc0cccc414348cbbe09907d7a3694c40b9c9fd2f30ae21ef0ca6b0163662a97920342a05bef5fe017158b0e54ba90e23606bff40597777d27cb629744b169749251e1d304c3a5b8cad7787bf129d6975eb582586732d8ce82b9a55f3ff062f0ca10018cc987f1f0a60033355290b3910e960975cfb6e1cd9f2ff8532bb7f8e7c02c7706a891cc8b7cca4c218388f6935c80c067c0277d8870b99737835b60ba6a89c7edc1a117f0be8ea1df8c86d3bdf644a20dde13c3492b6c108e97a5625b3c3de1f9a358d7b6af67063ae37698b0c8a79e1b0a3a703fbe73f5b90c741502df75583be217c8d8dda8984e9f177fffaffea76621bb08e8cbe39b0eac0d364952712e159dab59ea2bbdb3b0edc4ee8b8dfccc9cb5998a8b0bedbfece61f38b45a2712a25107836767c210ca41b953ce9d8a6bf8d77702494edd54fc24ccb860255c16b49df9962b0c249121c0e88d1af81ba88128a35529227f7387a9f491765263e85150d79128bb5490beb7957ce350b17d69d4dbc55c062a10bbdaf767fd50f0ddc8d1c011aead8510643c9d0813c34d19b48bee19b58c911009c361438134988ae7d28aec090cfeef99129cd10d17017b65d1380fb167deb144ab1eabb3a7c0917c93006bee0631b2406e3d75d31327e3642ca9f037ad73bc9ef365b280cd2b632def5bd4ac4a65a256baf21f2a64c0ad77a7f12d0fd21d2fca004ac29b064e89b067ffeeb84afee529bb65142589ee089aa7bca7ca1c0501d93299ea2d9dbf87afacda4cc53e0bfaf06a8128a611665d9e5efed27f8ad76f1cb4d93a22d985b2687e4331d394733b04f6b86ffe2b9da5673dd31536bbbc691e4562235f4d959fcaab2f0d7e2e2d176127661958e348083a398e712a2e509dfafe9f16556922ad2ec1af70955b56439dfc062447f7d59ecdefd23bc4caaba72ed397337354fbef94d81bea289d7efcb76aef0164772457b6d4037f3078eee1b960974528afa184d7ce99fe637d58a6906cc939e0c9cc811fd00f6fcfe1e01c3790532c0aeffac244be39fec9184be92275acad40d0e4c6a64500890428b4c34091d8fbf28f7e44dfbdfe90eb61ab96cabd7ada66f1ff437012ad93149c77ca52097ae2b30ad6670ff6bd09b7ddef9525a8f89484179f545c7afa3930076e202765401b9b2f0225a72e5b83635db58543e47be08f119670d1fdaf088b33a1a9da8761e5a522fc90ab8f06f15037f6381812ddf6591522d14472bd4c2b2160665dc4e1fe706dab6aa6b7da1b2a37b9a128f20b34f70932f41ae33c6bf7c0eafd406487f17bb393f6c813058b3009838eba7f21083a3d878a3a5919a124ad3175019835331c10361cf473dc7375b29b7fc2212a8dd90916314dc6037dfa9da1522e112cd9f19344fe64a0ba1605c3e1f21e6d9edaa47f6b680920644985dc96c42338b2480fa52afe9fc7fff5150922eebf9c3cb13839973e736e1be2a85e71f6c2ad1af507e8251d9bc7808cd7eeff5463aab2dd4a64a93c133dfee4faa1d9f0d3d789f5079e068c7002a79696f26c05524b619c370a4e7eb51ff0d12ffdaa1aa12f690ffa5d5e1c531daff5e3aac2ccceb8180a0c03a8bce5be07e16782bec5400678751c9ae5542723e7848575d0c43e128222f8965f3627fef0e02c47bca31fedf3fb3a8e0a61b78057771cf46284904b488e48f4fee8b4f4a7859fa946c7cdaf6c13ec86746a83c340420b747156577f7c51a9044e0861e1b63c75e8f0abfbafbce69c5ecf6fbc248561c3cca8a6591ac13b619030714b38c3461e42c66cbd63bd8ac8ab3f09f596d3d567e5f8c713353f4f59bfefc1972a776885afca3de118f305169fe15e9724f58c7a90cf9e1c99232ae14137f5af781bf733000556458a01110a0256f95880814a17427e954d5883dca2090a85aff5d596ef85ebce7f89396ee7930209a33f1a50dd1f65efef1ed969167ff0dbef242dcbf6fe5b303dbd922dd1a14865a2ed8551745ded482b951067c972f6e72e6a25262dcda93fc84b95fdb64ca8cc9da2f0b936c564d7c6bbfaf3b9b7d14e944e0db30ba6822867c442f9e03e2f3008003b822d3e0df4c25205b592a08cfb2ccf1eb050d9c547d963d993c5bfeb31aa7b3f3e80d21eb3bb91f988d5d80faf82e4e969bcd75c39ed9403f30934aecfcd3c626ff2d3ad44cdabdccd1e81b01e8e32383d883082ae8a032be12f1ffbe852edfab3183536494d56eb22e5f3e49adf7c31b3143432f8fc5dd457bcbf451bb7bb7fe108544f928ebf560ace1f93a9017f29e6fe6e784e4f93c0d4e9b845dfafb74afbf3eb51248b5e8507208167753c59029b021dd9739794c008e9d5b35d92b71b89760f011272dd6cbe7f9138135043b9cd51fcc01e4a9881a6c66db96694c0e9475dff0d9270f088bb08e31a39f50297a3261453c817649dff51a642d3f343b432bb1d99e4067b6f5d32cd91f76f4de10576355c4fcb34cdc7bb8e0e742aa02a5f80715720f72d41e2fc8faa907448d4503da2d1934268a03fce4aec15632407cc44873aa67ac4465490319852919010a15338918c14a23ac5b1ed2d0caff58d069bb246539bb1f623397b2c4612c5a11f070045069e9fc08b18d06c0393c82eb92f1343f294c00e526ee15f68d59725f533866498285051f2c3cea50fa1bc7aec1bfe85996f1b32d46d3ede583ca6c24be848d39f59466f8fd4b2f045551481667051221705e1740b0488d7f558e5b6bd96f41ad50f6e714eab7891d7e67a310502a108c968a002791cbeba1d41d402ffc693bedaed88b48604fa8a62fc0bbf5fd8515831e1c62732d75bb0e205e554865834e0167b9778996b96bec6fc6c179d98c4eb0d50a7a067ad85a902050163c38e3cb8054f92bb3e06cfe3cddddabe5817dc2b33a69a8f16b1d7859c7ef201d5d1249f8a9d150b70d7e2bade9336250cb47dc7fd14ccf1cbb4db39eec53fbd7edc1bf034e8d06a9e3cb5abc1c65f5945640be0f7338e70f1170ae81ba378e1663cff068268f070ee73074b0649a0ddea572cd6bff044daafc3da6e3d0d7fadceeaa5ca0fdb067bf6d16b5e05528bea6c83d462b445c837ca5f2538760fe359ca100f1663dee9eecd86d756b58807cda51205c71d0a0cb61132704349f7f5c26be76de7bacb643c1ca6443c98e5fdb95bbe5e9d439210d6fed3f9293e6d3ac16a5d9cd67436980b43bfe53306360eee4d3e4053d9f4e8f35f7863c919f7c556375f8b3f08b0082f34735b1f50965a66a5f96df687ed0546cb192cdf4fdb1bae25ae8a7350240658a867c831a4b6638ca38d872631295d0fc020f513b029ec0540be1630c6aaefa55180c530ad5276ba5d708831e4ec554c87673303ebd766649cd09e48be1f8becb84b5534be73ecdc382317ece4014c62d8cd854e63335eeb02297d0ba5cf2db8df5176a7663188de4fd0d054cb6b3f9ac367c52cb0a5dbf0ecb8aeed75874ed37f100d0812783bab40283a7a66c04bf2ea4d4de275c24766641f299eac3ec55acc34d450b28ba320d2a0f18b3d4473fe006bb3a4dc5d943d1ab2de492fc5716d6631ce254396fa1c535258508efc5b1d7951109049e867b6eb33fe00c5833ecf673a26f80c72b92bf7b2218fb58f8b6f6c05f51540725ac53b01a192ddbaee3c9c97392ff6f6a40c1d96b9c9b1c4bd4eac0d3146a39ba7cb4ec8ab55074e177a03838fd306c0a97c739274eb692b0ad57dded098a0f8fa7a0b1c2821a26cf939f3c2daf8bb40feed351f82f65b6c01acf0387d3f209608b4cecc9b37f652bd95a8aa2d58b59d0337de592f499664a25010d0e0b5f9f5de41d608a44d9cd5a48b51e7e1965c4a60d9aad69535e0c30699680e2829628170205cc24f7d259ffda65ed8f3d93e4ac3ca55c2ac5d2f67821e5d114442f801768bafe150ab54f8066f2d5cff1d06a292a3b860cdddd888a3b3189b555943489725d085f595c598e579d940225626efe5eebdf385c627ba994931124976ae02ced3bdb6e3532890c43e668d958cca82ebc72e43424db42177bb7b288db780dda9f1bf97a9e641681168900a32b938bf5a33d3bf5a331e4c88db0be5a8c5163cf0cb3da2a0bde431cd47139d4a69f1d7ee7ea541f1afc30a1892c4dc1ef92718f2107e28cb034807332b57c3b148bfeb7337af162d9e64705d8860bd2117aa61aaa2a83b11cc26f6d008c8873a8d54f2944885c1866a74d55da080617467b15b6721ef0b470d8e5e5fe1a9b10f9dec0280ac95c9c54e3ae30ca62ead2d18695c69fb9cac150f90a5631b269bbf624ef6b731b91f3fa393c3ab2165acf24d566e01a6ea1251443efbcd7e860df943f0b0decb9800db0432d0089afcef6402ce3e6dcf7bb733f574ba155ee1b70de15e3607c0ab58fb18b076310ae3c2918be89282cc928204bb1680aa6377a2e63a0b72308c4ad54813b583fc797b522daad4aea9cd00c04f71effbb58e2dd7cbdaacd48faef4cbf350de0941353d804dd508e0a158b5d4b140dfe050e0a1f90816e0e189d03ed7a43aeab0baf2e338393265177168d20d9b1306e3d91ebb0f1d3912865eb7b4687ca8b53bf503941248ba85077e2b7a344e30b1a414d7318c11efb9f578c01d4e2e96b4ce4d64fae2059facf8a2105b2fe524abf7b86a741d388593ed1b006318f2254af02e91489629d84c77d80112de5036e960628c1dcfcb17b24dced2f6baf94115af3dda4fabef4d8820406bacf1d7145623e10f89498e10d10d29d67d568459173c174421e0356de87b40366102c0ff8d313bc77ca69e4c5e9c049b977946a2724f1d776bfae4efa195ccda4882e84c66a13654eeed90856b946c4adaae27827aa232a49a3948bdc122f85b892fa7d2d2d7b8c1f3baa90ddddef7cf3d139282398c73c0e77c3abac529d64951b627ef3c127a0230404d75e6b58cc822f3ccec6f06648b89694557d114aac236037ba75a19cd763df2a82f6dd06fd1022ee756120961e0bc8c849096c1108ee98edfd73476e7adc6b7e5eb23ecbbcf7c28e861cd172402da5d68e400e295b323d7fcc306c47de9418060e2bb5a2a177894d0f52c8b3d2e584bc0b7f1787cef0635aa1ff6a34621796cb57c4be8f87fe84f6271c6ef26a387bc630537e4dd9b177ae198345faabb6fc3b0884343a49fe8978172e5305e2930ad56bc22e1eb178833893d77916d3f49e5c9db72d964d994479c93beb62a5eb9e88241129079097b924a173266ca59a1c0521051caf588529b367be59fbbfea2f15da6de49733070d0b9796164209ee7b96a55ea36d47ada784fff2350f40457a17441e4441a5e0201cf0db5655bcee794acb132a9b603621a2b818a61d45459a18aa8971b575811a3a0c3c5d7b19b8e033a97ea672daa9dbb2248f4d9afb56e7702c3ce9f668825eca148f4a766ca2aa3345d95767ad36b73d27e9cc2f93c3f22de91ddca5293631f74b39238ff6b0ac4470c6695c408dc0764b10b0315744da897dcb89405defc7334a3ac21d39d69eac892386ad18f33c179e4cd97c61df5a37247cf3bf3744d17c504d050a061ea810b317faf6a32c2bac57ca17dc7fa48d90d1d05de8cb63eb15c78cff2c454ea6bf200e771b1a85f7906e28c4e4952c3a5c586707169c7d1527fc63d0d6de65ea5524a97303a9b14f511f324945fe45de68b1122463cb9b2525468aa801800395157958fef56aada4ffc0c74dac3826ca7353ffbd4ce04ac6f927f855eb634f578386f5e2eb19ac79cd1ae12d37161d9eb32f5311699b33f9ea2feedfc6e52ab206f67191f8364e64592736a82fc53c1dd423ed286c63cc470e2f3c01e9d32951e88ec0bd972384de4d748b4d509293863380c11c043f661dcf1d316a15b0045aba8955c8082852bac1402c0bdf7a964535afef3a951bd3032ef5eef257bf432f497f9e1556c7885fa442179760c161519295688301d81dba95ac0874ef497e693ed8ba55b63655d20b128fdaad1ed138f6ad1418abc5ba9b6b03884a0a0acb622e3931be3501d3f3a1263baf612399d9c9c86db588c5e33c9703cd62b84560bcd6d207e2ef6bcaf2d868cbc59c4d2c765fe18b73eebcd14a930d9f361853ea8d26c0121e384e0d56f5a35ba3d81960b42635f1b3d7f5170255e011b361fafb35385fca0cf741c96cc4f0a0632833ecb4ecd3957a3e338372d11b53e6f5539d8cd3237e2bf66347e492f1546a2b04e33a7af9d6d1effec85b58725a14fd1865a8a159351dfbc581f7d85be279bbc0b6f4c707276ee5e67a728188a561bcf35ca2984c0eb4a1233d8cb95b1018fb406cf47d8974d7c6a102149a76805c0f9d6999db1a73ef1de508aa66922c915d0b64302dd856280a70732ea93ff121703ea1641e5a9342920ad32d79faa50256e19331f39600238e8cdd823a4add1ccd70244b7f734904670a5e626c87359e62f19124f82d2752822dce0f03227d1818160a0610265284bf93f6d7c865417ee0cc0a5e8833ecf892f5f0ee425ffc9c0490819204c583aa4d2e7569291101ed46f2e8108ed13e9b3fb617f8f9a809326c8a959c690a4dc25bd66d06a7a16c545dc3aab03952fb0499524229a3a6ecc2d5fad1dfdecce8d36de0d741bbdaecb8321d1220d1a172a338ff98a7b4a352f9c3a832f2e00dac208c2de61bd17126dbf5898dfaa3c1ee1fa0860a475e8b473b47c19044ab5d426c3a77555631c5e92b3402b623712c4dbcf79e4bed959a5d39dd9fc95da6e1381fd769415cb9396cff9a179bc4d87842d830d2b45a8f54ad9b4d3e37ab2ca1707d3aa9960c607f55e4f1b2cc707740192abae4a5e5f4fbb7aadc99fe786258a432252a5c48e5c149a7b539473575d2f78b7ddbbf2e7e30fce3d2559947beec2650922d755fbfa9df38f21043914b879be25126138df9f75e565c0fc8f46b8a4373cd49412ca576938c2c11d00f0a6a142c8b61725a5b1fa110e19015ec5c734b61d33ffd98f9585264ab0fe9dd8a4b828d8d2033bfd2e7e7a7927bd29e12bb9b3f4d9eecb25363353fa1c2ff2f28aa8c8c1569cadb1839b84a04a31a3f27601f5a5c9f274b7789954175098e7f9ec270d9d6a8a22a53289bd391320658fb33430ce9ae37bc4085619cb5485753b089d1a726c8e7f1f5c84f05fe544b02347e21d7602f0e42c194423a72d434a4ff0580dbf476f9212897ada455d15acf8a1f1f19ed3ec74ef9ddf7313343578afa92d33686c76dfc97f2940c140f7e3fad0f6b7693167d23c0181341669b14276556e7d4aaaf97663e5ca5136b3a08a40ee23b55525faf5199a95e58295c41f1a75bfadb5e03409a79d0f5cb1fd4238e02474a8637c44711dec30af63498e8287d01ebe85ba4cbf6f0d57307dcea8f88478a07fb61ca348b0b51a910da814d64b1330dac0d263dfff72c31fab9514327af29726756f48a331edbe49a6d9c982c4833a2feec50d986770d39487061f02a157e15f3268f6aade0720d304dd2e2b9098bb88e0565b730b76d444f0bfcf81d5daca183d45442ac4fb9b5b9b803266cfb3228ba20a3fa2e148d1284831c7ad35773f12c3b6325373ee5b14caa7b96e00022c11681d2af2fd4cd4ea9cc04b787fd67f24ef17c0d28bf8377eb54a4dd3ba5d780264a19276669cfb0b334154a5bfcc27b6a71b96486aeb94090b8e60ae69d60009ef93bf07f350529661505001d85410c0f53225f5520cdafd56428c03dd9b0eb62344fa7d218de4a1c6236065aed95669ac846c87e26371a719636de089dd0920942bf1a6c27229c80496b04471f448656332cbe7eb8e524d4437714e9094bf672fc1efce8b92d44cfe38edf1c5ea04dd76deee1b3defbc1b88ae117d987f90197410f66a9e61ebfd4faf45914e527617938853938a2cd0f9b7dce09fe08b9406711e3178e95b3e85fe4bdfe74dfdd5f6d1efffc2e4c37595937c32facd8408f41ef8252b60d5b9aa224408431c72397b110436ed88aae77fcd5e347e438fc108b5ffe6fe0c1f0e01fec96c29be9e4cab42c6c4f9aafaab73bdde458e08b4a96fdea96112d2a6a4a35b0b8085497eecbebff5acc8990d30a5523b8e024237be539469506820552529d67a7b77bf6945f3a2483f81354133acc4458c1f1ecd36e4cb119ba3067e065eddfc795ee523b475d1fe9d44bb63f7d8b496746804435db6f13706296a547f6f44a7d96fbf08f0e3fbf7e763f4f9d4f9bff9dbdbd19dfe702bd7dbbcb78be0d17dddb5a1cf20c68a443307d204a4e1e29e1eb048ac50394db88163342277c1fd629e66a035366a5a1fe773912d3adfde4e0c018cdcf5050ca8203b16067a9c150dd6bf08d99021d5d9f9207d0cd9db2bd8d90d4d2c780abdd927fdf11eed26ab99c946ae2726a412574dedb67299372f256214cff3d405e4e1eb62fc656b64f3fb24dd6f381df20bee4f05983480347af210ecac0c90951287ee5ddb5789570faaa90f91bb37d948cd745e6df039a76f1f8328a09f45ce00c7d94a03140414dbf491e9618e79fd09c773a925689e2fa79ef7fb598bf956ab55919df7693a0034e2082ecc41b5991e485025d861bae093662ab1731658277ecc396921747d48894087544004016b05c4a9e68cb956df9f015b57f93fa863fc9db00eea5d664429a5c7c77e4e351978f2274c1d562818f8fd3fb631f06f778d4f89cf579e1d46b5dccc1034260a936453796c6a2bb6b05c82c1f23bc11c8e1c7f9cb36902a9b5fe5a39a8e734c89c35bd8525d94ce7b73a26c8b2b46aa30bcb3a63f83c3e46d29a41d64d536d795e9d06f13496ffd614debd02617cb67b5891773842db454b2ca9e196b8ff7f47cebef27d20100d3a630c063f46efe83e5e71dcc65cf42c2aa8a4dc99cd139bc8376265fb37df4a82c4f0352ff71fa7936e1ad73fa9682fafc235c9317eefffb3fed949882238e2e42dc843cd31bef1d6effc9e943d6ddff84b94c19f75fd9d4cfeb4d91f353d5ff2af66403c2535d078fb4c8d752a39cd4547be4c9d510857efa889e8abfe7e2a2830c68b986f46c5b11d0fa1e20d4b592531ca31c97d1fea5f1569b2a9a71e7162c02c74a8635a566ff037a5a2c19110ae868058e6cd86e174d5f0a4e5d0e232aee55d3188bc13548ded0696800930a00683f67452c040c1406e2be716b57fad58b0be974794b35d618153f66538df6326e1af2a6d3edaf555816cddaaa046fd1e7a4ae27286ceb81946a7170176648e14853190d70eff02c791981dad1f2f99ae6423fd051140c6b4934a104e365d8908daaf20fb8e937f23ea9f201919406e0efbfbfa0e9236c6c830333373f2e55b58486367cf8d105da0c8d3eea022c040ba8fafb8bc79946adfafd4580d4a9320425369cbc52752ebfeba3f3489e81b8b41f72b6b8bf09a4c53af515dad2dafdf245469865a0999f47b5450c2ec7781819f7851e3bcd16f398f082cba4808da702978d4f93ae2d15d0d5629bad9ac1c90238b9ae92d4f70846dc29018e8a3c67f562a978f1f1abafb35aed763788c325d6b1eb626ad94dd8dc36c9c973353eae257ec3b973533e35a7957f10c34af1dfe65f6c6fc38052109804cf0aa1c1890f0dc79c40067a8cbce6cc9893f9388efe9c635e95baa7bab30adcc31ad005ba63d61d73c13edeae7b7645bd8321f829ff143b95d68c71c5c796f86d99f6c9d9bb5ee902de79f525ea74f15d66f96a7e58c842fafd21024a79fff7c4bbf4b5f23248c783c331e7efa4f52f8697f10212b8053a8f1b5bfa70b05cb425130131022ac2bba42b9ae28c498404675aa712a0b42015565ce54854dbe2ef3b649abddbc57e9f9ba094a22e4cf8fcfc19bf5a45038d3740368aa36313017081b1851281b0aa413aa6b02a9c2a00c2219d5bd46748635a24c9ee19baa4208a4e77fcfe7ab9486d92b882fd436b7f1437ee1bf78d1e45572c20d8fde92652a042590c858e69821c8b9a412ec3cd3e99b0302bdf913722c30130202826526cb82b357c98a34fb030924f4776cb0670e67537e03ebd5f8a96a57da35cf3639fd297d66bd596b87d788b56db38a079d218569dd775e34029ed676dfb0247263942460604e4da334626986881f6fda0c1bd0496f09a2ae69d75a740ea14c3938f4b287b9d74531ae9abeeb49e3f945d4da24766b60dba0a7c44f47e9b92518e7d93d83236df33bddb498ddcff4dc609c4ab6e941bdd814a24d0fca81d67551a2da773dcebb2018e2a02982a4ea28e7f6404819d4b0cfbe7ef04422bec7beeaa47745116468148c36c4b15bded941d0049edfa12418066efbdf16481b56b2f60bb6d50194bf34364e882ee276645f96a3e365fab8541db4fd5aa92e8803bd7d657c9106eff4448038bb43babae9a37a901208c0bc5888042877470fe520f5f8cbb777f3f58a70eef452f3924877166141a78cb0a25f47456bc4579452d9412e4e9eaefd95c56cf225e683154899d7268dbc5061164320b556004eb6f90faa3449f86f37ca729e01d0376f206fffe0f7ccea5f5bc4200a7ebe368d7cc2646bcd95b966415f26fb33b013d61240738b27651895a86c387f32a37298edfc405a04c963309aa7705bbd7537ab45043ec917b7bf4214e45708e7d702c0b23ddc4fdb129915fb34e7627fb8393a10b8626fc6586b89a9e5268264ff4232d77c650714b10c93f932db7c73c1fc8264d0832289c17ab84230b37022be7d54051bbad6868d5a64d8485d02e4c401438a200e1388874ffb7e65bf8f3c12a588474a369d7e65fa1003fc0d8f02d1f5aecafe0bca27b96476ff4d9535e394d82ab4e9600a61c79beba923fe1ade92f0d02149907893c57120ce544d5979b2c67a572fe3a1547c44c41dca4be87e27f325a1f9128254884aab050640e6a77cfca501c3618697458057697ca294a475a8d25d29b255ad998584388620157a075816b6e3b0ee55bf4e7085cc4aeb4c854936b1f7b6180029742ee4c4789c3941da18b6319140c4d520d2ec1b461896ef4999518649838507153e38d767cee67a1225f56715371cbe7f12718ee40791ce09e9feb57120b3c2c3c607b659a072d720066556117e92b0d78f87304b6d19e81b127ce1267a9649738fc76ba603925b30c5c43401118bf5bbe1e3a68db433b516aac852fa2092d53f78042e6c8c27cd90be37d7eff543cbc0a631dc721e418d56a2aad1221ebe96179d5e01f9fdb9966512102c374a0e8856cc7061f9f8b8c45f55bb8d714c4abeff7db8aeec1ef437dcb92ec298a06d517c64d38c1a614db2674a0ed67f1d2854f856407536cb3c234bd3e25c03bfc3ddeda60927a1872867669c1238e7fe2d5956ac2dca7d58e2e6aa10c8734d3f5703d014caa50c35f0a438323cdac1d85f0ae79faa20ac50c05418d97608e0a9ff8a76a82affa33aa3190b48ccc851b6ad07f13c3dc2ae4813dfa34f3e8e7adddc64a7ed4e7f81d5f08adbb702c11c65e3b10909ecb3a394ffe4ee4a4e0d81f943c98e8faa592badaeb4d30a91927918de1018892f20ea18fb7d041c4c7efb273151aeec1cd76faa704a690c9880a95793db4836157e4121a7b860a3de7f042f5f6a393c38f0bbf0187e5f1cd6a92c6ce1af6e19b5830e562b11e0251060d12c189cd7c8e1bf41456535222acac177d75f0d8f48c9279cf89cca9b8bc828a01d3ff3bfc5492dc7e2282d60ca770e7a6a9c0377fae7dfbcdb8ef5a62f1e266aca583ad16adae654fd24eb26031c52920183affee00d7bde7c81002b6be40fb8e9af0a38285ba67cd009a395d537f80523b7cbb9416a11d92f493dd3d2601372e7ad22822e8429843a33581531d24b157981e7333bb5ed358f390ff162604d0b85c420f312128a39467dbf96b31af27128505c39185af3fe8d030a3061abb23cf6f9e2d31f307b7fbe81a58571b5203d0cf02e65f90e99d42cd92808a865f204071a5b96cc3f9010e576b3225d80bc45421c1fe3319b6d63289b4eb340eff1e836a4e8ac405f34e798205b669162373e70f2c6abdde5023d8b4cb8157d96e12484223e45627d9382c1a9dd151083aff0d3db92921c59cb242466611b87be62af9c613110c86422048abf54c9f36da2735fb88ad00816b156593234fcee7acee39669c8f89571b71bddeb88bde63950706e135949c28186b1996de1d081f4e309fad96ac3daa2f7f8366f859e42bcc4c4199acf6ef6850f4742242912e114907b88d23f5c219ba86e8b0be7ae1ef1a1fa5e68131896f0b1ed292661130391f37a6634abe37e3e71d955c8fe153f05e3f3a99a777fb43199deca683a591e3171313357990f58821c8ecc5153e5c1329918643563a79b9c4fca8aba9b4a8675daaa4d7c5e7fbc6470090745ec261f8e81f93eea75f2d118008f1c432148d638b016f648c13303525905bcdb562f2e459b60cd725470b33d833a80ed64d2e61160ffa88e51deeaeaf7d2aa5f46e9b7c509f04c64766dc923221236c8be7cefb76de29fccc5aef557ff724e73cd1e083217f7d3ed6c759d3719ed7b3ee560f8a0fdf9801ef29b9faf61d212c2e371935254bd0466af88e433a8636f4471dddb6078af99149c26d4f8b560dfe49ecb93ac082848fee278a7398427d11ce6654326fa38a17f77f0e113a61cce7e0a35f6f360f6459094cdb240f4c80c5ab5ab7e2b4db3c4642f6742231dcb0ec9569f3f7f656e82c3591ff2031d5a4daa238aeb0821d6fbcc6db8da5a2cc2f1878221ffdd8752c2e1cb88b5adbeb668bb7b7918122a67bc05597aa579dde9380a705d54d0f29354b12de31a5918ccd45cecc2f9a5ae2b3986bbfed6fbbe30e166a595501c793356ab8eb288b42e215811809cbfeaabe33a49e1aa1c63ad95a55443549194b791ed785d8a496d593dd531829d817f69b7bff4ee3b3d0ff1e7f2ef6b908a79941c3bdc8fe60aab8b2d140da18522e8f720d54dd5698f0d2a188f129478c1ddd6f32bb81ea74ee7db949445d680c577fb5c1f141002983b1c50caa08ec2e29133381253864b2a12db7a35074e064366410817022529b3a458274607aaeae670251f16e8466a38a9c2637b7687e6fb78538dd65f92bd88c40fc5fc159cb8ac4c1404162f3b344502afeb04172746654008eec4609a523c8f49ce4f4ecaa10f9f3354f10e94b747a7494313aec2069699ac9e8e7ade9d0d6a9212b65b7510967219810f3e29b56a3ed49e235f5b69a4826e592a22dc31880b0c244a89c9c35986e04e8fae7cc74b72c646f50261257dd27ffae907e08aec805ffcc7a58b32afe8fef6ea63813bceaaa44ee45b7697c78026f5d465c60178e5e77a955d255693e12220eef9e881e8216107a9897937588f4cf863cced316223dd79564d77805c527a0d9e17a480bf8f850d1b3e880e837f789df6db632a783b32e5949ac1c5873012094d46818cf5ab9a19c90527252a059abbcf1d53df1d12a7be8bd861ab0e5fdd6cc55277c2530e2dcc2f423039e773a05cf43ed87a0d2ccd2c398366ab66b5fd67d512f1cb7d0e3af2f94b0f7832cc742794d17e6db17372c33ec8b3251c848501606553634ffb6f01fd80fa5e713b835fea165d867da4f2d9a8516864d1d31b65800ad0f0ce18a9d436e40bc10324bc839df0b4c57ed66d9e653b92acbb2214d15ab26fb597839a4b0417ac11767723c4e1567ede2895d4445883c992de21dc0f67789ff2a46ec5fc21d58ca3506f70f469b13ca94d57e0dd8e89d69ff21b0f06e40b9a193a134e62c4c41d1b9371546af659471fad877ceb4564344ec8bb28be4f1bd33bf44fa5dc0754282df622c7a5a6a51d365463e7255de0f046ae59fac54e25026753fe9308d889ad272aa8bcdb25c0eb82c17673f1312a72157b3683ab0c57df0c903f98a3140080eddecf97714bfd9649b55174b65916d308a2f77f77d10b3315c7b4799f48a5e975902dbe4b8590ca26675f96e8a53040f5477e59154e6664c7459735e2c5bd4239c2525fe985cc31ee0a5d75c37f6a19611a85bf733dfedaac1db6d5e4efd728a2324072a3c62844e9ffc678219d35ccb1fa5ef892cb44c670bf61f83e943bc550ea9766816a288b4739c3a98cf8f6ab16648a61833ee1296153b8342781fce5a450ae758fff62353c4b486bf3b27c84f2fee8e7f469a1233f9d20893439f99b654b5aa0ad4ea45558fa3eb8dff4fb01d9fe3d3933b82029fbc1fc4d965a8127102759ac3f08e7d51ccc7fe5c45681d4eb04cf02ec66c8d4dac4309827b08483b8b81287299f2ea489a291b424afeabe69a7810f88def5e5b11bf6e604778293d75c370517f15c0aa66b00fef1313f977672bcd99dcfbf29f097c38a7b440f5a30e326d12460c7abda37ec9a617bc2087c56da0dcb370021c16c1604990034b7408247498ab7f141b9a8d3ecac414de594c1e5144a39670b3efdd6a32363cb53ad4b93a578a872fe5c1447b423433e572d7c0bd64ef8cfa0ad23041d2f7a2d782ac3d142040f2ebe3f7cfdc18f2742f0cb1b55a7b986baf60fbff88b6ac4dde999aceef9cca1b237f7dd1517f010655247e68f2947c573d9e4be91efb0c581a00ab09da8e625eef106b946781e7d09482ad9c3490d2b151a658bcf6626f0727dec5886fd49f869765eeeae5f8a444544fdb2fe6ba466443f955461bd3b96942a313ce14df12b5117c648b514c808817f991b33cc0da2a1fb09cd19da66876458a37836b5d461a9a3c7d6178b0669f821ee9ab9b52db6f5174def5b35d6c7bdd189af07a3d86787ec63660b0cccf1852480332b61ddcd8efa39f7b81efd57a2ce1cdfd557037139c01de2cfde2e26b7d198ddd296712ecfe169a5bc0892a028840bf705970132311da2d3ce219cc4f90ec099fc654d1543522747fa86743b31dcb900c65c9cff3977c5eec1b35d824f081fbb99677cc22e6ecb785d2b488080c1128e9be0f15c29ffc3bbd011c2039b42b470cd3f7c9c7ab77658554a132daa25a8bc297d6e0562cf12415f0e2078bdd6fc7d204fd89c6199c59b7e7c2ffb3670221f35ff37044430ae6dd454dd856878c2e7e74af78a6be25d9b4c28b96dad180001261c36ded29454703d5ffe2aacd969469204ffc28fa75ed4a2ca9980cc96d9b6035d799107459114ede85d272b666317f99e0922e60d27ae5da96fcdc3d6dbe61b3a0aaa5ca2de1bbfb76fe4c79c797040f313e971d641d882a816c991be5cdb65bf86fa6eae0775d33cc6b37ea52abb6652237df2bc6c4a6f257d4eed3d733a28912dd05c8d60a4efeb533a0f3195f9b362f161c1d1775c7ea6f35d7d137de5a6155beef96e2d876de47ffe1579355804a42e12f20cd775cdcde3702e85234497ad4c43b1f6eb18b2ec61afa0b639c50a92ffb1302434f2d02c2ac884021671c66f8f003b407e4246efbb00eb55c3ca657016312506c67c97b1e706f2c8c075f5955748fc970b6b9c345f7eadc10eee575740184d5a7dd9f80b10b5e511a7697234f4a4e83bf2468f113ae0569e9d528c55921a20016e4585f3699faab419c9f3d4d3bdd73dfb3b25262ed6bb692301ea8c81d84cf528b48decb7be748c1f1b50401f6a82c22f8d32a7956d3090a207392655acb532590b01f2aca153db56e22d275e52158083deec0b2d69840c0e9b5d900ec3a8cc4c3b45aa6c1e75fac2c86bf82e23d2cff7400ca19e28fc1aa7dec6c7f2c2601e64343d5e3e700427f254d9efc37e7582d62af6e9cff77715bf4ffed9c09f1fa77a97aea04d4dcb4f99512c375affd86b06039aa8b9e8df70fd5d60071ab421664531f74a540968aac0cd4c7f9ab503a92dd9f4bd4188d16cdeaba52a40c38786b34589cac60226ab86adfa36cd9a4f9af4135f9ac945a05e3ee74581c66db9ac254775902c6b164bec03a1890a08973119c13de5e453e04c51a740018ad6ebc8680691cd98963e4ceb53de683fbf33c4f74584fc5fcdac9bf45426eecfe7bb98b7af4cc064b7ae70c0cc44e9f7a106c7dc23bacea6b1b700bd23b14c07c2d9d55221030c70d5a85091eb59cfd06fcdd8133cd131dacf2a14a9bac7c65f7086a227f807dad387f594bc87b1ae19a61d7726e5247ab9794ded5c85a6387b2d51003a9fc567fcb860c1090a19274bca5ae9f5f7d414365d4b93097f4b7c1e8ff7e36765ffdf2fd4d83e397e83af91452a3e2bccda2fd5739ce2704244dcc33f36afcba718daeeb7e8e1c8590a06f98bed027cd6ddfd2361107f865f902b95cb1cb42c7e4f348e51b97b931a13abb13126a7fe5b07fa0329d252d72da167ebd48e2a2ef52e1591b5a29874ed795d057a644614e2ffad9b420dc5b69bfc07b30c27c3c1e1208eff82c370cf16d2a6e35d712bf473aa40bb516a153dce150fcd2afb823bc47d893fefedaac4f975343567154ad1dadbb4f51f795661caa70530cf2504c10f0403a37faf349e933a47c6ebc15c40bab9ceef1230680bb0010be23a4c44393dcc6880b8770dbc665e146f3ff68a5fed639f9933194226ea24fe7647097fd6dd08e8d73ac1bc5a60d1275f1c7f941990709043d4c6300dea7f69abeb6318c069f6ccebe4ffac9722bbd80da11f02240206ce59de962bc8cb9339346c02eb00499495d54ea4b5ff6ffb0c149075272c97b8412ad1657187f2fa913392381cbd13f015a7863246577cd64e1b20c012415b1f937ef9eb753dd69f44f4fe55c7e1b7a7cbb5387e17c3a0a17be7586f75433ae677aab5f2e26a8df4023919f646c62d4f870bab1aba0a825325df4845568edcef9df25cb589b6e7a49ab3f44e8d87c10567216d846420c2546bac1ae4c5b344260083bb8a4735da4bb9209a90e0554ea16363a6dd8f9c0e4967d8f19c3c43ab6343797c7e4b706318b2b56f7a4884fe98b93b235edf5db439b5d030023891e3491b2a20d2b1fa5423e53bd9ae81b35456c431a1f169b0cc69dd5b84d2a0dadfbe99d5a6a659c4bf1efbc64d388cc8067debf2ffe4a3990d2831c1ef32e7389de34bde33e6d7f8831083349f43208326acefe6ffe08eff23e2931904e9bfb67ffedb8c0fbb24d67b293e9fe51f07f49428912ae1ecee4e8c82b9574d16178dc1d3ddcea038f0779029ad1de2418ee8442300d0289dfcee41beb3c2b0250b45a108ef64693f0ff5ef462cb6d65d680ef6fc164916dec492b4fba8d61dc9df011087039905b4ae834df69c01e7873d3e508948051567bf74f0992f03fd7c18af3c100ca4acaf7c2bc501591ee0505e9ad221d96e3f776cbed8b798ff802c81104a9115045a2d73d92038e54d777a39b49d4f0c6602bcbe09c218e92373786e5fc910a88a3bc623281e5bd0ff2f89c3d08afad38ef4c4c5b544e5ba8f9fa08b7a5885b30faffda7f6a10363804b7d43157ba44856c0211b0bde9ea7d5651da04ce9a3afced7ac36ff65b7861b1a2e64bfd7be5c4aae68e15e9bbe0fbe7df69ab205319f6841da5b76bce160979bc96c42f6f316194bbef0fdd0128a134fa842c98054bfee3be8d55cf85dcf3bac388546cbd9b71774934292800341d68491a9ca37050e86c27392b51d8f80e2a264182b1d6e2c68c183bb2246b8a9e85ab07c595618a26321947f5cb1f1470438123a554c85d78262d3f317c4399ff864084b2a0468342a026e90d14256a742e5f838bb3c57904fb11b9497fd02e58aaced29c24095b8e51ed47f505d82fb61bca4ecdbf32d40892036344bbde19079838d74483630ab5b5309d04115782c0101ae297852e0817046e5dbd81167721aa67c66470fb320dfd6c64b5a8681ed39c5bb35c04ae40fec17b5007965d1317c3b2d0edd1a72648536f62b174286cfcd543055d82da08ec9dfa3aaa6c5c7b2569344b4f4e47b8a1a63dc463f31ba7757af2e7a70ee77824e07d6d5f8cd73c7022b0405ae2119b60b6d1be4fc5ce6c3d7f9fd835331a628db4587d2e234e024c4481b89c4e2c006aceeacea88cd7672d0b65e6e468dcb69bb70ced11b2af9bb2a580cd00181ee031144fd9399e1957cfa71b4ad568643e4237a385fa89b8b3ccfaa06c7730a6ea26c094e67901a216a6ef70ffd55e327bda8235997845177e5f082a6c8bfdb690b1cfc226894036321ce7becbfdd30f83313afadcb756ddf42c9f9bc45bdcb9e2f5214335dd375c24545c9a7e517757ac5d6131e57adb3823ec669d415718fa3dea6a0db8ae11b9e80b3727fa7c837e23f9b65b6bcf0905d76ac57330d6c7e30dc430b01b0b2ee70cf25def4cd489e2521b64ec871ec3c7530447dac7134b92f9abe6f1a8acfe29fb27220e8fa6215e59d650d841004ca0abf6f3e2d6ab5434e0b20406b48d955cc23c7e56cd7409f3c51c144d5322023cd473e132ffbf33ee375f61310ab414162a627f99fbaf580803e7ce322a4b8d4f642170f863ab152c4b76d47f5b79faf13c1dfc7438783905c0d84c17f1e4d3bc4441ba56827f1010400baec17da645ee62683458301d1c094769aadea822b1f9e9bfe3efe7dd4859dad388c2f9c4b5cb1b8d93fc212d5b64b9bf7dfbbf92020d0f6ef4271912f6756a0d983d5643445f87a730a496cfe486882ff5618cae96d84c57702356200423d00712c61d78cd3f852aea153c5e897752fbbe0b9e974711687ba7fa312f126fbc17b7850ffd81dd7515279eeae368b85776cfc96bb2e2f09f81439e5dacab3bc473a3bcecc001146afe0efaddf51c772942002d7e3c510eee94fec61022e67c25a6efece714ff863e249730b19ae6f3de1ad4bf0cd2c63e4c03c84df6506de12745d90bf7d1dcf31e481b4a800c8b0f4662b1e6f989f0b56572ac6bb8334edb4bc0968567afb8ff51e8b41b4a02012312433e6dfdd60c6c9eb59ee991538372641c6083704b88f3c59d4983ab8a080dc7f3ff8fbde602755794260057d348c386fb73907ae349f7c4ad7335cd8adccba50f629a2014446dc0830f8291f3a6a9da1e09a9107489a3089239e59d18347104f038a66a67630376195f34e17d0d090e7304fd7ba4912717b508016ddb78e21ba9e26c3f3c7c6824ee78d75eee2689e34b8fe1fd26b4649185e17f5902844c1c99e72efc7a2b3f4185b028421707c4b6ef646175aec834d9c089f18b1e7eda908129444d1257754404163ca90be97adcfa8874ba5020ef6da3aeab5cbf9026d10dff36ae14568fd92b44ba611a37bb4c72d73c468329b0779d5224bb8b89e955444bd243229a583985a497b518095fdf620ada2db240e83f8281cdc49e05b3c5aa5e897f2042fb81394fc268ebfad4ad12b0fa8762fb5de5e89c8ff98ecda56183a51697fd98e4e05ffac0fcfe7c1f9fd86aeaceb65012a092de820f6c7c50b9fb5e6193386c182da218a27fa4626de20f1026c5d3d1defd474fe2b719e11569077342fecd399c9a12dda805c55d6ce2182748cbd3de7ac73962ba25a167fc9eb00115287b8c9e002e7cf9497923d70d0cb0ae99c553347ae61a0b1ab4815d2cb8fcdfa6430b444c49b089f8389b3404e0ecbebfc70beee3bb3b944cf1a7541ff215dcd07dfcc64d5397ba64c66294bcba04a3bfd3e57a5a8d54f129e48292edf97f7ddf36760d49ffdea300715c88118377e5dcc7d9240099e147ec0924c8830e1aa6106d7af1e310528680ecf8606a779432cc32c040c1020dde6861e7f692afa58d7d08a5b1ab395cd89b34639e329b47671cfb8f4fdbd2b905b995b51fc82fd23bf37433eb7c8e12acf900a51838988a7df38f4612892c0a1138505405b25231ab772559e9e2885a543c9e26a5dbd9b41107c0db4a9b51ae5cce46a89b016589f4c7d515a203c5a778e93fe32fcef0fa91f64c1c3dbbd5a703f57fec165c5770d82519b9e77a342d407e3478560d57a7588b5758a27e16103022b823a8a6add33de10090c2758cbbc9bbb1947f13d119fcb66a9b3804a604db5c8c818b4f75388c636d80612065e80e36b228e0ba8995304f6e7f270251ad5e1ec0d12f8c81d75cd46eedb5926bcf4f45e1206d3151d31a5aa34631746b9ac96b6ec8a8bd87ebd83e7a28d869487ef492178cc8a00eb9001c84729fdea88c839a9f2ff07264d79f4ab36d6bbfd0c50006a97e8ea795ee321824434dd47e2e398e441a9e5fa67d6c3b6aa5157eb4c3ee84827409700bf8e6e32124651294d14694e4046020f12a414af478807e38a1dc45cc1400ca2aa800f39f6845e140e6fda19a4dbaef80db2f0a81bfe40c9c02d72000498aea40e7f9b83c7c8dfb5b24836f44b7ca99c8a835949dc0006c9b157a893e00db469b24052fbd58bf3402e2e8436b40c6fbd28df26cddf530a0f61935f35b2ffd88b3f7bd870c844afe0705864a1e830b4bab9b4eceb0c03e5ba0419e5ea5eb73e4837ed03bd2fa5288c5788b7a2d9c945db871b6a61a6ba4afddf63de0c2d00feb4bfce7293ef95a66e11e6faa29a9f34a4c2d4b81a2496f96de115ff7576742db1627790b21f050a06fd1f3a64dc66327c4fb6b3e4ff2d8321d450159fed2e6b92ab8d0c6ea225f4ab83a1ad2187838fb400b4788c82d9bac6787d685055006520c49ce0087f74f81e1a6ffc6dceae1773a8ed9ea04c461381dcc4124502b99a7d2f14485eaddf7e8da807079b7f677b12276d9bdd84f2f3ef64db592cbdcddfb654e5020d0981aaa98c87eb3f31d026bd23d57f92156d84c0ef061c569fed1eb49838e0aa9941ec7ed22903bd138d5397244c486546afdd33c038beadf5b56c6c0fd804b3620e2b44b6afbc9cb6b18d093bdf1a82853d73d0b7bc067fe8f92744bcb966b1a788ebc76f287981783e1cf64c05fc55ea7fece80af9a69553b8a095ba804efff373cafb345786cab0baf9eba5257fc325546459b776187180d83ac84ad5437bb4987ff0b1ccc2d5636f54499c962bd5e496edf4289ff6334ef1cda88edeb236bc03d6ec66a540a30ba0f89c4ebe023bc4543530403eb6b6755f7b1d2a2b7fa0ef3c871ffa1c8ee4e8eabeff9125783561c5ba2c5739f8f79fb8942f657dfc1ac99053a8cffca72ff3e535a0ae56a8f53c8e9a7bd0afe784554b265c514603c938868a5bbb6d29d87cacf32acd31d3319256c79198f610415ef93ac2f3cad52beac39a53ef570c592de3fffcd17ea277134c0edfdcff73e54b04816fc2b4aa44b23dfe871767c467f9e2937a2f1d71cd5e459c0162b7202d57bfa5a964864a94ccf2418c97aab2f2ffb7467fc648f0a4ca0e321df9a6c91121cb8e90a5db47147285f56462affa9cf7aa0aa21c3d2a8ad24923443eae6a3dc3e1970e4cf0f62d0c89c37a99b5eabbe1f2d95fc1430eee9b107c959a43dfa23655de09f3e3b5de607cabdb86a552ff5dcd2a218261b3528eca475affb7136f3544ea6a783e0a90b2c2b94a124573919011e3a16edb71d36fe263a8f8ce91f30fb9be86c1bbb1e1a339b6591d13421d1f0f536b7553312efd192180e277c9c6bb18431af05b0abe4d935d8500b324a6c3c6bc5f1bc93abbcb7123f8c6244cb9da7bcc75194a1a57f6202fa852082c3c8b4c7fee55c13062266ec8a6ac5454dd3216403280e729899c4081b0fe40644387084186fc726871ab4cc3be73e557084e099fb8f73aa5858b212499e93605ab59aa350ad13a7a8a652bef1eb2c3f4dcaba08092bb4a53a1f03e6800a0a8ffd0f0479c32a9c77b00d03b4503e0da92290b3cc4954a660a00b73de35ed1ceb0a6924400b0721a7646157bad0b4106000b6f72132537c6a7719101804d050d4c2af8471251156fb7c98f549dba3083e4d5b7d3d73e653e8dfd630e91d0088039e81f26c46a86d398e83d4860159859058620161bcab162b7058c858ae2e2ea887b5c1a63c50c11f78aca6734ca69f686c5a5bf803f5c820b4cf089eaa101bc4e40721e5d463a3986328026f0bc5ef8eca4c7c149ddab1277e0851a4978e9c3502f97846240d64ae13f2dbe054b9eeca11e5e1fc28c44bcf0d26852bfdf0403208bbe9dbcd88176dda5062a1ba79c0463492f645819182b13a268907e880ec8a2fefa2775fa818e6e2ce2f4f9f5e05a185edc9ef0e7d3559f2fb8f7e3ebb4e291612ff24051f9b06baa8d6b1a1a96a046d96e50c0f2ab3077931a8cf542d52a6821dc5cafb092b8d41c0e563629ef6dbccb5d087e8e2f4e2c2755f22788c6b400012b54197c1c747fa163d5310a0bdafaff3f67fe7d92a8db679cb87483d1044aa25760a036f62056a469684b87417c6e93347d91b0ccd7eba66c27966b596e1947e34f18b886411d39ddba4dc74808054536dc240986f077a73a0e52acc1dae9a7ff1f55fce2d0d20690de7cb863674709873f696689d9474c4fee014c7b362114041c45ee59642a8eac1afec3e551d8ef8c187322e5930cf1889aa68fec34f7c1cf055fa7f2fbe0fe0698741d12b5926ddcbd8abbc983c80c6c2e3e5d2fcd55a2b1ea21f11e6df1a90248efc912811e03a7f392824f6279c239051210db9f648270ffee736f1e1d3fa35ea308c188a630df3e097a8cea45dcf53271ac75900fc958254c6abf251b165f8d2ae35774755c2f8a7f8789ff683bb25ec59e08e679f7a723ec306cd70a2642c47f7870bcc5a2db6a8c0f645140210ae7a0c5450a6b3e96f281ba2e18955e287558f32c92c38ecae4b180635deaa64d0ef5d8618e89b45f01cd2dd331149ea2941c82b667130636688617dda424851c8c19c655cbf31768326b63bfca85efc1e0c73f39c2187efe20b6cd6939b375630bfb239dd3f65f7436f5eabfaf83b44cd6ee319cb288bf4f842fec7049c69bace7785d3fc91d747fd4304d95fd11b95abc5b29e2d4747c80fb72f6f37dae4b9ee239edab9eccd243502ef3a4ba75e6c7e0f4d59ef107794f7a214b91273bced52ba63f55e71f9f5daec2266b49cba91aed83e09110c7c0a535a7065eb79e8900808ff58b45fb35259574fe69121812472e2b1723ddd88cbeaae9006cd0672378351dfb09a61743f0b68cafe139b91ce986325bac8f0231893a0a27facf797902096f4d726ece1c67d667f54abffd44ec86adcaaf5a5f3a6e8af622f0efaa8e659dc50f6f74bd07af9c3f239a026070e4cd1dafd0a27c2ac6338fb8b5616d0cc157ca0f8b6c32f13f757ba4f62da075b98e109dc8210aea99f1b97627f702c726da1addd0f99b966d68771417f22abffe09765edbcd6adf53317a47dda29f309d4ef0e5df76ae60df4f411cbf9a17deafbf80e4afd2e7a93f5fa59c6678c7e499905d56e0dc7f92749a0a88d996716661a98bce8e9257ea03d626e5898677b98b75298c2daf0dd9bcaf876ed110c0c0d734cdeeea72b63aff6e9d0e5402500c99bebda78516d3bb9e37335921c7977ca0ebb1fa527c54906d570d350e9d6570d7b8725d40067336bf6565c59e8fc5c7278b0ddb6e4138128b17ce5f65b29d1dc7205d76fb5710885536ff3bdf57a985c6fdeb3391af11be339ce1ec698c66ea9c77886b0517a269d1654b4e81a6608111e1a303564bb598145af19de4a99a05481d2fbddb69681a62843e980eca37325eca8f14d1e16754c1d12dadd011e8e72a945bdfe27316214a52e0e10a2a81b611cdd7dd6b6f8cfad3d35f33e5883cf63f3eacc7432aba17077b17d4c52f7ef6f5af031c5270ba2d8130e4cdc3c9211d44b9ecc3641a2c9a3e7975b677d63d71cdfda317ff3e7697fb399fee9522ecf4a37f96b8fe8286dff64ab2b8de9390aa6c958bdb157edf1a0fcf90f48fcfa079489b07d66012dbc7d2eca42170fe2749cf571c352c8c69d4b4325c811a482492f16deff0fa979e8de8b79e7570e8ad3112c293898a84860965217612bf57aeda8a58273df0ecb046fa413fd3b21cc29074e21f5be1ffb40c52409389644dc6a9737daa082d9bdb112b429cf98ed27cb9fc15d26e9ab109631ecdce23a910a668bc202d0ff40b86863b9a70d9aa84f1bd8ea9b6fa0b2c50236b97aa6c739fc0222c2d634d6afc2f1b69676e389c60ac91d586d2fe5774a8b2ffae0dbadd9454bc9b4af77a68188c1db4519679f446955fb5dcffdad586156645426b58a27e14a8ebcc3bb8c413b0d4810b13b52346113574a6b29543a7eb4c6fd002f6a8e79010a716c68c702210932883e2d4db27dd9be80abf723d5cc47611ad07c7cd95ce20ec990eef3630488e51d4161ecc64cbf16d755b852bddcc38f5dd330e3807f35a7454a7c8f3f9184929e062680bbe6810e278d2e4a0f128e1a63e74773213be7edbc95aeecc3cae550dbcdbb70fe28cdf66be5e4720c797789fe7de904cc30775f3c3ce23bb700de6628d9c5ad7aa35cf7ed13fec0c5cef08d68810d5cc157d6800c1926dd8cdffc76a3a66dd71f15fb09ad07e1abe635afe3c263d381632753a75afd7818480689e69b19ffcc781b88f3b930da3ea46347200a5e1cce6af51d899dc55e1ec81e184438c54c15f4c26d2fa8293cea9adb682b11a49c9ccd45c52d6187f0719d2b035a1ed0f263a2b9756849bcf7cf2bd6d6e6f3ed22caa85f2e1979ff1f4e31ae583407a87f12fe7694c962499a753d510def357d86a467f866f4a9eadf162a35c04150f396e00cd0041597f79d336e5128999be34534678fabd380e10148d4e519e86b60c49deeea4ef6b8b2967c9dc6e58ffefdb206eb734fb80b3f9c01f732583575771f09781c9d3aaa9af4929fc48f997fc8413b1a8eaf5a144f27c978ef8d4e154ed1722d3d2f66b10665df5e044c95e2ddf7f7987f6c40f26e9468d738e628ef42fe8b3c26582545c879cdbf30b2c4b25d160a0394503bb68ac9c4e1b09bc3798530ef49b8cee1d61ab17e45bb318c10e7f570bea7830d52258d21c231ffacc083e72b61c7ad774e12b8635ce1c12163abe8361df2195e79bbdf6ad91b65e456d2a0ce0853284dee37b04b65150b9a14d46336b4fc03d3c22de92f75d49f0e40c9af0791d1d6485d6dddfb5ffe450b50d24ffbde71b84a28b46e68a277ee24168497d54f551d40ce977c0db5a994a24d625fce5d10c74b533fa19a6edad301bfe7c0e58a57ca75e39e29bb17131a3df47a6d93ff6af8470a94795e099f2ae08e19cbcfcdf5664f0a6902e40ebfc1d770518d996e7441cd7f2f8409e756f5a19a061f367663a0b5ecf07f0295bc83ca814fb032a71bd140e83c868539f367a3431d62aa6b18418a370ce468c4a5cfbfa4ff0820f0977ccdbd6b7a3e352d558e9b4bda162d5aef90c315182d25ac50a3942fddf66b74b4e7bd8ef09e19720dc675754cb750bf2c71eb2dcd46fa715504c67d3b5d46b4fdcdbcbc96cfc9b29192f9efecab0bea40c54fbe9fc5feebec59185843f8b952481885f15758979f5db83a5086d8747f5ec09117b4e929a79657f6288cb6674c1822293d6ca768509eb36af34b2cead521f211fea8742d1e93338eb0c108edc58191c298dedb3237e54f02e37e95feaed04b642c4c6113abcfe05140fe17331fe60b52636446e2e925aa04abd993b09db291b6f519c3907a1ef183b688be3bdc427e837adb1478d7d0699628c88f2cf07a7032e0961ec46107d42b40c11c8bf935589097fbb44c6ce4ad6c5b62b53c974f068d30f0e671f3ebe9f52ebce429891b5c30d7eea7eb51e83bebb125d30e512e40af268df56ca37e63ce4b133e8ce12887026938b202e95242d42ee33a3699f0ffecb582668934264f08af49f1ee865bb060b6ceb6215e3bbeced34a68cc68173bc3bffff6735b8cb5a6afcc6b0ac87f6d0426c304ab933a9788eaa2299dc0e748fd9494da06a2b59c5426c4fc48770fa388437670891c2857a4781da4bd0ecd053a19bf27d1960855a8d3a19aa82335f6c18f9023dc30e1e616778d8baa4430421effda4a3851d95c7bf854dbe2905663964b8e05e26796e2b971f17fb43eac58c76eb582dd3832f331cea223eaca6ec2d486b8964373c42ee002abf7710603c05ff86f61b6950914e1f5531b607d5ebe7ee8dfcc5447413a5e2c8bd2e2341310be6194c5425297d4221b3cfc6fd63f1eed8f4074a7562540708f9e80fc7ac865cd798bafb0cf141da6ff2b853b74c39e403f2a7d14bd197f0c169c8fbe86e4e35f53c1a39fb8bbb1dfb1191f8673ee10a5e361ee1b4708a32d8c202e0bb7057079f5b7c26a68171eb7e75abb5adc1a96344451922938a3578f06782fc417b7088f4a73ae7da01b75cabc187fb6e5d170166f0d0e05d956078adbf8d0de7bb264ee7616ba5d8c5435d6e58be0600e1928f88c2fe83d67f5e897f7bc600c2d7bdb312e77bdb9e04ac45df4c371a84e3b9628526022eb0ab1da4ba6d340ed19c026571eb2c70b28ab309137aee623b1a687bcc5746c962bd88cce925fe08cafe6850ee2e553696dd693d97275c840d5c8bd73a5b03377c31de7fe4dc8bc0dc431058e6576261f873b0a6b333de809d1aec87240619d4d92a679d79241eef274bbb0397b8fd443aed16afb6fdb5b0ec47e5d5152313cca6d413a574ffe36d276f3ae31a14532bb0bb65dc9af3e49d5216c0a000c52a4b9b60cbe2a210f542ad03f87caddc6e9215d2be43995ec083b66e3a39fbd2ced5ec089c7c235d6eb2ae34d8b35cad3f00b28844d5a3bd917ec40c75dc275080db6fc22bb9866c6cb9fb2ff64b69177e35100e7c0b957af2257d98856a7c8148a52c8abf6d62b0da1f0637587a3ea390b5c1521c15fc50f43dc9e032bffcfd7f49cd7c48d179ac89d8b081a50b63528e4571fce013bc1f8363c603c1bef38dea3d2e5e6e365593fbead95ecbe569d2e27b4a69adc94947f4544c0d1e077e1654baefd7ccfbce92d55b7f7f6e01a7d5a8673ef24621d6a47fa74786c6d022b2287f2454c371ad495bb101f7ac103a6c3b0d0c1dc117b75929a784b1f2d941a33860a2cfb355f6ba541bc2f6c317e279f3ba9708fa6da61605182ad7627889280f002e09f573b99d8aec95baa127f9ccb0095f275b1a614dfd291247a6187b368a9fb003d59d16428e87470170259b7a097aa4b88c61888ce7cc212c287a8831aeb25a90c70554a91c663099ba9f6eb90264b686b073a5962361ac36f539aef4340423e2d32aa4c769cc3b701af1b20aefaf656022834c5291765fd6a363a91e180605562778becb71f6d88065c17850677d78dbcc23970cd20884ae60ceb5d7f956f09d2555a3f6c4f38c241af689de051c170df69d64dfbb4009c86b1073d290a4ae69e40a7b7451e91c8aaf1584237ecab9f1dfd76d253ea15cb3d9ad8d8a796ba1fa01f89fa598a800d4f63a2f90d69bb4e86a82c6b0568284f826a675175a468e718ec87aa44d24d6e032ae605d8547ecfc3f4b0cfe36f3d21cbef5af1dc513c7619b21765cf15d0f8bb4225dd28e106cf66d4c238d64615eded489fc3c9662646bb280872ed36b5fe6999726bce1c8937064bc17a531e44b1bba90afe63c7c97cfd9a0eb93512c922a4476692ca3f7ddc682761091cb6c7d23ff2a69fa8c77a857f7b8113fd3f39619e2f39bc44ab893f82cef195f1bb82c3204d77fd59c2679ebf11ca2f3a421778f14114b36cc46562b7eac1f99bd305a38aadf5d82ee0d5ac13613b6d263f951bdbe27d69ae87a587c8cd23b7c96bfdd0e2fb44de26a211b4ae6d17c1b773edb8f1166d055abb0ebdee4691fe46a2c388bc5c6be31b7cfc0b9af9142c140ac544e2b4c6c4f176f98c137d1565f52f6b9b868eed6d05b1232685f7d2c833f682fd0a5755d64a98bc2d288e77a74eb126a96907932dc90a2b7e6bd989ff9fb4f5cc686dae8b7a80ec709f082afc2bbe16aaa528867ae8caf23ae1d3c46d4c12023c3519c24fe02eae0e6ad563b92d84a364152f33bbe2e4b4a63740720337f2a3dba47c433664112ea91a68029e618f2975e72028da2dbe6d9d7ac3ea9844eabc136cdadd0eabc6800b4e5f746de8beee8013556e5d0f58f0bd61f0c3b4be9dc7e56ec44ac93d01701931b3a987d3c69971ccac97d97ad8ee03b7937fdc6502e1f5ec9903c1d6f1f4df4676e243245b27cb473034754e36d647527886bcddf6fd84dcafc3dde65fe8695f692c082f2e4a3b37110bcc61e05016f7799605e61ef277f95f937d13397e97d0843bc0b7f3fe433d563fdda06ddf00024e12eb13c566ca61c97f3c7cae55fc207e72a38b607f06123df6e55ed9da7fd90180f8d6288c23bee87b38baa0e80244bcaf04559b38d55939e32c667e0c0d3546fd2753cd99b4e8d4c7032fc150cdc9fe4b15951aa1d364833125de2ff260496f72c2415dfe53b82d444a27a1a5614001231980db3bd146bde696075fa223fc3402fed2cc15fe11e79b84bbbec5ff8e31f223891de9cc0288bd48fae55881f093f7502ac4dee17a0ebee859b4584e4779f5f57da14f9e803f7c93235096e5150f291edb5a5c78d05f03c8014496941747ea76fd0211a9798afe91fe1369586bdcdeb140fd317190d8c0faf6541852521e6b386476c1cc783686f2c15933614303651aa2d987e3bc03349504e666121864f4bab4304322fe3082da80049d5cd4ac71eba1fecac46e09aa1e9e133491e181162f43f17dcde2a3cffac33a23052492169fb48c5819fe6302d0b7dbffd0393b0c189c19df6f361d9570d4e07f60df5ab256d293ce7df87e61505d246e76a3633f228dae5e93095349390a9375bd3f78494233170c71292f8b2230c97dbfb922b87c73e6496892883ec91259bbca49df1a5cf2f0a1307dc73b848bf8c3d179903ed5d17c36fd2f83a91c42200e5bd81a88f0ead16b5f6a78b0f9339402221733c75f047af5284ed52def1af69fba756a498a3d7e4fc9f4c8b172f2048ac2d0bfcacf021765340260a3c48855fe313ab1a1d161e33a68a330a262089658f725760803f2a6145785dd5a5f520ed6933e44f3c3202e8632246abb151caa39723fdf048bedef2f92ed0d57766f33bbc8cd0e63ad3314e1b2ed5a763a2c4050951a9988a2a603628fea6fd4e9c22f9567ab15d0549cc0d811ae1c8b6997654c0bb4f21330dcec7913a1b12355f41561c6fd32b4e5dd139fcd717cd4d42df832f5a2d1c13756d05a4579dd5eb6086cb72d37e6cd3bbbe7a8310b4d91da7e4ea634721f9dfd9c964748fecf0979c82e2ba7ef608d2c0e031a1c168b65cbe8f3ce0a17c46508ccc14823225af6ffec3835bc173f03601670ca15a29330cc08e4f4e6901bb20109a7317ce3c546f6a2eb53a36a5604e64e06397811adfc58c38429e499fbcdfb089a48f4af15cc2d836536cb7c518b54f37f562ac8cdd123520900f29d58796bf19ac05d0f447fe1a77f31418c2c8dc8b4f6a833b7c3a3128c38fd4eea6be55fa366fe70ad2022897e97d6cc14440fd7bdadea7c4bbefb5e7295b10e8804375ae966c364407f7bea33d7cbd67bdaff09b0a819c393e81c6bccfe5115f3d35baa3d0335a0589e9b3a24bde4cb15dc9e8e7e4a4a962af7e276f9825af25804434eb161440c8e6fe43e249ff34fd9b580dd9360817a01e261c2fd6b7e8440661f6f8b4972c8a1eb4c0a320e2deecdf5093b079a4423868441375f84799a2ae0f04ee1d9c5b712427dee57abc6be32ca4dd4b0c4fc5498d8beb4a54f6b29bfd93170b2c3a43dd9937ac349fc10735db067c5ac6467087783f781db6adfa5b36e5553708c2107ac19d2436d2c428aadd7f245308da156a8711f2dcb4dbbc97f96ad892df1bf12c6747646cf299fc88e2a8bebca3b66fc57da62094553023dd68efed6db15680d302e73f3f8fc84644a381461bc975bc7dcc2b60b10801d2764de5f8aae4df941fdcf39d1299ab9c230a5ad99c1079e95b9e049b3f3fa09df3ca0e6ec442e1530c0015fff223615898f65e29e44cbbf71d61698b318dafefc325d3b9f372036a7679a016f643f0bb0a34fdf38171a43bd16e8258b72f0e9be1ab6500689fea57362b04a02e4142af50c59278b25176d39dda44790c4cba8357b833fa8dcf19e7270e41603728bf41e2a7559d192a32fc6095e4be005f92dcd2fa875ab749229184d55a360bd010203a516ea3adac567667a6442f3262eee086c8b19acbcc8c6b1b703261bd8401821aa3cb870c56efa96b34dc64c1fee05f1de20759060a5a4156d74e15281b8358d5d43460e23d0e413edfd568ab1cd3e592b57e88e0c6d3d5c8d609aeecfa85102c96339620ebef5ed0e691d7bf20d27a06fae546de7b8d00403031b8195496fef0d63006e7f1d9b1f63d7d4851073a047a02ff475ef7379459ac7f0b6d78ff9b7b7b919f28a8d7e5c3925dfce32a918ab4892c0062d1823a592ed6f2ba6d2efce9e9e9c6aa9ef527ffd353625df85ca7250080d414a220fe0ab250b05d6875a7716db1ef8d2bcafaa3d375367ee35f56e3104a6160c5098f830db37bd9aa8200fb57a6d9aed2295ee7fbdecccc895489adb2d1e99674363961bd4391e2cdc1f9ed1ebda96b1d441adad409f9b84dcc3b7545360f14dd2df2c941bff126184522813ec9d74c1eda4204d58d2e769f0ffe0979efc72a3368f3a37c3a52bfdc53e3c817b87b9f4e7c75fe1e4ae1fcbddc4379bb3fab81bd2c23d8f4fffb7ae98296f868d34de4bb802cefd88cc12b62869f0e6e7cca617b3565a95a264131cc514cfecba571631ffdbd6dca78059a7b5ba67ddc874b437e479f77062e14c296a78da3938637b2dae583f32ddbd35b18ce901be0c4fe25048ebdd1e5908f7b9a8dbc3e3bc151c66ce8a2676854757bb521cf1d58828280a6416f59beb9a9c69fe4757070117e80c45ca67ad3334a3333acee51feff569609f26b6cc2e56e4a7f4aa1bfb5081290f265d6b3cb6560a10c39be2cf12149f8dc8cdaa6cf0cea781f515c47008b84ee59c3e0b4298908bdef94a429405999c023e7efdea6ef1b1f28a95261cd9103dc8fa65bc944d35e84aba6571417232f083a1caceaeb8877fc45c1c0655032f6cc95c4618dfb9caea80a756cf5e757b6b7bc2eb5aeb84af582dc0b14a87fdc5187846f1825cc570b3eeb7f78781b3060ec3adb7e123d5953da3ba56e3200e493f0661c1c1f3d3512e29aca14c6212987108b134bdbd11ff196eb7ba16aa521c9991483360c8b94f7022667913def1eeabc40b3297688358994db20a0b3117c5970fe556fa1b9a9233bfd2366e7122882856e6e2594dd11e454a7a1ae351fec9e8dfd13ecdbc39051209e956aff61acd4842bd84d291c9242100d219c81450f85b31e2fd9c233de0aacd71b983a4e9abd3d9d0a7fc1d83da0cba8db57e684bc6f1f227f2b76453cdefdac5816ca53f6baea2ae2972c54e12ab0e013978a20b7ca94509200dfe7be47d6daa9182712f03d79ce9f470c22a5eccecfef56bca790a5d34f5941ca03858f7128c626a61b015b7ea7e47151b3fc25899970d6601b8f3c0e255dc8bf980eaa6553f96c136c789c0593a59875eff5ff7f3b6ccbf8789ad82f21f5bce17b3fd1dd24744ff08d60fd8684c2ff5a1854bc1d878a0971116cd8f13c529964b5fcdda7b9a6d95173f75eb0938d8d354db73a22aa6c0739ec8e5a60b79c115225b96818dbd98c6733855c2d769266043a0448028b619ebb20002d804b3d80e65e3d226c082020cea82f6aeeb7ca72c017cb5937c648e5eed400f1df7fab077a3a947ec65e088e828c11fa97d3e0388fff58e08e80db49624e64839e972005adf51ba34cb9baffa8d761c68bd59818b5f2d9d80b1dce823e06b33e029059296acafe4933c94a49b21577ad044ee730815f766bff84664493366ee1229c8cb33786fa67bad3fa897ef96c0bcdb1c8c376ee85fef6b59efc0b8c83cef1dec7229f311d49f00d633629b639ddd758244711bde3312def1157f6f5b57d7a64388660ae275996034534f14800b0a1f41a3a1f8081da087b550739578fb0bd1bc6839e936dca44c5c0468acd5fd57eaf39c2dadfc083e803ebdcb44d7eb6a3a609c182b6a177f4772a9ba21b41f4818f036e81850fc3ed73524a685b09b0331b570016084483a105626e1597b88d53fc2a8aa57b01037902d3e9b5d86f67ff37e55598cd3794831274d4f5671ae7a9e69cdbbffe6a29aa31927f7fde035d7278461cfbfd82f713cd4e145bf166f50190772f5b9ac72c244223213e4f3749190844835722c30d6897ff734f3fcf3d757ab908e56fb7d648a01c820ccae8fd5260372202d1be122f1a2de2f8dc5767bd95fd356d1480092d515b126ea1a9e16a6659a74ab3e1884a623c0297fd29a5db48dbf65ef393182ba1555e14eeab95fbdee46c2d474272e3878a6a5642941c9b9dd79c035adecf17fabdbbeb0ec0a66b0aa0bf827ee36f2863ca8a8135c83fcf9730dcfb2e30e5e36bc675a6c7ae48f88a8c0f2f0991cecb157916aefdf7957d984d18b6c6a73c8de9194763ae024b178f6179751d339d37edee05dafabeeef42d32fb5bde9f134096863c52bdf4bac54a1666eb6e0eb983f8625f9ef75457fd18a2084203fda3cea16822e69a45766e02fad9d35d6d2d8bfd92ab9ab1f87f1d15a3543fca4314a8888efc527183cfddf423a4dcb0b0f0d1b4fd789a70af2af6617d62a4b82fff06d4c77ff1ebcd382f667708278588c6d518dbd7f12d9b86d2c139bf401c32fbaadca32b97205cb79ec62ad13b5ec92580d5b9f42722b81e7dec3bd9eb2e71e3bd088cb7918cc10d838b0e8f71cd82ca137b459eeb4164a9c2d83ae38023939fe76b5e9abe994735e1d4a74919a087a40552496c67c17098359ffbcedc28ceb2b95d63f869562954abc533ffb5514a315e862e0c1eb1502c6b0d12c54ab3d2601d383ded407d404ee5d1e898c6dc2edc6b7aa26ae1bd3ef7fc6786543267ef87c898520c3fc0fe0b5307255a39feeabf9df427d41fe1aecabe3ba71c6556c8a53db8cf5be0e3aea853173ada14f5dcf8f429cb973eb90f6b57160f9e5adb93fef23e43e2ddb49acaefa4683ceb1657bdfa27e3ec46f11013d739fa7f30a9f91b5239951c7677342b4bd0ab2fffe28255fe13df52e9d44028e177a98fb9ee5a8ed5ab66ae838df2a9339b363fd571ddea4d40bd09504e0931a3c0746d727d6102749c97196b1e8306c79f03cf8600c1e4809549455fe33fc5caa69a6b12fdd8f8df4bde57b351f1f565ca13eda042ba40d3b6698eaf444a1a2f027803d01bc253bf1510f8f115994b22b538e2be938870e867bcb1165d1b027cf0a5cfaf3f6498dad673ddbd73d69a37090b81efb4bdb33e0b1c8037e225accdf8205e089244cb55588aa3ff0c75c68e4eec19b024d437b1602494b7ff00b957331c08b4280bcd2231ae9e02635549e44aa981cd1997ca52bdda0d7a16e20a5b17f0a8a5ebc1a9a9f6b4e99eca346dab60ae742e91056e58a6af7072f41b5779533561d0f9e5c1ed2478271786711130faee5a43ebc756d2c787de42bc01ce02d0f1235bd5f76d26b5462568b00aabfc7c1259eae8ec63012cb44c6b41bb54dffb4d9678d26bfb0655eda86559d3dad204ad52cd0ddfc52c9fb4dcc888b55ee1ec1c290e29d31d2e174f48137364c6050588157e76a44cda00de5b3289fa5aa2aca6b0af2a8f95a6751d426e306363b4b50dd985740b555b8fed4babebbffc89c9c2191fbd7020ac52ea899a721509d23c31838d015b0a8c3f063542bb4271688bb746f9f695f99b5e17e8c901563850d01f30201f501b8f8545053000fc4b233d7e9330bd529f006bc5082b61c41a3953e84a7f1b4436ee0515c46c603861b705a43868e93542ffebbdefc47e90324e858159e9b9f15f0b0739d9dc0a68a059f810ad91c82acc5238b42a70335b1625c3c8f047299541987da48c29f12a40f286577e0df78e9bf7b990001f5f6c90a90fe4d1d3e0921cbafc738aa1f165d269220d09b26a6e3e9e4ee8b8d36128d37b4d5d075963f9c810f85f4a1c7c095c8c11d9cc857e126a9b133382e4cd4b82bc05a9a8aeb19241d8ac408e017c0e4744fdf383554c5575f25b2399f72a09eaef5a292641a5d9f998d0dbc6a77fb06f2cc506a0e7869e68ac8f69141bd43bdc46194d5922b67009ac3cd4cfcd6c22d47f184902e7c5d9ebb06733d68fa15dc3fa0d8b5afaa5092209176f627e5f83e2a453f1a7267ffc415be6fa0c9aa93822ea9f757bfbf5f177988483f138709a63a9c68d5e066efdb8c11e5f0e070709c93fa5834af0e09926e07143688ec8bfcdc5c03ec4b6cddf99fe8de5dabcd1cf38eeb3e27b709d621bf36a91ccde3b6f591a55d8732bb17174168a2342790eb32ae606ce75f9b21550aa7c0c56684043fda4eb8ebbf2ef9eec37b88a9cb620c1b4518bf18c45d45b861af6f3f64b16b6d781337d3fb108ae3a9b3de8d85a1525a6d11207cd24c6b259eb6d9be2d55a4c5d31bb394919de6ec58e9bba50e96bd4b5086b75d385d7798742fd9241450d9de42f32ed52263430a56750cd79ca8aa17f9383c61f8a0ebdb80faed7e90d65aff705177942ce54dae33651cd3b4eaddc705934c1509929fb930e7980a18ced16fb81ec559567531e3a1b0fe3aee617bd272fa8a1d8abcd63fc4818f6189d603f681f2ad0bd641ff141ac31bd8fb53a3cfa3081720d7776ee142f93b3420d0772f2597e74eaf2c2d7b65b104d73994a13413aa83cbbd5e92f737dbeac3a5d9bf13a079b14121bd5f97474d02f50f88be7aabea4e6051d425387ee754f1aa87cf95d25f1f8b058e488683a68e74f3e47ed8e7af5b27ee818092d65a9a18e98e89b0281289000fae38a2d5822f3f4623dc590fce54094dfc51ac5cc7bcf5e06d356bb9cac549005cef0e36816064e1542a4b07ed64d9c21a5808142f651d68c42b80382ad3f4ea12325ac0442b493d8156c08a2c0adb143275b36d73b379bcfc23b1d03fe6ada07ec8393c9ef1d93123416e62469413219b793753e45563ba024ddcf16729f8a4efa86d33b41184d89758eeeceac14f741215d1de98fd38619d94ce30a942d4ec250e4df37d9a581620a459a25144311f0578924da01949a5d83abf9cb7151ae9c7fda95c7b19c9f2b14e9bbff973db35d6ab6e1450616bb40edb7f319975fc16d03e31d0720c11ea3b8183fbd4bdbc7602c6b726caa4a6676aaed5469035768daf95f49d3835c152aa9f17a97b8469c3e94da86575871ae793f28e0328934bb999aca8357bb96c441d36e9a6038cf9ab3ecbc303d70a33e7d7f55efcc7952314755cd0434c907cdd1aa3a5503d8badb097997df626fc06bbaab7bbc657207681fc1cb8f391bc43cb3f076bada1463edacb69ce7e60086abe3fbb97e924c092b4234a800b3b23c73c75444fc26e53c511b6c590c71df24f9d14aa5e01ea87c387671082a7623f582b78942246f732c697693d627b04f2a0e51ee01fe815498192479ebb813f446bf458fb4a311818f12b045ec2c1c44b2a8b705d8055a1cbc94944cf7cbaaf75ce27560a60b5ddaca1b6d9f0dfed1aa4387e795b034882259cfad2a4f8191ef6fa2373a4032df339d9d486fdd36604b931bf4baf5b59cd12634a02c31368724fe00bf657bc46980effe1d0956f4947825b6566f54e341f2433762cac479c9f70869e1ae32e15f658248675060d038a50588393e37c78faf83ad38f3cb62fd2ac9c2998259a5afaaeacb78520c6d7ba13667ba8f1f65d2a256ceb0d20808665fb8e211552fa5988af4fe5aaa3f24a9d2158e55ce5c2e70cc5e477af261981294d38e7fb559cd86fd2f42733858bee9497cc9da4294d97c29330f8fd75ef9681e891f90dbed0c53f461acfa9a676a89698d9e59c5ccc229ddc6fb2f17d8cfa920301e4d7a30d992e45c7669af33a19e8bf461313038dbe651a63da8e3db17bee1f72ce654e7a09dac6e98f30cc59e7f1c05032396c58febd24b825e0a233ac22f33f4afaf7ff3215566bd5425eb455db458a0fecd2e62fc1492812171b063f4ec6ab133b1005116ce8cc3c2e107643c46cd01616cb33b4e814321f13cea9db8b89203865e108ee09356a2a02b19ca258f96bae4220055e0ed051ea4d843abe7ee34b469a7f4f9cdf2c561ec70098d377111bc26d6f28a5689dd57410eab0c01870eb1fa3974bc8c099dc4cbc0caca67cdebf87a3b3ae9f85b79e2592c7542523f7d237821572a837c229a4d967a4217126bc7b20e930c0a7c9b134313143ffb620e1c2f680d01ed5608cb70560dcc2f9c7dea82450b95947dd84960184d1688e1e05ba39f66b6c7da7f7f0a3ed59a40ac84d734bcae1ed67b89021c65fecac86156cbf3b795c47c2cb7d0996e78868e09533f4ddc1061a1b28593f9b199111ca838f935e2a320a09e68b419e9dfb8525fd7cd6be9f168cd109220fa078970782767ce589383486b3909c4eb3fe28484f4b62014a5b0b7a47132d1b52714549f3f90ffe93365275cd7ed4fa92a8074b30f75c20ddbbc03aebec29829e6d346142702ca7be9415a960f7ea01f6c50e0bf7582823b41c1cb1036f75923cbda01d1b2099e557fc67bf7a14c9da61140c1b50d8f17df1d86620791a3cdca4ebb0a18e3534207d77b9bf35e40807c846c0cd4e7aacf9c2038a58ea849ed670748f81ae7787d12e4d5874a4391543f107505ee44e4088f63f0af163f9087856a2909159725a4749ce8934105fb784510195800e1ffc6e8d54b72e6e38653c8f3fdf24c56c6a26593ed86e55bbffca3053b0bbce03ecdf795329f12056f814e706a14c81b7e6a5a9c9bf6977027ead77a5bc89a2e344d1fd64e203c255367a37c93d5732da51d9c08568a9eb8f9d0965adb3eca553fac50188f9b02545d02dd6046d5fc64d0d8c20d0df40bf82c99fc1fc77120bfe3b6504b2c7acada0961bf14f1c92c86d8c237435695688b01365d816a894d7146a94394d9dccff09bf01ce092ef994310965a58d2fd39a05508debe20fc7898e15ddbd48283082297147cc6cb34aa04dc68a27805553928677c609a2893639efa32c34fecfc31841aa6f5be997496c7192ffc3bab138f1e729476eaa0341d6574acb35cc91962a7cd79993dc7d5180d3681425a2f3b60777c65f7a7c4ea120e921f44a772b17eb9d79393b2378da1ec852717c8798089ff3cd27703b9caf34aadfec6cdf1a8333907a8c00a8a58e27f007d0c1f2074b6ad7b578a186143cae640e92843f023516d8f660001f644b17494787edcb267a1b930d419e2f02fa673a18cf0281c69d3aaf2d0e4f505d2d88de4b6206f3455c2a1656e8e97f7d46e96899e97aa632e7495ad034d76dc525dfaff3399a25c9ee3ec72b76accd644b9fdfe3077710094a64a49cdc52e48853dc591856f8aaa5c236b2d5bbc5e0ee0719d7dbe5414d4cf1d397f54ff4c7eb60b7e37ee95ae0d6a39d988d73a85bbb3556dcbad470420eaa9f570512e02310ac414478bfa22a4077d06a3354ca1ecc627a16f223536a0354f75e0deda34b6214a966aacf716c6962633edcbfc3c4793c955190dff859a9cfe1af967822c9d6a904667b5c36ec429c6ccc1232aca222a2ac2aebc513881a61f434d3e280fd3dbd18a7a007286ccf6d6715ca35b96082bee1a03893b3800024430e82e81e27677e7bd18b94375003972408eee72977c6aa03cc224837a7cfc0214ed5b031bd3da11cccdbfcc731b6c29981128bfcd71c8decfc9d62184e86754cb37bab750c7af70507e76e3c10dc311227582a724b4902cf8175fa0a268303b36cdf0c336f2fdd413894eaa87333de35cb0c12e006c71508468cc264900b57acad1efb83fa43f577bb7e01a8a56ca9107b256b683a3334d02ffef780065713a47553861d37fa23a2f41adcb4e112ffef5784a282cf19fc67d80e5d9d375823c031d16734628b1c404ae7411f48344514eafe2d9cd4dfc38b6dba904f59eaeb439de1b334694ab1e3572fd637f290a1b7c6c82a9dc72c115e83fdded084f828830dcef4ce4af70087352b4051a6444b4da6055b221eae60738d3120e979fc89c86b77ea486ec3914235eea2477ea9d27f80e6f9d628b420b2c6c755a1b5c22aea949a70403812d7d0159163c15ef7a898254d1664044f7f4f725d43f87eb7e67cc7a29668a7ad269410ece75151905aef25a8ad0df7cf6986e9c3d0de02bded5b2d7cfd9a4f1f0809897b55b2bad830073ea9af5926ab38afde09c475b3d2b548966627b53b6fed29f27c06de68830fefa37888551646987397c49524cb1db239a9e6db1d18337b90134db7ff548da38ea3b8c419314e6924f6e7551583b957b572752d11015f64edcabdec95030da4fe4aff62ed41c3699e624cc7c3a43567a5223e15000b5503e7f3ecea0702ebc508a93a797f50294dfade7e8c88e4451904d136e908b14151ae84a3a7a88df6bb88c6fb5dc7af3bf810d675e880067604759cf9aa96036932d5e79510b92fe64bfc9871e9f5141f0af9222e1e7a34efcced714a29fcbd34541cbb7f2f3ca27ef7ac449b6d0b7607257c943bd1a6ba55a338d74ed95255cee5f1550e771eca68bffc31db4564d0961c2f22e608d5961ea547c71dcf4cf946016483163eff70325b674069573ed434c3cdfb294890d224a2deb28ae5bd75eb6b6c0c6427aab49faf70d479a150bd493fa8bd8c073c218b4743322cdf95f487474dd34af7ffaa2169000b368ae3f0495ef8994fb1df57c355c9e9597256828a6672f50d4f35be74f9d86e75f31714f78296abdd55b542933aa909df1ec93156f2273ef9ffdd86292d0a48fc5bdb0efc7be180053959353943206473dacfe1b195f6fe0894670b40bb473f5b65205287d39dfe4569ce38d7ef994b527ccf03e1d9c968538a265a020965c3169ea15e9284b06bff4b4b5412ae1b29bf6fd930c9b2d34a650b55bdcd4ed08f5f7042d205ad61fec64c8aaae9f692a97b524ef00764f0e77844deecbd908f11fdb94602a249a57776cb7d3478f3eda90d8b0409e6463c48015b6b7813f80b7097d00d80050956e100bb31ceb520f4442534c01ecba0006ed4e14bcb80f2fc9c07f4f30ade603d50200f0f238fabf634799de928a7af3cb9c446783f8a8ed13c68405302084073904100a8406a0e81ce093522d0bb2301a5e330cd9dd39eb652fbec95bddb403eb5316925ce02125fb810c8aeb12c242a8e0f09442664ee4dbeb48f6f611dc0fadaa64ad2030984bce5b7e2e7439d092f861be5b363a8a5d5e49f9564d74fc1024d4fe6f26b96a5e9e6eb130a8721ff4ae9e89e77887e554ccb043e2ebdf59f41ba821d7e658f1a08ff0d169f9a4ac9959585ded1f1012ff15440eb15f3f67ca3fe6fcde72a6b26272f30a56ac2dd12894d4f312460e1404f457b734c9519202ffc7abd69b51fce1d8b4f8ee1af7430220fb65136ac2a1330e7e082715c4ffa5a6c74d805a274524d2fe104c2df0486e516bdaeb5b24eefff3eaebe4fe37791178f501a1da3182a2654f3b08ccde66e6ce6b548b5bf5ed73b0a87b0dab7d23ead3caa9f683b055832564b58236e0de9d25ca6c2fa2a08c26b2ecc490efa57433275f3f6a0679cb06ba7cf8e3b6255fa73f92fd5073cabb392e6a213c690c7afe61510faa3cb6d732bbf133a8dbf1b06ab8abfaa96df6d0a485193570530afd27dae27e2e90e1cd0511087094a451808a09e80b6302356ac6d78202b5a500b5e390c469ce11dca0257f74bbf3296eb3f0c64cb47befdb8b11b93800a55a49108e1e84db9adf960c64a3fc17d062d9956eb2d062ab9cc431c5bf63fb63f480c1c644ef756358985cd409e27fa6466da1409e40bc815c0f61e89e71f16851ae5d326ca6bf7f20fdb565ab8ae82ab7e1ec12718fdd79a69c607d2fa3432f36a529e69825c11ecba60b9350454ee0b6420d6de3539a9e161df00c050cfc44736a76597f876c82b05d500d2596e22dd208335366d9da450d453b727369b3a23fff0bd72c120c9b93438afa7b8b952a0381f1317a212d3a1a8dfcabcff0f426f4f9caea15950279bb0d1d5df8633fb0dcf8e4a008897d9e3a82a3be94f8de5b31944cc2a354c67ea069765a44a6d3a50bd5e82377676742b7d8e71d3651caca75e0e1e1fdab23e4ba623a12ea8a3efdaa2b7452d21a7cb66fdbc72c01557024940406227e1d63fdd8232565af195ddf68667765b566234d86219bb7427f169a0cb0999b6dd7607ed795dc1fdc1854ff340b1b0edd9551a8bafc17055834fd302e4888bba14ac95364a32ec64cb8ebdec8d725c7f42492f4835c2e310460e970e0ee73abadb775a4978260e312610e05154841cb444107527065a2e00e523030513082147c9928f8831482982884805cc2492112a4509b89425d904b83757da23492a6d25ca2255a62a5b5b49136d256da4a3be9207112271da5a3c44bbc74922ed255ba4a82244837e9263da5a7f4925ed25b7a4b1fe9278992280365a00c9241629621324c86c9081925a365b48c931499245364bacc9439922a16b1489aa449baa48b55ac922119324f16c862592c4b6489644aa62c957f244bb264b92c976cc9915cc99555b24a56cb6a59236b64bd6c94cdb25576c80ed929bb659fec977c29942229926229961229c926d298d15a6781d81a3bcdcb35c01b51a79d3b2059f1d3edd0760eb5d39a812ac50e57679f0a356b5360a770f05f5018beeec92cacbd63db9c765da1a01335b6a3f99e86bcca5de94616255716271d73d7f8c69045c25c6603ed9545f1cbfdc552b6d8d1f678d4fd5957a8865c1b12ca5f06a5c95dcb4d530d2e8fd066a9cb936eee1e3bb7ea6a4c5d8c397fa61494911419744bb8cc774d463245c20a8fe1995fac314a427699289fd80d591a52d9cf009cb8ca03980ab348b13e74a8cda8b97f5730e578b1ba4e369e9885aa8a3f36912ca16a5d3ec549f2895ef522595c48802553718ff8f6eaadbea7e8e8973963538bd545148998571c415b582ec3788cc65c5162d52e3352b19b113ec991a6b6ca3d34b7d949d1005dfde772ed75472f8b055284f6d85127428d20bb4c657c7a30bd8734546d24e235a14251cc5ef14b7513719f9aab83ffecb4944e64be09afe5e9e0273711b294a1bf5469c86645771813a7a97350a105bb10edd058f0fe5a89c10bab7cccd7549e1415b429a90967c58c0d6ac2e3d56e76b0cb58a80ec5024565abf2ec75e3faee3f4d2340f9dc9409d8850ad65c834676aeebb0c32a7f87e7ff478a1274e013043dcd885864b821ef3f3bece0182521bb4c655c291a454c3b0f5ee5cbb6a68ea418c03ec10f370d9816e7e0e2c92bfc78542a606cbe89d42652197f568edd6e378c4fa41ce77724a973aa0424cb8474c4e09c16c5ed7dabcd8c6113a9b8b7a13f11ea7940787e95b77f9aa292644b68c0e9fa7cf5b4f07f72c41f5e0d33b6c464af4da243b86c3f5a9537e3e21cc5133dc7393b29bad0264fcc07695befa26c1efbd7997c37431c54681d4c3548da26b5b59b1e3f6512dcbfb208bf4c74d4ea8d0b83613fba5aeb0a99c148f131a4f669f1c32875c91e3d80f2f7f1d9f5f38df46c2d3715680ce2e9256b410847a6fc67deb6c44abc2408e895985b8a7eec13509f849f0a06e17c7e56ddeae62a0ca40e2a39d545c70dc3b14303635be5b5b126fba4c6982721e14dae36df68e2847f472da9615269a1fe421f965926312d24b040591d6717b3ab70f15a85834aed9ed7da9acbcd5b45c4a7c91e917d95609d60060b8fe26f592f999e1fa21e5c0dc48043cdb7b2044fe3b0f835ba4de2044923ba8fcecf011a11a956f145fd6e5c275e7b07a34b0364e705029cd947e7e7b2b7ca5a838e5f75108cee821e154c9223684b188bf6728118504ecb5ad83020c5598b838a2c5c494ec6d2836796a1cb6346948d6ba12a28dbe4f3fdaa3cc304c2beb7058a3299d4a30af00aacce03d53539607d8e8f37c5fe1e28cb6458b96f56cb763b04d4702b1fb8735abf7154dbb7f3aa58c87123abdb2c361de8d70e4d475d7a19c96ada5d8759153a052f022123db60427f1a6e0d99a93326d4638844ef4216e5adc8362545ccef504d250b16fa971cf7955ee8fa9871f02b838c086bfa73b67168b4cf6cedbe2d48bacd8a66392fa9ffd8dbbe207e3603fd0089f070361216a89fc9724a794f8e8d4044a0589371c26ee732b52a1a28c3e420e1b57eb71a476fa05e152d4ff78a06ad8df27a542b2c730e43e8bd3705f1ff41c041852a8c0630d45c6ec21babac9419300fa8abc931ff31e6f556d68e4046d80cc290f6ff94a1c120a0f926eb4dde5e8d45bf6a81169b2c4e3e12a73cbd2c0305992cbb5774b8c2a7a502b534009cf6247b3ddfb83db29dd8d7dd4a5e248250b09f6bac69b746a19b43e4d835063386830a5562427ec11eaf14b32486424ce6f72475b3e28f84405c6ce6f1cca295437cd86c81e24d569bb9cfb6efc0be00a94d0e1766b6e9e2f53d076a6fb2ae6d6f5713e57b04a8cf56967d02a9e4bc95fecf799dffcee12020409c0cb256ef1fb6fb2b5fa59a119d8de4789dfd200fc58e48c8f544ae0e1de88d18d67971c375377e0f8f102db44250ef874a6cef4c05ba502b88a65db7b20588103c01ad69e3323f4b3ec3c6619729c4fe4bbb63e1b9b44ef0b56d38fdcee4b94214067f031802a9b5666e640a38b1bd4a20ffe7bc3af3ff4dac76d16fddc1a85dc44a790e0cdd1a334e1c20981750a6c9a0ccc1c9764a6f018a333902ae632b1d19a2136488cd4aacd1db55c006134d20e34c1acba089c46f59acba4e944b16e7908629cf4655276692ed9c849f8d9eedbc8a3e8a0a28fde857074f248602ad7c66d8d7c83602575c35bd8c21c28bc146e9a7e0fad779fbb6c4e1104e7fd4da62ae2483684b761fe98c7732e0f97ca57c1425eb36a03ae8a3ad5ee51e8fadd85556ef0cf004d25574ad29f1c79e69b9ee5159b0d2e23347562c2fa03b83e4ac1506c5e2972dd7bf6764d22c926c4b8be13a2265e1369fd4b3195a216d7f3b200e2a7ba975ef61899b2ef80af84d6c94490cbb6cf5dce473adf1018e6fc7b140fc56e6df5f0e7836ad21bf1453f4ee84ec0b28d7f406322e070eecbfa24a53ca221c003d78a85c4e0d20aa0bf936fa7c2576f9f7a3d2aa775a182bff4eb35fd07cab91834aec8d8d3cccaa8bf1d3b52e0242bdc69668f801e50efd5908c2af7d75ab29ff097fc2fab8b53cc647ab9b830a8d58b107899a4d30373470cc49728ca1d6a298a21b6d6a4224c72825cc34e1ae9979ff3969e523e939a8f0a516e0c2da0c6472cd1bf97a9ecc0cbaab7bb455c06e252fb04779429dcc518c9290830aad83d5daae2a6d84b69ee72203caf85269d4fd492b636e47586b792e804810288226ba2a30a857da83b417917250a13a18783f2c09bee7cb760fcf4b44fc8ba87e991031ce2db2d8c0de48fefce192b6f33aecf5d0564c71cba452183490d8f0e1e6a2ee01d4047f59949f7249b90a3c3509d0b20a8c94ad650cba0bd5d48be4d2bbfbdbd6c3e4a5298b399dd9032bd03010497319d5c322bc8aa4d3d409755d81b1696208fa6aeac5702de4140474d4dd68ca62b9982931231dd8033565d1a7f20825f69312d28b0aa254fa96a53d332a55bd288eb31b9a679c09bdb11138b05fbd3e8504e098357cb4b9b85d001d23d29d98f696a06f71ee19323bcfe39672f3f052ab838a44f9fbb5c694716e7ab6f6653b6f00fde7fe7b1167246831913101bf8eba5e1cd7589e8cd2e678bea6220697731c15cde7b486a25ef88a68a45f95f4af01449a7ac15c2e93762dd4097e9a7a915de32fdca53665563d2a8b1a46c2cea9d6cf0dce6a465a1cd44fd6960d3d1e336e5e20a80250c39ff5416befdce2f38f5e04684c0557dc0b4b51c963aca988d1157f388cdb72aeaba8886126962627995cea575e69122519d28b49c3efc57ada30071fd8cf8eafd606d655031a3248d2246762bb2c666bfc410df50ca2ba670ef4d4a13a56f6874a1646eb1cf95e2b8a6fb51ba7e14616317309b4410aadc63382b75760694b0d44b4776ae35335106e41a437ab634d7227102d1c33e6c52451e10f42a86d45b82fb693775de4aba6220297f1be7682c1f3a0a62248d723e389ea8559b8a622341743da2aade75b86a65e5057a6ab01cffa1aa9a2223c937f9f67ed1bf87b5e00c5cee0644de6cdf1718cba56b46b3b6f15f04ba2a84f151167cd2404fe8941702f8aff6f621c54a6323efea8b7ff4115bea9260068dcd3a2784fc648cf7706ca5c82345f7f7dd3a0b21167175927651f9895d1d16c9517f0bac091a2ba88b8450857711ec6991a71384337afd04302b49e3bd497f286c9a2348a1c54a432eefba84dddd702d7bac24f171c9ae855a00e2d1d7479211ad50654efb3a4b208b24b4be02333e0e99f8b7859e8c910b90388018be1a08d2cc2e6bf15e0122b271651a8a0d1f44dd8c5a09c1241336ada1f8b008beb7051f95771628c867fcd22d368407723b1f91f04cda318ee0d915690033f5ee8d9d7781ee4063080caf85e17542eda25beb9c0640629c55b27a0ab9b567ee3add97c8d408c1b71dac5172e514b350ff7273ce2c8598a9e0f918769018c11dac5972bb372b0be23dcc19732a987ff2c986bb0735dd039ce56dfac184e2629cce2b92c57c647b517373465815cf9eba3d39da5479a6605e7da3addb7ae7b94d0d4899e56839a7537f2bd4cd33f2b4e89e5efae5a9f85f8bc0af99fd5280bbd4cc7cf67f2bd580f5d2a841d02ac78add696483fcd25b0e49c0da7aecf4f750b869156035558c13713e875194f03b2aa27d769f1b5b4e01db950a8cb45a920fee81f3fb7aa7928e243ddd3c82a6db037e082436f0d18ab15626b235e4e895acab091e28d7090ccbfae69aeb07eb8288d45728281a82104bff44fa1d9dea9c19d782538e50602c37bccc1605c8b875e2923b80d2e03bcd25d14f8354ecacbe2d37248c06d24d8d1965c538d581b7ccbf57d9dc5466d491f28c1aa2ada54186b2bb7954b8ccda4fccb33bc9acc2f2e9a3fcc11b4ec6c74c638c15f3e8888f5bc09d662cf4c83b949536b014fc28d78570114d2002079b664efb517ccbff2486e30a90a6abe69b991b6cf8f4c8a5451f170c2eb3301c2b738a99d37a46c96b3e3d040d841f2244f9111911d740fe5968e5abb818b1b1030c38b34ffc84d1632b8879f3c8fe1876c7571e1c6c29e7084aedbb03b802ba08d3dd6c62d43fb6f5d9086f13dfeb95bc23432da3ff46c5c043d7f6defb60da93e00531082a936e400e2e48259370c0372c98d832a5edb69ae50703510aa437fff1020ad31684bfeb7d83d01d2e091bce3fdf7a48ea3bb3bf6a2941e5c7d3b75f26707f0a594ecd239126993cc21586d18cb29abf1dea4cc5706980a8dc88e26d581b15f841f868e8a5f5e2044f4a77ac472cf062adf32febd32a243bd03723fefb006430071d938de3cfdd93852478c3743ac9ae931d31f3e571cc7c9c7ebc029848c3b58406c111559a2045b55f12e66e17630c1d0905deb2644f8fd2524881fca73aa480e0496dbf2e5988969bc8be584ba46ead1bfc3bc9c1a13d090b9636c9bcd5ea5e58c914fd9b6f469f4e040641744c96e8ff437f9741841c5d22c102a753c07d98c9f0d20cf400051099715c2408847c3db07097a34913e907c9f0a903e50afa157b27821b36487b717a520da2846066bae74b957d8b900fe591bda9f78ac6a7ec7a2ab770428ba59b8f2199c7dcd0fd72bb8b39fb5da3f2cc7c2c15a7dd0e9128c86cdfab0c903cdf223b8441debf43e6b0586f16d1c40cb471b2fd12c72fe363cd576828c9502943dc9731592095f5e1866674da6c330cc33acbf5cd26440336a69af1910a9ec168f76be5b43a49aa6f251c46469a29128a4ae946d7a2d579204687d1ef3f708278013069e9934bc9bc9d9624700e209d8ea4783537ea645ad5ef429d7441b8b1fc88680c37b19d768f1d82c984764b80ba7b1b0737d2fcbabb833ec14444be627a638405d9bb1f52e19dfb510b2da41c76d30595ab7b5ac91f30b874f65c74a6b3dc20bf572e5e303bb9620455583e3f76dbd96e78671575c8fabe40f7cdacdf67b9f5d05088a193f5d9012bdb0a2c85b4f64c32375a1090417f5420ce356f905e1dc7d7a822e5495d33fcc4445b86aaffc5a57d1d1430c0a29d6e4d4a45428230d01740ef30af4568d18e7b1aef523a046117be301c1e79487d82342a1bfa5eb3a95c713cbc4c77e030a0c131a0164857827d6b61a31a21a61a91fa61d58c19b4fa30c98fd9f9a9f81c44190e713b1c0c5626e4833183678735c7e401b5ebe557bbd5dd9cdc7301b0aee4a3fd2af9ca922bb7e14a62118a550103369b0b01be59be49d5e905f349b57d314c83d45d11a6e51d84915da87e938f84c702b472f3a2df557f6f33d64480fe00b814d02890ffce5fe4dd2e8fadf328439bc344bbc2dda72abd4c1422574305c8b58d56837f92794ee60a054879d0edd07a180b8535170a49354697655ffc1c1f20c67fb007f9b23d007e4d8eee2a90dfdd81703ea36d97e312716c193530167bcddbe948b863a88174993cebad4a0576899c287a7dace2d1c0f564f4263f6418412396f4572dd454efecd1493de8217931110d67d28bfbba9b6447e756d0f519fb8a6cfdf979de277eab236696dd1dea5add6595a8d15b7aed73890fb657126751a0da58ef6360ba0f59e7eee751913ddda764b858422bde4e49029b0cca064c9482542def53ad57d18df7d580af48ef9713139ab75578e967c731910bf0f53339e9739912f84466d20e09e565a134b56af92e05912e8d630f4cf41a5d66bed07f2250ce8d848a62c0b79304393a6a2df1fe9541b512aafc62c8baff1332db3892b6ff9252b7deba6324600fe396fb41d8ddd94bebbc1f7df350c69c94ba9f06fba45599897e309b7a0255de5645cbd75ff5dbbfa9f55d79ee4468cfd1230cb0bac9538db961ef31ac1aa593b40a0895859a631476ff81ec215e3b1f2807099880e648c5948867aee7308cce53a3e562803bd94fce5b9e91577a3c27c417dde34a135213bbcdbb75a643c200a09d920b019217cdd0c422ecdb5a5acacfb0cbab08775a668e566266b58efc14207992dce94d6759151983a65763e3dd85dcaca6930fb46908a5ed784fd981c05251e69bb0079b7fb46aa4ab064fc7858c383522efcc0c37fbbf298ebb08bf55e68cb18fc7209b28a7fcd366bc8806c7ae45935e7e062d50c390d4bddb23bc9e7ac6b24d3b75d10f89690a6df92e4e13a659210df7e2ac2df70143fbd41ea2edf2d2c6c4dbdb87214a73636432826d249a762d0f4ab22d87577d9bd27ef33b6a42781dc94a443d63e8072c8bc9eb913b488c1680a7dfe4e6c8d6de2d069e19273a0259c71477fbd99755485cd29c84c6c1979bbe22df185867f2c96eb3f6a81d68a34b248fc7b7889e72ec0899c85beff39fb49620541549a0e416fa215e0acea22290071b380648e4175888af03835c433b3cc07ad53ed276f6cc1593e33a66492c284b1994e72f35d03420e04a9e8baa2cc03ddcd7fe84504194b6de95dfd228cd0c04bff2f6e5ade3458f36d2e435685866928b2121531f50cbf9119e4a21bf90462e9c67a285a73f47ae1201208718187adba0e5c26bdc9963024f9eb3636242b05e2f6eee4b4b699caa51133a20eced9bfeee7fda7139d0fcba469cde0ebee8ff7ecbb5191076aa4e2e786e8137f1014076c69b27eb8331ae33602f53e98796c58acdb8178a30c47f6b53af01cd7f37d8c88d337834bde95c37ee3c8bff382a32b3e03498f727bf4846372787e96e46200741619e769adf513758d8214be0dd102a6ace62f6c8cea87fcf54c46be0d0b4b156d3c6db96f1acfd383bd452419b154fd9ec193f1e6533f8997fcbfc6a58e77f8e2124dc2e86b330efbcf6429e2252dab45b2bcb18c0521229416ce36daf0906e7892fd72c94996ad1d317ff92f9fbb59e8af4fde269b28a9567af44e64b49a3a7c6490c46246534e3feecca9b4f668b532a968176aaa2cbf786a0fb83150f2f9cce480527f9e660d52eb3ad94d7cf589dbcfca8955e6848cf05b18eb904ec3839ed168b6d05e1923abec531aceccfd3d69ca7a1f54b57183d6d20e5e4c04da9e18a89db2c10b8b6b4b01c78d367c6670bbeff9ab4e743a9cfb6d7ff934a81a849c1b9ca946a00de093a060f6347bb2bcf029cfc984dbf8f440f93f6ffde78f2db2943698963eb60b35736e9527af0f46874275d1ff66de71746723cde8355f8f9615066a538d61a33ec77bf3f987c956a58ac87ef0083b6c08ea03f0d51cf7ca00723eb7dd135a4babfa837f22b3672610e2ef6ebe8708ea53a34795040c9426958f7cc85f1137c1873276533e558764fd8ff84f276a3a64785c36255f4b0ce5a52e10b0cd475a83edf39d3178e56f7e2dc81ba80c6bf85b648be236ca6cb63574a8e2251b466d5d30c5baf1428e8bb0990d29d67750b0a540ecdb2caabde02a705f927f0066309f0bbc73c84cecff43b9e32f1e4685b34236dfc1948ad505b310ec3d2e9720870dbf26215456a20ebe3e3a726d164506ca3f70aa056c8db5e7a2181de26c04887abc435ffffce2b82b4e56db50ac1f31cf60fb3bf0455664e120dda02fe2bd544d99aececa49280222f6ec64ba44ba9718b6d6db6e72daa93cfe31c1371275ec01a48fd2e2938f56ca0ee91e77662769721ea3500e266bff7961c7acb4712604b85484b0fe991b21b6f0f3ad3ac6d6b9e2870a4be8c93987f7871893ba4f634ce8c882c031e615ca16ca2a05de4d23eb9a22c7cb510dde6bfda69ff2d193d11ec18007ce11db31d3f82532b597a893b4ceb020fd9e924391f87066e8c2f7004bf53d67d10081b7b3a69d51c16abbd2d210ba2f8e96e95cffc79a53cb8335a93728896c90278b8a5de21a7ef2e5d2d8274d8547b556ebe366b111b3d9cca1b11ce6f5fd1a087005287dd2b9e4049f6d47752ae6942ea7a2b1a8b4972184c598b2b5568f52bbeacf8ac9e449855e1699b50ae4282861c65bb051913113cf89ae73e57725384ebb35354110091db0bc7ffe8ea7cf6146deb9fc2297bb19e5425ecf42dadb374d2c47f0818be83e9f23ac24917d01343da7c21ae2dd7c3c4c0c587cd51acf50e51c7c363f302044bfd390782b1ce897d54f3151934633b8c349cd01bbccaf73a45f9a1a056bd6fbd5192e6331ac19b2785fd913359770ffd7f55ddd7273c5dd30ddd1b862e57d6f80209f9db40ad09c10568fa19c42ab228cd9acaef2c722c14c9b9874e473117168bcc9d3dd69a80c338096223de786168fbdc0616f122fa3c69559c715c1e2480d538290f8adac88ce85b06fa07894eb5cd191a0d36ad93eee72d3a07e2370be9d7799771b1cbca4924d2729d96c70e4231950d608c3c0cf9afb447c9bb4045abd7eaf6ca2b3ff715e00ad9f556b144b7db6b7b1904b036c5788139a1d26caa9788f372c05eea02a926e1ae2af0468f15ff513a98325bff934944de2ffab698be1449e9056549cd21809c5a3d8f7ade364d14852b2579e2b2742fd248d381550dfa7a17e7a5754083c9bfa0d5022d209553ca6aeeb6615dab615ed1c38674cd40e5828a06777938ac99e4eee113f596b0163c87fa26af6a13f2566ded1c2a35ac759770615e9a8071bb750e84c2ba26c2e2624a69f468b096343b27311c2e9e942ca6161e7dc42daef543f54770c5085005a3e3b563a5f701582549f304261b2624c02c0940aa0edcdaf14f1190621b553ef1083b707ea5780a4eca5c3e37210c5e399845dfcc535d032fe355aa6ff1996cfd4dfdfdbcda59eb7034a80df0aca07d9a486c774ee24ed17b23be7e821db4931218974db57ce8137fc04316662461f9d8ed3f291dcd58996a13f60155ddff628faff24eee70e78b4568f574f58bd0158fe490d24d49a26f8150416a5666c193b94b532aa011506018fba98c9fe402cd5075c6cd0f96af032393804b2e41f10346acf2a226fef9102d0e6f690f3d5d52b5bd9a73bd836c57f4b41b5b958d79b95676519c7e5433ae6d081b624b05df565c7faaffaf94322677fabfe1cb4ffd7c417062943ce39611c8c1d4ba7f42bc19fc12b48f4c7fd58158154ce3e0d63ea197df82556edecfbffa2e1e1c582c5ae8a851f3c1ac456a54664addb5cd3578e98de011523dfdd801942d97a3362bf852f564f42b91800010efa6e8918d9d863a6879bda24babf6334641ff28603b2744648380a8aa46242800e1e7d46f51a4a3578b75b154c828ebe28e596370a5b933c06dd7697c5a3a15dcb12d80eb657542441f05b89250cf4c33b1c04598a381ce47b93fe7a7f6b0d33b2c898d259f28136e1f5649e38c3e2eaee1e313a9e561925077df0effd50d40077392114c08e84d1c8cbf4db18d7b1c1f3ef782de42de92442a00b22472d2c00ebd9b91509c2f5de8642504c0852f3dc5cef4c4ed32782c412e31a52f861f68f7fe639a78397739549c23dde71658f5bce112078a45c1c3c9b09f40c658e62778af7e98d69488a006eac929593887c5a1bac7cd309eb0734be314d1a531b5b063577fbe765f1a4982778f54e61b143c93a8f92efe5b7865abaf4abf82877c413add12b09f2330673c1975f6984085e4146a28a9d6fad88c70ce6ec55030dfd4e1e072fd7e78062bc9fd0864da4ae9aab887ee9a789090a7fd444dcb9001b3978ebead9aca8c742d148dbbb2a695ac53e1819eef35c73f6301981b9bca58624a617bb620d42e2bde89913214d1242083a4de095b346490c98d347e8a3fcc77641a21799cb8767447d15bacddec28c4e4f6f6b9c985f005f58933e5a16b71a61a4a2ec8f3381aeae4fd8040ebe7d23067d99c529b290687837f9f54b49f6f6abef1694f3285057df7f9617f8c09a838c293aeb891676214cb701b93b3e331e7cb9c24f8af27624c382655fec9b47146a1f5fb7dd9f590457e41f2cbb95eb93d82e1cce52ee54639e457e62c390fd837131ccd2fa35c67544eee9400aa3a46dcc52ecc3c1bce3d07961243f9e9c75a3a36cf797d67296bdc2ad129a75e4585225bfb51c828bbb8bee6fb6d945e049835a6da8e17e817e1a6fb728a5818b08c2081833a8a39bcd0e79f65bdbf35b1f93f45c4fbf66007021e734ffe7905c12d6c3b501c1cd86d2c8ab889d856a780714d9f54e356d1cf1d139b581264126df54367a6d2521111a4f5befec0465eadab6fbb2c7045f29d03d7afd7e775deabbb1e137ed63566802fd2f624ff08312ea4a18b823ca1a773db7aeeaa678e1e5622ffa877d4e9e1a9482662ad469cf18fe8d8aa80246067b374adc62df5cc3b5dff3f5022adfec2d91b8aa0af1b32209120fbf6c9b9ec7968dda16279a29c9c49a361d60593a8797807feac8913ec8f9ec15f2984282a75de96be4320f6b2e16045a00a6a6420f5e5df729ca09add4e57ac033416b3f8e79c785e6f3b137bb5c419fc8842e57c69f6ee3b3a0a5aef5f33750ee62f92a2486bc9fc39ea7406e62bb116ad1c443d763e3eaf87a599f7172e15e9f6586950e4923b3b55b207c5deb9f7b09c7326967a1f42e89a56cfbc584eeaf4f85a2e1a41093bc9e9bc82ff6f94f01ead19a6b5414f420cc05f5b637492bc6bf55dc28f9d16214ef444a0fc5aab9e8ca0d6da2d6f7a2cd1f4d9aa6df49ef2e8b0cc7f8fd66cc12b51942f5442a14a7a352a3b0401a433d1ef387d3782ab70fb2fffbf5f9f78f2d7a9f64ab87aadd4708c45ada53a56fe3e3035195760bb582ce15e59745184e37161b539abfeb792b9431a195a9508099832d3792358cf16a8296be745057413e39ad4a086a011fa8227366825d3edeef0dea627637676ce1c9cfb230b5fe976460ee5d27cee6e706662554d5e6359acb4be11ca4956f44eab5808255f8d44da22e7b8570ba0cd63fbbbfecb3c8460c32e287ba7a54d9838878580596cb90d0af1322e480644c1edbba3e3d2e0334ccc2d1127b6d93c2cf986054916da411eaab2e7ae6ff3a11cf725b834d017f9f18c6ffc2bd5484f40e7d7909b3b71efddb2ed1fafb199da4624dc4f76dfc4c7160625663b3e723e1feff503eaa0efc4f871c26db460a8dba64e199a20d1219cd76ff3f889e585a901b81b63c75fb7bfcbb4274b756d445423cc33a7c22441386491618f45751d6f87057f825941afc96a28782a2879d1c0e84c9453da485e3bc81cf5a101f0ddf4b057ef417631414d59447a2a4a634cb3c778296486108852aebd66ba1203992584d9b6c63a76383cd90213b75477c55599c88e97713b38c7dbb40865853e7e36a7bbf86b8bac7bc7ccf5becb80c6f5d45ec520cdc2ab7dd5135e200c4e57075dd9fe5db0a4f63d8a05ad7f7b26a5bfe181390e39e987c139a2ce859fb9d970f1e74ff3f26c5f043ef1bc1ed797187e7a71aa57dd906e2b2d6dcb981829da9bdb2a8a28b4de62b6ebb0cbec00ae4e27409dd693f9b278ba72d6e81c976181f596ee0850b71db45b8bf5d47904a1e2a29125eaed4e292f19ed44e9d12f3adbfa802d0457544ec8e4e06682fe691e69f572e3628670db72451ca11320e937f64f6eb241fe8a571d19ee64eaa6c8b0da864037dc2b6c4001773a1d161aded2e48b93b8738f241270cf43ec780c0e44244dd9270e6b698c002d78d16dcbfd0cefc2053b53ec042aaf4d800ffc50d8a753a9120e906503d94075938b72d0dc9d0b7d0b71b64ae2589e8424585de6ed30356928e852f631061ffe8264d366224a3cf9024a63116a485b8339df657d6e8d2638d6ae3fd5236f4bf59aa8f1ee23f3c76e333adabc1e2925835f0dce84c6246daede34cf7bf757c6a221109f332cf5576a442d821c0a506fd595a673bfbbbcbc8c6e72da986fcc5db911e65b74a32cb12233d486012d03c92ce9ffa347a19e6f96889eccd5a8d5662482bdc1ea01a42619681f836fc8c6a8c2de01fd816b667e912b601ea1ce63703b31d9e3e6db0541c1520e2527a0e05769f53720095b8076d6953c7bc14545d6e11418a1ea0c3cf0bda2a0170468cbb2ea402b8a19067180d8cb1a87d7bb246acda6b461466d5825bac8563b88498fd32eb434a7fa69e23bc3aecb647164a4cdf6836eca329ed88fe6e431aa6d8d795c7c4b22f622294a704fac47d3adf841f2491f1e16c1eb9c34d248f04b39af5d16b8072b8a9da14b9e5754260ee900ee2473306caaf97de1c5168b5122318343402f1a6a313abcef5efbb749b32e36bd93e7ecf5921a702b7ca685f9a3a6952be6effab346c39c1f2eb1df8b8e34f7e9740db2060e549d186cfcc0008b0a5aab8a74d45975976f9b1104eb1c32cff2448177edff9ba7550a09d535d7510c8bba9d496df6abd6c8f2a1c2b42997264672488a3a79077361136a8005d4c53c06750bf6d85a2b9420e9e286b70e430b73f2e69cca4f9cf3bda4f5ea4996249ef2a1ab8d4e0e2620c4fc95df6e6077065d24d47eaba2f7a3d928a7ea7bc85e770c2b1579241c1ee2dd4f4a79da8b0dd4f71ba4f47f508f3bfec747a26c8c1934d64fb458f130d4db460ef7c4d1db94f327208accfc2f6fa1c7110e9d6131b30627040cae9559803518da9d323a020590868ceb962d29b7d874c376217469f5dfa86af181218a5e341e206068885128faa292f10bd92c786703bff3d2ffe7e177cad2b207b44eb53004db9cdb670a9fa1015a2775755428b2f82c4ff0eff2cc4c4dc886ce853b4e9a23aea6c7bba3f745894b4d71b716174de127edc476bc67c83369ff72765725b25f40a8224e3f8cf4be56fa616e26441347a88ba5b1dd3aaff547ac3f1caecd1d30a6f71682551084ebe504466ebff31cafc67f6150baef802a99482fdfde794f06d2994ba9fce385b12c239d1b851ca6e88adbbc26bb163d114a5e20d047246c2e428cef4dfc3d2d2051ea145d50820afe9485af516acfe2a9d6951bf9a17d1d4edee27c5df9776c384903939903fbe37b02095e1c29022c785460bcf77c19d4d43afc3fc1379c460e16f245112e26df726a4fe1e286c9573119acb5ec1c5f4cc2d01f5d7c91a581e07a92a8634afad83874c5b39533ab7bda7ac4915eb95c04d3405639586593c4653bbbe77b21bb5c9225473257d481a2c4b2a0578aeaa2f65e408c10bf9de26f57e7b2e8a71a93305943b3a29947096c99ae4fb567dd59a602d6ed031602a294aad723d9ebb12e8a87baea901738b186760364784cbc81f15409bbb14f716dfad5d326ee1a12799705d3172254955480d8c8225f2d281a32d9359b6f515a2afd1a9747ba812344949d2bdfe1ab50168e443e2162c75031da197f8c7cf75b0fa2e79b5cfe2556e6cd114c4d4a16f6f2b975b6b2b6a43350d89f28d155b2dce97c24c560b432fd1e363fc69f830ad6d6efa8a0751b062adb79661e4865e719b5d2cfc5f44073779b763f3fc9c5903bd8775a07718c9c6fa4d5fe64c9e2342233caeb6e78346652575d3ab3e50d44b76a352f3f7ad82c7395a6ad0d845ccf9266dba4100e10f5e805beab153a0acab15ad5b1e51765c1186426a4e064e2df89789c822778f6e9d7d115538883750fac4657be3ea08a570a21cb61a055210cf4ddcbb36539c3175aaad93d2d4604113803c4b679dfa6ca14ee3a31b4d6e15a95bc539d563ce110903d53aca5ff4030628074700d14c07d02daf28ab510ccba9ad6f6b467e2a9eb2c0f94bffbc74309319d0986eafc9dd7b85788c0b95c708da185d49fc1bd3ca4bdbd75ff5838b4713182c8abf712f4b2c660c849160eb990f89cf0a6f6c789bd69954d834ebd9fa84461493c31b7a9d572be4e8f0009c63c175c91f97799694fc8a3ffbdde8fa8a7be953b8f875461c8a519c06d5116ac6989d4e0a90700546995e59248d17ec6ac1107e4586e9e75e9df765730a1dfdd5afbc01fab262a66e7df39d3977efc0d64606b9204dd6640f13f8386578db93836a1fc8747801d8cda430fe17be02d3e393609b724bc6c3602bcad79f3712e2ae0bf5c54de6b58bc5a42350beb35aa9fb216d1f629fceb57b5b4565e220370b834d8df39eb39909ded5a7d353f08946ad80cc4e7e3f5a4eaa272d5df8d43999bd27841bbe71b001856bbd7946777c3fc9a55f6e117d21cfe7e076bc09a92410772fea4f810f5a37ffb5462b90186496fc55c4a631984b1a4e5981e7060810b015e656ca5de87f5dc89495ff65e508faa16240d84e98e2c0dc1b55dbda831a012dbf7edbbe6033588dfbc2525a5052cbccdced5618c02d708ce9fe01bafb19f20389e33645f4b747e32e9c1d994e5ad35c4a5c804c407a6a8806ed9d8259c4f1a25a4d49f85b8512d35f814c5a8f61f1d141bb75b95d6174a3ef401c54efcbdbe9ee97263e45b6cb546fcf32db2069262352f882dfdc8f3fca580ec23a05561a9c9eef1ae773e297d58e0321fbbba39d43589474686be871308620900ae6778186201a64d826bf35dbb2549ea2d7b2ee3983f3144a1a62bf8719588471fb45132a619581611209c33f72237af149debcb48d33c534a1d2374a574e627a631162d1cd092bdac1fba38d17bff3f53c09a200c7f146942fe5ffe2f9de89e47d24969b3857f974e2a7cef70699e4cd76aaf0fdd80dd71187c9dc340ffdc997fe66cff6bf069166c8cfb2b43f923d90ecf968d881ea4cd2faaadb3616d935ab2348b2471346cad84de0d98f45cbab5cafd97983e66fd5d5faa68af49482d33a90ae4c35045c01afe7ad9a16ab6d3767d4defefd86a18798d59752869ebc10d356146c373926be6d690119df290ed75f8ac8060fcfe9eb9a8afca5b795f39e1a597642b1911c52ebc47a819e2a082d42967791248745c038f178f3dcfe6fb179d02c1a909763cd1408f5fc7933c3146659df0b7d42258a016df39f8f8cebe3939c73990d88bf706ba8ecdd8fb4dacf82a29cdddea610d6b46f11e77ffaf52d70d12cfdeee0b96f3f9b400248b0fa99a493576bb7dcae43a5b58dfa9fd4d68c52ba1703cefe805afe48332b14869c7e04dd48ce464d5abea120a0b0cf8138bbb1ddeeefb272ac5d2d5ae5dfd916d7e8956b7182ca148f33d3d64ea032f5ebd4b3d647bf77c48b72dcad677e350bacb7c2ce337b9e49647b8ec9c35c00c00414e9022f39a32a0b14741403e08e2acdd83bdf9966521e76d9ab9e233cd743a1692319d93d211539e1527feea23a12b3e957ac49a0952588d164daf35b97d6fb162f31a0ad23a1a92fcdfe2ebca824ceba5402b38f3eb180a85bf4667eaa884338f38189766f56e4630177963edd256a7a9f9c4787ae8eedf350b2ed0d4e932d8df2fbdaf3bdfe970e1fc4912890228334fbd716214ca702486dfb466d993bdc2f7af0252a92fabc4f4889e2ad10acd755c7efcc14618d81d870466dfb6bf8a01c44816a361ce9662f13dfe4043cb05b4ccadfaaaabf505ad29a3d269efdbc5d3155c00244e7e5578f59f9ae13fb4bec06ae5e093700a697f27ddb7d16a8616ac7fecbfc82ccec7969d075143f16739bf056977e45a8cdea03f5ec95de4ca03fb64605d72b59cfe77a81f3c34b7b81fbedbcbc09b5c9333c28393ddcc746ca032ad4e6ad1ad1a9827f4d5d3ab10b4e9a8006595c25bfff7de7fec3c85b54138d6ea664bed96ff8c7973395fcff3d638ae07f301754371d3ac34b134d57e38948fece992fa6955d35fdbab5e0e431e95e156bcf26066221f76cae789cb3c53a1ece817403663e49f69ce5e3a8e58b407d033bb9d266db68a1c2e66884681c883d16a299737549126f301342cd877bbd8544737295e6b5b4fa44cb1af6aee70e00c01872ad30296a099d6b9d4acf81bcc6dfc25563b8cfb02da436419e3247f60733108806ffb948de322e0dc4fcc44acc15f77bc685530b4e1dfd5d7a71527a35eb57eb930aeae670fcca6de6fd50b7acba754c465b1d10631a97918cc347c967d4cdef29c905213d884569a335bbe09362f8165221e2685c069134ad0811c0e5d4d1078db91f0268d31aec8b799515ef6088ad051dfbdca7969bf6ffd9095d401184c05b165a1da50f4daca56c7f9af9b2e918b79f35c13352b8600dd27a741d2ceca00ff8073a87e1d0f08683ace13acd5e0f36a519cc8c3dc661a0eb381589a56f16338f20c6af101cc575bc083827be4242cee33ef83c784f2ac8cac9798acb0134411747616f67acf8434d392bbea364f6c4e9a5cfcd515e166efaea9aa690c657c3b42d4d323325459444a95701cea52ea227ecfe9af004533d976b9cd2318e0a9851ab2ad8ffad8bdb7f9ba502b8bcb8278aaee88026abd368dd3996fb60839234827679421aacc8155a527788774a74239ededb2848254e7054e4440a45be0807fdfc181c4ebed98f1cc698859b562c29f770ab3a7f66e520c85e4da4f02284c3c94212809424a110f1bb507d79909b47edb1b00df752cc8a8f97b27b39288c16308923e5dcf13a5082b0599b1e5c7360cdf5a3d6fd77d143d2e4281711d1ced37d5098e5dbe43e2bacf69fd73aabfea961864cb5553d63ba2bb868f8c26d895bcb83600463ad204ae04ff083e804d9459bf6a3529b79330e02fe5bfbfd40946b1ed2fc5d129b228ba82ebac3ef0d9fb1cfee698a7693934d8af90533a97f349d8d9ac9b44a36946c3cfcf5d5567ac7b76350651a92f43426c2357f9f71d9f512add4e5ca13c4dccce19f328f2a5f08b0ec105606317910f3a5c59f2776101c2fb08e1c555e778f14bb5b4b254418e874381394e19f19b1b5f625ff62fbc222cef43b14d654e09c6ca2f18a63c329b6b720f87e702e2c2d99e02278c65f5bb4dafd297602c0eb278569915d798de611cebc7fdbe648a3ab33cc98528b23f59e1c5f40d9d3d01a9901821d998324d164367922c10ac7a732bf33403637ea35094dad076d74bad04fb560ff2e131cf69dfb9e8ef729298ffaa47b6998ffea7e88c8003ab078393d12c9e04e307398517396290840645da8307b834224d3787c8ee12266743942dfb57b4f4d6507b5bdfc1c6451983db2ed219f465ce7d18d792f1391dffd53e941a1315d7b8f439b4bff874d6bdf099e3434faad930392b886a95c9898c1d8256d27bef05c437572c5a1cba3615f8b7b20c2f265a24391b8e9623d9cadcf60157af66b27393a5e413bff391ca2e64b22440e268c8e2548ceab5867fd0f8edec3bdf7c4f7ab3087e797d813f08ba8a6fd14eff61f81ddb65a5f8f91086afb9af553bd1a944773e52212eec1ba5fd5f8cb5698ed4202479409f838f7072ca89d7649db30da8497c69155978c3b0090b4277b9f8e05871cb2acefe90d70211b26f5e3b6a49b1900320932165773bd0161633adcf0fab5bd4bdb7e7bec5c2d8768bf152a91613bad7bbfe40db07cd82da0caebe455eefffd1ffdfed41550c8d4de4a7204937214c25175bf73be8b6066d783fae81f3fd5e28e6636081755f88ee80e356e7a4f36c07facbd2a244696fffdd5130033016a449f2612972347abd0c961fa4c516e7dddaf88580b0b6c9d2c2fad7039e19ea89147aa233fad4495bea2f8d39f75d2ecbf0cca8db2324d9b1d4f59de89dea16548b0342c73829ec36ed2c5d7524c98172e87a86df3b840bff53881431bdfc3dba0cfe13dedb788135ea206565ea28b0afcc4d102368500ea970bc530246db8472dda4209f413116936001a0b08f257ce0e33ff8688431696bd76dfb70d3ec90f9ef9d060d42694fd74c7569022a1e20e8b71e5682475c35a162be3b803f2aeb833ae19420563cf7df37f9ad359742d7c540f1d901cdc55815e30fe206e4d9c88ab5caa34ed1b59e948989cb216faabf752cd5df85d161fc66e785d8955515e51da60a52a943ed3a10d4c1f471969eec1d70749bbee1ce44678d9b8b4f397f0ddc95b792098d2b05ddfa124f4d5878080a2c0a12808300a128c82124641b376757606c8c6238920faf3da904559372a37933e037ad75eb5a83bc422e295e507fa4c27e4e7d5ff77423b822d071015c26fbc9944750fbc5270ff2bb5b02774afbadd1b98bedce601800650385992cf9bc98fd67d8e7d05d8ec805c902896d86c1d5afa2e2b8dad3770ba3ff284657490343d6b26d9bef4f0e7b15e5823f13e9c03bc8cbeae1c5084942fe3a7ad106c637ec2e1ae27ad21732c3fe6aa898cec4d50b809c8b94ae52e6e94d6a5af526e615b47e1fd27943295163fcb97188cdf706451306feae0290bd70a54a52f5012289634d8d291685b34ed07004af89070853226bf517037da32951ff7dd28d38a4ad660658733942d2baa91923180ab443218a5c52ae56298f857278839cfd2458c85979086608c255d6b526cfa862c4b4d46a57b70462a0824b24ebaa44c438ce679520b8970ee04b9a61e9a19020f24e48277643036759b775bc714c1ace7a32cd69630a2b84f56fe4b62deddcb59f371a15ed3fdc4543329f35d775f1ce7384358b161714a8495a6aea891d6a72db46889940d59a931af2111d0a5aa42f5e9aa5bbee79e0a0c1c1016481fc40bb9f226b7fbe4a6507a31fdeaad9f05717051b76c1400e2b19d7b4a1f96f9f14fd0cc7ac81c710f821312fd796669d1f630c012c25c073ff8d619d4b6c38b6516fd050fce29d266405fb2d81491b1a641f9a881a9011f58efb9de022bfd4e7b3bbcb936ccbcd63f580c10998dcec25592fe535d9d5d25038d65c78ba83d848f85942fd9ca4010434c3ed02418c885372bf3e776ae5fa984c08cfb2262bd5904c427eca414336de3fb83cbcfbebd4ca94c395d65ecd6f1fd193bbe40be824fd9aa9fc3acb9589cf071e14c671115ce171acea17e854b1477a0fb4252bde21eb4988225c6e93146e5f35c7181c16c11ef7dd529f6296527da57b5507d542f27074c56480dee2d35d8be9656ff993940d300c2f748cfabcc8768795ae95d1d661ef15f218dc936f4e723dd42d1dec5a8ba2df4fe007dde622b776ea9042e082eb330fc1523c9b8d7975f88b2780207268625fe4c61aad845c0b21d93c17cb5a2e0633180bd16fcee1100fc35e7fbaac1f3283e5a6c9b6cdfcd173ae55b4b3b6bc91fc7f989887f80bf028177640240457ec1a05e96bfb3aa55292e7f2b0dfa4f22b90469b6cbaf0624965e702d730da99fbbfcc48b8825438c5c8357c6c478d90788f13df4e099ffd3edd0b31c41c47dd61ca6f4a086f790ddf49c09275b46ad8aa46d044088f70737d2317af2fa5a1f1bde57c0138acea102d210f689547ee6c48e12f3f67ac75d02cce72159459141e1b2dd4cf0aa799b1c42664094f2c846fd864ac1fbd91592c1977fa5e2a07652461aa3c9ec0c5bab65fae8a499fa00250be17aba31fca834856631937bad6bd364eb5ac914764b7be69a2c6afd545fab7b12895d01ec00d6cb38974213c738cd8e8564b72e7ed74282a12a4204f0b5a8a9084e61b1e0de3227de8702cc221e3d168ab12f9cb29f93a1272ce6ce026841c4193f80629b7ea20ea7ae1daaed47c42a2b7387347a949c9e727cd6f8270ecce48d919847974d25e23074bd8e5602a21cf96f9410a67a3eafa6bd276b1a4bd4230853e00dbb5b710b852dfb9718e40749a3867fade40e94e37477481b8c75b52b35addbf8b62e82dcdbf3194002e31b072611edc6ba4fd46990731f3d3b41849cf8f78db6984154633a2b141f30134087f86c1666de8ff039c1f56a3319342d5c07d6581264727a4aa501c4f3e29b6d83e0403955a97259e24ddbc05e4f0234fe9d1477dfa025297d951f931220d59e457700014dedb0e97c0dfe3f7022953f8dcefec93524cdaeb05b1813fe5cb08e37252a9af196352f332de6b56b971dd95a152cd8b1d68c6ca633f78a331eed3d4dcf945b300f361d818dcf6e62b9391b3be7e353ae115f1bc217ab3ea0bb6db1a4d3b5f4164a82bd6a86e12941e457832ce1cff4d393d243116be28c4527086701d2802270d09bfd135f96fa7089e2a8dd91ef6f7c7bda5cdee0744094d2b4284e9b963d2b589c55d5c6f3aa8beb061c8eb12665cb48c2636c1ba6e7bf15a2320511b17757038a9c3d5ca6ecbd8ab50d50b9a786021ffeff411f255704a26f196853a65a57b2acf1d52e1f8e045b4bd7ad41747f5f8b7efb4aaa6c56f6c13a8bbc044c633d6e4c291f7f9a681fe634b481bba2fe2f39dbb8ba226165dd725991cf0bb48b3fec6c4049c11e061abf456465c5f0b739a0956ecdb5a560878862372abfda942bafd566924adfee74b8036a9313c153e463b2350c5f5f3d4d035cd70d9c89449ad5de00fb980d0765432fe82e0a5aea3f3d6e1c382a459568c2a0c71934729cec67643e04dee18aa0ad079eade84eac3e6dd4a337593310a292117a053b8e11c24c1d4bab279c816502492ab1ea08d202cce4c4aeb5babb1322c6eb4480bc03bf259c61703baa61df29d06181380d530496d10932bd422ace8c62e2fcd0e45891d33bb850000c5158195f159800cbbe1db1986df7305d44c116ad8a8ee325ac32896f7858283aa00311fb348f2c2b51eb5b1f1a727db968539b7922e8db380709cdc5f95134e30531d886fb064a4b785a90356e597754539f366a62ac130cb0a6675e73bdd97d922675613ae125ba57869e1619e0331d7e134008f0217849c77df0ef80aa1d3d941c20689b68fac7df136f0bd15129da39ea84e2ebab4d6e385a2a0a19a15321dd7fff2fab7a9e36295eaff5d7e54022ebfbf791f2dc5b34af9ddb4db98f992a3579c19e594770d2d0e3f79f3f2122723392549e3b850fab5bd05dc639d1013c04c82c8a61a7ad0e1bce3a54b449fb65cfd7c5a2be9debb46a75c6fc8f91deb467f64efbb9bdb767871e60386ee93b7ef5015ad3f9596c8cdb13ccc841a12fb35bc81f8577d730386c9f9b9069fd5749574de280150ceaf8a7e5d01f26d0629e308c99c348887bd5219c26e6276254bdc031a1537c5e3c5d624bc722e1c37d9cc00d0cec82022e1e368455a1d800cc15dc31570bc582e0e55c0771fc502c87caca26140c7c0116aca2afd2f66c0463bf936c22307f9d492df5c58401d7d6bd8770b7374f35a8e0d1fe34103af2d1f19eefa45bc3ad6c71455a2ebbb77236147f8027af75ddd4af9385645e339d8f3fed9bee538d2587ffc65cee9aec8984bef2f37b03a0c0feeda84713e3f8c41510e15101787b4a3aa186f0800931cb013934a0993abbd0c2988403a4c5de0f0f5b4970ced479c5f7e8af2c3fd0456a7740fe2b2d2f45dea27d354eed0f528b94e65870d3f1c12ac62bfa623ccfb9f56560b0f05f57be307e4ca98830ddace0acd29e500f172125479337e4a1a6f4801c020c03990e82c66f68d285be79d8a9fb843a2a34cb5bdaf0d71c5cc3fd6ad88a964ff4152b859eaf4a77be31f8f75c854fc59bfa824c6a8c17690b7f745303c74343fbbd51037d0bc83d56d1650a6e4ffba57919545dcd7e1c8f5fc5d5333a2894af01592d47d0bf7cff4986455fbc5d70cb761fbc2ece0cabf6ffd89de452f3280f94f0fdfa6f7fb5e8034f271ab26af3f8a7c1e759d56dc2a518575491f12e42a18b7288b4e71fd1e3d9e1a377a8b23f584585e958f8adef156732f14ce56cd16095fd4a468932ffbabf2443efffc8020a73ef613145fe2935e3b79708b06bad2dd0df12fa56ae80363e8ba278b68a3d59ecbc567ad609ad453334efa2117dcc5efa3e1faf6df9693834fff481461dd544469fabec99613fb07231e3b9139ba0485fa6be454c945ae06f36aad6c12f663b617df4bf0049ff3cf96ffa4c44fba6169a90b3ea26e871503b28c8acb675ddbd477905b0d561c8190424ff6990f5fe0e13ad9443a345391b25fdf99db24c29c0e02699882cdb44fb1cef0d8115bc524f1e0806266925d3ead5ca97fe53b20d22408fba8ab7b43da4d63a902e124a57befb2424026226a64c660f35f2ccde9aff7415c578e49e74e380d1afccaa2945064133935977c2d60368f2948468ba0bd0e8279e5e9b9133f7abd46d0f2b886c25b73b4c3b2878b2942f56800af50461b27d669a5ed9ddfbc5019da27a70b64e2d8311d5997424f8f92a442cfbcac4de15cd96c9767b25bafd746143fe89372419c73ba40e24ec28853b0c7bb5214d77f84d6608e6e1cfe46164fe743e7281c749bc49efe61136533f04f87304e4b8749dd4c87efed8c60e6a14d7c942c7292324018e6a1cb139cc61d66f0d5e78a92c5dc06d4ac05528107118a886e4ce2475335daed2d683ed1d1688df1a7666ec13b901b29fcb98fa95ca296f4974ca81d4818483a014a285e132f2a60d6fd8345a1552fa18146f3834e1e404177c1965448198363f791a03fe8dee8f381e6fe216827028f888b1bc03d75491a529d5d8baedf234584b8befc465d807a2b9bfb0dec3665de5a1dfb47ad152d180c91acf9d2c1619c8efddbd2d055c24e12226be01bf1519436112c2365c1fc2429a89d3e4a21fececf0b4c73cc87958978dac645c7194d6833ba1463c5127afbfa8912de7ff2e73783bb7f901a20e8985a94f88afdb16bcc64f757359048355ed7f76006c6d5c3dbc284ccb9d44b36d447175fbd9f59e5bdf43499b8425ac878760cdea8c4ccd3f3e7d8ea542fd99f79bde2e328725f878809477617e39961b7561f4e563be7770c9ae3120f5220800b8106127e0461178e52abdae9cc0eedb7c17d104ba87160a5cc1afda1a70db7500da08599ecc3072807677bb56bc70228c77d140a0be24958caacc9a044b2094c53ccd2a0d0b53c754ccd28230b1413009599bb2a911b5024b5fafd99b29addb7c37087767c6298254be1e648cade0933e953b9b645d5548e654f0ba5949ba29a007ee9ff014fb268da5185a285176a46ecabd2e32dab7b4237d723a3008f1a3404acf72927dc7034e32a2ce1e9ea093de015120844584964a49729f75403b3326ba62f7493bdb3187e59fec13696894b7fe02a0a0b19cd6ce5a6ce59b99758ecac5627d2b5ee20dedb8af15d87626eab03e5d4627d8ece247aa8b698b8ac439380c7b4bdb62c9fef1e8e05f8da93cc59ac891bdb291d76fe6ef504a0d6c85d434ad81481e070c69e7ac49af7da96b4078700308b4342c547a5d1c5d7fa9687421523b3b74fea6781381c749a4b2fca89478a83541aa8129a33ae5e6cb4ad024a9dfe85a3684d1e037422f597a9c274766958ffc0d4f6458a3b92775f416b0b9b297a57c32e69b0cc8a606850a8d105f0c3c6dea3695d4df08ba5930a7b06f77877f9b4e0bfa6a1654d6a2976aa764e8adebc4fb393f60d98867b68a96be1f25a7d47215ab311a61412ccd4a6799d3d0e944d584aef59d115121aeb977810c54b0f475ca1400ff57a94c65ace234a55b10002b5b96e8942ff4e85e44733592d00e25e9a795409a9468bf00814014884ef7e775d641ca1a61bd0ead808a983df784cd47360e8966a9885fe47973d8ca20da7f0fadd2c1579b4def0ae4b17e55c9ba7bd2d79ebb6b7e8515eeea5d8da8210f0e10291d928a830df56d7563943ba3f9f322cc42d219163f142562f0e604a2413a32e199a4cb0f2dabeaa6abb284da9fc6d6cf6f725b835d647fb564b7b45c02e0b8aeefa79651b338096a2354d7109e42518d289d83a7836df997aaed9c4ddf4fadcca049454200e62d4af9c403309040e352098788bbcd660a3b270a2207dfd63536b22195e3c60fa343ba7b48ab80f2d1d66195ef168f64f6f3a9e0ef479852117c2182876a1b967e4a973cebaf7fe795770b75a44414d7a1dff80b934af8361d819cbbff185964fb4734e180dcce0b5ad0de99358ac184c8cf558099824a21cbc67c9552e079323a6b8db7e97638969f3165eb2611fa163fe4351e25eeff12d0b59bf82037bc5bc830445b874fb3ae4584ec2080272d996cfedf5a247e9fd4091293256433331ebc802e1609d1f8ff7925f678e3afc713e3f189f7f3ad4a2f5feba23d06046404ab98185cadc4b9bf3d3f1034f22a5637b2daca67b485a0981fa82683af061277f6c2df0ca5bb77195ae145f37a97f511954a73c01606756c0c8aed5d3f9c973fbb16a6b00f9e5b080000123a7e387afbc403e987f00e1d18ec18e11cad3bb50085c0dd36ffa62ef7904f26a338e35227645e291eae5e10a01fd5e0d5bb7ddb3a3d47342d958c9f4625618b2a04f70246b87da1289318126e254c1806edf3fa957b8b7eae9e6c08363fd8920a7d19daf016487aad398634012648cf603641faf7a3dd80059a1327efd360c9b8ad6f01720afa4d091bbd38949dbcdb51779da0db45e130c61a9c34797620f103a32659efbe018792548809ab9a52200fcdf023047ea71432cd6095e16b4e5de59aacf2ba9360e6035fa6947c37c81c0882bd0923d8aab34820e9790fa58e5a46286fd4771b7673287d4181a0f5e83f044b6fcda7b1aac466fb9a9f4d6a8ca622b35d2d3053f288f984529e419f0778fc2d9669138b25c00fff987751056cef3a9ab56682ab758a0ce31d121b9b868c0527c5c3544ca7c2fedb21878b8ba2c8bd2464d63758cd6dc99713f4290a5dc7ac8d58a23e718ad6bd7d4b27981eb4af665740481e78e2fd47460c87748b7762eb67f313cfa672d93fb5f6f6fe3ab45ada0918d987b9a843dce372048c1549f9dcf4f8cb9aec292c0211f06e6ecd8ea1a5e13e56892aaae382fadcc84dbee977553457544181b55e3f9e38f1d069f61bbfa80b84f5116a581a8a11b3e905ebc11f53bce42dde0ddcfe8ff2107f0658827ccf3a81d05bff0b6e53dbec88faad2d3d6e2e2050424b2725c5c408b41dc93362c50a62927edf1ec0becd3894752d4fe12bdea552222a499c4b5a134c85ea443d714632902ae6c6b26c24f364fe8df2a46cf3748d435fd3465ded1b13cb58fc6d4426c7ff1e4d8b98ab7d82a9a4588c07b28be82e98a55be38a126e287dcf752fc0c96f5fa39325e8adb7c4067a61f161a9a813ed643066f0dd2fa45b7890fa7f11d1643d1fcf17f4a22bf3e9fffa761ba78921bfd14ff0b0e209e247a7add8b06a9d2afe78ea0395ecacedf146ec66f210a64adaf8fdd0c590aea5fabd2f9c32b3554bffceed28c13a13f233dab8885108eabcd830aabd2c9895daacfa3219d7882d880e3ff23397dd69a93cb292c230ba3e2fb7ed74ef2d7d3cfbfb61dfeeed182427de3ebd08de0cf6a4bd40ecd6f44ecfdb2c55198894835369263d64868d4c4fcecc0c5a46b460edc61b2ebb6484ccc74229802c44eb99f6c0b8ef7c00a341f58e0505deb02cac7b7a857ddf5fc3edc494d396cee92a3665490005aa811ed4e19e46ba2e933e6c09f32d1f0b281da2ced36a5f6ebaabeb8f174197e2e505672f0f957caa5d5c170ee94b4ef56dd752cd4ea44e8f32047da7c41ee4ef0e8e0131fb2be9f9ce2fa3b0bb4f3eec24d5add65ece58907f03f94e57674186703e21c49b1c6826800e0908d3df08082f21ad1f6b43d051d44817244367a648b689d174b87b50fffc0171fb44b811cae2120ec216831a46e266938629ad9789f01879acdd38be3eec97e197ccf658d253ed2f6984070b4a7242481a563d5858883c735103d59e0d80878949b27e6c20c033042a570f42dcfae674a08da366e6705dfe4afd194301f840d3a79469887a25a1d316b1ea8f5a1801872e504a9ed8878eae322833f5f10364a089229a2ffcc0b1329f27e3534239afba43c0bd314964d79e247c13a74ccb0054fbaee912593f8330680f9611dd404b00511e8fb7d1e745326a2f303946d544e9ebc502d0723dd25cfacb47b3a88150f4fb7220750a048ae6df5b29d06bca6078e6851b11b13c48e1df527b28e36ea1b38c325990c99b80560cae71eebe023576ab1b0bf13e89f52a6794df050211c0216091b05fdb8da3eb356c69c6be66d2066654c3d2ed56a465a3aab98fdfab335082a549ea5ac754a6f1cf3f01cb6aeb61f48960147f085e21950dc0e3c291c4fa9a9ca80dc61144d968eb082eda2aa37aa5c825503d11999aa85e543595a7582a0dbaff8295b6108d6858e89f3b1d884b76de316c523ff2fa3d0cbe90bd085048e25a9218c2b846d97af9974f023d675888ada69d275ce5bbc920be38d5cad5112b229f5901fa68bc620cfaa32dc9fad3ced59d09a71dd5691bca5c7986509c1ee7860b407d3b422d2e59fe3360568295687bd95a1a0a1781068390ee0285f3f5a76b6e9ba309c3dbc619afcd29fe7210d4d05baca9b86dbce16e53d51ed4f64245375eafb5345f812d7bed3ce5f5949c77d05ca09112d2649e2f79740b92978f497ba4f0a47ae63f55090c187fe06ce7260aa13849340c376c00ba0a45aae9b6c8ce6627d323cdec5875833150709b2c4ebde1323df237c210a108eb14f72200691ab70c4f322a3db06b93cf7db4b4edd548c0677219e7dc1af43d145ce234b9f1771443abc026e643fc123bade057c70f3b4bdc8e85166034da0f1042551719cfae10bfa0acee65e05c06c255b5a36b8a47c8df13c33c36d3d137a02697062a244363270d38b7cdbcd34e723512fe30e4a304bf96fe7faf8bbc414ecac586ef5f493211b1910a95c5dcb487f03854a0066478add1357c6372fc7b8dd305758b023253c4e70c5d2e62741f7c9fdc8b45be0bcbe22b120e876349cb2c631ff60dcf84c5ead84fd3d81f02acd857d02b07fcbd477241660b1048f98c9ff2e6a1b98edaab84cb4b5e826d2b6b666e56e5ba7afe5c732136d8f6000d386e3f399434b55934416f209ae182aa365115a4ea0c505eea50e6d8340c913a112801b4019b6ec2954f4398306588ed50082eddf44a5a0c72a8b4d4e69f04861ae7c3ca28d5c669000e103a3b3f6adca8c1294f554c7974ccb866503f514598f2db1ccb6496ad39480eb9629d007f1c803578088721c39d4ed5298692f236fcb1390375fad497dcda64db9213ddd1592cf00cbee8f171559c713f4a1bdb4f410e2cc693b29e285d8550fa804aa335363fe9cb51aeb2af6a20ba8576a906b61039b44450ea9ba6640f61734167ae8270bc9cbbaa5ef1b6005caa4a2718c910b241a314863880bd2b0ae12900645b5779e01e535009ec0ea20fe3b3f40109ae4bb88add2c25caca72f77af133517964fa409be443622f4f3b4b5f86a423d5301dffc1370655fe90297eb9c87fb587ee52020ebd896a8df53c33a53c73e59128a4afa279df8035a1e7b80caefadfbe302edfb8a72c58c8864f0c3a72bf970df4431a18a7cc9fce8d5d0662b59dd7cc142aad1df829c0fa3828eacf5681d5d0e673229daf023c1d5d0a9e58f936d68dea789e7515bd958789fa357dcf97b7884da7a85bfc77d776adde5e8f1681487c704fe1966cb9cdc32218eb018d58d632643b9d61aa729a637361d05d2543416a67b1a9801f2634a5e3f52b0a3c57c9571ca48eb386cf03178163574d9c483916ee5dd82686908137ae3517ca5706c22d8839872b6f4b7933a32a6be32e70ce2232b4916ce2a002ed8a1e459e0a3485249f6c831e0c388edfd8c8c72119184716a31eb9ad2a6be99d9e2b087af3b1f7b8802e180e2a64b58ad86389af1a2b5b458804f605a426a9a769ef74fda93e2e3bc522491820f3d026ca0b99e1a32116587ac80072d33a8985f2410e4c77846159295514219d98c9bc24700f4f87e12d86ee286521827d8960e91fe245c86570d8957f7898b6356c1ab29eee7fe5af392f1d081cf72f2ce439367cafe62870d21718399a8b4e70f29a0ce51bc946d49a30abb52665784f0ec5d35f8afcdad7c80cbf7fc0ba5d8286d805850451753bd48cc23269b51d31ff68465869ba759c458dec09dadd324507dde0ca769ccc30e24c0104b25c864765ad41aeffd5107aefe4b34df64131ee49ef78b15ba4fbb0e0959c30ff32466bbfa3d435f0073da253cbd6b5a0edfe0dc461a5e2741f174b6f3ae4387426ef2f1afa22c6b95cf1292654e0dcef19075b1e541a0c16d6a5bd85be82be64e2201ad682a9d852a68737422fe8a2fbfe5558b69c0164259c918b8c4a935d8dc32aaf9df0db5442a7c1b7099091ea3914fb2a0f2316c2552bcec5a36b659318c5aceab826156f67f5b9945c1255761259041a362079d4ec465fcfc0c7297955b0029c106f16628056dfaa5ad0f2ee3a567aaaf9df8c1a62a3597836aedaf5262fa22d5778c5bfb5f97cdc1a255b1f8bf2bcb81b41364f513037850c261e76eaa7b13db47bec99862c13552b17545449cd0e10ce864c3064c1db1e699d71e5b120ae62eff8fa16bc473500d4b83efa933e5f1dd671ee47235faa57308480482010095cce906ba3c7fa6c6c9f0379d7fbea3775538a382213441d03b2a2f446cda517819ceddd7e3f8c59c7d8ac040b93021712261a3475a5ba76906ab8fa74284bb2131931540c53369ccd2151e45a8eb4fc7ba53cca33a0f665ac34bcaeefab5064f00aa0af8dc25a0f73043e2d4bb001a454a30168b987b58d9471e3692ad762fe2cc5ccd0b01eda232fb45f35107aa61388d88b6e1e2053be5fc00654c142ac75e91a5f011cd40d612490c0058ec772d05eaee380f89186aeab24b51fcb7b20a9751b70aa32edfec9fc132805380e0a2bc2d91e22793b767668e9eb38b89477d2338f0aa439c8ad6746e6a59bfab01ca5367385553dc195f981438960ac5e113227b3475a59bd8bb1c81cd6b7989a7b67fd3a24ed77540b24d4b1d3127a860742eeed749d7bf4e559b323746797caeca35d12bd47b9cacbcbeea92a5e2aa28876e2bec37ab14608c46d33cc64c11f90308ae843e36d69fb2e285e1b16647afbae069b56cbf3bd838e8ed21d3034133471372e46036d0b43bfcae8a2c7f9e369bc33e08263f32519091a59fe1ee297959ae47c7d8a3f56d91fea30ad9003bdddd9c8159de8a8d5fcaa0f550b8a208bb8f3e9c68a80b358d92346102d1fce9ec51ba026ef492113b3c1fe56e970524f54bb7203006a99c70cf0f1147e3932a5ca93b77f319b4a419303e5b96b315d9824fe83e7d947977bf56fd9cead457aeacff8f654a1843f635780641a28cec3016f442561f8fcd7ea7409d799282d29d27cf5b9490295a29475fb8f0345600ea34f781cbe8ee060e2699a392ddef23c7c76f40bcaf99a7bde4d585dc8ba267d7b7a75556d21b053577516b79a35ad1fe42cadb617bc82fa7f2be655ff1494851a1977af29789f9eeecc25a9345489e2c7fe3b18d4b1a53e03927454130078772233641080fd381200716562329298003b12254528f0fb0d020a06e219787e58548f6e218f39527b0c94cd7ae2f0932f5097abdc186e5c6960366b060d0b0b891d0d6d4f1274d7e3f9b9ebe069ffa5f62c782c725a25c9c32d2ed277766df781a69db7cb1f2b2fc7b9533c4bcecf823e535a470746be71df10cf78749cd8c7a2c99b51fe954d0b56d9b096f17a9e902753d73ab7b7732a4fb16159db585fe4d8ed67d5a13cc8f753e2545bced8b5f4673609086efbd3d6006a13bdbbe655fcd3e8e1c6b8e776917a98b3f2fa9cce9439da2187cbe5a76482b81246e9b020a7da5c951aac5ddbf3043ffd463f8a15cd74db154e61254a899435ee8c4212ec3fe6b69f3b9f6fba7581462ca3c6182439bcd2711bca49a62ea2b34f531a25f2915c7036b4ba18b859072056142e0b4e5968d16c23399f261153cdfdcbc122adca86d6beed2f13681d9bdeb5473580d1f9e44b42faff13da27f4b6d3bf9f301e2499db16f6249ded5c6f7f3e376f912e053f40c5d56586e0c557aff213f838a790410a4977b4f265590ab85434b56346f1416003d3156305d30a7891ce97e8a5c6b93fc8e4594e23bb236e8f79f21f65e744097f465ab5ad3cef7aec8d33814b5192df5668d93fafdf0cef714eaa2be821c6a97f721bfd9aaf22acb15357b8a19549fac444ce054a9b16b00cfd0d655bdbe6f138edc9363eb232cbf5a25d523eae27a39303ddf9c1cce4f8baee6f10a00b99097842a2d84459cb0314adf13f4f901d9263a384a4189124094ab6471d0dc49fb6df28cff83a97d302ea8c10f8aa65119cb7e9156f94c08ff2fca00ad537be7eef10c10a947ee50df43cc0c68feb0ce574a310f6c4e4388b0551934ee568698eeae3d0ef1d6ce9023f39602f9218817fe861da51b8961dff81d780bff25c773ff62d97597e028571facbae52cb33069f97d6ab0476a52e702ae2f78ea48258aa8b9c6d3b96de563ed86e2d141e3c44fbc27cd2860101cb8863f9ba1f021cd04550b2c061f3fd1d8a1857de68692faf0cd0781ed256f4bbfead9123faf247f042686bc5980ecf61352085cb6dfc9e583ecf780ed46cd6e278c9123754f9878f4bb2c120dcb9fbf18579eeb3e40fb37d20aa49f400039733a863e974b7f7e13fbe596626d2be0b05a73c764784934dbe60307ca07258b19bf5a0c36f7a6b44cd0bcb0b044ffbfe57160008a109393834de6848e46cce761060020a061ae9dac8ac1f3613bf9868e3b29af4feef5f7ff2ebeade2dc477b1fd3579692c0a8a1d69617b6a6e3f0bf3bde62d51e8cd75b6ca57d78d7eff91e73bd3bfc103accc5de98c15c39228eae1c516a7369c552f048f6e2adaa0ede5256006eca126c93f4d1021a9eb2570699b2891bd95bcf68dc0c8c95bda245260f3c21c95e7a0699a0056e4d3f9948348cd2cce49bd849dfcc2197ced46b95e1e8c225a89051d255e60d795aefbf806bc463d66e59b9a7f6d87edb561711b95ec4dfb584f2b991e91660601bfed441af54c95c7aca30fff45cdfb79ee7f6373a936bfdbddfe52df8e6a16c1929fad5bfe79d7f611e488a504beb365dca0b504ad7ebc4997b7a7f318b334e386f4682aa8f7d00ec4ab81841a0451c1277b347b2a4ff70da76e67e08feeff45c2f2c2740c2284af3e55cc45eee37ea3effb777dc9db0fea8753e1c6f072b9c649b185fe33b196b6e1350dca4caa9f8dac5b3cece88c65f7f73ad94e467cecce47795404b31fba40856b01e8957ac7b8d2a0c06aaac5e45de85f952bf8a2f5fc40e93762cf6bb6ce7fb5a4574be318205d71bc76e20bbc76156e59ba41579c689fbc6e044262e0d5137f882c8bc6c3dc7ed82549c18782fd56736fa216c9e6c4b890ec1d1a96d9d5b1fb36cf4f0ebfe63004ff1c8e08dfc7461f734c8c5e223c96c211958a3b522231dbc64d4ebb50be866948d8a4967e003ae1af375abb2717a8622da8930750bae64bb579638bf76e215e174a46f81716db71e1a15eaa579221e7376f2f42130eb47d7a0e5f7b01be000fe5f11890448ecbcbd72fbb7a0b1d0eb6032cd9ed28647ef84ba1535cf83cab308d0674114a648d48eb713a851d939893fc97e9ac1b49dac9c908a8d06fe82c34a1c309a048c37705aa284f3638f0e6f0bd7c8f7ffeaba59f40b5627a181297cb6b19e979440bdacc70dbaa21f34039246fa0d422b370f9c5ece592509ab22e297ddc4cd31435a05c6f24235661aa18e11022e5908102917e0c57fa9c898d933f34557a0553fa0efdcd9a6e54390b1d6e72f267eed7ac6a356c3d921c045f9b794654df2b28a4dd20339febec4c519ecee087d9439069fd3d7efd32c3581443997f5c686df4b83d365a5c2abe81332f40427b759cfd7db117e852c08672680520bebfebe20951a598846ed14c9bd771b9530ed417e3930e6d39ff0e36ad1d5108f74709d5e40b351233394b79c518327f0a11d5b08b6892b29bc6029f8450b59b08da8937b247a9e50c60dbc280de3bfbd6b3c0704e993ebe3512e95692e1c5bed5b06246adddc32f9861fc21b9a8a28b30043f3cedbbdde2000f6b3f55bfd2fc287774501aaeb606869a011020c76ff282364c59b1a52491fc75abb2a7ea8a00bd0e984bac785859a18045a81b100aad718693c5f0aa1c01afcc17f1750fbcf97aa45174ed028a3c3ef31f5fcc8b8dec0ca5f38f81591989047d5a320023293a2259cc1383173aa844b18da78efe03c1c030c53ed6a25a1d45d6ec50e5a947f8e934620e8260bd4b5a8774fd615804ca3e2034e4964074564c0ffc77be8d1b31e234a4b26666fd9aebf88a6037c3dd43952a988b8e816836abaacbea2d0f7348d5e6b07b95d2cf7fcf8d88cae3913df37872aa27f99720ae01efb2d45c5985c30b88e0abb75f23dccec18991f1e109f01a48167a92c11a6f40b8fb94d36a55a4e932bb0251bc5b94d896f16c71bfcc9b1f2761aedea73e21b117e17bfc607e4de8c350ccf3d3d61b6438bc04534d5ec7aafab559deffb56cbf3c8116a374f4ec8c30f25e6b6594a58eb370c000dd7fae8121fc50b4a2d75473f7194d6aa28a7da1fb2460c42ea9bc9066d9dbb65412c84e09fd22e718497532f97b06cba19712eb622f5bbce393d3597a206aa569cc1ab59e4417fd9668c29f9f53478a7349f4c08348816cf31fb393c0d7be1c374fd24dd5dff0199f0b70aac29090ad0f9f71cc0f78c4c86641e4ecc1f8c22a7a5cc278b331b827204699f64de363eddcebf5e455cd5fac3dac161e33726e9483aa037a90d098ea91fd08202d38c051f09227a5a435fec2398051c31186adf81826843b435c0d7e25718ba5aa585554a886a49e1d13efa13bd7733e5ef58a415d1120f67cd0316957f18c643b4108434b79c1b67a4f17f4457c70ccb1eb4d8d6bfe3b85bd51573d56d93a56b142303e85d1ca3355ecb467620a13725455f701021febf8d1d4fc525e78fe1b09aff054e411bfead0c2570ba20fb495119530d9209dbd1142ccdaf51befd3400cab3cfd787beeb8d5f0884f18d8fd5588b90ea2e73b68666a43c1f812616b5610653f4f8ae150048547aa5a4086d04bd8531cd64a223a2a40743e0716ae63973d59f6bfd7b412371b855e31e17658717561cfac245f8ef72fbe0743a710b2f2718d3cc12aeda8e8053272ef49f93f62a11b541a87e47bccdb9593e252171540bfa5bc709c1ded4828ed0b10eeb4a38f186af54ed4f779c161255449d8a2e79f088d29fe014342bb634f1f89ead59a480b66d826dffa3b83ab2a7d590546190a90e007b4595ed32f58930eab03e2787172b955e9b26da94f4f915a088247edc047a80413c79c1944c752b755dd7fa373c76edb9e619db734c90e405a50f6ba6073d47011d8f3e5cd12e2e20de50b226b50497577b7a42fa218f151770fa58c93546f7dd5cfc395f17be2425bda1b34f1e0e3a32ed5e0a827b47156cb8944309aaf562f4f9a849e05faba98212927aa6310923f481379db369807e7f06ba8ffa0fbbfb9f551fc8cd64e14d357f3aef491193aedef7aa439150845bc7c4d25794c2c04651f35a126b814f78cc5f6639b0b26f004caff63dfc6ea369475fc1778c7e0ceb877c462d588966e4297d07cc5f4c25fbc6423a47277f4e9ef13c939be1ed90fda2768f0cb107ac39a3fefbf5f35f1df624b191b734d08dedebe72af8f9e070425955886716d3153fd2459dec3de4bacf5c71f2782542afad86555622e0ffce6eb4aff8e917616dddb22ad7fdb286ffa0e1a81ecd8940b1d13957e030d776ad1272c6799613faab23399b9a437ef4360604603b5e344886e8ea0f5f0a6d838b9696a34fa09da214eb7685c35d72051541c7c49343ff3e1b3c927ce3b223357c96a45fc357555ac83a2ba9c959c14108ae163befc6cf37bf61563cffb0d3bb5b7b09ef9fd2443fbd16da4a40b63b3c115a89a9dc0c12deba53337e49c93390b0f972a323248fca977bc8916bb3090aae083ecd0075e6c3958ec21196f47e981cb33f3b7105948205156fedfa8d96124106be0b030a941e9f8895578be541ae2d1f65f9071d48c1ccdc3ad1b8bea78c91a286561bcdb5e72e3f325c3e5d8f0c3c11ae02c704dd5ff8d376c722e9a1c584fdad97d1ac855459527f86406168844ddf41cfc28f554cbf43c282918fec0775c0914c440c6aa8c60a13c3f434235b6f75647379f58334c68201333f79eca256f32a45e76bdb552eabc1e40c75e0b1c6919d89e92220f6d39de3e715890909189498c569a461f09d209c9b2120f22ad2bc4f746a119e7e560275da5ddb02049c518c123319c9bf2a0274fb5697963af20c61d76ec4a9fcf72c9007ad60f8ac3499f14e2a664bb9401798b44ad344ea13e07fca9cc4ac71e3b49723b6357642c3acd254c7d826ea20baf3380d864963e7a731e8e82f7690bca11b8d4befd35708a8a500761559e00d9bc4408c2e8f4c3b5713cb5453090205183923779beacb29573f2f5bd2668a046eeb17daa22d91d77fc461c53526febfe3dbd4fb214ffeb15758baecf419064b93d2aa6e9961c182ad9d2fd8fe249a4077d4fb2f35c2d58dec4b9d5f1ec638396af21f49629515d275c8cf172395cb41d170204b3af87158dcb150916bdd436d311df8aab339de2714173fd929461969237cf6fd0d91eb20e4bdd9f7c6ee72fa48530749031afed3e9fd25ace2acea5d7c8d2ee13d6cf55fe245cb70a2bf55fe7f92d93f0fe0881cebcc964766c463c72854332567b5e502c502326d2dd529d13a543d8e09d17199b95bcbda658b4b08b218a52af2d075372dcac6717661936cb4dba3afcbabf1d34da368f0ca38f9fd09e0fe51a0e3f8723f47d852759e736272a373c521b5eda074ac7d09f631274431cdbf6c9fdbf87afaa2c4010ae79b8635c983e4bad6ba9cbdb787fd27ca6633305ba0049274943058133c84605ea56f0a2d275150ec3c1be3027df954f38c87b029bc3539f73d0e00e10666439c1cc9f33d2d5991bc351a9d6cdbf80a1eb51fb16b2a4f301327e7b53c06e11bc30afbac58d29d5dd41abb3ad94e5c477a98035298e8bb7fce1704294d45a686805ef79a7eaa396f868b91b2b68146a4cba8cd63d45a9be43dccb04cf778ac168665501201c33140dc259cc39cd3fd3c15ac75e2287a71f7f31306e422fbca47f0880772620c6bf69312654b0231c142724398c82463d2d108161f31777a31f39607d8633eb29463d8a7339383239436cc8190a0f286897f1187117ef989c0f184fcfab90442c259bc85db72be1c8432a95edbdf70d94ad5132ce7d921733ba5139f5471cfe6040c26952205996b5c6ef77b607b7daca2e860926aa5a84105cae27f529a28a8d322a4224f0b962367695c6ffeb1a6fd84e7c6609dfff4f4320d2af861cae64bfd3536414019006f0aa968c1b956d0bfe8a8cb4c1e9bc6aefdeefc550eecff2593684044cd10f6cfcd3f588a4fab407f1fd0e6bb10b5eada54be3af4aeb8134bb37ab5d0c36ce161f8d2b5f89c04e9e928884c1567405022a20c3edee8b4b6be86eac6b7594ac3a91ac688ba123941caa4122a9b6bc2935854fc63d5c38da2d16714c673a9c15ffc333c8a65f23aeaaafa1f0d3e6268b98a2496746547d5df022fa754c81055fdba0895944955e2b38172f9b434b702d8bdb8e9fbba1c95100eaa46df36e64c22ff45894088e553a9e23c53378c774b2f0c938a9092c7a0dea267b4c3ce8f60d91ab730866f5c1beecc390c8afb3e8aa299b172616ed15d01eb7839ede007dc59edd523ab061e31a53287e10689a4240bbc55bb864e4afd56ffada5b005bb423de29622d9bbe6fd76700f7f180ce0d04743e9045e3b0a165b784dc36f27fc01c84eaed3606f7f242069a3009867391d0adc85dcbdc82ff0374b530022231e84ca4d029c84527932516e7b07a80d40a722580f6f22213bea66541ad15abadd421efe4ab2455866d409cf71e01fee0599062aa23fe92987e12547f8dbdfedfa499a930958501f58f766c7746aff5006f2920690181bc2e4592ca677a498835955376ae472512d4746e2ee4b0da6823a53829604f75d76f947161d26385947924edfe285c5dba8b18f59eed6c9d3d9319a1eea52d1200c1929e0ef4e02c0fd87ce695d8548f1094e8b060c7cca956afff7bb37888883b7489016790ba2d9994e1ee961e0c72816c3aeda6d034f0581fc023aaa86cdbd81d0802e3a04324a8eac84d2059e755f598186bbc297e4a38be8ae1165dbe404cb7251b20b8828e470bf51e4e75ceecf1fe6b08e1ac4433caca32f62dc0c6c42726a7961b20d1b17041f521c64cfd5905baac3879c4fcc4f4e2691c477d132a54ef08932ce332ab72ae287291e21f719691f8b6fa706d4b26d66cdd525135dc455d2822ea9981c0f14c50666e05f773a3f4a082b7f78578d3eec1113fae0c361cddbbfe13e26197609bdf0eb0a3c0f53bc9b8efd05bcbabb16fd4b7d16d40def936646459bdeb330141f03da9208d196b44f4f2fc7a672d666ef42276f2673fa85de3a1a90ee3fcfef224e48df617c7da2e8620c4c73b3c1be7475672e931dbb38b9222707257de9a84002174808be7a0680a907ebea46bf0865ad59aa7460c6ef64341ed6ae02b16fdd61aa0a3b391a66042d6f0790ea768e2ce64c9fc1862fd73de1da2b3fc383a148de70950395008b9e86479697fc6bbed94ae27d21fd012a192f45e48a8a714918766e7171231220e42fda93ae37c4f611ad65c8403846d8256bc4c80e2cf473329bbfe5106a8d5ee2f1da813d6afff9c565710f994586a0a2bbc64dde5b17edcf0fe5d957eeb92047f94134f56bfd730c445372dc168d81ec6ff530ffbcc6ab8c931edfdea9703414dbb83cd1a9b6fc5943073dfb2eb60329a85563e22f5abddd58a3083fdd1f4baa308a42f226bf196ce9435eb0991ef21ee851349b7f6990cb576ba0a508d97e8d0a6a665d2516076acc95488730df682f852cff8cdfc827068964fab3d50289162778577d84d5c54a4eb512020c1d0acc42458dec1049df5d528390d601945ac2240e730930cc716ffffa26965002304b172c47cafddb61d24c9fbb3aebce1a9f70269d020f11da27d49cc3c7e6eb17670ab5652e1b9bc2835d101ae467ec9aea5ea7de89e71a5d0b897f23898bd17d6d680e78d9a2d4485a0dcfae0ab91176e5f49cd08d51949aa5ce2aeeed136ed479984768162b02e633f77863f2f0da73085a7906843321f1a54c147fbdbc774a17d6bfeb7ca5d3ad0dfbf85ea0d66531f12cc306a8bcdcc3b1977a64128363a3a6ff4ec4028fb006378c4798e9cce518b8ddc3769c95610cb7b256a794ea07805294f9c58322009d4dc52ddfbdadcef1ecfeb23e444d7a99c04b8226754ba82815c92863ce95870e47b3f902e4d6c5299ae4a5362db1531b457d243eb0de6605593591f200909c3929440dd301652ac40f03d9ce916addfb99c2c7c35fee40858d8123ffd4b2f76b469e8a6cb6ad616d3735a28859141fc4e0069d4b8d9fd6df5d1f03b2c98f20c0d35689f52d677c1c92b6a11cf8678daf4ebd8f847101e10736170bb4c70a23e5d66f5ea2035a411d3c89c17b115f7b60a2d29b3a571de1548bac453cc0a5dfa77ad971513b4b78313e43b5ec4dfa6e98236869ded48c718acffebc9e0371cd43ab4238cd87f0106a4690ce476f855b75536d6d77730e519b00c84f0800c1955c2cf10a5dddf2616535421b89748e41983d2b0cbed287be73da2fccd8640d6ad550f876dfc9103ed876c54f14bb30eeae33e19c53fdfe29714c4043e185d05e684da8fd8c170cc42f7facaa98e5b07021140d2689efbfdd8ec01d4d68860b3e8a8b3ecebeda4dca7bef54a4f3a9ef5cea77ba007e825a46b3749b9af617b86c50873b29ffc78244cc3fe4910ce5949644eac6081546ca59b35b9db5a4b546934589030c48a0f6e79e95ef9f660895090789191a9ed2331fd99439c23c92c7d99fe6f29103250d9ef47d6a30189d1272a00076bbee8d16607ba6035b4326adbf6d0411a4617a81f45e53777240afc7f1d4e381cba12e9b8c565fc95531795274c9458228da6025e90bef48b8879439b4cd83558112d1d390c0db711c78d0d1cd4cde8eff92cd3a9395de2530d45d375ef08d4ab6fa0d9b4e119eec8e0653291e8c44937058997946bf6754702f792014f8c6c1b4779d0f1d2b053cb7c7d5811e841fc431a915142f32b9be483160b4010a15e22873f409fb7d083242a2a29545c02359c7ff74a23038e9f0bdabb4c5fe2a8ab470e5e3afa1d461985ac16c4f1bdc79fde5020f63c9da87243c1d28d914f2c31098bd93b6732472c89c8137ac3a3e2e4187080f40b8892f379cdc173a41d2438e2e06ad98f72fcc31a4925f7785cfc9ccd7983243701e260d5290f6539090785d7c20fc32ddd8eb89ba96c3e79e169768150cb27e3dd87f6630301fe0c5b841708e0034f129f764521a9e8a259b464a7c41bd0f735357b86cd49f2e666ede429e03a2d80c6052211d7f9826614e72397ba8c747fce89e76231ccfe570e482ea3ec2b7bffa9468688301b6d15df2c8a9331bb5602d87206effc3b4bde81a463e8072ba8284f14a3faffe6bc0b99b8b6768ca8e5d71ff4409821e940e819c9cff674474f9537a0f39e4a14d13c6912470dd628e1c4e3487bc9573569c6cb31c5159d303e674910115fcd629c08ef41d7839d36d4024f461a3d35a6409406219a4379ce7ce5b2d60d72fab822108932522ffa6fb5e5aaf9ed5eef330b30379ace070e81509f330d866e237020392b32c7b532e26ec44a32283ca623e48eb26df44465114c6b04565770dbb1dc5935ed38da3870f6a0aa669c4e23657f6d1740b7c357bf50fec9dffe9ab0e0c99605393e4ae11df8094aeb5dace03d7185047b36956e98a66ba0c8737e03e66fcea4e03007a3e0a64b9a2fc550c26ae5ca258987ae7702b6156ac70464bd89862ac27064637ef2cec2dab2423f967e705d7c94794b57cc058e98c3f07c23c607eb94fd4fc98716b5db97b7d020fcb2ab9def0290a88d4408fd1f615292f4b1f950f0b0938797591113c7e5177876c03bd59bfffb45b16f1546148cd9bc302a51850b012bca26eac1512c809e5bc25c440dc99f89688c01521e5198771bef7e5b9e3c54a72e11feecabf064ec1e9090a8995d970278dffa1def5ead4301e9b6c2f0a0506e1a4513532bb15d8a30c9553a706f85933f2ba0fb09d0f0838f67ea00ecb82dfadc6c6a53937c6a6f7ece1c834382c6a02600c3661d7601453cca030830a81322b1ea9b898cf13be3733b04dded4dec9073c06d52800ed037fe2606860a481d346428c6f2f7fc34cc0a65910b4c6c696209a34bfa91dacd79109b03fb5513cccd0ffb970e81a42b86ad1a39876d20a4a795b762e74e85cf322b44a865fafb9b88d178206423ecb1b1c8a255f7aff8e727d3b24e05ef16d43e53655352e55feb1995032439efc4ae1f561e5cd994c1ef67f67fe4ca2bf61c1a1ad26b2eca98612281f53fd335ed5322c09af4e7f7f06a933f6e9e853c2afb116065e7e4497a3173c4301a9aa546166c91160953603b78c31ce128a01971b79c5890cd99620fee8e4d83ca76340406a35c47fb57dd81d35cc53d90421f83d5a773febc7d343e2b436b2c2af1e417fd82db3b549d132e81e90dc4aa133cb3e7a7c47fcd88dcb35ee2b0c4a8f059cf5caaa7b2073df61cbdd82dc9fafa1662af34c5ba3ab39bdb9828e02f7612aac3d97272e6b58b33ff910cf4aa51bdf4ae28b919fabd8030004dc2db391658d96f3cb16a656705efabcc2e1a1bdbb78e54124675464005432cbfdcdab09ea6dc03916d8c85e9df129fe7b121bbb70f6cb5a7e8679b7a77d106828e6654ec9ab2a130e4d5b8d72a7f13b391ad6a08418050ab0218ae5fff08a22fc504d04970fd19b6a82c3183510b1d56ee044f14953781e0281908a68a65419c86a89aceca57f1ba232d617c8698e6b61d8a195851fea77ba9e91d01fb645d23570e5b9784ba326be70aa0dfff000734737ef0d1bb63b8abfbabd43b32c0d4d6ead36964d8b6f45e72ce753e28f4ff1d5e013dfaf47ad820deec4aa99ccb6eb28ac3d524638f220b0cfc317c0b73846e693db08bd05341db81d52978260d04ab26790f51af718e24bee289023439d6401fc35ae33c47559564a08ee05f1f3fee8572aee1c0b0a9b8f21c66ecb6734f9a4e484b10bf9a51f00ebdd8d48b49b45465c813dea8f2debcfd4513f9e475cd67df83c928513b85b1733bc78a753c483b441d5a149e7a2765ebfa96d2ffb39fa2617464914b8ce6b7bb60f56cd3d3f792e407c2b15872f46fd5f3cd422041ad5d6f5511ba808e0f20d47c66adaae7bd406823dda42d7f3da25c53cdd53fbd35b39664100bd6c401b23fd1f2ea11e1c4e72679b52e30a9ac595fc3a88e4babaac68dbb1ec35c30d381ac0dc27912152102d87bed952981a9628f343be23996fd8983a0402c8b495741253a6210c242b09d0e349d7a2dd413594edc2abf18d7e01bc978c628aa1722e4c44deb4b74e3b3b73cc199959083df12e4307187b27010d826f92bbeb15f2748f1ca0a40d14000806ab96746cf4ba260843eb8b1fbd0c914378b9d74270102530d39582bb258d3cf3841320a79d24eba43432a87f1817d300c064c03c9fe4f06c042588c217cc0888afc4555eea371be2f0bac575e4822022a1c607e5cd32c49c26e28bf0059304201f622d7fa164aebfd2a00693d519e7db0d536a2f683eeaee0daba26d58d6a8a2ed11bcd4ebb3bdff8b96e07044150d08578027de2318fbbdc4cdc700a897583b743ef17e34f0d729bd6ce67494cbbe089cc75e29438a7468b9aa0203ca8e99ced0aaae94979b61d1ffd5751144cc24ebe1764a25b09dca0e39260e50486c6a1c20c09410758801c4a03ea70714a51e3bb8d145b8fd74ea99b9af53e352c9172454b503cb7daa4ed2aa0152647455760a71fb6b77ead65cc94a0ab49de9398e93c15c2aea6f23453fbe91570b0068a8cc43a0f77d4a89cd4e6b4b0ae7b87302931fef1c08e8f052aecaa69708182be5560a02ebc816414337f02adb1d23b3e534ad55c49abfb6c8f2189669c9c6c984bbd297dd0fdb99650a9d2bf6a4139b73d034b432ee7ca5471ee46d3a84dac67a728e24ae663cf3f676ed992e7ab76db30d189b4177e8d96804a2127ca5704abbea95e486b219eb0479e2169760674e99cf884c5590cde8da54e09d06a92437afa10109a5e9003d382350e915b898d34c81429fefdd16080a0e5a7e5d5aa8ec93d150477eb9187edd6489927a37a84ae7de5d1a06aa4f6b30eb4453a09ae36b6abc521c130dae358fd9b378c417a725ad49f91a17cd1e6009bdc091208a0b6ac67c4f111ab10ce05f461e4fb7b30d6ee0569c46d026f49dd2fe20893a390e457f71be9614d0d2c24272a145dec3eff03570a1b8d78e9f5e43f3857a45388ba87520622223d7ec7d51214a33fb86d793424c9ba3ee761392710ff1bba30fc58a8260764e7c15cb5497bc22122d6c9f9e1273712b912f0f0d2c0c7fb80fd882ede1660e9a7d80db40f1d62a5e8fe36ba838c2f7ed79984f26bb85b757a148f7cb9f8ab1109e2c00fb104bca1912f88b6de317316cb460d77b571be613b9429f5dfdbf15f0be58db4a38f7c1a4e19e8b74e6970b49787c99614282150f6ff56ad09592fb0fd3e63d4ce56881fdb3c98f89eb0650609eb7698ba24530cff3e3475f68d677f44481d0059b2faaf4725d766cf907f63f2df478f6c6acb23fc84a04e067b85d80175c05e5ccc42b1e468b924329439bde2d055ae65de5bae748bf939d252713683774adf313d24672549aed5548be06e615b78b0a4d4331218c504739d16ffe9628bb405224d31fd04ff2d5fddae783e8bca8f45fca6c9cfd49f2d13921bef53b3844a7578f84f25bd35e4da1a4eda13fb5a6b09c36a495109d9940e7812eb3d6a055f03531a0c87ad99cf41d8f47e9cc9fc3d8542c6297c5ecde168cdebd4fe4f06151e6a2e6e93ab61871d7367c6abcdd36ef03f5d59646c0efd9eb4ccade6d25cf18863b533c69c5a140f2baaa37f3c38e4a6e57b231afa053d90ddae7596ae9ffdd67866fb6fa4fa5f874b9633ad6679a8f2fb640f9182f6e3ef53078838817e5bf1c3f067535a5ea7ffb393a0ca3fea148c398b07fb5bffeacae3a486794b990fa7e18fb4d8b95d1b365562545120cc7f7fb39e8f83fa99b9bf3b9d69f3eb004f310af4500b7e1536279f53af35199d6a0e0c33830e9a45a7c4c3adffc4aee23591a68ef9de9ff731b61f5007ecb4ae1e8966d5edcb32f5fac77c598c4fe169abe6e359d2f416b850797de5a2d6eb92fc07e3f19dd3851d2bf6a0ca6097c68b96c225fd6d7df0bd87b15cf2c006d25898d9d341c373be2e824decb61279fa48546e920b958c2bd5e24c7df97f39a73be9e74dd2d0775ccf8f242599813b5976c305014950ea26debcf38a68068e1ad6dd9542af228da480ea41f1d7665d18075a3f2815d6e69af0012f306932f8d8c0249f04e7bbbd519a776b15aaa540661dd346175c28ea087b8c8762766d46b556fba2eae7deacc1d7921fd0dd8afa0fcf8b7d9be5a334e32c1d0df65d4376be30eed01d5ab5e383daaf8b0b90d8a23afec8cd7bad71eb9ba5458faebfe55b847c59717f53467ab37f7d37b2bd971f1452a751ddde5bc46dc6736b92e3e698ffbb8168c5f2f2683f8882efcc58cd6cd42b21328e57b0d2706f39d718502e45aa905b4bbfa3a2a2f7ceb3285039d199796f3b4f97b5e1895c2506e587b519bbb6763c917b3bf161ffb5a09dd3c096a91ac0fa8dd4320171cac2f61f27247aade2b3c788ff7b5d71ccdfd85bce2f3ff3442cb29d3196c415071016267f3a41bc8c6425483f84cc8ace4da082c37134e623d5b1d9748febb684875ae05cf23694066c1258a440d7bbe1d348f65838afb022b058cfcbcbf482c044f92e38f33ab95d256ec64c1ec4982a2ad3d7643469237b41aa2bf2e744735db010de4bfe410211a8627d7c9d11062fb1672545409f772ddd72989aaba81069b8cb386c0d8e92fc8c25bee515f7954b3f73f439e4d163f2522d5283d735eedb295fd6dc797495daf167f837a86e085be1e9ffb6f7e3b62452c336814ad4e4ad65f4e7d2ce77767fd0650c72e8ad4558c0503370f440e891aaa85f129d697a568d5218005f4aed93ee988046b8ac0850fa04ba7658bfb3711071a3874450669b45eecf6e952a6aa4b7681dc7bed14cf574278c69b87ffb596444292b516dd69aa9c28cf1965670242987a3bc999334aa6a3d82fd2899718802c71b93d45aa8b999bc8d30b6c0d5864e531072318c991064780cf5c8822b28db7e0057c8de45eb90fc3abbf1440bff11279ad2ffd02ac136515839b75614914f9b835d17e547735e6353017a18653a24fe275ad1d7c60c5c2d1ff84b2c294dfaa1a89c49739bf8cdbf6c8c2acfd2a73bf2a38db93bcb5fe238dd565b3c0b1ee73f80b5382fe2b914ab75d8c1259521c4e08cae24ea831d69696e9812c00d469ad8ae629af92ab5a784f93424146e8911b583f724eb67d8cd8b2bdeb0c885a0454f0ef53802f331430173463ade1f8a2cdee58a88c4b5525699b7ea63cc78647209c5bf2a5e38799df82685f8d0f58da3c08765c2dbee051337afa13d584b44ea57ee3ffbca8619d19cfc37a22c413c3f2211b447cb9bffce715955a99d9d7b8ef91003eb4fd82ba8b4c129ace17c2aefbef365ed1ed553e7711c29d1d2ebf53c8a1e04f722453cf9d0216242dfcffe1c2c908f2883d17924710a1b9794dd6ae0520a012b5bf7e83e926faca3677a4468197939f6d6a170434efca8787509faba1cd4934d1a1097cb3ff0d5ff58742a16c0e82f683799d26bd54a98171ecbd603d1f734c97f949c34166c0355938ed48e334eb0fcd77f25a10a4ee9e849dc8518f8eb9bfb9084890bd17fae6e179e530bcf88c14614c5c087c218bedced23cda80103e8023b568c109aae97cf828db8f2716ceacbede54e5a0e930622b40ccc900dc22d3f48bf360892347aae0febf01406f99da9a217dd264a4b4142f89d320bfea2bd530d13c0bcc6457cd9a93fe67e2700cc3602bdbb434969b0e5601a39be1a7e43a0eadaa38bbbcd50d7f77e99c9e4daa38b579a7ef10bda2e92adb2c4a7f87120f6d2dd7eaa58dbc5d70bc7b46a09a773239892c299be379cbb334772cb59ee27f42aeac17147d6d5d75524aaa4a1cc35486cc93feb0c20a8e3979fa976e10bf4fbc6546d472b820d85470942278eef7de088ae4bb5e07aa9c054c24c6e9a7c9ef01c4df46e30abd3bd7de4976f2eacbcfac89b92e2c94eb41d0e4494b492261033b04985ae7df716b216ed2b641d6be86d9b4ebbfffd18640ed31ae14f0fcc62c4c11429e1e3960d3249557c731ad12296de83a783aecf4cc3e0b8283eb14a162f77b00c72abff22e84ab3007fa43db110aff4b6edceeea86e1004984fb452e7c73fa8e99605242dc8dc5526fe7a031f282a315975dcac9798efca4c1c8b1b3e2cc927010f24e5b5b513a697b655581913aa56487b110332ddf55efd5660c3d7e91a2b2d295834a77353d8fcf274e38b6fd5d2f8fea5b5ca9e18f7aa9fb7c4ee7fac18346a6c902288471fe658fc00fa7986ff930e8cfa3e3c5f7a2ff1e8931ee1dfa8faf1e1f13413c94884b4d161702a6469a3b19d5c857d1822fc29672fd7df5fc8f54e3ef77b24f3fa9636e576107902ab78d4d6b2c7d73b02e589b022ef5c6446f21ee979d1ea934a66351f64443f5f7a1e180c1164350f79354be3eb79b03c1896a73d1d0d92deda47492d94e490525abef412bd9bf29cd2808cf36d760e7db780746d4b2655f2ca52cfa1f7cdd5346765d23a72ab3478d8b1df7c0f257b6711f4029f2387fb3c91defb1a90baae02b0a9b193ee2a8026c95d24b457274b8c79396613b28a93f4b00f2fd02fe8c8e92d523e68a03bcb1f821b8215d4214507ba6b4f00f573d394d68cd2fcf4efb1be9775fa4ca503ffa9879345a4dec62418a1fbdeadd9c74ecfd283c7365d3c85fdfa5db33aa1fdd3b6676e199fe185c8333962da0c326ca016815428c251c6c053da3d4623a7db920996c6542922484eceecef394ff1b7a5a762cefcd8f9290443158fedb84532a974f3f43c389c1a3c55d06261e48160a9acc6ff1facd32dc161b0283f629388f57d48f7e77ac674d3603d8fe1e507c669880803a8f9d3f4b2ca2f0ae98a4e309cef92a0ba207a2d28cc20363cf2ff67988cd87093bba24167d68fa1ca27759c634e1ad7f2d9f668d33ab2814790e6c80ebb78a949d35b190dd1f649fda38cebc046f582500b5c877e0bb22a1c9f8495a36ddf770ed2e9a29eebfd2cd5077aba0d4272fb85ed235a36f112106b32d76f518da82288e4f221f24fd4600d024392206a7767bdccd989c2c94270bded2903286e51aed4b819900a4212f9152663102bc5c7826cf73700fc5b8372db824da6605b1ff0587a5637ff95c41cfb553b998783d7c4c4be228ae3fefbc639999857296ef91c4beeedfe451d4139064cfd6e664c9cf58e2c34f0620f20cdd0e80112bce06837e8ae0faf0651308e3ec1439ba8a6064740bcef7fd54124ced4480c7e8bf0449120b9df0361e80609071d3960197993396aad63a451da6e74cb23d48050cfcbd8f3dc7f71a0e5f28bb6e75b26b668fca4b9aea708e3514839a203980b1508450ae087744e7a484905c5b681ca55adbbe69f9ced5c765a0d8dd027dcff5d5b73c8d6d94d7016fdb082a9709bde0d799dbcdc8b6da75616484d3089d85fbf128335dbd686961b8a82d9de6442b9c56b7cd077c1c47df061d8535444d399bdfa8b45559ea6960276040d6a042ae9627752941d2961f397664e02d4f244e27452e94e8fdcdd4f607a74f0a53b166e6df4ddfa0db141c7b445acaffea45fc9dd7fc52f9e369812eec7142e22a139c04a1da8322f62680e30f8ccd11a9c8be41be298c48a03f9bdaa75fcdf6034760f898691dad54cf46adfff8d29004958bd399f5f200fef6e4b24b26fb416e00ec41fdb7c173fdce8507c0c0874ad032320f02cdc416fc9a1d6f88c7d9a3f076b59bca9fa6f2b6f30c039d05cb1322965012d071e361ca182aba0f4539e758f14f60f39de828281e3617751d47f1f26965d0cdbb84d1e7e84cd6dcbd4a3ea63fb3926a037357d1f419b4729fec459f461b0a860029fbaa49d96585d25503fea273b8ffabf05a317c448dcc4fd27f6960a64e8b78cc88a99321f0921dc2405d77b1a46d4e34d708bbf36b68e62590e163fd2cfc83eb4fd7aa8915b8fc1eb66aefaa1770c4a6231d6b74fc65e7f8536de00dcf15f4c6c46790e890c2852b1a0604c3cbab69b0b89d7cda3bad6d86b08d0cd455cb345353f6eb993874112aa2ab0f0b650c5947102e77aab17bf0bd6f9a7a409e34db29238e9896bc60f03aa92dc542f52640803ed24ed0c872c1c1280795e3bc78a36e21d851b6df2710f27cdfee8a5516315b3c3d3cfcbb2a3a842f67e019ded2a4aa2b86f89ba13a283dfa00c2d67fbab7bd327e18f7ee350b2e891b2fae5888ded2bb61b4255eba8452f446c7899ff57382b0c6ba8dc5bcb8d7cb57fd5928ce551b4bd9752113fe701f399f659540338f286575d77a65b8b0afe18061e41ae665e1839f7143fffe674976bb8c440c80208eebf4e2a9b837edd23108e16df29cc81a3d17a47e5b91eee6072d2654b155f83054e07b179bc0b4aab69cd953eb14616aee98e8885f479a626ed3a2c590b4dd54873d7b524babcca571b9b4a51888ae0de4a4131439c3959f9053b06d7e617ac921aab3c7da27e34a4d48321020c6cad4d6a598e8712b8b6a537f601e7768200636e49ec4af36e0fb933e26c850100ecead419085838e84638c08a2e4cb4df42eda8b92404559d64ca6144752986453418f81e4e6bf10b6dee273edbbc8866a058bf1a5d34002184f8be92defbe77bb0bb45ca4d41183ba6751b48a6a14be3cb40c23a2d3ce6d9dfb1dd5874ebed48ef5932b8c45111f2f58d32870d111395a9928886893a66b20fc3fd454dd3f61bec961462ce934019b6f60a35701ccc782d3b98b17f328219d0f2007eb2b3d14796ec01976583edcdbd8afbe94a7b6ae5ec60a68751cbf1141d10190b2bb34762b8dc5f0ef977ec7ccd717d81a42c88ddbc33a513e4d1e593d1d0b73c53d970bdea0f83af6264b38f6b0ee484c6ecdbea44c36fd68bc8c61ea6511cf8ce534922808bf72c96cd8a73776205940cd6c391b9ba7cfd161197515c4f72bb92bb753635403d11a0b27f53fb5be20f9a6c7d3d84ee9cf10c8d9e7523ef3f6e7d5bfeadc6e04ed8a0cfa6a4b0af8014411f1fe385125013d6b24c92b629e0e59d42331a0ea42b5ceeefd284a340f98de5522a6449e38e450900c5056016a98b22a6a1e27adeb00e9ee192c7d54cdf923153bd8f42571ce2a81d44dce052108b0a124b3719efc4ca7ac72a5b3bc8fcd1d24c595997bdda5fb6411e36d46286d7790a1c987f3c13c818784d9881ef522e24c0277b6e6c42d745c653dbacbcb5f9db8b7e235164e4f67af07f07f47f31f52d646173605a0d6095c607969d0ef6dc67aa84caa00782cc45f7e89bc8fc89ecef6559c49106c29fb2094b65a94c6db251246714fb89920beeea2031ed0f03ef501ed8679b6514b089e09092412928a2f88492395927ad37b90186d74a4ae33694fed069ff79af2e265b07b08611e5872732ee62329da3a735a04a382187069c795cf61731e1dc1a154ebaa1c346affda272e387bf27f6e14dad38193eac50d252f1845200f94e513bb190d017a114aef3723232e97bb392ce8f39a109567fccb0ceba8caeb709f926b4b9234d3814210fc143529395952624a51ae71a04ebbdbc8bddc27d574153de07c24e20510751e66da9f08e4997201c35c6ab938681beac341669e826a13fe70fc7c6af91b6f255ddb73960e4e8b3946d84f9a0888cc3fb177599a3662ef6711730b55ddf196a0128a2c6cce05dc4a0d8b645eca67a56cb58e59e04b15cc08d9621812b7adadb6bebbaf4c5c370bb2773437aa2c21e0cd7dc6bc43c3d5f69e870f82af02bf35a8da34a709949194818c046ced1de2f24278a2710590d68cb5346f5e7829c5299d88c52930cc6191d1b359e9f2752231d6ea123592ec3756c9f5c9959128bf389202b197510b92405eb261bc8713cb40deaf23eb115a79db32adb5ebf2db359c66b904da9def82f03b6bf376a1dd512f986b0a93b49130426de782afde6a22919f1b6c6fc07dba58dcb98c0fd807814337553bb17c68aefa5949691c2fba5169ffea3d85b5845b15add9872618700812dee09d0af3f3a4c9d5a1a62c1e93bb17d0864c5cfed2fffeeb475b56dc4eb01bd6410fe55b6928e6c4858bc87e3f49c8c3170c93a4d1b51e3a3f65fd7598d53f511b5830a5033e20c062fbd9ce4e3dc17319e5645e5df9e2217a8819ee65049dfb9c7467f867cdf1a859e0f0814866069ca68390e6821b6c2cbccf45d5aa7c9f0ff8e3a6647ce00a1f50c508d8ee4da9f53b1da730bf1416f50ed934dcd87950abee3cc27661ec5f44368b132f2a691dbfaf2be1c04724cc1684490dd6583c7f41ae5fd1590b64e75b9a2164894e4d8c8aadd4577098391b2272911c3d617630aebc1a548ed7e6b0b10cdb0b7a67f64743ff993ecf73b582bd9a85a94408e7a3be22c830665203b8bbd3462379dfbf42b1f506857be2b3bc6064deb910415b7b871186fea277e206e126016123b62d7399221829ab4a69a4519131c0e62b50b783f5e60b371b265ebeea2e72d6e5424cb7217fc70ba2b51c916f60d1f40266e86d9ea53dffff267f48c2053eb90f5d16ab4e5f79eee4287943eb383e845e4ee3b3bbcbe7ccffea733d167aa42952de0253e4eb4b6440447ff7813b6e96bd2c37bc8d54c45d5c1afc41e7b25b9e51f9793d789e14f79e0f85d3547dffa36e130fd187981ce2e5c13100ebb175abfcdd9faef90d3cc5b0df4461dd7b4310990312f8c82ff32c90b2292680f70dc6787b29d6b758efacfef76442dc0767afa5c112089f6ca9817828fd6c39d2cf41e02f68d8160e0f232682d8f5a3c1e65e102e94a19918bd743be0002034a67b162d043a1824629102d53e58f13e425428710b32efc99ee9c0ec57b0f1eb3877bb9089de4eef4836927e2fef571409931f8874c33540bfe70cbac2d6c9bd848d33327d1f6786fe8de5aa4abd3412d2145e076338f02e9f1bfcee20776429f5cfb9c6f9d7521814db4ca27377fb171c04910fbec679d15df09ec41e8401a58d53b4d9b1da3038254e511c2bb15e9a8b0a7618205b218ca32d764a8391182246a8ea07db4e509ebca4b25cc503a25c89c924c4010fb5d8ca6ae621e825a1b0d3d315a41d7b475c35e04291cfecc65259e4c1aaf15525bb77acfa45e75a38820cdf9edfefe8bfc6e5651f4e77a3aa834f55cef632e820c5d6d23cc6548ee529ba116491912ffc5fffe348220e3b99e0df101f99f682f186abb8f73fab8b56271116488f005f2425eb5e24bacc7597dd9bd59f3f4af1d0469f851866131e8d36d4bb97a4c10d6a9e5d8f89210412c8fdeb785a25ed345a5cdc93db7094208b0f8f02148c11317b2d8149be19328e97987a12bfedff2b8df1064f9f880b9a9b3ac3173b68b1a4522bcc17e17ecc692f404e582bfd1b4361fc0cf170cb4c15dc234d52f8f8634de4c68c4282f4d823dc401ada744f01827d4cc942cb55b03ac4e5fdb756bc01533def76ef5ab9aa6bd7c48031ede464ea808031d5d49a781ea21f64fedf8d37b59dde86c303a0b3361a0cf2b8174d084be38b72dc60013eaf39eba848162f15ce0234602e22aff6305fb5ad687e123b63b0c7a5b1acbebc20f89a04e52d0a46cd4921032e9b8c8ffd50121864f61e1932938c71aee22073843f78420ee8cb2a2514020a720a7eae38dbe24c46eaf4eb0d6bc12b13bf65df54f1ee62016f00a122d6292fe4a52bfb661c2f02c11b47869b9bc807e175c299ffa6e8554e4740b8d9387bef5ef73ac1bd3f53ef8498ce7caa6d16fd9381cedd77214a403fb10388e5d44727acffacf4f52aa675fabc20625494afbdfea4c41e6d5bbd1babb96920ea343ed1611e85a879530525d22554279eef32414fbaf7ffe0f7395b718792e648151ec681beddd4c6110fe57f36c5aea299deee94e3674a8e82eb88aa0348d2da3b0ea79ce20e6a23479061ede1ec35746940476cbd51cc4575fafcbfff27cc77241059340ee226f3f78eb6724d13822ea405b958b9146564838eaf963d7c4123d79ba5a94e802dfa5118f499c025a16817afe8b09ea7dd2279ba30f31822ff83d03300a573616ebfe9e63b67e66a647c857c1e8d7d8fc9c370639ad7b84b637b98f96ec69cce0af5a4e2b2c59ff8a85c92b5ba9aa603917f7f6bc2b8730affb6e759f86ff65504150b113d9fc8dd2588ea9a8d5111a01edf7ee22f617380b5db69e063c776ab7855f5ffd85a9b4fc3566f22d16e08e82ce18cdd76fb5cf3acdbfed5747937101724d50a098b36ea1a505f9c062be6dd0f6a29603ec41c172a9543cf1e4fa4690fb8e98fb1af44205486d323e5f1cde80dba9ebb99c27fa1c369cf1271be884b22acda6406b539275a0318e712f43d3c0d2d03a1d284eb0788d3cb2df9b8331c017aa3aa86be957baca725c428d597bf26f5dcd9b6671e511bf24b7949f70ad1bc4f3f1d6423767c6a402e891daa24b6541fc4e37814300d6afcfd6680f5c599e918a2a7beaf6370b5fc2090507411ad57ea554276adc55a24a181819eda4bf2f3a04f894569a90c3c37039295e119761d4398c774419480bee378b09eb9f185edc24196251944b07b57dc9ae2b82a2b6be1d360fb8a5ef438ce37d4beebdd2dd559fe342caf465330ee805e98e6d1f8ed3d72174b2e5fe16a52c5244edc6758b20076d7af3075587061ec2943d454a4cfe2cb5117623c809f13c1847f72a252dc234acb61c9c91a273c647400f7e29415e19fe836ab15ac5a2707b3359b26159d31e6ce1441041a4d6f7caa696f3f63aa5e154bf061d9c7d8c1541f6168b9ce1b661f9c1215c8e32611443792f37364290a6d79d495ad004d3d770891564f3e1c9b02ca71b08929c3ada91c1f4b50b0aefe2b4d0e787cd88c85f8ca0f74fcb31a1d3933c19b96e8c5c5f9d67c49dd9d53fcd0ab92bb8db67af855a44e73ae2fcfe5fdfabfc170b21bc90c0b422238b2d98617fe86e185f665b353d20484fb079d71739071b06ceee591e0cccf23dc8c8280431622c24f19758abb5a5de51d7e5f9c245574ea62148c4d2b5c9a29f3759e12c4ed5af6b169e81e1130e821c78f0ff2010e1871da47317efec476b39e0cf77419015b1f79c53f53dcdde181478c624d6d0818392d2801e5c5492746a7195f6b4926a5e5b2f5a186561dc0c3fdb7d2ccaac34d7482dd6588802018b858f14a768e3128f18e397a084252a75d3304dd332b1699bb874463757f3a4f14e6b82df6da7a60a6235e19590b2c06ab112f3e04b759cdfe643646586865510db63e31c0a3e21a69e890a26d54b95b7394a2c02597115481e6c91ede090ae4c1fdeb0ddbc646ca049576a57cbe4c949f78d2c81ef0ecdf4d149bc86d2f72f6e7ebca726272beef12ce45fd1aa685b5c7e07820bbe8cdaadfe8c0946a6ee86635ac1df6849d846bfe2ec2df052598fdcde5b41928c8bd7537b344c9612946654761b2ea65caf0aedae0a6aa9985e242e131b9d8ef56601968981237e782c7979765fce2fd73b25bb0596470ec3e248f48aaf8492dd3a30982bbd003523be6d064b9e4f50f1c95ebaffb3cbb451dceeba7063cfcd62119a0782b89310bb3e1c9d2dbfa9419b153cc14750a046a42b6ed8ea66b291e3ceaf03435cb4d45a9b5357ee44ae15ee0e0a344496c08b5a6e672bc189580f9c804f7c39bb1b0fcda303baa52ecf02e0ddef3ea2aa597377d5f702f7faa6bc9509349608139448d7c8874aa5b74522d6ffceddcc3c4f47ecfe186f30753a13397f0e6fdc1070a6b3837ac08204b172565f8627dcbfb55a6996a90ff5aced18afdc85c4200102177f703667bf1af9fe234c14627e5da1828877acbfb083ac38e8c909f107afaab91e49f15f1afe775dc63eeac54ed0276eb57bc026969aec5ae312639500d53b425befbc3f4ff01c1ae379c65422826d68fd46f2ed86c35f66b020a04c02a7d7a2eeccb18ed95c206dc6ce65dd0e7b1afe23d0f5be41a7c8f85987367d111bb74e70e814d6ea50a5fcddb5b21dbb5a8415668db71b9c8c5b321b358dcdc4e6dbd6170c9a76db5986bc58c39cd272fe4e2d197c467b2636404b1c1b3036040420f852ca9948c9ed0abc3da25db53de86e912fcd54df81e293d5c11c0773bea40f1c11a07d96df816c9e5abc2cb9e30fa7a3dbbcca16ffe9affcc613512a922616a9aa51801c97e4b4575a9433d0c0fa5ab3f07bca26f6136e6cd4c5b08df5a9dd6c21bbd22d4f84dbf9c83632b6feb8f8a367ebdcfc7b70cd32b85aad69ed50a7a117b6d57e3d6e406f08c899f2b9c5dd971342b9d1f2c5097e19af30ad6d08e627a3b78f0cbb9d12d3b747837a9587136f534ccfb5e763ec6ca2f7f2effe2fd7fd2af0691c78ea1e09d5e2eea899c3d28f9c33a5c913879ce4e7fc4f9e66edcf675bdf2b889a88463a7e04a0748e8feb58d14c59fb3fe477c3cf928fde7d83d8a5ce5c806a80eb231c5b4c01557e7feb9f6c014de35423ed9840036770e66dbfdd8be8181e5b9b5bac3315e1c6c481b25cc525ce8e6362edf3ba068f6c1c4a5de1f3d69ee09d62178b32b4a697a0563cbfde58dcde4680e8cfcd1b57f31a2ddc0e7881287f788d0e5a5087534460b113e402059eedde18760fb2f0ecc43a82aec944ac214e977b39200ed9ce1aa97f55bdc43a7e2a2dd7c15e342b7806468f449b4df510da40a783cd2a42360da88c19a6f79da8c13e81159471e345fce51be884156ef31cb981b3ccdf78784ac44a531cc3d3a576f7642cbabfd3ff27b385c8f7a844d2bd1ab1c05d58f0d55544b80c8b6abb79921da149e46b1fcbc8a37ee43de8ebf8de887c001de408dd9c78ffbe11224dfd9e471961ceedba9eccb5231a2c66f86fd1d7951b5b1e8bc39453e56066b55dcd4c937f8d726dc9f5f0bb716d97f38776405a6f2b11d449fc13c94cb8d947b0a270bd89470c3ca6c4323dd088ea7d2fcddaeabc50992eb9f35db03f951e0b8ae51687c392ce79cdb5f8d98c89b9b1315a9cea2eb770c549d1c8adad69908ac4d5c8f89f9b83b468e90b4c699f4b60c0a96518bbf978e86d37f08175d6b8538f050ac3896de4db8c48f1833b90af37efd5813fc91366389d5b9308f108afefa0812d60c7fccd8c2e8feded70d657a5e02cd053623feb1aca69a85f5669ae0c48ea8a6a6234ab65f727ea2a6b44457f4307f75e0e29908ec66f59da0707429c5f972d86457dcf3d389dc43e3e7c8912e4dfcb6322631fae72f4587d08e80715e01be128d178561927cd65cdd1f914f4010d7bd00f704f9e5b947a5173102eb6bdb3499aee00f6b9a66f5e95b0f68bb389f19e3de9306040d68c2b18d183876cb11b20b869f3aaf175162156523e73368010f390bb37d42c1336afef97f236fcc50f58881578387498bb6b6bbf687b0eaa0f7d86e333708e1acc3e2872f75ea01dfb4e52639c0351bd861237498fe9e658a9c1f0d62b6e6741c3facc12af0ec28d5c6337dc72af57328be052b7a3324ee6fea48f7bc60b20054079d9d657cb0f76daa1273ab020eb8b7a082bf6a7b0db236b5f75838391d3eef7ad733f913009be5a1d2ff35ec3eee6f31ee6b3cdc3ba5dd1298d8db28fccfbf7ae82d22d894d48da48ecba8c6c08eaca9493dfea141e622d020c9a4ca66e9ec05a7ec0d72b03fde2f3576f39deefecca506dfa332c8ea4757393d78e8827b11f5b56565596ae48365e05e577179dcbcf4b179709d876fe59a5fcac0ef6de4327c918fc7abbe73f02fef68da82d8a5b79cd253f4c6a6fc09e1d18b37fcb6953d028a957341e5b38d0aed8487e11e4c24dfcbe5a289f1d0139846ef6ad050706d0984e3c1cceceec59ba5d65be5da322d8701f4cac428723d33270ae6ef35cab1b1bb183f1af391c2a88a0d0874ba19b949634e2ee51c7686374fd43dcd1148ae94026efcbbcecdfb5d022c8f09979d5c83aab10eea857022eb424a00cee109c608695e9dac0ccf0b08ec0bcb84079e06b703160708eb3ef41f8512707c28b599dbdba0be169f6a8794d120da1a176d1bce16a5a579b1ac71ddb358b721d6b81c3d18bbd67c94af6809c791ede4ae088d07f32a2099bb03e873542c628640257c314126abb716551391b36123d6abd5b9d263b810fb4e65c89c77537b4fa0554531c84ac4727d8916a9753c52769783e9e962769badc9870627e61fdef0582f20e9479e94f99a4f9a235eed8db6bb828996bc3f762decce4873d731b28f33506a6ff76833e97718d04cce4f17c5f0c7eefc0a78ad2daa284657c1d738dbac83c135c26e5626949bca7fe6f9b93d23576f84c963e3f5c02852f9409bfda1db648b52c890d86aecf8baaa442a46ea2e024545908a84646e3d5e9b307abb9e6b4bf7a95a8ccad95e96489d4dbfe48bdef92662104478a9889cd3e05f2d5a539b3aab5f098afeb0cc0ec917d6f25cd5f8fa3b546ec992761c5f4ec7cf34248f59494801367c3328c11f481ed3fcd2aa08af3d7ec0005f69ba2074fe3808d2522b5fc0e0ab30626b599ff365a033ef9ee52ae9bff34ce47bbb7f33cf8e80c0b6dc1786dd1a37ed09f25cb677a6cb484c819ad37fed162bf89681521727a4b7db8c83b5fe6d5f24eb7d251765f5389bbb359079a5818df66801b814a4786c07fe38a6a9f01b4f2178033d5cb26afa5f33e730e3a44a3d60a3cee91e9ea3b117c254de79151253b44d13914664d8c91c945039149b19591a3f2917aa62cdd7af20368a3a0248a59222a158647a905856452117070adb97e4482e220d04cacbef2c728c91723036b4d97252c6cfab4f93dde6c7dd5767dc91841a732a1eb36b00aaedc9db17d63a7dd45861657f08311bd5f1fba02ea114da0bd11a44e62ca7ac33561d63f11c4d5603b611bc54ef2f95399f7f470d18591740dd12efc08c8b267f1b335548a2eaad88a18d986b8f7ffbd7ddb04f646dee9cfca62adcbdc14ce20009fbca031e179647f12518a3da55fcc96299ab934ecc2171197649d83a5927106abbc7ac1b51db74a2777ba7061589c274c9c354f50215e8423ad6ed2701c6d90370b7d536ec1bb874b3a968ddb2b90dba70cff7b3acac426d53471927f32fc54be514f3073ea2b432b38524df08cb2f1e5fb73fdfe387247e71213b6fff341807e8b5b7c0c8ed0701d245f13740049978fdba9128f9915d37c9376830645899c1524b31090c5b17142d0c716629b078d1825a24058a85a29188798da7136832b7c8cd3693c45ecf8fcd57fc89531cf04471362ddaf02f3039d9d2f3fa16579551188efb26306e59b06f9c9aab431f6fc5ba052d45511a6bee925d33cf83bdbdf399f41871dba5d31f5d508bc918809634180194a1c7b34c1f17dc2b564d6af8b3487402dce51360da8f93dc1977e8a8abb81030521b9049b96227c2f914cd45159881def01379b915abea8f44460b226dca2c32f08929df7175e35c42abab3800ab51245299b655b5385879b2a923f9746b3854280b675499b081468ca1d3f5a1d32ef8015d6eedb2bc56f405441f19f650c1e217843c012799859f41670a72f6741be71b07f2999a416c3ad04616898837b9c8f25db52107c06ea0c5576452c68fb581a1b456c0b62e5e29b79a2bb391dc8599a3e3f2135428306aa42ded920443aa8b48cda84e59de3f2c368c0a7a597f358f44578183391918edbd29cfd16e95dc45251d0cb2eac228e6aef2832394884d2a8250dd027ed618a7bab6f253b5b7f02b382a12cd96886dc84b657db7871e4a632cfc2d81ec6f23afd505929a75e81a25d2d6ac97c9d6aa5433d924ca407f621d683601c146f56da2a422492e3bc731302c2443f489ebbbfc588c92cd079e2731745fbf5cdf1b1a598d0fb67211c820351f1552d16a881cb2b23e4c5adc1862f0ed3d977000385c53815cb29baa8d73c4013fc070ab4d7ee52c0f30f4e3765c6c7249ce42202351548a5486370b2310e0fded5dc177d3db8e253f8098edb4fb06c26fe642304ae8da48fbae735699f86753a40d11781ad68d06f51fc99749a4a97c744de0d05ca9d0f443b7280b52415133e09d6a2caacdbfe3701107c71422ba4aab4ec1a276fe652f01836c6af644c04ec4736aa92a4c68d5c1b90c3290315138e60bba9be5e3910a66c2659e59248ee9d7a2c63da5e43857e4eda136a8c9865de5da8395224ff324e4bbe95de064c4c24f0b97ec4e96b3fa2bba4e1fe068da206e05025363fb7ea22fc6962628754cffc7be28671dec752cd6f54aa6e8d09ff255bf4d8e2148e33d0feef178bfd1b87c5b9d35cd4c684bd8878de4e9ba1cd0ad541dc9099563559c2f2fe7f479ffa5bd81a543aa88a57d381df5a9f3fc0a78c5222c01d44f2ffa3291476b425ce5c9391f7ae578cef004f7d2ec8be5547b8ac97803928b55897c962c9362ce62fa9c4f84bd08ec66d07a2e8b9d451c50e0e474013033e47665f79fee4a99699b74c8d766d30a5598ccc2103ce26d02440d137b1e96874298ad68ca2dfe87f936d73901d7ca7d1cb4d443c7fb96fc852358fe293c622fb5ef5749a47626336570f221e52ddeeeca2cd4bd9d2670a93ec123206c3dd1ea5b798118ef847865509aa09ff5632023883c04f2b3a8e2093d9950b4eeb10fae460766e2c4f4c7792ceef52490455ac17cd800edb6bc1fdbf8255c8230f42721ff14e40a5ec267916b218dafd1a77626fa8e93cda2946b35d097f3a4af7662fda7796606611a3d431d45706e8b574c3042f47834e035ae36cf84e6872cc822ffec7a172a4621dac2fa93b13adf9ce178c8cf4e71a27267fc818a85282444dd1b719618b2e6b4c8e41b297737b8fa40ffdaf457f4a0d758e139b69caf43b9bc84643d1e6e6b62ac2dd86c51aa7f5333fc2ac496754f4a142397a8c6b12137037e0bf74393617419396ad5798f3f29e7d8f27b15ebd23a7e35e0fde8f6c2345a9ab463a7918edab27ea8a828159c6e0354609dd3d071d71d5d78fb437aeda38c3fd627cc6639de13fddb5d467763cb0922bc52b02f4e1f6c4253e5ccdeb489682c027260ef77dbca0aeabc7f6abad7cd96eb7c73de2db339baf1457d8d5bb0e7d0844dc167309a1e1a988269e71764aa57468070b695e8f6b3446eabd964a2686485454905a7e31f9c0d6aa8779d1e5f66d9fadb995ec0adc2b7f1c6b5fba66843515e969bb4dd7fd96c1f10229b78972be898a441e9efa3caee9eedcd02cd84ad979a63f4d88c5d986641564bc1be9b075b64467eee23ca95061ff301cedd075fbb230992575351d8e918efdfafb6c2f47b19a31747805ff19ff653295216931cac9c0e98fd8ef45c54eb7be536112481617eef74c1479a19966b19e2bf0cda3e117c40f5b414765d380ca5761213d29abe68a5d794e2c2af9d42469ff142991b302be7a8cf4bf20c56d12b09126b8a1cccfcbb7752883fd3bf05a9ed18caec582af1f94326b9f6ddf463abc0dd37a6c485bfbdd7339d02d27ca43923a9108fae8c89dde9dcfb6ca35eedef3b48629dbbc411d02382bc21cb89cd9e814ebe1bd6dc92816a0ca29bb69aa4f96b015eec75ba59efeba05952edc4851db20c9b88e05e322e61397e896482dc4a47c3db7ba79c4eea944512cba0617e795531d636e764de5e623e884741478f611418d49370624b0b9955e865db404211bfceca18dd9a314577d09b920baa177d68c553a31e54c56b898dd6715b5e34e1db15c2db26a81c0272d0993cf781c8e49b970588ea66118f0731739a47ec9e400b7759a522d4d69dd842fcbaff6cab96573dffcaa5098d77efcff1fb1dab336dd93eec1df8b596b012749666e4214c21a14439d010b787b942168a237b099cf3beea045d79e69fc0b76c4d1352e036e25d4646fcdcae2de000785cdc18a9db66123213974cfcc20445ca02a54e8bec7d98483185626f4daa31f895a5f433054ec12c63f3a589ada368e2f48a1d9c59c74dcd78b1b73cabd77c480d0dda6c516b369b8ac494d67192c9cd664740cbae3140ccd429ec796880fe8b26cb6628d3391405b83f745e17124b4bba23c167ca43354a51cc88ec47d4de159c79634082e60062146d91319c9149034a892c6ee426d8a99d9e7ba9f5329edc8e93cce71e7b93dc1b5021e9c44fe520d08be9ce171b0f24b5dc2ff48cb2e51fe36fe053ac08fb12be80421c8dce327193fd530b06caed6d36c8071d2c74ca074dbc51816a465b5c1b7cfd104b14b2c05c0cb2bfc8f3e68ba89c4592e5a8e911e893a3d141e94c8fb7456f0b85a0adc80af82834f68761431f6985d972b2ae368fce37defb5ca7fd03587083f203e41a294c586f6e5fff494210fbee1b319e2cde625d9ac35e6126d68b5b5df8d3ad00effb9e66bec93535a8c22c26a121245740c655db7bb4345a198befa1f0a2c22ef81ce10f8f4b76d67a0afb588080a1d2318d181cf4dec638c982ae333a72483e6f93e18d5875e8d38524c1c17e1cbb1e79e7274aeaf127cfebdb1eab259c9449639129b1ca3d52af90776b504d9f7ea5fb73b68779eaddb753a964abfe33aad4cb0b7c290cb85031c6580ecd6e7b8056768f70dcdfccbe590391481d7c34be0f3f6412c222aebe5d6c12200d7137a5ab373888834b3ed9ac75e39b92f9ddd1f259503b76785babc69d5f863385989fd0f4121af36e5755ae68105820f114cb2992f71c888c6e92f537a566efe0bc943e83de002ad4876f0a65c2bf7f13c186894c5645d2304f7dab5836633814a596ebd7c830882f1c8ad4c28edbdef6d45be220728086ff2bc10b18f53db86d99174e8b08cd90180082306eb9724005a258a8e918dce00c15756d32e4f2eb97cba0e3cbe36dfc36a716174fe9b72b338b8ecf2d478636408a236d14a23fb4862083b345ee28ee1f902843167a601676854385fe65cfba917b662697f553b63c541b6b3ab498f9892143da804fa6233d2aa9a5d998166be54f02e2fc5687117f65fc3d2735ab9e36816bf558c480543057cfed2e8f871c38941dabf8103b7c7ceded746024b4d373c46909b08d81e4af79faab8a8fae0c4ea11853504fdf29141ff228d757640f666d7d632aee3b6cdd444e331b63cf3b4087a814f16c4fe80baf37ac771d2e2b71aa11a0f185e07436e9c0b528fde9f56e09678beb135603fc75b772c27e45843937fe021c11ddbff2cf5b6b73e300d4336619e7d5fbeb596e4d4bf4cf7824c48b08b4b0dad6deacb95675a1aa68aa77fcb5d30d9302f7d40413ae6f0f1ffc9595c4646479430bf1e448e1d806917390a9afd1fcfa13baee7f44193a1dbdafe5174fa1364afda29753c487b59e0b4dff0034546e0c0b24b741627b66a2313716833f5e20a87b644467a88eaac63e7d61739acf94a8ad372439676f2b4f2e11547dc38798309f07283be7f79a2df7a9bfb3c6d1414091da5fe89c50a4a76022be5cb3a692778f11d891a8cad3cb9e78b2f0bc3769c8e5530e742955d6ffdcbad404350b18e75da04c53a21f395badc3e9d127b316114d195e0ab3fbfc27c0adbf549045bce8dfa96c63dcd2b0b51919f407ecbdecfd9a2a587145d2d54ecbb347137d4ad548b3e2a2096e98f4e77c1a7fceb6a77c94082ca92589ecabc48c0fe2d50fff31dde616142b673a6a865bc47034109b067742915fe56c5b4f1561e34960556d449b8c80ba011d553dd7bc2b5ef99f90d959a5c7947848e1eb9b68ff736d31b49f6c3fc429cfc52b6a3cedfdb82d69b98c482af245e80c6902c4751fc8a705beeb2ed90c3d9ae2d99ef48175f6d74ad98ac8dba6f38013e9507ad197cd7bdc0c5ae751f3f47ac9f0e61e4255cdf5fd79df51c1948fddf534cf2a6d092fd760fc8c618aa2bf65b2fce7e94d2986de7bfa60a5676d7bb3f29d9496bb0f75603ee97202a025d42fb340ff1eca9ade4601ffeb42e6dbf8c6457822a0b42b0d2ab42369ebb9bbd6f19c4e3e139bb810a739e6922d7b8f55f513c98655d811510925e56be365d649145e9254260131ab8b80e9cf39536320b5209d0e77e7c9cc5c74e6e17816fee763399e94d2bf65e3d09b43a1512952d67d442e27497000db2137f1fc595e17d00ec4024f3e241b1942b2707d2f960c230063f62eb1aaad1e00b62aa94c73603be35022319d3642542d26d7f431d75a6f8b41a0b48dd85350631783004c90dce160f5bd0095dde5b72337594dc0949d3634a045fad41ee025a271acf56f362c7fb737a6042fb5d47f92b8c60db7bc8a3960fe2a446e09b7d336e90cce1fcfebc2cd1d82ec46d5cba2c652b011e4c5f501a2f4497b85eb86b3ffd7e98987eb62898812d5d545d607e3ee0b31c6992a5a6eab6e2d2c0c97a40f081ad8ecd6f129b76d35ea104f8ba176eb1559f56f9fa1702e886d4147132497e26daaee72c217f563b45f1639b997bf1926e39180d550b89457475fcc87783ef9480aebe4909e5d8b9266692d34be3005435f5207f3874077b1725f089c3ba4e8cb671bd81f1864c11cbc31738052906ded36e7d8ed4400892fb0c4e6d5e4b5592efaa8bcc2df50a6f977019642a520a5202fc3b3b4e9c0859b590bbccd7e5c679f0473d0ee3a9cf096a4db2352dfbb70db58cd5031f37c4ae7ac6983b28b5d7f7dcb459ec9f8ca85bc858dd5e09d3fea289d783826e3709b1da83332ca2e7c9abca186c4b521e0cb4af0a25f09772f276caded1fda306a52101d6511d49d1c9da939d3f61a22ce5759cd66abcf601f6441c198d688dd3b1667165700ae711e545c6bedbf2a59e10076d0d5994f8a244e79e5ff9096b7ae6cd48fd3b8dc0c1d6848445360b50292fa26e8128f4e8a6c615f8c6db0d7e55c31b2fb3ce518e89cfb87bb5fd0fc22c9773107d90b615d9e94853d8efece7815af2eec1a014cf0d822fd2fbe14bbde3aa7f7ed05cccc2266dfdd4a8bf6720966a5e667b76b4da5deb590372af31f99c64e5597c6a77a03a5e0838e6af45962639a4def64128ca8ec2c3ea7308183aba9f7d6b5fbb5c3546d87404f9f4cb460acd68d3a72cba62be1cbbaca1117c9f679e1a430665fde3dc49f42c8f3915a13f90937aae9c902ffaff27a2f3115581ffc24e6a0ce75bef00836021dcd64a3bd6542e1a857c75edcb5ca449cfefe080c2e9882e39717a7f7a56140a11f14f43309cb3a34b529afe710b3a009f76243d6fef7febcef9ade84bc4ae085c20a07dcc2c14a55f9f1f97a460f9c11f86e4274cf45566604e289b6a9e852d30076a0497bfd55c92b9fca248ffa1b725a19b721669cb970ca185f11224be74fd24677de21c396af977d84dff2f7a64591dc9fc4f37666c63a5d4ced0b3ae858dc63c88985b81afb571a6667a0b0c2737386f142d1d2fd945eb69bcc19b310fb0a7f364219461c09f9e443f90d44ebdccd894c275d0f9ea760ca456b220ddf18f4d987cfe7be9a0dbbfd1e3ed3cf715575c9c5ee325d2ba0c222cebeb20964860a699e1a74cae599c9bdade31355e120210de3564090341af84b10f494ee22cddee423aba1ef5d82523324365bd339f78e865f26ca12131c5deda507af8cd74c6c58c82dcaf54992f0a147c598c8e2012fbd7612426298c7c7548518e5bfb4a30b3fd68fea191158cd19d754a6af032ac8e89c1b7afd16eae1944c1a06deac83528a16391e6b8e0403772bb4a4d2f7ad8b579dc057183ff174b8d26fcd1adc5b18fd181810bc59eea3eea1e100e41162efabec2fdd24c2aa6ab64b4b060ecfcbc915a8706b654d4b1474fe36b30aa8fc5d404c866ccb71d42ec99e35971285aa17192836e913617d64c768ba30d9ef46e93b3f2be150dd61f508d88669d2ff0593e7afecb7d922985ad98910b7ce2d10f99aa1cec36ffca66289157ebdc7018b283ab9eb69f2e65ccc8ae6b1a1959997bf4183d19924a4791645064cca1e4e7fc7103a7d406c6ea72859d47a6f0d0d5aeef1b0851255534c2724dc75840f20e097292fe766dfe6004c290123a9f07d499003cfc0292f600c70d506b43262d4b8bf0f21728490cac5ee6489efeb770e712fe56fc6baedfb9213360eda6ea85e006b18da1f9eafc9310d9957de118a567f03e42337e399b32a5716556483f17c23c677921158bd6f782b4318a19f286d3f09ac4048eeedd7f3b51fb3a53d1f9bfea54cc32cb49e98d80e23410781182474cd8a0c347a45429bf1507f93ec2a256fb4f1bc47f31969acdc2a075c6b0e37369d0bee126f43909cc1459572091bb2da0b57499ee51ba136db05a9ff549eb11824a045f85d9278d74e75800ab9f61fd08b9a3cb2a3428e9e26889b174be2e101537584d5c26b64e4b80f4bf5a753144aaff248aa66079d8abd0e925a40fb8edd52ebc706298868a1ec9e81ad5aa16557ee05cd1ac867b201a093c74c60ced9e4cd22275bf1d08378296031b85c0f0908e9edda2be280e0bf3811426c4458665684ea34726e524ee6f219cc8e3caf45d426e13cd5a39f45209bcf2df1511b046416562be2bddbfc173c47fd9c1b3b861cf8befdd68901c10b35dcdbddb8bfa04a2e356f6804f337ba747ac59c1c4457117fe774272d9b713af3f9f07f4511b037a5721f3bc175ad4768413263650f4ab4f925b474e1b342313c624315847f6d141b0f4cb332a4cd46ff370eeeac5f81f4bb212e33bc726c60a0c4aa5605cd0a4c0217656542b3a623166b02188f591f5d17419d3ad133296704e259489cb5b095f96140369667eb6fdc91a2fd3d4fc8a2ab19b3a0e9726b9f3ead70c517cf70a96cdd1a9f44a1bfb6af06a7e84327254138effda3fb7dd6540a34d981207573c43298599da3d9cce702e8eda94a78752585dd565ae37a0f14fb58fcb1b37bf33009bdbb490674d455732683f9d0854c634f252bf11b74b8851ae314b42f09580af2fae7235e2f4400cde0331e99bb0d2e572a3e8300cd1045ba11c4e2833c58c70e73dcad3d3cefde34f00147c39e7150d421066438357b6961ba7fab9bc1e288d80d554087d0e2d78441c312e2a67dc7dc8eb47749ead24661dd220ad507fde0eb3b09a90d78fc0619220744a6f956e5e4015a103d2b00de357ba27932e5b0a3892b4a2c36a29619eb57e1bd0d6622df7bf40520d1e874f75d9690e38f44bb3624dc9b81d178403ff36f774103b7a2f525cb9b5c94c97e2cef3b44683de3f2d85d806d76fe5ff75e4a499fcfad7b1f8ce549f273b55eedcac478da382ff36812c1bfedaeb89e08fe0352b829ecd2f1f4bd5ff5742afc5962d0c20802aa8fabbcb135f0292beb8c5eada9ce143b2cea3edfd571e298383fb65dd9786401781f822c69affb47baeb133c753abc800d29400597912b7b1f278ef49c08328dc6a85b1e37fe7f4d801b180be4d5c5b7a7c598bd16bbf916c59b4c2ec1934f408f7b5ef6ed37905e16be8ddb2ff9c2a210ed52bcd3d9558d124bf3195b0ecce7e7c40ce2d6f239f09e0c3fecd9a173e5b59f51d41fa264d567c99caec73afe15923b41fca63b402131861711222c1ba6bbaeeee82712c783a28134046532cc25ad0d0eeb00c4217c5433f06e14c5f5218b9ca00f87297c34b1fbd10f32c0f27182ad1807f1d7b38c25d33ce7d21bde96ab194f545d12cc6e96c97ec3e8955bbd03398e2ae03b9599183bf3e70305e6c5819559b83c3a366b15e0bbad395c98adbfc2f756a796226acd50e2b2c5a260390f7decd654d80b0fc5aec08ab9a7266fba96d78d6a692c5ea257d7ce70ada74b3f42d2dc50f35a0f927a76dad5c5d813c134ce5f7243ab3317dd6466a5d0860d8649788a923dfabd453c2b42ccaa9c43054f369438e9b6dec2e8b1f99ca926cb5dccf9f0ce4bfcffd95e9b9a5b12ae50ee824338edbc8e529a490e1d5234cbf645c4d8c283617d2967b7afefbcfc4da57717d6a183f030d9b29fb173fc51aa7290ed51ee82fe631a4f7f475b044982b891b0a71eee5fa6e5c9c62be5ccb13127cd05d117c73e8f2903c50bd3cd3b19642aa5ca449eee134c4c921d27d838f0de31227470ca27c87d1f9b80c9ca114952032a50be8923176d84d15708fa056a4d97aff474d97ebfb11239e4afa746be1d0ecc202c2e37fb796ddad16b9e5c960dbe42657fd4688496f2fb0d3f7699ad54943a46630eb6fb40c86e19c0923b34f092f9be92d5b5499a0351c70779e0aeb14c80255ca86d3a88d2fdd3715ad2d96c614257018aab0ff1c1498774c9dc51c10e6391832233f6ba26734e35a429e215a5dad01a55e448c84b90dc157a79247335ce3b1ba54bdf220d27063d10562ff6534d47102950f2fe946e90551768e967dd195a1ddababe04b5aa8deb6c8a55157401bd4e1d565643fb50887af3294c02353f6bd530a9b43fa71c5522f817c497936874d14270d5ce5646e349c7ef14ed3387baa68e8c1cc44134510569da472b50634b8a5f80ec4f5408c4cab6666ea1ace90af240d2cc99de7313a9a22df47b2be397c06e2cd01c448fb1807348333a794f1a808f60f8b63198d4ac0996503a8d8a4c7c217e085c4b7507e354bdfdf7775b8bc4b814d1101f429c5efd4ed84f095e53aa7e1c04d051f0d69b2f71f4a9808f732ae6b4738bd9dcaf1a244551d8b8c565e80b80c1f380d721ab56da9b4c39b041cb25d57d4ff4b188a2c89db5b4dc442cde5ab231f3df75f10f7317bfe072cf396e33f4b052ee134d762ea431fc367f893a42a3b7de2731d204b0265a25a5ef8d1f59e5fcd2a83bf4f3f6e4d6b074491e61d97b761d187a5b4c71f7c0e6c2c9a57e6a8fa20fa8c115bbfb4013ba3ec0551237ca44574ceae2908e1f20ed8456e08559ecaf0c04b210db7173d4944c0c242b5bbc877805ab2698e9c67024b014af8c61da8c956de1ef4dc4c7e5561c12c854d86d6a6ff0e2a9705ae36c040d3eeb8ed56d64377c4373330466ec4834869e7a9e8bececdc7f97431ab5f2093d56363aae97ea3ecb02785c86ce505c70f1695a93cfd21b3ebdeaef07c8138e3206e04c8b70a20b84cabd491ae4b7e0919de3b7ae06a7fbf86603194ee15493ad921276b3e5c327ae39bfbf4e9940c2473236c636f204cd777029003e26618b665287dfb71941967e8d762ae5953c1d25a23699eee91df8c0a1222893d4edaf61cfa2720a6bfcaae927a6daaffae63b2887575fbfedfd348305784f48029f8c369b3e4325aaff1e62f98e0b0763c8015edd336ad7e1c8b74b3e255ee8cdf9375b288a391ba33b0298a8b4d9298350b65af11e15e9a1cb8ba63a874c56dd1053d2a5fdb901aa085299304d5b8cb24ff0e02c4c080129ef2d88121f497ed759c22553414769cad6f26f67a441ec07436a7f918439f43f1ba39b97a83098f14ab40c2ef83b43eacc1dd205a4f0500426439557ba3c495fa271f1b38aff8eb407aa36744208eca67e70a765e29d6fc18e7f6c5587fbb8a63ebfb04f293b7a39a8e758daa8290164b06f9c6a2204d613039355ef5864c64958955c0f8fa90e71a8219a77a891f17e5b7fb89223580e6f740da917852f1a039da88c5e7e888392a6b5ef1832cb1823c786611162180d91c67940cd5cb9391dd2eef37142c65fec0299c0175827e36972be77e3ff99b33419d8c29b531f0d6e0369397aaa7e353359b9dca0012330764767ad6fd56d66bea14dcce1cc8d60a11fba66ece40dbcf627387efc725d17a778b008918561cecbe671ab2ddeb787d08badfeede9b25c68e4670b03558ce5d72b8489f473463c9841bfadbf5b53b555c6a00e87d178a4692b22ab081c6e5c50b6796fe6a559d7fdc291bf47e991fc9edb3b4011bfe1a6448af9f9707e16b92ac8d01ab2fda2e82aea69349875447d6b77f1c464f3501f7b4737d8ac1da3ae7fc8bd98b84c8fd61f9a506215dda776c70764e166779f17b4de55f859f69a1de8b4f2bc64fc1775dfaae6f993808bea7c65852f2b06504565e5e95936dc88fb29308d999f9a7fb8e464052b041fe0d01c8bbf0c817e1f4ead660467ce57e7caabb85e07f7616e1aa94be566c54d7a1928e419d9d54c12f4be923c4e4aef241b1b857e0275cf0405b9cf3003753e6d6a05856c9691e824f2cbd93b19f24134a518d193ffa2de38163991dc31fd0d6e069cacbc3337aa28221d1bae12186c31d8b64ed8dcf250b5397adc552411c1f3b2ac265c1571b41f9ef36a8065aef1441f5b171a6b533d81d8299f86d23d255f0d577e58f938e05924f72a51e279b7dae49b299d203ff2c54df95e0fb960439297407122075af201dc827f94fb73c02539540f70388901e70b30a380c0100feeb0fa006b0200950023547398068f974d5fa341e0544743e1d1601003801200d80fb31c0c544e0771270a91bf0b50a609e02da7f019ef500d4dc873ffda1029c08417e0811d904de77005df980a77f7e69278067e20080c6104874064ad60141de4ff74240a206106e0bcc510086eb032d0601a0a67b1f10ad7b52fef264f600a8763e6d1b3ff5263da14c805a644f5de30050b31c0e900400471200a09fb3efa6e8a01ecdfc64c89f32df3002fb7c07e37e4b72d8ffe2be1bf2c5adeb6431a4ec4a516415235d88ce9a53c4519e9bff81e4f84ebe34cd5e3af359a575f7d6881790002a05dd1a69d9d56b0daa64460cd6e02399a054ff28b8c0a4c17fe541e75fe36e90c4d97f7d70c504faad622402ef4722a1c0d4ea07c495ad55b260487c5bd3ef8d0bf0031fd67001bb7d6bb506b0a4b1a221f652e0827a2bd35700171b662751a5515fd257786d0191b28c15cab7695583583459510edb101c721a2e87d5cccdb0d7c7b03750eedc1b14292d2b814fde607059230a108b4c083ccbabbf1a5ee720396f0e037e49413569453db0beb3bdd464dda1afa9ab9d5f3376c2b011e4d52e9818292ab03ade1b24436149635d130513e31d774b72ead806d25d8e4976dbf65b0d1d5dd7530985a6b690a42b8eb9ce04d7ddd0b17f1b1b1706f713134d092bd209cfce1f764434ed0736c0d7cd5aadbf6c66e5adc5b622068ad2cb1f20b7c57a1593c70b3ec267f7e8bc955374896e0c704266989bed64c97a86514f60b0d1c6be6ea4352cad0c827a8236f606c2b2048069c4e9f5044302acc5ee8ccdc6414b03a198428a37aca7f84dc22e1d9dd7f266f358555f9c26dd0d13797ac63111031582faf92cc763fdd7323bc2899e543364ce208056a6bb56eecb56325888c5af1674c8aec3744f0d30b57b88dd225096a732c5d354e49b0d41852bf5bb46530f0062081d7e6fb528b75d5609c0163eba558595cf4cdb1ffa7b05b34c0269faeaaa7ea6e84c67ec8cf38554c6964206c6c19f8da53b2fc1836d0dca145eb0a9ba7295bf873701af306e8aa621f6f5624e8ac4173cdfa73d838c150a2ddd7b1436fdffa9a6e6676307d4c46843b7fc646eac4349faace29f049edfb376f4b511742fbd98fab18e728a7782858dc31e811681bf69eac59cc3e33d193a4b751f0112df4a1af3e07b3313182b4bc242423f92ef560f7a3fdad70b5cac04dd8914828d00e8403026876e4bd27310a1cd09475bd8cce342a3f20a01cd29b2487a43561b1e04751b198848d018752e9f29eeead0d885ec52f05a745495fe81b3fddb7d3449fe67b549033b7453499e5d42abe2ae4a7cb43691a8f60c0e1f1a00fce94561033ff65d7161cf98254b26576fc841cb285fb8246e8a99391c29a17a817251fb0ea1a4d908ac9c595d7bb9d7414679557a1a90a2828932db18ddf8f8e9a7577ad447f19bad2a3ac065f69a63f883ef67647ee4dd5c7c762888c238c50d93ca9fc9701185ee5ea6ed956d5fe3934683299b9472f3781a07317832a245c63979d3208e83b465ffdc1fb39525e03f1b9926624ef903d64d14f2b8e79f6e149e9e18e093666cf904c4cb0efebf1bb993bb381fb3d0aeb8166228a65d685401197a4a342f75a2a0cb05e68676d5ada228dbf34b6837067c497b38f6392d734fa1dd7e37bc10a5d179eb2afdcbffd114128563c69124304e442a85b55a0cfa16f437b00a428f17ab49863eb240e9420f3affaae9df2fd9f1df6ca1ec013590a342052b7c4d27b1d3e9e67b3cbf317e27da87bcecda7f4786f9b53f094397e5815724fd94f58f9745ec0c49f9780176d0b9e75f6d765785d85cb9dc7654434f9ddb01edae9a6f9f5e27993cda0b2f09318bc8ddbd756b2f68b1dd1929c275bb084ff6a844626f5f4faa2ab374c3ded812d047bebf1d84aba4f42e8bb673ca18ea112749e96f6b48ac2d775e0b585602ae0f2b009724ab83056ed8971ec4858614b835e739db423b1069f5d620164bb0c81015336e9b6ceadeb558beba0066352f49148a6246009d2660b68e55c166a3c6ae36b649501bd48502409a82271282161a01183d586e5384664087caf8b2bbcb8104931378d93799d09563acb578157e4e3f6a64d916474bb3c494b7de9d6d2cdacb93c3eccaf2d47f694bf646de517eb57835f4ab6dd2ecf6aae57443568c81505043d39b82d1aafaf01ce96d7241a53434140464660bb15c7d309e97c381704b334f1aadb601e5c0c4ec7eac91e05235214e6bd0da6ef90bf6f0b78a0157dd9f8c793a2d286d3c8c0e2e5a84a9eba55d209e6f1087bc02000947cd9184654e6cf5aee0ff95fe8d3e4929b671f68acd9099cf6d7731f2b9652bfdffb4c9883facdf63670c2816be194ffef51e2afc1a344afdb6e646d715b3bca62da87177e2aad09599ff15979ad8d86db27e9110698bc4901ab4fee5507ff45bf486444292b22de1af64d910f7e5f9aa5ad1531d6d6d5ceadc7f5eff51c66c918ffe96fde5fe5f0512829ec7fb7a1cf619a865094e2a831a34d61a888fb649d2d6b57f06f9fa609fbc5931587bd1ffee991456e19e4b40c9bdf24c403d2f82e24dd9d598cb3ae89afd6760846977c72f8fc4550eef9b77909bebea5f490790cfd34130365b002c44b020e37b07a58562ec7545cbc99dd574d82c9ce66333cd077b05495e28e430b02eb13ec094d08e92d1dea7a9a916aae84cd6cf14fe0a7875d4b67d4051edf06bed2797db614c4d249213e373f569e1cfe0406f455b5954dec33e8b79c672b3423c4f8bd5960c06930a7cb18f8563a3c8506ee91248c564bc393b06a77af8423b46c80a5e47e356beaed67015546ea3cbf8befe19765572f9d21699efdc10bf2e1f81d4bec1c970584f97e0f4c2ef22dfdf592900c224e6ed70024675748ed476c34353bceeefa2ba638d4924dc7256518c2fb0eb0e863c0d2c31bebd7a313f14852f276ce12ecec4d35c91917f21bd660b5f3f257ff1261667700661b5204ff3f452c282d557167a780011aec08a9e523623d7120445545c9dfb67ff3259bb036f760004f8eb5539625a45687bee435544fe7f1aaa9684d93bfde63094c0eb2068f40c421fc8505e9cddfdf6bdf844103a6b4e71f5cb119474f5b907a36189ac443222e276316b72bde007fd3da9088f7bd30457f084c9d231c9c718d08aab76770aff11add344217d579c0608d3f31a59ed6f7b14986ccd2979ca85256f6fe12f75b0a13772a4f29d6b725f9b8d1d4010cdcc3f12675d05ee4183a1430f0ffb73ecf067fadc414af8a0929d8c12be0b1e6cfe40c37424d64e71ac3d7ee79df2b3aa8736ea85f8bf2e241b963561278ab8ee5899c99e28554a96c8e0a924a814026d6a4f285feb170bc6b467bbdf9c85ac24caf1e4f0b422767870f57b3a1c7d391a6cf6658bbaa3fac69a702d3f8d1be8a27a673e0e1974d795ace05a3d59b905efc5ac58db3ac2d85e3dea647c4257bebf610c7a7cc3d8daee68951c7f0bf8553aee76439b41de6d8fb883012282c4cf6d94bfe7b1bbd63cf847a9db393f9fc1b9f6fe44f28261399193251b2610ddcae85932cab10777a37e83f8d9b91932aa76d9c332e80bd3f375264edfb4b213e93fd059539a5cd6c1e1a0d394c461dc560e6928e9f316ef7cbc5ad64f2319cb40dd35117af3f648714e79bd06406c919c2a6f6f04d38652f052e7f873a815bb2205e69ac79a89f3e5f5588523945443262c517773d6802c9f82285bb1619d32061ee9248287fb00da8f7df4fc9bb32c95260b9f0ad0c72ab8ceab59f83a677dfc4b0639af714c6845e24262597ccc8dd40b086d19c21bbbdd10dfff52b7b1d6561def41c3c2bb5f5417f7dea7b7b17bd4ad3e437e337b207c74ad3c75446ad70f4d1dc926bcaba36a73ed5c72a7fc41bcae4acace578cfaa724a882d3f5a675d2b31f05f4facb71cdd52940d9f27f630c91fcf5bdb7317bdab521eb107d4e1f64880cd99bc396711368ec99fe6aa69b72f7fa8ba6d3454f7245ce8688da54a14dd9ec6a7152a9dd438af767cfb7785e5d7b91ee975c30ccc04417768c29d55b0c55ed620aed85883e57e02873109428f3a496f1008a67f398454f172fd251c695b29e5ffdb36c97a3af5c344cb617fd75e78cceefa188303af05b861506b7a311f69fb922ec5ace784652db80d510d2be6cb5c0542bb702a4c5d23d9ee88f2b3c0c085c0831abcad5d8a46ed9b12fdba943f2e0688b9b013ae13be7ecf462eb6fb0ac34ddcac572b7654d3ec361da4577fcd9ebfcfe5dad7aa542343f9cf23f5373649469e9bfd4a319fda45c8588c76d381630da616ced4dd86a15e20940d7b02ca207e8a010143d367db1086f01ef2626a35d4633143ba00bf90a7d6d7a4cc3da5c592a5f717cf0ac54be990a24f15c6058c607fb6cc4ce139e9e82b5a03906364efdee696f5215d66ba8634c009c564a67d6428e461f39b113e2f3b31aa4e93084ae3e0401c0f166fa08e1a10143034e8891d0a892dc933bce4ea28043071e0d592104924c47118a0088a983405f9ac5b52ab2088ceb015b329cb9f8b24f44f3ea971350d38efafe94aa7a34efb03f7257b3234aea6aa3f1533795235e20ffeb29948fa5cab79c288b374745ae863e0d121abfea2180b9a3c166fa685954c7a28e76e183b3a1dcc9e0432feb8f63faee51bb5a550fe764a412fd6cd9c18bc939eaa93cea6de76bdd4a60b417cc7b66f40ff8ed96757f208421152f827861c965a0860cd722a0eaa61e12f9da5934620f2a4d63ec2e3ee4cbaf5079adfe3147513ea0e3a79ee18362a20322e4e9801db6dc029b0f822d2fc1968360a72a0e3b5617d8776ecb8a74dc5bf950fed4efbcf72162a41476748bf796f993afcb31d3ffd5ecbf99a343b107e14c04aa9fda0127ccdd5ddd17c0c7b0ee8a2850ca84a69da9baf0348c3097a0de53b0e3a75fbdc240baa082c3416c3993c3c8e98fd22cfe6e6a27189a425e0966601acf4eb5d0108c2e0587134468ad0f80001d84cbc04c1685fb1c76c874ef4c2c802305496bff91f26bfd19d80f7c78c2a280060bef4c23986706ba365d15a720c50f5b6d1124a649835a77d1bae3893806f3609698cfbbb3f72c5dc336d51ec2fe5841e3e6f3321d71971b984a4cffa172fb585791087c32a0eb0be5495dfa4ca86418f4caf90e03d1709c8a4f48ff3d797644f13deee4f33394ad1f49467fa8d095bc86055e795e0eb958614896a0ef87d4fcc3a8309fbb717df6c6635a42e2abdf3c5350a67c12a8dcf00673f017978a6665b8a778488fe68bdcc9d704add8876add330df0b76ac69118999d898880fc188cec0f345de6a414f662370d494ede6e0a2a79152c7f28617f3d0be5e10e0830df66d1ef4a71798a1e7aa64884253c0f57c261e1aec33de9c6d72cc39f2aebaed15a2c44cea04f5a4abb3470044b780955f61ddb5de1d7ec022b6bf2f4c72d1acf33c811b39adbacd93f22ef33887c97dc77fe94e0f1eba0d0626fafa6b56534e8ac421230e82a29c3cd3b864ec499992f7b5294ddcae1823d15948431bf7ffd509ee0a5e23e67b1a3c9f8b914bbe3a62dbbc4ef93f0d3e97cf200faa7b5509ee0e24cf6d288da3295b04f540e528e9419b397e037d257cd651cf4544bf4b0b9722afb698c0f7e811edc8584c6fadc3ecb7dbe584563cf440ee91a86e4b7853a2b571b404d4d71998324e73642733a53cee395bd20dff77fb96262ea55d0742539fe6e123699fd3c02c024c24ed0c5ffcf70790b75c4c51b3f7a0751a3fa67afab6555c5ef037e739fe01045604128faeaa4fe7bc1efc06ebc5d5f4b6415dd33f45ad6b2cb22e627579c260c03cca548eb189382720711199f785202cbbac1b10abbabb0b0390289e60c2454587b6ba718a004cef81e932b79bc4e22484122b9a4f6ce0edeaabd2ebe1b8610d3c6abf0f620fad76c1ac46bbf331a961a779357cc2677730f29fa5298898c5db0794e29b6302c22e12f7d3b3aaac3d07b943e2d5b3875fab5287cf2897e70f8479e32436d4e684aad7b38d1b8f00b93b19b011ed45710c5f5352b3a6f99d4897096f4bd2d7745d21b4e5fa7da9cee7ffa43ed017be4e0f3210f24d3445357b26fd385f982edcd7e02230a72c458a5a0e5a04d5ae52e30f8f43a50d81e0cd5ce9822f1d021b9337a325c2683b6b75334fda9bfb5d7bc7efb2614b47d4ed2ec0b24a7261f2908fca2b6074e1ff63acdff5b45e8008e7b101f23f297629ab29ce5955844744dacbefd1910a1b1a4520ce9f471b3c7e9e4dc6fbd51103bc2033aab777e5b5ecca1986d2b2634cf0e6507b9432ee75fda9c7eef3d4d6990348f1e1ff74e99e42b78def1f0e9d881ebbbad783312b8221eed314c22d14c77c035a304ce38535de54514eab5ab161b520b6e4789c9e5df79b36dd06efc49c260328a5cea2522c8d5ba17657f2c490b57c9facd60418777f40ff5ed198177d5324cac1bb532b0f3f9f253a90f88c192214cb38f941a4bb5182af4f721b372546c69ecc77face714a5802b78841c7674cb6c35f93090b847e07e695956608b3915c42507b9bd4902455fe00cbdae6199b18b3acb688e5782fe08ab054638fd18a11fdb124a5df0d4ac3a9885cf51805db13209262d8c42e729d206eabc27495f703891392ecda7f5e5796ade98ee1f9441d00ad8266886698a191302f6714ee50bfa4cfcb91c210df4c6b4861aeb87d18dd83a68608867e08266f7639f4e81789b5496f36934adb4ebd8ff6a0a6afe8e47655dbe4dbfcbb61636b896dac1392cd2e9d2db597d6ca58749683645256ad886ee80b8f3f7c63ce7fa1cfa958746cddf6504967b5fd259b76ee5623c5130f0bb49b8a60517e51b9d4a9194abbec52975b5465b327dbc84f28141b4cfaf6c2841e593d92117b3623b2f320fad37856765cae934644af17d9d2ff5e1a7cad750b77eb113ca09440bb7b75c6013686fa4d95d46cfbf199261350849690e6cdf5f27614df3fbc90f9f71b0dbe0c9c8c6122872a42856da6ab4e9f73e79dd1ce43fdc92ab4b97536f1f92d7bcdacc8a381de1413c43e3df5c99a4daa65d56d42f3630efd66435f976a436068d0815c951b1158fc52f6d765cfac8fcb310d5f3501ce625593dafc8c0511f7d759b9c6b3996633a3a7dc824c422b81b1a515b01e2fc12e6ef6fda77e3d35ce450f111b9ea698ddec92f8ca037132cd99ea9f9d828063a18d4f175244b76519110fc28b5352300677ad6bd8b7f38360fa97c63264973595a58f199e3214ae48bb9740743501bfb3acd565c0681020a02c228606a938901c146dfd7649aa54774b3bbc448453c53949867dff02663223a416f29be2faa53a8ebfd6c5273a5bd3b7ba5496d97ac4e91cd3bd0dcb1c1aad73f4822636730bd21f869f23c61b425df558acada441cbcf654b8bfbbdac7b77f3fd169555e787cc4e0be0413969de57649f80975cf6bc12827ea6e989c2a473a240f18b29ac68b8a585cdb7d07bc4dd193adeeae574031daa6d9b5e5cb2d2e269fc2cb4f95698601ba1cea8e971fe8ded7dd44a8a79d7caba221c9edc38b2321ef565dd78c07fc85f01bc45c8af0076f53060f43bd2c572d74b7a2437cc309366e4dc5cb0a9a29f4adf88b50d89489d4f81d1840209a101817601ef1ac330c058dcf7c984c5877bf03e44668836c331f5622a917ca50006e7382a2e833e387bc1b6c39c223fe8e8a6cf737d5b0a4949e4ef757f99d50aae71bae55bfb7f55cfbf1daee59edad0feb2b4a144869622dc9744210f64cc02090c6d78534963386bda883a13169d98ccea713b6433bb93f3ab45a5e1bc9b66f3c49dfb7084ead8d84ec2901d947c897d5f6bcfdc8f9e9d75355be196dd81b1c47c19078e85dae53b8c6f5c095dc4a431e36ce035dd832ea11d79ce85ac10dafa91289e851b3423eeec5d8ab10dc4503c727d9841f0084ab312ee4440c400af9c065149d3800a43007e0b3a671324a7f271a03e4f29d286b63d8852685b340a119402102e0531715a0c4f359319ff708bfa8c07a3ecb35d14074399c2ff994f884a659ea647a02d235eeb995b274c880bc487626e674e2f23c7c66e76994199187e99c5b49cbe534cfa18c4d61c4bcc9f36c486fa8996a3c5110f03123b28ed718c6ff5f0e3a99511fccf013c7cd0769a253abca5477d280c9180d8681242d74abdb2b0187dbab290fd6e58c2502bfb4ff36777ea1ee0cbb0a7e2e2121c1e548a46f3ae611aaf4649fe06be22719f23caee461da4425cd492068f7b765bee336f3c5c5db1049cabee4bfbc6f90bddeeca8a36a779bed25b38eec7419d44cb98d832b43ef2265c25f727ed702d686eb5d25c7ed0172d9cbc77dd0531dd8535a1f300e624d2b5d2201791e44815b665ffdfc59599bda92cffaed7b14d1ef952a2e703d24812e6cf2a9cc10399a4a5a95f791f691826f0c73dd48b0c50543e2d972c0b9a242c8339259742101737475cf90f3d9d2eb6e5215da06fd17a1099953be319047a307ee899062a7d54d788a92b334aad1d0e58c0ee65462c788d4f99ff2628f86bf2b631074b6c78cdb76bab8d3f9e53d0dc354d5ad2cfde9761fde2f5282f7b4b1247d305fe3fff5bce2db79db039454b21cf64f1ba146978c82b9996701c85ccab3610c38dbbfa3191fb9f16c6c5f5bf250b33c9dc33f2e26e72d2acd52e65e326270bda70b482e3685c7359b721d910e02395c192f431447ab64243877928e083eac367ebcfcbf56c0728b772826f33a6571a02c709f733e4570c136ab65e5d87b542657d6394e7af3726edbb30bfc5a18bccfb4fd6e5f9b953e431d010c7f2bcb664d7e2ac4e572cbc698a2c8f308b1912b01431a27f46808cb3d099dc67d4b1f7c384fe3a79484ef5e28bd4aefdb10fbcdb3f3519fdc16fcb2ffb66d569e05e2ec5d5022648428c1658739bd0cfcfb57567277f6a52c6dfe23d7d24ff02cf4edcff76c99c01413cc18278043998e433fdd7b7e9fc9281b3bcefbddba7e1536c3fc9ef21bb07487b9fe943b7cf73f86fe7d8dbdc3cf7f67ecdc0fc3ba04b78f565ba849ceb72574561b7bf5112f40bc03757c3ac43b6ddd367e34cc8d8f25dc9c80a175955c8a6506b5e1eb56fba2d3bcc1e36ec1f776c26eb77ec2276235dbb9fd582cb2e33d5ecd83646b1b3b3b79bc9c1c6bd5ec2051db53136eaae26610872b51a88f5fed4a8d7c42bbd4a2dbb6286bc25cd0fa8bb5b323fd239a1e58afe8db6c7293fb0c3ecefb6514dc4f777eb6f74edb8f37a3de55db7e446560d3d97576591ff894df8bf0b16fbd75bd086e8171e9e4a13b17a9ef6e77db50595d7bdfd6c523865bfd71bef3535ad96973fa94fd308c9d2ffc514ec4ba9ccfad0dba5d6d7a9b9f59a66467615c3e8eeaf2a29c2f557cf5838dbbfd80cb8aa6c8b6c199df3757ff8f3fa4e7332594652e4569f1ccfeb28aa789ec7f022ca2a912d5d52634bf87fd102d84fe0731c54b14725acaa10a1943994b4700c972896ac6a766fe1032a54ad732f2bcb896509a14c465c85725e2e2d61d2e8b23d6c5624bf1fd4f6fa34e51fbb2bc03d0f383ef9f24d3164c3968cdf08c27a8494fba62102efc262e341742aa0f38312407968183de1495045a1dd21c5dfeff680923ec1c1dcda9a40a994a684654e6a8cdd8b47ce450cdbb120080fb74a4eebf820263f466d34e4f3e5c69e6bff9bbd29f45423a76f0f8e79cf3bc7b68442781b8eea73c91a2ba851c28274d56711cd96531ae4cc468b3adf4ee6e7381ca1bb2a7a0da0bf5e6b2ad6eecb269ecf65af29a872555153bd1b3156fd7a8cdd952d51035abdd091b9994384c7a0f24326fb9706dfd00642cee7aa7d25c04f3cae2956c5de6f3720c2b8664c09aeefeb7bf669c42cee35ef3f79477fbffbdbd2754f64ca48192b2124a0cc901e60781885e8c05860993f05b72e6e67ac27a1cff447df4b130c75cbb25a44d16760141495ffb50a8fab08d7ce2d5dce8f0607ffbebfe69c76d5b53c458c03aefb5ed60818c94237e1b172e2492557c51ec653f6b44b9d7270c004e46214c0b7a5ec7536aac233e79859937c87cae33ebee23ad3633fe92d401cdfdee4ab412b8c14d9ac8a410f23b93576f8538d6fb4cf6eb3a01fbc12661d5d5463b2427a099d024b156c883737bc4507ef4075731d5d04f98d652face386175a991ca90a8bb6ed207e8d535a53c1ed94b84beb8824e9a9e4f18b4e7e30bd7aabe4016cd9400a57b89a59219d220c778994d5f4c914b1ce5029a4c6765aa5ac9f36533cf1280b3bb8399eefeacd170ed28637a89d56397aa7570c01470d09b09650ed6bb0f6f0a68884887610e7ca5d16fca42c4e5122b98f339e0bdcdf76a6a0cd1be0487673f37a98c686ef63863c99c20685e59a579f7708636a9704798257dd724a63114a4f47be923d80ba4a80838df17ff222c9787f95fff655332cd384bbf6bd9540edffe81c1e0c48dac1371024053b4e9d2a388cb13a20ff58b8321492bbdccaaacf6e2ba973f5d8fffbf7e8859656e8e8f6eaae715f4edf1966bcda8b8774d7e169db525ab798b614a401996d4b8656635a2e8e05b991a50395e2cad5a677992ee90e89636b98554fad80c18779eccc2381947d58b469a5b4a70bb4b1693e3c1ba67e916b6e6dd100e52080bc8c60ae0dc3ea2c255d8a6dd605608354dec24594f8eee09a760f20389365cd43d63acffec4fd8b5f9bf7859d1c425bb2d0d76d1e3f520298114be5fec61bbb07927407ba77910c493d78e4568292acd7c5138f32751ddc990bd7ee22945a98b8aaf744c68fbc22ed16c7bc2a96999cb7d688b5385cfb6aae806aca3228108666e52f21db4ed4140a2b03f6aa3c007080b7008a4f6aa63bd555108e6beeda0959321aaded8e74abfa741dd41d525f8541f1170f789685d090b2fc230d4dce79ceb0fb2923f435fbbdd22fff548b12dc93d083458740069d0c975874873022dfa3ab07866d844b45d938ec9107721b572f8da810944885ed2ea156662acce81306e0eb7dc5c24675ce85f294f764a253a9dfbbef1009e7f6a3e45267d5c19872f04f9ae28b31c971efeb7eb40ae6d661c96a11a6bd0051a3c22ac1ba46eaea011605dc5f96aa02bb563c4ab56c0f43c28080ffbb63ebd021d77cf3373083b68fcc016d81d15a7c3ab931e9dc473287914dbde0c2b4b4ccd814b6a7c25412b182756808cec6a749c8d20d6c59b6ab42aff7156711e81d1a335205376f9ee743567e70caeba9356deb4e8a61341a827efed5908e875f694b8564db0d40de0bfa889907f80a200fde2ca232d67240a6ee851834d57dfe3c9127a6ce76efd1d98684148d7e48e068103c52d94819b0f12fbf4c44ad388937efb8535bfb40ce39f07c50bfbeb88c967c063ba37dbd11f797d7fda478151be3f15f1f1183f2029abc6a8ac6b95ae7997fbc00f61ea92b3a61bdc1d8ceb45ec5e9c8dabb014ac9a3fae917f2d11f395e3b6e281044e63da81757dbfe277b7c89d3729bc30a2477aad79677dff6b59d4a13ad9a88125e45b9d1d512d1c1eddcb0c4becfa8e0facf810212098b395f32f7b140fc915c03a75cfc5429a22c7f5c0e107facb0a9f5f66a59bff901f5128193e1c4f494f7a404990d802cca79437378f0931c81650c402b9d88be4a9d6ff3621dcd5cb9aa56ac03e7584f7cb65da1e6698513dd263fee5e6896d49b837cda52dae6c2b4fc57e8ac08f783fb3faeca82ea4a7abddefe713de58c89e1acf7ca899a8a4ae6ebb774e1bc02390a1e65060a15bb81c058d799fd7cad8e2f57cbeb701daddfb1f5190745d212096d4827fbc750365bf81437617c1f4c92f018384676774e5b018aaa413764b1fd01fdd7249fed0a962b503cad05a0bdd071ae6079acb583d53aa915b2265a03026d0ef7beffa91b9b0de3ec1f2caace22d0ee31273282ea9d8475b7111a1ce1aa5067861d127ec8d40eb9160e0b5fc81b29b8cc4fe07753a2b3881eda7e05b2b22fd02d816b54bbf3a9e1b9b2e21429506adcfe6cc9b72b8bf5118c41e52ee2c0565873697c0589d38ff5b0c1f262486e72d861b04fea9a50d0949df3433a9442e621d2dd227c224c53c3e92017878c92ea4a31ab0901c998895838714c41f8cfc60030c60fe68370ba8a112e20ed5ba7969e421e9f8a8fba9268635ce8e26fa5835b3bef98a20c4f979d0bbeddd1e2e55f3da9b61921fb4a28290b9d07733e0eea36a2dded144aecd5083f5375742553a5df93fb391224cea175d8517b2e67b20048d48f9cfed6bf81ab7103b2e33af5fc424425b1afefbe252e84ecc505d56870d6fafa7d4a713e1e7cf1b9c02e4e392a68972e39261e00336749169bcdee1e9f54d80dea2928b680061f8d32e18fac05b130da11e316cfff8702705187979d96198f6d797feafc252fca152eb4bb89c4e0411d8d2bfd043e03ec908467be29206fd6426aacc312dc4bb9e33685fd883afa7b784562cd95320fc30c8e48d9c456fb3dcfb669a825b6d3bddf02e678ce5edc243137a384891f7d121b0618cf5c588108f8f5e20f499f0c4c5ae650009c92c7cb67e50441bafc3c357a903b88881c3e507c00409bb57f0455a7554ac269b0e7966b1fe785645b906f5cb199fe1d566d4891dac49768f9b242bb6ab4d11d88016fa1decd6695312b32b8f86ab12d73d4c07d13711cce61c33bdcbde5b23f212cc4425be674540687c361f29976d6dbcf6f86ea54bacfaf45528507c9a2716582b0930dc5f39051817b7c3fb570230006305b68494a1b6a67d3d2bf41e0edc209aec506561bf35e176be05cd37719e6ee72d2beb61c1cb31a78cf9ca7fc97999dcc6851876bfcf6a870e8eb6cd502877e1ccd5ea9fee96910fbbca5c0c4f54ee476dbab9edbdaa194b335a7c0d2c07b296d1613e1220defc122c4b9fb18adea97ce2196bdcb7ec6ccf9150d1ca7ca85df386e28ad0683b94048466e4ea412f7b3fa2b28bc154e42fa2233fd66eb796981bd834f53a519a78f32a74c1786901f59faef66e7feaf94a213f11b11d36a503e6ca6aee71a1f7b0131a63c0c2d8a68bf933cb4030d060048f14d4133415f0217cfc3632d09b5a66125adb6855221ddb319eab1c3a0a48de056d6e58a3be08d72e799ca21892b71c7b49f3e8021eb9e19fbf0e066ad3781e9134d3eec24d2d036de9a859979625cdba9fd2313ed214c2536582e48125c22281546f01be9e749a7895937016fe8dbeeb09248c0e41afd212a6fa9a14e8cc325810f73a737f1045a08eb9f488fc242e09d13a67d819a1a06f878be702c3b1feee375d2188914af53e33631b903caf48de4765139ccd4325b9db3efdba9a1cceb381c9ddcb2aff3bb4fd728148b7620ca43399df6c8bbf23291a1823c7793dc04f533113e0b8990a0ff07b04b42844a0e984956bf751759dfacf9f68a00c726ba526cd4bc244e92111e71c44ebcec5aea19d04188267c96d1c3e6bbb41c19de0e1c3daea81e512978d891acc25edcadb8a1092bc33d6dd9fdfbc2562a50099ffe3b916088a162678afb33f853ee3885945bda7fe11e7002964465b98488ff458822a097c97de8d0d997fa12d7539acfc9ae4646f72ee0e5193db89de555b1b9bded19a664114e24a820a0311a4285018371ad5cdb420a1188b744dd76f599c53c961d25db391f9634ad4ec97415b785f105acd4d36eb9ae98c7dfa3ef56dd7f20a583cd9564fbab334769f0c2dbd1e9cf8978182c9331998804f5d0777e2ae6317fac0f3470977d8ee1ba7355ff01e3dc267df21bc26e055a1890f7d2122e9cb13c3ed5d6a218914e64ca1569f5ab5ea4551961625657ff0cf4d88c9afed07dcdd2380a075934c2bed24d2d1692f43d37d27ce0b07ad8d8d3927b4784c0f67e737300ef85076a80ef807550a0349570a5b5b1c083b47faca66d218498df1a0b4f2d7b3fec2292826a44735c70cee043d19ffbbb027c62203ffc0e81e38d5c54094d250c4ee60892943395b2895691263772c2edb45758e7bdc4a60f0f3e490309a09744cc1309cf79300c46c1ddf98d06200ae17c9f5243271731c5a842e6a0b75323c3a60c06313c43f044d3f082e7398d5ba979351b7df806630690cfa6e3a94ad2bcdd9f58870515d6c965790d8a9a91dadf7de21225b23d8c5132d444e0a7023c72564d9f8bb30671d778bbfc97ba53afc654f431d7a35ef74d50c2982b9445d5b3381ea2f056caf4481786f2e2900ff7f272aabc1f43b2b2dee54f596853b5ecd3a78fefe408f3b094687067e7b2475df1b6f86880bd311e8c9c6e4f2afd50d743e38337b80231208e12ef72ce84c880a44e9b64fb2be2681381fc09d04ab009d4581854cb58d0d515d7261151d8b1e4a3e1eb7cda0f3843019b1a18aa45f4e9ca222ec81604b850f5b2e14095c0356b72f8c3746fdf36f3de3f6a296443bb99aa98d178b94b3da94d476b4cda1d53aa92ca11656ef0d28a9a23a96797e9bcb25d20f135825d7c5c8d37348947c799fde35f52c1dbcac33072dba116f8fdf1056f74b3588ec9d90b49226f6b9b4383e82caa7f4e3d790ec52a4020c7fb36d5ec2be8178347e8df1a9b3212a91bbd042130af8730ec3d7e4a8473a4bbefff78f677eec0f34e816441216c94f6932b906eaf8e271c36aeaefa3a1f59476205d98f576df3ffcfc0ac05618d85de6c3d3dfa4bafea7155d0b12f167d6349c35eb8c3f82ac0c688f0ff62d28524f64627755592a9d008f7ae5362ba8279340fc0185af42e463738eb234cf814e13aba99d16fa1f25d1e3e4a32993cb9bf301b9220e46b0b13a0aaae4dfe95fac25947da1724f17ceb2cc794977c2b9de50d36d2f637cad93d0ab13dc20c6426c7cf414501a91406119a90b3654ad15f2fb0f38b0bb8e857281173b37df407b8cafe4a369a60c9ba333bbf5e92dc06c1eb61a030cbbf826c8c2cf15c0b77e86da220678330e83d14c7c717c81ef1e8104bfb86f3c6b8076c0262abbcd27dc1a3aefb6594f6395625e11425b3b2b53ae959e0dc9b24603d44769885689a91178e050333bb5200eeca5e5bd4b078dcabe31aea3228ef6b441954b801de1f87fdd0794f0f827622c93d71e4b1d346ebfe5824a29ca0ef385a90b6ddad9e0dd840bb3c308206937e9311356e3e2684f61bb4c02d29b0657fbc9f15ef176e124cb5afd75a90a082e92fc2d20f2a6be3779e34dc7fbdedd28d299bbc2909e4b489ad8a82d8b7d6698740b260963bc72eda34ecebca130299c76465ea8ff10ca3ca10d9f131037403591d4ca4811febe7618d76d47cb8fcb6a358cac798b7ac3510fd101df2281548a9a4284f448ed6fc97990c53d86715442b880439560420ccac4d8470f11819d20ee0b4d96190a0603db4b8c35ba37669a93d8cbf58f2acee26880708962800439a7ab1ad0d1f860778e5f5abe8b646fdd7717df8508c8f249ed6c5098f415292ff93b0ff4df59610fb376c725ff79a9010522593ba815f8728d79277bd56524786a08ef8486d2eb74a2559e0b4573dc3de37a412bf82ce93251923c43f7e1bc5cdcf86ced1e297785a26687e8055e46b84cef4fbd028ec40886ed11d2a3e76bc9f5f9d96832ec564f996cbf99cd300eeaf78d6bc015282a3631c2a832b6248cf4147df717ac3dfedcd08ac140142e1de361621559d4a31362efdeed5531a463e73110795b7ec2962a4c78e09c99efef24002f11ce884abb2528d8f0766fb98cb486bca41ff24ee97c1d19469cecc1d7084a0235dc03956e037d5800927d727faede628ad9a21d06440bf3ceb379c09eaaf9009053057ade8ecc278354b8687c4afa91084c34f10d100b5ca3f987fdcdbfe43ce82f08ddf70bb0c93e7958fb35b64828cf6890c91c65779fbd2a31ac24887caa353dc35216ad45a80dea9904866d76150c68e322de20b24028265b93b533e5defc5c08dc523c08e2d1d1392b4bf86de3492aab3f6286029d63fd3a87b8829073ba84561f65381b32018d4471997827db7a965327aada363a41e9a6a6338f42913fff2fccec003cbbb239f6ff27a2bcc54265bf72bb194cb04d2034109140f2e475871ccd97b46b65c737e3fc571f23e30c67615278ea744a9279c085e76f401df679a2dd873cb161d3f8e03529b3f98a3f76d4ca941169c2dad131ae49a90800803b78315d3fdb5210f55547daa4fc60052dcf0a7e2b6e6048240e7b97af2dbce371c56dd4a426747755f7bbd4608bf366a958f87388a50c2704bfe121bffba0a842082a15aeae635a7351026d9949a64173ad2bde8d841f8105261141a7e3d181ff3a4fda8bbef54473f8b702ce0c042d1cf1ac29380c5be30aa2fc73d06782d316ba3e719f9eb6e21b0ccd4210d6097b4e87c952b900823e9f47667e22264ffe3d2e63396cb0d8c69d3fded26a1fdb83100b13f6b5978a051484edc341f226d45b1ea0eee6020a7d7879c8a83a3bf6fab7c5a7c07b06f37f76a07c70caa62bb940a7b29e7f6a8c1085fa56aef85d47dccd81b97f644baa8acca296bb17a0e534d14a118ebb3fe0159120257ddebe73ebbe7bf0626e340f8f138c7da46033c0f6bf9c7da930ddc2f240e42629c453f144e8ddeadcaa22bf5bc24634ff6ad4a7712d0bfb0510311e54e24e884467bea4482647286db9ddc0da403ea6d634c0990b686ab6f38cd2e6935e9d5773a66785598c2805eb55548ff0e74e9091561ff06dd7a1488dc449e64283be281944a3212c76fb60ec87ba6ef7cdba5d1c12163bbb0355d6a3962660eaf692991cc660770fa5e4be6f0e802c1c938e5475ce4ad57d5b74df20297639c2d43763dcfb23e2cb6303fde64622ebd4a3310389eabf74dc40ef726a35c0f9968bef34213666be90147133b21cdbc07743c70f271ff6118c20843b5b98df86ee295ffc2f835ee511a27962e13b2617dd18e506961783ce3878dd938475a27156a805105e35465522fbcf4980ca46867e65b89661a2c04fd5f3714dbf26dfebba443a52472a9f4fa1d02f18f664521d8677c298b60dabfca93d243168be2bec9d3f26b37c98df9f13a9fb84ecdbb59fef13cf12106549df717b6e194d61eb14ab47c7ecb6001dac8702ab2ee2a41028f281169d69b854b8d5d62d8acbb218e5e447e6503ca07b518fcc67c12d43805c0fb66912a78924d1b2684f2ceb08c81f3f80ee53a509e00fbc620004c8e5732f9c021b1776e5871ff40bbf4e0bd9b0b0976974dd94132c96fce9508008245fed13348bd1ba1b44355b9d695403e52982f94ade0589446a5891a939f4c3ed663413f4165d4f2e13418aac314717cec98fef29eecf6740c07fc0e9cd1d4010e567f7ad42bab85afac1083f16326a87d24858b83a8c04e79db2de14f2b520232c9bd67f3d19315fc43b31da9871f9bbd3a51b10ccecb1da9350b906d1658afc643974982d03e662407abe8fcc58aff1f98ee6f433cd9fe1ff7a54dcc5aef691d423d57057a4548691f47b9e2181a8e040b162135b24984f4b2db74ed6b11a465b8c827cd04f25407d0fa576754edaae50b5d878824b93d17c1a3a1b3ec7cf0458b239d90b859ac94ee82a309322fafb6dcfd6e53ee1b0cdaaaad95a0d7b522cb238383de53d5fd6affb4b42ce4b522be9a95a4a47a2b11cfd03674f8c42bf81c88b2b9b72f8df2e5ae8df569b606f23ff325b9894d85403c1555952bfb6ea99efcace0ea28a809e305c19d7d4ec94213d76a49d868c0513f2a2fdea601ce2f808398a0f31b3dcf836e0cbde92bc30e52f0fe2b399f08a88d237a32e357f1a5a41545a859acd2bf63fe4ef3640672fc48c0c0983fb8c780a57bcff5027bd3239562c23218fbfd3f5f9ad2b47e1b0de6653db6efcb01679e130f69ddced6c63c36e02b9676ce358e5f6fb59d8b8818b0cd975aa45e85d33e7eb6cfba6b717bbac1181eedc99f66aa7626b64fd61c6ff05346a40d4866d1471169052b540f3e501abe4eee9eeca7f0c0a8e7cc0f7c701ffd2c1243584399a9b31c9ced98d62c0b6372998ca9b5e731c3eb60c3c0c0049cbb7279ff381ad70e566ed1bccc3893fcd22a6f126778c32501badec5792b50722324035c2c3a7dc5de9c49628bf4380331ded5f6c8d551ed8cddae1db460a869f0a5c4f8d14027a9fb0425fd8637f63e6616487824e8b71971801c065420023defa4db2082594ed2b8d6841f9140f7b8e2c8cfa8797e17ac08f52af88d60b77f2869e793b274e69044ba77fd7c7b2fb4f89c21031d7c32fbbffbaf0d38caf288e1424feb89a590bc1835ec34d0dfd0ce81407f66c26dfa9389764c3eee9930d941dc98ac5f9abfccf0288804f6131d7d1d5213538bee4c557979bfccce647f7b817caf198f0bd5ccbe756faae39dc73be7ef5f746bb1f221b3e7636e3afb3ffebca10ee30bf670445019f8202c0f02ce954340bb2f9bb29d74277fe23b1d7567cc7e2f74dc4d3464307c95ba85c7cbeb824b42ee5e70daf9a29c6c8d67b8b11ea41bea725d88236c258a5e041cb4b8d7caf3e52bd00d3b1bc5e778f2af523d6923a9e0f561a8b6ef0b7a101e5423c86ef00574decb4dfc9a5fa6975875dd02f086562ee4ad73a4df694b54e20c9a7a1865f36c78f04755ff4eb4e952f228cb192766a9adb0769304febd09cc2fc276b850bad34b5e05e6e14c847ded773f05447b85f8db11e44cdc591406416d2614434609438b74eab3c9c212e14ecef0ee03820056c9dd1ac477dbbe44610fff62f70aed2159032f34c0c0403df93a84ea88091df2fcfba21617937aeaf6a7da7394f32ac4021a4741ec48f009b4d84b7f476f5ca77ae4ef6eaeaa1b158a157ebe4931a1733a3d41002433dded2cc08b60835fc7f63d4c1d566cae09c17c9a92a88c85a10596ba61ffa63297fa9f8753e41046ce0ecb9d724263e8b173ee360bfdd3b50024528ddcd97910bd9cc068f05a7898cacd3e9dd13335991b5f17d0797e65741da6139dc0067500c1c2d5ceff8d887092d8d06a5679abc975f9b6a645298e962a65984b24bc8add626473f9cb486f210e88694d0642875a865ee7b0ebb7a96289f4aa4f18514513bc93ac88318c98d70c0259dbc7b5551ca05b156ba26a5a973de3be12bc2064900f3cc31c80e6314c1294571bde91eda27037ca0b0ae08515bde564e1684aea8a64794c3c23103e2c2d7a7f214ddfeb966e8931c89da998e8dc8d09d4b74808760d272904c54f389f7978ae22f7acb6c918e9f1316ebd43adc2ecbf680ad1e8c8fd965e76013e7b91260b4c60f84fb942afcc3bca3a04e6d637cb5f195a01765f704af1510f2d85dd6a8e1924572ce0c035adb8c83f95f33d52bdb5af0c7c4f244a2af2ccaac50687ac09e3c408a5ba59cb1a3335128e5ad5d435796137ef958b1c4442780b1bb8f750c4fc56f3ae12716db0254f278b5443a2cc3ae8faa5449b9b60cefe7de74c021073acca900e934e1ecbaeb385e0ef6b7a242447ebc4c18104f355251a6259b685a0f7c1d2d7be01be6169283f59a6c456e556c31b80c22457c120ec2b96466c8a5b45db7153a73955cfac08870f832911d9b739ee10a76a0b218589cb48d790017089e08916a2b6256756b2bcbe44e7882806c66fd6252f85f3177ab28dc1ac98051b5ede9b74d5b99738f0fa75d90411764ba4a4318a37cd0a4a3635c0dc15d12e6c866e8a2e1121a4d790e665e651a5fd3cdf4e3862f2ecacfba60e44dbc158ace007ccbe73f1e27ca63584c655b603f7c2a66b357c5dd115ed6daffabf2127b7006d729366fa6bab15ed5a6f782886c6841eb231d3eec001dd22a9334e845efc22653ff7bcbada531497a9e1559f334b921f528e8d149e0ba2fd69f4c3d9a4a4dd4fe0582dbe7736103cad0677b99a1d748c57da9ee1dde245799854bba6eaab75ec178bf390c50e2f8a0e15eeb421b94dbcb63c17fa112d2e74704d66dff9eca77914113897555084b7010183e9757b6c451098f3cb0f459d35f3fdb4f25a31a740ca0eaaf55c3f0ac89036d1d12bca099c476d1fd87deb132daf25fcc360f5d57ff12f2e8cbfc894e1a9b28e780f034923fe5ca722a6d110c34fb52a2e388e104e47f3175dd3a36dfe4e2f9e0be4994a3f188829b90aa1a1b443275333ebb9dc34bfc94bbdedae8bd290ba387ee09302ce291189286b0a9613afa2ed0ea9c30aab107e51410c7660f234a1513024e202c1cc309121b3131029b136305456ba771077c4eeebb203fc76cded0937c1ca6fd1db7ae85750a3549c3aaf2dee11c5c1e3e8b3eca76d66c2ca9bcb27ab79e920816dcda425b23b55799e40a03d19bf82de1f084d209eaa6874d09900ec0ce971f3d8e6c8eb937fe3f53b2cdc9cf1d690638e8431f6d0766c0c0ca13f70b799c2120459bb53da7c94826c902132dff606badb0940617a230bef70cac9a294fee1c67054f63a058f80f02829e3ec7ad3f00029cb4dc099a605753ace9a5f96c7ee2e2ca4f38727b0ab23a16636c63fbfb91e004c48cdeafea1f50dc45c3f53005cb9aaec6f6cdeaa8632f8e2528b98ea415f6546cfcc3f902487f6f5e454016c5802e739804cfe7c09aee027a07c1f6226fbe00829cdf85e1994081814d97b1c2eaad41ccab3d95c30cf10803eedf0d7456e659e1fc41c097697e9b0a6ce04cd850dd7871931a5c2fce270c0c897d869e7d5f6ce10563a4583766aafb718972c4a9dcf38f72d366abf44a08449387aba9ae8fef8a8ffcf4d9a1920c25406fde69fcc028323fa7524ee99b994f9decf2601ddf1edc7e534ea7edddf46921e81654124e348e60f3cd8bda92745b68910d59aa76b9d93ca28db242792d0651f7bed894f98d5a6cbba7adff855d16e4d0747d10791be667785c73f50f03f16ffaffeb668b252d6f0de1828a54efaae355723772ff93394a6f0f3be5c6fc5747784602ee0912f64f6a3c8c860f78db188bfacbe147df59b712f6359309e92812238facf38565ce2e158c1a03c1c07a2746bb8582fb93fd99a633c8d89c8b652f5e13af9a5368188c92eb84950f0e20b50e469f699d30630728579a7f15721147ef2311417a07d1f53459d67bdb19ef8ff33b91f6a66aca24934a1f20b0425f265a1250a57dfdf3bae273b0ec9079c677305ab2ea05a92560d97aa52f47f6956e9954863396b90fac2c9685d5e4736f725e65f75627efb3f7fd32cc00e5ed67d5c19a01d14bb7c2b0aac95fd448a28a18ae92a2c65f8ed95e4f2d2cecb4b86807b962fac0de611e3e3eb38999862359b50a0bd9565f8cf0bda9c09a37847f3eafccdab391137f16ab9a40bfd3efcdb74a53904d00a6f6304d90b8aa9484be8439eceeb0eca09ff240281136a611157d0629ddb5b0c280fdbaae3fd717327c9b3254b7d857698d8118155cae5b822a836565519c0a1b05add740791d44adb04c1652b897942ae0b46f30890c53f17457869a839c043fffe0b2411420d8081af388e17489bcddb7d960c8189c78302d5c166165bc134ac6e9cb030142a24f633178123026e25959dc1513afb7249a2db9fc1d59e1777d1cc7e5073e7682a7d93fd32db224dc100d0b472a712f23455f462eeb8429e212dd3d471bce4cf944e0e998b465875c78c3ebcaec8d28d5a040bf63837bd2c15dc51f1866b6d2a60045f1947db1de53f008a3e5be0b9ee760aca87dd35920d4121ec589d582b47031ab0e3cf62fd6250860d8b675fd148acf1540bf7f79de7e1d560c207322d3177b5fd62b1c39d3fda222b81d09b8c45a43beed92f588a798141b29426698c9f28514b1824f6e819575eb3481d88048d2f3d1f2ec86f20c6de9ffa5c1edba9ee04dcdb95acd2946ba0a507c0a2fe288dd6e1fb686940c002239cd9b95e744801bd726fb8149e30fb30c9cbd7d78a9f88cfb3a2aecf586f28d31fe0ab123de16e93a53f47c23b96a3ac49e38e3db95aadaf0ca5c3e49986ebb75930c6f305a8193c612178f214d055cd78c0d0547dd187a433d5aed5b64d960fc8c120b022a0ccfdb7e54b25caffc045d464b59509b73485683658f13088786ee7529ccfceb04e9b5941ae6f4be9931b7c40738c68d00c17aa548ff01bd685881cfa8689bbc06ce9b401c992a0edc4c1bdf781dc7667034202e083fc3b11377000428e37941919bb052b46961effc68a547d05bd498fdcff148a925f2d780231c0da7702be24407a5c988ffab2a13a5cf24337c5c1126e333a815bf4fd673d348ab79ab12385d61615f8969d534575ddebc07c0aa5623e4d4f74577cb57c18c8dd96124505d86538a3b677412f74a2b69f137ddf507335d09352562d89661cdcf2ba0ff3f10a93ff38448028b9e1c416ea0e0cb3b773b2c9a28b22a477b435a2dc4171d27d1c8872b5f6d4586cdc81bc4cf1d77ab517f8f296742d91ee26b8ab0254b482da33792b486f97ac752b6107720ee9a0cab86599736e120b90ba2d61ae3aef74b9c495b0b1d72b9c3680cbf577f4703e068d9acf9a45290228ba9833f4eb3080813a3ecc0482f742f73d3caf173a245d96f5518115420fc73ed3fef12c813b303d36beaf3588262adec78ae6bec2719be047a3d119a18d27942e03c1168826095ee41702add1f5f207952b1877ab2e11ccfde4abd2de5981ed3dd53e8796b703888214c4e34a29d589a6c4551178cd2dfe3c4d514ad8a79c26df247cbae289082ae31f0b07bd67da1997178492656c6dc6d9aa9a4a65d8695b58b670934a65a9cda47f1933cc668a097a7f105ea1dc1efdad47fce4c75d9705fe2336dfdac818bf17b6cc915126099d7a763cd9c789e60b44c27814579f259fdbd16445f23e5d872bfe4fbe7c6d3813705327d163a1f4440807487f6955493b504648186d4ec7b7f3252cbedec9f1eff78ff951a33f1e2b1075a93fa3864c553cb5d8f90793eb3663a8bef8f5f9bba5491a7adfd53ebaf49c90200dc1dc86a90c39484e89f19479a6d499a1065932bf8e096203d7216a1a6a2651e87ef1c2ddf232e85360d8721e1f0fcffc2893bfc4f9179f2cb23592ac83c74b9594cee74bdbf6f8c1257a1f8abab845196cb1ad275aa78ff578b1a9b2bd13ed5f9d05b237317114708c99fad3395964af3f885af69f150f7e1612d233ae06dd3252dd092bf850b6fad9bcc75f0c723f465c53d66f78306a6809eeb1e1772ee92ed258a773f7d9487734e85d0a972f4d77cee1bb351f516c5b8918c63f6624487926e56b40ebb3681635834853b67fffc22f7707c63ed7c21405013ca68f9ccdc4982bd4f0f3fd803dcd3dc05662ee643037e4a911796a6d56e8074eb62a2c19d3516bd0986df8ea1f2868111dfe5bf374181914eae6f77a7bd71487fee04f101d0a40889bb09e4e0eaae9ab60dcd68158fbaaa4e26d3f519f045d7ba1574c4ebeabe2b4f26f40434fc8cac4cbb2dc9e9e4d656f632649d44b59f4b22090bb5f3ce2950bfc3de000f3230a182869dcbb92aaba55dfff1652c7453acaf92ccb2a9e82998ba0593d44cceaf2da6490e96a4e8001ef80c584cf96d10e989ee9a4a2280598c19853edfdba568316a9a7baf41714159dbe3e266b7e49c795d0abd38bd3a40eebb724e9f21578eb0749251c768788837ee197f1ba6498407bf8575673a6efa3ac1f09690af0ac31e843e1c04d1357ccd8fdb30fae19c01395c951f470cfd4d462997d0876e4ddf8561d39083f0e10744032e77b05ede31d8d4313be125c1c0c9e3dfb1fe609b91f5549792da70d80d347825a859cee8ec7dab1d22d59bf97ea0842c66dea583253fafe08d5e86629d2352f6eb678eb61a4d5d65bfee16be4a5c27f8919341fb39f00fb612d26d112817fa9d5f5d26dd2962ece8df33bbefc683391bcbf3bf6c7dd9500c43562c6c0afca4d4a4957059a2a24c0edf030059846a0fec07e44a5e2be3d6310f5c7f03813422d79240b5b826251582fddd2e4db8b544401ec923862634548120ed7244925289fe82773b43ab2ddba46de1bc33b7d5624be8571e4232dbf0fc33e30bd3ca5e0bf5f9723260483b54ab76aa2867534caffe9f4132272a77ac3e339a9dd0e50e90d92f477963e05978f3833b1b7ca64e6737624e12253d09f66fd84fd113c3cb62cefe3099decb035a156c82b6d4a4be9f8b5774711721bdf1818452ce757c936a343a6efe098860f31776fbb970447b065ae623ae0acc498542b621de13e6bb1c7b1de7fe8b3cbfe4e302e1d0a49956422ebf3925dbb3740f9b776e083348e600abc33042f6300b46f8be33a3b5366dabaae4ed4469071b7cca60669ce18489bbcc431caf2615f38e8b125053e9ee3e85a5248a8b0b7a8a161a586b2cf96233935a23e9a437ff43f6fb1ebd1523414d52373143f340f64dea42321f9cb988808aa747b1caf9c4eabbefef599b4668326c4e2e6dc023f225f9aa8460f56b82d90b24ab78cae485f3730687981489a899055b0b7190b008d7c87a1f91e8e93b54f1a880497bdd8a3f64db43513a6433ecc931d3b14a3fb9a4b75cd1e2598ba0330a66f7bba530684bd56ee959c09660a5b97236820e741a506ec599ea7583127f28ed2984e2ef77114563bfb35fcb289cea9607bfbfd465a4bfa01d0f263cd59a780d9c5cc8a19f5f70634308d7df9160575cad321bb3b715bfdb91393b1503ba370bd469e3b2c57d75775cd29faa423e05afb1b4eadc554c1baaacfcec45f7d1e387441b6b7a36ad7445477b8aa6f1decc58c9859fb82752314094a1ec0a47671d183f8f109486341da85c1a4f0e41c1f0d0be85d331aac1ccbe4e3074c05b84786be38b8c2d9b9c2b80fe376c882f699b0246908f5d915fa67dc6fc0121651f1f71baf1c4320746b06a0f8bde95ec618c20622afee0df6b8c7191e1750e94fcccaaf95ba350dd5e11fc26237c7845286c0af830ec2c663f68b3af21fbb2148ac1bcb1009e8d8cafddab97cd3f2e5720fda2b87bc6dc18bd7da28d860767d82f057b96f995416581b030a4c8f2cdce63272281e51532bdea248581646e852d5a0a870d26d70fa093322839989d5fd6b54b58c85a48067f8097625af5e0b4ec18b1face6a6508af71335fb4e12b44f5a4b24e048ba4a25462be161fbe7e639eb59f73992d846954eb337d752ed17f0bc5bb89ff8891cb6185aa5af7cd9e8b2b114fcf2b6d0edb8ecd7546fa57e779b436a914d94297cb0a3af3fea7335cef1007e0bf0ae121d70c179b367c12641fa03fe8fe1fe49d77a94c64c3b183d723e08bd01131d1b145ba32903ef4a979d5239892e79977a16022791bb9b16e67ea5fc76232a0e16eace8c5a850e3df111bfe87fd8779c824359b3ad61e97f082d88704947c9cd3eea3cd896791254923acbad1470f3f84109a5b6d93b2cc41db4113c0dfa11e1b11ce96fdc7181561ccdecc120d2049dd302e7f48f92661a0257b9b2241d1a298254f0c5c426656f9024d837f7c49149289cdafc15d106fb7c701e2b6505bf95bbf605a2754581cf32c2eeae72ea36b3024ad4bf3fc434f8050330a98b6ede57e405d6e6bc936e210d03b0975ecffa68e9583494fba730107b0d15f79dfaab6084cb8dea5f86b0a0e637eeafc8468d9be853dd8fb430b4ad75128c0a333a4468680024301004d56a35187650e90a073d27d98e1153a00b71d13ecf0d4f0efbf2c6c1836eeaa484230a4a21564e0d5dd25f0b2833fe7163cba747c1dfd3805f80bc1a9b82a67aea243a11a2cf88e68b8d8b0fd269ef8670fb462464a4f7a9d815996b140d80ec6727089f6eaa754611e9d25b2c34235c04ada731972e11c04cd9aced8c8d3478980957d951aec3f08c300dc44487af97b6ef7097f76d356671ff31f01fa9cdbebd402c10e55d711de55715d5523a8f6471e88f29564b986e5c47cffe7a510f8e9b5cf662d26e6d4123bd0e96fea86493b077dcd047a2e20f229dca8ec31844aab30b79708303bd3a2d389c6af12aeb21c56a72b1ad8e7cf0865c3cd679b6c19f059f5d16182d3d469619754ae6166f51cad052bf886222622ee23f3a2d1200cd57fdee1b90116dd2fd87e473c508728919460bcbc66e04b80dbc6eac43c99c697a6517bae8b9cf417885c67ad48adc472cfec6844b7244e45927b68596189e2a61f25f5519cea8dab8a54ba3c7f5474ba8782bb83843de8fa8439ed3183b16d5c42c1c0ab592392f9411b59f502f1924652da2322b724aaba12e1507b977aee53ee170c9e7e04fe59ce9990850ba0eb2fbf48dd3c36be2b7ba7b01b820dfee39fa3294b8d3824f7e90a97eec3e0eb47cf4c2f750e1c0ba880426a14732cd5c048b5348be240ffbb354478ce3090f68891a6a6493612d4e692df6b96720defccba4ddb67cfe7083d912beacce849d178358694ed73779ec5f71688449b931f95e9b0868ba33d785fa23f704fd3982430a3416e24db32dc61bd435e5b2340907470f5f31e0918791062e718164df8f713c644b2f5c2ecbcc12d0e72a5e7ef085451a6c7d74192e2640a09e6bef215c66f6c61df82dcd224517b641e858ba430ee84f203d0f3b98784d59347c6ff01a83c062002967865349361f900884c4358d78f964932b9ff5622a872d7ce2a5683528d73da16bc3cc94fff5baed02c96b2a046e4eb3c6204a82ea50106d273009acc1a1392a5da0a445accc4ed3a2f2873f98ca1b9f35dab46043fdd9a5fc880c00f2eea7991a5f784d33e22c37acc6dbe30f5228e5e551939badc253b47d9ca419a8bac21d419cc1c09b3f46ebc92940d66bb0be925cc0e85ca851507b25448735290b9987954cacae4f3bea1b285bbf4e413e1d5513e37c56fcfca6c2dad0fb3ddac87dc5818c08534fa8468b30080470d9f1c5a8b1b60dc46ef19f486de3de328b2f062aadb3606cd795fb5db967e4884c9a1dec06a886a030f64a862a829a7fbffc1cdd33c5e9c4bf30a7ddc2c55cfd944ea42794e19e391e4798ecc6fdf4f56ddaffd2179ed838cb1c809a2705f1f13c5a386c6bbafead046c15b97bb6c3788ec5a6e87ef3421e38ec988ad3621797385a3e961422e4c46489bbc0431bb15d69aab4110f6bcc07a80111e41207d9bddba006538db946b300b807cda8aacae812b01fabf31dd38cea1c9de3205a3df6bbac7713a71832c5b7b0243225d4e18c99b810c4b03426e09e5554744f87252335c31d2082256251a1cd31ce4376d46765300a94be9dd88957e601e3dcf1754a3fc7f2c0716059db8f4e89846b4bdc8489a5f9e04e114deaa4f20e87e7ac70b98570ce4d1712d3644c86294d54b9c4d5c55c55ffcfa0314d6f49552ef25707e9874855aebab0388ceb4738ec1769705d4e9cac2c16924ca2d21c92ccec9c326caca8d09169e61ee8ac99b4a76af1212f71d6d51e5adda90d206fa35a4a449088fe43e0de1fe21c6cf8c753e39e971cc4c2d9fee0fb4576d35d8bcf3d7f8ea44db8f8f14df04204b8e4bfc6109cf78749b55505081b5492a46a20692a8474d2c985f9bbb099d0998fb66b817b965f21ee5f7d7380c7d314d14eebfb1482473235c9331f45aac9b9f9a891f177281963f59ed77ead9788bfb1b0fc97aeef0e2577c5cbc520c49a3a29ccc4d1d6cfa75183d5ebe6fd8711157800c685aa8f5f2239847b26543229f3ba8d54866f18cc51bccd55de441c69cb1264c757b8f2c68e6a681fa3e3fbe94ab61679a07611343c769000098e01a70c468981f578fb835f6140064a587c406f7330c0d5ab8597bc21f3893fbb5878415acc10bb4c00e8424edd9639bccb3c841fa379438dd4f9f4b98e5c4af469ffba9c786d9c8144dc42d4acfbd7b653b37be3e187b63e8f526c1c8e4a13e12007a144147b67eada993abe811309faf6ef651f1b0d282572e0a68be10fb51b962df3f321c6682799c7a3d30eec07bf46aaeeefeb0cbe8c1d4acf601ff5265dd7d83b76a80ec59f2575a8a5f9f83e4b3742850109d890f5d3ae65397a4e0513b49a2c5a48c3c4ec18bf62c586dde91e0317acd924fe954bf125194915689d5cbd2c71b34365fa1e747d00749644385698b3d9ffe7893955bac421e98b6b60877fd68854f9607178b963991a3f9f233de06709b00c90db09ddb3555420e6510bf51452401ce1bb009dba809ad74d000d0c2b02e50707a2b7ff3388945c28f6d7e6f5ff631d8a03054e5e70e61f02572632040e2efee62d008c69abbec064ac6efe395b05d4d13982629cabb92fbbd3053fe0a25abedc65f71f459a797a41413c456fb573b679806ab39f875c1f4ddeddcfdc7f85eab36cdaa2f51eff9878f79118d9d1f573867f6579cdc18bc97d5507cd6e4a7f169af6a8e6239dcb009251e2979ba78764db3214f95612d7d5c98cfedd871d5ffd6aa66bfd800ab5ee6569d17fc5fe0c0caa96039dfebc5444f319d9d790a5d4e89ef6998007c734667496d2850224c5c885cfbe6c0f0418f42cc6ac87a7c895f40e15c5abb195333e87e172db45eefc7e97b1193d92eec412341ad9c58cf90c6d76b5e20dd0f8524a5ded37398db212b6f51771c3b945174f74b5e1e4f157b35ead64fd1c8b5c4137bfe4c3816fe6ab1328019014c9285b394dedaddac96fde39a9a0db1a0f2364ac7188daaf719ee871dc3b84219de9cabbf510da4723801e28b99476572c3e2ef764df99a10bb4a28f99319e7c2f7c02b0336ac3333f05950b4e3a792f63ad8ff5656872419acebf4f2314975168460564e67a02fb77e3dd5796c1ee39488981d1824364737a41766a0fb3aa97f19d1f4bbfe8f3e1f11701ae2d9c8cabd7538f7d75eff4700cc2bf352b8cd094f5f1221abc6139e5728fc7f2c7c457bdaf20c929c439f0f1bd518b3508c9260ebd8331a69c2504dd249ab94d37464b59b69dd169c3573af7bb4056c9fffbbd8a75b368e19c7fcd8c62ba4c240d73aa23845f5acf4c5c7382bdc35a97bb52575186ed2bcf16640846eeb1f5f53c5ef00992db874ccd8177bc9ba0bb8225e0765a05203b5d87d51efb7bf0ecba0615d2ae55eb5a5f63ae7f671c5af270b914198248a7f3ea35c622abd2e26170a2d253d9b29ef74b39837f43d818cf79a35f52c980c4c4940b55701c41279593d2cd3bca32c640fc1c1c4ed43830aa6e3c31f870612ead23c8f20794593d55814c627c11484ce1ff44266123f2dfa070ccb5b33dd4885f383cf7e388cd832bbc0a696f65d0aa4403b97d59c258b97b7a995a7fb5bad0eeafb851ca15c3bc2c5e73896959d490b41a765aa7b5a151b02e9410f2244fbe25648310b7cde3d7c807c6d58ac9bfb1cb04c6fba7fecfe4ffc19c99f7b5d958426c0b328b12db6fa21ba6a62455d72ea743b7797185003f13dda3b34ed63ce9a7a8c79a9dbb699b3b9765a53397c90ea18758fa8829b4dc7463e22777da883545856f8e3d44036fde6f65f2d053dad0f44aa2d90c65c34baae35cc554557c0918922d18dde0a8f07eff90a820e69bd71c4d331b32d48863ac04e00e5b1845f7907fb367013f956f76f13cc329bbcb38bf99e3bd5b6301f9d97ff5f121c20948d3349d4d533b1b84a039a8b9ba62eedae474dfb40bfe536ffa5ef222c87dafb1e1ac0b38c56967af9f38f6088084c42bf498143e37f4662915648c1699287ceb9ee4ad1e6bbd11eac68f54f7ec29e4c20e6db4d9af4006cb4f2bacf65b8a58bd0cb5b87e7fcfffef66b94cc8018a2052d78a6f979368d0c87c68ebe894572774bd5c7d68f573906d18e6404973d2d22b07a9436350e384d30b72854baabd7aac92d603d4c27c5abe04c22e348a4aac2894a179e661eee9bd6ef4a3e254c076d73c624590c622fe72abe923167a464f16d6ae37fbba0356968f99e8a8250ea2fee46516495b23f5d37e3e5da46028a3916fbd8f0aaf1f3936aaef99f4b9a8044180720ff33bda8a332b54564b08e5f7e71d1f7cefe8935e309c36a547ac8680600000000000000000000000000000000000000000000000000000000000000","nonce":"0xba9ea","to":"0x1c479675ad559dc151f6ec7ed3fbf8cee79582b6","transactionIndex":"0x64","value":"0x0","type":"0x2","accessList":[{"address":"0x1c479675ad559dc151f6ec7ed3fbf8cee79582b6","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x000000000000000000000000000000000000000000000000000000000000000a","0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103","0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","0xa10aa54071443520884ed767b0684edf43acec528b7da83ab38ce60126562660"]},{"address":"0x8315177ab297ba92a06054ce80a67ed4dbd7ed3a","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000006","0x0000000000000000000000000000000000000000000000000000000000000007","0x0000000000000000000000000000000000000000000000000000000000000009","0x000000000000000000000000000000000000000000000000000000000000000a","0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103","0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","0xa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8743387f","0xa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a87433880","0xf652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f37989722"]},{"address":"0xe64a54e2533fd126c2e452c5fab544d80e2e4eb5","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000004","0x0000000000000000000000000000000000000000000000000000000000000005","0xe85fd79f89ff278fc57d40aecb7947873df9f0beac531c8f71a98f630e1eab62","0x7686888b19bb7b75e46bb1aa328b65150743f4899443d722f0adf8e252ccda41"]}],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xeb274f91285d5f6874badd7c51878784e3c0586798896f30f26af1f633219ae1","s":"0x43557a2c82302adddbd491c73a293bbde370e3e66b6d67a7ddcd4cb3fe65f2c8"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xa299c04eb002e667bcb6c0d38a024eb5022a5e1a","gas":"0x15e3c","gasPrice":"0xba26429d","maxPriorityFeePerGas":"0x2faf080","maxFeePerGas":"0xd89ba007","hash":"0xc26a4f6931c0c5d006e6ca77b27422916056412c79eeaf3b05b978cadc70422c","input":"0xa9059cbb0000000000000000000000009a3141c36c5ed73a2d160622fe184245bc5965120000000000000000000000000000000000000000000000000000000003846bd0","nonce":"0x3e8c2","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x65","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x3746f91beb920c603f9f9c2140dbd52ddfc627ecdb5d3a203cccb38e9142f666","s":"0x2a81842987dbe4a1b8f526aa4651c7ad25ff1e0d0027d595ae0685c010154a30"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xa299c04eb002e667bcb6c0d38a024eb5022a5e1a","gas":"0x15e3c","gasPrice":"0xba26429d","maxPriorityFeePerGas":"0x2faf080","maxFeePerGas":"0xd89ba007","hash":"0x489618f61747dd891a674f052cae970b7f87fb0b6f1e486841eaa45eedb9f161","input":"0xa9059cbb0000000000000000000000001fae1575d953e06fc6465d652575ed96ddbe66520000000000000000000000000000000000000000000000000000000008038b50","nonce":"0x3e8c3","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x66","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x6bdac009a612aae9b8ad62ee6d73cc181bcd8632639178319d8f8ca1197d4d50","s":"0x6468debbf6b0bc059fae8c0fb8ca1a5730e97a8e53882447fdc287f74cfe7a4"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xa299c04eb002e667bcb6c0d38a024eb5022a5e1a","gas":"0xfe3f","gasPrice":"0xba26429d","maxPriorityFeePerGas":"0x2faf080","maxFeePerGas":"0xd89ba007","hash":"0x4aed2c917f226e68c50d12625f5aebf6c16af4bac7e4c0f8675899fa9dac9d04","input":"0xa9059cbb000000000000000000000000499c61d40de8389fbdfb54904745421c2eb919ca0000000000000000000000000000000000000000000000000000000017ce0e20","nonce":"0x3e8c4","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x67","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xcdd3342c2fe120694d6cfffa149ee9716660cfd9d04287697537da381622efd0","s":"0x5db8ab91bbc44edee767dfd8f4ec1c4975ed4235e265429028c71270243818d6"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xa299c04eb002e667bcb6c0d38a024eb5022a5e1a","gas":"0x15e3c","gasPrice":"0xba26429d","maxPriorityFeePerGas":"0x2faf080","maxFeePerGas":"0xd89ba007","hash":"0x379a9c5e64fb1979131aaafca9de68b063b3d6c614a99e226062adb70b757fe4","input":"0xa9059cbb000000000000000000000000182675f8c2ebf7972442090c78b951519af89fc5000000000000000000000000000000000000000000000000000000000455c590","nonce":"0x3e8c5","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x68","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0xc26455335a765a02679c077683c941a5750ae04ec414f13c5b48082e7df27007","s":"0x220b2afa327347521e8c209263eb14e5395fb908e6116a498b6973c105a843bc"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0xa299c04eb002e667bcb6c0d38a024eb5022a5e1a","gas":"0x15e3c","gasPrice":"0xba26429d","maxPriorityFeePerGas":"0x2faf080","maxFeePerGas":"0xd6f474fd","hash":"0xc06fa64671bf1a6cc07802148fd85a9df2fd89d6eeebd94d8300064b9fb1b722","input":"0xa9059cbb000000000000000000000000641bf8a7c68d0b5aea80f1fe82f9dec2dda289ba0000000000000000000000000000000000000000000000000000000006e263e0","nonce":"0x3e8c6","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x69","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0xcb3b58668ec53c7d6cc127ec2d014a3566d8eaae922e5b5a1d4df86dc24e5d9e","s":"0x6d09a71091c861de5cdabefad4b377da6992b6357d22671337782d83bf094948"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x550e97871bb1f83f33065b3b18578a004e6462dc","gas":"0xdd66","gasPrice":"0xb8127dd2","maxPriorityFeePerGas":"0xe72bb5","maxFeePerGas":"0xe4c5c4cd","hash":"0x361aaa7aec23b1cc3d3d4c3b33dee5a69c9e87878997a7d937eb347f6e4e361c","input":"0x095ea7b3000000000000000000000000111111125421ca6dc452d289314280a0f8842a65ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","nonce":"0x4","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","transactionIndex":"0x6a","value":"0x0","type":"0x2","accessList":[],"chainId":"0x1","v":"0x1","yParity":"0x1","r":"0x110cf70b0d786850e705c333ce53cb15eb28277c9d1dfc8682b7cbc86dff8f16","s":"0x46f5ee11e56eb78a9c106066b8ad6513e9994edc31d94ccfa3c6bdf17a9ea93b"},{"blockHash":"0x2ee47c8f40f6bb105548656ddc9a1d2b7b07340f3988b94dd235139ad6dca569","blockNumber":"0x14d1d15","from":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5","gas":"0x523f","gasPrice":"0xb72b521d","maxPriorityFeePerGas":"0x0","maxFeePerGas":"0xb72b521d","hash":"0x70239d6e523dd9864a32f746aa634bce7dd163f1f0ee71eecd2b34f6543e8d22","input":"0x","nonce":"0x1ec674","to":"0xd4e96ef8eee8678dbff4d535e033ed1a4f7605b7","transactionIndex":"0x6b","value":"0x9ac85d6fb6d239","type":"0x2","accessList":[],"chainId":"0x1","v":"0x0","yParity":"0x0","r":"0x6049a7f624460d5b9083a600e13ef667ca45bf3e5a3c75a416637f0ed4c353c4","s":"0x7cc8062ebc48934a15572b0d5ea472b85f69a91265130ef9ec3afee8835826ee"}],"transactionsRoot":"0x9b03e875d283c1ca188934bef9b520d21824b45c285d89a815c3c3fa57be7bb2","uncles":[],"withdrawals":[{"index":"0x492e43b","validatorIndex":"0x17260a","address":"0xa5612fc74d33d53845047891a4197668118d1aa7","amount":"0x11c7a54"},{"index":"0x492e43c","validatorIndex":"0x17260b","address":"0xa5612fc74d33d53845047891a4197668118d1aa7","amount":"0x11cb636"},{"index":"0x492e43d","validatorIndex":"0x17260c","address":"0xa5612fc74d33d53845047891a4197668118d1aa7","amount":"0x11ed68e"},{"index":"0x492e43e","validatorIndex":"0x17260d","address":"0xa5612fc74d33d53845047891a4197668118d1aa7","amount":"0x11e4bfe"},{"index":"0x492e43f","validatorIndex":"0x17260e","address":"0xa5612fc74d33d53845047891a4197668118d1aa7","amount":"0x11d9e79"},{"index":"0x492e440","validatorIndex":"0x17260f","address":"0xa5612fc74d33d53845047891a4197668118d1aa7","amount":"0x11d3afa"},{"index":"0x492e441","validatorIndex":"0x172610","address":"0xa5612fc74d33d53845047891a4197668118d1aa7","amount":"0x11de755"},{"index":"0x492e442","validatorIndex":"0x172611","address":"0xa5612fc74d33d53845047891a4197668118d1aa7","amount":"0x11d202e"},{"index":"0x492e443","validatorIndex":"0x172612","address":"0xa5612fc74d33d53845047891a4197668118d1aa7","amount":"0x11d15a5"},{"index":"0x492e444","validatorIndex":"0x172613","address":"0xa5612fc74d33d53845047891a4197668118d1aa7","amount":"0x11d13fc"},{"index":"0x492e445","validatorIndex":"0x172614","address":"0xa5612fc74d33d53845047891a4197668118d1aa7","amount":"0x11d5be1"},{"index":"0x492e446","validatorIndex":"0x172615","address":"0x3832ebddb789ad30ebdb4fc5e8c1857cae6e3bf9","amount":"0x1231fb7"},{"index":"0x492e447","validatorIndex":"0x172616","address":"0x3832ebddb789ad30ebdb4fc5e8c1857cae6e3bf9","amount":"0x1241d0d"},{"index":"0x492e448","validatorIndex":"0x172617","address":"0x3832ebddb789ad30ebdb4fc5e8c1857cae6e3bf9","amount":"0x3dafe62"},{"index":"0x492e449","validatorIndex":"0x172618","address":"0x3832ebddb789ad30ebdb4fc5e8c1857cae6e3bf9","amount":"0x12448b5"},{"index":"0x492e44a","validatorIndex":"0x172619","address":"0x3832ebddb789ad30ebdb4fc5e8c1857cae6e3bf9","amount":"0x1238402"}],"withdrawalsRoot":"0xc60fe14c256d73c6c705b2adcb63d40c122397fac1e54b1895598d42b236ffb7"} \ No newline at end of file diff --git a/services/stats.go b/services/stats.go index 6c0b30092b..51012fe05d 100644 --- a/services/stats.go +++ b/services/stats.go @@ -75,12 +75,23 @@ func calculateStats() (*types.Stats, error) { stats.ActiveValidatorCount = &activeValidatorCount - pendingValidatorCount, err := db.GetPendingValidatorCount() + activeValidatorEbEth, err := db.GetTotalEligibleEther() if err != nil { - logger.WithError(err).Error("error getting pending validator count") + logger.WithError(err).Error("error getting active validator eligible eth") } - stats.PendingValidatorCount = &pendingValidatorCount + stats.ActiveValidatorEbEth = &activeValidatorEbEth + + epoch := LatestEpoch() + + if !utils.ElectraHasHappened(epoch) { + pendingValidatorCount, err := db.GetPendingValidatorCount() + if err != nil { + logger.WithError(err).Error("error getting pending validator count") + } + + stats.PendingValidatorCount = &pendingValidatorCount + } validatorChurnLimit, err := getValidatorChurnLimit(activeValidatorCount) if err != nil { @@ -89,7 +100,6 @@ func calculateStats() (*types.Stats, error) { stats.ValidatorChurnLimit = &validatorChurnLimit - epoch := LatestEpoch() validatorActivationChurnLimit, err := getValidatorActivationChurnLimit(activeValidatorCount, epoch) if err != nil { logger.WithError(err).Error("error getting total validator churn limit") diff --git a/static/css/dashboard.css b/static/css/dashboard.css deleted file mode 100644 index dbc117f1e6..0000000000 --- a/static/css/dashboard.css +++ /dev/null @@ -1,535 +0,0 @@ -.multiselect-container { - max-width: 650px; - margin-left: auto; - margin-right: auto; - display: block; - float: none; - width: 90%; -} - -.multiselect-border { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - border-radius: 4px; - display: flex; - position: relative; - background-color: var(--bg-color-secondary); - border: 1.5px solid var(--light); - box-shadow: 0 2px 3px rgba(0, 0, 0, 0.06); -} - -.multiselect-border:hover, -.multiselect-border.focused { - box-shadow: 0 0 6px 0 var(--shadow-light); -} - -ul.multiselect { - z-index: 99; - display: flex; - justify-content: flex-start; - align-items: center; - align-content: center; - flex: 1; - flex-wrap: wrap; - list-style-type: none; - margin: 0 auto; - margin-block-start: 0; - margin-block-end: 0; - margin-inline-start: 0; - margin-inline-end: 0; - padding-inline-start: 0; - background-color: var(--bg-color-secondary); - color: var(--body-color); - padding: 0.5rem 0.75rem; -} - -ul.multiselect li.item { - display: flex; - justify-content: space-between; - align-items: center; - margin: 3px; - list-style: none; - float: left; - border: 1px solid #666666; - border-radius: 5px; - padding: 0 5px; - color: black; - background-color: lightblue; - flex: 0 0 5.8rem; -} - -ul.multiselect li.item[data-state="active_online"], -ul.multiselect li.item[data-state="slashing_online"], -ul.multiselect li.item[data-state="exiting_online"] { - background-color: lightgreen; -} -ul.multiselect li.item[data-state="active_offline"], -ul.multiselect li.item[data-state="slashing_offline"], -ul.multiselect li.item[data-state="exiting_offline"] { - background-color: lightcoral; -} -ul.multiselect li.item[data-state="pending"] { - background-color: lightyellow; -} -ul.multiselect li.item[data-state="exited"], -ul.multiselect li.item[data-state="slashed"] { - background-color: lightgray; -} - -ul.multiselect li.item .remove-validator { - cursor: pointer; -} - -ul.multiselect li.item .remove-validator:hover { - filter: drop-shadow(1px 2px 3px #888) invert(0.2); -} - -ul.multiselect li.input { - flex: 1 200px; - list-style: none; - float: left; - margin: 0; - height: inherit; - width: inherit; - border-radius: 0; -} - -ul.multiselect li.input span { - width: 100%; - border: 1px solid var(--bg-color-secondary); - border-radius: 5px; - background: var(--bg-color-secondary); -} - -ul.multiselect li.input input { - color: var(--font-color); - -webkit-appearance: none; - -moz-appearance: none; - -ms-appearance: none; - -o-appearance: none; - appearance: none; - -webkit-tap-highlight-color: var(--shadow-light); - font-size: 1.1em; - font-weight: normal; - background: none; - outline: none; - border: none; - z-index: 1; - position: relative; - width: 100%; -} - -ul.multiselect input:focus { - outline: none; -} - -#validators-table-holder, -#pending-validators-table-holder, -#active-validators-table-holder, -#ejected-validators-table-holder, -#offline-validators-table-holder { - display: none; -} - -#chart-holder { - display: none; -} - -#stats { - display: none; - flex-wrap: wrap; -} -.stats-box { - flex: 1; - margin: 10px; - padding: 10px; - box-shadow: 0 2px 8px 0 var(--shadow-light); - border-radius: 10px; -} -.stats-box > .stats-box-header { - font-size: 12px; -} -.stats-box > .stats-box-body { - font-size: 24px; -} - -@media (max-width: 1300px) { - .stats-box > .stats-box-header { - font-size: 12px; - } - .stats-box > .stats-box-body { - font-size: 18px; - } -} - -.dashboard-container { - margin: 2rem 0; -} - -.dashboard-container > div { - margin: 0.5rem auto; -} - -.dashboard-header { - display: flex; - justify-content: space-between; - -webkit-box-pack: justify; - flex-wrap: wrap; -} - -.dashboard-header-content { - display: flex; - justify-content: center; - -webkit-box-pack: center; -} - -.dashboard-header-content > div { - margin: 0 1rem; -} - -.dashboard-title { - display: flex; -} - -@media screen and (max-width: 818px) { - .dashboard-container { - margin: 1rem 0; - } - - .dashboard-title-value > .title { - display: none; - } - - .dashboard-header { - justify-content: center; - margin-left: -2rem; - margin-bottom: -1rem; - } - - .brand { - display: flex; - align-items: center; - margin-bottom: 1rem; - margin-left: -1rem; - } - - .dashboard-header-content { - justify-content: space-around; - flex-wrap: wrap; - } - - .dashboard-header-content > div { - margin: 0.5rem; - } - - .dashboard-container > div { - margin: 0.5rem auto; - } -} - -.dashboard-header-content .title, -.dashboard-title-value .title { - color: var(--gray); - font-weight: 400; - font-size: 0.9rem; -} -.dashboard-header-content .stat, -.dashboard-title-value .stat { - font-weight: 500; - font-size: 1.1rem; -} - -.dashboard-header-content .stat::after { - font-size: 0.8rem; - font-weight: 400; - color: var(--gray); -} - -.dashboard-cards { - display: flex; - justify-content: space-around; -} - -.dashboard-charts { - display: flex; - justify-content: space-around; - flex-wrap: wrap; -} - -.dashboard-charts > div { - flex: 1 600px; - margin: 0.5rem; -} - -.dashboard-charts .chart { - display: flex; - justify-content: center; -} - -.dashboard-table { - display: flex; - justify-content: center; - -webkit-box-pack: center; -} - -.increased:before { - content: "\2191"; - color: var(--success); - padding: 0 0.1rem; - font-weight: 500; -} - -.decreased:before { - content: "\2193"; - color: var(--danger); - padding: 0 0.1rem; - font-weight: 500; -} - -.dashboard-container .tt-menu { - margin-top: 4px; - background-color: var(--banner-background); - border: 1px solid var(--banner-background); - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - box-shadow: 0 1px 25px var(--banner-background); - overflow-y: auto; - max-height: 300px; - cursor: pointer; - width: 100%; - color: var(--banner-text); -} - -.dashboard-container .tt-query { - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.dashboard-container .tt-suggestion { - line-height: 34px; - margin: 0; - padding: 0 1rem; -} - -.dashboard-container .tt-suggestion.tt-cursor, -.dashboard-container .tt-suggestion:hover { - color: #fff; - background-color: #007bff; -} - -.dashboard-container .tt-dataset h3 { - margin-bottom: 0; - padding: 0.5rem 1rem; - filter: contrast(0.8); -} - -.dashboard-container .typeahead { - border: none; - border-radius: 0.25rem; - background-clip: padding-box; - font-size: 1rem; - font-weight: 400; - line-height: 1.5; - width: 100%; - padding: 0.375rem 0.75rem; - background: #3e4461; - transition: width, opacity 5s linear; - border: none; - color: var(--banner-text) !important; - padding-right: 2rem; -} - -.dashboard-container .search-container form > span { - width: 100%; - display: inline-flex !important; - justify-content: flex-end; -} - -.dashboard-container .typeahead:focus { - background: #3e4461; - border: none; - outline: none; - transition: width 0.4s ease-in; - width: 100%; -} - -@media (max-width: 960px) { - .dashboard-container .typeahead { - width: 100%; - } - .dashboard-container .search-container { - flex: 999 999 auto; - } -} - -.zoomanim { - animation: zoom 1s 1; -} - -#selected-validators-input-button-val { - position: absolute; - visibility: hidden; -} - -.goinboxanim { - visibility: visible; - animation: goInBox 1s 1; -} - -.gooutboxanim { - visibility: visible; - animation: goOutBox 1s 1; -} - -@keyframes zoom { - 0% { - } - 25% { - } - 50% { - font-size: 25px; - z-index: 9999; - } - 100% { - font-size: 15px; - z-index: 9999; - } -} - -@keyframes goInBox { - 0% { - visibility: visible; - font-size: 15px; - z-index: 100; - top: 0%; - } - 75% { - visibility: visible; - font-size: 0px; - z-index: 100; - top: 100%; - } - 100% { - visibility: hidden; - font-size: 0px; - z-index: 100; - top: 100%; - } -} - -@keyframes goOutBox { - 0% { - visibility: visible; - font-size: 15px; - z-index: 100; - top: 40%; - } - 75% { - visibility: visible; - font-size: 0px; - z-index: 100; - top: 0%; - } - 100% { - visibility: hidden; - font-size: 0px; - z-index: 100; - top: 0%; - } -} - -.tt-open { - background-color: var(--bg-color-secondary); - scrollbar-color: var(--primary) rgba(0, 0, 0, 0.2); - scrollbar-width: thin; -} - -#selected-validators-overview { - scrollbar-color: var(--primary) rgba(0, 0, 0, 0.2); - scrollbar-width: thin; -} - -.tt-open::-webkit-scrollbar, -#selected-validators-overview::-webkit-scrollbar { - width: 5px; - /* background-color: var(--bg-color-secondary); */ -} - -.tt-open::-webkit-scrollbar-thumb, -#selected-validators-overview::-webkit-scrollbar-thumb { - background-color: var(--primary); - border-radius: 12px; - border-style: solid; - color: var(--primary); - border-width: 2px; -} - -.proposal-switch { - border-radius: 12px; - border-style: none; - border-color: rgba(159, 162, 177, 0.4); - background-color: rgba(159, 162, 177, 0.4); - cursor: pointer; - width: 65px; - position: absolute; - top: 0%; - left: 1%; - z-index: 99; - display: none; -} - -.proposal-switch-selected { - border-radius: 12px; - border-style: none; - background-color: var(--primary); - color: var(--white); -} - -#proposals-table_length, -#proposals-table_info { - display: none; -} - -.summary-table td { - text-align: right; - white-space: normal; -} - -.proposals-table > div { - height: 100%; - display: flex; - flex-direction: column; - justify-content: flex-start; -} - -.proposals-table > div > div:nth-child(3) { - margin-top: auto !important; -} - -@media screen and (max-width: 1140px) { - .dashboard-table-nav { - margin: 0; - } -} - -@media screen and (max-width: 500px) { - .proposal-switch { - position: relative; - } -} - -@media screen and (max-width: 450px) { - .outer-container { - padding: 0; - } - .summary-table, - .validator-history-index { - font-size: 0.9rem; - } - .dashboard-table-nav-text { - display: none; - } -} diff --git a/static/css/layout.css b/static/css/layout.css index 8a64bd055e..bb880f258e 100644 --- a/static/css/layout.css +++ b/static/css/layout.css @@ -645,3 +645,11 @@ label.btn.btn-light:hover { background: #3e4461; color: #ffffff; } + +.val-link::before { + content: "\f183"; + font-family: "Font Awesome 5 Free"; + font-weight: 900; + display: inline-block; + margin-right: 0.5rem; +} diff --git a/static/js/dashboard.js b/static/js/dashboard.js deleted file mode 100644 index 15ca1e07c8..0000000000 --- a/static/js/dashboard.js +++ /dev/null @@ -1,1459 +0,0 @@ -function createBlock(x, y) { - use = document.createElementNS("http://www.w3.org/2000/svg", "use") - use.setAttributeNS(null, "href", "#cube") - use.setAttributeNS(null, "x", x) - use.setAttributeNS(null, "y", y) - return use -} - -function appendBlocks(blocks) { - $(".blue-cube g.move").each(function () { - $(this).empty() - }) - - var cubes = document.querySelectorAll(".blue-cube g.move") - for (var i = 0; i < blocks.length; i++) { - var block = blocks[i] - - for (let i = 0; i < cubes.length; i++) { - let cube = cubes[i] - cube.appendChild(createBlock(block[0], block[1])) - } - } - for (let i = 0; i < cubes.length; i++) { - let cube = cubes[i] - var use = document.createElementNS("http://www.w3.org/2000/svg", "use") - use.setAttributeNS(null, "href", "#cube-small") - use.setAttributeNS(null, "x", 129) - use.setAttributeNS(null, "y", 56) - cube.appendChild(use) - } -} - -var selectedBTNindex = null -var incomeChart = null -var incomeChartDefault = document.getElementById("balance-chart").innerHTML -var proposedChart = null -var proposedChartDefault = document.getElementById("proposed-chart").innerHTML -var summaryDefaultValue = "0.000" -var countdownIntervals = new Map() -var VALLIMIT = 280 -var allIncomeLoaded = false - -function hideValidatorHist() { - if ($.fn.dataTable.isDataTable("#dash-validator-history-table")) { - $("#dash-validator-history-table").DataTable().destroy() - } - - $("#dash-validator-history-table").addClass("d-none") - $("#dash-validator-history-art").removeClass("d-none") - $("#dash-validator-history-art").addClass("d-flex") - $("#dash-validator-history-index").text("") - selectedBTNindex = null -} - -function showValidatorHist(index) { - if ($.fn.dataTable.isDataTable("#dash-validator-history-table")) { - $("#dash-validator-history-table").DataTable().destroy() - } - - $("#dash-validator-history-table").DataTable({ - searchDelay: 0, - processing: true, - serverSide: true, - lengthChange: false, - ordering: false, - searching: false, - details: false, - pagingType: "simple", - pageLength: 10, - ajax: dataTableLoader("/validator/" + index + "/history"), - language: { - searchPlaceholder: "Search by Epoch Number", - search: "", - paginate: { - previous: '', - next: '', - }, - }, - columnDefs: [ - { - targets: 1, - createdCell: function (td, cellData, rowData, row, col) { - $(td).css("width", "0px") - }, - }, - { - targets: 2, - createdCell: function (td, cellData, rowData, row, col) { - $(td).css("padding", "0px") - }, - }, - ], - drawCallback: function (settings) { - formatTimestamps() - }, - }) - $("#validator-history-table_wrapper > div:nth-child(3) > div:nth-child(1)").removeClass("col-md-5").removeClass("col-sm-12") - $("#validator-history-table_wrapper > div:nth-child(3) > div:nth-child(2)").removeClass("col-md-7").removeClass("col-sm-12") - $("#validator-history-table_wrapper > div:nth-child(3)").addClass("justify-content-center") - $("#validator-history-table_paginate").attr("style", "padding-right: 0 !important") - $("#validator-history-table_info").attr("style", "padding-top: 0;") - $("#dash-validator-history-table").removeClass("d-none") - $("#dash-validator-history-art").removeClass("d-flex") - $("#dash-validator-history-art").addClass("d-none") - $("#dash-validator-history-index").text(index) - selectedBTNindex = index - showSelectedValidator() - updateValidatorInfo(index) -} - -function toggleFirstrow() { - $("#dashChartTabs a:first").tab("show") - let id = $("#validators tbody>tr:nth-child(1)>td>button").attr("id") - setTimeout(function () { - $("#" + id).focus() - }, 200) -} - -function updateValidatorInfo(index) { - fetch(`/validator/${index}/proposedblocks?draw=1&start=1&length=1`, { - method: "GET", - }).then((res) => { - res.json().then((data) => { - $("#blockCount span").text(data.recordsTotal) - }) - }) - fetch(`/validator/${index}/attestations?draw=1&start=1&length=1`, { - method: "GET", - }).then((res) => { - res.json().then((data) => { - $("#attestationCount span").text(data.recordsTotal) - }) - }) - fetch(`/validator/${index}/slashings?draw=1&start=1&length=1`, { - method: "GET", - }).then((res) => { - res.json().then((data) => { - var total = parseInt(data.recordsTotal) - if (total > 0) { - $("#slashingsCountDiv").removeClass("d-none") - $("#slashingsCount span").text(total) - } else { - $("#slashingsCountDiv").addClass("d-none") - } - }) - }) - fetch(`/validator/${index}/effectiveness`, { - method: "GET", - }).then((res) => { - res.json().then((data) => { - setValidatorEffectiveness("effectiveness", data.effectiveness) - }) - }) -} - -function getValidatorQueryString() { - return window.location.href.slice(window.location.href.indexOf("?"), window.location.href.length) -} - -var boxAnimationDirection = "" - -window.addEventListener("load", function () { - var searchInput = document.querySelector("#selected-validators-input input.typeahead-dashboard") - - $("#selected-validators-input-button").on("click", function (ev) { - var overview = document.getElementById("selected-validators-overview") - overview.classList.toggle("d-none") - }) - - searchInput.addEventListener("focus", function (ev) { - var overview = document.getElementById("selected-validators-overview") - if (document.querySelector("#selected-validators-input-button > span").textContent) { - overview.classList.remove("d-none") - } - }) - - document.addEventListener("click", function (event) { - var overview = document.getElementById("selected-validators-overview") - var trgt = event.target - let count = 0 - let match = false - do { - count++ - if (trgt.matches("li[data-validator-index]") || trgt.matches(".tt-suggestion") || trgt.matches(".tt-menu") || trgt.matches("#selected-validators-overview") || trgt.matches("#selected-validators-input input.typeahead-dashboard") || trgt.matches("#selected-validators-input-button")) { - match = true - break - } - if (count > 15) break - trgt = trgt.parentNode - } while (trgt && trgt.matches) - if (!match) { - overview.classList.add("d-none") - } - }) -}) - -function showSelectedValidator() { - setTimeout(function () { - $("span[id^=dropdownMenuButton]").each(function (el, item) { - if ($(item).attr("id") === "dropdownMenuButton" + selectedBTNindex) { - $(item).addClass("bg-primary") - } else { - if (selectedBTNindex != null) { - $(item).removeClass("bg-primary") - } - } - }) - }, 100) //if deselected index is not clearing increase the time - - $(".hbtn").hover( - function () { - $(this).addClass("shadow") - }, - function () { - $(this).removeClass("shadow") - } - ) -} - -function showValidatorsInSearch(qty) { - qty = parseInt(qty) - let i = 0 - let l = [] - $("#selected-validators-input li:not(:last)").remove() - $("#selected-validators.val-modal li").each(function (el, item) { - if (i === qty) { - return - } - l.push($(item).clone()) - i++ - }) - for (let i = 0; i < l.length; i++) { - $("#selected-validators-input").prepend(l[l.length - (i + 1)]) - } -} - -function renderProposedHistoryTable(data) { - if ($.fn.dataTable.isDataTable("#proposals-table")) { - $("#proposals-table").DataTable().destroy() - } - - $("#proposals-table").DataTable({ - searchDelay: 0, - serverSide: false, - data: data, - processing: false, - ordering: false, - searching: true, - pagingType: "full_numbers", - lengthMenu: [10, 25, 50], - preDrawCallback: function () { - // this does not always work.. not sure how to solve the staying tooltip - try { - $("#proposals-table").find('[data-toggle="tooltip"]').tooltip("dispose") - } catch (e) { - console.error(e) - } - }, - drawCallback: function (settings) { - $("#proposals-table").find('[data-toggle="tooltip"]').tooltip() - }, - columnDefs: [ - { - targets: 0, - data: "0", - render: function (data, type, row, meta) { - return '' + data + "" - }, - }, - { - targets: 1, - data: "1", - render: function (data, type, row, meta) { - // date and epochs - const startEpoch = timeToEpoch(data * 1000) - const startDate = luxon.DateTime.fromMillis(data * 1000) - const timeForOneDay = 24 * 60 * 60 * 1000 - const endEpoch = timeToEpoch(data * 1000 + timeForOneDay) - 1 - const endDate = luxon.DateTime.fromMillis(epochToTime(endEpoch + 1)) - const tooltip = `${startDate.toFormat("MMM-dd-yyyy HH:mm:ss")} - ${endDate.toFormat("MMM-dd-yyyy HH:mm:ss")}
Epochs ${startEpoch} - ${endEpoch}
` - - return `${startDate.toFormat("yyyy-MM-dd")}` - }, - }, - { - targets: 2, - data: "2", - render: function (data, type, row, meta) { - return '' + data[0] + "/" + '' + data[1] + "/" + '' + data[2] + "" - }, - }, - ], - }) -} - -function showProposedHistoryTable() { - fetch("/dashboard/data/proposalshistory" + getValidatorQueryString(), { - method: "GET", - }).then((res) => { - res.json().then(function (data) { - let proposedHistTableData = [] - for (let item of data.data) { - proposedHistTableData.push([item[0], item[1], [item[2], item[3], item[4]]]) - } - renderProposedHistoryTable(proposedHistTableData) - }) - }) -} - -function switchFrom(el1, el2, el3, el4) { - $(el1).removeClass("proposal-switch-selected") - $(el2).addClass("proposal-switch-selected") - $(el3).addClass("d-none") - $(el4).removeClass("d-none") -} - -var firstSwitch = true - -function initValidatorCountdown(validatorIndex, queueId, ts) { - var now = Math.round(new Date().getTime() / 1000) - var secondsLeft = ts - now - setValidatorCountdown(validatorIndex, queueId, secondsLeft) - - if (!countdownIntervals.has(validatorIndex)) { - countdownIntervals.set( - validatorIndex, - setInterval(function () { - if (secondsLeft <= 0) { - clearInterval(countdownIntervals.get(validatorIndex)) - return - } - - secondsLeft -= 1 - setValidatorCountdown(validatorIndex, queueId, secondsLeft) - }, 1000) - ) - } -} - -function setValidatorCountdown(validatorIndex, queueId, secondsLeft) { - let [seconds, minutes, hours, days] = [0, 0, 0, 0] - if (secondsLeft > 0) { - const duration = luxon.Duration.fromMillis(secondsLeft * 1000).shiftTo("days", "hours", "minutes", "seconds") - - seconds = duration.seconds - minutes = duration.minutes - hours = duration.hours - days = duration.days - } - - if (seconds < 10) { - seconds = "0" + seconds - } - if (minutes < 10) { - minutes = "0" + minutes - } - if (hours < 10) { - hours = "0" + hours - } - if (days < 10) { - days = "0" + days - } - - var $element = $("#queue-" + validatorIndex) - - var tooltip = ` -
This validator is currently #${queueId} in Queue.
- ${days} days ${hours} hr ${minutes} min ${seconds} sec` - - $element.attr("data-original-title", tooltip) - - if ($element.data("hover")) { - $element.tooltip("show") - } -} - -function removeValidatorCountdown(validatorIndex) { - if (countdownIntervals.has(validatorIndex)) { - clearInterval(countdownIntervals.get(validatorIndex)) - countdownIntervals.delete(validatorIndex) - } -} - -$(document).ready(function () { - $("#rewards-button").on("click", () => { - localStorage.setItem("load_dashboard_validators", true) - window.location.href = "/rewards" - }) - - $(".proposal-switch").on("click", () => { - if ($(".switch-chart").hasClass("proposal-switch-selected")) { - if (firstSwitch) { - showProposedHistoryTable() - firstSwitch = false - } - switchFrom(".switch-chart", ".switch-table", "#proposed-chart", "#proposed-table-div") - } else if ($(".switch-table").hasClass("proposal-switch-selected")) { - switchFrom(".switch-table", ".switch-chart", "#proposed-table-div", "#proposed-chart") - } - }) - - $("#validators").on("page.dt", function () { - showSelectedValidator() - }) - //bookmark button adds all validators in the dashboard to the watchlist - $("#bookmark-button").on("click", function (event) { - var tickIcon = $("") - var bookmarkIcon = $("") - var errorIcon = $("") - var validatorIndices = state.validators.filter((v) => { - return !isValidatorPubkey(v) - }) - fetch("/dashboard/save", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(validatorIndices), - }) - .then(function (res) { - console.log("response", res) - if (res.status === 200 && !res.redirected) { - // success - console.log("success") - $("#bookmark-button").empty().append(tickIcon) - setTimeout(function () { - $("#bookmark-button").empty().append(bookmarkIcon) - }, 1000) - } else if (res.redirected) { - console.log("redirected!") - $("#bookmark-button").attr("data-original-title", "Please login or sign up first.") - $("#bookmark-button").tooltip("show") - $("#bookmark-button").empty().append(errorIcon) - setTimeout(function () { - $("#bookmark-button").empty().append(bookmarkIcon) - $("#bookmark-button").tooltip("hide") - $("#bookmark-button").attr("data-original-title", "Save all to Watchlist") - }, 2000) - } else { - // could not bookmark validators - $("#bookmark-button").empty().append(errorIcon) - setTimeout(function () { - $("#bookmark-button").empty().append(bookmarkIcon) - }, 2000) - } - }) - .catch(function (err) { - $("#bookmark-button").empty().append(errorIcon) - setTimeout(function () { - $("#bookmark-button").empty().append(bookmarkIcon) - }, 2000) - console.log(err) - }) - }) - - $(document).on("mouseenter", ".hoverCheck[data-track=hover]", function () { - $(this).data("hover", true) - }) - - $(document).on("mouseleave", ".hoverCheck[data-track=hover]", function () { - $(this).data("hover", false) - }) - - var clearSearch = $("#clear-search") - var copyIcon = $("") - var tickIcon = $("") - - clearSearch.on("click", function () { - clearSearch.empty().append(tickIcon) - setTimeout(function () { - clearSearch.empty().append(copyIcon) - }, 500) - }) - $.fn.DataTable.ext.pager.numbers_length = 5 - var validatorsDataTable = (window.vdt = $("#validators").DataTable({ - searchDelay: 0, - processing: true, - serverSide: false, - searching: true, - stateSave: true, - stateSaveCallback: function (settings, data) { - data.start = 0 - localStorage.setItem("DataTables_" + settings.sInstance, JSON.stringify(data)) - }, - stateLoadCallback: function (settings) { - return JSON.parse(localStorage.getItem("DataTables_" + settings.sInstance)) - }, - pageLength: 10, - pagingType: "full_numbers", - scrollY: "503px", - info: false, - language: { - search: "", - searchPlaceholder: "Search...", - paginate: { - previous: '', - next: '', - }, - }, - dom: "<'row'<'col-sm-12 col-md-6 filter-by-status'><'col-sm-12 col-md-6'f>>" + "<'row'<'col-sm-12'tr>>" + "<'row'<'col-sm-12 col-md-5'l><'col-sm-12 col-md-7'p>>", - preDrawCallback: function () { - // this does not always work.. not sure how to solve the staying tooltip - try { - $("#validators").find('[data-toggle="tooltip"]').tooltip("dispose") - } catch (e) { - console.error(e) - } - }, - drawCallback: function (settings) { - formatTimestamps() - $("#validators").find('[data-toggle="tooltip"]').tooltip() - }, - order: [[1, "asc"]], - columnDefs: [ - // Pubkey - { - targets: 0, - data: "0", - createdCell: function (td, cellData, rowData, row, col) { - $(td).css("display", "flex") - $(td).css("align-items", "center") - $(td).css("justify-content", "space-between") - }, - render: function (data, type, row, meta) { - if (type == "sort" || type == "type") { - return data - } - return `0x${data.substr(0, 8)}...` - }, - }, - // Index - { - targets: 1, - data: "1", - render: function (data, type, row, meta) { - if (type == "sort" || type == "type") return data - if (isNaN(parseInt(data))) { - return `${data}` - } else { - return `${data}` - } - }, - }, - // Current balance / Effective balance - { - targets: 2, - data: "2", - render: function (data, type, row, meta) { - if (type == "sort" || type == "type") return data ? data[0] : null - return `${data[0]}` - }, - }, - // Index / State / Queue ahead / Estimated activation ts - { - targets: 3, - data: "3", - render: function (data, type, row, meta) { - if (type == "sort" || type == "type") return data ? data[0] : -1 - var d = data[1].split("_") - var s = d[0].charAt(0).toUpperCase() + d[0].slice(1) - - if (d[0] === "pending" && d[1] !== "deposited") { - initValidatorCountdown(data[0], data[2], data[3]) - return `${s} (#${data[2]})` - } - if (d[1] === "offline") return `${d[1]}${s} ` - if (d[1] === "online") return `${d[1]}${s} ` - return `${s}` - }, - }, - // Activation epoch / Activation ts - { - targets: 4, - visible: false, - data: "4", - render: function (data, type, row, meta) { - if (type == "sort" || type == "type") return data ? data[0] : null - if (data === null) return "-" - return `${getRelativeTime(luxon.DateTime.fromMillis(data[1] * 1000))} (Epoch ${data[0]})` - }, - }, - // Exit epoch / Exit ts - { - targets: 5, - visible: false, - data: "5", - render: function (data, type, row, meta) { - if (type == "sort" || type == "type") return data ? data[0] : null - if (data === null) return "-" - return `${getRelativeTime(luxon.DateTime.fromMillis(data[1] * 1000))} (Epoch ${data[0]})` - }, - }, - // Withdrawable epoch / Withdrawable ts - { - targets: 6, - data: "6", - render: function (data, type, row, meta) { - if (type == "sort" || type == "type") return data ? data[0] : null - if (data === null) return "-" - return `${getRelativeTime(luxon.DateTime.fromMillis(data[1] * 1000))} (Epoch ${data[0]})` - }, - }, - // Last attestation / Last attestation ts - { - targets: 7, - data: "7", - render: function (data, type, row, meta) { - if (type == "sort" || type == "type") return data ? data[0] : null - if (data === null) return "No Attestation found" - return `${data[1]}` - }, - }, - // Executed proposals / Missed proposals - { - targets: 8, - data: "8", - render: function (data, type, row, meta) { - if (type == "sort" || type == "type") return data ? data[0] + data[1] : null - return `${data[0]} / ${data[1]}` - }, - }, - // Performance last 7d - { - targets: 9, - data: "9", - render: function (data, type, row, meta) { - return data - }, - }, - // Deposit address - { - targets: 10, - orderable: false, - data: function (data) { - return data[10] - }, - visible: false, // hidden column for filtering only - render: function (data, type) { - if (type == "filter") return data - return null - }, - }, - ], - })) - - function create_validators_typeahead(input_container_selector, table_selector) { - var bhEth1Addresses = new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.whitespace, - queryTokenizer: Bloodhound.tokenizers.whitespace, - identify: function (obj) { - return obj.eth1_address - }, - remote: { - url: "/search/indexed_validators_by_eth1_addresses/%QUERY", - wildcard: "%QUERY", - }, - }) - $(input_container_selector).typeahead( - { - minLength: 1, - highlight: true, - hint: false, - autoselect: false, - }, - { - limit: 5, - name: "addresses", - source: bhEth1Addresses, - display: function (data) { - return data?.eth1_address || "" - }, - templates: { - header: '
ETH Address
', - suggestion: function (data) { - var len = data.validator_indices.length > 10 ? 10 + "+" : data.validator_indices.length - return `
0x${data.eth1_address}
${len}
` - }, - }, - } - ) - $(input_container_selector).on("focus", function (e) { - if (e.target.value !== "") { - $(this).trigger($.Event("keydown", { keyCode: 40 })) - } - }) - $(input_container_selector).on("input", function () { - $(".tt-suggestion").first().addClass("tt-cursor") - }) - $(input_container_selector).bind("typeahead:select", function (ev, suggestion) { - if (suggestion?.eth1_address) { - $(table_selector).DataTable().search(suggestion.eth1_address) - $(table_selector).DataTable().draw() - } - }) - } - create_validators_typeahead("input[aria-controls='validators']", "#validators") - - var timeWait = 0 - var debounce = function (context, func) { - var timeout, result - - return function () { - var args = arguments, - later = function () { - timeout = null - result = func.apply(context, args) - } - clearTimeout(timeout) - timeout = setTimeout(later, timeWait) - if (!timeout) { - result = func.apply(context, args) - } - return result - } - } - var bhValidators = new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.whitespace, - queryTokenizer: Bloodhound.tokenizers.whitespace, - identify: function (obj) { - return obj.index - }, - remote: { - url: "/search/indexed_validators/%QUERY", - // use prepare hook to modify the rateLimitWait parameter on input changes - // NOTE: we only need to do this for the first function because testing showed that queries are executed/queued in order - // No need to update `timeWait` multiple times. - prepare: function (_, settings) { - var cur_query = $(".typeahead-dashboard").val() - timeWait = 4000 - Math.min(cur_query.length, 5) * 500 - // "wildcard" can't be used anymore, need to set query wildcard ourselves now - settings.url = settings.url.replace("%QUERY", encodeURIComponent(cur_query)) - return settings - }, - }, - }) - bhValidators.remote.transport._get = debounce(bhValidators.remote.transport, bhValidators.remote.transport._get) - var bhPubkey = new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.whitespace, - queryTokenizer: Bloodhound.tokenizers.whitespace, - identify: function (obj) { - return obj.index - }, - remote: { - url: "/search/validators_by_pubkey/%QUERY", - wildcard: "%QUERY", - }, - }) - bhPubkey.remote.transport._get = debounce(bhPubkey.remote.transport, bhPubkey.remote.transport._get) - var bhEth1Addresses = new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.whitespace, - queryTokenizer: Bloodhound.tokenizers.whitespace, - identify: function (obj) { - return obj.eth1_address - }, - remote: { - url: "/search/indexed_validators_by_eth1_addresses/%QUERY", - wildcard: "%QUERY", - }, - }) - bhEth1Addresses.remote.transport._get = debounce(bhEth1Addresses.remote.transport, bhEth1Addresses.remote.transport._get) - var bhName = new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.whitespace, - queryTokenizer: Bloodhound.tokenizers.whitespace, - identify: function (obj) { - return obj.name - }, - remote: { - url: "/search/indexed_validators_by_name/%QUERY", - wildcard: "%QUERY", - }, - }) - bhName.remote.transport._get = debounce(bhName.remote.transport, bhName.remote.transport._get) - var bhGraffiti = new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.whitespace, - queryTokenizer: Bloodhound.tokenizers.whitespace, - identify: function (obj) { - return obj.graffiti - }, - remote: { - url: "/search/indexed_validators_by_graffiti/%QUERY", - wildcard: "%QUERY", - }, - }) - bhGraffiti.remote.transport._get = debounce(bhGraffiti.remote.transport, bhGraffiti.remote.transport._get) - - $(".typeahead-dashboard").typeahead( - { - minLength: 1, - highlight: true, - hint: false, - autoselect: false, - }, - { - limit: 5, - name: "validators", - source: bhValidators, - display: "index", - templates: { - header: "

Validators

", - suggestion: function (data) { - return `
${data.index}: ${data.pubkey}
` - }, - }, - }, - { - limit: 5, - name: "pubkeys", - source: bhPubkey, - display: "pubkey", - templates: { - header: "

Validators by Public Key

", - suggestion: function (data) { - return `
${data.pubkey}
` - }, - }, - }, - { - limit: 5, - name: "addresses", - source: bhEth1Addresses, - display: "address", - templates: { - header: "

Validators by ETH Addresses

", - suggestion: function (data) { - var len = data.validator_indices.length > VALLIMIT ? VALLIMIT + "+" : data.validator_indices.length - return `
${data.eth1_address}
${len}
` - }, - }, - }, - { - limit: 5, - name: "graffiti", - source: bhGraffiti, - display: "graffiti", - templates: { - header: "

Validators by Graffiti

", - suggestion: function (data) { - var len = data.validator_indices.length > VALLIMIT ? VALLIMIT + "+" : data.validator_indices.length - return `
${data.graffiti}
${len}
` - }, - }, - }, - { - limit: 5, - name: "name", - source: bhName, - display: "name", - templates: { - header: "

Validators by Name

", - suggestion: function (data) { - var len = data.validator_indices.length > VALLIMIT ? VALLIMIT + "+" : data.validator_indices.length - return `
${data.name}
${len}
` - }, - }, - } - ) - $(".typeahead-dashboard").on("focus", function (event) { - if (event.target.value !== "") { - $(this).trigger($.Event("keydown", { keyCode: 40 })) - } - }) - $(".typeahead-dashboard").on("input", function () { - $(".tt-suggestion").first().addClass("tt-cursor") - }) - $(".typeahead-dashboard").on("blur", function () { - $(this).val("") - $(".typeahead-dashboard").typeahead("val", "") - }) - $(".typeahead-dashboard").on("typeahead:select", function (ev, sug) { - if (sug.validator_indices) { - addValidators(sug.validator_indices) - } else if (sug.index != null) { - addValidator(sug.index) - } else { - addValidator("0x" + sug.pubkey) - } - boxAnimationDirection = "in" - $(".typeahead-dashboard").typeahead("val", "") - }) - - $("#pending").on("click", "button", function () { - var data = pendingTable.row($(this).parents("tr")).data() - removeValidator(data[1]) - }) - $("#active").on("click", "button", function () { - var data = activeTable.row($(this).parents("tr")).data() - removeValidator(data[1]) - }) - $("#ejected").on("click", "button", function () { - var data = ejectedTable.row($(this).parents("tr")).data() - removeValidator(data[1]) - }) - $("#selected-validators").on("click", ".remove-validator", function () { - removeValidator(this.parentElement.dataset.validatorIndex) - }) - $("#selected-validators-input").on("click", ".remove-validator", function () { - removeValidator(this.parentElement.dataset.validatorIndex) - }) - - $(".multiselect-border input").on("focus", function (event) { - $(".multiselect-border").addClass("focused") - }) - $(".multiselect-border input").on("blur", function (event) { - $(".multiselect-border").removeClass("focused") - }) - - $("#clear-search").on("click", function (event) { - if (state) { - state = setInitialState() - localStorage.removeItem("dashboard_validators") - window.location = "/dashboard" - selectedBTNindex = null - } - }) - - function setInitialState() { - var _state = {} - _state.validators = [] - _state.validatorsCount = { - pending: 0, - active: 0, - ejected: 0, - offline: 0, - } - return _state - } - - var state = setInitialState() - - setValidatorsFromURL() - renderSelectedValidators() - updateState() - - function isValidatorPubkey(identifier) { - return identifier.startsWith("0x") && identifier.length === 98 - } - - function firstValidatorWithIndex() { - return state.validators.find((v) => !isValidatorPubkey(v)) - } - - function renderSelectedValidators() { - if (state.validators.length > VALLIMIT) return - var elHolder = document.getElementById("selected-validators") - $("#selected-validators .item").remove() - $("#selected-validators hr").remove() - var elsItems = [] - for (var i = 0; i < state.validators.length; i++) { - if (i % 25 === 0 && i !== 0) { - var hr = document.createElement("hr") - hr.classList.add("w-100") - hr.classList.add("my-1") - elsItems.push(hr) - } - var v = state.validators[i] - var elItem = document.createElement("li") - elItem.classList = "item" - elItem.dataset.validatorIndex = v - var validatorDisplay = v - if (isValidatorPubkey(v)) { - validatorDisplay = v.slice(0, 6) + "..." + v.slice(-4) - } - elItem.innerHTML = ' ' + validatorDisplay + "" - elsItems.push(elItem) - } - elHolder.prepend(...elsItems) - } - - function addValidatorUpdateUI() { - $("#validators-tab").removeClass("disabled") - $("#validator-art").attr("class", "d-none") - - if (firstValidatorWithIndex() !== undefined) { - $("#dash-validator-history-info").removeClass("d-none") - $("#dash-validator-history-index-div").removeClass("d-none") - $("#dash-validator-history-index-div").addClass("d-flex") - - fetch(`/dashboard/data/effectiveness${getValidatorQueryString()}`, { - method: "GET", - }).then((res) => { - res.json().then((data) => { - if (Object.keys(data).length === 0) { - return - } - let sum = 0.0 - for (let eff of data) { - sum += eff - } - sum = sum / data.length - setValidatorEffectiveness("validator-eff-total", sum) - }) - }) - - showProposedHistoryTable() - } else { - $("#dash-validator-history-info").addClass("d-none") - $("#dash-validator-history-index-div").removeClass("d-flex") - $("#dash-validator-history-index-div").addClass("d-none") - - $("#validator-eff-total").html(summaryDefaultValue) - renderProposedHistoryTable([]) - } - - let anim = "goinboxanim" - if (boxAnimationDirection === "out") anim = "gooutboxanim" - - $("#selected-validators-input-button-box").addClass("zoomanim") - $("#selected-validators-input-button-val").addClass(anim) - setTimeout(() => { - $("#selected-validators-input-button-box").removeClass("zoomanim") - $("#selected-validators-input-button-val").removeClass(anim) - }, 1100) - } - - function renderDashboardInfo() { - var el = document.getElementById("dashboard-info") - var depositedText = "" - if (state.validatorsCount.deposited > 0) { - depositedText = `, ${state.validatorsCount.deposited} deposited` - } - var slashedText = "" - if (state.validatorsCount.slashed > 0) { - slashedText = `, ${state.validatorsCount.slashed} slashed` - } - el.innerText = `${state.validatorsCount.active_online + state.validatorsCount.active_offline} active (${state.validatorsCount.active_online} online, ${state.validatorsCount.active_offline} offline)${depositedText}, ${state.validatorsCount.pending} pending, ${state.validatorsCount.exited + state.validatorsCount.slashed} exited validators (${state.validatorsCount.exited} voluntary${slashedText})` - - if (state.validators.length > 0) { - showSelectedValidator() - addValidatorUpdateUI() - - firstValidatorWithHistory = firstValidatorWithIndex() - if (firstValidatorWithHistory === undefined) { - hideValidatorHist() - } else if (selectedBTNindex !== firstValidatorWithHistory) { - // don't query if not necessary) - showValidatorHist(firstValidatorWithHistory) - } - } else { - $("#validatorModal").modal("hide") - } - - if (state.validators.length > 0) { - $("#selected-validators-input-button").removeClass("d-none") - $("#selected-validators-input-button").addClass("d-flex") - $("#selected-validators-input-button span").html(state.validators.length) - } else { - $("#selected-validators-input-button").removeClass("d-flex") - $("#selected-validators-input-button").addClass("d-none") - } - } - - function setValidatorsFromURL() { - var usp = new URLSearchParams(window.location.search) - var validatorsStr = usp.get("validators") - if (!validatorsStr) { - validatorsStr = localStorage.getItem("dashboard_validators") - if (validatorsStr) { - state.validators = JSON.parse(validatorsStr) - state.validators = state.validators.filter((v, i) => { - v = escape(v) - if (isNaN(parseInt(v))) return false - return state.validators.indexOf(v) === i - }) - state.validators.sort(sortValidators) - } else { - state.validators = [] - } - return - } - state.validators = validatorsStr.split(",") - state.validators = state.validators.filter((v, i) => { - v = escape(v) - if (isNaN(parseInt(v))) return false - return state.validators.indexOf(v) === i - }) - state.validators.sort(sortValidators) - - if (state.validators.length > VALLIMIT) { - state.validators = state.validators.slice(0, VALLIMIT) - console.log(`${VALLIMIT} validators limit reached`) - handleLimitHit() - } - } - - function addValidators(indices) { - var overview = document.getElementById("selected-validators-overview") - if (state.validators.length === 0) { - overview.classList.remove("d-none") - } - var limitReached = false - indicesLoop: for (var j = 0; j < indices.length; j++) { - if (state.validators.length >= VALLIMIT) { - limitReached = true - break indicesLoop - } - var index = indices[j] + "" // make sure index is string - for (var i = 0; i < state.validators.length; i++) { - if (state.validators[i] === index) continue indicesLoop - } - state.validators.push(index) - } - - if (limitReached) { - console.log(`${VALLIMIT} validators limit reached`) - handleLimitHit() - } - state.validators.sort(sortValidators) - renderSelectedValidators() - updateState() - } - - function handleLimitHit() { - if (VALLIMIT == 300) { - // user is already at the top tier, no need to advertise it to them - alert(`Sorry, too many validators! You can not currently add more than ${VALLIMIT} validators to your dashboard.`) - } else { - if (window.confirm(`With your current premium level, you can not add more than ${VALLIMIT} validators to your dashboard.\n\nBy upgrading to the Whale Tier, this limit gets raised to 280 validators!`)) { - window.location.href = "/premium" - } - } - } - - function addValidator(index) { - var overview = document.getElementById("selected-validators-overview") - if (state.validators.length === 0) { - overview.classList.remove("d-none") - } - if (state.validators.length >= VALLIMIT) { - handleLimitHit() - return - } - index = index + "" // make sure index is string - for (var i = 0; i < state.validators.length; i++) { - if (state.validators[i] === index) return - } - state.validators.push(index) - state.validators.sort(sortValidators) - renderSelectedValidators() - updateState() - } - - function removeValidator(index) { - boxAnimationDirection = "out" - for (var i = 0; i < state.validators.length; i++) { - if (state.validators[i] === index) { - state.validators.splice(i, 1) - state.validators.sort(sortValidators) - //removed last validator - if (state.validators.length === 0) { - state = setInitialState() - localStorage.removeItem("dashboard_validators") - window.location = "/dashboard" - return - } else { - removeValidatorCountdown(parseInt(index)) - renderSelectedValidators() - updateState() - } - return - } - } - } - - function sortValidators(a, b) { - var ai = parseInt(a) - var bi = parseInt(b) - - return ai - bi - } - - function updateState() { - if (state.validators.length > VALLIMIT) { - return - } - localStorage.setItem("dashboard_validators", JSON.stringify(state.validators)) - window.dispatchEvent(new CustomEvent("dashboard_validators_set")) - - if (state.validators.length) { - var qryStr = "?validators=" + state.validators.join(",") - if (window.location.search != qryStr) { - var newUrl = window.location.pathname + qryStr + window.location.hash - window.history.replaceState(null, "Dashboard", newUrl) - } - } - var t0 = Date.now() - if (state.validators && state.validators.length) { - document.querySelector("#copy-button").style.visibility = "visible" - document.querySelector("#clear-search").style.visibility = "visible" - - $.ajax({ - url: "/dashboard/data/validators" + qryStr, - success: function (result) { - var t1 = Date.now() - console.log(`loaded validators-data: length: ${result.data.length}, fetch: ${t1 - t0}ms`) - if (!result || !result.data.length) { - document.getElementById("validators-table-holder").style.display = "none" - return - } - // pubkey, idx, currbal, effbal, slashed, acteligepoch, actepoch, exitepoch - // 0:pubkey, 1:idx, 2:[currbal,effbal], 3:state, 4:[actepoch,acttime], 5:[exit,exittime], 6:[wd,wdt], 7:[lasta,lastat], 8:[exprop,misprop] - state.validatorsCount.deposited = 0 - state.validatorsCount.pending = 0 - state.validatorsCount.active_online = 0 - state.validatorsCount.active_offline = 0 - state.validatorsCount.slashing_online = 0 - state.validatorsCount.slashing_offline = 0 - state.validatorsCount.exiting_online = 0 - state.validatorsCount.exiting_offline = 0 - state.validatorsCount.exited = 0 - state.validatorsCount.slashed = 0 - - for (var i = 0; i < result.data.length; i++) { - var v = result.data[i] - var vIndex = v[1] - var vState = v[3][1] - if (!state.validatorsCount[vState]) state.validatorsCount[vState] = 0 - state.validatorsCount[vState]++ - var el = document.querySelector(`#selected-validators .item[data-validator-index="${vIndex}"]`) - if (el) el.dataset.state = vState - } - validatorsDataTable.clear() - - validatorsDataTable.rows.add(result.data).draw() - - validatorsDataTable.column(6).visible(false) - - requestAnimationFrame(() => { - validatorsDataTable.columns.adjust().responsive.recalc() - }) - - document.getElementById("validators-table-holder").style.display = "block" - - renderDashboardInfo() - }, - }) - - if (firstValidatorWithIndex() !== undefined) { - document.querySelector("#rewards-button").style.visibility = "visible" - document.querySelector("#bookmark-button").style.visibility = "visible" - - $.ajax({ - url: "/dashboard/data/earnings" + qryStr, - success: function (result) { - var t1 = Date.now() - console.log(`loaded earnings: fetch: ${t1 - t0}ms`) - if (!result) return - - document.querySelector("#earnings-day").innerHTML = result.lastDayFormatted || summaryDefaultValue - document.querySelector("#earnings-week").innerHTML = result.lastWeekFormatted || summaryDefaultValue - document.querySelector("#earnings-month").innerHTML = result.lastMonthFormatted || summaryDefaultValue - document.querySelector("#earnings-total").innerHTML = result.totalFormatted || summaryDefaultValue - $("#earnings-total").find('[data-toggle="tooltip"]').tooltip() - document.querySelector("#balance-total").innerHTML = result.totalBalance || summaryDefaultValue - $("#balance-total span:first").removeClass("text-success").removeClass("text-danger") - $("#balance-total span:first").html($("#balance-total span:first").html().replace("+", "")) - }, - }) - } else { - document.querySelector("#rewards-button").style.visibility = "hidden" - document.querySelector("#bookmark-button").style.visibility = "hidden" - - document.querySelector("#earnings-day").innerHTML = summaryDefaultValue - document.querySelector("#earnings-week").innerHTML = summaryDefaultValue - document.querySelector("#earnings-month").innerHTML = summaryDefaultValue - document.querySelector("#earnings-total").innerHTML = summaryDefaultValue - document.querySelector("#balance-total").innerHTML = summaryDefaultValue - } - } else { - document.querySelector("#copy-button").style.visibility = "hidden" - document.querySelector("#rewards-button").style.visibility = "hidden" - document.querySelector("#bookmark-button").style.visibility = "hidden" - document.querySelector("#clear-search").style.visibility = "hidden" - } - - $("#copy-button").attr("data-clipboard-text", window.location.href) - - if (state.validators && firstValidatorWithIndex() !== undefined) { - renderCharts() - } else { - hideCharts() - } - } - - window.onpopstate = function (event) { - setValidatorsFromURL() - renderSelectedValidators() - updateState() - } - window.addEventListener("storage", function (e) { - var validatorsStr = localStorage.getItem("dashboard_validators") - if (JSON.stringify(state.validators) === validatorsStr) { - return - } - if (validatorsStr) { - state.validators = JSON.parse(validatorsStr) - } else { - state.validators = [] - } - state.validators = state.validators.filter((v, i) => state.validators.indexOf(v) === i) - state.validators.sort(sortValidators) - renderSelectedValidators() - updateState() - }) - - function hideCharts() { - hideIncomeChart() - hideProposedChart() - } - - function hideIncomeChart() { - if (incomeChart) { - incomeChart.destroy() - incomeChart = null - } - document.getElementById("balance-chart").innerHTML = incomeChartDefault - } - - function hideProposedChart() { - if (proposedChart) { - proposedChart.destroy() - proposedChart = null - } - document.getElementById("proposed-chart").innerHTML = proposedChartDefault - } - - function renderCharts() { - var t0 = Date.now() - var qryStr = "?validators=" + state.validators.join(",") - $.ajax({ - url: "/dashboard/data/allbalances" + qryStr + "&days=31", - success: function (result) { - var t1 = Date.now() - createIncomeChart(result.consensusChartData, result.executionChartData) - var t2 = Date.now() - console.log(`loaded balance-data: length: ${result.length}, fetch: ${t1 - t0}ms, render: ${t2 - t1}ms`) - allIncomeLoaded = false - $("#load-income-btn").removeClass("d-none") - }, - }) - $.ajax({ - url: "/dashboard/data/proposals" + qryStr, - success: function (result) { - var t1 = Date.now() - if (result && result.length) { - createProposedChart(result) - } else { - var chart = $("#proposed-chart").highcharts() - if (chart !== undefined) { - hideProposedChart() - } - } - var t2 = Date.now() - console.log(`loaded proposal-data: length: ${result.length}, fetch: ${t1 - t0}ms, render: ${t2 - t1}ms`) - }, - }) - } - - $("#load-income-btn").on("click", () => { - if (allIncomeLoaded || incomeChart == null) { - return - } - allIncomeLoaded = true - - const url = "/dashboard/data/allbalances?validators=" + state.validators.join(",") - $("#load-income-btn").text("Loading...") - fetch(url) - .then((response) => { - if (!response.ok) { - throw new Error("Network response was not ok.") - } - return response.json() - }) - .then((data) => { - createIncomeChart(data.consensusChartData, data.executionChartData) - $("#load-income-btn").addClass("d-none") - }) - .catch((error) => { - console.error(error) - alert("Error loading income data. Please try again.") - allIncomeLoaded = false - }) - .finally(() => { - $("#load-income-btn").text("Show all rewards") - }) - }) -}) - -function createIncomeChart(income, executionIncomeHistory) { - executionIncomeHistory = executionIncomeHistory || [] - const incomeChartOptions = getIncomeChartOptions(income, executionIncomeHistory, "Daily Income for all Validators", 627) - incomeChart = Highcharts.stockChart("balance-chart", incomeChartOptions) -} - -function createProposedChart(data) { - var proposed = [] - var missed = [] - var orphaned = [] - data.map((d) => { - if (d[1] == 1) proposed.push([d[0] * 1000, 1]) - else if (d[1] == 2) missed.push([d[0] * 1000, 1]) - else if (d[1] == 3) orphaned.push([d[0] * 1000, 1]) - }) - proposedChart = Highcharts.stockChart("proposed-chart", { - chart: { - type: "column", - height: "630px", - }, - title: { - text: "Proposal History for all Validators", - }, - legend: { - enabled: true, - }, - colors: ["#7cb5ec", "#ff835c", "#e4a354", "#2b908f", "#f45b5b", "#91e8e1"], - xAxis: { - lineWidth: 0, - tickColor: "#e5e1e1", - }, - yAxis: [ - { - title: { - text: "# of Possible Proposals", - }, - opposite: false, - }, - ], - plotOptions: { - column: { - stacking: "normal", - dataGrouping: { - enabled: true, - forced: true, - units: [["day", [1]]], - }, - }, - }, - series: [ - { - name: "Proposed", - color: "#7cb5ec", - data: proposed, - }, - { - name: "Missed", - color: "#ff835c", - data: missed, - }, - { - name: "Missed (Orphaned)", - color: "#e4a354", - data: orphaned, - }, - ], - rangeSelector: { - enabled: false, - }, - }) - $(".proposal-switch").show() -} diff --git a/static/js/layout.js b/static/js/layout.js index 9503bd376c..6dab2a16d1 100644 --- a/static/js/layout.js +++ b/static/js/layout.js @@ -831,6 +831,25 @@ function addCommas(number) { .replace(/\B(?=(\d{3})+(?!\d))/g, "") } +function shortBalance(input) { + const [amountStr, unit] = input.split(" ") + const amount = parseFloat(amountStr) + + let shortened + + if (amount >= 1_000) { + shortened = Math.floor(amount / 1_000) + "K" + } else { + shortened = Math.floor(amount).toString() + } + + if (unit) { + shortened += " " + unit + } + + return addCommas(shortened) +} + function trimPrice(value, decimals = 5) { if (value === undefined || value === null) { return "" diff --git a/static/js/validatorRewards.js b/static/js/validatorRewards.js deleted file mode 100644 index b7870e9fc5..0000000000 --- a/static/js/validatorRewards.js +++ /dev/null @@ -1,539 +0,0 @@ -const VALLIMIT = 200 -const DECIMAL_POINTS_ETH = 6 -const DECIMAL_POINTS_CURRENCY = 3 -var csrfToken = "" -var currency = "" -var subsTable = null -// let validators = [] - -function create_typeahead(input_container) { - var timeWait = 0 - var debounce = function (context, func) { - var timeout, result - - return function () { - var args = arguments, - later = function () { - timeout = null - result = func.apply(context, args) - } - clearTimeout(timeout) - timeout = setTimeout(later, timeWait) - if (!timeout) { - result = func.apply(context, args) - } - return result - } - } - - var bhValidators = new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.whitespace, - queryTokenizer: Bloodhound.tokenizers.whitespace, - identify: function (obj) { - return obj.index - }, - remote: { - url: "/search/indexed_validators/%QUERY", - // use prepare hook to modify the rateLimitWait parameter on input changes - // NOTE: we only need to do this for the first function because testing showed that queries are executed/queued in order - // No need to update `timeWait` multiple times. - prepare: function (_, settings) { - var cur_query = $(input_container).val() - timeWait = 4000 - Math.min(cur_query.length, 5) * 500 - // "wildcard" can't be used anymore, need to set query wildcard ourselves now - settings.url = settings.url.replace("%QUERY", encodeURIComponent(cur_query)) - return settings - }, - }, - }) - bhValidators.remote.transport._get = debounce(bhValidators.remote.transport, bhValidators.remote.transport._get) - var bhEth1Addresses = new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.whitespace, - queryTokenizer: Bloodhound.tokenizers.whitespace, - identify: function (obj) { - return obj.eth1_address - }, - remote: { - url: "/search/indexed_validators_by_eth1_addresses/%QUERY", - wildcard: "%QUERY", - }, - }) - bhEth1Addresses.remote.transport._get = debounce(bhEth1Addresses.remote.transport, bhEth1Addresses.remote.transport._get) - var bhName = new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.whitespace, - queryTokenizer: Bloodhound.tokenizers.whitespace, - identify: function (obj) { - return obj.name - }, - remote: { - url: "/search/indexed_validators_by_name/%QUERY", - wildcard: "%QUERY", - }, - }) - bhName.remote.transport._get = debounce(bhName.remote.transport, bhName.remote.transport._get) - var bhGraffiti = new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.whitespace, - queryTokenizer: Bloodhound.tokenizers.whitespace, - identify: function (obj) { - return obj.graffiti - }, - remote: { - url: "/search/indexed_validators_by_graffiti/%QUERY", - wildcard: "%QUERY", - }, - }) - bhGraffiti.remote.transport._get = debounce(bhGraffiti.remote.transport, bhGraffiti.remote.transport._get) - - $(input_container).typeahead( - { - minLength: 1, - highlight: true, - hint: false, - autoselect: false, - }, - { - limit: 5, - name: "validators", - source: bhValidators, - display: "index", - templates: { - header: "

Validators

", - suggestion: function (data) { - return `
${data.index}
` - }, - }, - }, - { - limit: 5, - name: "addresses", - source: bhEth1Addresses, - display: "address", - templates: { - header: "

Validators by ETH Addresses

", - suggestion: function (data) { - var len = data.validator_indices.length > VALLIMIT ? VALLIMIT + "+" : data.validator_indices.length - return `
${data.eth1_address}
${len}
` - }, - }, - }, - { - limit: 5, - name: "graffiti", - source: bhGraffiti, - display: "graffiti", - templates: { - header: "

Validators by Graffiti

", - suggestion: function (data) { - var len = data.validator_indices.length > VALLIMIT ? VALLIMIT + "+" : data.validator_indices.length - return `
${data.graffiti}
${len}
` - }, - }, - }, - { - limit: 5, - name: "name", - source: bhName, - display: "name", - templates: { - header: "

Validators by Name

", - suggestion: function (data) { - var len = data.validator_indices.length > VALLIMIT ? VALLIMIT + "+" : data.validator_indices.length - return `
${data.name}
${len}
` - }, - }, - } - ) - - $(input_container).on("focus", function (event) { - if (event.target.value !== "") { - $(this).trigger($.Event("keydown", { keyCode: 40 })) - } - }) - $(input_container).on("input", function () { - $(".tt-suggestion").first().addClass("tt-cursor") - }) - $(input_container).on("blur", function () { - $(this).val("") - $(input_container).typeahead("val", "") - }) - $(input_container).on("typeahead:select", function (ev, sug) { - validators = $("#validator-index-view").val().split(",") - if (sug.validator_indices) { - validators = validators.concat(sug.validator_indices) - } else { - validators.push(sug.index) - } - - validators = Array.from(new Set(validators)) - if (validators.length > VALLIMIT) { - validators = validators.slice(0, VALLIMIT) - alert(`No more than ${VALLIMIT} validators are allowed`) - } - - $("#validator-index-view").val(validators) - if ($("#validator-index-view").val().charAt(0) === ",") { - $("#validator-index-view").val($("#validator-index-view").val().slice(1)) - } - $(input_container).typeahead("val", "") - }) -} - -function updateCurrencies(currencies, container) { - for (item of currencies) { - if (item === "ts") continue - $("#" + container).append(``) - } -} - -function getValidatorQueryString() { - return window.location.href.slice(window.location.href.indexOf("?"), window.location.href.length) -} - -function hideSpinner() { - $("#loading-div").addClass("d-none") - $("#loading-div").removeClass("d-flex") -} - -function showSpinner() { - $("#loading-div").removeClass("d-none") - $("#loading-div").addClass("d-flex") -} - -function showTable(data) { - $("#tax-table").DataTable({ - searchDelay: 0, - processing: true, - serverSide: false, - ordering: true, - searching: true, - pagingType: "full_numbers", - pageLength: 100, - lengthChange: false, - data: data.history, - dom: "Bfrtip", - buttons: ["copyHtml5", "excelHtml5", "csvHtml5", "pdfHtml5"], - drawCallback: function (settings) { - $("#form-div").removeClass("d-flex").addClass("d-none") - $("#table-div").removeClass("d-none") - $("#subscriptions-div").addClass("d-none") - $("#total-income-eth-span").html("ETH " + data.total_eth) - $("#total-income-currency-span").html(data.total_currency) - $("#totals-div").removeClass("d-none") - $(".dt-button").addClass("ml-2 ") - hideSpinner() - }, - order: [[0, "desc"]], - language: { - searchPlaceholder: "Enter Date", - }, - columnDefs: [ - { - targets: 0, - data: "0", - orderable: true, - render: function (data, type, row, meta) { - if (type === "filter" || type === "display") return data - return moment(data).unix() - }, - }, - { - targets: 1, - data: "1", - orderable: true, - render: function (data, type, row, meta) { - // return (parseFloat(data).toFixed(DECIMAL_POINTS_ETH)) - return data - }, - }, - { - targets: 2, - data: "2", - orderable: true, - render: function (data, type, row, meta) { - // return (parseFloat(data).toFixed(DECIMAL_POINTS_ETH)) - return data - }, - }, - { - targets: 3, - data: "3", - orderable: false, - render: function (data, type, row, meta) { - // return `${currency} ${addCommas(parseFloat(data).toFixed(DECIMAL_POINTS_CURRENCY))}` - return data - }, - }, - { - targets: 4, - data: "4", - orderable: false, - render: function (data, type, row, meta) { - // return `${currency} ${addCommas(parseFloat(data).toFixed(DECIMAL_POINTS_CURRENCY))}` - return data - }, - // }, { - // targets: 5, - // data: '5', - // "orderable": false, - // visible: false, - // render: function (data, type, row, meta) { - // return data.toUpperCase() - // } - }, - ], - }) -} - -function unSubUser(filter) { - // console.log(filter) - showSpinner() - fetch(`/user/rewards/unsubscribe?${filter}`, { - method: "POST", - headers: { "X-CSRF-Token": csrfToken }, - credentials: "include", - body: "", - }).then((res) => { - if (res.status == 200) { - res.json().then((data) => { - console.log(data.msg) - fetchSubscriptions() - }) - } - }) -} - -function updateSubscriptionTable(data, container) { - if (data.data.length === 0) { - $("#subscriptions-div").addClass("d-none") - hideSpinner() - return - } - if (subsTable !== null) { - // subsTable.clear() - // subsTable.empty() - // subsTable.destroy() - $("#" + container) - .DataTable() - .clear() - .destroy() - // $('#'+container+" tbody").empty() - // $('#'+container+" thead").empty() - } - subsTable = $("#" + container).DataTable({ - searchDelay: 0, - processing: true, - serverSide: false, - ordering: true, - searching: true, - pagingType: "full_numbers", - pageLength: 100, - lengthChange: false, - data: data.data, - drawCallback: function (settings) { - $("#subscriptions-table-art").removeClass("d-flex").addClass("d-none") - $("#subscriptions-table-div").removeClass("invisible") - $("#subscriptions-div").removeClass("d-none") - $("#subs-header").html(`Subscriptions (${data.count}/5)`) - hideSpinner() - }, - language: { - searchPlaceholder: "Enter Date, Currency", - }, - columnDefs: [ - { - targets: 0, - data: "0", - orderable: true, - render: function (data, type, row, meta) { - if (type === "filter" || type === "display") { - let date = data.split(" ") - if (date.length >= 1) { - return `${date[0]}` - } - return data - } - return moment(data).unix() - }, - }, - { - targets: 1, - data: "1", - orderable: true, - render: function (data, type, row, meta) { - return data.toUpperCase() - }, - }, - { - targets: 2, - data: "2", - orderable: false, - render: function (data, type, row, meta) { - if (type === "display") { - l = data.split(",") - l.sort((a, b) => parseInt(a) - parseInt(b)) - data = "" - for (i of l) { - data += `
  • ${i}
  • ` - } - return `
      ${data}
    ` - } - return data - }, - }, - { - targets: 3, - data: "3", - orderable: false, - render: function (data, type, row, meta) { - downloadQueryUrl = `${window.location.origin}/rewards/hist/download?validators=${row[2]}¤cy=${row[1]}&days=${moment().subtract(1, "month").startOf("month").unix()}-${moment().subtract(1, "month").endOf("month").unix()}` - return ` -
    - - - -
    - ` - }, - }, - ], - }) -} - -function loadValInForm(val) { - $("#validator-index-view").val(val.replace(/([a-zA-Z ])/g, "")) -} - -function fetchSubscriptions() { - fetch(`/user/rewards/subscriptions/data`, { - method: "POST", - headers: { "X-CSRF-Token": csrfToken }, - credentials: "include", - body: "", - }) - .then((res) => { - if (res.status == 200) { - res.json().then((data) => { - // console.log(data.msg) - updateSubscriptionTable(data, "subscriptions-table") - }) - } else { - console.error("error getting subscriptions", res) - } - }) - .catch((err) => { - console.error("error getting subscriptions", err) - }) -} - -$(document).ready(function () { - if (document.getElementsByName("CsrfField")[0] !== undefined) { - csrfToken = document.getElementsByName("CsrfField")[0].value - } - - if (JSON.parse(localStorage.getItem("load_dashboard_validators"))) { - const dashBoardValidators = JSON.parse(localStorage.getItem("dashboard_validators")) - const dashBoardValidatorIndices = dashBoardValidators.filter((entry) => { - return !(entry.startsWith("0x") && entry.length === 98) - }) - - $("#validator-index-view").val(dashBoardValidatorIndices) - localStorage.setItem("load_dashboard_validators", false) - } - - $("#validator-index-view").on("keyup", function () { - $(this).val( - $(this) - .val() - .replace(/([a-zA-Z ])/g, "") - ) - }) - - $("#days").val(`${moment().startOf("month").unix()}-${moment().unix()}`) - - $('input[id="datepicker"]').daterangepicker( - { - pens: "left", - minDate: moment.unix(MIN_TIMESTAMP), - maxDate: moment(), - maxSpan: { - days: 365, - }, - ranges: { - "This Month to date": [moment().startOf("month"), moment()], - "Last Month to date": [moment().subtract(1, "month").startOf("month"), moment()], - "This Year to date": [moment().startOf("year"), moment()], - }, - locale: { - format: "DD/MM/YYYY", - }, - singleDatePicker: false, - alwaysShowCalendars: false, - startDate: moment().startOf("month"), - endDate: moment(), - }, - function (start, end, label) { - // let end_d = moment() - $("#days").val(`${moment(start).unix()}-${moment(end).unix()}`) - } - ) - - create_typeahead(".typeahead-validators") - let qry = getValidatorQueryString() - // console.log(qry, qry.length) - - $("#report-sub-btn").on("click", function () { - var form = document.getElementById("hits-form") - if (!form.reportValidity()) { - return - } - let btn_content = $(this).html() - $(this).html(`
    - Loading... -
    `) - - fetch(`/user/rewards/subscribe?validators=${$("#validator-index-view").val()}¤cy=${$("#currency").val()}`, { - method: "POST", - headers: { "X-CSRF-Token": csrfToken }, - credentials: "include", - body: "", - }) - .then((res) => { - if (res.status == 200) { - res.json().then((data) => { - // console.log(data.msg) - fetchSubscriptions() - $(this).html(btn_content) - }) - } else { - console.error("error subscribing", res) - alert("Subscription limit is reached") - $(this).html(btn_content) - } - }) - .catch((err) => { - console.error("error subscribing", err) - $(this).html(btn_content) - }) - }) - - if (qry.length > 1) { - fetch(`/rewards/hist${qry}`, { - method: "GET", - }) - .then((res) => { - if (res.status !== 200) { - alert("Request failed :(") - hideSpinner() - } - res.json().then((data) => { - showTable(data) - }) - }) - .catch(() => { - alert("Failed to fetch the data :(") - hideSpinner() - }) - } else { - hideSpinner() - } -}) diff --git a/templates/api_docs.html b/templates/api_docs.html new file mode 100644 index 0000000000..2ae2e51834 --- /dev/null +++ b/templates/api_docs.html @@ -0,0 +1,97 @@ +{{ define "js" }} + + + +{{ end }} + +{{ define "css" }} + +{{ end }} + +{{ define "content" }} +
    +
    +
    +{{ end }} diff --git a/templates/dashboard.html b/templates/dashboard.html deleted file mode 100644 index a168940938..0000000000 --- a/templates/dashboard.html +++ /dev/null @@ -1,728 +0,0 @@ -{{ define "js" }} - - - - - - - - - - - - -{{ end }} - -{{ define "css" }} - - -{{ end }} - -{{ define "content" }} - {{ with .Data }} -
    -
    -
    -
    - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    -
    Dashboard
    -
    Validators
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    - - - {{ if $.User.Authenticated }} - - {{ else }} - - - - {{ end }} - - - - -
      -
    • -
    - -
    - -
    -
    -
    - 0 active (0 online, 0 offline), 0 pending, 0 exited validators (0 voluntary) -
    -
    -
    -
    -
    -
    - Summary - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - Last Day -
    -
    0.000
    -
    - Last Week -
    -
    0.000
    -
    - Last Month -
    -
    0.000
    -
    - Total Rewards -
    -
    0.000
    -
    Total Balance
    -
    0.000
    -
    Avg. Eff.ectiveness
    -
    0.000
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    - - - - - - - - - reading list - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    - - - - - - - - - - - - - - - - -
    Public KeyIndexBalanceStateActivationExitWithdrawableLast AttestationPIncome 7 days
    -
    -
    -
    -
    -
    -
    - - investing - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - -
    -
    -
    - - -
    -
    -
    - - - - - - - - - business plan - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    - -
    - - - - - - - - - -
    ValidatorDateP / M / O
    -
    -
    -
    - {{ if .CappellaHasHappened }} -
    - {{ template "dashboardWithdrawalTable" . }} -
    - {{ end }} -
    -
    -
    -
    -
    -
    - History -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Watch your validator history right in the dashboard -
    -
    - - - - - - - - - - - - - -
    EpochRewardsEvents
    -
    -
    -
    -
    -
    -
    - {{ end }} -{{ end }} diff --git a/templates/deposits.html b/templates/deposits.html index b893a6c01d..3a2cd20ef5 100644 --- a/templates/deposits.html +++ b/templates/deposits.html @@ -53,7 +53,6 @@ { orderable: true, targets: 5 }, { orderable: false, targets: 6 }, { orderable: false, targets: 7 }, - { orderable: false, targets: 8 } ], }) {{end}} @@ -99,6 +98,9 @@ if (clipboardtext.startsWith("0x01")) { data = `${data}` } + if (clipboardtext.startsWith("0x02")) { + data = `${data}` + } return `${data}` }, }, @@ -248,14 +250,13 @@
    This table displays the deposits made for validators Time Block Validator State - Valid Signature {{ if len .DepositContract }} {{ else }} - Waiting for Deposit Contract + Waiting for Deposit Contract {{ end }} diff --git a/templates/eth1tx.html b/templates/eth1tx.html index 18800f7705..43e844e8c6 100644 --- a/templates/eth1tx.html +++ b/templates/eth1tx.html @@ -240,6 +240,10 @@

    {{ if .TargetIsContract }} {{ if .DepositContractInteractions }}
    Deposit Contract
    + {{ else if .Withdrawals }} +
    Withdrawal Contract
    + {{ else if .Consolidations }} +
    Consolidations Contract
    {{ else }}
    Contract
    {{ end }} @@ -312,6 +316,53 @@

    {{ end }} + {{ if .Consolidations }} +
    +
    Beaconchain Consolidations:
    +
    +
      + {{ range $i, $con := .Consolidations }} +
    • + + {{ if $con.IsMoveToCompounding }} + Convert to compounding request from validator + {{ formatPublicKey $con.SourceValidatorPubkey }} + {{ else }} + Consolidate request from validator + {{ formatPublicKey $con.SourceValidatorPubkey }} + to validator + {{ formatPublicKey $con.TargetValidatorPubkey }} + {{ end }} + +
    • + {{ end }} +
    +
    +
    + {{ end }} + {{ if .Withdrawals }} +
    +
    Beaconchain Withdrawals:
    +
    +
      + {{ range $i, $with := .Withdrawals }} +
    • + + {{ if $with.IsExitRequest }} + Exit request from validator + {{ formatPublicKey $with.ValidatorPubkey }} + {{ else }} + Withdrawal request of + {{ formatClCurrency $with.Amount config.Frontend.MainCurrency 8 true false false false }} + from validator + {{ formatPublicKey $with.ValidatorPubkey }} + {{ end }} +
    • + {{ end }} +
    +
    +
    + {{ end }}
    Value:
    diff --git a/templates/ethClientsServices.html b/templates/ethClientsServices.html index 9382f279ce..2cfb953d9f 100644 --- a/templates/ethClientsServices.html +++ b/templates/ethClientsServices.html @@ -137,7 +137,6 @@

    Execution Clients< - N/A {{ if $.User.Authenticated }} {{ end }} @@ -258,7 +257,7 @@

    Other

    Rocketpool Smart Node Go - {{ .RocketpoolSmartnode.ClientReleaseVersion }} - {{ .RocketpoolSmartnode.ClientReleaseDate }} + {{ .RocketpoolSmartnode.ClientReleaseVersion }} - {{ .RocketpoolSmartnode.ClientReleaseDate }} diff --git a/templates/gasnow.html b/templates/gasnow.html index 3ada10d750..be1ca2bd05 100644 --- a/templates/gasnow.html +++ b/templates/gasnow.html @@ -599,28 +599,28 @@
    Gas Price
    Rapid
    -

    ${ page.data.rapid | formatGWei(0) } GWei

    +

    ${ page.data.rapid | formatGWei(1) } GWei

    {{ if .Mainnet }}${ page.data.rapid | toGasPrice(21000, page.data.price, page.data.currency)} |{{ end }} 15 Seconds

    Fast
    -

    ${ page.data.fast | formatGWei(0) } GWei

    +

    ${ page.data.fast | formatGWei(1) } GWei

    {{ if .Mainnet }}${ page.data.fast | toGasPrice(21000, page.data.price, page.data.currency)} |{{ end }} 1 Minute

    Standard
    -

    ${ page.data.standard | formatGWei(0) } GWei

    +

    ${ page.data.standard | formatGWei(1) } GWei

    {{ if .Mainnet }}${ page.data.standard | toGasPrice(21000, page.data.price, page.data.currency)} |{{ end }} 3 Minutes

    Slow
    -

    ${ page.data.slow | formatGWei(0) } GWei

    +

    ${ page.data.slow | formatGWei(1) } GWei

    {{ if .Mainnet }}${ page.data.slow | toGasPrice(21000, page.data.price, page.data.currency)} |{{ end }} > 10 Minutes

    diff --git a/templates/heatmap.html b/templates/heatmap.html deleted file mode 100644 index ce4af394ff..0000000000 --- a/templates/heatmap.html +++ /dev/null @@ -1,90 +0,0 @@ -{{ define "js" }} - - - - - - - - - - -{{ end }} - -{{ define "css" }} -{{ end }} - -{{ define "content" }} - {{ with .Data }} -
    -
    -
    -
    -
    - {{ end }} -{{ end }} diff --git a/templates/index/networkStats.html b/templates/index/networkStats.html index 0a327ba6da..51b188aa19 100644 --- a/templates/index/networkStats.html +++ b/templates/index/networkStats.html @@ -23,13 +23,26 @@
    {{ if eq .EnteringValidators 0 }} -
    Pending Validators
    + {{ if .ElectraHasHappened }} +
    Joining / Leaving
    + {{ else }} +
    Pending Validators
    + {{ end }} {{ else }} -
    Pending Validators
    + {{ if .ElectraHasHappened }} +
    Joining / Leaving
    + {{ else }} +
    Pending Validators
    + {{ end }} {{ end }}
    - - / + {{ if .ElectraHasHappened }} + + / + {{ else }} + + / + {{ end }}
    diff --git a/templates/payment/pricing.html b/templates/payment/pricing.html index dfc99cfe89..56f94bcf7f 100644 --- a/templates/payment/pricing.html +++ b/templates/payment/pricing.html @@ -219,7 +219,7 @@

    0€ / mo - Get started + Get started
    diff --git a/templates/slot/attestations.html b/templates/slot/attestations.html index 8f047612db..76fca3054c 100644 --- a/templates/slot/attestations.html +++ b/templates/slot/attestations.html @@ -13,6 +13,10 @@
    Committee Index:
    {{ $attestation.CommitteeIndex }}
    +
    +
    Committee Bits:
    +
    {{ formatCommitteeBitList $attestation.CommitteeBits }}
    +
    Aggregation Bits:
    {{ formatBitlist $attestation.AggregationBits }}
    diff --git a/templates/slot/compoundingRequests.html b/templates/slot/compoundingRequests.html new file mode 100644 index 0000000000..bd498e5b9d --- /dev/null +++ b/templates/slot/compoundingRequests.html @@ -0,0 +1,28 @@ +{{ define "block_compounding_requests" }} + +
    +
    Showing {{ len .MoveToCompoundingRequests }} Processed Move-to-Compounding Requests
    +
    +
    + + + + + + + + {{ range .MoveToCompoundingRequests }} + + + + + + {{ end }} + +
    IndexValidatorAddress
    {{ .Index }}{{ formatValidatorInt64 .ValidatorIndex }}{{ formatEth1AddressFull .Address }}
    +
    +{{ end }} diff --git a/templates/slot/consolidationRequests.html b/templates/slot/consolidationRequests.html new file mode 100644 index 0000000000..249d9090a8 --- /dev/null +++ b/templates/slot/consolidationRequests.html @@ -0,0 +1,30 @@ +{{ define "block_consolidation_requests" }} + +
    +
    Showing {{ len .ConsolidationRequests }} Processed Consolidation Requests
    +
    +
    + + + + + + + + + {{ range .ConsolidationRequests }} + + + + + + + {{ end }} + +
    IndexSource IndexTarget IndexAmount
    {{ .Index }}{{ formatValidatorInt64 .SourceIndex }}{{ formatValidatorInt64 .TargetIndex }}{{ formatClCurrency .AmountConsolidated config.Frontend.MainCurrency 5 true false false false }}
    +
    +{{ end }} diff --git a/templates/slot/depositRequests.html b/templates/slot/depositRequests.html new file mode 100644 index 0000000000..952583193c --- /dev/null +++ b/templates/slot/depositRequests.html @@ -0,0 +1,34 @@ +{{ define "block_deposit_requests" }} + +
    +
    Showing {{ len .DepositRequests }} Deposit Requests
    +
    +
    + + + + + + + + + + + {{ range .DepositRequests }} + + + + + + + + + {{ end }} + +
    IndexValidator IndexPubkeyAmountWithdrawal CredentialSignature
    {{ .Index }}{{ formatValidatorInt64 .ValidatorIndex }}{{ formatPublicKey .ValidatorPubkey }}{{ formatClCurrency .Amount config.Frontend.MainCurrency 5 true false false false }}{{ formatWithdawalCredentials .WithdrawalCredentials true }}{{ formatHash .Signature }}
    +
    +{{ end }} diff --git a/templates/slot/exits.html b/templates/slot/exits.html index 1088a3fe55..05f78d14f7 100644 --- a/templates/slot/exits.html +++ b/templates/slot/exits.html @@ -10,7 +10,21 @@
    Signature:
    -
    0x{{ printf "%x" $exit.Signature }}
    +
    + {{ if gt (len $exit.Signature) 0 }} + 0x{{ printf "%x" $exit.Signature }} + {{ else }} + - + {{ end }} +
    +
    +
    +
    Via:
    +
    {{ $exit.TriggeredVia }}
    +
    +
    +
    Status:
    +
    {{ $exit.Status }}
    {{ end }} diff --git a/templates/slot/slot.html b/templates/slot/slot.html index 41d4c09111..9e40886d51 100644 --- a/templates/slot/slot.html +++ b/templates/slot/slot.html @@ -53,6 +53,16 @@ ` + element.CommitteeIndex + ` +
    +
    + + Committee Bits: + +
    +
    + ` + element.CommitteeBits + ` +
    +
    @@ -121,21 +131,59 @@ return att_card; } + let attestationOffset = 1; + let attestationLoading = false; + let attestationLoadingEl = null; + async function setupInfiniteScrollAttestations() { + if (attestationOffset === -1 || attestationLoading) return; + const infLoading = document.getElementById('attestationsTabPanel'); - if(infLoading) { - try { - const slot = {{ .Slot }} || 0; - const res = await fetch(`/slot/${encodeURI(slot)}/attestations`); - const data = await res.json(); - - for (let i = 1; i < data.length; ++i) { - infLoading.appendChild(getAttestationElement(data[i])); - } - } catch (err) { - console.error('error getting lazy attestations: ', err) + if (!infLoading) return; + + try { + attestationLoading = true; + + attestationLoadingEl = document.createElement('div'); + attestationLoadingEl.className = 'attestation-loading'; + attestationLoadingEl.innerText = 'Loading more...'; + attestationLoadingEl.style.padding = '1rem'; + attestationLoadingEl.style.textAlign = 'center'; + infLoading.appendChild(attestationLoadingEl); + + const slot = {{ .Slot }} || 0; + const res = await fetch(`/slot/${encodeURIComponent(slot)}/attestations?offset=${attestationOffset}`); + const data = await res.json(); + + if (attestationLoadingEl && attestationLoadingEl.parentNode) { + attestationLoadingEl.remove(); + attestationLoadingEl = null; + } + + if (data.length === 0) { + attestationOffset = -1; // no more data + return; + } + + for (let i = 0; i < data.length; ++i) { + infLoading.appendChild(getAttestationElement(data[i])); + } + + attestationOffset += data.length; + } catch (err) { + console.error('error getting lazy attestations: ', err); + + if (attestationLoadingEl && attestationLoadingEl.parentNode) { + attestationLoadingEl.remove(); + attestationLoadingEl = null; + } + + const infLoading = document.getElementById('attestationsTabPanel'); + if (infLoading) { infLoading.appendChild(getInfoElementAttestations('Error loading attestations...', 'red')); } + } finally { + attestationLoading = false; } } @@ -290,16 +338,32 @@ $("#block_blsChange").DataTable(blsChangeOpts) }) + function attestationScrollHandler() { + const infLoading = document.getElementById('attestationsTabPanel'); + if (!infLoading) return; + + const rect = infLoading.getBoundingClientRect(); + if (rect.bottom < window.innerHeight + 100) { + setupInfiniteScrollAttestations(); + } + } + { - var attestations_tab_loaded = false let atab = $('#attestations-tab') if(atab.length > 0) { + var removeAttestations = ()=>{ + document.removeEventListener('scroll', attestationScrollHandler); + } var activateAttestations = ()=>{ - if(!attestations_tab_loaded) { setupInfiniteScrollAttestations(); }; attestations_tab_loaded = true; + removeAttestations() + document.addEventListener('scroll', attestationScrollHandler); } atab.on('shown.bs.tab', activateAttestations); + atab.on('hidden.bs.tab', removeAttestations); if (atab.hasClass("active")){ activateAttestations() + } else { + removeAttestations() } } else { console.error('error getting #attestations-tab') @@ -396,9 +460,9 @@

    Deposits {{ .DepositsCount }} {{ end }} - {{ if gt .VoluntaryExitscount 0 }} + {{ if gt (len .VoluntaryExits) 0 }} {{ end }} {{ if gt .AttesterSlashingsCount 0 }} @@ -426,6 +490,26 @@

    Blobs {{ len .BlobSidecars }} {{ end }} + {{ if gt (len .ConsolidationRequests) 0 }} + + {{ end }} + {{ if gt (len .MoveToCompoundingRequests) 0 }} + + {{ end }} + {{ if gt (len .WithdrawalRequests) 0 }} + + {{ end }} + {{ if gt (len .DepositRequests) 0 }} + + {{ end }} +
    +
    Showing {{ len .WithdrawalRequests }} Withdrawal Requests
    +
    +
    + + + + + + + + + + + {{ range .WithdrawalRequests }} + + + + + + + + + {{ end }} + +
    IndexSource AddressIndexPubkeyAmountType
    {{ .Index }}{{ formatEth1Address .SourceAddress }}{{ formatValidatorInt64 .ValidatorIndex }}{{ formatPublicKey .ValidatorPubkey }}{{ formatClCurrency .Amount config.Frontend.MainCurrency 5 true false false false }}{{ .Type }}
    +
    +{{ end }} diff --git a/templates/user/settings.html b/templates/user/settings.html index ec44c6d294..3ca9c4ae37 100644 --- a/templates/user/settings.html +++ b/templates/user/settings.html @@ -851,7 +851,7 @@

    Usage | docs Usage | docs

    diff --git a/templates/validator/countdown.html b/templates/validator/countdown.html index 217e1f231c..1096f74d9e 100644 --- a/templates/validator/countdown.html +++ b/templates/validator/countdown.html @@ -28,17 +28,23 @@ } document.addEventListener('DOMContentLoaded', function() { + var rocket = {{ ne .Status "deposited" }} {{ if not .EstimatedActivationTs.IsZero}} var genesis = {{ .EstimatedActivationTs.Unix }} {{ else }} var genesis = {{ .ActivationTs.Unix }} {{ end }} + {{ if and (not .PendingDepositAboveMinActivation) (not .EstimatedIndexTs.IsZero) }} + genesis = {{ .EstimatedIndexTs.Unix }} + rocket = false + {{ end }} + var now = Math.round((new Date()).getTime() / 1000) var secondsLeft = genesis - now setTime(secondsLeft) // $('#estimated+span').removeClass('d-none') var countdownInterval = setInterval(function () { - if (secondsLeft<= 0) { + if (secondsLeft<= 0 && rocket) { clearInterval(countdownInterval) $('#rocket').removeClass('d-none') return diff --git a/templates/validator/heading.html b/templates/validator/heading.html index 82d5811880..76bd99230d 100644 --- a/templates/validator/heading.html +++ b/templates/validator/heading.html @@ -36,13 +36,6 @@

    {{ end }} - {{ if (and (ne .Status "deposited") (ne .Status "deposited_invalid") (ne .Status "pending")) }} - - - - - - {{ end }}