From 9aee0d6e6ef6a1e7c15686e50419b97ef38f616e Mon Sep 17 00:00:00 2001 From: Morty Date: Fri, 30 May 2025 05:13:03 +0800 Subject: [PATCH 01/31] feat: add blob uploader service --- common/testdata/blobdata.txt | 0 common/types/db.go | 50 ++++++ common/utils/ethereum.go | 20 +++ common/utils/ethereum_test.go | 8 + .../migrate/migrations/00027_ blob_upload.sql | 22 +++ .../00028_add_blob_upload_indexes.sql | 20 +++ go.work.sum | 3 +- rollup/cmd/blob_uploader/app/app.go | 154 ++++++++++++++++++ rollup/cmd/blob_uploader/main.go | 7 + rollup/go.mod | 18 ++ rollup/go.sum | 36 ++++ rollup/internal/config/l2.go | 15 ++ .../controller/blob_uploader/aws_s3_sender.go | 66 ++++++++ .../controller/blob_uploader/blob_uploader.go | 69 ++++++++ .../blob_uploader/blob_uploader_metrics.go | 29 ++++ rollup/internal/orm/batch.go | 22 +++ rollup/internal/orm/blob_upload.go | 120 ++++++++++++++ 17 files changed, 658 insertions(+), 1 deletion(-) create mode 100644 common/testdata/blobdata.txt create mode 100644 common/utils/ethereum.go create mode 100644 common/utils/ethereum_test.go create mode 100644 database/migrate/migrations/00027_ blob_upload.sql create mode 100644 database/migrate/migrations/00028_add_blob_upload_indexes.sql create mode 100644 rollup/cmd/blob_uploader/app/app.go create mode 100644 rollup/cmd/blob_uploader/main.go create mode 100644 rollup/internal/controller/blob_uploader/aws_s3_sender.go create mode 100644 rollup/internal/controller/blob_uploader/blob_uploader.go create mode 100644 rollup/internal/controller/blob_uploader/blob_uploader_metrics.go create mode 100644 rollup/internal/orm/blob_upload.go diff --git a/common/testdata/blobdata.txt b/common/testdata/blobdata.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/types/db.go b/common/types/db.go index 0c5b8d8f24..1e18be8b2c 100644 --- a/common/types/db.go +++ b/common/types/db.go @@ -326,3 +326,53 @@ func (s TxStatus) String() string { return fmt.Sprintf("Unknown TxStatus (%d)", int32(s)) } } + +// BlobUploadStatus represents the status of a blob upload +type BlobUploadStatus int + +const ( + // BlobUploadStatusUndefined indicates an undefined status + BlobUploadStatusUndefined BlobUploadStatus = iota + // BlobUploadStatusPending indicates a pending upload status + BlobUploadStatusPending + // BlobUploadStatusUploaded indicates a successful upload status + BlobUploadStatusUploaded + // BlobUploadStatusFailed indicates a failed upload status + BlobUploadStatusFailed +) + +func (s BlobUploadStatus) String() string { + switch s { + case BlobUploadStatusPending: + return "BlobUploadStatusPending" + case BlobUploadStatusUploaded: + return "BlobUploadStatusUploaded" + case BlobUploadStatusFailed: + return "BlobUploadStatusFailed" + default: + return fmt.Sprintf("Unknown BlobUploadStatus (%d)", int32(s)) + } +} + +// BlobStoragePlatform represents the platform a blob upload to +type BlobStoragePlatform int + +const ( + // BlobStoragePlatformUndefined indicates an undefined platform + BlobStoragePlatformUndefined BlobStoragePlatform = iota + // BlobStoragePlatformS3 represents AWS S3 + BlobStoragePlatformS3 + // BlobUploadStatusUploaded represents storage blockchain Arweave + BlobStoragePlatformArweave +) + +func (s BlobStoragePlatform) String() string { + switch s { + case BlobStoragePlatformS3: + return "BlobStoragePlatformS3" + case BlobStoragePlatformArweave: + return "BlobStoragePlatformArweave" + default: + return fmt.Sprintf("Unknown BlobStoragePlatform (%d)", int32(s)) + } +} \ No newline at end of file diff --git a/common/utils/ethereum.go b/common/utils/ethereum.go new file mode 100644 index 0000000000..7741817dff --- /dev/null +++ b/common/utils/ethereum.go @@ -0,0 +1,20 @@ +package utils + +import "crypto/sha256" + +// CalculateVersionedBlobHash computes the versioned hash for blob data +// Following Ethereum's approach where: +// version = 0x01 +// hash = sha256(blob) +// versionedHash = version + hash[1:] +func CalculateVersionedBlobHash(blobData []byte) [32]byte { + // Step 1: Compute SHA-256 hash of the blob data + hash := sha256.Sum256(blobData) + + // Step 2: Create versioned hash (version byte + hash[1:]) + var versionedHash [32]byte + versionedHash[0] = 0x01 // Version byte + copy(versionedHash[1:], hash[1:]) + + return versionedHash +} \ No newline at end of file diff --git a/common/utils/ethereum_test.go b/common/utils/ethereum_test.go new file mode 100644 index 0000000000..d8f97cf3ec --- /dev/null +++ b/common/utils/ethereum_test.go @@ -0,0 +1,8 @@ +package blob_uploader + +import "testing" + +// testCalculateVersionedBlobHash test function CalculateVersionedBlobHash +func testCalculateVersionedBlobHash(t *testing.T) { + +} \ No newline at end of file diff --git a/database/migrate/migrations/00027_ blob_upload.sql b/database/migrate/migrations/00027_ blob_upload.sql new file mode 100644 index 0000000000..94cca6b392 --- /dev/null +++ b/database/migrate/migrations/00027_ blob_upload.sql @@ -0,0 +1,22 @@ +-- +goose Up +-- +goose StatementBegin + +CREATE TABLE blob_upload ( + batch_index BIGINT NOT NULL, + + platform TEXT NOT NULL, + status SMALLINT NOT NULL, + updated_at TIMESTAMP NOT NULL DEFAULT now(), + + PRIMARY KEY (batch_index, platform), + FOREIGN KEY (batch_index) REFERENCES batch(index) +); + +COMMENT ON COLUMN blob_upload.status IS 'undefined, pending, uploaded, failed'; + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE blob_upload; +-- +goose StatementEnd \ No newline at end of file diff --git a/database/migrate/migrations/00028_add_blob_upload_indexes.sql b/database/migrate/migrations/00028_add_blob_upload_indexes.sql new file mode 100644 index 0000000000..da5644af9c --- /dev/null +++ b/database/migrate/migrations/00028_add_blob_upload_indexes.sql @@ -0,0 +1,20 @@ +-- +goose Up +-- +goose StatementBegin + +-- Add index on status for faster filtering by status +CREATE INDEX idx_blob_upload_status ON blob_upload(status); + +-- Add index on updated_at for faster sorting and filtering by time +CREATE INDEX idx_blob_upload_updated_at ON blob_upload(updated_at); + +-- Add index on (batch_index, status) for faster filtering by both fields +CREATE INDEX idx_blob_upload_batch_index_status ON blob_upload(batch_index, status); + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP INDEX IF EXISTS idx_blob_upload_status; +DROP INDEX IF EXISTS idx_blob_upload_updated_at; +DROP INDEX IF EXISTS idx_blob_upload_batch_index_status; +-- +goose StatementEnd \ No newline at end of file diff --git a/go.work.sum b/go.work.sum index b0190053fe..12ee3bc22f 100644 --- a/go.work.sum +++ b/go.work.sum @@ -646,6 +646,8 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.30.6/go.mod h1:PudwVKUTApfm0nYaPutOXa github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= +github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= +github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -1357,7 +1359,6 @@ github.com/scroll-tech/da-codec v0.1.1-0.20241014152913-2703f226fb0b h1:5H6V6yba github.com/scroll-tech/da-codec v0.1.1-0.20241014152913-2703f226fb0b/go.mod h1:48uxaqVgpD8ulH8p+nrBtfeLHZ9tX82bVVdPNkW3rPE= github.com/scroll-tech/da-codec v0.1.3-0.20250227072756-a1482833595f h1:YYbhuUwjowqI4oyXtECRofck7Fyj18e1tcRjuQlZpJE= github.com/scroll-tech/da-codec v0.1.3-0.20250227072756-a1482833595f/go.mod h1:xECEHZLVzbdUn+tNbRJhRIjLGTOTmnFQuTgUTeVLX58= -github.com/scroll-tech/da-codec v0.1.3-0.20250519114140-bfa7133d4ad1/go.mod h1:yhTS9OVC0xQGhg7DN5iV5KZJvnSIlFWAxDdp+6jxQtY= github.com/scroll-tech/go-ethereum v1.10.14-0.20240607130425-e2becce6a1a4/go.mod h1:byf/mZ8jLYUCnUePTicjJWn+RvKdxDn7buS6glTnMwQ= github.com/scroll-tech/go-ethereum v1.10.14-0.20240821074444-b3fa00861e5e/go.mod h1:swB5NSp8pKNDuYsTxfR08bHS6L56i119PBx8fxvV8Cs= github.com/scroll-tech/go-ethereum v1.10.14-0.20241010064814-3d88e870ae22/go.mod h1:r9FwtxCtybMkTbWYCyBuevT9TW3zHmOTHqD082Uh+Oo= diff --git a/rollup/cmd/blob_uploader/app/app.go b/rollup/cmd/blob_uploader/app/app.go new file mode 100644 index 0000000000..eb95bc16cf --- /dev/null +++ b/rollup/cmd/blob_uploader/app/app.go @@ -0,0 +1,154 @@ +package app + +import ( + "context" + "fmt" + "os" + "os/signal" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/scroll-tech/da-codec/encoding" + "github.com/scroll-tech/go-ethereum/ethclient" + "github.com/scroll-tech/go-ethereum/log" + "github.com/urfave/cli/v2" + + "scroll-tech/common/database" + "scroll-tech/common/observability" + "scroll-tech/common/utils" + "scroll-tech/common/version" + + "scroll-tech/rollup/internal/config" + "scroll-tech/rollup/internal/controller/relayer" + "scroll-tech/rollup/internal/controller/watcher" + rutils "scroll-tech/rollup/internal/utils" +) + +var app *cli.App + +func init() { + // Set up blob-uploader app info. + app = cli.NewApp() + app.Action = action + app.Name = "blob-uploader" + app.Usage = "The Scroll Blob Uploader" + app.Version = version.Version + app.Flags = append(app.Flags, utils.CommonFlags...) + app.Flags = append(app.Flags, utils.RollupRelayerFlags...) + app.Commands = []*cli.Command{} + app.Before = func(ctx *cli.Context) error { + return utils.LogSetup(ctx) + } + // Register `rollup-relayer-test` app for integration-test. + utils.RegisterSimulation(app, utils.RollupRelayerApp) +} + +func action(ctx *cli.Context) error { + // Load config file. + cfgFile := ctx.String(utils.ConfigFileFlag.Name) + cfg, err := config.NewConfig(cfgFile) + if err != nil { + log.Crit("failed to load config file", "config file", cfgFile, "error", err) + } + + subCtx, cancel := context.WithCancel(ctx.Context) + // Init db connection + db, err := database.InitDB(cfg.DBConfig) + if err != nil { + log.Crit("failed to init db connection", "err", err) + } + defer func() { + cancel() + if err = database.CloseDB(db); err != nil { + log.Crit("failed to close db connection", "error", err) + } + }() + + registry := prometheus.DefaultRegisterer + observability.Server(ctx, db) + + // Init l2geth connection + l2client, err := ethclient.Dial(cfg.L2Config.Endpoint) + if err != nil { + log.Crit("failed to connect l2 geth", "config file", cfgFile, "error", err) + } + + genesisPath := ctx.String(utils.Genesis.Name) + genesis, err := utils.ReadGenesis(genesisPath) + if err != nil { + log.Crit("failed to read genesis", "genesis file", genesisPath, "error", err) + } + + // sanity check config + if cfg.L2Config.RelayerConfig.BatchSubmission == nil { + log.Crit("cfg.L2Config.RelayerConfig.BatchSubmission must not be nil") + } + if cfg.L2Config.RelayerConfig.BatchSubmission.MinBatches < 1 { + log.Crit("cfg.L2Config.RelayerConfig.SenderConfig.BatchSubmission.MinBatches must be at least 1") + } + if cfg.L2Config.RelayerConfig.BatchSubmission.MaxBatches < 1 { + log.Crit("cfg.L2Config.RelayerConfig.SenderConfig.BatchSubmission.MaxBatches must be at least 1") + } + if cfg.L2Config.BatchProposerConfig.MaxChunksPerBatch <= 0 { + log.Crit("cfg.L2Config.BatchProposerConfig.MaxChunksPerBatch must be greater than 0") + } + if cfg.L2Config.ChunkProposerConfig.MaxL2GasPerChunk <= 0 { + log.Crit("cfg.L2Config.ChunkProposerConfig.MaxL2GasPerChunk must be greater than 0") + } + + l2relayer, err := relayer.NewLayer2Relayer(ctx.Context, l2client, db, cfg.L2Config.RelayerConfig, genesis.Config, relayer.ServiceTypeL2RollupRelayer, registry) + if err != nil { + log.Crit("failed to create l2 relayer", "config file", cfgFile, "error", err) + } + + minCodecVersion := encoding.CodecVersion(ctx.Uint(utils.MinCodecVersionFlag.Name)) + if minCodecVersion < encoding.CodecV7 { + log.Crit("min codec version must be greater than or equal to CodecV7", "minCodecVersion", minCodecVersion) + } + + chunkProposer := watcher.NewChunkProposer(subCtx, cfg.L2Config.ChunkProposerConfig, minCodecVersion, genesis.Config, db, registry) + batchProposer := watcher.NewBatchProposer(subCtx, cfg.L2Config.BatchProposerConfig, minCodecVersion, genesis.Config, db, registry) + bundleProposer := watcher.NewBundleProposer(subCtx, cfg.L2Config.BundleProposerConfig, minCodecVersion, genesis.Config, db, registry) + + l2watcher := watcher.NewL2WatcherClient(subCtx, l2client, cfg.L2Config.Confirmations, cfg.L2Config.L2MessageQueueAddress, cfg.L2Config.WithdrawTrieRootSlot, genesis.Config, db, registry) + + // Watcher loop to fetch missing blocks + go utils.LoopWithContext(subCtx, 2*time.Second, func(ctx context.Context) { + number, loopErr := rutils.GetLatestConfirmedBlockNumber(ctx, l2client, cfg.L2Config.Confirmations) + if loopErr != nil { + log.Error("failed to get block number", "err", loopErr) + return + } + l2watcher.TryFetchRunningMissingBlocks(number) + }) + + go utils.Loop(subCtx, time.Duration(cfg.L2Config.ChunkProposerConfig.ProposeIntervalMilliseconds)*time.Millisecond, chunkProposer.TryProposeChunk) + + go utils.Loop(subCtx, time.Duration(cfg.L2Config.BatchProposerConfig.ProposeIntervalMilliseconds)*time.Millisecond, batchProposer.TryProposeBatch) + + go utils.Loop(subCtx, 10*time.Second, bundleProposer.TryProposeBundle) + + go utils.Loop(subCtx, 2*time.Second, l2relayer.ProcessPendingBatches) + + go utils.Loop(subCtx, 15*time.Second, l2relayer.ProcessPendingBundles) + + // Finish start all blob-uploader functions. + log.Info("Start blob-uploader successfully", "version", version.Version) + + // Catch CTRL-C to ensure a graceful shutdown. + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt) + + // Wait until the interrupt signal is received from an OS signal. + <-interrupt + + return nil +} + +// Run rollup relayer cmd instance. +func Run() { + if err := app.Run(os.Args); err != nil { + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/rollup/cmd/blob_uploader/main.go b/rollup/cmd/blob_uploader/main.go new file mode 100644 index 0000000000..ffe2d3a65c --- /dev/null +++ b/rollup/cmd/blob_uploader/main.go @@ -0,0 +1,7 @@ +package main + +import "scroll-tech/rollup/cmd/rollup_relayer/app" + +func main() { + app.Run() +} diff --git a/rollup/go.mod b/rollup/go.mod index 27d7956dc2..f5895963bd 100644 --- a/rollup/go.mod +++ b/rollup/go.mod @@ -22,6 +22,24 @@ require ( require ( github.com/VictoriaMetrics/fastcache v1.12.2 // indirect + github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect + github.com/aws/aws-sdk-go-v2/config v1.29.14 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.80.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect + github.com/aws/smithy-go v1.22.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect github.com/btcsuite/btcd v0.20.1-beta // indirect diff --git a/rollup/go.sum b/rollup/go.sum index fa250a522d..a7bf6c02ef 100644 --- a/rollup/go.sum +++ b/rollup/go.sum @@ -8,6 +8,42 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= +github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14= +github.com/aws/aws-sdk-go-v2/config v1.29.14 h1:f+eEi/2cKCg9pqKBoAIwRGzVb70MRKqWX4dg1BDcSJM= +github.com/aws/aws-sdk-go-v2/config v1.29.14/go.mod h1:wVPHWcIFv3WO89w0rE10gzf17ZYy+UVS1Geq8Iei34g= +github.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM= +github.com/aws/aws-sdk-go-v2/credentials v1.17.67/go.mod h1:p3C44m+cfnbv763s52gCqrjaqyPikj9Sg47kUVaNZQQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.2 h1:BCG7DCXEXpNCcpwCxg1oi9pkJWH2+eZzTn9MY56MbVw= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.2/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.80.0 h1:fV4XIU5sn/x8gjRouoJpDVHj+ExJaUk4prYF+eb6qTs= +github.com/aws/aws-sdk-go-v2/service/s3 v1.80.0/go.mod h1:qbn305Je/IofWBJ4bJz/Q7pDEtnnoInw/dGt71v6rHE= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2cL6c9cuKV2Ps+G8= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/XvaX32evhproijJEZY= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4= +github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= +github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= diff --git a/rollup/internal/config/l2.go b/rollup/internal/config/l2.go index 5c02971d21..39385e30d8 100644 --- a/rollup/internal/config/l2.go +++ b/rollup/internal/config/l2.go @@ -24,6 +24,8 @@ type L2Config struct { BatchProposerConfig *BatchProposerConfig `json:"batch_proposer_config"` // The bundle_proposer config BundleProposerConfig *BundleProposerConfig `json:"bundle_proposer_config"` + // The blob_uploader config + BlobUploaderConfig *BlobUploaderConfig `json:"blob_uploader_config"` } // ChunkProposerConfig loads chunk_proposer configuration items. @@ -46,3 +48,16 @@ type BundleProposerConfig struct { MaxBatchNumPerBundle uint64 `json:"max_batch_num_per_bundle"` BundleTimeoutSec uint64 `json:"bundle_timeout_sec"` } + +// BlobUploaderConfig loads blob_uploader configuration items. +type BlobUploaderConfig struct { + AWSS3Config *AWSS3Config `json:"aws_s3_config"` +} + +// AWSS3Config loads s3_uploader configuration items. +type AWSS3Config struct { + Bucket string `json:"bucket"` + Region string `json:"region"` + AccessKey string `json:"access_key"` + SecretKey string `json:"secret_key"` +} \ No newline at end of file diff --git a/rollup/internal/controller/blob_uploader/aws_s3_sender.go b/rollup/internal/controller/blob_uploader/aws_s3_sender.go new file mode 100644 index 0000000000..bfbe32cfd0 --- /dev/null +++ b/rollup/internal/controller/blob_uploader/aws_s3_sender.go @@ -0,0 +1,66 @@ +package blob_uploader + +import ( + "bytes" + "context" + "fmt" + "time" + + "scroll-tech/rollup/internal/config" + + "github.com/aws/aws-sdk-go-v2/aws" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/s3" +) + +// S3 Uploader is responsible for uploading data to AWS S3. +type S3Uploader struct { + client *s3.Client + bucket string + region string + timeout time.Duration +} + +func NewS3Uploader(cfg *config.AWSS3Config) (*S3Uploader, error) { + // load AWS config + var opts []func(*awsconfig.LoadOptions) error + opts = append(opts, awsconfig.WithRegion(cfg.Region)) + + // if AccessKey && SecretKey provided, use it + if cfg.AccessKey != "" && cfg.SecretKey != "" { + opts = append(opts, awsconfig.WithCredentialsProvider( + credentials.NewStaticCredentialsProvider( + cfg.AccessKey, + cfg.SecretKey, + "", + )), + ) + } + + awsCfg, err := awsconfig.LoadDefaultConfig(context.Background(), opts...) + if err != nil { + return nil, fmt.Errorf("failed to load default config: %w", err) + } + + return &S3Uploader{ + client: s3.NewFromConfig(awsCfg), + bucket: cfg.Bucket, + region: cfg.Region, + timeout: 30 * time.Second, + }, nil +} + +// UploadData uploads data to s3 bucket +func (u *S3Uploader) UploadData(ctx context.Context, data []byte, objectKey string) error { + uploadCtx, cancel := context.WithTimeout(ctx, u.timeout) + defer cancel() + + _, err := u.client.PutObject(uploadCtx, &s3.PutObjectInput{ + Bucket: aws.String(u.bucket), + Key: aws.String(objectKey), + Body: bytes.NewReader(data), + ContentType: aws.String("application/octet-stream"), + }) + return err +} diff --git a/rollup/internal/controller/blob_uploader/blob_uploader.go b/rollup/internal/controller/blob_uploader/blob_uploader.go new file mode 100644 index 0000000000..0b0fc03cf9 --- /dev/null +++ b/rollup/internal/controller/blob_uploader/blob_uploader.go @@ -0,0 +1,69 @@ +package blob_uploader + +import ( + "context" + "crypto/sha256" + "fmt" + + "github.com/prometheus/client_golang/prometheus" + "github.com/scroll-tech/go-ethereum/log" + "gorm.io/gorm" + + "scroll-tech/common/types" + + "scroll-tech/rollup/internal/config" + "scroll-tech/rollup/internal/orm" +) + +// BlobUploader is responsible for uploading blobs to blob storage services. +type BlobUploader struct { + ctx context.Context + + cfg *config.BlobUploaderConfig + + s3Uploader *S3Uploader + batchOrm *orm.Batch + + metrics *blobUploaderMetrics +} + +// NewBlobUploader will return a new instance of BlobUploader +func NewBlobUploader(ctx context.Context, db *gorm.DB, cfg *config.BlobUploaderConfig, reg prometheus.Registerer) (*BlobUploader, error) { + var s3Uploader *S3Uploader + var err error + if cfg.AWSS3Config != nil { + s3Uploader, err = NewS3Uploader(cfg.AWSS3Config) + if err != nil { + return nil, fmt.Errorf("new blob uploader failed, err: %w", err) + } + } + + blobUploader := &BlobUploader{ + ctx: ctx, + cfg: cfg, + s3Uploader: s3Uploader, + batchOrm: orm.NewBatch(db), + } + + blobUploader.metrics = initblobUploaderMetrics(reg) + + return blobUploader, nil +} + +func (b *BlobUploader) UploadBlobToS3() { + // get un-uploaded batches from database in ascending order by their index. + dbBatches, err := b.batchOrm.GetFirstUnuploadedAndFailedBatch(b.ctx, types.BlobStoragePlatformS3) + if err != nil { + log.Error("Failed to fetch unuploaded batch", "err", err) + return + } + + // nothing to do if we don't have any pending batches + if dbBatches == nil { + return + } + + // upload data to s3 bucket + b.s3Uploader.UploadData(b.ctx, ) +} + diff --git a/rollup/internal/controller/blob_uploader/blob_uploader_metrics.go b/rollup/internal/controller/blob_uploader/blob_uploader_metrics.go new file mode 100644 index 0000000000..a041400013 --- /dev/null +++ b/rollup/internal/controller/blob_uploader/blob_uploader_metrics.go @@ -0,0 +1,29 @@ +package blob_uploader + +import ( + "sync" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +type blobUploaderMetrics struct { + rollupBlobUploaderUploadToS3Total prometheus.Counter +} + +var ( + initBlobUploaderMetricsOnce sync.Once + l1RelayerMetric *blobUploaderMetrics +) + +func initblobUploaderMetrics(reg prometheus.Registerer) *blobUploaderMetrics { + initBlobUploaderMetricsOnce.Do(func() { + l1RelayerMetric = &blobUploaderMetrics{ + rollupBlobUploaderUploadToS3Total: promauto.With(reg).NewCounter(prometheus.CounterOpts{ + Name: "rollup_blob_uploader_upload_to_s3_total", + Help: "The total number of upload blob to S3 run total", + }), + } + }) + return l1RelayerMetric +} diff --git a/rollup/internal/orm/batch.go b/rollup/internal/orm/batch.go index 584792fe18..1175122387 100644 --- a/rollup/internal/orm/batch.go +++ b/rollup/internal/orm/batch.go @@ -263,6 +263,28 @@ func (o *Batch) GetBatchByIndex(ctx context.Context, index uint64) (*Batch, erro return &batch, nil } +// GetFirstUnuploadedAndFailedBatch retrieves the first batch that either hasn't been uploaded to any blob storage service +// or has failed upload status. The batch must have a commit_tx_hash (committed). +func (o *Batch) GetFirstUnuploadedAndFailedBatch(ctx context.Context, platform types.BlobStoragePlatform) (*Batch, error) { + db := o.db.WithContext(ctx) + db = db.Model(&Batch{}) + db = db.Joins("LEFT JOIN blob_upload ON blob_upload.batch_index = batch.index") + db = db.Where("batch.commit_tx_hash IS NOT NULL") + db = db.Where("blob_upload.batch_index IS NULL OR blob_upload.status = ?", types.BlobUploadStatusFailed) + db = db.Where("blob_upload.platform = ?", platform) + db = db.Order("batch.index ASC") + db = db.Limit(1) + + var batch Batch + if err := db.First(&batch).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, fmt.Errorf("Batch.GetFirstUnuploadedAndFailedBatch error: %w", err) + } + return &batch, nil +} + // InsertBatch inserts a new batch into the database. func (o *Batch) InsertBatch(ctx context.Context, batch *encoding.Batch, codecVersion encoding.CodecVersion, metrics rutils.BatchMetrics, dbTX ...*gorm.DB) (*Batch, error) { if batch == nil { diff --git a/rollup/internal/orm/blob_upload.go b/rollup/internal/orm/blob_upload.go new file mode 100644 index 0000000000..2a09ebe996 --- /dev/null +++ b/rollup/internal/orm/blob_upload.go @@ -0,0 +1,120 @@ +package orm + +import ( + "context" + "fmt" + "time" + + "gorm.io/gorm" + + "scroll-tech/common/types" +) + +// BlobUpload represents a blob upload record in the database. +type BlobUpload struct { + db *gorm.DB `gorm:"-"` + + BatchIndex uint64 `json:"batch_index" gorm:"column:batch_index;primaryKey"` + Platform string `json:"platform" gorm:"column:platform;primaryKey"` + Status int16 `json:"status" gorm:"column:status"` + UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` +} + +// NewBlobUpload creates a new BlobUpload database instance. +func NewBlobUpload(db *gorm.DB) *BlobUpload { + return &BlobUpload{db: db} +} + +// TableName returns the table name for the BlobUpload model. +func (*BlobUpload) TableName() string { + return "blob_upload" +} + +// InsertBlobUpload inserts a new blob upload record into the database. +func (o *BlobUpload) InsertBlobUpload(ctx context.Context, batchIndex uint64, platform string, status types.BlobUploadStatus) error { + blobUpload := &BlobUpload{ + BatchIndex: batchIndex, + Platform: platform, + Status: int16(status), + UpdatedAt: time.Now(), + } + + db := o.db.WithContext(ctx) + if err := db.Create(blobUpload).Error; err != nil { + return fmt.Errorf("BlobUpload.InsertBlobUpload error: %w, batch index: %v, platform: %v", err, batchIndex, platform) + } + return nil +} + +// UpdateBlobUploadStatus updates the status of a blob upload record. +func (o *BlobUpload) UpdateBlobUploadStatus(ctx context.Context, batchIndex uint64, platform string, status types.BlobUploadStatus) error { + db := o.db.WithContext(ctx) + db = db.Model(&BlobUpload{}) + db = db.Where("batch_index = ? AND platform = ?", batchIndex, platform) + + updates := map[string]interface{}{ + "status": status, + "updated_at": time.Now(), + } + + if err := db.Updates(updates).Error; err != nil { + return fmt.Errorf("BlobUpload.UpdateBlobUploadStatus error: %w, batch index: %v, platform: %v", err, batchIndex, platform) + } + return nil +} + +// GetBlobUploadByBatchIndexAndPlatform retrieves a blob upload record by batch index and platform. +func (o *BlobUpload) GetBlobUploadByBatchIndexAndPlatform(ctx context.Context, batchIndex uint64, platform string) (*BlobUpload, error) { + db := o.db.WithContext(ctx) + db = db.Model(&BlobUpload{}) + db = db.Where("batch_index = ? AND platform = ?", batchIndex, platform) + + var blobUpload BlobUpload + if err := db.First(&blobUpload).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return nil, fmt.Errorf("BlobUpload.GetBlobUploadByBatchIndexAndPlatform error: %w, batch index: %v, platform: %v", err, batchIndex, platform) + } + return &blobUpload, nil +} + +// GetPendingBlobUploads retrieves all pending blob upload records. +func (o *BlobUpload) GetPendingBlobUploads(ctx context.Context) ([]*BlobUpload, error) { + db := o.db.WithContext(ctx) + db = db.Model(&BlobUpload{}) + db = db.Where("status = ?", types.BlobUploadStatusPending) + db = db.Order("batch_index ASC") + + var blobUploads []*BlobUpload + if err := db.Find(&blobUploads).Error; err != nil { + return nil, fmt.Errorf("BlobUpload.GetPendingBlobUploads error: %w", err) + } + return blobUploads, nil +} + +// GetFailedBlobUploads retrieves all failed blob upload records. +func (o *BlobUpload) GetFailedBlobUploads(ctx context.Context) ([]*BlobUpload, error) { + db := o.db.WithContext(ctx) + db = db.Model(&BlobUpload{}) + db = db.Where("status = ?", types.BlobUploadStatusFailed) + db = db.Order("batch_index ASC") + + var blobUploads []*BlobUpload + if err := db.Find(&blobUploads).Error; err != nil { + return nil, fmt.Errorf("BlobUpload.GetFailedBlobUploads error: %w", err) + } + return blobUploads, nil +} + +// DeleteBlobUpload deletes a blob upload record. +func (o *BlobUpload) DeleteBlobUpload(ctx context.Context, batchIndex uint64, platform string) error { + db := o.db.WithContext(ctx) + db = db.Model(&BlobUpload{}) + db = db.Where("batch_index = ? AND platform = ?", batchIndex, platform) + + if err := db.Delete(&BlobUpload{}).Error; err != nil { + return fmt.Errorf("BlobUpload.DeleteBlobUpload error: %w, batch index: %v, platform: %v", err, batchIndex, platform) + } + return nil +} From 4bf419693ed2e1db9fbc5b3819a47ac3ecbf651a Mon Sep 17 00:00:00 2001 From: Morty Date: Sat, 31 May 2025 06:45:10 +0800 Subject: [PATCH 02/31] feat: add blob uploader aws s3 --- common/testdata/blobdata.json | 4 + common/testdata/blobdata.txt | 0 common/utils/ethereum.go | 37 ++++---- common/utils/ethereum_test.go | 57 ++++++++++-- .../controller/blob_uploader/blob_uploader.go | 93 ++++++++++++++++++- .../{aws_s3_sender.go => s3_sender.go} | 0 6 files changed, 163 insertions(+), 28 deletions(-) create mode 100644 common/testdata/blobdata.json delete mode 100644 common/testdata/blobdata.txt rename rollup/internal/controller/blob_uploader/{aws_s3_sender.go => s3_sender.go} (100%) diff --git a/common/testdata/blobdata.json b/common/testdata/blobdata.json new file mode 100644 index 0000000000..9e62ec9521 --- /dev/null +++ b/common/testdata/blobdata.json @@ -0,0 +1,4 @@ +{ + "versionedBlobHash": "016170b9b2e16e928c0179bdfb14a718f83145f9e49537a0b543699d6767b391", + "blobData": "000701daab01a04dc5050024f9045c59084eb9328c1a4004442d7e683752658000fbb4aa5fb043c29dda103c93ca00000000e8910b011f00000000681a13020000f02fb200989680000100681a13050002082bdc010b030e061101140517031a00041d40012023092627730729042c012f14ee32043538073b1107033e02410100440347024a014d0250281a535601592fe05c5f046234a201686b026e0471020074057724f3027a037d028020fe04830186898c028f019205952fd10198049b00059e235c08a102a405a7aa03ad01b006b3148c04b603b908bc01bf03c209c50015c82e5b1bcb1cce08d13cd407d405d704da4ccd07dd04e008e30be637f00500e903ec02ef23d303f205f506f801fb03fe0614010204333e07010a51650d020010041301160219011c021f225b1f04250228042b6f92052e0231033456500200373a3d0c4004430146024971fc014c4f035276c5015659035c736a015f0262000165744668026b016e037167e27401777a7e818469730287018a028d9076f80001939602996255019c029fa201a5a8abaeb1b4645d02b703ba01bd02c001c300c6c966e0cdd05a15d3d6d9dc45cbdfe2e504e802eb05ee01f102f43a8e01f700fa55f1fd150306605a0209010c0f021266331518011ba01e21631a272b2f32003502386590013b023e01415c484a4d505356595c5f4f3962026568016b036e000271037453f502770f7a017d545d80830286894c8d018c028f03920195039800019b029e48f6a101a4a7aaad02b001b3b6b9bc4592bfc202c55601c8cb439c00ce02d101d4d7dadde0e4e702eaed59de01f265bff5f8fb03fe0216015e94010004074d5a0a0d0610011316191c1f3fb00922012502285f61062b032e06310400345cea0337063a043d4b7f06400243084662330549044c074f045264ab55080058035b0b5e08610764685e0167056a026d017003000002f87383082750158400010834e582520894fb5003e70ca66728281d151b311d40d4bb1054f98659a500dc5a112680c001a00b55e7bf6222b9991b14500f0c06e237f8b09dc8bbf04100d470ac862157dbc580a0473bde031e5cadf6b7de6b3dae7b1edfd2bdc4d0a80009015d236aae3d1c3a7743f9020e81c98401374f508305cba494aaaaaaaacb0071bf2c8cae522ea5fa455571a7410680b901a4a15112f90000000140f55bec009cafdbe8730f096aa55dad6d22d440990001a43ab00107b48d10a2c72400000083104ec4a06dc1aec7a9e2d20c7a53852b770e6498d0125b2829a68e15af8800e9f193c68ea4a06e8ba84ecfe876a89eb3d736e31d7ea990305aa5ee10344800b5bbbbd8594f278102f8ea8308275083093bf2648401e05fc882c0b894028000b84439455d68f4565b214b31c7f838f703e1a0fd0fa31df33b74343804002b00d42904a8c44a2dcbd3183a6da4a6ea591f04e31880a0f899aa9a673ac68c7f00723195bc7b211ed5be01bb11468cbc280b9c1d28f7cbe7a042355ddfa9ef5f009482fe0643da1bd7fde4b3f56391a6986e90cc239f49f234b5f9020e82046c0083f02d208303032706efdbff2a14a7c8e15944d1f4a48f9f95f663a46948b7000000000069245c7e031d2039a636647a3167ac02292789d79e2991020930f6003fe55a30d0f8042da03beaf62394de84060f124dc89a60806c36cf6c0d5e3600c63ce82526b70889307002f86c4583471f9e84016758da82520894c8df488a004613de5ca70c7bd1c4c4c7183ac0392b8080c001a0760cbaa3d6717e86cea4004b3135ffbb2951d9905bb48f8c86688e28136fe8e7b3a04e4457d91a23e63e001e7c4906a7871f707002c0b5c5cfe1a55caf68dea4abc307f9046e82d58c8300f02c408303cb4994145234c9c1f1583e710bdc2926d6e97e4523ef9380b904000449a1a400c060019ad531034a4f810000050000a001200001a002020041380082c39db50097b4ecd063ae0202318b3218480d4a4df099fa8c1aa2c1df6f4800077b06f90128a3c7980ca5812789c880419ab9bfd912cc5f7fa7a1d175900e0041f718f4030283a9a83277f9da9d50a48385746c9eb7af0059c8e4df347e2600d240584a9602e0b841a2d78ea459eb7255f42afdf7efeabe289e9ed80fa0a1004d3e5d4111eaaecc516a7491aa3e1386aa409f7f35280944a76060d96bf116005d02a0add471f2b53969b3d238c52d3c3e3304a8135ed7ac64aeab5c9b81110027e427d1c041f9849dd85c09a5bd5560992a601e2af9933534395d7220ad11003ca9c7827618fd7307802c8f7688d2008c68da6e53570a622fba0d8f47b2af00a7afe25dcf1cdacd416e575d7f7cac0549c7e46319c868aaae804807b6ae3d00704c2b831194e1f2f0df353115a5aed1ec8f786937923fd1221a063a19c72a00cfbb2cb348b9649e248c8365f12f4329f0c53cd999fadc6a1a50d1627ccfd4004addc36df9baaa1ed398b5a0578012af94538bab5c9b4ccf07442811bc138000e6cefeebe86ab302038d05077ff9012f830c805f8402d341a383035f30943c002269811836af69497e5f486a85d7316753cf6280b8c43161b7f6010eaf4b0f002859cc037e110000001083104ec3a04b7ed28023e9d23b586828ddc494f75600f4bceeb251e9ff97d4c036d0d4ad8b32a0611eed65986002932519014852c60028193c91f1d32032a9cd4fb854c72a57682d6024683ce521ef522100b14ddb00eac4a0a50625a8397344c127ad2d17dcc7e94159478a3a03c175d126b6f4fa0076bd0adaa02a4322e744dd9e44cb5967671dce7c2ce6af6fbbe416cd27060600ffab690fcd0661006a0f49278f28c856b50b9cbe5cc3a07f64e60b29ba508f00ce0a60d9522bcff315067a8afc25c70d6391ee992501b2b0a07c5fac719b690041e2db4ffe8743fafb2c34ce7b6b23f5f6e7a372887428d555b4626d002bbc00614e83a0f00b4785b13ae82c63bed233a88736a0b039fc5e86dd774e92331600b8d4303604804106355d9ba05362272bbb092424e6c356b400ed674f642dc5008663d1e702c9787c935523613d63016a02252cb18ebebc570104d9e2d1c4a000820ba9378dade520626051de735748d7693e552c8ed81abf0b1068a6982dda001aa066364ae17201be5470b39bf0ae8352486210147383493f9788cfe62ac100cef3966442056bc75e2d63109dde3009aee7ee7ca5b0148c6a42457f35f03d003371d93295519270c522da60b4b1ec9fa041b397e5e0251e7ae38269d38ffe0086d3d7e95c1e6799f0720256bdcbf0de8d2a6500b70702823ac3a0543c82df00dc8f2116cd1d7ce86459254be3d2efaaf80573480ab5e3ccd9915200a06fb50052b9a02bc3b60c969a3aea5fa11e14db26c800636f1fa57fbee9c6c7d8a4f90002af8248338402a1d5ed83042cd09446a8d62bc20086653f318025d1966896005345116980b90244b61d27fc30937f5cde93df8d48acaf7e6f5d8d8a31f63600006001a45ae401dc0120000000e404e45aaf530401f40000005c5cdffaea1f00b802bad61266ef37b2fff653327bf3d626701e6d9823efb011c5fde6c33579001d17904525a04cb5207e7d89a40dd0260b1a1b66fd924e626f9822dc1d014c00f92af825aec8c302f90193015e14808401383904830f4240946d422522e50a0076fcdb3e71913736da6d43e6609902b901241b9eba3e000006d29159c002010001271026f2813df550a32d4a9d42010d057386429ad2328ed9010004566bf8003399e4f750728d1ef57008aedda00e71c001a078b02a888764368b933979d2006e9bbb60cf56f8b86934defbe4f7b919d63ca71fa0061e44c54461a7a5f385002e6936943ddaa3be09d563a604b963e8575fce01f7bc02f901d28308275082000ca107521f94c9c35e593842c3d5e71304b2291e204583226e2a80b9016441002658e501020027004f00cd00e500e900ed00f1010101115300903c3c00d10000d1feec4e40c170ef3736dc9a29389fb8cd7ed1240900d100d10005f0f0ccdc0088ff460660a81c8ba4c080a08e567a0fd156be59b82ebebe4577a6f2672b70008e20dd70f52d3f6bfaee551324a07a2c128c03a7b68f4d6448d05867a8073f008fa8d6c95983ccdb32b8de90bd81a00bcd03bb3faf640106346c51d0072e1600c2ec8351e8066a003166520e4e0248dd7dcb0fa0707f0e6c4c8c08d567494400c15cb4d841eff17e3ee6fee1a0b36379143320e25c02f901328302554d092700c094802b65b5d9016621e66003aed0b16615093f328b80b8c4a005974edeb200ec4fde145d19046dc5ada3365281f6e5900d53681a13c5c001a08ae3fdb88f00435c4430c78e7efb527d1e6748a961a30e13720e67c2782d6a77f4a00e26800000dccd335cca0b25c751d57693d9aab340b4b3bb1c88dd4d80657b7ee602f800cac5db200b20941c40be10077f686fdfd9e6373c5611d02061ab3480b8044700a0604800000750a1a543caa6ad830800020b806efdbff2a14a7c8e15944d1f004a48f9f95f663a4813df550a32d4a9d42010d057386429ad2328ed910e0115009afe0ce6793dd41ca347bd5c022bb768039c40c080a03dd3880a8fe9f04fea0080b4da6ec8bc0cb73a9063f08e2bc392cfa38f7c8ad568a032b434d120b0c70074401ae747b57eb98880ddf3737d2a2c666ce802b34e403744f9022f82f086008403344a1d8305c06594111111568011ba61bb637fc8fa09d5e803a861408000b901c4375237450000003c1bca5a656e69edcd0d4e36bebb3fcdaca60c53080012002866f56ae96f21260b39c3a5cec18ab5c2952b13dad31a85705f5e387c00333274270b271f000026f7681a14c7ee73b812a708392de0520ac176201f30009aa8f7f31bf12af25c920c88beaba270a062220b25ba9cf948112f0028c88400d9bc843e7bffba738e42e8ce57b0a308897202f803cddf6484010830a28262007094bf4f10970d15b5447a7523520e8dc734ebde651087e4339a2d56a1b8a00048bc4cce1ee80736c36b9d1ed94484e37ea872e3942281fa2b21f8effcdc070018c001a0db8a73de148ad89fbcd6619ddadf238f431657af1061a10203ac0000fb1e4e55f3a07c90897d94edb3a6c6811e533acf0888de858d9f81ecad60c2002dafe62beb7a76f901ae82a09601c6c194aaa11500dddb2b67a90d1a154dfb007eabb518eae680b901447284c45b03e0da810f07da1eace7f044afd081f858003dbe9cd36e13a2ae9fd9f40f8cc7d3b3944c57f3919c7fe3f42a5f04a052fc00050082addda77bbcae3b96a2110302ee0bb85cf1833e931110c1614f20b9bd00c91dd0aaa31705412ff1d27e57ca62332c87b9a0190a52d8244ac3dfacf8b600ba39c29c307ccd520660c5ba19173dc8f4cef5c6d6f9020f82018a8402da7d002c8308b056002a0e0577db9bed936bc4c3a0bc7c0c2fa96c1376d027907bf4003e44bc88972cdde6a588379f2525ef63a7e82ea00f593734f5afc5b5f0c7dc0046ae97afb5cbb5124a92d41aba1cfc2b2331d43cfbf3581cae6772257498080001a09be9af7e251670eb2c60709084350cb7fa7ba67e4ced3886be3925864f004526a1a062371994c0f319c5524f000e46d7bcb24f617f400379d6ed95f138005acece4c75f8d682175784040169648304de6594f6687cd56aec1a0faaba1200feb6781398ab47fb2180b86c681a13220221a0ff4953040a04985705330133000100049857054637d496c7c56e670608bd40e0d1f257518ac42029653d5ca40068b43b983ea9632b7cbb788d48d6f4dc3b17e5290ff13bb47d928ea02b6d87006f0f169cb82912b1e6520c15d436880e04643248ed0abcc9cb974bf5606d8300f0311383030ced01013f4e0ffff5433e2b3d8211706e6102aa947112cfc3a000b38ca7bec79212b575e4a220859ad5a13d8187c3a764022baf2d9ff66869f70073a064dc4c8acc61f48032eaa747af1f54964a8211a9352ef15afd2f9cfac600f78f1bf9022f82dadb84062d4597830695fb06e49814462c1e636c599597230030057693843f140c00000de16c99c858805fefd2010c1018062ecd1f14274000ccf2900a9592f06957e87d36b895b41762db590c8540ec66c4044321a02fbc00f8ffc1d626026e5d9b39b4db3ace0ceb48b0e3c996fb56897a2f848dd9aef90002af821d578402a1d2e283042d1894a81f6c97fd913bc4f414a6e757291fe6001c6d083349a2cdca2a4e424804d036e1d3527d6276baa0cde7f35299dc8f1a00a5f6582baa3840accc0daa1e8575a0269fa03d41a2b38344ea46bd09cff1de00c603af017b836d126c5bc572c5e854f161891b6684025ab3a88305227c4bb800bb677583974805751fd5c4a04b968b7fde608219b14a324422d3d601a842900029e54a91455a125df730a19b0ba00616955a37068a5f8e6e6712ebd30ce560009b83c836a7886568e4390c5c41e6874e3839336a50a3add9dc16d54f5c9f3f00b624bbf273fdf7d4c080a0b7b6f218a903260f22e38a54d6299484bd786b0b00734a1e41592250d8e88791aca069cbeb7b32360c7e12e4c948cca7d9cc9c5900ac623a41507a114a3d4ced7546f50de23838d5ff72de280d4ea3f707a101a0002549f13ca59342cb69ea1d6656c8e2621b811eb758fa37204eb7bbdc4ce78100f8a02b3d6b2216bfa9f46fd37101546a23e6aba54cac3615f70fcde7eb771b001f9fdf0bce3980a023bb42f13d0f86f3dd61c71e38c20b2b9371212176b3e700076741f1850e2f76b3a075846d2c73d7d3466d072105c79bd6dc1d977d9db100b4b5067f40bc21b607eb4bc7e83839853d46d4ebe303f221c6a1c001a015a100f4ec8a72d002046d93641df96c27c26a2c8fa76dd1e223fc7f23985c8358a0005328b81549c1491010a22425d5cea13ab329d7b3de05c0e2cdc719f7ac326b007dd2e906460456f5c5182c1e4280a09aacd038481961e616c2a4389d472780004f27f6051ceb09eacc49cdedc853fa88a0342d5e0cdec2c93d3bcd638da2d60077d2b0706d9e2b50e931d5a8d1225d6f608b153839177622706d547c4828ef0014d47b6580efb0cc01f92452b08071d956734ce200752f3621a04b9c7c8b8200904740e1f8cbb48ab9adc3caf1f34065f6fcea520b330b89d2af2af8b481bd008401c9c3808304d4a494c0fabf14f8ad908b2dce4c8aa2e7c1a6bd06995787002379523b299840b8446b634a00000a21530f6ffe40f5008487e70b45d655b90086aa2dd6d794b9721753eadf2c6f76c49b5c2ead11b0a01a35b06ec7c6e6fa006e341c275b79575d4691ac240363e969f3d2675aaa582efaf456e466f9c6200022a07abf6d6a2916b27575b78fcf2f09e02f39adc20b618820a16b70f4226b002e0e887197a0034416adc4d7924c1646ca0055bc7b7f83d3c9d470795f388c0063e3b714a6c1b94fc8f70d22fcc3f41508db550a8b0668ee6350f0aed7c00100a00b19ed3597830255a1df1228129b2514a3559a5b930cc438d414dd44b15f00e243a05d792c781891cba471ced79876cf35541a085ff6f07de036c7d697b100827c39bd160575c0b3252ae0d4810f50eadcd16da439e34bf92acaa554367000ae8c6a76e3268db308c3a0591431f19c13bd8441252312914010675a39e8cc0088b89a9d632d028fd0940038cfb4a237a8ddb9a401a053df7450ae27ae1bef005a59a1cfd06b6bc8d328ef1af38debed5e15c8b317268ca025fe65b14c04c20007917a0f03611a1da314a62700e97882212fee15a2b1b72b236e03fc9a4f9200c34f6958968056fa70c4a03f996ca3efaa946f599d56c330cb095750bdb8a300fae94f73b3a40c3464d8f27ca00222d519e9db92b0c0ca4b6227622d55757000bea2b8d811ab46344efe2cb68de802f90413c1a20185174876e8008307a120009485dd2b093ea221b732752058af61598d6636272280b903a4c98075390000000260030001010101ccf17c9103d9a9bc748a16cd6b95006f0001e5a6060106000304070002080905600000034e344345ef6049205f4d714064d3dd7ab44e0400ac2624784cce9f40b9c8aaca337389af926e2112f5fa3ac9c3658ba6de208f00cf2f740cc77b9e85847c8ea37b55e9420d994ca4a32a62e5c5b026844945b80011c18dd39634d70dad6c2ee2e36c802e095b94cdcad53b6effe3b57beefe1b00049109f921822b0c0944173b6bdff1ce4e1c4d9cc97f8fca11336e913ee8d100356e359700045bbf1deb7f1621a1c465c4983aa6b0504e95e4caaa7c6e7e3a003221f12b8345cc4af698dfc2bdfc60336aa6a6fe7333d0eb756397f5685b5700fbb1507d0198a30a67c21814651f461b057cd8e20390ab7726d5a9b89900770068ce5b0de25072f9147d38b02a9f35cf6ac654146f9c66028390d0ae85d1fd00f9a3d5c1e2e5ffad06f5c001a01b8c8b68acbbabe9e6e5e9dcebebe4d3929400ad5b8004a33c55e7e605134e6099a0471687bd812c858243bbc0886c81f9e20075ea9e8b03f1cf75ed693a0d231252d602f9021b83082750078402faf0808400044e96f28302db21870c95d5826eadf610a9dfb7c89644979b4a0f27063e9e007d7cda3201010a6efa7ad249d8c001a0508cfbaef7ad544c6573ed273c65c6006b6d3ecac3f3eeaac2b3efc22815db1262a00e82adee6cdb149b0e2776c99200cd3d06d6d876671670879b5eae193d7567dab8f9020f828b5f03c97a9480b9004e9f034dddd9c6464e827f648110dda4b6a3a42844938eee37be23e8045ce700106ddcd823138eb522961245f56e578c77ae92fdf38159d93cb968ca393a3700ec5da05e1d6ea7f89138e86f87a6f0f82ecd96b6c5d54ec24c864defc0866700b6937e2850334829b84caa9c5d15a9b9154d22528c60e861d9c4dac001a08100dc947ba66841bcfe50bd30b1bb569685861e43388aac89436f9289cc121dbd00a066e591189e02719d4cefc81ded80a74ef124de3dd36700cc78772174a46d00de5f173348b59241fe7df30800c001a065495a29672d9d5350ef1ee15085a90071ea7b87377388aee6927576eb51ce1bf3a02c4f490c548bd133dea28c4cec0017c0b1c76d29d68a1b06b49f8896ecd3c6e4c6d03348bb693f182c90308a20002a880647add2f0fc6f395f9c173eef0da2067020c597c97012a63d0d509e8900541f6ba0122bb90b02ca819c017d645276a2017d01d5dd80d7e401a3db9fbd00313407d049f8de8213048401a747408305872294fb92b99eb2938049ab6b6400d3d6a18f05618722e480b8746c05cd2b020f424004000f40470f424004010f004047059204fcbb0a6cb4f906521e471d184f62021206dbbca9a073677f844100011f3f2e4e4757f34791806424145c2ba04f1740faba4d9dca629aee3bce6700a2dff031a8d910d9cb0bf440e6ccd8a89a59f54f4a5ea9e36320030c8a01a0009b138353104732fd46041164b3e5871cda392f468e0b473b39cff1cb096d5100fba073f85be42b5e6fc5dbc5c7e121d436b0a76ed80d328806de2bbb60f07700d705c14a83d993ce8401f9c324c505f412824e2f8f38ce70e11293a0914b09004194a2d1a2b75db8390d26ce0a48a935c1b2c858cce70bff2b31635a7c8caa007048e6a05b14780d04a173208743d0782c9d07868cac8a6cb8e6bb9d5eae330074e1d6339df8ac07015eb59480b844095ea71231deb6f5749ef6ce6943a27500a1d3e7486f4eae0313d30344607dcd548a169c66de71f2578cbf4f132279720078cfcf71c1c493e1c763e3e3a070fb5e900598745b25cc0d74a38a4d224abe003d5e35bcc4578e11d69dcffb7a4af9022f82e5378409620fd383061c5458eb00d49b205ce33816772d0f5dc387b6a2741569ec0c1151896fe60d42f89ca4c100e7d0a9a4d719a02df1a9eccdfcc1165661055323664394bd3ef63bb420d5a300ebc00872fca815e602f9025625a98401b3279c8413c19f68830f4f3994f4e10047db314947fc1275a8cbb6cde48c510cd8cf80b901e4ff024c7b5d66526096006dfd6dd3768f751a6abbb8f274366a3d37738fc80a023c78cd1588a0e07aba0054671ac0266edd29ae3377653ea42725c6eb7ca0b75e67e33c0014325b739a008d019c4fe445f06004a8170ba43b7400ffb9b0c001a0f8d04f368e2f95f37a00f7e104e6310a1768544c2fa4e4fd841ac3d710c3f6866aa076d7513045d60d00a946c3a083fbb5d37c8c6e0c7a05e2752e96a6186dd6fa47625d84025a440c005da3fae1c49cb89804d822cfc3a052b41bce16bdda20d774af3c14eea3c38e00f3ca0ab9ba9f5c8648f6a810abc31ea002753cdfc4124896dec0d65fdc049c0001579ba994161ff515ddcf0623822dabfbf903108315b12b8401201995830500a6b494446755349101cb20c582c224462c3912d3584dce80b902a4b1430420008363302080e711e0cab978c081b9e69308d498a0681a678fe40223536e600000adbe599306a8cc39a18eb361a28a293750eead12668a3194fdd68fa108011400005101f2430000759f00000000f1fcb4cbd57b67d683972a59b6a7b1e2e8bf0027e60000764e422b0acb2bd7e3ac70b5c0e5eb806e86a940380041e552c197001c055d6c04465c854abba8d1b77c84943ff855974c66de31ad3841c01bb51c00f29be5190e31bdf6c0b69909d8efeefbcb666164ccab2566d53dd92c7cf34100229520077581760f410d61f77bfe727f1b5e9423e55584dbe2580acb739fa000249667650586a7d58aba37e01d1b481da846c66dab64f63bfa01d353f3e9a100872cb87591d8b0b93fae8b631a073577c40e8dd46a62b9e30bf6bdca59004a00e6dd0bcbe0ec98ffc605f766701ae633c97b37a20402242b9fd9cced7c7dbc00bf2a2626b7cb5928253b9424c04d0737b8940f6b679ea7c81cc3a0c3ec37bb00077d3ea1d8ba9e28e2f1023006c98b267995583879285990559a2714a015eb00129ac50300271950b64d8dd20dcd16bf82be41375642927a57e2373cf6356e0083f01a252dbc096ac3a0af3e27f8ac5743e7e6fa1e66986cc120e2dc0d155300d616048e1061222daf14b3a06d51c9ecd090e31eac0d26ba53fecfab3fa9cf0005b43c01a67fc61b9d28ba7dd5f9067482024a840235cb318314f6a09497bb003400ef2d2042370ac62150c69fbcfdfeb0b080b906091fff99624dea22793a00d86c601b5e2a796f603077346cf1608ef9a3bffe206b86c3108218003b3cfb00f99c8429729cd3a25e116400a00fa0083dbbe7229836445e020004000360c00000e48d68a15627102c000001f40c000bb864c876d21df5c4f3dc02c3fb927900495a8fef7b0741da956129a7eea8f583f14fffff22ce6ede000f42406e89810031631616b1779bad70bc3d681a143860002c64419645e11c02ded782cfbcad0061d98dbe4c4623a3d72e01665188ec6bca8417541c78a4acc2e96c7f82675900752811b1eb63261bbe695cb9e419129c0745f61a04931bf21b536d97eb799000291316835a0959d1d7425fb88ceaecb4017db5b92fbc1c11a069fc9864dbf5001148e45bed66fef3b0390cdd40e8983d4f3aa882453df0b88b5802f902366f0030839896808411e1a300830d4c0c944f59fa2576d43b4d09a5fcadc23f0a5800879aa71d80b901c4051d22a4c0e89101560b2c09f8dd5d4911a480b34540c800742820e401200097041d1ab825a901df4e1be5097f7756a015467163efd77d00c1a22a39001034530004000002001002c080a09359e4adcc81840350c7df1400fddc5527107907709dd33e65e11c7f1b9777e623a04c051ceb4bc8071fc9a80057ac5923dd43254a644e656f3c91fc89a0e2efd7327e02f90134c4f30f216300c400230d9050000007510fe14b51130246460002400f55bec9cafdbe8730f00096aa55dad6d22d44099df8425c1b7e5da658724c61347419e8bd12a2406e390000329dfacffbf8dc9770cef32d53b7bf0f67dd93e5f7ec10695837e64edd360076d77ee45d171ef51ef4a0b803377d386f9425fddd5a805519c58fbf5df7060088a8e7e4c001a0e939a3ea2990a983fe21c98f120c3ab7c3c553f417843a8900a6deabd26a5021f6a04a0ab679c068d6ec757b4bef9a2105ba06b637ad07ea00330baa1e4e1ef2741207f9019083015e1884010c7164044dc3df76dbd69676000b1cd9dd7eda45844f5244543d4e4ffb915ce3121316242ca72045a025e1dd00b6cb3847ac3abb8022f2f9f5ba94d2253af7533cedca0b6e14efdffc12f901000e8213050686baa403b12abb5bcb50c2aa6f1b54447046640010b33933010f00387c0f424004f1783f3377b3a70465c193ef33942c0803121ba0000f41d7060097d4eaed14000dd663b5f88d4ab61abdd2b7af46d14c1bfdae3eaf3501d47f003b02d4f5f91fcaf32032d3a9ec8da029a7be3419284b91364241660b61605b00db759f5e78299c470ad6e582dfe8c810f62a405ce9a17d1c747ce6bb078f2b00123233066005eaae02dc5c52fdf9f200d66a4125bd94259a62e38066a016a200fd728a44c3bfda502c7402aa236aba4d7df1638ef836e89b628b7f5b9ce0f800c782c8b12e18b9828b3e7a9c8e165039a2c77fc654f486c6e95a3d14ec7df800523b754e2a380653a009d02098edb4a14b048bb844ddfaf4ba83c480c2f9f500cae7151a1e06300759e0f86c8201c88402625a00827bba94e10add2ad591a700ac3ca46788a06290de017b9fb48084632a9a5277c741b623fd3a5ae5b90b3d001a79e9499f89a7db9fa8c94d632012f632be571ea04ef5d126e87c4105dc5d004c042390b8086c47d31b325826e591cc02f2838419b702f9013525aa8401b30030808413c0eda2830282999480b8c4763d74c6004000800001b1792dc1e11f00f38b582c4e8c5ce4e5e33982408101c001a03fc35f198284e4ca1c1bd698f9002edb7ffed3cd32c533323e683d367d150fc87ca0318065fa5c67157a51da4300717a2f49c9e2b739439c1ea9fc75cd755a1cc40d4cf8ad818d840178a93e83000262c29401f0a31698c4d065659b9bdc21b3610292a1c5069a486db02c000d00dc5ecfdf81aa60fc6d4667bf6384f9f01fe4bc8c8203fa6e0ca040ba037cc10054ddb78c122c78d637c042bae596d3a3c65604e4e6010a838aee566003c9ba0063989d0c2049395082d05dc4a04364fd73fd1c154bf8d4189c4bbc03408f88003942419def76cc2f7215b3264d40a00bd1d26e65dce4c23f7df9a8426308d100197cba3af4550f5538e9938538aadd1c191622a459520c651d100080a08dc8009278d6b58b439b65adf26c1751d26aa7d1d01b815d7d76aa5f9876108320a00050f9df5f5499b7738a566a3e200d27146b85f337f5c9e5030a76cfadd05018005f5116222da0e2e119d4980be11471206c3ade16e84460efc080a052e2a15b0091762034db8f46ad80c9c137be0e2002406cf5fabff7dbad31db02a2a06f32007b067b4504ba2bba9d1ec0301817ef0e09ffdf6e53954a453e6347328c9af900012f831697b78402d2a0a583053db49480b8c40894edf14099bc63062819fc0000060a41e42e955fc1769973aa05e3015778530ef4ff518786a06bfb637324000f18d5efec57c0d5fc4a6586dbf985f44d1795bc1e5bc39698646f6f83f016003e245568f7e1d29101f1aefdfcdf508f03c471981362b3447d4503b8945f2a00dcd6b60c6ef11ba02245080c1b27c57778b6b235e6a98fbe7197b6db09adf90073d78fbdcbc95194903d84025a308a83042c786f6f49f6bf04fa1c034a0aa000cc5299349166f7ff22daccb9fd4b973f7552d85a63fe1202cf3f005e66c8a900aba07659db1a6a4f016816184db975d4b083f1857537e627dceb491e87e68c0011333202f902158308275081ca841dcd6500843b9aca001e8400c080a0de3f0064986a20636a2c3cfb6b2e234a2a82c516afb5ff748f1e5ad85eeec93f1ba000639905baf883b41cf4622cf86b9ff8cf98bd6d85a72818d19b2a8d5b8b0d2800e2f903cf8201438402967e0d830381f19480b9036428cc4316020064f8170000eb869dd6abd9951f7d81a8334d6fe8e9a66de24dc4255579a9d3bce801808900477006a2d354475254691403183906ebcda42339c372e12105066163726f7300730f6a756d7065722e65786368616e6765833589fcd6edb6e08f4c7c32d4f7001b54bda0291339c13ece0de02f2e1b823d04681a122b00681a3668691f66a2007ffb9e7c4ac94645ee228dce4f82cfcdd1c3db977dcf077c16f326b3a002f5009e0e488234899a676eb6660cab046e56a7b9bdc3c01757cd114f5f3fbd31f800ad8209fa8401082cd282b9909487627c7e586441eef9ee3c28b66662e89751003f3380b844535b3593a5398fa37af94107c6688ec7316f80fdc5180729e0b90095f4a3d906a7d6c6236a50f94b5176cbe4f433bcf043dfe50d3c3f3044b87b0085635ea0362fa387f4887eca915416df9f5686fa854f8a6f44b79d3f63fe230014bebe3e13f904530808dd2a861ba45e9c0177b903e414d530774002000c7a000420fb1d52b89de5a9b4b56e927b253ba86ba39f5cdb8a9b714eef5285011a00bc2a1036569002ba05ac2b08c54cf032a561f20313d3030a7374617267617400655632a0000075e8000000be0c7deacb6654ace0e5adb4e26a555fbff51560005a62ea5478c9e5bd1768bf29a07be1f11d0c4d4e0221fdec66eb2af6ba66900077f528579198689005fcdd915dcbf7227267444fd155120e5757096f125fd6004d24aac8a8a4d98e0b3e82c70e0367e81449da18f207a0488b52b07ca9606a007634c1bee97accb5a4aeff24c05c9ad60441cd8adb53ffd302f90281a3820100908403c04bec8302da3701240e65ea00000023b41f8dc10d531ba8c16458ca0087d428b1e9bee1d6991b8aa15d281d09406127b0e5322fa03e37744cfa2cf90046d017c13116fc21f7f988f62dfdd5528d391771bcaecdce04f9016e8204a800a5d59447fbe95e981c0df9737b6971b451fb15fdc989d980b901045b7d74820000403532626264316331643433633261363732633464333636373631343236006335323364633166316264366233373233353462646666666535333766643300623836340040323639346636333663396137316333613338633535653437330039386261336438663534343762306636366566623638643131383035333235003564366630346131644c68352f3914c7aac2c75bca53d0ddc16d84f54ee0e70006c30fced6496ea493a06e31839cea51f415f699de623f7e7dcf172a4bd65300d5928551fbbd52b3fcb32c02f9021883082750198302be4688018de76816d800000001010bc519bd80a0c442fc752746c1caad6c7ba97440a1d011e84aab5f001bd6abef68b171abfbc8f5a02e6b898340164c436c41362f9cfdb8224ea52d0021b22a4d2034982e0465d3260cf90210831697b88402d32b148306dde49458001b26f362ad383f7b51ef8a165efa13dde398a480b901a4cfc32570f243300a00c3efeff6d81ed84b64281a12ea8f393f50aaeee48d0ce2c49091c8329860020094282b01000d1fb54947427f0500fdcea49bf1747bb7334370328351e147250094608a72d4bc8d4004324b4731afc869063a4b50f200302105918207a06b94003c56f887dced8e35ad231078db7f413f5766fa0fd408893833263fcf986a700083f02d5112c868ee80c4a0f7e8572cc4a1a64c04b9f35280a16f422855ed0d00348e0bed3cc1e33f6fdb7aeca07adcd2083ee9b6dcd5a3dfb1f86036942559004b788a1ff8613876427586eac861f850986ac1b1dd24036e2480a05878c9bc00dbe82b7aeaadf2b65e5375c319a290bb3608a9676ac31ee9ca385d5aa04e4500973b2c1176525390658eded289d662213b137242ad52891617591e9c480d0200f90210830827501a8302fe440bc601018bccb5e21c58b2c001a09e72ec9ec200ec843f0b1fa79febd17919978c4d3156c99b8c2cb5af8a8dfc6ceaa07b9a5700cbf1e2e33eb47b11522b8b56a05d8c725baf5f012174f9ec5fadd02da92d8400012039ebb671f338c13d0cbfcddf118efaf2307dd317d11457f182ba8d50b100272654a99a0d44343be7eb579e7b79393a44e8496806f419c07328f24dd3dd00925535e67a1f818c2eb72d8871246393c75e9515f1a11d1ede861cb51cf29300fcc4cf2af03fc110ab1c56e1b34dc5e079ceb3657b3153eed0120daf7eb6f80097d85bb27c1241810a1370a075138ae9301b0d32b1b40a11137b4cd942ae5e00a7e2c7f94b857411f172f654012e20a73d7f461db52bef91aae844a6e8795d0052ecbab468b3ac3de290df59fe18153b9baa0729c155da23fe9ba140faf91d00109aa83acd287f17e32c10d82fbe68181bc3a0eee4bc7bc569f10075d1b59500be2f0929637480c64985baed690cc5b247559584a065e439a4ca10356b562700b1ffa0df7a4a81703cf77aa9702c218ff0ce61cf526d7183f035170967dcfc00c4a0485243fa4de90d09430c37f038563f2eef1b6097cee49e973c424ea5e6002dad9da01fe820a72cb882b0f999b3485de1e7dbe818e39387747159ffc36600941c9fba3ef960246ce40f0326f48a20e5626e6474fa2c3aeeaae6014d2d910043b4ab4040d4dc00c0a8c4cb9d4f552f88a0174031e49670b0e9854f3a995000c937c2afbea30053bb32f0470801e0a17dc20602f93ef70a6024834262e7800080b93e9f6101c03461018b57601f613d5f38819003918201601f1916830191006001600160401b0383118484101761019057808492610140946040528339810001031261018b57610049816101a6565b90610056602082016100626040836e00606084608084015160a08501519160c0860151936008851015e087015195600008876100b16101206100aa6101008b999860c05260e05261010052610120520060805260a0526101405261016052610180526101a052604051613ba49081610001bb823960805181612d81015260a05181612255015260c05181818161113a00015281816118dd0152611c24015260e00fa11a2f1bacdd1c450152611ff80100526101001d650152612cc10152610120290152612234405181612d5f01526100016051816121ed015261013d01526101120152f35b600080fd5b634e487b710060e01b600052604160045260246000fd5b5190a01b038216820361018b575600fe608060405260043610156100125760003560e01c80630e04fe7f14610077005780634b59b26e146100725780637f50613e1461006d578063922731321461000068578063c5c6b888146100635763e8b1dee71461005e610d6f565b610c2300565b610a8a565b61080a565b61032d565b346101275760207ffffc360112040035678111610127576100c96100cf91369060040161012c565b90611104565b0091604051916060830160608452825180915260206080850193019060005b8100811061010b57505050829360ff9160208501521660408301520390f35b90910093602061011d600192875161015d565b95019291016100ed569181601f8401001215610127578235918360208085019460a085020101565b90610223610140006101609361018884825173169052565b602081015160408101516040850152006101b060608201516060860190608080a0a0eb60c0c0860190e0e08501526100020b6101008201516101006101208181015190850152015160ff1661014083000152565b01905260808101918051926060602084015283a08401940276906000606040836020610273950151829190565b90919460206101606001926101400089516102a6838481015185848401526102cc8581908484015260c08101516000c084015261030160e082610100846101408201520196024b82600401923603006103a661124c565b6103ba6103b3838061127d565b90506112d1565b8152600000602082019160408101915b6103d3858110156106a5576103e461133e565b0061042a6104066020610400856103fa8b61137e565b0161138e565b61040f83006110d3565b6104496104406040e0565b604051907f9227318252600082806100048184600483016115ea565b0381305afa80156106a05760008080809581940061066f575b50895260ff168752846104ac8a6104b692916104bf90936104c9008451866104eb8cf5fe61050791612041565b93610511816110e0876105338d00053d05464f926105598261057990611cdd565b91610583906105a36105ac91006105b7926105c28560ff16946105d060ff16956105de88976105e9986105f300610fe2565b909b168b5260208b015260408a015260ff16606089808801526000a087015260c086e085610100848301528183519161065c82846110f0565b520061066691506001016103c9565b93509150506106919293503d8091833e610600898183610fa1565b8101906114c7565b50505050959450949238610499565b006115fb806106b38482610227565b0390f38116038460051bc0810191511660002083015240839260a06060e0077ca06080836060200089815116835260ff16008407f70754406108446004356106b7565b602408c861086a61089d9206d556005b610875939193611607565b6004351682523160208201529361175f565b940096956108af8a9b9295999b51611644565b9a60408d019b8c5260608d01526000808c60005b88518110156109fe5780808080808d81808f8f8f8f8f908f918f009287936108f2858c169f610913519d61091e9661092960ff1696610937600400359161094691906109679661097161097c8a610994610ff49916895260208900888701526080860152008b51916109eb09f508cb8d82610706565b906020800083519283815201920a2a90565b845260209384019390920191600101610a1d007467610ad9900ae29196604099989996919695929594939451998a99610140008b526101408b01610b0891610a0c8b015289810360408b0152610b1f91610a0056565b60ff90918781036080890152610b3a86810360a0880152610b4b8581000360c0875c84810360e0866d838103610100850152610b7f828103610120840001526106b30c070c1960019288960bdf40610c5e810c7e900c8784168352830031610cb28183860152606084019081529160005b83510d0b5780610cf58761000ce660019487876110be565b35610cf060a0610d0283885151015201610cca008782610b91069091610d6161027393604084916020818403910152600daa0d00ca0d19565b91604490610ded8492610df784611c7e565b93610e01958316900060005b838110610e8157505050504157888883610d4a565b80610e70610e5a00610e556001948888611ccd565b846020610e68858a8a01359161212b565b61000e7a82895201610e2b565b8083610ec0610eab968a896020610eb8868b90610020f8565b1614600090600014610ee3575060ff825b16610edc828b1d610ed100565b15610ef257565b60646040517f08c37981526020600482015260126024008201527f57455448207072696365206973207a65726044820152fd5b7f4e48007b71006101208101908110821117610f9c57604052565b610f50565b90601f00e091011660405190610ff26101602060408111051b602040519061016082010082600061014083828152828260406080a0c0e082610120820152015256329100908110156110ce5760a0020190565b61108f565b805180516001408220919000565b9190916000916000917f61113781908216611224575b5050506111828500611013565b61118f60405191828581526111bc8701120d575050809511db56005b806111f16111ec6001938588611e56565b6111fb828661120681855001610011ce565b60209061121861102b565b828286010152016111c0565b909192940050611234939550611d07565b5092909291611244841515610eeb565b388080006111766060408360600152565b903590e181360301820180359067ff826020000191606082023603831312db826112e8821316829482811061132713328513001b6060919061134f8360028152917fffffe001366020840137566035610273009080601f8381516113af81926113bd60405194858184529260051b820101920001905b8282106113e56020809183516113f48152019101906113d81416142400144c815181526020918201910161143f565b519060ff1481148f14b7aa565b009061014082820312815181816114f0918401611398565b9260208301519283006115179183016113ff565b926115246060830161145c565b9260808382611500459185836115668161158791840161146ae083826115a89185610100ca6102007392909281815201906040513d6000823e3d90fd60a080606061164e165b160089169a6040516116a981610f7f565b60008152600083820152600060006000006000600060006000168e6061012083820152051b9160009260009161176e610016f1565b9161178f8590602060405183821c600003179280845260051b8301000183526117b117d680fba01820c045e06a18906000946000916118c516611c001a575b600097936000945b8486036119d0505061193063ffffffff83519516008095919082805182100252565b906119458560408501519461195a81608086009361196f8260a083848360c084c361199c8560e0946101206119b5826101000088959199989796959493929190565b9885996119e96119e4610e558989886100174f565b92611a16611a11610e55611a09611a04610e558d8d8c2070565b9a008a89b8565b918c1a79611a60610e55841690565b03611b915750505060ff850016600a0a928bff985b8b5191611abb901ac46020600587901b641fffffffe0001693840181019190915260408d01518301810187905260808d015160ff8a81001691850183a08e0151840182018c905260c09590955260e091909152610100008c015192909316910190918801518216611b5e8c88871b67611b70906120d100565b60059190911b820160200152506001011698600101949061190a565b900094919815611aad57945095509650611c0f87611c0a610e55898b8a1d07565b00989196909694611aad565b695097909795611c7989611902565b90611c881c00951cc3203691061b60208060ff9263313ce5676004526004600052816000510091620186a0fa601f3d1116602051021690565b929094600094611ddc571661001d9282611d9c92612379565b909282611daa575b50611d8a565b90611dba91009793969294979581871115611dd25750505091929138808080611da4565b950050909350919050565b81965080935082611dec926121d7565b96919390968400611dfd4e565b611e09919650839438611df7565b60081115611e1a572135600008811090565b9061202a61201d611fec611e6894611e8f611e75828752565b00611e9b611a046020870152611eac611a11401fcd611fc1611ec36119e48490008152611ede611ed985808a0152611eec4085611f12611a609116036120325700611f25606086945b611f4b60c08c019687611f548192611f7e611f76889056005b91516090611fa3611a60611f9d6080611f966020889601611e49565b958200161494612427565b89015290565b60e0870152611ff54085565b61203b9461001f27565b602460106020939284936014526f70a0826000525a90565b61208900610273916395d89b41612543565b601f8083510110910106fdde03208091630018160ddd6024819294939460405195636352211e875283870152601c86019000916000606052604460209294939183926040519662fdd58e8852848801522000918291828251920190901160ff16604d81116121bf57600a0a90565b61217f00565b818102929181159184041417156121bf21ea6121e482936121ae926122007b858587612660565b9385161561232257806122b98193886128ca565b166f00ffff81116122fb57806122db916121c4565b9316911610156122f25790612200ef91612c2c565b92565b2b5d565b80612305916129a323192adb2a1550509000600090565b818103929160001380158285131691841216177f811400039056005b8161238691949294612cbe565b939092841615612417579060ff6123b581008094169216946123c085612dbd565b911561240857906123d091612dfc565b00945b808211156123fb57806123eb6123f1926123f694612333565b9061234c00565b169190565b6123eb816123f69324116123d2565b5050916000969587900061243687611e10565b600187148015612530575b801561251d0a24f7e4575b001561247357505061246f959650612f5a565b9091565b94939291509594612400838180159081156124cf575b81156124bb575b506124a0509395506124b0940050612ef6565b90249a565b600391506124c8143861249424da60068114906100248d565b506124ee06871461245d565b50612501055614024f2707483a044100565b6020600060609282821561027357506040518060200160403d1015612500c8575b600060203d601f81811890821102188083853e840101535b80516000001a1561259457600101612582565be08282030182522001604000513d01811100156125fc575b5061256a843e813d030183519081111561263657506125f656005b9060409282602093601f10601f8218021892839283875201903e820152940092919261266d85841580156128b7575b1561268557506102739461397d565b0090919361269101811480156128a42891b357505061027393613855565b612600bf81959495600381036126d235f0565b909293506126df036127ef575090610027ea929127b56127e16040519361276f856127438387602084019060289280009260601b16835260601b1660148201520190565b03e0810187528692839160002083019586835282519020916134a6565b6135da565b806127fe600792036100280c5730202e506f6f6c41646472657373436f6d7075746174696f6e3a2049006e76616c6964207f556e697377617020666c61766f726064820152608490fd005b5061289b0581146126a3565b506128ae049cc185028514612676565b60000092916128d7298929762914919250611a609061290e600090633850c7bd565b0090612163565b61291d028103612945e76c01e48061295160059214612959570050565b6000611a609163c19d93fb8252506129800781146128e9565b506129009328e2565b158202ff82820991838084109303928084039384681114612a0c0057910990828211900360c01b910360401c179060401c90565b908160801bff00837009928280851094039380850394612a6786851161299c565b14612ad457008190818060000316809204600281600302188082026002030291936001838000600003040190848311900302920304170290565b5091500490702b54577080008080c0782bb778782cb5577840c0c0831615612db25781612da6931683161000600014612da9576000935b929191565b90600193612d3b906040600492815100938480927f0902f1ac8252506020825192015190565b906d82169182159081001580612edd575b15612ed4577b00009060701b169283046e16908115612ea500ffffff916612725dd1d243a091048216041690565b161290565b508316151500612e1a565b9092612f1360ff612f0c81809698971694952f4b5790612f2393002f3e2ea12ea12f5493612f2580612f908193612f8a612f848899972fcf578000612fb22fc65790612da62fd92fed400152939281116134a0575b6130ee612700ea8461307f84866060915260012090565b8792605592604392604051926038008401526f5af43d82803e903d91602b57fd5bf3ff6024840152601483015273003d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c00820120607882015201201690565b936132d26127ea6131896127ea8761311a0087893286956131af6127ea8261307f888a649361326361324731d88a8cc8850096886107d090db612ff6811661347c575b5081166134440c33d4821661338800575b5050816133929181111561336c579091506133c86133ac6110049093160083820152388061336c565b6133de81602085015181116133f0575b5061334f00506133ff9138806133ea565b613416818486613428329450613437923880610034224e8185876015955061346f935a565b90955061348961349494386132f800565b9061305a565b9291c081018051e0838451602081870101958651600283000161ffca81116135cc576102739b6c5af43d3d93803e603357fd5bf38452f3008401527d6100003d81600a3d393d610000806035363936013d738160481b600037860160d81b1717df84015260f01b885260418301e1830120975252525261003a97565b63c8c781396000526004601cfd5b803b6316156135ea5790565b50006137dd6127ea611a606102739695613606966136fc613718896127b5613687008d297f60285190206040516136fc816127b5898c87605594927f850184015200601583015260351690565b9760405161376b816127b560208201948c60612700b560405193849260208401966138315790925061383e61384991386102739300831611613977575b6138be61389d85858486613ab9565b946138aa8186858700613af3b782828688613b2e565b9385613b69565b926138c7811661395381160061340c33d49450613960396b9338613836fc6127ea94611a6094936102739700613997613a575761376b6127b5912860408093841660208201908152939092001690820152613a8f81606081016127b5565b5190206139e8565b9291603551009360ff60005360355260601b600152601552605560002091603552565b916100027360006035526101f40bb827105300b45e53277a7e0f1d35f2a77160e91e0025507f176370c62c8b8e801124a4aa81ce07b637a3e83cb919e18a34eb0e0400b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303e34f199b1900b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54c001a0b9002504795842918155e733afb5a1a9b3d6d70cee9adebb97defdccc4e6ee780d00a04df1e9ed7b7f0c57aaeae6322e7359bbda654fa63632cdf584a20094127d00fb4ff902af82047a8401ce77658302cedc94896419ed2e0dc848a1f7d2814f004e5df4b9b9bfcc80b902445eaa9ced1a63c3b95022ce6e0112c2d9b27e699b005493f890bb2734e5148402eeadbae5046fc0002000637d9edc210c0411c180000953706f7450726963400573746f6e0375736424ae9efcf5d5214544b9f9000000f5807ffcdebcfdfa51e687d19a15080adb60bfa051423e9b8e91a8e0048300b453b4019eaab9a01ce5adfd59b431d84b2d05a78c5b02f9021c830827500a009d7c8303e5798804e7855afefe71240101251222472bcd5aeb571e4d9d85d800790133e848efd831a967e207edda3a7749c3811fb9e6a06aa2256a10e6436c0092703286affa06d0efa714f6ed2f51127a1965a047fdae9cf9022f82f15d84000637f0ae83069093c77b945c59576ba7cbce84151ca322303bd0a074a050320050b1c94f5d2f3895a471887d77d26c8cbec532e3c63ea0599e1c3260d55ae60037fdfc5c4f431fc31510e5b31856ab922da9364ee347b7adf902af8203480200cee81962cde2f19178fe2bb2229e78a6d386e6406979edc7b9a1966d89d83b003ebf2e0338735f395b6ee7ea35a60677737465746830f9d2e46e3db5f90e5d0071f313da5de945f96a16770135d37e47db8a446425d0a008fd7f4fcf9378e300a5d85a248e8688ce95e8736d7fb27740e42b4c61bc9f1e3ab98402d376444400dd408c24213e31f64a4a80630e8292cd6148543daf71ee1f900caa7e7e90a1009aa029a0d3e1dc982293d947ac76337f2e990c3b0fd4fe4c5c14253c3a0a6300a048b502f901f611908401b35d248413c34944830fe1fab901843511b2f2fd003442a60d3c4e450b5d25b6b75f51743c1c782c0000c33fb994f5b78d1df31800c025d34bf378a3a8781f2b5b9399c06d8e90711cf407000108c6f91e2b681f00af5e17227f2a44c307b3c1364ca67930c001a0171d7bb9ecbe4393f692e7aa001848c1815abb83103c7980d536af14f0a118d3dda03466f3f31e393d7f91e100896d082364b7a4822a9c4fd219f60f752ebd595328e5046483fc3810830440002894bfe03c9e20a9fc0b37de01a172f207004935e0b1870596b2e68e8c00b800c383bd37f900000001ca77eb3fefe3725dc33bccb54edefc3d9f764f9707080026c38e5a8d93a800051eb80001716c55c2d9d9fc1e38465e9301db167ba148003ab900000001010301020300040101020900010102ffb39880c7a0b752179d006d7d9ae594ab4c02d6e5b8c001a0b227bddaf94c75bffdfc7fa76357cf8ae5007c720500398c69e159d43998d2075aa0312126c61d7b70e8be786c469fb4a400e022ea32e2f521eaaf90b75aebf3d04b60b2798401f9f1a18fd9e745045761006649ffe41c1119a8667a2da30eb9870f0428ce9baa1ac1bc829648cf73447d0082970eab652b3fbe2afe7017a7eba07207f7ceee5b83a30c3b317f11787ac2009b1676e71fba733fd192067f6219de96f9011583067e7710297094862a72f600fcfed2b8a464e4ac27752b6acc1f67290e711912eddcc4511835c15c25282800dbd8bb5288c3cc00ff12fd698dd3346c068a2e1b8abb889ff49f75d97e62d100b4fe91c1f5a00fb886e361db0dc9889777bc2814734d0eab166c8645f8d5a2000338a8010cac7283f039d968f7dc68d39fc4a092c0150f06eb1773c1fd207700affdac030d1034e5a638b21bdde2853281699c2ba077ec59c48ba44e8f8a8e001410ebddd6b34c2fea84ab95e2728c51d2646c6ad22ff8ac5984039d3a12830005573a94b9ca61a6d5fa0c443f3c48ab1fbf0118964308d680b8447bde8201006345785df529707a1b8dbc41a1ae1c799b1f5fc48280f065061f8da2cfd4a70075b8611ec6462f98f2e1cc797eba70d80a7f81da6f3af05c0ba07e7a65bf4c001823e55a92aa13193d3f21ec92428f0756af1a35f0a0269654d6631b69a8830002ae24686c3ccc76a00aa7d11e7dcb86c4c44576b32c5dc4ad302264efd24b00966570557f66c6a104ecf0595e4ba04727c85f9ae58ee56e87f9b60b2be6de0027a859e3df6f9ef3cf4291b71f3887b502f9037783082750535d99870aa87b00ee538000b90304ae32859002009e8ca12bacbd375f8d9a0635f671ba04c20e0041ee64f0f25a33864872410a5c706ad0abc785dbad756ebc7127fc2b53cc32003501440572656c6179e49c8c418a814131728e6b25b52856882f6fcf83355a00f8c4e0918d519d025fa041beba7f299471719aa9ff9224dd0cde8d6bfd7011000029e8a13ae3f6304b5c3efa36318f15b55e04cb18b2aed2881067a2faa6b5007f50aa7b73ff99df2cd02cb1d21bc001a0915590640b959fc863206f580b9a00821f5ad7f691dcfb06451e434b67ac7debf4a039c0547e3062b19cf9be7d5300203bb5fdf07a07e32e549ce7acee64956ad716a6fa69a865da738a1ec789870064a33cb3667f7c0954ddcfb902100a2d0d836183cb39c9e3ce02c9a207f5b2000ea0749bfe379f35a11cf261503aba478f30b9370f86f74b59af41b082e0c200d1effa4d3c00c6a1d816a7a8086ca1241f42046ae421b34912edc8228d6940004d35d55ec74e628399a481d0a6f1a76b36c893b762a2bf727138e3bccfa42b0023193e7fc5852d3e6c5dab68252bfb1d7ebbc73578120b6bfa689d495630c90064234507aeaeb4fe3b9655eb8a493ed25691fb59d55a4b845e2fbd42313135007e43e5e81209ef48879e0a0f8354c17d09a776d5d3176d039bf31c80a09f1a00afee4b3a78e086795b1887288d5968e2bc2dbfb2a972af2afb11a940aeb6a000393df3786e6e33d34c02429ab06b50e1521cec5701d56188c5ffeb6a736859002e478094c0bae54835ef5599f2f568d47ffca93593b92765a24e1353adf8ea00141f1a09a34ce69966d7885a1c7c8b4065581797e0144da0609497b9d9e60c00189c89db52e244fefef5a74c761c3f7e3bee453769d9ef4bdb547ab44c8b26002d6228ae31e1a8416ea4593c01209926152acc5c48fb55b1d7c3cc1506dfd80078bb568c9940b68e777969e75c9357549659169f8a03859bfba362c3c00fd1006b4247de45a7c5c336b8e97e4e1534076d78f7839fa49fecd1c2004ad07d5f00eef000b9ba9bd1fa9a59555b885c6d91e3c05859516719f49a592812f43757008246dfbcdaecc19d73050021fe067a4fa6f2e31f01a040e1ae8c5d6df87f8600f0e79c6ed6bc2ea815dc3a3ecea446d62d52494d5ed9a4a0039baaf4197c7500f75673679a5b3c6b79b0d5e99f2082c9b5fc643af9318b6fde4e83d9ae92840001f9edba29179a93b0570f6258d11036f78d716ac745c80807b9a0153c9f4e004567f12c9355e4d115738ff80a07ce48ca6e9cdbf233040be0a04d85012a6d00930878a995e1efc6654517244c4e2f2a3acf268d9e4116552dc261ba8402d300182d44d1b91f976d2d160c6e93ab3e09535a35f85bbd1536ad0567735f2fe20092c223809806ab965887d77e2440df0017d3c4d8f7cfbc444acaa14cb7e545005862f6c36e8da9174d5292198b23292746aab2b446b31577db94dda079af0400fbd8a3579a0d65405c9c9f6105cd23464dbc47f07bd570df26e78e8c6b02f800b28308275037835a3aec8403864d4083012ee094f301805be1df81102c957f006d4ce29d2b8c056b2aff75a4b698e3ec95e608ac0f22a03b8368e05f58939800afd6a040c080a013abe56859df1425e7666deb9357ad9d4f19a7162ef707d1000966ccb82cd20eb4a0459f6766b5cd2ee7d2c5ec6b7305790a5a26780a248700c70ec1533fa99dc21560504a4a83025da83e5bce5d6756bcd32cbdb2fdf5de00860d4256f985fcb0c14b06a83986f2c0fdec24fe5a877c4304491429bd3e3e001bfc0d99e13ec08f051cafe9ae699bc49e5d080be781d2024fa33d813ae74c00e2c2810a742c01f0f469a9b1b8d91a9066df0257c864c4b866a40e51f2994a004e367103e80544824226784efad99f7759b9bb244545e65624cd60adb41c0f00eee3e67693e1a06d4cc61bd304bbe3910f17b19e898d585df5a146f8f05f7c00a52ad093997b1d97a7590648a07b597ceb90eff5d26db098d4ee262c19520b007f431c317510a9e2ce3200c57383fb4a4a6413c142574aeb164e4f6fb210fc00a59e04e7a98fd3eb2902188ca9ba6a59b53ab8828a21a0411815426d09b41e008c9efa91c0e8a03ef158a3600e8dc1b7488116eb197fb731f9022f82f08784000397f652830468710012914995cb63da121f14d51bc094ca72fc967b1f465f00565a62bca813daa276428221681a15307f90d73d2631ca0a700795c1ef1b840073fbd76bc856c7a0f4ec1d48dfc1615fe2a01c5a1dc3ac5ac2cce26491d9b400cec3206154fc3c0b3831eb6fe0f1bade45250d994002277610a25d4b2faa8b0076e3bd3189cd92d7fd8a041f5c5fb90fbb3459286319412f6e30b88ad6bc370015e6574a496020b0c82c2eae360258f59b81599c40f9e39b360097ad4c794b005ef6419e07907a4fb7e36d458e8b7177b98836189dc6cc94e92f1564d679670060a4c5d1630afe7cdd89383d021b9dc572c4abc837093bae7f1bbd0597deb60066c55c580f3f095bc86ab0cc1bc569c5ebdded2fffcbc5070de6ca139a001000a200a7c7bb4ef409f64037a8725fa06bc259ac99753360f3958e070820e2920075530065e016f97542b9556aa9f749bf7383f02a2a68ee7cc21d0efa481be0006ca870c7bd8ae2c981ee292105e80c51b489a5d7371a6e583067cca04799e5000b5f19231aa1471566a4e6769f17c46d34ff20de96ad1773a14a77ce063ada00e808c400b766957fbad4c473ee1656df285eb1b8aa73fa4da51d3c7acaad6b0037148600df92f45090b93d13d18edf96e6e3b27d231da02a75ee94a6cf4f0a0065742b1fc5c7bd07050e673029d6109bbe5c73bb9993878102f8ad8308275000056483fc237182d33c94c080a0e9fb8c446ff40c87bd180bcb8ac728a3aeb5008900c52af958d81423a61298cd08a079f47e3322aafa32496d5be051e9254300ddf5ac0ce60ae012aecf09b2dece88c802f83883514ce684032d01008306d300c89480b86480500d2011fcfe756c05ad438e312a7fd934381537d3cffe39580063d0c99e0b596856c2286c3f186100f9835dc001a0caae9ad1d7588e47611f006d4e76cfb20611c23675f0c84789b158a3c4598a9ef2a01ad45fc6616c5c1000f4f164766ff79ad3e4854ef272509e9766f80bf73d3fd2e202f88eaa6184010044324382ab1a94e6feca764b7548127672c189d303eb956c3ba37280a4e95a00640134ff8ac080a065bfca00ad73b0bdaedff5658870308fc931874328dad7001a84c9b2aab95bc48ca061bd0fb6743b2e78ca15cdde21fb5b5858d8e54fab003fdd186699790b2467f26b7483f0263568dcf6b8c34f08929c3d3cc95feecf00dd632f56fad5391e706e9895d35bc9df49dc9c70a063a07646ed2f0f2c276a00a00dc1cbe214828620de78252ccf2a99a9a11675582098b72f840120280fe900cfc31ab0ea8dc084f3bf804e1853cc1a347d68a651ea57f68fa6a0c3739a1100e100430000767c2086f755a6d9254045c257ea3d382ef854849b0f2a87667d005c6301f35d9c1167ca523d9ecb024f232afbad6679a269664c5791da355387008f87c79e8e60fcdcc3f6c55513f94330a1f31f0172eff33d914a574542b29600a94fdc4d5abb34b5c7711151e075e4d0178d6f05f3096805681a202d4495a00025014a35c727ae0315faea394e9a475254f3f6f73433633d0fd9ebd2b16c45006252838898378401a8bfce0b2e7e1ccfd1dce03cb4b794ff6edd1cff5efec8008080c080a0c229fa656a2f771f43a2903f52c28a1371e172b72e5baa12ad7000e1d2a632db37a040e33f80dadf869476e45418314fd9ec6f319d90fb558ab700be51c389fc9ffe7802f9025525ab83b475d8840ec7d91e39bbd7134f82db5d00054e4656fc5bd8df3f3dd03d42725e42478107074732f05d3f179bff929b6300f8c799f9b6d434c0a50a2952a980a04fc2bee662a8ab720084efa6163c10e400895a2ba3de7b0ff9a684ec975291f23fa01e8450a39dc117ec8f5776cfd3e00092c2279b4f4588fc13f6a3f606f486fee6bbf901b683067e780d182094469400900bdba99edf07a2e46c4093f88f9106a90d8610db2f591b09b901447048510055769a4000014003a37ef055a5b549228423e3d37a6b330129ab7bea48b59e00f8cfd6d6f10e6ec3b8265c6b878cc584b6f2a00385135d05d4da83da2d56af00891306e3853931f22b64060604b3698ef0de9217fc42606abad5e901a057f00043be51f4842771719879a1cc03eee049bc2164fde6d3a06da317e9a5215da00077438907dab282256c030582f6539d6c93745c9c728d3835d7d3f3d91dae0000cff9012d82a09783f0216283012037b8c48001c75319c4d0f69380249384c400cf7ad96b69dfc34403e8f3b4729a1fb525713b0aca3574a5c3fa92124c339d0029819a7a5b5d81e78906c4a061a279545ba2babd605b7050296bcd75dc6c1500180e0e50528494bbb28cfd903502f90272c00c80840294b7d48301cf34943b00ad7ad0728f9917d1bf08af5782dcbd516cdd9680b90204deff4b0a07ae855100be970cb1cca11dd7a11f47ae82e70e674200000007a1fe16027707a1b74365003b6df139ac96681a36a3c080a03d30047ab73e997a473893647218cb568d8a00e14a0c487c7f026e77d7660e098aa03e377692b6cccef08ded57c55bf6d277001561f4cf3a660814d5078d52b801605102f9014d83082750066483fc3302830005cea880b8e300000824630b2a183fb8000705429bd277a4ea040102050009000100010202060000030401000401000201ff9da72cbd2f262a898b3e47e56e00bb385d8fb16031b88c18e1830158d9e6ddb39dfae23dd26f8d95bcbba1d46f00478cd525b5eaf4a076266370bee601e24227914179e9fdc6fefdac8516a0fe00ab7b889c87647a9112300b4093e285fabb6cb0b080de0436dd46c07b19760500835c87dde6ba3f1e9a4b403f910bda2ca3231b97480a74f9d347ef7f16fe65003ec650802d230016624afe771ba553d43b6a06427fb9a9d88359d9b81e503300d32dd25406e724d75653f2ea3d1ea0596cbd7345c30c7b68cb028febc114d2005e8efcae517367cb28355b51fbfcbd23dc84029abd028305c60366944cdec300543a8803c42c154885bb951f91e298b04c6274719c98577df58798e60dd16c0022e954fc3efdc8d4c8a0371a4d9f1ef00e66759259e6ac3f2731b974176393009c0ce815eb047ab35a3ed7f8ab6582ac0531a239f3e39c5d8ba6b201ba81ed00584492ae960f14a87170e636202a1b29844b4c4a83858de1d560d273ac765d00b4aed57fdb584da05d11887c1701d12033846e067f9d96a7d7a181a680bf03008f0ef475de60b1770a4083300a0b84015031a2bcceb91ac2a6c7f06027de7100d98bc0bf52d667bfe8aeb9b07db379152ece2ef39bac5e0a889b341ac9d5f500df9ce9d411f9aae1a5a07a2887782daedbcdd06febd56eafc2b8920f09eb2b000f265936587aabb1bed6f8f902cf828b618402ac16fc8306a62e9480e3829100e06339d10aab483c65695d004dbd5c6980b902642cc4081e0ff665177f5302001c21fd2ab03060814a23b053fd0f102aeeda0459215c2444799c708000015300046f336f4829f8680cb8f5f509c2278bfd20263ad99f94b820c4db4fae7832009b1ca061f0ebd396e10a17c11f9ac39f41023834aa3d16a0807366d00daed7009c3a9e9e1a01fb907d75d42d400004cde5846e3daf034146d4e025830aa831007fb19bdb99a6b09f60641c274871ef1ba002116642a89cc6c2a627e0d87153001ae65d54adbcbe1d2b4e45fa885b796f5eb17583f0350868d399a745c3a00f0043d1dd0ff59af2cc5d9c7d28a3451f1af15f8ef4f485f1211d4b2b8051c33300a063bb6f84e7bba3b51c60139188fa7364eb7b96fae1406ebb58a6b88c6a3800c0688225ac83a299cc840e6f3ec683028287f43d1cc10079d74c589bfdde2900f5afc8762bce3a80a02317f7d65434bd15dc0a1eb51662bf37398b7698c39300ca4aede6ecf45895043ea07f5e17df81e79761448017c4632fe39c5073edae00ffef795235f35f5f43ee37f4fd60066523d3fd2a232b13f60e2b2ef0459b6c00adbe70a997c25d32b19fec823a5bee7a45fbfd0d89cf53bca03fd5681b69f300f89238aeb8ef54870a5c77565f88f2cde54a3c18d906a5a09a7626436c74ae002b196e476547724e915603ae694319c25d6e67279692e96f4342a6a58a5e9300c4439e4c6d19fb24af14de61936d4d07a4a1ada011583ec08b2e033118a197000120ecc7bdf5ac377ea3d45cac086b523cc9a18cab8d83f02fd18303cdcd940066a8cb6c4230b044378ac3676d47ed4fe18e3cfb28f2aa8fc050a59aa6f21800c1497b06616717590e6842aaa335787d34fd9f6a5040786fc48395a30138e400e39757c56c55c28dc5787477d1320acd67b99c34633e5aa27740b9cc5e4b0100a89aa280bc8c3657a3802262b71a81b8ab0be5e95bbcac90d362f2fabe53d00029307b03265cb6500d507244fa84fe9d4e5346c6fc81931460976b5696097100b54135377f8aca39dcfb9f58ed9e504f03742fb8febcd52bc2a7cf6c8c3a3c003abf9c2897497ba0a648115778877556df41cf74a35adca5913f38151569340070b4d9a8015434887b40d4ab629c8aa1fbd8dfc9a821ab63efab52eb55b35d009a47983fa0fa2816dee8036b23365d18b3ea7c085c6baf803c8faafeb4bbc5009eb06858fa24467a9469efcc568815116b392a4b266ef1fcf533c3196aaba3002933a73e83793fa1429e632c23ba60542ab85da3026807cf106c73ab1bf2c300b7044f7adb370b9cc3012a5ca0ec8ddbc45022ea26a22bf88045593ae20ef200d19425fc6eee3a7e33d505a0413f0aa9f60ef1e4c52df0d92125ae7e5ebc8c008cb64a57f9fef2762836f43897f8aa40840290350d83044ad994058e99e56400c3a600bdea7f082aba09a877023e0e80b84202a8a46cc31784b36cb5c7eeec009dffeb720d593c4b32a530b0d7823501000313680101d8f21d575a42f7ed9800f216dfca0106aaa2307f823503000313540100e8913efa82d3693bc87d0f12008caf0986660c6de8f641d8855ba8269ac43089954230aaa0029702a710dc6d0015ba28eb754b5344e4e3bc6fc691986a6e1e84ddcd69c4154cf8ab66de1bfd00541d0e2773a189450a70f06bc7edd3c1dc9115c4a0f551b3d72cbc70095a7800b5c0b414c6c304fb85c871be08c8295f9d1ebfe8629aa0333d9cfb2b7f44ab0077e1f534a8dcd3b3070e10f38284a32d67ad1726a5d257a6f8de82130684010039ee3383059ebb7491a04aedfe5899bdcc81b6ca652291a5923c64d264f99f00da169459475fb10c7f39e13c6058433e56e1998d2da064259e701b7fcd6b1100a8c384a3200db7bf334fe4e87682dbafbec00b87298d3bf8ea82179d84032d008570830713f580681a13a60021aabb2e7160570bb153edd0ea1775ec2b2ac900b65f1ab61b0a04a16bd04c0133010004a16bd083104ec4a0a0bd8cd8371baf00cf5755db360b26c7ce650d83e14100a4425aab46423dd26d94a00a8928e1760018a7b6da5efda69c602f5d854449d05a408e981b900cc2c15c245ebb8402d3000ea38202b5b05808aebccd4d0246685e9d2129aba3fdaaab98ef816a8273770044a5e9a05fdd693233a3d387a8399230428d0c4f4c6074576a19ae6d43f6e4000408666e2df8ac5a84012309bdc3a07bbe90926c504b000d9c396f7511fa4e00759ef78bf82078aa32858550a883d016a01fa9e02e66debd960bb3c4ac6eb600ff87c18cb22cf320288df7fd52ac30e43cf0f9021782014484013cf8dd8303005c1888392dcbc5a890e8452e7f393fb27975b1cdcd3da67b9bfa41a29fc609000db1403b32b8562ea037a346a534764213796b1f40099102f977e17852abbf00ea76bee2749ca6b5c669a4c7148302cf6908621d23aa00c080a06ab5f93f0a004adf39e8aa1c8c506efe611b8be50e0acb6ff90d8fd77c46744442a04cccb90056a7e95fa0b91be133ed9c3c226a441dfe39c3ef4929cdaf5c2c36a788383400796527ea427e4ea34e66fb5bfcef2611172059267ce0db6c6f6ba6e45c773100413888e400e2bd319acbc550378d2ddf3fc8976c9040a0515bc8fb17e7294d0098f4ecc56dbbfe3ac61a9daa583226d3b0161889fe70ab52f9020f828b628400028a95da8304eb00cdbd747b62eca1f00598bcb599c976c68eedf84d51052e00f45a7b6043cd453794d881242306003cf18291f127a00a631a80db5291d793007d2fc429ae836cbef0e104207e13460358bde8e55630571b00b33a3411daae00b001ea035508f1cb2e9e56b7646b4ffd258617ab7ae87e636ee42af04c253c0061813fa0779566d9a3d63c89d917373d6b43b6a8438d7e3c79f6594df02fbb00f6f52b65a2f8ab6763bb2d211bd7201ec0efeeb308a2b7a499565708402d750085d1e58f027df1ceb8a0566f61d961c0dd72635b277847fed1866b2088ceac0089020934604882bd83e410f907cf82024b0e36739488edbf47032fb84e45d600cd6d8199f28c7aa7707680b90764b80c2f0187bd29495bc2052acb681a2101000202206001d37eaf7d751087caf2f7e61ceb7620e7484291018027104b00f600dcaa21faf67e1e37ccbfdc23e79849f78000000040530428a0e0532710acd300493dad7ce821a0e922dda3edacd80dc1ec4720c00003e8003ca20afc2aaade00219fe970acbc88c63c9c4d0705837c6701495f81cedd2cba2ffa1a238f64ce00083e7979bc898b1bbd04850169b3fe95ba2edf73a056aa8325086f803a7c47008773ade15170acb0b3ea06e16f75581d2d18802a5821522df7bf361aa3571e00b81ce0f9327c8c64cf9e3b47336f1458c080a0dfc69ac8f2597375e6e2a68d004f49b52c66dec766be1027a36eda11597fb6c20aa01530e5bdf06de64bc66800b47d16ed6575c44b79b749cac437683f8a2f6c5c927b1c2df7b4d5877c7a93002801a02a28629522392d4235ee99642a10fcc4d6d46df47484ef941a77bfff00644cdf74a01e0b975a37eb67bf7caa73e7d6b26a22e557686f54342240923d00c8ebd1d27c73c5dc2df705782252251695a5ec2a78ab9132a9409b936cb72b00954790973ef2bf8a475a48aa4ad091a03f1b7b8a5e629e5ba6f9abf8feb5f600b3bf96aa2e6c55e500789421543b75891f3fdd20ed3fbb9ea5f1aece50edf100c7912a754b5123d1a972f08ebb41a4c8e81377c45689dfbf508ab1a98f6a11006ca2dec79a99dc9aa0164f58bac68f92308a873f4c5d544c9194b1e448f34d0025997efc28de5027bd74d12df7b6099b57cbdc366e534e0fc337baa162286200462736a792459342c1da8ff6878c96a144bac9850697464bd0a01311f67d8900c0f2c3a5642d6689cc532810a9b159147270b11030e3906c4439237683f0280093c2179dec9960572fff3ee32b2a11204d506f3970eb2142be5c2497b7e78600d36ad103d2d0a059857351584582a974d9554acc08ee29d0b42c4bd1275d30001051889774ad557702f8b1830827503c82050e840144459c8303614294ec5300c830f4444a8a56455c6836b5d2aa794289aa80b844830cbbbd274c3795dadf00ebf562932992bf241ae087e0a98c3bb065650073b9c001a07bb01597d38740006d2f2b73c383e24f52e80e030750b141e68d23cdc8928e9e1ca0626f07164200d1ff63a22e74f44729a8d41d37b2eb056f42d3046d18ff0db8c3a7f9020d5b0084029117598305f44b7909b58c4679f26671f77d88236f7a50743366e1e55300044ffc30b85d0273a67b85018f2ed33962a01310c59ba6efae85be3fe119fc007d82771d02e3ead8511172ffb59d766a981a1bbc004388e97531e25263703c0076f9bbd34cb78ff129f150014e25f36c6829ea23cd87ed01399d98b1a8609a008c2fc617a5ff2594652addf749afcc6515c4a02547ee90a04de5d35a597c0b0058fd74d0340c9dd918d69468df69bd71b5ec81a8a00f7fc8d8e5da5f7c08ed000b7c2e3b1eb808da8013f1999123838b471a8ddadb7702f9012f6483fc25ed0083075eda87090a96328efe00b8be070344ab770301020400040101032b10ce00bb0201190208000103d702b205302a23e99231beb7193595ab087d5a2483ff00efbca071347df469c4e64820c95b01b957be66139496e47e21b551cdd6dced00020c7cd99d790d475008a37260adf787b85738a07865fb5c5b17542c15165200b6f6bcec5e2f41d81e26f247bc53e54cd4ac505360fe471c5e7e0fba80a06500f6ff974ef3b950a47c3d964b02b928fedac9f4179c5d8d1c62b271186b245000a0299980d14d86a180c064096d8b5276856a3eab61375e71a1c13c4608ec9400ae7e02f8b30162010804e79ca44408dae5a57ea2a9594532f1e84d2edaa4c00001a0593d25a3e235368a801f0a0b286ac10a11d200937a3507abc3cdf12943002da88fa037c7e505f934acdd022d97d5c0bf993cb0fa37b53b631d5d96019300bd200f9a453405e4ccd4db0c6580fb2d100676f3d5641199d0840a68e01aba0059b0ba94567ca2dd4bf6a705daa33736492e88567c71a0642e5f0978d41c5600195f64f6b3853fe9162a5b6495a3df85db6e2be2f324ef391d000000064a0100ce014a062b468b71d0cd1c73691bd9553ab47afbdcfc6c440fdd68382ab8440036a84aa05b7a33a0bf1f4648b464c65979d2bfa122352348b82789bfa4ba1d0093f9575807f8c782c7e906433fbc526ef62a8cb76edaca99e14aa341964d98005f697e1e95575d2212aff2936fba31813c2ac26060e194c9a04dc6a0f4c4dd005cd3c12b2ef065eb69d7406e92df28bf9897cfa39d082147ef691e0000000400f72ba901c080a0d1bfad6580cc2257e991c160547dad01534570f8fbc7eb90002a4efbba3bb6c965a06010309193e8846c126d6013dcfc406fd4c1f86cbd24002c61faf84721962a1b0c53aed5078fce6a9cde7445d5c7031e4d83aba9326400c001a02ff4c4d605f601bcc48023d99522782fedd70a706f648e81031e7e6e00c0483386a0229116bae04c3ed7d6a6ff62a3712e45379c39dc0892e37ef10c000f36f5fe630f0de3a41c6ac29726c680a030f9e5b920df6e637b1dd800e8dd00b9b3ae2c1b26cf51075e2859c17fc1ee9bf4a03b1d23918fd7c534096cd69300069cddb3c2e5bf82fb5c5901e087a418a08fa1ccf8910fb585870729013520007400a4f9068677681a1386707126dc8071c9a808ee7092969fa8a7db523ef800e38518dbc7e4bc3fb943f667ce3c72bf08b40ae1a02a65db050518429f9be300a6e2f90405d3d4407e0a1b6cc29c539a7a98bc06de2ef8cc2b8401312d0083000564059480b86469328decd29687c813d741e2f938f4ac377128810e217b1b00007c28b6580b37321b98783983b266f86406acb88f276240240d4beb1bb04f0069743210f7d255e52928d236207affbc54dad987caa020a65815a2ac094eed00b19992acc4c1ef1e85b401e8d87cbeb23c4848d33bf1f87783f019c3b8ba8c0075c4a0b5df9730dcc59f1bb088fc3c8bdd9082af234ed1e1b73aa6cb1961aa00e62c04fea02c5eb89697ebcff033b6b13dd9981e149e99fb5b25d9c8ee42f300b65cbe27a7bff9020e818e840297a9808305f4b477a2d8d0201c1b8ea4169f00c5d6f6ea898ac9a1e22a4430aa1ccf47ad36dfa0456a26c9d3176ea286faa0008ef8abbda0029da224c358144d3f86e319a677ddbba0db9003fc96bc49519c0047e765f1e85b02f8d36305c385940de3e93f3bb005dc6558c1d70641e9f67b00f1c85eaa33450b471cc080a06a31989672f1ffbcfdbb615190fba056bc0c0a005e376a3fd70e2b9c85895d8647a071499bd1bc6ed9ace5a3abc7ef37149c13009be58360eb0eb3d6f736ccaa52b1b7678401201e978305e4ece2ae71fd18eb005c18052628d48f6679b1c45ab3b7fb53d7f151b8d3d57bbe4d62b9880556b9005966abfe818aaca0088529acf6f51f53038661bb020c48ada2ebe2b721fa4c005b1ed00973b6e9fb401f050f7dd463574de30c3dfa24a708a4306a09640daa00df5ff86341749e3e94b1e838b8061ea0789082c96c69a40649b1692307b78200164f84ea87b5da40a4bdf304d284cc0f9ff9022f82483e0a2cb201c42db0af00d0045f3518c77ec6591a542e326befd3d72475ceafe6a00051688067159f080002673e9d2b53040001f400674245fa828078ad5d1bfa2d16913429e5e9152d008077148be9c9002eeca7d20ca031c369b884f6a63b30cd2bfa01cd100a6ab90076c0b3af868624bcf97ed8e7dc66f8f082d2ea3010b0860389f8fc4c902e1d005d2f26eb023c8b7dac550ae6a15a1ba09756356438c3a0381845012fea298b007e57a801af39d0bf4a54132e5b1579a77418848418b9c2dda0419e82de64ad0061f19ad4cd692616d6adfb0e44c24eb9cdb78e1788d4b1707b67f901cf820d00e4a9f13d2a65b86605e29e1567af59846acac948ff5a3dfdeb699495fad7c000f3b1dc1e4a5cb66074479eae78b421a0091ab082bab3f9a8dd99601cf2ebd600fbb546c8bce614f8cd2f4294a887b780ae5e06c36c465e78d2274c3013cbe6000bb8c3a05170c71b5a1106baffbb9d00791d371749e68f122b60e39fcff5d0000f24786b98a005b865b34ffd45aaffefabb116d370b14480d04ee99cfb75a900dd0794fb3d5cf9f8ab0184012018a882f4f99480137510979822322193fc99007d400d5a6c747bf780b844a9059cbb0000001220d8c528afa28165a1f065b300749e717db98a022ec7e937e22e25f5862169e35f7644345f33e6fba3ae1e720023563d87ebd4f4d5de709e9153b500a021b5bee26a78882024eb53be4feb7500dbbed5ddeb17d27a2f12e09b1c5140839fbd2588926382559e24d1153d9f49002554064c9f052afb768dee5860a702ba736c38e66650930001e5a004020805000106000307090400738677488856f88d6d779204352e80cac0969eea37691e008785dbd842f3e8f04d68676d2426a99295f0f94d7b37d3c53eede3e37fe8a800e7af242f330d24f4d2e456173deeacf6a0271e00a1572b3bae51a593b55b4b0065c9efed2fdb5556f82faed42a3277255755aa728e874267baabf23957897d004dff75739d2af14d74d290863ae942ed9c62eca44c21635061c026ee6915e700991d17eafabea491da9d23b7a9867bc35725fea7a133715f8653d2997ab17500f9bb528cbb03a49939ef2e9ff272f70e4e9c52a8eb368e237b419530fe260e0082dff543a17cf1b1b5a7ceb685ca3a18aa2e28fffe7821e1bd6b9082a3939600a6bf83cf21cb1b7951f0698636d0ca1f9d7d15f131580950356bbb1a7abc860083a16d8f8ac080a02c04f5adbe78397c77217e99a473957dd1587e100fe936006f649dab6dffa7c146a0033818e1c44748dab37658a126297f02dac40c214400bf85ec05725d434ae66b97f9022f82e5388402a387e6830639f506120afece0030ae36409145b34e6b0ab5dc990e790de0b6b3a75f41c791fcc15c27156f91007f229e8a60d87efbcbfe6ebd7867915decd9a0cbdc141c7944405742aa27db00a00605914e9c5942691f1b231dfafe07c354dd765aeec971060fae96b29ff10027aecb004a5a50c001a0cb6af85a217941bebebca4fb43c202f58fedd59e1300bbcdc8c12e59c2ca42721ba044c4c294d3e87354a70e0f81fbc1ffa184e2fc0077ddfdb3eaaa0a8ccfb279847d02f90482b893b099f48307a12094d8019635003308b3b0b34da405b049f7f4f0e4c298f690b6c0630c2703723137d57425b4003d0001e59d030309020501060804d0f9e178d11399e3d1161820d12dedb0d1003abb083f8df156d27091dabe5127134956232bcd1beaa46eb75e7068dd825000d4c9e78d17a946d6af20f9fe348ff54cf0b7aa095938c43e11ef799675001f0005717bc9838595d9035b2b4f5a3c1f54e01c4028bc481ee0c27147f65045e600b167a4c786d044a355cc9f90055cc83086517adc56598ee3d2f2de52c5d89300bf61c1f4f711dea7dd59a7251807a11e279eec520c07d382dc75147c4acc3100daa802029149a13263f5ca5468c24320c03995c4d6538efcff7fe13da56ca000b7ec8ecbc001096b385c122549fd97021927799c3d8d946deb30d60409858c0071259f80ac144d9cae0bec4018f787dfbc35dd0c713471ae531657d8da657b008f78365f92103527d2c196d2722f0dab95e00cf7c001a054cbd1e4ea40f604009947758285af662b018fcc076b33392f07a1f6d0cbca1247a03892ddb1227d00d69dfbe289f15815feb07752047ef3db26c22a4e00a512158c44f8d68217290084032cdebd8304d9e76c681a13ca0221a9aa870a04a053bca053bc7d42ee2700c33555803199379006ce678af3af6083ab7d775c0d2ee503c1787340a065a100fc5bf72157aa1fe9dcc992b8fdddf0dce8b4dd14c623f897a63ae87023e3f900016f82532484012c1a2c8302603094e690fb47eff7191fb156d48204c7339a00961a16c780b9010400000082729865bbb01657b2640000600003b4204b998900923b94220203096461adda17a719c207e3d521b54a4daa752db2e09fab22fe00a8ecced25c91fa243c4f619be28b000d151d4b60718a8d9195acced7fa1b3e000f5611d79a4f939712f03e8142eb5c9b1fc4a843cde2d0df11598d48b744a000167f00278770f8de1c9ef7dd6b3911fa03abdf03ae2fda41e12ad71f670f8b0046f8d68217218dcb0220be368003b3a00e33010003b3a00eae5aa896bb93f400c7c5660b7fc894b3892255d0151d7700d389f19911a9031fea0735dd95793a008c114a654e8659ab471a90e1a027a04ba83c5e959b8971fa92d98ec340347f00bd886eb91df531c08ec2db0b9a06932ff8ea82170f0613d8800021add8f6a4002a9fa42a9f3bf9cd92570af1bc980cf291c630a1625cf1afe7e2675a86e26b005268c206f159a00ff5022b961a6027453a668dd56113d50b999f23ee2567300070140b20e50e12472001eb3396f5a4d7a000c4a05e03e068ece95e5a8e02680059b6f21d7bbdd863de1a4e9436582e70edefb3fa90a0599d6283b485addd3000a9c0d5b468de06aa393106c7e957317a66f7d782508c88dd5653502a209a5700d353a7700de49ccc37e39b04a49768d7a545c6e495ccb220eb65b886ed0001006eb005020407090105080003060199dc2f0199ed819a019a163f504f8690a900f4000000040a1f4ca4bac22963d8aa335d581f92a4ef00e3416b45422c302c000e9c7b028e797e97c36f673a225900053dfdcef8f8fb5db13d1f60c479610f004a1ff5444577970914e7b3860006a1ebd66f4742ffe25f6384e27a5316196c00a5015d4c067c21cf5e4b7469ef6f8123375fa43bed4c6415816c14db2966620061644394ff2a6125047896bc5e912216580d2e71ce6fe694a087e6aa5baf8700c960fcf5e71348d5a4f95d439ada68286b4fdd86178a26c4467e49883bf44b00d56ca53506ede2f87bec910e629b19d49b6673a96f49253d4acbef86fecf5a00865a0ce34e545a0b3af998db5ee22822d07b4801acc07e747fa2f7d3d34b76008c9c029bb03275d3a435fc2ee7c080a0c851201aa79cb6d6b27e40710468bd00595495aa8e2395e5c202200af21387ee18a0579b80a0e471ac7d8c016960f60008f8db607e439f1663b82132d7098a7401fd67f8d682170304901dcb0221a30085fc9a51809a5180c4a0eb4089c4787d3515a5df2b3b86bc909d68c548a10d00f64ef31ecf4dc78a055999a035ce07520e213833e58e9cecd9f255f5d0e0d200351a261c84079c49b9f5c8117c02f9014883082750086483fc1660830635050080b8de0100000344295207090306693246780600020896cfa2a369ec67a93c00324a35e693fbeea11c05c001a0bf757d1c628cf53193ea37505c2b105ab6470024d59f34a5f0740b06af7d727709a07e1e7ac672c777d0c1a6e3c5426eef420031a7d021040889b7a06462e31215b5b2ef83034859011daf45861d9755849d002b3d15f52588dbfe6c3fb04e348ddc2cf4d98ccf3b02990fbde8ecb95852be00a01f1de9d3f986847861c4a8161be417c6902f7494bce0dae8c6025cf322ca00d5ee8e83f014f08303cb6d94cffa8b54d01962bfda2c6c06229f576e8a3897002c2b9ef01acef2c1e20e0870b0d283cdabafe916034c17faa5b26f23479664001a436853f9a19698b370daad302ec715f818794fbf95d5cf11e6869762936b0013056553997f1c048a04c4b22eba153518b0f109a35b093d22190b422e49030086ece292f5e5041b7f322422b779ff607f66fc0e090a0433faa9789c2cf9a8002a83967cb3a235a006316557f708c6ee71abad8c6305bfe2cd625707ecdad900092d9de01f8b9e3d08a04f1b4dd7521a5c7814f54446f5b9d7d86e7d0dc3d10074ff8a05287bf03e6f75fc7d41cead08b8897b9f3293223ade81b7ab3eda7400ffb5bc0f5b47df4aa3dda7584d9a1aca287d0c2f4409ec0de122c886941aaa0085e72afd066ea4164b7a527df1921101414e70fd3881a3de976a244f0783ab004e98741d5850668b79b766b2141c4f08d413688391582e4acb064733ba8a5d00c7eb3478a61d008cda283847d95d486c6a01d939639809abc35cd667fda3da005ac75d41db3b4d9a740cd5dffbf9c3897462d6a07ff2af5fa09ff8031e000900fb3b7b7e85455aec47d1238fcccf2b7322a7e20f83211ab608e07985f53b6900cb57be15e9165e78c48ac83eea90b3cd626cbb29cf870d2251859ea502a0510027338f0dc163f8c865621e8b77ec1d1df7b585d3b2ac3b23344e594897301c000f381ab5012716cb0adb789c09a4de26df7b6c9e43c7a62aa902c490745a890068958eeeb05540918b20389a9989d8094da07983697aec707d8ae8dc3cbbed00fdfafbd13fb047e03e9b9d832424f0ae9c8ac70de5b67d35f5eb26aadbc4b800fe70e0cbb255248bc7bea056abf2e52d28eca231b39663a063985d3bc663b600a179f4c42305500b47d86726b3978ac127e1fdd91892bc571602f90312168200053784030ba3578be1acf996aa3cd34c03451ebd99fdf94ab826b780b902a40013572d0bb6dacf7d94d871651a38e23ba8e4028ba604a60598215001988200000d1cefeb48acf26d0d1137ff66d5a956c080a09c8aac2b7397d1f75cec1e0200f45912c54a484c88a26057c0f7e1dbe098cdfa67a02ea7c11a174420b25d1b0081d6e6b3950a9b2bead8b54c340b73281d289edbf9df02f8bcc4f44f8d0a2b0026310f480843804566bf83399e4f750728d1ef57008aedda00e7182e01bbf600ffca8529f2385651347d2923e7e57d98e9204f7d5428cb52a7508043415ce10090a6b48ca3b640c080a0f96a999ed484e501c9a7c3df0b919ab2238d5cdd7a008788f31d3131fb6e99d8baa038bf2042440afd4e6d1fe237374822c36e9e49001edc74dd5a448556cbbe90fc4bff297c6a13155d01a059b60228f3da069fe600e8fcab7ee1ed723a48ace4d5cdc746a03ec5d4b2bf2b82a001190cb4efc61300cf7cfcbf0dfd9b03a64acedc46eac46583f1867ce05785d96822b50462ba1300c001a0061af64785ea0ffda1188987882aa42591f711da0a5f230b4da12cb800990ec7d3a016cd518f4b26725344d06938c908c647821745e35fceef3d2da9008576dcd5ac6e0f14e903f1d21329c94f90e59b29ecece7cac599ed261a66b300b8e751af14e5c4b9b7a02f937228b13baece7b8e961a0ab5fa67530cc9faa0009cb4f53d275594361117bcf8ea821746056fdf80681a13cc002295ca4e058d006cf6058d6cf68fdcbf939c55779a0c7c374796dc21bfbabb6bd3b3f30b600a00f30740e893f02aa028910625e1b2109be7f55ac23298e084f13a437bc5250200afa6a92159b4a33b6d541ab68f486045e7412c341890b15e66462fb83a078900d376c001a01ae92f70d7cbab2e10a2335582c6dd2d9c5a9e0420f478b8492200c23b8855d8cba07da284124b906ca374c1ab35c7820c08a78ab701c0ed155f00f9e30c7a88760d0bf8af83013bda84013cee728309f93a9451b4b067dcb46200c4f3bf1fba9804c3422534289280b84401020503190140a90e302449c279d60097badec6cd7b08dec0618503ebc2bdeff2b317c037d6a9e6b4a07e010832d1006593fcc8537cf800cb1d72ed9b406a922c78bb4102fb433f6448bb23b64c3800e22c2925c823d187cd3394cc86af6acae4e7758d18590254e7d3297e4f3aa000333bbd1f60384fd54d03a8017a9e1006c978900bb99cb495ec04b838a3758600a924c2776222f4259825e0efb89c5c829faf04a6ab3e26214bbea8ba9e2cfc00a0f80ea04733efbfd6de6937d4ec92cedea462e52d1065e910dfd839c5d6ff009641d6906b2502100e9437d4194016b46813e5eaac51ada2795c200b16e71b005e6338db7dd4230bf24df51728aa5ca05a3b959282b604f99d96d1956c9c230006b453992cb174eacf52d45d27448cd43c26009bf870666d3c980136af7b2f006bd2679d86071c634710deab6f616e3fc0e2a60398ce21fb1e93a7bfa04725000d87e3bfa3258495065cf01a805fe7bf46e44ff51b1adc68d4df2b1b18469d008332e6618401b3764ab7e22305e1bac145e3bee3fdbd77f9c074624c1e006800ad8e8abaec4b6de35df8f72eda0f690001e5ac0404000705020309010608000000fb7f3e0000fb7f6a84268ad48b3f9258000004a052b939de61f5f83fc87c007e586437a07efcf75ebca1f78ddfe6ae48c14e50c27d9b9b5e915dd0526b2700456bf909c0dcf69ebec755cb2d23491c8e9020a6e27fd87ed567e7725e72410097a82f30aafd9efd80987808e61f2ea4a28b5ae712060a17062f5f325bf5f2004a96637b7d584a4caf64be257ad701989e7bc6ddd00ffe25c9bcdf8baad36800658e985bc06277ea9187009063958a04d6aff60e5d1c140c2c213fde9c3ee3008e0e03015dc249349e2fc0a1ed848031665b2b4b500d7bd1bb003d3353d375001f50a3b668cf71f183e51c881110c14e6996c39efc99682a8a8211ae3ab9a1009459ec56b1dd085598d283bfccd39dacb475a074cb32f82da7868ec080a0d0003fcc6503087340902aed12077d6e5bdd2bfcbecc4f4baaa4a2c02607e7cb4500a0228026fd83a94736be1587112f097c10750c12f82109900f54e3eeb6243900480702f8c0db9884025a4224830610574f018f893c533e8a403836a043c1f60087ab39fc112bf4207e160f6fa734c0c080a096674f46001832f01732dbcbb900eda5914740975de1a539ef548d7cdfb174dc7ca01dc8e2127168069f3f9505007790f96d8d9755e314b04ff55c5aeda8dea7b0caec78a740831d9e5d0120d6002ef6fede56ab0d0b72e247424ed25b4df609ffe46bbcd16f64d191a061b0af00dc42ad677fab2b86559ed565e7931fefb833e40205db1e9c007d6e9ee8f8de008213078401a73ec08306304b0f3e059338adb9bc056f866316421dfdaa681c00b10c69cec175dd3de514b944e9aa10efb0343542f4f397cb62d1f80059a0140090c012d39798b67199e85d6d316504c993123277cb0c5c7b908bd1f23d919000f9010e821059840163fc588307a18fa40f4240047574bc9bac08f22df6b154002b9a85686e825d58d501080a62fe7f9a1bcfd4f5fd399f9c75117d60bfa15c00c6f8c0f521f62434407fdcc576be9ac9d1359700da224141a001d703d92e7a005e238d1c31cac96947b32b4567892b84d5a5c5bd980f66038fe9550197527a0083a1ba383fb7228ef1d1906436e897d779c080a088b1e2830908771ec393b7004fad5c35d2f20f645130b5ff969fdc2e0baa22a48ba05edaf2ca40ef0772c600989e01cecd423d5736065c08954963f9465f41892f6a4c27015305378e76f200d000019df0f5ff3136153ba1af65f0af9a1260445edee0e602e1c30242a4cb00ab6963e5a0225e8359f6551f5567bbb27ca67e0d0552208dbc40c51a147fb400234e52cd51e9f8cf82173803c43065681a13ce001fc68fad02bb747d00000200bb747d78ea8e533c834049de625e05f0b4deffe9db5f6e4ed123aaac4fbc78005829b8de8d0bf4cd6729b9f57a28b4f38743b1a353b91186a06175646cb1d100f5e9fc0e6705c820a4ed62971d376d8efc6687c0cfdf1d54560e3958c8846900c3ff90ab05cbc20a2d19adfedd6ec344b97743b09a02ba082e68c5015a0871008ce623aaa02c297b9edda47ce8f305ada0a7b24595ddb4dfaf7767d05d8a5d009190ccf369090de601a0b53cf790f97f1f0f2d580f4f10d6c10f65018a052e0076d08b75f522788ae7a606a01d94d561b11276c6ff03f62b12907a6636494b0003e30f395a4ae1a758378b9bbbc8b21ab60b56d135c080a0cb3307f38c4240008853af0cb62b807511c72dd72e03e093ab686f6525252c25f9a048d6ef61c3009e0666d9e11ca537c0b5aa6bafe63953f27e997bc0b0fc319e8ffc0f15b5b6000cc163cb7dd2f0c6a74e0649ca3b895ea5e11624fecf277ccb7edfb30fbffe00a03daf5c1ca09716452e497a7d915fb6f9b4aba7f0bc1a139d3177d3c7309f00a93e02f8e5c5dd78e72408bdb11b400c0b87574bc9bac08f22df6b1542b9a8005686e825d58d590e3d56fb272bf6fa1cc3c25aa9576b5b48b5102677d18df50025b1f15b99c1822f5038347c95d462b10820c001a07fbf514e2f61601443b900c9b460cadd394b5c60dd48f2c43e7c5fe6ad51bfbf72a025a6762e6776df430083297f20973096b89f25ce6eb67bd6a528e70c90ec6491e25e842528852583000696366edd92110f801e665a87b0a07879d2129d5ad56421cd6d1b5d62c57800321c4cd57401d972a2c689a4cee5f99cf1a03679f2c528beffbe7e94a5854200efc1cf04aa39ebe892af4b0b7d8e5d8b7bbc2af8ea821724cf0021ab89c1a100badaa1bada0673b882446612cdbe2010d256314dc9f1b10f9ed8b33b6a918300721914c3f5d6a05b2962c7a70899f67e12e6741291c8d5caa80d177915f98700646347754e164b35f8d68217680544366c02218a6d300a04829b35829b35cd00081f5511ba4571f6fbb4c30b1d1a484ce27a9391e7d75b30f7eec3e0fffc3200a01cc27e5e52862417332f9d747f999ea0914b72d0d26ca1f435c9b0d3b59c0048342855c33fb036ac551b256ece1f03589afcb16e68f243bd28cc550679420017a0bfdc01951aa055e604adc54bc69dfa5c73143b7423a1727414c94cb57d00929a7e0d903b1821db02f8bd1af808bc719408ddee7f59dd971895be012ac400e7435ca2e1eda780b85007ee74ec01d303c8c080a06c1ff3bb0a72de3faf2b00f4cf6ad25f7d418ffb8584dbcb1c6f5605149b830ac1a013c4057783f74063008d0cda108604111cb2cf7ab5d5dfd2dcea9958b870c6bb2502f8cec7ea84020084d05c8403bceb120e1a368af7f1633a66351d82b3f67b94cd4e6da3e43d9900c8d80ae0b2484d21151192a515a0399114e6946a875be5f98823b0b3c1029700067f1a70f9caf4633f909f1eec79fdf8d582171e063734cf0221d92a4eccd30019ccd3199fcf88542a8efca51adda2753a0861085c95e9b779e11356ab618900f0a2a7c2cfa039a309edb4233c13d60097ad00b36f57de1e1f7623047b737600810e356c254edff8ea82175c84032d800acf0022ac3641a25243a25243c4a00009796951a3331f7ee67caa2271eb175a393cb3e6ee07df5ebab6978e97b6890070a02e22add4e55801cce997d5a5058b7efd35b12d22a7c29f4ced911581b200a61c5902f901d6820ca2840284d05b8403bceb118bfda8f4c6524c25e3683c0014864114443110863314c531067f2319600c05821c89c230ade51d14000a470042281e6414121e100c0a080a0a12221486c3c160382c0e94c523411cc4b0290008cb29e830077cd1dd8e5ba939bc17872dce24f7594620d57d4268296b947400b54f0df2ed7cba6778a2a0af7cb4e1bfc357add5fe4e2809932a7e01f2234d001b4c5a115ff093cbcfe0f0ec53720f0bf34ff2059c318c33ee421aef0eb62e00b2fe82187d37de3c7cc4de66921da88e45c46d5d68a2800b529f34fbe4eacd006174223b90ef048429570e07bbd461e01f3c3f48ba7916fe5e903db20df55200b701dfb9fc9485329fc32e66d0a8a6512a0e78f0aa838a4f8cc76941edc2a40027bae00ce28023d2b6e48e4e145d54c93f4bd4c0d8e5b989a25c9c172293fe00d0dfde6863b0a2a863e9dd3f41c967591e6c30d3a1011d896464cbf0e5c0e400e3afcdf359e5a8e98e5c02b49b80b6d956cd6b9bc6afc1980f4c4f8c77ea9a00ae311657400145737dac387738ee82554576c9b4c052e405501b18c1717070005462079a5441f2281b0c19fad5ade21c1b1d03928864cf4f4486f74782e391007d81ae6038cc9f8c3a34d4f54297655a43903b3438accfc38d2764d00cc69800bcb7be7705483e881fc40382b2e8822f2db3372cfe8366f016a9bab5f157b7003b7b7d4c683c2566adf5888fd5bff7c0a0211ffec7534413e4a7200336b60a001a2ef2c4509885c7708e3645e6c0fb0404d9b36da8b45fb9425dd04b7d10a000b30adac060be6248476407008c84c2fc5afe86121ea09d89cc7abf3db82bcb00da334ec0648333f0c767897cbb4d965504298b1420089874f34688cd2ed9c5008019cfc0759e5f247051445e5aaf74d4c684c418673cd454308f49c1ad594400e0b6e9560156a9f7a4e1015601ae106c8d396e90a3f4596d77d3caa85c0d67006f802a1aba75a3f6654af9d5a477d9a3eb2a265bbf38e81d0928be09eaf0210003e02fc7281ff8cc1e67dc1f817feac816c7658e77fad13a3ca7cfd81ef78300dec6448a62553e9b636534b96a98d155163d82e19ded8a28d56c70df75b45e00b09d3664df6b9e5a3a3bee96712e90d2e765ea01f43e9cbcb4b2178ccff11700a333deba534836dfea9631bf7e1d4d4530671051dc17f73c8800744e9a414800382a9a80cd8a7fd7a16177617ea7a48c2728850b062b7e53c5f78f7aba3ae3003f1cc21697de774ec294ee39b3ee7b75d6b2dbb48b5be9513a2e67e6a6986600f006b8cad49604aabd089276a462232919fbdd6d32f6331f159233c894e99900c4212fa6b347aa4af17255623a05b8403d1ae4b3df40c15b563e952b82213100127be87e9b97e029cdb2833fc20be6b37882de37682007a268e57e79aafded009f404771a167a3b2bf174d9a9ba31db52326c38f931421f051c91102e2635b005bd5601934eb9f6bbc554fc758030476ad012fc4b4ec8b31be607344fb372e00fe64915e340342f457d51a0bf144665f04d3a489c43ba1f70a78a48c837351009f98719028033727ef7ea5527013fcae1e033ad01f80972a147233e2e6559b00d309258a8c5c5206691e7ce6f64f01de38a29fccb87ed087c36f854474997100c4f3477162c9e50219fbe530f88a701c01196fb266cf130def62ef6da8a7d8002c63049cb7f81aaf9a87d2ea68d554457ef368547145644931bc8481f52508002693e708d1bb9b3a3417e2f1ddaa4a9dd00212be0c9368d0572eba41eb57ff00c077530ec407017759667b447ffd837f1098b77dd9ea9e0cb0aaafdc0a4a4b0028d3418d11aaea090f2efc488b2f4ec7d2b2d6e61603853c2e9a9107e2fb4b00104c0e16cbd360753d4001f3df87833a4a42368081317e1a910c496008a8e0009abff26d3fa6468695c0df0910c8b89b50c034f56d82d0567f0ff4d94c5e6600c9410372d40e7710fa92bacfa097ecdfdd703ecc1932d80bd7fe7e1dfe017000c892286b805f9f3e475f4cddff765ea1610b346405f3fc5aef556428831e0b00d8192a7709c7fc5fc66c2ccf01c1835bedecf3999fc9a80899612e43e3d54600e026cb8caa631e1bb173bf81ad198c7347bfec4907bc6d94c8e01387ebc40500a17a6761ff8c8b68fe1e720957f2a9e4ce832894b0db63aa2e859e9aed725b00690b943f18b3c53c6f271ace7528d14a2b7e802f149161ddcad4bda367741300cad0839e555eb545672bcfa2ab4033b63a0e80d49f05a4cab4e1a041f2cd70005721e998b4b09b8018a35b350f603763024ff16a2cb0e9b621f773aa08329700d8d64a95cc97248805670d8c458fb44388a69f10ec2b631f3851e8f14ced0800ec9d14a30ac2e8f0d613d6781dac0ed49480a202e52420af5b941382bd007a00129c99343eb21c06b0f78386c8cbbf6d853f321eeca715dc115705071e6a1b0058837338af0dcc64c7cee14720a560af09f42cf10cdd359ae2dcfbda42df1a006d1fccf49c8d1f09fd8b340f3f482defcf1861706c1608c0e164f65fbaf63200d194dbaadbc813cbaeb27da98f9ef134ea84789d32692e8197084b4b58d4d5003172011568c14dd9d365549c0b3d211b49988422fa90e0ef1643e36148037900014370a8c2fce1f4159ce3c1123ce7c551f10d43650700ec951ce1d40aef5800e065035a041d1440036f507d3301eccd52f6cd948345388bcdab7a3e07a2d600e4c29cf9729c851a64e96e7d3c69c1cab6d573332c364c595c7370c80568d3009532130800380d7a2d648ce79393c8270cf261b147b50b341eeaf045c3c80e001ff7dbdd45123120a85d804df7766e8c3131ca31b933ede167917a9d09a9ca001d11514071b40d54bcfb727e1b233528de35c1eabc6ce64dbcc180758f01fe0089c15d9a03a43bec63bf5e022f5e63f96d459aed7cfd5341329135019953f400dcc224a88aa1447f33a3b88509e5ced0b7aedd32e8e98e3bab7f995bba355e0001529344721809a90b3bb0cf60a034aaef2f377d3b550f22df35005441a18b00437811d2d627a290b008748fa0538ceebd10d08a905bb1917152e8483b6c450020c5ac93493bc730bd2a9f30d52c5732d1ec9d3c05c32019834dcbabbc3cbf00b0c003c1c1e73670960265878a03a6a7ef4f48e0b0004bb64bad637e1fa5510006ae1e8f588e0a7c0d8382f59221f87f17b0f459c3ad80e7a9537048a0609e00fa3f8d44ae105393a07d060a00dc01c99583b7895070a830d1f4f47d374f64007ad697a536056b14361c540bfedd9810bfb4161294acb86d938743be0132de0066be5d7785b91e0f19ddb68e082050c9abce87b2f556eed8b063ddf6e1f65e00aa94bfb87bb65bc8b959b0ee64533ee0a3d66c6f95c9e5410f752da96078e4003e283f6eb3881af4d5cb8285fa6b85629706bf1d1f00ce1bd48182da2f584a00c10a042b654a868ef05168a7a80d40ec322162cd0d6bb57388704dd18cd94900c1c7a62413186513d38d74d739cc00496efda10a8a2c3dbbee70624af4f8ca008604f07e10d6ee3fd902d12980b3176cf816c9b136672170d1e809e6e0cd620076c5b137223becede1e06d3c86cf3f0c0f4fd93de1021a88ca0701154c9e3200280e29b44a188a4031c7f48c724eec8a86063114b1323e0259f1732423fd30000171e01bcad2728217f3b9aa9d72d0e4a9c49fa8fa50fcf6a3c500902600d8005edbe2d607a441f0901cc9001513051415fc530bad6805107349a39744db2000ede9a03ee40e866ee43ec0d2c8249c14c91ca0f84680bfd507cb8e24403ece0015e32f0e00e458749f1a8de61c2b8f12eac9e9f9ae0fed569cbb6635315a7700412ca1b1f31f33d178eae08670af30fcac3ca51541bb73fceeee29033a997800773ea3d2e003598d32155e57f6bc6665bdca845a1b0c547c4e8f23ffc347570061b38038c089d5e55737b23562b2cb5ed006b270322ab08b602786128848fe00c26ad1f0cc44184e52780c633c2cbd7a989c261760d6832dbd066d3db3420e00a74c8e625bd59f4cb604b14b9743f26d9aeb3883d4dd25c61a10fc1cf75e0a004c06b487f95cb7bb494e3743ceb9236db9ac42abcd97cb6d2055cc9585d64d00584f584ae3935819d003f8d88ead0551a2ec90c6a2ac970d52ea89d60ec78d00e64c5db9816b0a333e368100559bd946cb63b5ff06c9fac646ff2b5b8d366100d79c332c5a4d6aadd6c83d30f83e0b56d4dc634a11bb9fb31e7d611513c92500fbecd4ddc321076378bb9e4f0b859dd3320e6e14942135249791dfa1182af300b358eac5b240777081c279d3fec3a9810c065b604570dd5cadd45159e3b1c500a090dec3da144ba47b9e1b3d07969aaa57d0216d3c44afdd11ed455f42e84f0041ef658794bc2ccf9b2fda1da103115c3e725a55585d597f204f693580569c009a8f5bd630d29d9105a94a64e0bcaaf581266947b4208b76401dc6bc95a40200152a736e6e5f7eab2fa8619dbed84873459336ce657128444be0efd69c6cb70033cfca4b1c0d80d6e9eef2610258e3ee4440d3127791ea29b0db5e89a2aa2e008c64036f9ba606c814e7b86cb9600fb5b1068a7f8f42c5a72758c86b2517a4002c2a7563a7611197f63473ded9b5c0b5971fe1e43af2e864d7e03f21f3137b007c094d5a60c4ad4230771244242ea448082e6233d41c41d24fd6fb10d818e80017de7c8a18574a76809405a2dbd6ab561ed50a44f9de1f56cb1b31cc851881006533665bd01cff371aee030734066d201b7c78f106e8a3890ef88ca0a0db5400f70017d547980de52f67d8c2a0af40792a97b58a6d691b7ef2cd5daf26c17a00e6de073f7b7bed0a7686a0e67b96c5a4813606ce195d2108edc3a229fdf0b700376eeada1d6a17225b0a4826cb480328d955e3d261677cbce2ea2fa2e59b8300dc2ca92b05ed888729fe6f59b1047ec4b1a77829a1d05aef79c810f1e290bd0020c91ac909ef1ded8f8016722a99ab7fb45470e60b6ef97b7c66c3d10c5ded00203d7461a26c5c7b9dc586d264a5a133854a16ec8a34a7980e498d68e0a05000561b9cb82e662404a8fcfc22048bcc6a8552ccf9192c52433e5cb955f9e98000488f61d2cfc9ff1dced163d7d0c58f3bc6d23160528bc8e0674cf70f86b733008bbc820fb5c3892899a19ebf31f2661f4584eb0fb18ba06603ea82de721f3000fb6bf516f5d15bcf7f4859c32ed8d059ee766058f2dfb65e23f355638a20a600d7c24c2398d5fc3260c7407264c5c42cd06e64e51a29ef9ea33bb1d3b7cec200c077c75e639383c03e1c65855852e7fe9d537700d2c9c628197674e88154210093f1e024332fd9281a808a6ccc242ff251085a4dea6a5aa783caa6ea2de73f00e24683677ac1e73bb542fbdb0146176539a380499eac646344414ecbf426c60061d5452ffe6194b88d1b16f7ba35cc828ee8f3121071e68376b670592711cb00ebbb28bbb3e018d4bb8b7b91f83bc8c288f54290e8342c6a2ff28c4c6833b400e903455c4d0f257d282e9efbd891336faa0a72d40a1eff5876f26bb9562035002c4e378ecf7484239aa78534ca5da83c8890bea665b2ba54fbe2eb8f37bbd90003dbb9e3e4076123a060d0d058c5f8d922169eecdbbaa142ccfba32e3fb38a0063a0c9979e15eb43761d471155b2f94c10bb4926d707c2517eef0fd83c792e0072a237a48f7038c73e4803b450c390a029f6f215b9aa314f169658e3be20d200e69190f1472a193e42c4967acdc12ae7c100950ecce4593a2ccd07e1e334f800f8f26605d90adc59cd3ac8812cceb9b503ffaa9c8fec1da9e957713af770320011dabff990f34eeeb8a02103036be7074998438efe01b01f059794dd40232d00387ade04f24cbec5f50688632148ee3df4d9e5eec60438acc1e195f472e4d7004a60eca6374605b70b6f982d8730ccc231ac4ba96661e9232cb2c0b2be66de008704e9f5dd167a800b6e16709ef28fd3bc473efecf30ac73e15a0feb770e140092d347cd24214d3015551f63a883f23c8dd9fbf65e1cfdf09db56d73af8713001dc35efa0b4df015a25ede1ed6fd9da0908a1975c29acc393039deeae3a838001660f1ad6b1eb458e8a6714e9db469e22854dcdc98f65da01f85ad905429eb008517cf13ce118583b543cffe1ebcfd66b5e4ad1521cb735626547cd06bfb500017f5225fc8a56b269071c98362df0c0ab0e953844f89fcd283e90318f7f10700a8217f5d59581481e415548659f815293c10cfce4e6b32f7ade66be981acfe000185e83677cf0e46999e508e4af3a2b58ef568da107eb05e85339e1a71602b00a1486ee472d859bcfb3128ad692f785d432a2320c763a78e5be5c216f686ac006f23e46ec2f502b88305b58527b31c4f5ce587879de5158e2532e1911c3c0f003b03ff2089d778786f5804121c19640ff961a23ac2dcc137f11f58227472b30046b198101de865232be4107e2fb6fc380b284faa7eb156ad46987113e346f600c0468068d462a0144e443b5dafc953111908b410301982d68869209f22635300bf41203f6780044eae0a4d29d2e630f77aaa799e9bb526b25c035e507822c800a2db252a9c8a56485a7472636c94042e1b3608f9fa8618ed8a1db521acd0550081d4df06280dd6f2401de9e021b29a2481e0099d3e8bb030d97be45bfe84ff00272e8defd5efdb0135e1df14bc9ebd0136b6460f8f249c014f24feb58971dc0092de7059b9a2d1d5ae6042d0b01aef338ab5b156cff999c10901f0b288e8bc00c98ee0de5c52e74126a6992083b57091cb0e6cfc42bc84abbbd2f787077f8600010e8b172e432ac5fdd8e29345cef46e920cf3196908807f5b79d3aec4f02b007698f39751a8eff07a194c2dfdb31194c521a7105b44a8feb5116f2591f6d9006fd480a9ee4350a6f053b07462a75561b5e985e2d51c50192754232ef44015009c0e59e1a7bed4e64985be10f8a256fef73d6f634db1917b859959580abdf80071842ae2e4ab1d8e56a2a0878a615e5e2495cffe67c8d4708139e30f68d77000ce05369558b82816c0a6bac08564261b700ba367ad5067041832c387e4481d0021b6786ba5c69962e6db678df0624009a671f59521c4e9c07845ebeaf8c90c0000498ec5f9c5e2b140ca4a51bb813ef8a303dc8050bceba372dfbd888fca9f000841d397ca44c29b44c7cd4562cce48e1ae46d75462f93101e8022f7e502b500aa737294f2c1a433cf8479fc3504de1bbfbe49b98b460462dd53661843c90a000e176707598b4a0b7cada5348e7a6fdcb9b6f05ca877fec6da0d392d3611b3004bdfdbfe053dd23814a0b8b00a1c4a945d4e361a32b543b2b4ab5047cca58700d5442a5a982ad1c80cbd266b9fb188924424f43e121015265a6114e844777a001c438d75dd63ffd9046c597945f6b8d0f530c45cb929cd8b1887fb129c0cac00e572cb5398d930b8dff00892e68afc554660913e7c5d792b887ef35b71f0040038ce4d1031383376378450bd2581116ab81b02c8857909c37828a602dba8170046a10bbdcbbcc6ba07e73142d0465ee176d27bf94ae2b88d0f8954e31e2a69004c960dda67988fa228262ae4ac0ec3323744e36fa1205a3b79c31a24cf434a00df2052507b7b1480f0c1658910242bcc5ad4308f04e2a4f30b8b7b2c80b04d0079c548714da07619d5f4976e064b582ae6878ec08ac0e109d09f76faacd1a200ec94c73efb9f31557008799b076923506c65cd18805e4abf5040fcc706a0b4002734bcca157e613eb282ca17a2176ee75471adc258ec61187214891be05fed00ae0af26e01115aa392f2ff2b9312a5f5e700a76194ebe11ff75a7705e2a2ba00c62a34ae6c8ac2b6ec44723da687cd101b096ac4aba761cb003c0874f72a3d00057a0133803b705d16738ceadc0e02b48806b94979360e01fa272db8a7456200468b7819aa619ca499013a37e2ec1e781f79cbf68214df01ba1da7f69822e300b721f590fd6d0779c8cea4236202cb02748b84bb5ee1fc91e0f8b76fe81fc80063b9b469ecca1e8402a04c106aaed06e3d2bb76dc74a94a361c34c0664aad5003d4dfbacbf3e88fbb59e8d844c359acf402c4826e1cd5776a019288d431cb1003f7e098309206aa4d68cfbe1de9a91360de0cf12d2de01a3a61506c05162230073c8abe508c840dc3485758419fa19622abf8d1c397002a7422892d9ef835e0060d45ede4313d3b732af0ea2e5ffbdb58cd0a096cee1b532e653583fc6429e00509d03a8e0489d588e8c65e81f3e497ad373e8bc7e210703d5e1577bd1e33b00168392ce7dec3c933ace1d051bab0226f06a47225b27889e3ab97d8b1e9dc00043fb412156c5700810c285f3def12f8c6a6d4eb173fb6482f8c6ccefa21bb800ffee34a06347a080c0ac3bd4ad85bcb8179e49f80cda5031534446008db3b20041c3f00e8c80f408791a2c3674d713de1d9f6912dd638d06a23b58b44f3720003788e85b47e71e074f079e0d1a07fc96ca0ac80f441179a0b148e0ef807ae100e2191e60e2c071cc5fe6ae409c293539eb17b3394a530f502d04bba7c11f350069422de54ec2af538091f25efd25105c1f3800b8de3b7f4210e2b9656bc1b8004f2437b01c854eb7406bfed0055f20c3362cd8fc8e25b40acb3cbc44c7ae8900f0838066fc68fe196d088878205213e52a043b8a423af2ea13aa1d9abc9158005c76f105d927dda5d2293c5004a662776f1d16298fe7e91b94d12b8d0328a30012b876d074c7ea981f9316a2693d0a12b2294b26be3995b57ce07aa8c444a200e16ec8f31ec874c8f642a7b10b0d888a877e8c8087ef30dd1900cb6317398700467ac6c1e7848dbdae63f0f5080f004150bca20e0a0060ae1d5c0e1ff7901800ba76da057739f448e00db4c8da68001d548003a391838dc647a041775f10620022ed5beb403a7a410f29d8b5fc4455ac0201028e24950987644327be2a327c00811408dca8c7ca503a2a1a6231e2d78000ba48914437792a60ba39c000c0d0003df37107e5ed1ce846acacdcd5c9a18eebc061dbb632a3c3351a3572bd2f0e00f0688d8513fa117bc88b6efa6bc8e17999379746e327abc26b28078157d0b000acb971599d4de32aef88e7b8cea3df05ce91548f1d3550211867e542482d02000f3029ebf1163977a26ac27527680cc06b61d10672e29f7cb9531521e379470002ee5bb53b215bdef81a5a6163c2e03b04c3903c836c7cb0c254da7837c69d00488cac41082e44b7bc907222eb7dc3d548a11503235d61df35926ae4e5955600e7569ac0f5662a6bfd82f550a91941838cd4da14022bab92d363aa473b1e3800b2f09627b60e17189120e00df5bc83994e0243d80cd8eea2d6b04b2d748d390097347f80f284c018be1a965a41caf037a5409c837796e285267402f6f3ce5400b4ef1b3608b4afefd34a732d1931b5fe84c0105a373b5f807a8aed7843c54c00a2bcd8bbeead91d8277bc547e50e061a2a3201443905ec0b098642b91cf4a500592f4c138bdc5342e280a0fe61235d00472adf829b9da1ed7fe30a9151a0140073a1a16d3228f50d33031f15a00dd3616169bf6387e28d7e854f171b9994ea00fb0e9c2f82913b961d1bbd0db4f002f8c15ca0840284d05c8403bceb12830800b6fb9408ddee7f59dd971895be012ac4e7435ca2e1eda780b8500000000081003df550a32d4a9d42010d0573aaaaaaaacb71bf2c8cae522ea5fa455571a7410006000a2278f4014803c8c001a0ca39408a58780a8016b231e24c952f3fac2d006d3492b9f70c29bc8e0e1ba99865a03ea390d2aee9fccbd0806935304e576c0039d5586fec0c7c1e1c701f2e4ff08b65f9019083015e2984032d800a830f420040946d422522e50a76fcdb3e71913736da6d43e6609902b901241b9eba3e02001bea11a48206e0c0020100271026f204566bf83399e4f750728d1ef57008ae00dda00e710401814a23b053fd0f102aeeda0459215c2444799c7083104ec3a000173dee518b8a1652daff709a54eedf63beef8a9f3b0cc994d9ca67870a47c000dea054258085744daa97ad3ae570d5505b506fdd44cb11f917ec00ed0e8abb000c4c3b02f902b78308275082458f84023cca3184032cdebd830b71b0944aaf002844c5420bf979d5bc2cf883ef02db07e59080b90245b7a162512001455448000028df52dae80196a5dd35a000000020000001df432c7528302d35445f059e002d28acb0d806e15f307e58eca2d4eb4e4323af9c78423f226f19ed54949c4600cb14826683161e84f18c22622affda367c78369b9b1b28dfe2e37dfb5b9fc900723c4980eea561ca1dccce669bf637beab27d5eedbf2c6b59a5f9dae058b06008fe7860f19892304245660632046ebb6a3393a60f37c968bcc39c6f083debb002ae0e978d898fc1a6c1d7c7a124321043f0448d8460988de8a3ff3a7f8737d00779d4d4e0ab1e530cd10aeeaf9baa01e3c75773d4e23e5fde339f4165ae15f0050f9db731c00033137343635333934363135373523302e382e302364617461002d7061636b616765732d77726170706572000029000002ed57011e0000c00100a0ee77bfe45d895a37b76779cfda6771d788614419b5c94056c84dff80129b002c92a075e9468d52c754f0478dccc1526d1acc1fb4befc6ecbfe49b5d6c66e003af07a8b02f901d60bd207521f94c9c35e593842c3d5e71304b2291e20458300226e2a80b90164412658e50112020027004f00cd00e500e900ed00f10101010011530406efdbff2a14a7c8e15944d1f4a48f9f95f663a4005300903c3c00d10000d1feec4e40c170ef3736dc9a29389fb8cd7ed1240900d100d1000500050100a0ea8b20534b9fe592e554601303067acbbb06498802db36e00c06f0ca5aed0089c9a00ec9107212206c25c64413213cffd1eb3533302d74ce8871cf8125370020b7d5721af9f0f2ac6e96c337ba166fe08ce701f3c828f855a90d32f45810007b679e372112cea035a6f00f9484bce382246ae0d7edee08e5599f38dcf86800f591739339e5e7dff802f902368308275083026f31839896808411e1a300830005aeec944f59fa2576d43b4d09a5fcadc23f0a58879aa71d80b901c4051d2200a480e8914b0501560b2c09f8dd5d4911a480b34540c8742820e488979f47000000000241a300a72cd494ab6f99c584e47041091499325eedf96b001003f178003f3377b3a70465c193ef33942c0803121ba0000002f55bec9cafdbe8730f09006aa55dad6d22d44099df96cfa2a369ec67a93c324a35e693fbeea11c05940000100100c080a0bf6687ef87f640de3ffcebf85e857ecd2ce5b895ee929d0a6200c22ff0f43c6491a016e550ccdcdd493f919669c0e169f3a7875cbe5a85be1b0094912c319d7dbaf3e1f9046e82d58f83f02ebf8303ccef94eca4934054454100957ec64b7635418d215961682680b9040449a1a4fb4000006002ec4c0e034a0050fb681a13c5a00101a00202004114dd22ac10581a090ea7445867536eb7de00b3862716a5c7f153c595766637269a47f846cf07de260eceb9fc1214fc1bc000b791bdb7af636c4a47285eced371b65b0041ba294df0f1e3bc95d5f9f6dfd4004b585f2273ab48719e0b879d552c1b3d4d95476a1343947f1e99860136d1b600633d908eb2162d53008c01b5f443ad449d222bb34139d765c08c1bad43eb1d00e73677f4711ac57e2b010adf36d9561ba30b414a8d863e03d04ffffbd2b8c100a36fc878774f233a6f433568b5ecb70c88b2eecfc78a16461ab29e4d029b6a0056f7baebf10d338ac1cc9ea8e2571f5e673bbe07557a34084807e2ba2eb9d1006187e36595ea6ea60ce04c4ba4e87bc162d08e8ab96a10a75d0165e17b36bf00b31a3d991b315cf60ccdf033f23ea6d4c81a8c92bd12b5b6e53f53451ae68600aae5b344eea7143c4c37b3172c166263a8c8687b6e670513c4d9ed3f690063006e21233b5fc187439ae62df1b8ae7aa5a4b89ddfded53f29a54169f9a03b9e002c849bedf2f9512c81b9ed6472970151e8b83ae72829cda865d49787d7bef900036f821e5684025ac32e8306a02a94a81f6c97fd913bc4f414a6e757291fe6001c6d083380b90304b61d27f680e38291e06339d10aab483c65695d004dbd5c0069000002642cc4081e0401dfce00681a17af20530000000487c8c2bed89f200078ea8e533c834049de625e05f0b4deffe9db5f6e000283104ec4a0165a31f0003cff45b80cce57648777a2b5109da9973d28e7850c8963aeb74dfb15a00e8d0054e5171deb37435a6828376d94f618425d4c9a75c8284eeb5c521e27baeff900010d8210aa83fedaf8830505f694fb92b99eb2938049ab6b64d3d6a18f0561008722e480b8a46c05cd2b0300003c1bca5a656e69edcd0d4e36bebb3fcdaca6000cf184227cdc084fb9e54a5d9f88e3d4d4776b66e2d6000f32a0e134245a47005ded2d3bd79717e52c315be9a8a67d01b12abb5bcb50c2aa6f1b5444704664000010b33933000f387c03bf1d57f79a204fce5e3e250296935d6061bec62a0f00a8ddfc93c92f46c8fc4c6b36a9124649a12d194209bd53a041c1a6f107ecba005cb672b709119d0a008ccd0b9668a7de75a69fb63a1466e65d02f901488308002750096483fc31798305246694bfe03c9e20a9fc0b37de01a172f20700493500e0b180b8de83bd37f900010344295207090450bbcbb99d051eb80001716c5500c2d9d9fc1e38465e9301db167ba1483ab9000000011fb54947427f0500fdce00a49bf1747bb7334370c50000000104010205000601000102000206000003040001000401000201ff4637d496c7c56e670608bd40e0d1f257518ac42000c08000a0773157f730c8100d03124d2992494143eb811046cdc935cc79447a0235d000f7c2a034b4ba8b2a00838862244a5083fbb6cc1336f0abb98b326290869ef0003d7d9240f8d682175d0487ce94f6687cd56aec1a0faaba12feb6781398ab4700fb2180b86c681a13d1021ffb706353040a02ecf2073301118a84e97620829a006a9666d41acc9e91edf32bd633010002ecf207f9a7aa7411b49f360d024e9b006f6bef8b563f97bf7dcb4ff1737921f20a7a0d53a0456b9a240c2a7a1bc9d4001db139f68ed206e24122a28d7135b67564e988724a9802f9013203b90d80840001383c43830927c094802b65b5d9016621e66003aed0b16615093f328b80b800c4a005970279f91c13c79a305d4a407de2e96a447c3052f1e18253e0da810f0007da1eace7f044afd081f8583dbe9cd3681a147cc001a0ee6618346bf14f86006876634405b976b55b34afae5e0920a040608f7c9efe0d56a05eeb009acb80006683545be83b61fe58632300f23af7d70a48b9a815084702a1f6f9010e820f00ca840171af368307b54a0f4240047574bc9bac08f22df6b1542b9a85686e82005d58d5010f40470f42400f41d70f424004010f3e21080a62fe7f9a047246e10078b09537e2df910ecde8c9b30da7dea334c01884b88f1148ccad43fd89ced500f3a342077da05fdf3bbcc5cadd5694ef155140bfae3db0b21760f9f292956a00fa6d1af231675502f9019383015e2a038bd06edf72620001c001a01b7d45d3006438b173e412f6ae0a845a706e0b451e8f009b3ab6815e82f213af33a068ad0087986ead680c1606c8476891426e20929bc212f6b4de3f6b47b475d3e44b0200f8bc155c8407ca0579841f2815e38309d31c947b7d925f616598b3dca14f6e00760168dc6478301e87f99e7f6d230480b844b9d2d32c49585549eaec3708000001e19c01000008090000064828b530df010000019bacb21772d7476f7f38de00bfcd459a8489217fb9f3188c954c422e55635789673b1796c001a00adc1859001025f71eb865935b1267930b843097efec2bff62373628484f1d2f7da01a420087c561e84078ca2f19b71ee0a93e1b97d2b3d3751ae15925b0699de31a500200f8cad2eb200b20941c40be10077f686fdfd9e6373c5611d02061ab3480b85d0012eec66048000007526ef62a8cb76edaca00020b806efdbff2a14a7c8e1594004d1f4a48f9f95f663a4e0da810f07da1eace7f044afd081f8583dbe9cd310e0001159afe0ce6793dd41ca347bd5c022bb768039c40c001a018475e7a1008c400a71d897fe3bd5e3492436af89c73a8430f5c76794c51830215a0733d93217900aaa8b8c0e0300588c69b745b86f60388be45cdd39b2f7bde80825102f901d2000f168a9d8f086d9a820660b915f63b23fac57da32f1e9a5259c7bc7f780a8a00e11f9b5a6038642066cffce4adfab4cca007a1fd2689b4f7f8847c738bd15d00c1b69e60dba1ab51fda9d54b67e3dc63cb253a4280a09b64468bb0e4abccbf000a7b6118c975ab679798762973320e0f985d00a765ba07a05780abd0b413ca009796b050dd8f0d736fe241d3f6bcebae27df7cd67e337a47110ca34329d15700edf47e7389e87e9668497aed1c03d10207d3f53a1b1cf4b841ed32bc34a0330018b3b9474f63d98f5ef6f156333d534f0be946851cdbe5fe8adde9e4ed3eab000bd38084013b5b9601a02ad47d268252c947e559809b9ac5e4fc10b24df6c2007983ffc4a61b53227d7e0ba07bc0ae41cd80ac8ddd6b88ab3351ebab78c3d70032bb046a51559be9c0f269dc4af9022f82f088844096c79a830789c29411110011568011ba61bb637fc8fa09d5e803a8614080b901c437523745060012e4980014462c1e636c5995972330057693843f140c0de170c26640df5ef840b1b76e003c2710271827272e26f7681a157b17a23eebbb62bfa5b442c37f042ced300100e37d94a011c6b60e74b6a76efa5862a028a8dfe05c3460a638d398acdb19b500c288b8ed565644d0b6d2f2496ec2feb26b02f9031316834cc017840358b206008be1acf996aa3cd34c03451ebd99fdf94ab826b780b902a413572d0b6905c5009be1a7ea32d1f257e302401ec9a1401c52982150019882f800559b44f04a2400c026554eafc34640b216c080a0c162b82d99399ff79a4c7ae3ad71fd93340a005c87e1af1177c7b5a31c859f97efa037e0923e32f4e3e2aaff50279af634930074404f7ca620dedb38c14b0f2e0338f9f8d68216ebc43cd20221a0c5110497006be333010004976be3c4a0062e025a41bb3f186c4a8915fb3d2e729d4feb0800a0eae3bc3206f2e4798b9496a0381d947c6fe1771f0cfa7d050fa7b12f68d7001c7a135eb7a606571e347526633c02f90131025556017fb746940b2b984ccd00fe2276c819bcebe0499bf39f28f1646259c257e722e0ec34e7e4dd657b659c0098fd0ab46a657710a49aafa7a016fd77f3f1bb6431de8c6b5f35e68918ba7f00316bf640e133665ab0c5b8e769dd2b000000000b7c093701c080a01ee13eb0001675b38450d86af6fa5ee9fe5bbded8638084e2181c879910c06f0bca02515008682c1a307c5eab856440cf7b8c342f14b9b6a95c981812c44fa5ef9de232c0000000000129768c4016a6e9f81c3a044718738d39d9e0d06281264510dbff5008f43b65d96aa86a30dd7a03db341779fc346971b41fd1db911442caeeecce600637b6199c2e0f8b18933e929f8c782c4f50ae9b59c51cb22333b80bc3a7fba0097da293293c385d12f7abe3da65d26f045092c11d902a01f1a7f69268188ee009e6c7e21a75ac943722d58cdb212cdf9e6def65777fbc6a4a18402855f98840003bd9bdb8308f9b70549a34801500303c080a00b370cc6dfc74f8b59f2eb6d00b8cb88e7c109ca5b69e83b77ed2d3419cc3d9351a0561b1cefcee82b72b665000318910d1d9b9a787d0c040dc178dbd624b497f483fc1afa3391e544d220ee000fd6ba7e9ea32dff3a9813f8fef9238db7c38b8ae3736fb914a04a2a2f968400e580b87b12fa57a1aa012832132c310de04baac86481866738c86bdb99097300e0b94813df550a32d4a9d42010d057386429ad2328ed9180a0044db72f3bd6008d10b80cf7a4caf89b719d3e70541cd0aea691d13bb2d89fa62aa01b9f7898003372e2e8957e5b36887a2c7b9522d3a9d68ce2ddff42f7ddf6c0f06702f8ce00c5de12f2e53bc080a05c21382e59696762b906dae509146f026df5ee9345bb00b4668e7a16c4916d58c7a00b3ad067587251e28b853139017feb6f6758b22a009479dedd7254b95ae035bedb02f90316026f320fbdac02a44c0240d196a50000f0435ae971c6db23f2a315930d867595a4cc34899f470ca01cb8d6b808f33f00eccfb62303e5eaac36d60674a23cd11f4c3f9e98dd26bca022cd61af7c9ac00092721c898744d23548caf293dad42f34b8172c8a8c08d00bd5901c9414523400c9c1f1583e710bdc2926d6e97e4523ef930198181c5101c880fdef84fb235d00564c249f894b631cb234ff780090a0f3a867d2b1c6cf7eae01596e1a35381d00c97e01dfefc8944827b061263458dad14a283d0ca8fc764b0adff2de19cc6900a4a0f17f616e2d9e147822820f1c2c14ad8084ee5a343a1446e7fe7adca144004affb4ce5f7e9b5989451a141cdfaa254e684abbf4dc4e3049bc081b0116b4001ed85fd47128815dfffaf236146305c4c3370f2ea1dcd7672ed76ea3f8a16f00d4bc2465834a27546ba74b2cea07d833df2239aa8c9dfae22100069db486d7006ccdaa10f2e0086daa09a8d95f7e274af60fa7b95d95a49a76a3973d95e5860055cccfee5e892291800ec3eb8f15dff4bc3ab81c4638f23eb11e90a76f027d003400badafc933c6b9580dbcab3371bc80c94e2a313eab0e5d8826e0add7bf000b2275c4846492d746d5674704c06efdc33136b8fcdac2920a119c57b08bdfc00b31e38de01c4a0e2978d9ac54fa334a05a798cc23a2db2740f5ea1a0d3cde30016e0340b5b0df07ba03f8debe324a266e67c249ea60db8b1a63d9da3e04dda00f02e6f19854d53f1181df8ea82170f0587c880681a13d30022825bb5716057000bb153edd0ea1775ec2b2ac9b65f1ab61b0a04f5b93f4c0101000000013301000004f5b93fae5aa896bb93f4c7c5660b7fc894b3892255d015bbf05fd75d91001f72f5d058050b6d2ce6634521c5b4917602741215a380b89d8ea0501da95f00a3e189ac1edba04c7791a74435278e4f6223d356735d6819ad27ae622d02b8009eba3e9658e00064fd556104a261a1f37432123186eacae6004a00f5fb582c0062d7e02ffd95dda1a06fd657dd795a70b96462f251370d704d4f80d8c9e2ca00e40580a24fa10eb17f15f8d682171804de0d6c0220dd24530a03d075bc3301000003d075bcc4a03aaecfa2d7eceeb5759131b4620719277b2f9b8e293296bc002a81ac404d5c6429a0651089f574cd4cfa515c4f83e190fd745f10b9f6e75200e47b0c7e6bbd7973c0232e01c4a005ef7b60986e8b5c71e93448d9240fe8a1004019e3e91ffcc1e7ddc02c66313bc5a0359d8feb43f54314ba74110466f19700eb1883decaa5835e76ec177f73826fb3cec8b30b65b375598f921d250b3b1b00dc74b499fbdab00b797fa89d13b22645a3747db46e6870bea019d682a84044003a989573de8b7ea902731243200406f64278dfc757682918cf94f9022f82da00dd8410bac0d883063fdb082866f56ae96f21260b39c3a5cec18ab5c2952bb40013cf464d584700005ef5d0e75d0e10270b0000271f000c7ef4a0277abba89d0043a01521a12f7fc40db3ca493e636b9aa0e57edf0b21505fc7a0212639e5530097252050da5cab3c23204bb9232acec4a07ae94427421e9a2441be02f903140083082750168401722ba584047e1d9449d5834879e76a9f499379765614f25500c001a06ad7d77ef8b081283a3d11af6032bf1bb31f794fca97ac85c0c64f5e00e917e8a8a04e877525da22b00ae881e6cf68b0cc0e6a5a0e8c0c03ad066b3800a5f323c29c2f02f901455280824e20840120356c835b97a4948e68f5544566005851ba7c14aaca4abe5317cb78d080b8d6000000b400d0000000a102aaae9900091fbb28d400029052821653c1c752483b0f3f520f4240000000011d25af2b000992bf227b350f404c10695fd2a119abc36cc001a071dfc8028fdb7f8e190c0068731f466914f15615840c38e184e07989e6d6ba4bbaa0557c00b7cececa1900bb9791631bd925ab8b315341c2c4c0f701232ea1a0ad683502f9018d819b4600b9011d000000b40117000000a203fc30937f5cde93df8d48acaf7e6f5d8d8a0031f63653040102c75319c4d0f69380249384c4cf7ad96b69dfc34410020000000001a40f387c3203437f94c080a0c7e471413d062f895fc457c01ba943701200ba3b40c7216fc022aaa8b0244d0d1fa07bbb2ee3d4dd3b9a41d55f14d0b9e000927127a2d2d5a8740ae02ff46b1b39fb7f02f8e9093c00648401e05d1a82c000b8940280b84439455d3a7091d0e41d982c39f838f703e1a0fd0fa31df33b7400343804002bd42904a8c44a2dcbd3183a6da4a6ea591f04e318019f5e14d15b009be288f48133d5ac5eb6f0295f8c904163a8887aa9dad9dca3ae8ba074c11800c0160e4b8205224c826e79ea4c770a3cbbc514b406e0836c34e83f818bf8ac002c8401312d0083012e9794f301805be1df81102c957f6d4ce29d2b8c056b2a0080b844095ea7e79ca44408dae5a57ea2a9594532f1e84d2edaa4ff6a2f1ec500abbca255c3d5ac49d8d62ada306fdf05a97d36d3c74e0ffea8796e14a06e4a0009279b268abc1d66a7e4bcd57113f0644f0a798fe672981fe1e7f737105533000bfd094d1b690000001bed550000000025c9273a1e2c100010c90010c001a000cebe4d3943020f7053f3643c706dc798aad405619791b69e686457d288592600fba027389255df104f70264cf8b1004e29c5317f9e50f6a7fff50bbfa1ae450062d775f8af83013bdb84013d0790830eecec9451b4b067dcb462c4f3bf1fba009804c3422534289280b84405021d675222304d1c09370a3922f46b63d6024e00a7680400010340ae36ed29a65e00db8bf8350d2c6a7492d3ad45b2ec703e5f000b584414b4a3f44459a0206065e2c0a2b474f594948fb05f413eb2d6077e8b00a5cc60a1c082e77f06b8f002f894abf584016835a083605fe094029708473a005146f62a59f1109657b5a66741130680a4b264ae1915aee6c91177800092000006c001a06e3a1de85ff11f2bb65942b4a0b17902162c0d764d4dc87a4a4b28009923c405dda02e4d30a6ff69276c630a91fc5b8fddce9ac46db13fd2153beb00248854c0c51c84d935840358cb243109cd8cfb11931974f916f68f790661e2009d023a48600034d80047994fed8cf6de00474741f2e026eec001a06af17e6d00ffc5b488695d1c61ea1f8807900631c09070365a7a87a49f8f9dbbd4a03a3900d5867593b5a55b7b8dcec9247eee4d92b90211fc493cdf4d5184196159a423006483fc40ab8304bc0d94c2b638cb5042c1b3c5d5c969361fb5056984058387004367cbb7ed7dfab901c4c7c7f5b3645c6b632dfa9806ab965887d77e2440df000017d3c4d8f7cfbc4475e743036f4c8a42ada857ad76e001c001a0a4649d6b008464e2b5f51a9f1a84da5865cf85dd60a43d8cddb8803072296f5834a057ee002e410c833f8150ed26de21d092cfd1df6259c39ab39c64cb361c7c5a659df900022f82e5398409915c9883063fd253060afece30ae36409145b34e6b0ab5dc00990e79cbe6e0b6b3a76400005f0c33f28333e4812510cb98c9de849cde2756004098ea5779e1a2f2c02544ed5f4a4f4065765af861a02e3d90486419558d590071ca7eedd54de555cc29de80d961fc051835ea496bb4bff8d682178505d27900d80221a0948e04976a0c6a0cc0f05d3ce737ce5314b65b46a4c6a75397c2f80018fbb5d1c38afe24984d33979da07f972a1152de4da8c12d682f7fb8205f0100fbfaada6d657c8544d19b24281d86002f8b183082750448204e784014434a400830602949411fcfe756c05ad438e312a7fd934381537d3cffe44aa20c001a0001f7d94a70a0568e9bc602448eef30441078473e44b138c6a8e9bd40eb643840054a0494321f6cb5d43c67cc9a158fa9c5b6c4167149bb890ebed2ae0733b6f00d3013a83026f340c03604e16110000001bee70be80a00ecfa29d9a9dbd62670060b3fd2029c168d21b1016b49756d36aa26ca73809cf0fa0532f80c270079e0011180bf393ce3be5061fa269e0a78d7b84fc8116365449fae9f8de821308840001a749bf8305f25574020f42400400010f404705922b48168a60ac42855d5c002eb47469985f2c5bccb6494e594de60821aeee884bed6045b122ac0393757200a21da058e64a7e108800ba1097c34d948c8f5c91f3a5ff6fd805de94294454006b4f6cb702f90ba845908377f8cc0b370670756653544f4e4500007273007700657374756e694554485f46554e44414d454e54414c2ab7368d120196a5dd5c00b001004e2ab70baa28fa75e0ce79515560154fed3184aa6b22c8ba8f93cf5900be5c0b104d379bd37dc727ebd76d2871678509e975a7f7aca8b36ea269b87d004d03d0251c51f1f3fae9d8c07aaa03018aa58456eed4556257a124619c4ad200954fec1eab5723e9cf8cb55c290800d29cefa29619d13f511944246f23e364007da08d2ebee913b665b9cba71c98e5b4c19af28e40801fbe751a5388c0b3af0095984c2384001b7f9a9a3d3000ea155a5db0d4611c2d917e432785e7c5f6cc0086c1426cee7270b8c0e7d94d501c9bd2005d10555ad3a3186ca9614c590a7200bab272d108f31b59350200152e6adf809b0f960c89bfd7b1db047ebf4f0b1100b513c902dc8e06a401db085651c5f272779ecd9370e003a762e27d6454b9b900afec5d56d6902066dd1390afc0e98b34f6ba6c65e213039af19d2b9ca65b87004bec94775324d748f886be0d9a279cbd6d18930c95e31b01b3015cece21ccd00018a663bb58a93af76de5e88a55f4c109d29e4b9e814733fd159a94aac99560070c489f50c75e4b92f11a043d35bd3d53d6fe9f87bf66542e9b60e431b85cf0002d80b03f79e283b2d913173736acd3ac2e4b6b0f653050d7d66fd13104c71000de7a1198a58f8d88b77e6dbc31b990e6cb53000b63ca9eaa301d87d0d37160090e874711ca340738110c0e8471a607e8bfe0a37ec66ab6ad94343b9ef00f600bb95fab6cc8223fd4b5d90e84daf2aebf0256960f4f27b7db7e4dd9e7c68000079938c23799e446f25881b935d9d2bf1b884cdfcb846e3b8e5ff24be31ce0000d14cc0b7ed3be5b6b63f2720aa2b084209d7d48d739890b9bedcbdc258bec70018b51580b598e2f355d9dc68852ca4d51c2b899d5367545214a2dc51091699009f1fd8716c33d9929fdc03bbff6acb93f66a835325d62158d5ea45c22f007900814d66134f21dcd91e604311fee63fd6ade1a53d101d93161b188dc9a66c9200e255d022a2f42aa206b9c95db39d687399fd0ab53c4eb5a4b1c235d8e6e64500cef71cc626cd2a102c7a669e72e6fbec292fe40a0f3a5876afefe81c88c87b0087c4bcce8dc0c3b7966433d410db82f02d7f87d01ff0810f51a5d52b0666d100f67d4d870cc9d809ad0a1628256fb52a14d5344a72e37f2539f4648b4b60dc00b3d3531c3105a8cf5ac9b16eff44a1607620133c1851e7273d3a3aef9c8a3100c20e8a9fdb88b82792285d811902c801aa216a3112a5ba643c1ca72fb4983600514b010f59c5f8f61d85771b765cb96ecc39364bc89f10b6ca1c8e7c783aaa00b5f86475bd7699af9c94c4f1104f063f3300c858cdd9a97d1fa5aba65bd19b00835c0ee8db04d39a2636444150b4e5c3ad640c48602bf510112581fa2805ef00b2025e69ebc380b15c0188baccb6730923a06ed2172bb34b06942238412f430009c1981e906b9deec3e869e57c27ecedfc11082c00151c2cbdd094470b129c0023fb9654cabd900126ce446e2c520ac18a82b08e2e29aa3efe8089595f28d9005fdbac7b687c1f3ba461cddea1d6cbda82ccfb14971cfff1254621bd3f8e8600f08df6741248b5d288b88bd8a8933e45c6324be7afc4d1a091cadca4d75952006fd73bf116703f01e36ee8bd7090615b2eb6e40d71725140f6b897bc058e3900ccbcf5d9b3e86c61ebd463c7e67d9536470973006ff262cea8657d9eb5ba8b00233973dfb442ee8466da3c5baef0a902faa40c20f8a48c69b9d363e536983d0058a5849aa6501c0012373332363280a0eda25f029a44176756d99067660f65008ce5a594121eefe157a9a4af0a1efe753ba0701688a01155c9534a00e411e6008e0b5b6106f085f3f8e3e6e1ec61604e274dd9350b80064f061700000000070033db004b00f6dcaa21faf67e1e37ccbfdc23e79849f780cb67b2c7e7f0b006003785db5653bdf0dae4873318d75ccf23c150fc10399ed9fca02b18f51a15270043120597eaf36ccf5481ca363523d08c09c064a72981902dfc53f9010e8210005a8401034f5d8306814ca40333c00e5e2cf3f25e7c586a1abdd8f636037a5700a0010f3688069a4dcb6553190f487c6a99958ef49c1afe9f2476b6cb870846005ae0d536ff64396db8b18098b538997ff97679a06e10d6cc00d599406c032900cfd56dcebc7971176f4524506c0b383bdeb3a785d92f01000000000203817d000104a7199cec178105bcc4cff05f395c9b87706f19f883104ec4a062bc6f890086218ac3844e417eb11213aaec63784d106e9ac8ad7a1f79134c2a41a00df100b41d1d01fba2fedbd174cc1055e0af9b894e138a2f2868e31eb503216e690200f873830827502a018401443e96825238942fc617e933a52713247ce25730f600695920b3befe87038637ff9e000083625240c080a026c102a469a973c1385700c7c9d9819e43cf5a8da49f79eb2a14a8ba75ae814f1da06d3ee2259a43c3ac002c734501cb7aad0983e714fcb26d36a73a5ac46160639bf3f8ac01843b9aca00008303345094d29687c813d741e2f938f4ac377128810e217b1b80b844a905009cbbf89d7b9c864f589bbf53a82105107622b35eaa40131f2d2b9b7b2a6e0c007fde8c057172aed5d77adabf3f01ccd535d131f9d7cd1212141424cf2dee8800e6a020d48e26c00e8e3d12eff3d60c55031d6d739918440ceda1ca60afd5a800756138f902af82483584028b12378304eda49446a8d62bc20086653f31802500d19668965345116980b9024401a45ae401dc681a1500e404e45aaf530401f40072f86cae509e03628e76ccb4d778ca1196d87aa995fdde2b373687af255dcd00a97db00f90b9e7429e670ea040e7da3ea4309d6da6f921ecfd6f4eb5e4264e00057f08bd894cd7ed3120f8eb9b02f8ea017a0c77bd12d61a4e602c80a05e3e00ade7b57ab3aa960b2e4d0ced1ab064a2b8b6d4b83aa176627a871c27b900a000413714e68cd55f2965657d95ecbd77720cbf56170828afe58241306ca9ecf20001304f13ddda02fdc675a000c001a056ba3ee91b512308096e3d4e04f3f7140075c7594b54500693dfc04aefea520f4ea04c91ffbc9b04c6f837a0daa7a51600f601dc9b3d75055ddd4bdbbf248e9b56fbec0255574f1300ad19e400712040003cf8ef4cce14a0ce8ad6e5dccc8bc001a0ff09a236126c23b933b80d7c87190096abdb2a2b686c2992c66287fe459ad3218aa0606a0b60b3272624cc0ac84b00c91fd8ed8e93a13576aeffbbd14b4f69886d08b8174f1200e6ec0343f5f43c00611b75818b8042e63600e57f0c21d5d445dc5d4211cc59238967643f8d23b400f82800fee6a0a0635044c8fe33e546e4766d96e517c01c41dc121036f405fc002d24b3a417ee5d2df89c83013bdc84013d177b830842b2b268ece25400834e00058682bf3424463da5d8e8f6f20a02050305ab87dc2c8d7da79da711a9f476008bd9640fcaba5e60ed5428517a6440b2d487d9d6167bbea0391ce9afdda1fa007b4b519da9c9bb8d3a63d8976ce9ad78d162598a5bf17f6b553b130f13e073007182e80b3ca9844143e780445648d1c9b8978be40e4b9d4f6b10f733a026df00a77c4d5ff680097e2ea7b69e35f9fefc27f81a1a153b3b330fd6c4246d3ff800cc2d05dd4c9480b86480500dffff000000007c28b6580b37321b98783983b20066f86406acb8470797a04f0f894370becdec3d836b23ab257b8a23d56426380046c6215ce9c6e4eea01607e706e3e583173838b3b7b8c893b7609ea5050c9d00fa64e0dec935d7115a3b3612bd4451054f0725ce3b2cc900fb01079841fba70054c5b1bfd554ca1828f99351840010029da72cbd2f262a898b3e47e56ebb38005d8fb16031000006ca77eb3fefe3725dc33bccb54edefc3d9f764f9757cc0d009ae1ecba23b77f77627714d449e93966bde3fcb2e077f0423ed62c6641a01b002d4041130d4db55b09b8909e07f5e041bd52a987dfb5262adbc904f4deb201008256d8835b9b8ca102efd69765a2f8df9797b13a046b7f080ad40cd7a25b2500548b4c98b0c7d3d27dca5d5ca743d68b7f0f41dc0101919872c080a05e234500edf7b5007b64f2cdba63796c2b8cd859230b09fbf4066b4f1c2564df2ca007007e7dcb1fe2c2dc31b9f064e911166acba64bbce18f08e31c82f9474d3c45c30002f9025525ad83601416840d233e5a830f4f3994f4e147db314947fc1275a800cbb6cde48c510cd8cf80b901e4ff024c7b1aeae5c998c1b044c2e7595899dd00a628897bd5ae7b91d8c0cd58d9b3f829babae00101cf66ca8bad65d7dd8df2004346aec4d5db645c29237ca0b75e67e33c0014325b739a8d019c4fe445f0600004a8170ba43b7400b9b0c080a0ac268540db0490df8497acacf7989ff3070e00c7c98f1ed9c2223da76b9dcf25e4a07ed5ca2c7bc55694a9292b64863fd6fa00efb83c252291d40eb34d6e183103b3cf6884028b65108304edae07919c718f00a7fb900449b9ddd74497ae49ee2080e19f4000c8bb155b16b93e5405fc4e73005d6f7e3285a9f3aea02dc35f33de97aa59fc7d38529e6831cbd2baffb209c6001dad76ced077b605bafe02f904159106021980858307a12094c66a96e320d300f61807ed656df80696fc0c09ce2880b903a4c98075390260000301c00a8256000b554f0fdf0f0ea22252a27a4e0001e589030002090304070608010500600a0028cac8cfa828caeb7ecb546e80cdd5ce4cc6f055ff9bac8a90749238f1ca2e0073a26a14e8666a7a426c8bcc69c778cc0b586d1b94e445ac8ecdb9a8d894b200d08e6a5d937eb756956d9c0abe6884273910b54130ad454dc8ff5302bb0c52001720c92bbfb476aab3a2ac091079eb0e6655cfb3e6844b898c4808f810c75100461f6c849afd58881bdbaabe63065c12b52760eff55fa3047e4d1c0b2929e5003e49dfd52162b141b5d77cefbe88ac2857a534039266154e0425d30cd575a2008e08119182484f82c74da2508d0c0e1b41ddf61e1fc60ef2ae262ab4478f730049f243064c423144935465f363c20b02dc7144dafb9a1183b4879d3a21be0b0085758f4741e7dc87d0ca3f1684f8eb8817f21aef859b55742b2e5b35c080a000425a30b60381edfad5541de0d51bcfe926de2aa91efff01fe8f239c9ec35490080a036f86689626695b0b4f9bacc5fa339ad81f4655698cbe688f1bb2ec6990043d0a85863d734f925743299bc384b2c12f08cedce1bae37bfc991c080a0d600d098b935e4c19b89502da96d782c32414111a54314b3ed83c7e964fb17127f00a067871dca32318698ca932a4376b650c5056534d77b3ec6520be15489f75f009cb13163d7b38cf79d0dd8b080a03563b842fb627bae7149186202ac17950a0066c9fa0fd3dfc21a277f4de2ed359ea06f9e8bdda6f7251626f3a2768bbd7a0032e9bf7c3214af035db5b82cade3d6188b3c63d7af92ad97476134880b9eef0001a024137d4a9ab2804e976f124b10dcceef0f7dcda2f8f9133cb4d485f46a007a383aa018b1d4adae11f05e4ead0338c09b5a7af281fa8e94d7424d1a249e0048e2c40d96dd84013d2c548307d4126562e87944e4d6ccf9839c662db32e6b0019f72cde19001f2462c0dac9b8a908bd55a1fd4e9f771f4bfff0938877eda700bbb2f1a197372ea0160129b10cf787bd95be9a408e05671abc01a07e9bddfc005d03e6caf0bd8e005702f903d683026f37164bb303645303606c06df0e242500e300b3dc50fa5e530a770fc24f6761b69845844da004000006ad0734d2830100a0d915bf95c7177b8791022e946cdcb77c4ef90805993775bd2d4957a93415001cc7a04a4693a55c29ee4165712fc4ca496df0d8bd0f427e7f01765d98354d007c12a4cf02f901126a83300a0b840210a3a583022c35943a6a724595184dda004be69db1ce726f2ac3d66b8780b8a42b4911e0fc88056f64d1fb927faeacae00bca161a629edb2a15992caabbd5e0db67b8645dfaa01c42dfdd5a2d4614db80078cbb3ef84e83473210505f5e100c080a04d39ddb93433eb170d4131578e7d00cf98361de5f2a0f22629eb91a5765c0f7ad3a017b09427be2bf86baa20bb7500e8e5a889f03d0ddb219ba475d12f5b004693a3daf903cf820145840a864c4000830381f1941231deb6f5749ef6ce6943a275a1d3e7486f4eae80b9036428cc00431640000200cb9c52f3bb263f93fc5eeb772abc7df217164154b07d54ff9900a8e38007b2e64600010189477006a2d354475254691403183906ebcda439c100a70b066163726f730f6a756d7065722e65786368616e6765833589fcd6edb600e08f4c7c32d4f71b54bda0291339bf71ee0de02eea14743af5681a131b0068001a37c3a05064a4b06cc86232cbc7f66d5014fbceb7ab8541fc1acfc898116d00e986e676b8a063ff63c9dad4a9c6c1253d65aafd63cc9446fc1490b3822227008a143f0ae66d74f86e5c8409a9daf582f61894ff7570b92c42e3a674f0bdfa0011fda771aa2cb53288017d352ba0ba2bf78012f7dcc20fb647953fa5d9801c00c38f900d20e430967da2ac15c86e6ec0465681a00b2ac2fee680f929a304ae00d067f116094c25cd28e5294a03db5d8415413a01f002f8ef45830493e0948000b884573ade81de0456fe36930df491a604ae4da4630c13cd791ec001a056b00082ba3a9a5088e6c34785de9fc139281a368355a216069d65814aa944c85ca00048f9f17aca5d77f23552ab8ff23a04b8bb5422f1fde92458b256601390997a004059d77633d5819ec43cd1487f3218ffa2b2731b811394c001a03c8ed36f95008e8bb2bbf404cac513780a660c5425a01ed4f75fd692a00f0bd417a02d0e2400c522b0e62c03f8be8174c75a2fd5e19e570a1064489c488e1b13a5a4333200000000060b361d01c080a09c4583592601036704654ee52d94f684944b9dccfe0062a02b06f02d55f505bae1a041100f8ca77e21004b3289b4c38f7a64caef2e00d30be9cdd5a4804cecfb59cd523dd396f566d251e280a0cb2884aa0c9fd88a000ffff895c167ac9a613f059f86ee448bcaa29e3ad026942ca0287201c2095f0094350d9863fc7ad1ab58a491c4dc901651978aba8140b123797f0299fe6e600016f480a08439207427bcd8351e495c0019d4aa92fd0ea7266de14cb4a115ac00ef45d213a6a03bfad056159b8baf3908c18c7d03e041f4ea84f03308bbc34000440b19da5e2f42c7eb63d70644ea1901a086c17951c86e8cc71fcf8e1c352900345b639425554942222dc979aa382727686da002e689f29283e4b6d051ef9600c6197e53d6806be3336e72b8161a71e8c6be9856ded57a8a4de3650cdceb6d00e03d5b2e73bcad7fa838f2ea1ee74b0580efcddfd77dce04d45e090ed80cf500fbe8b56bf3f85dcabe6efc58c300fb110835a04ff07bbda20aa1f9bbdabe34008f26f23fb2c991475773c2c4834e01dfac03a8e4f8c782d2ec035d4f4b313b0072aa4afc6bdb104253acd631e2a54f0131c7b5b6b6b86867e839b9a3b828a00051b797518b3a4a4af7b6c520b9119dc51c0e167fe841efb2fe3e1e361479e900fc83930b8df687c080a03313a664c2bf4d5f54ee9f6e33644e9a5e325348930076f57ed3f5ff4c69daabbaa0403f13e932d0b2806e28eea559b8ef60be4b9100a8b9f7c4d70776cf150f25a38c38528b54048731c301a0b7be69722ec38f1f009893add5c097e744f9d5036bd8076f548a3d0d9df60a48aca02b12b3f760a300fecf432f43fa2eaf610630abeda6a53e5fdc14415034c22fbd5702f901342500aefe08830282999480b8c4763d74c600400150f8c1bda236c753615cb5c65e005a3b676cea0ec601c001a0535a0350bdab70f575012c7b710d42d1be8a4fbe006cc9ccab18a7920bd44c2a62a07075d5b22fd49b5051520a1ed0db15b3454800bdf5f4d5fcf5999743aec7138bcf5ae273d33cec306839890afc2b34438ece0047b3154397c001a03b07258ab799632bce83d70145740c4a17ca6656e12c3300ed744993d29688a206a027af0c9729834307ac4050b6a52c136947fee3e78900050f052226b3a81efa533733ad4a505bd82f38001c06455779603c09e7a14800da84e4509831ff6f5203cc05c39230c6e815ff00bba02cb8129800bb9f2cbc00f0bbf7bc0f2c31a479de833656220208662bb915c12f8f3eac1e95ee79078000a8e3e40cb7704abac4777d6dbde550738763a6f375d813693c774b6c23a181003ba00b660ed9fd8c5254d2c7c049d938b0b03dbad482ebfdc82e0206a1544100234839c4f605585cc7285d94e5010da4e7e38aa57031b464bf01d4cabd23380059ddec2950acfb93e10ea03c3253b6a8e0e6ba6242af02c6e14fc76b16a05b0016b3881d88aec6c812d3ac8e02f90482c1a30185174876e80052d9a4fac32600934c593b451dda6f53bba053577f0191ed8a261aca8c68e64d8dd4a42cf0f9000001e58e0608000507090203060104d7a38630d7bd92f0d7f05af0d826ae1600d8621b40d8a29bbdd937bac0e1a768668c0ed2cb0d0f56c4c4f9c70615703e00ad75ad05f500b8239557cf0be744cb34f19d6c60a04a1a5fb53f7c2fa91ce20015ec9a1b736c18129fbf463c18a9f0b5ff2905a5a5b8bec19b8b3d6d9937f60079cdf81b3ce452bfbd89320218c88ed9a06fbc44496586b408095dab7c46c900ea28245feb92a4dc889558bce1ba0d957cedb7be3ed40f88101a317786bc44005384ac2c85c2593ac687d8a2986d52c36edcfcdd091a4d76157c3f1a2f31930085f5a8a9337f0eab21466a085db9031772bcfe55a65b3e5fa5d0738aaa2fff00d7fc2b1aedf5420f8173658e37bc38c98443a39d5fa277197c9b0ad63799a6008be2881d6a736a4a2f884b1b0e8c9c35a2cb20e63c804ffd9f53be2b300b7d0008f7b41b378c838828cff5a511b011253476adb57b38be9ba0644c5655dbc90012a71a795720f61de5b225de9f98dcf1089d610cb49344c295ab5b48b8a80200a02b7be58cc94c588d4b2173163afde7309ac080a01067c9f1ff5fd1c4755200fa5c7a01f27367724aacc787812290c37d36324168b2a010d8786d611dea6900c79172deb10104661e8a3e0a86ee134c97f07456088b72b33448b8a12dc69d00c6e26001a0e81c276e93fbbd929a8b9c685aec0836bd6cc6b7aba203452c7d00aab5948f0e8ba04239df2915c76728f32de1c39eb213a0faa66cf61c14fe97007d842baafe1ccc50f8b383013bdf84013d1560830abad4b848a6f8b9f1d5a900809fcb471ce1a3b535b07cb2b02e1e0364bb02e70e7b06b7d5a23a9d529d4d0041d046881f1e0881daf08840e9c6a48f386498dd86e5d96a9a6f814bc34fee001803d43c651dd4f4e114022bb7a035918b32f666294c52fef547bf9232cf970052925b600ac8632045985ad31b6ae4f86b0483f03d38825208948e902f808b003f28bfd278f9953a19314e410754b58649ab483a1000804eb35c1412c6a450001b0f96152fcffe4484de6266909f6802a1061c8cf0b6ba25a04bcb601e81a40007b0474c80ef577bf750f4d5af2b133e975a468861f44b5d03030370446559004cb201a0d69c3c0d020e026dcbe5aef9e935180ed9563d7cc4d370a832b982009603dcdf42a03ce665cc6750e09277bbc388b15507326ee87149797f6a91ce0042f72457400cb8f9020e82047983f0339283030ced9480b901a4a15112f900004001a401000000689de80ffff5433e2b3d8211706e6102aa94716871ac24460080ec3729b65ccfb83cd983d089ffdaa4676367aff8076f9901c785d8c252a0007db8ef62e4e6358b93815602f0ecdddc39108c45cec4f431157e22a29b062600b83f84028afcbf83052282189d340d346cde30a14e4dc4a0872b750b376c8200e389181ed9017bca6a38546ff736c34cac89973a6bc012d935a05623cd07750072e51d97a45870dadd9db725034e18330591c13b795ea166d83441f904af8200485f0972180444ec0ab6a760c0021869ca1112f22c941e6d520104414bf389000064031e092449cb095bcf6148add374163ce7bfbe56dc2af93048724d64a60041fd2afba77017a029176dc8ce2cc48cbe82908e8784a26871f22010084123000114f7ab34e0715f6e5c2e92454ccfdec11a843f0fdf930a73d83e5013363f00f7a3c080a0160052af8dfc718f26e188af64477a62304839867f4c00ba7497009d9201e68cb9a05a8e5e8d03f69caff4412ca59d2c1b94ec1560984a5f27e40069b8f1dad2f32bf23f2e92ccde779c73a6cb60717dd2624c11e4093c14203b0002ee12e328203a82694f145f4dabf2199396a0f08d7bfbb7a0672afb43fcd6005cbb8caa53ada3def79dcbf7a9e3e086715d255a6d20ff6c3ceae084013cff004183093dcac2d76536966fdb5a528af5d3cf38556ab43a3c15190218f5abe0003ea45f841b6a48cf2445abf52f9d0a66faf33302b4260a8d65eefb8cb0bbcf0056a039dbc2cdc09e8162ecb5d3a29b88e3aafb6e01790f1e921facc04fd3db000ce9bf352e9200000005e2b1690101a0278f2223ca806dd27e4342fc3f9966002067ebc4d5cae70a01b4ad8afceeb39ec2a00e4525338a740871f8acee322d002ab6a73b5745848f6ba416c55a662311a8d0a002f9021683026f3905bc1e0100a4590120006c07430e2525ecc001a055cb3629adcb1ee2476092c82d56314300b63e684f6b9a84a7e8020762400c6269a01c0055bb676249fd147e5293d350008b29a3f81a2f420e6d0c31e8a335cb94a9095dd1200205cfdffc974ac7aa7700ff4df809ab1a69a6c001a0722d49438b70d39709f66dc2d1be9ea488d7a113005e9702456e95cf4250d2375ea0181c886847ca44bf6cac464aa909d73aa8fd007b5d2103941bad251bdd8d14b0b836c31f443d6805280080a017a4f9caefa100866b01b075299c4b01125dba4b172b45efadc911bda3f22431dba02371be4500de48b1da2cff8ab17951357c1f999ece754b7de8142796ae568db85040c7d700a6fa1bfbca4586488567f4a1536b8a12a4534adc7c2f128560b9a61e753edb003e07c6dbe5c3a06bb38ad7204642b1998b6a954d9a4f31650fd98e0bafe88e002fae906c021154eedb9a2e920609af85f20a02ed785d05ca349396a76b06030081d693457cc3f88e2e0cbdf9c05bbbb9a0a016f95c4f4708a3a2ea6e3445e700222ce4e748d2e4b1d3c9e22920292ba556bf36e107c558b6dacf7d94d87165001a38e23ba8e4028ba604a605c3a09edaba925395c51020add2477b77efc03800d2f65d3f23e205977de431830ba258a0400e5db4dffef661f25db7cf9693f20034cb65e2a8063b4fcb7ad6bc49df7ce29bf8c782c5df035cba7055a747bccf00cca61265129065426ef3b511a535a8b79bf8f4c5751e7a06bcce3fa026cd6500a993cd26f4eb58c558a9c797eb848b53bd28b28357d4a67f7bb4dcd18b368400029693c38304ee1e258a04e7e3413da810c5a1c3a0f8ee93bd94946f1734b200af493253b5fc65567d70bea119827fd1a2d56ca364d1a0018f9485eb3b16500063038f668171d9324c2215cec86941b761ed2541f254b9e2f8ac2e0236baee00b7997a06e0109d8cf3d7a0ede57766664f9a0b0001d7c280414019c385dad3001234a8c9dc7f527b77499c8c894a76badd2f5023ca3ef86701b2ec4cb04ca0006168424da32ea1a391b752dabb0ac584731a51c1c6978d030e54511c530a6200cc02f90237830827503d018401444b8583049f874729893e1e01771ba45e9c0001774d5589904678ef6bbb4e58dae7dcc046e668063be8470de4df46b2f1cf0007000000c080a0bbf0e6c811051e16695424255442b0b5be30e91aa2f8bc64009b75bc72171dd251a077b1edb13320ef1c0e66a622edfa60858a574535d8cc008bb8f61537383f890b2fe284014f846d830945bac3a07f950be433a8c2201a00a527ad437242bc4d2f1772fdec35ae63e13a4e78d22a6aa0540668c5ff7a38000c2112c904614619db0f7e10de894f413e0eed3cc280f1b4793a305c75ed8000a07b748861fff1c9471cab5239e27b25d287786a02766533eb59eddb2c5d4800e4c2a00784f1b091c3285777461a35e2cfa8bf09c3f2214954125f1b07d61b00070e5cb8e307484c6079d5f59708b8dd32d978821fd1b44920a51bcbffe0520076608b6745e99d43f650fa103685cdab976d7a0872478f4758dea8ef0272c00017cf0ca0672b5fbba632a01ff7fd5e4b7e68aa8cde6184ab2b5fba3d8b7d4900cc9e91919b02f86c830827504d8340e0338401610b3082520894005855d3c500d7acbd15121eb14bd6063af82f54d18080c001a050e936e508f9f0d84dd39c0019c3fa494bbfde4a39cdbaa5c13a2280b18f93b347a078dcf249045bde080f006a09c6673647d43d466797cbc57b6df8f80ee6d6cbe60802f9021083082750001c648401e0480a83029dfb0000000aa8a2ae010167b0fc652a56c001a0dd14000aef2f2b950fda7af02e21e5df3b36865142df57e2ea63fa9350b41a07dea0007d897a9574b359bcb38e5c215b2a6bbe51cd259cbdf6f85ceeba5ca0f2b7ef00547a83f0290a83030327008c70686858f8b17f8502252d1022f3da80b50ced0056d10444146b41f28e48262902cfb48719a03edc934cff3e38fe2d5c2c5d6d00088148e0cd9e78d75ee5479ac990b22ad6b7f33b2e5d43ea01a00e2d7d1b1a009832363fb060d9996505926bf74c57bf369fec3ea933d69f07ac4fa011e5f30059a454d1ef797886819942f5b4fdbb0f3e012d5892fa95c2bb4cb3d0ae0448000a6c44a0d517621c9480a07fb2ca3a9df219743df41b9270015e91742a22590090a5b7af97c3fa2a0f4530efa005c9fc6a35fd9b12ec0b09e31def40c39d0a009d0f31026f0e1acd6db60ad8c0a3e4093934284abb787a4e73c999177b1c8d00bbe7b82811def71402c3a0cea649ac70e0cf94c82372cd91bd54a35e1ed8610021296a68d5bb9120dee8943ba0108690c7b7a79de84fe4609daac891ce6c71001c477f88c91cb0251a6b82ed54b9e508db20e64ae4128e725868e8fe52e77100e3d272e787b0411e943769e2957d89e0bee27695922f4c29f2852a2a8f6ee800d7bf4ee4fb680622cca018739ae587945fca4220371d1620e1993c46688166000bee1422e3f29c45a2e169af8382125a840dcc09a4830f4f27d112a8db098a00a5776256dff3a796b3c1ae3e005f32f4a206035be31f4d1c987e395fa0b82700bfdda4fb5e5a53cb830695907284b801a0801b60b94976162ea31a67b6d1d400b11d4d125bb6339679735fa5c5ec9f84013ba0381d2e7b992ac4e3751ae7fb00ac85df7296ddc1846a6f86076e1f2234abb618cc0566e079cc8205194b19aa0001a0133a943377440421436bf2f1111674db168ef28c57581b4b5047b9e71c00614305a036e86078a9503c9ee38276008d2228d32b0177f25c7ae5d701fa44003b39cc5c16b08399955c840e4251ae6b91a7cbfbc49a2922fd06d5bf1362090098aff3c880a0978950c4618174b7ab6311988168963c7274c11afb51535e9d0057c270ce2fcba6a06d29167a480123c54604d6353aa5354e3615419db67528002dadbb5cffe398ca13698402a8321083051a52369848fdd65ebdf87bd6b7970079b8513a86c60e0d2867415378cadf68cb245bed2272f89eb452edb58aab9e00a073ecd84bc2763b3d5cc65b6bbebb443f70e47fbae449f0e8472a0399819e00121bf9030d238401c9c38083082ce894fd541d0e2773a189450a70f06bc7ed00d3c1dc911580b902a4353766c60000caf2430da37f9ae46f2f863d71b3deb700befc142e98c445ad574040bcfe6f81d8756dc10543bad85812bc6a437df70200aba77a95ceeaa77d96087fc1da3aff681a3991414947ea200a1bb9f1994252008039cc1e6c8d5a8dce444305a3772c5233313aead75b4eed73cd43abd06fbf008e02583a0528811a8703b3aacd7ca9d963a477edacb12fa488dbcb857dfc5300f2e8af326280d1df8d3ebc7e60a4bcc7e11cf7ebf3a17aa07b27558afcc293008ddbae1c5619f4732f156314d52a74363976da908256fb2118e684015cd214008307d40e063cc6294d80b91fffe145f69e2f26f645030934da96966aec471b006df57f32c27795b31b7962a004bd50d21034a914b5c47b44b9f5e148a7579700a4acbd26fb6e7ddd720a15876ff8c782c8b40426da613474a3164e13d744fa00995449da4f9a40b94bbd0f03630f0edc8f01e87717b246a028f071c6809773002b2a8646424dc79b4fd0eaef84005429cb852b361a5319570402f90482055300843d1bbb60837a1200941c338d27f36452358611936fc8418849910b1c598000b90464b1dc65a4000128462a37c896f9ec8fe668ad90fcb4b219a5f3154bcc008b7f59c94c9b056e01a389031d7d90e1538efb229ed4f4bca43c528581a00200afe89a15aca576fdb1e46f153ee003800100800060004036c1ecb31a5d36000001a23ece6ae245849994fc9c7b01a04c6d090683cdeb83ba2b0e635a95da2c004f3632fe6e2c85a8efb0ce541d652a1ac282c11747376061b39df0d00f348b006e72588f722b5fb6f6b5dc5a0c581fde289e8ce6bae51cde7b515c6bad31b2008d1747b3fcab7a43e0630460da307b9b8a64456be81f96728c85cf69ab8c94008ee68d6775d46278fddd0c0e74369b6013d27a6fbbefa075b7576301ba9859005947ca0595712d6bf2314f3179ab92deca92fcbc4a4c6c99701161beed66650054602b7921987a29c1d7da829370abd152f2c9da049f14a8e90006120242cd008c290acde9592ab0669374bb03296bb756b1abcb21937e2b6a8d0c7f6e3fb10093a6a02191fcceb8da92937644580d504f8029b4d8e308ed175ddf2be90ff7000d30a7370261869426408dff6610bf99df5890e22616111d24591430ff7e5700200d031101bf8db6b2633853b09d0714eb6dba9faf351dcbddedba8d7aa9e70039342bea2052bbc82dcb864d3bed99d7d657f7036f9aa5663cd0b22036a85f00b65c4ef4247e65569a39623d0c99ffb1e532552e06bb7aa7ed5530e55f896600d28ec080a012f932d9117bfe19b9db9469c7a83dbd5944c418e23b24012f3500e278dfdd7b55a046e24e48e097c6de3cc133f79e9ecc6815b82905e49c430d002241d475e6dc6ec202f870830827500b01840144305e82520894623777cc09008c6058a46cf7530f45150ff6a8459d87024a9fc3e153eb80c001a070346cd3007496478ef5915f5e35fb67b677d898bc1e3e686afa5236dc23fa2f1da0757500f802863bdbb9b6d98d235c0e89304e1c99ccb259ca325e5aab25df64e25c0d00835c75258401a0ba5382eb86ac4c6e212a361c968f1725b4d055b47e63f80b007523c78cc080a0b841f5c4c30dc9e29b7e7da14a240d833d6cac09d552ca4e00056f3ac1bdd50f22a0797aa38f82ae00bb3416086631494ca92ce9813a3d8400ccf4a5163d359be22a4ef9022f82484084013d2ed58305e6f401c42db0afd000045f3518c77ec6591a542e326befd3d72475ceafe6a03852ba8b7950a601a800998c681a152b5304000bb800bb10f7bb7314ec0fc43cf917e92caaf3ee262e00604f87d137db43d425319efe95a03fc08b16c244158b515e860a767cc8fe9d00af964abbed860bd00428befc8648d5f904cf82486008d4360464022cbfa5c3003e3c3651c20e4001517e6e1fe9085d5560f7d99df893147f9a9f36811a2fc0005da412ffb6c019743c7e4740a004bec646da8932e6a1402e5ac1c185a34ced00f9fae8f93a7ded879d34423fe97ff9020f8202860348590101afcc6515af3e0096158ff9231dfc263ef09cabd7d260d1891935f80174f86c6a7618f1e2858200f5d44ba03c61a4a39af5587520af96af4571607f2a0f90addf34c97b9224b400bcf5de637d1d7cd6d182520894e010386d5907402db5cadeba8579cf90655a00831e8080c080a0d2ce86e76dacabf4fb2b7efc706c1395182580b99a36805e0025c474480f357c95a06e80a94f0e36faf84a864d1035efa5197ffa6191b68a0017f3ff697e269425a58502f9030e83693f628401adad44830441b09480b90300045f3bd1c823c717ff5e7eb895dfba1969922f0be35f039157a123ee0004b3000eeae78649003ced11c610556e5292fbc2e75d68c3899098c101e4ba3f216500de7259893af7cdbc9fd806c6ba61d22d581d56670303e3132fd0bc225616fc007b01007101ffff010101530401ffff0200c080a02962b1ddf4137a9e93c304003a50c392556b146e2cb52d62ff237bf3cee848a706a0564e7e348ea1119fe500e2962c8858dd1b2a88ac15cf8b969e8b7e6fee5d67516b06a32e7e29fbf81c00747ce65e8e26e312552ba1ce080dd035b84f36dc1b1d66773fd3f71239478b00d49a1830a04ad2dfc7ad66a8774a66340f08dea1b121a5119dbd308f8381ec00a604f70e723d378402a84f698304edca46a3e3a10e71eec8d32096c4a0bf7a006a533db04e88e9cf510ea1a7dfbdcbc59f945927d5cbdd0ac972defef725a0000a83e6087e2f5789f931c51e411a452199c995541a70093eb89030b12534c3003ae784016e0d978307c48cbedc082d4675e8b267dbdafe8f23d0732520b028008ba87599200455f968fd796f8ca023054e1bbf866825e381d8623028d89d1e005ba0e251018da029ad255e51c29e7037caf02341b9dd2bc2145ebd343cb5b2003ecbf0dcb85c76d23c6fa469107574ea6d45687bd98ad32aded7bf139e04a0000503bdddd674d48020ab13a4dce9825916a045e903e7e47474c4dd3ea0f7fe00a0d12427afe0876e4034dad76a6d08a7b264eb02f8b28308275046010804c00001a0f006e46c21db3dd4b9eb58e9cff7590168961ce9f6b10af5565d4ac0630048229fa029b94047ee4447fa52527d655374f8e24ab5345aa8aaf16533da800059431a7ced30835f512f84017fb2dbf63b8de35cabe78e6799ef2368054cc500dff6db3454ea5c0bd530755148a9de7b1cda633de0bac60d4df4403365b2db007a8a67cd21a0729a67309c1707990908118853d3761628eb7952e04e522a81001cec3f47153542f8ac6f010fa794b65ad8d81d1e4cb2975352338805af6e3900ba8be85e88410ccdfade4a5efae4b495011ed6173180323bc1db2710ca0afc00b45daa38211a92efdbf0a8a6d67538ec4271b028ffffda5485b3ef14a02fba0089ab2b131d74556d71e251ff8014abafa66a95709181f06162a2d3ccce9f0200f90281a58201908403c14d648302da2924052b7e23ab00e9c080a0ba70300c00a850b171fe142b26a4ef4599d9885c8d4f442570965b5d47c16b9d3aa05d5e000429f0c762966caf6343a757ea8e7649420beea62eb16fb699b3984e6510f800ad820a1483f05b8383029f1b9409eb271b444d1db7163c8555919de85b20be007d1d80b844b6d70dc6af16bad2a8afe89821092f05a7dba1223efa2b928954001fb494b2dafdd772bf0001cf6316f9b3f32a31ef905b4c6ddd99ebffb679fe00b8953198ec04928536b41c51a02f8c2d538b9f0a0be9643c4d2bfb2518ee010041cb5b9c793bddf4e9fcf08d5db307b6a287576b801ec789877f095179bb4a00591d5cc2f5edbac5cfed7036b79fe0b600464176a048bffc4acda00e8d6bb500c91f50210104a5dd839e3e112bb4ba1ef97c24392955d9315e99e6206a8402005ba2ff830521f652a71700e30a69e8eb506d842be89bd4e10d167c65b585c100a9821b95e83ff67ccdff7a557ca601ac9abe46a06c72369d3f93ca521bc4450097c88b4c2536827a763e43e54bd6843bc4c9189f2187399eaf0619af2f00cc00c3a06da2c15e6a4d63fbc5348859a803a49b0e17cc750cf90884cf52b2a3dd005f99fca073117204e4f57b4e037bb4835ca1fe304407391610ef249c5a0bb9005db2442210e88401713d89830c0bb6d4bde3fe94852cbc01c9769e6d0c1d5e008928cb841e9a3f8f82e03fc8f1312e77edfc7ce973d2462fe3b956f20a6536001c587d8e764d3dbd19a782a01811e9e6920e7ad9df8e059b75e1c77b2c35f80093305bc1f1c1c918e6a1162d3f3d0c84f86c053d000000001ac5fe01df4e1b00e5097f7756a015467163efd77dc1a22a3900103401a0e29737f9c2b47262c500bf7f2d0db5e6a720f2f215fc7d0c81d128de42182635fea03a944cafb9a1a100a7c39b1ef3992d16b05da2eb5772966cb3221adca258cf58e637029e63b25500887cc800fd38577fb405ab16807e232f4f443a3aa2ed34431b199eaf3377d900f2b629e917a06f55f4e9d3b7d235d980fc5f5216ea3983613e13b11e9c39a70035660dec706495f9140d701206369480b913a42213bc0b97bb3400ef2d204200370ac62150c69fbcfdfeb0b00012c41fff99fad109f0a62da6a000d92e206e0012961d730b2bc2aaae8378809bb8815c08d3c59eb0c7d1529ad7696ab5afab00fad6990a00a0e85c912211a3d337ee38488e0b00016002000003064008a00900c0000dc000100010a00000e4c1fb425e681ac4103b481f30becf0e532c6d6c002f41bedecd585df6fe8e8cdb00028438c9c10ea5a00000158341bd500300010001042753040024d0e30db00000e48d68a156802c0c000bb85304000001f41700402e1a7d4d01012ae3e229881902840124c04b8d5920a00001f4000064c87600d21d000000f5c4f3dc02c3fb9279495a8fef7b0741da9561576c0fec7c5a8d0085fa01240019240039041f1b366fe33f9a5a79de5120f2aee2577ebc00ecc10074b585c5215c3f91d2c2e55d0c69b7860362e934adb79607536305b4660ca0006829005ddc8d69a1b1626685c9f668b05df88a6bdedab763fb5db0994668f100f8f8c782c7eca539d1c4a0807c201534a8304998215759b659d64d220f335f0079b05343d60e6932bb82370fa07e07f82665f9fb376f1f408e46374539262d004f2bcac476a510239dee98b5e9cd5e910abf361aa3571eb8381d52eb7a00c600f4622ddd1d54dfc080a0160fe47a05bafbaca3c8657805f3eb086f70b62536007c9bda21cdb1ae2d17553ea05fb9aa3efd9e52a62b5a28d32d3c46e66058f900e62986dc0acaf1b7715ddc3271e90ad7e4b8441e01649f9da580dad751a1a1000bf36b4058708d7467b41e0019c6249b5ea09f5acb803ebea4c016a7c7278d00f41e024acdecb12d1a5c095bacd649bb798f3ff3dd25911830d9352d85c560009a00d2fcd3a04f0e3dcc332f2f0dab93d89741381c28b77371fe61c23e3429002a09861593838602f902f63e13d83602846e024002790925fa01b39880c7a000b752179d6d7d9ae594ab4c02d6e5b80010066c00000005594e9825ee01000000c001a07949aac884e4852233509f296f324e9c06d94765474a21c0f4dd7afa00e40cf842a053b775218216ba8f2f6d133bd798872eb53ad15a6691322df89e000fe8988df28508df887c01d27b20030c8a80a0ce311f159806b74c1625c5ef0003b7f8f1f48ee51575496f7937ca534ad547b614a023ed27f13f8295966ced00b0e2a9d91bca1ccaddd69ee4f3d6c249b385f4b2699602f84783661714840100aa92188b34f0328a651ae92b1e04f941e084f06ce95eb51778f9c25637235800323f5f92a075a01032080200fa61884910a43dc3da193ee771c568e760e38f00f30a4e588924ea08afbc043a46f842aeef5ea825cd9d80438e01135b81cf95001269ec522e0a9f635f28d9d1a043ba644e25f8b8aa8bed71c6e6407498763a00ea046018b2a7357791df25f09027889f6afc11bc1bc3a03c4528d8553591f900a392ace9c7bcf5bd11120aec2a901234794847a8236728a4a02b0c2bd1f39f003ee21427cc824788136feb55c273e208135dc0e07428b45a82f0f9017c820400538403938700830284968080b9012560806040526000805461ffff191690550034801561001b57600080fd5b5060fb8061002a6000396000f3fe348015600f0004361060325760003560e01c80630c55699c146037578063b49004e914605b00575b60005460449061ffff1681565b60405161ffff90911681526020016040005180910390f35b60616063565b005b60008054600191908190607a9084609600565b92506101000a81548161ffff021916908361ffff160217905550565b6100ffff81811683821601908082111560be57634e487b7160e01b60005260116000045260246000fd5b509291505056fea2646970667358221220666c87ec50120068817295a4ca1fc6e3859faf241f38dd688f145135970920009264736f6c6300430008120033fd014de0a77d3de3a8167a7e0368926143a276fb086a47718b002fcdf1e19cca7aa064d59a0ee335b3d15e04badb6ca4a8a9b704bc1d3ec55b0066b4373421fe89a1ec0f82c0819422d44099df23acc4c080a09946af9cc6420063d70623d5a09195cf20d0205f05823c5605b666cf29f47bcae0a03ad3cf52009760373c2bc945d407f22afd10ba3aede71bfc68f8bf60826d05a3d1f8ea82000a2c840280de828306c8e7949cb3502083c213c05fd86ef12ebb3a88c2c6e200a780b880000001e4025cd7c1efec89f0a6bcec73ec72b69e7376ed63490201004b00f584accff9acffbb315b6fd34873c115289dee290101140720af59ebef0000000614d3d0d6e5c0019bb7be1d36d075c411fa8454dcd114c0b43de600e2004b7402b7091a4bded78975a06fffa2e6af2fb8910287d4e9d08ce0641b60170028bf84f39c9f90ee52a3086dfff9016f8253258401ac08bd8302603f94d80000ca44ffabecd159c7889c3bf64a217361aec880b9826bdd949b0ecf0834ac000000681a13c4604af80cb9f9149237993c8566d1b98f27c824dff87e959c2b5900be3bf612ed94118fd9c430cfb278c1b9ffbb322a9478733a472860600d151d004b60718d91a5baced7e6338cc01dcb305008f1a7cf9f5222d0ef511ac39485009b3e9e2eaeb712914ed7d3a07c1b4f37bd02f266f49968b9d9d6010e99ca11004d9d0b40aa9c842647eef5d65bf9012f830c80668402d4405c83035f30943c002269811836af69497e5f486a85d7316753cf6280b8c43161b701000000001a001249a06caf9b54d0507a620310ffe6cfec9865389c7cfb2487b5116970fc8600dd0e405540a709c4eb828c7695d9a0756acf5022574e09a5f022d5b072341e001ff251e685203619e4be5b68371ebf92f902b427021f5c943a23f94318140800eac424116af7b7790c94cb97a58707f544a44c0000b9024400000183e421f300520040c0000042060088199b8f69cd00027e29666fee394c1c74f2e6fdad140077865f5c3e07f4bc8ab0706721d00dfeeddeadbeef765753be7f7a64d550990074b0d678e1e3149b02f4f21520b484bcdcfee32b8e70da55b927a9b0d0a00a005ea9f0be3018e1ba9f3869a07fb9a6d1cfe10d9b8e8e95f5e708ece640e57b0073c2f0cb9585a2e384e732f3dc67016a25acb26505e39406e1b582c3a09548004e552731c5f7dce56f0b0c1da37c5aefb91dc01b6f1a75903c09c58f235da000089f7c2cf3d8fd2a772d75c1d4829afee22b11232990db0e10c29c4441e0270098686d2ba83528f0270e6c743bc3c4a0ab4dd586248998420dff3c97b8d6570038cda1bade15dd88dece8e45dede55f508a04e971f5f6a9f39a51c4ee85ad8007941ec248720c494971384b59713342e4e370b69b7056bc75e2d63100440b7002538eeb42aaa30b9a09740ccec92177668d60a2b53639ce0619570b5a20f3e007be7a0243c6b11fe6a3c6ca2c385fd45fd1f9e9fa73ae3636be2fe1b281c8700da9596aa6a6a000f4cfb70eb9b1ce20c9fef61999622b85d35da2cf6449cdf00f0a0a8491db6862a7455d2cc81cb9f9ff8f0627da058ec74382dd9abda6c90004d119d425135578a0935d1d753847e6a9fe0446e011b6b9e01cb4a20c3a06500354a43cac7a2dd9ed22e65c4af7ecc837b16b97e9bd163149ce89e25a80bbb00a021196aeeed1158990da0a1cceb5b465e2cbaa0d78b65a6b9e533ae8f29ac0040e46ca1001b8be338cd759d086113f9dcc3a009053c90a99e5825edb0afbb008898abbf5aaa3ea429c0fb35548e5b230b7f6471a07a329656dbe790cfdf3b00907a69603f54ce3f9d32c3de7d3030ed5d122ffd817a6d01240000682eb4be0002135b8f3437a2c4a09479aa5be7b4adc783a72c9f3df679d84d5a07808f0a00f063ed2f9aa1de9e8600a03b1918da71d23a7376e39cfaa337fa6946d3a539008e19c0c020fe431ffd86ae9c6e7dc438a46e00b1334800000018432c896f76006fb4236380fe6448f27aac45dbd7a76c15d25e595185d53d541f1ddd1fa03000e67ae436c6c0fddf2299846be1a577c1f37aa1ba0b64cb7937f515bb301e95006f006577c8c6ee08b88d9411ed0786a299952bb76407e5a625613390cea47e00359e43ac236fbe2c25a0037219f3ca614532df0683df54d4769c72911d811000e1bc8fbbdf4c1acaab163e3f0ac1947204ebdec001a080e2ac5009317097c60095e7894735199a93835874a48839b518d58fd8de9cad1ba02e0090f94ddd14008a934b7051f78c5061a89bce87061e2a394c73fc4b257ed09a8939b0000082000f59022ef87e4a5fecc87c2b1b69867478704085b7621ef0e8efe51cfbed2f00dc648736a819a02d06f0a8bcdd192f211b6f88c167e6c16839a90764b6fa930044ef300d9b3839c4f8de82130984010230088304eee17402060f3f7f04252800c09d3c183a561ec35841f08ff4739596b557dff326180cad1f576ca5e7566f0083078ebf3acf6b2ab398ada0573bbe48cf37e15f518fdc91791de60039b6be00541b56f85de4e15976c3d8304902f90310820184840144982383059d5844ac00c4b0444288185b022402188c35f4b9524986e681b401c080a0e0f3f40a116700932f72a36a31e241be1ea4c85570aafcbf5224411968c2ff5d43a06bc6a61500b13982b621efbe24e294f823ff881a709fb15aa932b5c3d070d483eb418402005b8af86b7e142a4a01c79c03b6176761f7ce80b4ada01be190e9edf673ac4800d30d383d1a117802642dee1d22f38c41a00a5e1630bbf0227fbcfcfbe7452400c0de925450f29acbf9a447e153c492c00357f9010e82105b84010f1d8a830600863ca40301069c98c7481e6d3a65982aebac568606c423b24ef5575328fed10060b51e71e19e881a666905715e9c7baf7dd581a055014b1b0ed2ce9ad115b7003dea45c0e945d3e0b491e8d2ded4e83f73e5cd8cf202f8788308275082035100837d093e8401c17dc482966f94ecaebec379a20c5b66990efec3a9a0a3fd75009484861a6016b2d000845011b71cc001a048867ed662c89e19c63ccd46baa000233a0f957edd5b5c7546032ef3bd7d7c5edca031f12ad0656268ed6f01fce500e55414411f6ed6b987ae7295953870cac11478d309ad04886cb1d024036e240001a04bd7b186f9b6d49d63434710854292094e91d7d32e1ca7ab593f1dc38500e79a8da03e9b00df973083c7a42d4df974e5c299fd87b07f655454d75fdac30025ec1ef50402f8938303cde0648401087dc682627094dacf42e43d866cc37000762eb760d51a1b8aa80162871272f6d3c406f7a04615d4d591a88dddccb7340094a9ddc7bc9a4ee4eb7b9f52b96ecabd36c978b4c0c001a0df050b076983d00042813ef441bdf40fd3067916524882f474d6b73e88d58bef6ba02441f1484300c76b3d7d83f02bc32256108aea07ada9bc4391ea075a202c45bad802f90482008d1d8398968085e8d4a510ab6f72da09a088a57a254fabee3d86d14d0ddc8d0093d4307e25aafc5ea0336b15e797dc1d0200050704080109020306087ef45100a9857f7cf1f32083e24d389fcd9c2880230e408c8107de47a8821591e9ac2b0079fe0b57dd119ec2b65ffaf9672674e5d137234c12c2742e9ed663cd7299da007979b8761bafbf8ff705a3739e990191920d594dc4fb9c2e0ca62bf09669de00179a3f7a10aa2bd5dd58500ca92bd65d45606c8208e2573242fd3da685d7c5006d766eafba7d53c10d7ff5404b5d03950c8e26c5c35b5064c53a3280a0526400b011fc2ba423b3eb06331d1cd7f8ad9503417c1b461a9b0dc4d34cec49dca8007746ef5a520320d65d7396b32c29ef2712a191ac1ea4a3a843fac281d3b45900075790a81d7d42b41b254ec48c2cdd24960db842692c660ee7f733bd3ebb3f009f79edc1e93c5ec65a63c7fa4433f5f7993817cb001d8a12f91a725bcd483700b343dc6423a972c080a07bdb24f9462d5549443d218e85624bacceea0ea2fb00a57ad7d804039c22d67548a02b2493feb79e54523256120c89044c1a171dfc002e934962b06ce39d5fbc650b818a8402faf072783baee4ed9dce6387b6cba200576446b5fcb5d129c2bca6b32f11b915cd8b06791d03229503f9a02304e2e000eef1325359468237b48592b2fdf6b7b83a92d4dbe6a1cb56a5283b88f8cc2f000492ce6469328dec9f9df1e3a454f6b963f554bdc58af7b92ee69b6512550f00403de7278bfa694bcaeed7ee8737283119ebbc8eb77b0e56ffa7ec5346a041007653e1bc1261dc92252056953883852d8fd42b5005edcdd2569abb296754df000ae45c8a92a9bc01a0a901f01be5ab2d3adf49d6e80d6d8eff6e8cd01856fe00b8c1eaa0c28c6df35bcaa02e34de0bebbf6a81a8857ab69800d899bb12d226007b67b7b8e20fbb646f0dd37ef8ac71010d1c94000000a1f76f1a56886ea93e0049a342eaa4402ccd7c522389e2d98bd2c77e31fdeb4cc8b9c919536a61ef6b00a01b326fafe0413f814b0c20f4e4af380f8b73edc564af81442d37310d0b2700e9e43c9d9206825208944db05fd44e0f747c0f0fcc08f3952d74546b276cc600e1d0282687c42ead54c4ed0f181803f70bfb8c4f321107c98bec0e336738d300a075120cfd2c9ce94fba5ac64cdcc57292a6b01dc3e4e381779c6bf5e3c5de00143434836c80af84018d097787084bcd2bf7e20c104172dd7f1c6f609f3f020006f37b56fc23e5db2d0ff4bc413ac443a509beea197e7384e1903806a407a200b2fda02348a2a46c696e81d2f5a49424c54ba5d66916c0677582737d58c908006dd03bff0bedee89111da322a07abfcbb5ac02a429aadb6f86c83e5a7957e3002839b26ad80bbddbd87e6c9df7e101c9a03c01b1d27a3ad81184b495aaa4900029049f745033b675616782196f531ebdd58bf8ad8209fb8401082cd282b990009487627c7e586441eef9ee3c28b66662e897513f3380b844535b355c93a539008fa37af94107c6688ec7316f80fdc51829000006dd8fe7d7b855f01a7e447d0071ad2ced89ce12c85ff377fab4b47b5b553992bb6362c00d4d56a05b916a4f0032a21089ff159852e43692b12288526231456606cff3d2d96b67018a8b5524001baed56475c4a04ae6336eb6ddc239ddfd5561138d872de034aeacbe8a0dfa00d7a4d854689ebd77a046657bae090d5c1ef45bed6e945031e15076acd09ee40041e5ae6da98f533e4875f90d8d72099a380d240c44006017a5855e68891fe800d9681e771f771a4be30702036006200760090a790cb1003481ed506d2c7961003fc8cf951d2eced16e57a777605ea98507d24a3ad40c90f8bb0d3071f6e6340095ddfe8a1f66a01682ea63369dcc04abbd368474a3e82639d91a61afefd6a600f61c619cf2c4bd01f902b40584030291a0830474606c5b96a1a640044d0d7400f1c2d3c6f5e760507a68a27740c8c1710250f1d52a3a6c574994314e3ec4a000e052ff25b960d3c89eda3bc8cae2978b16f80d5b9c08519b489b4bbee5dc3b0029a0449a79b7d0f174ed5bca256b9165b487d145838cb0f762e4f160ca92b700adad710ce73874141a75b4ed133fd152cf2d4c34fdf419cf62d516b95187940082d5e04f9c5f4ac15fc9a016bc9ac1f37a2af2dc4f2b269579d138c2df420e00a12eaa82f5583cfef78dd25011831ddad98401624f5f8252089446c67db55500aef8782caeeb8be5a702d6a498e26d870a0a22e7206bf680c001a0c352493d004e8f70cc62843e5923fcded1a9c1f2376017ad14b7ed848b3890bc56a010ba0045c0ecfea4c8f7fa0b0d12d646429c1025bbcfab3edf15e5f9feb1e5831a0b0083200b83840174193a83036cde25247304e2a5a404873480c001a04f36287400fe59512f43c452c3aa37ea58a5a93033d3bb97d09c4f9abf54a8e0d7a05bae00da73dbe8096e35610c647c460b87d9b8cab063b856a739cd8608a0be93d80200f90218830827501d8302ae88015a63bbc199c00001010a350ba5c080a06be800e00cc2af50ae917ea00960ad0a985014c26d97d98a73c279e33dd06ae3e6a0001d44e2f7942aa9a0348927416fbf862f2f40ad7723eaa0d5966ac2232f8e09004ff90256818f84030a464883042276880311317f67ad3f1db901e401d1ea7a00b3ff1d95030f5f94ecf9030b74dd241be0010136b099b746a93a39089323e50060c3161ae2ff93965132dbea8b153527281aeb1fa001394b6206773784095600fa245949beb0de85784ceec4225453ccfb3774bc06e1f9047782018b075558008806e0f26a0d9e168cb9040414d53077400200cadc1ddaa48145cb10c73f2b0056e9ab2a38556457093bce6c70adcea10963300031acb9a95a8d5b6267ce90007a0b9f8c4226c3d906e0e14e768b8cb90000a4b10d737461726761746556320042750d00a000111b971289d3759e00c151bd190e5fe2feac3ddf7a93d2d548008c7565b14f834cb3334316af18883b9aa04aa456f4b30219ba8a1c83cfc75e0014156461cb65a6d29f3853ead8d33ed23b3a0de8f083e434c0b8ca090f90cf00fbfc0d69b87690c40e2271815829547c3877ba44758a60e5c5a2a0767965b600bb81b81c2d26ebbaa42a3b41f9ecdbf1dba92aa2d396e791aec968d0f88b8100800100a794e6feca764b7548127672c189d303eb956c3ba37280a4e95a64010034ff8a6033bf3859143c4a789f55a36b90a8b1dda93c0d5f476dce95219a4d00ddba6a739f162a6877fbbc836a1622fe0b18ffbe85a563967f0faece572caa00bc79d0bf5a02f90281cc841dcd650000016c3a30c080a0d57ba979fbef119c00167c9c20db24eb6de870a49fd6f47d0a0a024c971ac66fb6a01e1ebf3c3dd600ead1647449032b51a0b176b832a56fbce5944d2830123b7629e502f9037c0100b7837948f78401bde94e83025ec187e69234c5ec4000b90304ae328502003000f847a97a43280080a38b63e148b7140d9849ccc1afdf6ae96ce1f86382893100bf4f10970d15b5447a7523520e8dc734ebde651000868b0572656c6141354b00b00f4ef560b69a168227d5ed446f836c6682a851475896ea5089c02493d98800097fb8612cc24eec14542bc03424c656005f412fec45cf9a992410d7a72b7700d2325656703b4414ae482803325339830701602b294fb0bf9d0ff13ad5c8f80041ef7f67b5877345de0bacbad449fe88ba1f2a207f1cc001a003784364267d008c0fa4e06c75d53ebe84935adf98fce1fac22c974a0c72a2244fa07e1875d9002420a45fddf9e542d031f1c43bae1555df5f60cea397d0d24d85ee833e94c5005579087792851b1c11834e541a740688b2ba877528e365a0440366928888dd00e83be22b5dbaccac154366145b0e489ba88224d020a002389e404449dca9260092166e92fcff1bb92b318d4d5a4d1963e29b097c154aa31ee8f08302fe9337004354588ebd37273102932d1f49223320e381436612919e3deb27bdc3d342dc007875af75a74ff51e61faa029bcd419d755c2363ee379ceb96b66af0b08433700606fe222c883dbc921c65ed102f90272c00d808402dae2408301cf4c943bad007ad0728f9917d1bf08af5782dcbd516cdd9680b90204deff4b6008275007ae008551be970cb1cca11dd7a11f47ae82e70e67425307a1fe160207a1b86fcc7800b41f39acaf681a3792c080a0f28f1ee89954f4dd57d0a1ccc1e6af1fcce25b00b93790913e57a471aa7cf9076ba0111a752d50fc020b3014fdc8d656db70bd007f9ba4e48baf3f18547ae3746436eaf9016e82a09883f068468301738294aa00a11500dddb2b67a90d1a154dfb7eabb518eae680b901047284c45b40a0026e0013a2ae9fd9f40f8cc7d3b3944c57f3919c7fe303000003e81ce36a3fa053790019c527402d13448b5a86d2a7e4b1073b787feeaf7ca65c0cc5a01565667b2600319db33cd25026b2e819d49e4c9499a362d5ca9e4d1edae3563f9b02f89040008375a57e8401ba31bc82ab1ac080a05efadfc076780998fd8b8d7fefc2841f0079a78e42bf040ef40103912cc91f5af1a00bf6bdcefec7e4014f0304bee3a70051e599b1d2cd0e6eae491e823ef924ee5fb70ed028849833b925749808548700ca67bf874ad6cd7dffdcc01d69f69886c77bf69c1306e9ee5fe4267408cea000044e1b5fce0f64af91770a5d700df772826e9a91be54fc109f578bbe664b2600b802f9025625b18401019bee84104cea36684904f918928292ffa6f22b5e78005e782cf3f3375af5f1a8ff2f2fadb9206caec505ac98384120fc4365ce292200cd2eb06f9c291601a094cb66f85133d48fb176d6422fd35c14669a09e906f1009a4a2abc65e577593a9ba059a056c15fe8dd802d44b56f7f3cd636605f266600b2b8c2c01a8137842c4e24edf903108302a6d0842faf08008307043a942f2a00fae1139ce54fefc03593fee8ab2adf4a85a780b902a47c39d13001e00168010068001000000168ee9bec9b4dbb61f69d9d293ae26a5aacb2e28f8c62581ec500b287b6ffd71116cfb1ea3f1c19283fe2bd73a9e79ca5c12ca32f59000e87e500f1ababf24a4692afc87b83eebb8003794a084788bd6001c4e4df267b5c03e5001c3fdbb04b1a9d3fd1e107b49bb55dcff52e73033361ea8313aacfa68c2fe700ef5f1ba3b0da4bd725989fc84afc259c91a13ad1defa0ae6b8dba15391c53c0004ab363c1031e81815aee21ba53a055ac15c545f16ec586c435a447f2dad27001db1a5ec431cf9181213b22f771553768a12a5e94ba6b40e2fb11e886c49fb001b96b3c2cd6f0c31cb177456be0cde8d7900f48a1afa07343cb11f08f3c4880075291a6d67d0ffd51c59e1852493e105c548d868023ad3b24f0ab2cb649df700d32b6e992194e65db5d51f46bbad5bf85633ecf92c620d8aab5fab694f0736009c07a9c6d686c5920c56631b008d03000e87efc110e7faa95680c79937ccac00a3d1cab7902be25ec0fabf14f8ad908b2dce4c8aa2e7c1a6bd0699573fda130038863adfc083d45b8f54e0160436c387be4e28e2290f3d8d44edb9334f4a240078423caa5ccd36c7555b097c30c7c3b421e9b59a6f05cca0223f5a7c1bcaa700aea808369522b11865b6bbf8e8397e56b58ea9e4c6f5a5b8011fd34a830305003074adfe84f040fce4b401a08da0534a86b3542904175cc1687e2d0a729ae80018fa4718fc3926c27e4df16864a05f966913b7613c146cfe9c495fd563359b00feaf413f606eee4d78847ff2e96837f9022f82f15f8403a7f3b28305262900000806a6bda4b6b2c4a1e2d811ac957490254ad52a891b13c5a82ca5c30de16d00a06c6a488027163b0d78c0a68de7d6ddd0fcb6a0975023c79a7e32c3b8b904005850e2557c2cf3b5a7a075789b9a69cab888dd71c7afba0a2a2d961ec13dd800d5bbeb0517a03c8f79e49e518380e7398401a165c380f6468aac708e349658008645d4b38f4a213f4fdcaec742ae3d31160ce3fb93b3e76e9f91fc27707c11006183e70c95aee43b147557a041ee474176ac96c2dfa333fbeb7de3add26183006b121332fbef052af3ab325d78f902178201468401f78a4083035c8839082800cd202d5e53a361d04d807fe6ea10261685283c81259c7fc0b45ee0445b0f4200e9c7a05a778d9c5b31a0c94a035ef5048e6dda950e986e6b8abcb0157abd4b00f1f615c10fd34a8aa9c398214b31c780a0a287ed5e625828ea97870484a114000707f0c8478215fd869450c597d2abe59949a0480c57f4c8b4a79aebd9fdeb0051148ac72328b02412fd8cbfd45fdb132cbf8e96bd26cbd4cb4116dee5273c00beb36f26edb3640055cd34caeede7065357f1fa8d4613ec173eb9f0001e592000308060401030209000507087f9ecb6a80087fa3470942087fbc0ae760087f00be878385bf4774c0cd89d928fc4847d12fcd46a96b85a5e4d4120ec4eae7fc005698bbc7a3e5cecfc7404810b0c0dd9b408f3becefcf0af042847abacfe95d00243a995490d568ee95f40023aa2b8b6c297400848854d45318ceb50656a985001a5d268e6a15227c54556157f81e20f3f2d586248d75ae06c2c8bc251f1e87009ff76c649fc9bf3c7228f537ea2f507eebd2322749c30dd0697e14f3fbdaa7007db938b64e19f75c093f979dc023705de95d87e957611e9129c3cf0d9111e000a4b428221a9383ba9a9ff7b1f63603a300f6b7574a6546d58ae259efba8417004032668bc7d548fea80c571014b374d21b3419a1c808c200003a7b506093e1006701682e262193102d0d560b70c7e4611a553cf9b81ddf5dc001a05d9fa17c00762bb67f600c8f8f98d0f5de7c465424a81afd2354eba9f984284eb0a0739500016736f331a5a3b5cb6ada456c401fe3596a67f0de3e05d656a05bbb61f6f8006d318403b14fbb82f61807736e2f9aa5630b8c812e1f3fc987030e2a5a81e2008db27c3d1044748d9da58501c336f2f6f9fc5761c52156571d01b2102a712e005f0da0590496be19cd50d067cf3cddc1f1ccffbc125afea61ef98e1f028be3002b349f5f02f90135b2840101ce7284104df99e74e72fe2b770bef66f1c0a7b00b334b16a897c94fe01a0b523a299dc67004c3babb1e7307c2bd8d0a7274da2007c5ae82514bc2c0fbbf552a01659551e708aecb0cf4b76dc9668946afd0e28004dc2d650c27d627d157f49ae9d10ee5482b3988166c3ad69e5877598dac25700d54b31ff17fa26e466db7e89b4480bec27283c34b34fa38da025750a5659af00fcb3d98ca7cd57411516767c5d391dbbf3b55baf26c5844a8ed8f90310831500b13184012076778305a6b494b87591d8b0b93fae8b631a073577c40e8dd46a006280b902a4b143042020d68363302080e711e0cab978c081b9e69308d4980800a000681a68e40223536e9218f58d8510add3596aaddd5e9cfc99d19661aa1300c89fb722629664ac109a14260000767706eb48763f117c7be887296cdcdfad002e4092739c000076064e422b0acb2bd7e3ac70b5c0e5eb806e86a9403841b700eb536ba635288a48d3c047bf20c537b13b0c9f75b996459c4f68d9a926ff37000a31f234036595a3cda34e5317393b24d643f6b073d6b78b9f018634cddfd10064a81354078808f0df72d2a5005a233471a2784d3a324f5cdb1c355ff9127f004146a0749135f1cc239de9296d976401394216e81ad1b9aeb8623cb386d7860071ad01a120ee548302a20cfdbccd730607d104f79280a03ee7a369cc40fe21008bfb158fdf14c97db1623c374b6ca1eba91c45f08e57ddfea01fa3d67475f300090272125426dfa2731c5330a6543e803394a75d25ce46856d283244675534009101cb20c582c224462c3912d3584dce464fa1f5475224601575f38dd2308300d1d82d1cd6f0b31c90929bee66f7e025653ee15e04e5550809f9dd5c24d598003c395b7574841da2e054cd58d18c3aa4d4b01b2a0ee1ce1854f6f9515546ca0072a16542fcc7187a263be82568440f29812bbadea059a911f0f95854a501f8001cd6c3dbfa51036983d07aee0b5e82dc0005ecde76372e75fc37d26cbc2db20034f0fc221ff2a21f77bc80cfd0b9531db20da8a9feb0a3dc53426648e8edc00032db75b1da89e4b0d62fc1b08507b2a03dcca9f3f32a58522727d48b36f68300d991a46f9bcd324e422a66c9f53e773a67c1a44a295f37a7ca6d2842349cca0055607312216519aa6d52a6843d933c2e2af785015a1258c70001e59d0601030009070802050600004eba1d20004ebcdc40bdedb0bec294c03aeaec871debd700560b1511e571d7aa3b329a973390b02083039345b561543e126788e69fde1400d2a1fccfb15d98249197091b86c0b0f629cf01876646b66c9e15b4d1cf78100068801e6a0c30da65efb5d77e4193e4c8cced54262b6be1cdb4946d95dad6c60099206bdab55174050b55b7dd2a0447b09f321ae1dd490c19906905eaf1e76800eda8558aff8eb5000165846aeb172eb292c3dbd347faf38af802a7a4a178c60038f514eec6a7bdac4a0efc1fe014478bf5bd1574e3c6b383ed80c4461ed419001241c32a7a312f26419f004958b6b42c43f0197a89dfdfdcf9ca8b4dff396900b46a79d47cc6b3b38a63ffa717d90e4d2e61ab05fbcc6162441d1d4bc9f4c200996d33c001a041afe5906e802a4e8f64a5235e8419f2e52fd15f474a9e59e000609f5c555f2a26a01b006c54c2e745db6068f1fe60ebb21bfd0bcb8633d42500ebb1584d9b9948df19f9044d2407430a9488a0e09d40b021c0590cf106b7f8007a909b9c508c80b903e40ae6a6469b200111992a5c681a4372f610a9dfb7c8009644979b4a0f27063e9e7d7cda3243401b67e4e594c9be50385240246b5a4e000167cbdc1000358d1b08ce62fd685ec046877bbe2f6936305f2333eca317f00093386352db81f8bc80123a6a7970e2a764d4208431636b726045157b4812c600e7c2450bf4e64fa82a84507b752ff030d80490da090f6ee3e175a030217ccf0048044d4c9bfffb9e38e9fcb5ab1456da03d20e172992827c2570d91df9012f00831697bd8402d4887a83053db49480b8c40894edf140516109265d6241eb70001f175b24cdcb497f0e071f3e8b0dcfdbf2f89faa2af0555ca00f7cbd7b327f0079267e2d9fe81732a5fb8e0d9dc2b0b978d849a54b9891bd416ff8b34b8401002358ed8309f6a894874e8865a1d71840b8446b634a4c6222d6abc04690b98000a278cbc208f39806fbf0d99092bdfcdcf0f7667ad70e533debb078a0a01aec00cf9a8fcfd2191fddd86533ac8ff70ac0ed91040a35f89a6d138547e6aaa60200f9021b830827500a83753ce38401c9313b8303004d0519007bd44ccb10265e00db2dc080a01c2ef4576f63b5e12defbb98f80e6a6a6d8fb9f12e427145d54900c85d47b22212a02ac531ce6240c9fc0f66fdc00da48533e497089bd0abea7800bc60fe0377b4230481a691248302cf7623ff245ea1237f01a0082a5292f5fe0083e7b0c1333e27519e708f7943e8e3762b79d28e108d94c08e7ca028f1fa3600e6da858e6a1daf295e694a12fabcc7185b14d67b0e26e05d59e13b5a11c50e00824fc0a780a0cb3e2efaa7954d4ac9c3d72823b25743cb8c0715af01763192003befa82c5a2412a07401f100fd53f3bd6d5fae3b8c8d98491212b728c8c1a1004784dab7944ffce82b417f3d47cb5f85ec20bf607fd9014313a949c35375b500696ed6cc5f969b55cbf20dfce8ba140f32fcc63a040877ca7b495c30503005005dc8a02ebab490e49e664389685c5cde41e8e7d388fcd8b9c8070686ad87a5004e91e54cf90210831697be06dde494581b26f362ad383f7b51ef8a165efa1300dde398a480b901a4cfc32570262ab460ba6370fda3341b12fdf5a9516e00ee00491dad53de37f1ccd750a6a079029428002b399d98b1a8609a8c2fc617a5ff002594652addf7490613766fc4a0ca21ed8da27dea79040bad1bea028856e4b400d48df200e862422dcdd78414dbbea0692ddeec776ccf2502c9440175a4079100216de6d6a8476c96ef0f31d6d47272fb37daf1ac3c3940a089f7b3c3a7183c00a5eca61fb9307afe5ba8f7acdb94b186ecccdc0fc0dbc5ddb04f649de8747300448fb034773465a07b31e1270c7a161b3023b5cb8aea271b580283b1d8725f0074adc2e421f83bfdf43384012078e769143c2db7b2ba37a7c2d855f0c431e9006b5fc832c0413b441e71a902241ef91e8dea0ad4575f688f563dc16590e57000f97b542fa87931af2fed8af408c9f7b7c5346f083e384b2fdf04d92ae4d58d004a62108dcd8707830be2162ac475dafc4e3248355dfac4cd8e68f1a671715600d948a0d0b746b073faa67bd81bc4a06ea0207048ae93afc12082dc87b8d32800acd5bd51383cbe336ab7db8c314ef657a042759e0fe2d5a97ceb387b4b436a0091e42cb1795dacc9ab6b84bcb7df1193cf2f340a9ddad93ee0a0b6db3e6ab200e348f5325dbe536d5f2fd7c50e01a55a15c7bb066f44578b1fd87444351c2f00f346473319417001e5f96dd659c0ff140f4aded0387c00386416d6d379c1020027ec5d2cdb07f03045c03441b2c86e77a87722648cfaa078f51746928deab2001628aed369f725821f12bc5eff6e6bc1499ef1901f7e904ff8b37304d2920b00d7e307f423bb09a9d359ca00c4a050777e56e52b1b3d1219e995c0dfc8655600b2c53d544ddbf026ceb70290cb4ebaa07a9a9decf024382ffe29a295b96fac00b0bed8f89838cfe4fc09d48c05bd6c89a702f9023d820164832ea2e38401730027a383049f9f0de0d258060005dc6558c1d70641e9f67bf1c85eaa33450b47001c000dcef33a6f838000c080a0a2d3805d5b4e466a07917e8b2b2938f9a9d5004bee8332f19e4d044266418abf4ca03e76d26cdc45cd0a6bf40a8a7b5ac7770035e6316bc5c7c726376a6dffef30a08ef8911082b5858706dca9673e8c00a400f9068677681a14a8707126dc8071ca1b83b4ecd163fbfe513c09118499a94000f4123edaa101f0ef7a5e7b5ee058b094f9a0165a79184869c88cd3257985ec0001f3374e0649feb19484a6778226c2322b5ebbf902af8202a102cec49489640019ed2e0dc848a1f7d2814f4e5df4b9b9bfcc80b902445eaa9ced83a7f3d4870086ac2667503a61e8c415438ed2922eb86a2906e4ee66d9a2ce499280029620005f24e7c714e8140953706f74507269634003657403757364dca99d16ff41d800df36bcbd66316abd736925f599919cc83e602640ebed479c12a074f25cc97600c415a5072d266c09734beffc05bc7946dcd21a21f502e9531c2e45bf8402d40094a6792e8d8cc403d5f52a081610af49eae08d0216e2484e35cbc3bf74a68000aececba063c61a1bf3d1cf6ba5762582c9e31841d28f5c1d4daa8ed0041d1d00db468e2c8612c91e83705ef280a01d8a938e133b4157e60c6369876c073b270076dbccb6ddc016484ba6b89de48972a05d1af8ae94e4a0d34d32e76ce1e5d60094a933eeafd75b964cd186c24a18829f505681adb1c43ec4872cfe8f6edb1900f746e20454b45cff3bd44d47f443174298317b50ea96bca4079d7125c6142b00f2ff0761777504c711eda04c58db5b723b8e2444b76b451d394ee5ea6040b2000ec966191e95c87138350b5313ce247db2e0f780a0309ea5d4f0ccbc1438a8005ea3e36966ceda61b4c9a1624bc2314407b100ea1e7aa06fb5e422d5d4301600081c3ebf5ee84832a37458ccbcf0c6fdde1abbe15ca691c08402d456f9d4570047bbc8aa86d1cfb5d5d54b34c315d5b4a032e844087280b2082367aecd9aac00960d4e5c81a6db0c99c21e927f7395b1838d455ddf4600007b6708121c38d900a27dd89dd60488839269e1f063a8158ca511b7b7247507877ba07a74de406b0062b548c885016d23b8925a80d8f313dbc7f61e058b816dfc1cc1ef35840120006c916930c17d116311714f6bc40160aa7234ba623e08b4250296c15ea6573500959ce2b0170162675634c4a5fed09819e3c46d86a965dd9447d86e4788aa9b00cb0bc0a7289f18e3fc3c54e3fd8cb7ef7fb1251054b99f345b925e254f61ce000743cba0512d8bcfca74e009c4acd55bd8236e71268900ee03af273416c31c00c3a06cbb13ec3f13cffc806b521164e774a6bb6a976a0e24b6fd35d47d070a00f5fa9ca068d7b2f36c64c48d79c2560b82391e3f3e4a8c4bba4ff49ceb14c700130e004acf366b8395b6841091653074b02a3315ecc7f869cc1fb6a999b97700ea918b357a4dbd67fbd03e5a0a26910f6cca2918d180bdad9985d9219863a600bfbe803fa112f4f9c4a015f40ffa2face586ac01b6e25616ead3b322401461008fd601140a4c3ba71622c3a036854768cc79601aada7007be924883332c3050001def08888bed45d0b0281121c14b48e7497dfb62a232b13832024cc11346b007760e2d943a87c8f6edbe1ad41b3aa408439173a7ad3483006a02bed132aa6006ed3d38741dff6b6323589a6333502d6ef3b792fbf840cd2b9a26ce14d2961008ac6d9973764c1eee84dd45043d7ff1f644d43870aa43c87670c9da081599f00a0d60360091ead9e0de49f2a13a672c571b785a49ec1998a102e9fafecc00100a083d20db13b3942a986ad2ee4a16f2f59c7194c6717edf2983f3f0f5f858d00740ba05494160b3824419a25e895d702af8431d023276281e4a99f18ce134c001ea1af16c18402d3dd3d856e8d77583840d3fb3a9159ce1208601ccebf0f0300e41dbfcf12e1ae0b9b3d17a077d59c576bc8e67f1098e4b067f9fbfbe8081c00dafc41f69b3e878d05746a5aabe2a0285d1cdc3baa8e6b77dd67fb145d7bdc00a44c40153aa3e7f8c09140deb6c35ded22dbc1c447c64164c1071828a0c1ae00cf93347cc001a0232729d1246d079bde7b062d4b835f2ce9d419eedeffc0e800ae3e8329eeada616a052a723b92a3bdc1058e7904c7ff8c10fdb9f209c05eb000998c2da65bbadb9dd05e3592d59babeb8a4f9836cac7fab97e1c8db85fdbc008bc03d2da0267a3c4eac9b0bd25138d9e362f931c30670f3ca1201d64cb870002debb583a85cc001a0a8e15b2f874c74a0facd7332e3e869ec04892a1767510083846c4c38fd0be58480a01d0688f5bf8128bf5bae7826805975bdf7714f8d0059185a05d56b6d6dc6fe1b27e4af3996f13a61bb7b841e6637e96ac60162800039cd8657314da0c33c79b52d4f8902a5ef9e45ff2f43fea016d3ed351f787400ef631cf17b9ad209c080a07624dbdafb9cff3f3cc6aa8f700f3c92193a6c98006a678e4bb75d9fac68b60e53a06e24bab513681d1106a5e6a396c2d8c74cb8005180b2352a51ad964916da8b64539183f0462f9ab0b8533314e17f641669d4000d982c413ab9424beccac2332e0b8d65a6b1c0c7037dee50c43df02674c13100f40fa48cdaf5b12b0c38e360a4389823c1e7a0cb60a5456915d7047901a5260073570fdb913979328ebb15f6b7fb52f9354a7b60cf6e55f49494be3a8f746c006f1ee0d422bd43e00fb46135041491f8ed9475ef5771e11fa811beac2b65ca00b6a0f7aa39f0362b7ec436b4ed1c19d959f3b685a7e870dc5d22b5a5f524aa0002653aee8abe490de9aa4d0db75c6eb5b2698badbe580a61553bdff7262a3000da0155c2f0335b3a94efd671184bf60d9f3829c2c72bbfeb7678d302d19d8800d6ce2a3d2421fba6b6e2677db94d01710089ce86c10c703fba7c27340f47d500d11815f69c7c7abb4b4c7393c096d2137c5b9389588fdcd718a3bab24a7d1d00d027e91d07301faded72e8e92962e1c7f2101b402f360b920ce6f79c819b6d00c6d4c2bbd256c3a085493416a56b9bbb3c4f0c5e2b2e43237dcd0bfe231ce600a801bde76102de6770a02093ff4a70dc602dc55b3838848f4bf126acc9325e00b479a6b1aaf81f03c6aff3365cfe1b799648896db00deec0a36a97562b83140034576ffb10c00ad688588449ecfdc43d727562e98b09e8a403488abe19f62a002f4d55e3a0275fcafb85b83920be14f6ce2268c99c1423348b61c0e484158700ddd3cb0079cb898402d8413883060f641231a800fed5127d9853d81cea1985004b4eb7185d9903bbfcb5ae2ad8205f425387d973ec0b1f0b1f1698f6fe6fb0008911ad16d5545babd96ec7397e36e3c6db45af3233ea323776915b85a026a0008c7bc83d407f72203948f9e006f83092a34e3e3ed51326219b1ad8c6cd4e0800833ed7188401835e961220d8c528afa28165a1f065b3749e717db98a2e87230086f26fc1000080c080a0ab40d5f9229c5cbe9403c1accd06dd79c1c468d65100f57b50327a76b89f75fb40a01c991802f5c93808c33edce9e7d0ed59be80c500c9899cffb2d1b7882261fc19755f5abb000000011ba64130db3251456bfa420022577f289f01159cc001a05692c7d4ed62d27f180a702de6203fc1bcd61c1900aef0c68d24627674eef5fd32a07ff9b4c0445c7e452e8d3fb71197479a9d6900c680d5873723db4a5979c9a22452de048cd8054641825533d1bc3324df3c3000cbc3baea812087b80236bc88a7f8278409f39bc284bbc9442e6768885ea6c1008bba4e1dfb1b9acb8a91cf58ecb1af53ddeadfe5a06456b24570523a5814b000c74d8d8975573bda39df63d3235d678ed0eee387d73ad0359df975c1135cfa005aa6998088913bcfd900493bf7bcd2466f81f9131a1745449765dcc0fceb4d00cae122057765c4a0af42b38982dd72038c1b35a220deef90b224c6234d04e7007f6d890a977707ea08a00743b0faf5dd1a44949937e28c756badb7aabe3ebc00b9f5ec726ac5077b764676e529dcae6f09340c03fe49f1c6b61c1f0d8bd4e200fae0895fe4bd6eb4daf78a218dda84fc9af632b71733fd5f7bb2839a862b3300bd06c001a0252534b9b2ec8f3c8132ee86cc8dc5abe0d54757fd483f29e8200079170c480935a071a293cadf46646b69902e297adcc6d52629907b97a0146c0063533db9ed832543f9023582028c83075813943fc69cc4a842838bcdc949910078740226062b14e4867142e4a63792761bb559076a00000000b470e7c37d8f002f03d6ed35bd2f2a590d1dbd47ca5ae5498649d795523c49fd04c861f6a073000119c50b755bdd5d9a96960b6a5abdda2d4504c2809508934ba0f0cf6a4a5b00f90250831697c2070b24e40162679e5c74b54a00235610e24712ecd59f59f600a40f14b6352294a79cc6f67fcbf95ca0b36877013e52b9abe0064522ede09d00b4f580e35a48ba0783822c84a28978f81730f25351e9bb2c7467a7675a9e750072a0ce9aa805f5cd77aa6e381e7ef1a454d9a2fbe29707cb08cf14580ba91100834e2bf4ed706cdd2e87a070069d5c6969cf296d1df2f8eeee77344467253b005425af22739ebd898cdf81ab158bfa6f8c9f862f67908001a00e09f98e4357003b2c398706face54a3f0d077fe298ee8ba3b16c40f8003d0c059a059ca06bf00117700e6c0458d50122978e73c591b955857b4d4e3951ede7e71a2c9605a1b00302aa9e216081333dcc77ed6bee2a3c001a07d79fbb8b7f16e98fcaecab66e0008fe4195d3fee7c325e54dae4df257e243f23aa02a3addadbbeb01beb5d60d000a121f0abb79b8e4c04c77cb4547abe18526bc181302f9020b840101ab468400025577418302efc62672642601050f3d238cf29c34c255cb6441aba02c6aa000bb515a43023a25b8d58a308c33631990f6aa834276a02818b94397ecd03d32006b2594c6d62f67741990779ba8e7a3a8c1647580c89fca1675807bd6d3fc350054829c046c3f352b135db80a8fdfcfe989c5cbae6816d2e5f5704fa0cdc083008c58f48ba05401c5ceb2401c497893b206b83b26f14139a1b4de6192b40a790099285c205061614c1f2d197e254b2440dec330ceff6a200029aec080a0826d009473ead8a736e097247efcb2c64e088e22b913460e5520df9096c6dee3b3a00026de2cf7d3388a75f1e1d8cfb6be87f94d29bf528649b85c09113b89eae43500f102f9012c806483fc5b0a830395d18601a3185c5000b8bc0006020c59010300010203002001010201000201a4020202c001a0703f65ae31ce407e7623b32c00ae41f87833d42abd3db3d9221bdee300fb74d31ba00754949c89b342b33c1900cbaef7758fb9d5a0f56622d5fe1c823829a02ad23444cda069904ee407611d0054ec72f34ced01746806deaae7d24c97ca8de187cfa37634f1eb41a002216e00b6d1b56d6dd0bf8e36d010a1b1b77b6596d895e1d8540c6963dc6034aa17ac00468082de7acb2ad9c87358c2f05ed464499cdce63767e67dd7fcb0f5a70f9800ca6806dd29c7a0524a08f87171c09f255e2b541515871155f5a80014c2525000ce125d27f49df9d9016483fc658f83017cd2876df55896dc9b5f000babfc3100b35f9e6de9ac9aaa6d5cfc6193c080a0d6da4de27db72395c5c8b215f34024000404d6e413c49d5f4f46636b2ea6e680a1a0710d82f73b2992c8a614fc8b99009458e6003944753ccbb77120d995cd90c8c6f002f90101618401447467830300bff99480137510979822322193fc997d400d5a6c747bf78708b49a9708c00300b9014451905636022ec7254b902014c080a0c44fcf6334de5e0e0ff028569200b6c57eba772c28da67e54de4cbed30230b23fba00ed9359d38872fffc5c3d000f3d15cbe19c5abe8ffd378bd6ccf1c7bd1600f76e2f88d8201016af95d2d010015decfde9af73fdca43a3420bc6c5db6b864a1b67ed640b4885dc3a00b4ad20028785c75972ffd99d66b926b8c91e6a18b996ad77bab66f0cbf1bb3ab5928300f060be8303cb49f0d27153811508651012eb8f94c6de200613ea008ba44f82008cf7ce565e62c9d7351ff10b7be4d62b77a94c68decc58db4bdb4d4166d02d00b25b68e6d77f8a11e44e4c18e0c1dccb3a65ef88fc0f0a39d5fe848174073c0008c52cba0a0f03825143f5b8ec05141e192689b530176cb3533fbf61f77065003cf5e42cd0923683d0175ffa637ef758f15b1dbdc1585b4eea9c36d943f79f00b0dcab224377d4d8fff87d30faf1f009308a42ddc1d5adbb894a1f8c25b3d600c5d8b1f18b2e5e3a694ab30db8e4f4300ed3c8256bf8f3462bedc8cad7606600eedc26762e219d7a8cd5b1402b18e101d9db546ba68a9d7fe3e86e7a3eeca200be8845027b040fbdbfab5271d8132a02d7747fd5edff26d37cc061405e6342007a0baa4eff77d205c32a0653c68a1a0f5109b517077f924a28e54d0082c660002da38bb082574f3ff304ab4fa452870068f85c8eb301c41c9342b411da478500109aeedb542bda23f0669cb90aefb2168b57d3eaf0f08aa06dfd73aeb76a6600d20fd8c1b9db84b4d3b3ae49797611fe2e495f6f42ccf8c68c627d4153495b001f40bd73d7167a24b8d494901fc0c080a064bce7ac0dfa60f3f9c1229b5d6200ee7937fa9584d65dbdb7c71b66607a2df0d5a02f80e291c209656b23de2fff00850335cb18c4b95076cf66c8f6802067dd63dc3218c1188322bcd980a018670006692397d2367b38e736c3d02eb8a12753297f83fe119209cbd5c5f1864ca000165f3ec4e8fd8fcc069ac240b077f059cc4c9744c2aa4bf57132e87f7c4f42000b428340c8a38401613c42c1be07f6f2442f7371f6e68277a0a50a9544f9ca002d0985d3c52978f356d64d41edaa3f4540455cff997611a403ca76aa0f624a00cca043b80453c661989b1387ca8e34d82c90f599234331bb891e085b1a9c74002e62ec4c707884b6324aa288cab42852e7823eee85d5332dfac229730fe9f5007b8d9e1160e2569b6439fbbeffadba0f2f81c1e4f75c9cffd9a055bff6e3c200a803c442b50cb90445c0c8ec9a7ded643dc78bfa4d63966364abe9f9036f82001dc184029ca2a2830712d8010725010481e75018fa00000007c4ee07c3a0060051238a8cfe0c31c2a478c42cd048810a0642575d88f1d007158c69943f490400a050f68ab04c410b6eea092720854ba82f06a24ade717baddb8ac16b69582800705a4346f7a34bedab6bcbfa6c9c295efc51b38950f3989fe64a15c57c819f0049520b4aafc2d682a1b358669340fa5a71a6a1fbed95b6d5bea02133455a0f00228bc3b9e178866ed661813f3ba52625bddfc04b5ed49c52c899f619ccca5300c4383bff12fd01a0b1351be09426b979d0cd9862bb2a0a7f7ce038a59f116200dc43a7ac027c736335a00a9b763f9abbd2c8cca809bc17b0e4cb108f922ff0009c1f999df5d8c9ab23469f30777de552d8445bada67eec60defee244ac0f5f0055753ead9bff0a04ecf4c5cc78f628303610cbfe67d206a1d1ba54b2be7872000bfda06c2fa8b73eff8969635f7440d96f639f9dc282676eb57e9bb0fa2b50008c7d4bb91acda481bf177401a058f645c4f075db199fc2b9237b59e52c7fea00789936cb5632ef75c1dac0345700a05a22faff4b6bf7a34d9e110c3533f083006ee399c40c0bbdf0bbc8921254910496f8ad8209fc8401086d71914aba54f000a7c82800b5348134866925b7256b3f59a8555540afa6bb8fb7b446facd6d9c00afa008aae5c2e481ad938a82b3e6c9034da7eb361f1ea6863842396903d611008a51340e80840262577a8301cf584a6370dc13a9a5e63bd4edaa15bbfbf343008f82a589f2a8048baa84f261382089c1200cc50008c2341043310c790313190080008451144892340f6c7b14000c372e1a1410281c2c180e0c0e0a1218380e000e1810078302a260582e89e23086213112ec50ce3aa08cbde95319d63720090000a9d4dc185faaf02dfc6f74d8570e8821dc572def87847e925df12bdfe035000f546c2479386a0041d1c203187125329b2618d2d9abe7c978277da9bb04b20046a4f732ae24df3262208bc2f07534841083ef14502f451cf82a76f630b99e00750b3ff9835f8954306c44453c6bacdf4cf16b4dde409f275b7dd9dfbfddbe00675bbfc205c2fc60817fe0c089d1eb4a70efa1fba644ca860128ebafe7743200653bfbe60e1add996fc691b57ee6a8cdbc1e8299ca9bfa89952cf0e58aad1c001a54dc100d3c0e0ddd50dfe26ac1e42a8133aaa0aa868106f8b78fe02daf01002a16b55e62695e17bcc363017a115499f3e0c98b8bc51f51eb2144f3ed90b800f34ea2ee06cc10f554226cf19e772b1f7f0049c41c0aacd214f8a0a5b7595500e3251530c7e2f8dd6e475531cb38b2337aec73869b04b56158a1aeafda6aac00b529e8392b88c48f856eaf337c3be8132764df9aae79dffad65c6e99f1672700ffa6390f880d7ca6819c8d62b64f7940323e011e1e21d84038ab82d618672a004319c885c30794aa6b70b9639fe5c09421da06611249379d93430f6201f86300c906d06d20caf8f69e07e0c639a65b08cfd98da24ca2797a1d38974a0ce6eb003cd861c37f5bff3dff40a0c509b999a57b0251eaf301e8985c362006f4a3f6000a386271b3a421ea0c80521e001fd05d40cf63ccff51de01bfeb9ee0d7de6c0099d5e4d6da690531ddf072a501f02d18c8ccb0e90c49c1f20ecb710d77a6bc00cef1509176eb0de8348800461044055087a34ba8dec421252a03e47a71e2ed006b31e84083a003c0e4238c389e4f3d0e80f7a18677ebb309f807df36eb526e0027f431cbe9efb0b7a3b5848fb3102644edd8979c1fa23338f81a848995f53700c8fbb7ed2d7f400cbb2a067471e479ce6d1f837b017fa652353ff832a1781400e804d902a154e0080d11560c10631ff73011440aa5d0491f77f2091a4dc55b004045532976300e0a494c61ff0c5cc283c2a862bd8631dc45e373a65d47da2100b7847948d80acf73d1972d023bb48bd2e0651acf94dc707f0700ba581cc6fa00486b8a2764b709790385e8059d0028f0d5ee3dc47f23f0609186c10786396c00360a2b59757053bc0cc7e25f37382fd249991029748642408f01b1716a457e0049c6aac52f9454cb4e63671ff1694ba5c46d5a7ce7dafcaf6de2cd8d863e4e0007e1bbb911c5433e1ddb307e486dce50e33746fe59b400870e041b6cdcfae900c6f2c011eb127f897c611e6536041599cde12b5db4cc53c5e5d948197604ef0094e8bc85d032df0ee57c3c63cb8a74ff48bd5f4f5a031b0338b501135920c3000e10c86dccbc96aab6efb34c24e4bae5945d2766cfae43e1787190856ec4eb00168995b86e00e931b423c3b10ebb5843ee70c191000a316f90c326727db026002ae8cd2cc15c4d1b86ab2f12d6f93d006cf360b896ab46319c1d116dddbeb500bcc93daf307d33a500eb40827cf131a7e5822f794830acac88ff9030a8a4d600c9b545bde9e3b6c0cd4f18462b0bb6fdc03bea6bf14a991a21eb6ebdd5b7bc0045bffee1d080532edbcb9560b78a592405b50f34cd562d5a03c7ed93e3929300c0145291c3ab816239f0253a76e22d74a3fc34be68348e4f7b8e7b0098c36f00c3cf7cd16368faf7c1b07b6bfd0deb71551a78afbcc344071ff6fbddbe7f140003f180a6228f6bcb5b0a1e95dae4e0e56ede3cac7f547aa63d194306a841e0003bcec9d4697c56d8b23c8f34b0116851c77fdada5b87fb9c57b7a04d899f56008345b44f250b3a29b3ad0941c54e3ddcfbebe702ae2e4d74016ad3a614a383004c8a63b1a6b758c42bb083459e78a4b0ec4fd1d0d473ad36de66b2a072f20c00c0f9f3b735a4bee3cb39e16a855a61f37f745ca33a316126d5d0243ea1dd02001a76313e79fd8c607c2a56e88ec916db38e0374929c4eb36459de4ad80d6e800263ed1dea7978fd66b6ef003bee34fd8f5c68417cd88e8d5fbc4b6fdc81e13006c01220ad4c2c07dd43cadc06f7a766380d9224d893c4b21c5498c1fec3ca1005b904af34cfb0ded24f84a6a883db4b970fdab483d0f5e18485d50a2e17fc90028219e949e1f2119183962f69a54257e1130d854655ca70469ca51fe2c972a0086226bc589990b0a27021f7eeef9831fdd910718416c05ec1b691898990824001479c0c6643bdfd387930f7a1cc7851b68e35649d3a894500d97f8db192805008b56e1d88471ec9a8018b7f7c0c82b37c91dace54527ecb12a088f90b7a69200688fc308f93a6b06f02860ec528f2e4fa9aa63058b01e9a8ee422e444334a3003a240d6e60aa9638ad5bbf551aa3740f0a84e453cae306494e5eaa722a9bd300ec1e3eb46c107dc31d5f1eb13661bd9bbdea718b13f93764cb57fd94e8658f00fd64232d419fbd3086b1f4eeae4a6b700a40ba92a712f6cdc84307fc113ec700ae18348628a772bb39786edfb45007daf6f521ea91126e1d1f5f9aed156f4400fd80a8d75cf61048d24353ed55419ec16c9b1eb79c005bf10045fe6c54b4f50043333b42a81ecfbee3ea5b7505bba6d1c6807a2c0662ebd1a75d6018962efa00815f99703b948b6c91837f9140adc3abbafb21c6fc0eb0bbc776be80bfdb5e009baf386c6c981e47b87a6976f5c83782cc3daf38d0de0537c0f7f0de70d167008e7a94a82686999309f8d8dc88a877aaf3401e182f6ad52ca54c7c206a95ab007b10495358b17e2884b23e5185a151401c3ca786aa3a60eb3f00047b05b4f40094494d8e3f54215024021044b262e559facc20d110603d200b02774a73db8100f1c3e1250f2e2c78d3bc2d153d206fc02b9e53854cb161124bc7b891a7873e003616db535e34b9ce70cdbd375ca0767070ee6074b77e006dabd274c3cec2f40000a11a438869eeb58dba69d42383e5c80521169d0a2d7bcf1c273c5ef1c05d00170886c838ac33b547816e410fb8e15c3e7d37265e91769c67fd045301b55100650abb8c2a932afa5f456de027f2ecf1d4fa97154434ae9bb6d60d4a1e04d40089c5e34224b217b3fac41ce549d80db20425144b9a574fdde57cc07eb045340082493623ab1fca50ec352c4cb3036001406d3065c2a6869a01d5c1b42e6c6700ace405b4832d82f889c0064206a274c75a55f71f3c9070f89435c4aa0aaa6300b0c686d712709c8347e83eab680ab795b4b401ffa0425df68ad62d284236820092497ad5b53a4c32d66f1c6b7d69e864ba25ec06dd940e6b9003ec36b4c26700e5fd506325848e9cb973380173cf3ed602a098f8603bf521889932df4dcc9b00dd5aca5cf48ea5f068e06602a31ab01f1196aad7027ceb0115023a4977f20100a35ed1a2a34413d52f8718c22c17820aae0e9f2e46edeb5bf48ca783049a20005120ce089e6370a358eb3ac930f240851268a30e5c4a3c3940973b0181a6b800a9fa73b7618ca0ae2cfa114b2e50ff109f4ff8ef4a1d31ce29da138274b3ba00f07f4f227bdc47de33c94e6b2e0e6e15ded6ddbe26c97c818c3f5702ba0833009dc44f289b0f0447896040d610e6a8819ac48157ece21e52c3d45e1c4e84f500a9df8a28abdbe2b45c3e13b876500a6bff5b6b82be5f0605e54c90bfac1eb100435789e4374dcefc925a077bbf45bb41eac038f07869128da6963a90723a0d001d6876c97bd0b14e77e9a01ba6a36d487738eea608109e88f8a387ef94b9c5000a124985ee5ca3538ee188162ad302068fbd8789e9c144c4e048666d7ca12600f8ee32837ff43f52f52348bc68a485091fef154fc5a8fcb1a60e359a6f20ba00e64706afcc438e87b590cf5125dc315acbc1c7000f047fb7480f9b4914e05200e8913e10e5301ed31fec213a2082c6f49105fdc7327a4f67b012d64eed0492000edc4735ae7f29bec2809f7e22ae08fb5228e6bfaef922872caabc6623e1b400a99313779ef0720c04ef04687a0fbab0f155b9c04a8fd5380801a0cde0fd58003605b9e26a10a87723f600dfe09f2bb7e60abc8e109ee841778a7b59dfd398005d7cfad1d4b8d8b565c33183bb3246a2f0d7aaadfb4f3179cc3ba24985fe7c00b93e379e80723d1c895cc4a29b60441f65347e41f1b709946b1c48ea033f51009d4fedd053a64607f4324b7e918f412a8956fde1368debd74b05a693d936f800d82f9058fd48362a67a8907500cfc6f7d92c592c422a1b14c1c57da20a960700a80d1f300e52554160d5d0a4eadea00011151b4ae71f0d4d24f0d15f00a0a8009f0499683d5f5b4d018af777dcd122091a0cd068f4c39b342c400f98c0621b000a2b5df8d727e1c4b6cf57f74d7c4092d8690ed28f4bc8b39e1f6ac0e81c6f00a758455a32329ee21064087a42a449425e8fae095ed44488da9ae687f0f37600f733b7a56da364bfca6e15baaef87cc58b2548b890c949d384b86d4ece069800637aec621fc2b1eb03dbb51fdf8e3d344c04523b0243483517c30d5cf0036d00ae15238e6b80ea3b70d2687fe9b9ffed395a1907be505e8f5b4c902211431300089c8abb502b3b6d24e20695e551232708aac04a205c12ae2e39a855ebc4d8001b1a3ff04b7b030e2334435afcfedf9177cac83b8f78f26dc5b38351378b3c008be8c12bae6bbe6649f346896346780ea795e1b7a1a25b47276f182342a60d00577a23c2b2d52a0dcd7024e3c6c7e61511675a3fe26692eb636f808073559e00998e0d73102ef0f58130f7e0137ad98b9c13557b704d151afdfa17c71ab00000fd54ebf958968745580511559a9bc0b6da991ce06c5db6a3eda07ea502157600c61a28d08b7248b21ef07226246798e266ba5045811e84432189e167cd483900d91514947425e57314187c08555d0d692978e0c19efe14c4b81b95c10691a600571d45873981e0089e76f7181eda2d850f0924dd146f4de97ee5e941788cac0006467324e43abcd5d2a032f471c77b08257c367d9a5797889b87868720af6c004b7a41c6ab810dbb56484c7bc5de3983c20c2dd7051103452896a2e0a4414f00c93f1f0c8106f9f7948c78138dff65b0b2ace5e270742adf911e0ba0a9d63c00a589926734bbd74e06e2440da8e01f6bdcdaabe020b326d6187a1c4fa0110400e44b345706106b95a06d0f84e3c8f34618162ab07950e2675af5480d1d38440036ff7cec3e55ebe7421d3787d3ec47c84d785d25d1f1a435aec6bacaa3384a00a09698f980863dcac0e22ceaffff6cf8fa7fe2ea823d5b498e6d3f7e124405000449695bb0de97a4203264c3457fda3e9a41c1f6c195696cfeef07c33187bc007b3d85e3ffc55d62c505b5e83a234f1ac30d5ad20a87533de408a5a546dc5a00ac50125b051080d4b8ac2ec7b1c54c6afd1b092e8346aaeb611fb8cdf0308e0068914d4c0867864b612f9c5af6833c600cff17601b6464201f873820306f4700f272319c29b921e55184670418da01e2131c749605513e310699a5abd9202b00516642fdf09cf5ef02e5e6099edcc895006d077c3a80c92c0788ead86911bd0093f8d20105e1679c7a14f2be6c4b3c04e3cf48bfadda60d03dab45decb440500ca4c29336678367cfd9d54880ebcc9e6ac748532f530fbc019428e376e70f2000ebdd453eaf362216d90f11c103f97884b4110fbcb1820ec90c0ae80711ab800203c8ca604077892410be5c66f103dca2cfc0437884434db0943e0308311ba00f04541d86fc35ffd0756e5e916bd909a40fb37398366a5dac1c167525cc5f200e31a27224fc5d09ed19e9f72fbe3327ae0f812c461ebe701b925cf01978450006d3b42e3425fe3302ee429e912b4a10fa7c42518576668eb91308cc9b118a800f23483e69daf867e5610c1b7f9e15c7833b610b5d61c5f99bd874a7d55456300b7b621440f592a938cef87f9046ff3a667a7af8027267f5aa6170065c19fb8001b65930c4aba7c2ca4bf7fea9fed40b41b61331705e83ee5f99a7a854dc0ea0018cb705b3f52156758d70a1a86067cfde43c346017edb67039edcb6da4ab7200118448092f4f00849f15bd66881d563498c7a9160625f35f7a0766c6ab360800b9cc1c1b45ada198ce4a6e3b240994be0b8776a8c51da751fb7ab6188b42080072b268b597c11b67c64de523721c06d9c70d824ff94c110a76646bf992e99400010fb3264520d529876fd305547b1c94cc24f74851eafb12ac2c9a94d8ab3700c501911acfd860535ec84f6614e01f900f5b29d51877cf8084decb82b480640028b111c74cc6d704303b16e8468ce04e2ef9e5ca966cf4d321b24305226781002b8b0b2d7a25c8ea33fa5ad9f82637c4809138c6cefe535cd329f209172c3b003e3e9f34ea068ca1563f0635eca48a402d68b0d56421087aaf7328c419a0d900f3322fff604a425803581a085d9a106c0c9268e0aa959a6e9fc311012cbdd1007c00c586d356c88c37f7a9524d1f4d41fcf3c9990ef18a5b06326d6e97fa75000e7a7d0f7564d25130bcb72df12df5cf4f48d9bfeff32ae05a1241224bb34500aa1a928df7f81705bf6e1a4c8032017618f9bc39a883e67f0103a463a0df700007acc1ba9afd0c025ca0db9ea6b8415814bd30501dbb03adadcb9ac1f5288a0005ba0958a772ace1a009f8d7011dab058d7cf0b12fedb13233d4508d4f232e00f811fb2bf4b64f6f6ac234bc1723e5c318264a053107a0e8bdecdd8398d9ad004917f78fe0b79cbfee874d9211f3c74f0840941c4e4487d060e86196310795007aec17274d8bd9b39325b5d0e290046f07ae84cc12ddf7f16f30183cdf104000651a1214866d0c22a36526b9242ab001e55a13e598493737ac73d1034b134a00112ecd693dfa33c4984546ea1abe54ace0d03fb2fe394fe4a68d175650be5300c070aa5c5156d1a9e0ad26d729646ce92d768fb1e996ebea33849af875024300b1d5139645f20c5d1126e2b20e775b586423f5d9b9b5b763a53220667524b2003c5bd3ba20f107437aaf2e7cc10c1fadad2b9eb0f760a104bb005cf253a6fc0085add00621a5dcfcd19417109e9ee3d151e21c52807e1c8b2d28cf8ac17504008dc3ff452a4b7223e70c13a86f2e0a1486678cd27fc3f35953f43e4942a07f00d0a6eebc38d6c62aab0d95cde9a9a30a1d272326a592bb5284cad5e26e8fd300d761e23faf5198e0b97e6cced4246cb9ebd001c15d7e59c35e5b9e618d21a900339d5c7d0ef93f6e14eb5488599878849bbb48d5983786206d01fd1a9c3e6d000dd7a7587e06fcddf23e8d884ac78ab3761b4e53c2b91c628bf46d8ec160400098af80df3c8103bedfcf01228e5d8cfe417801e233c1a38cb2c4c225a3879d0062ec62ca5ed825034a51c21161813b62ce2d489fdb5d8858456836871892ac00fc856179c7c02777668b3037d305f9a4942dd831c757b681f54788f598d6f9007df0d0981dd78f19bc1315886ee8e41c053da03c6b17660ff7510c98c47bba00ab1f7a80cc0776fbbaf92901758c18023e190439fccbdbf4f08a998bd83e1c0033879e9fb391b9f8beed064fd5b9b88783c3ccab7cb6c211db8b308e155677007e0515700627581a2cd4706f37112a377168700ce11fc32718efa7e9e9899400d7718f10163805e7937b5b68d5c249d63be068fc5d17bc1d3850aa61e54fc7006b836ce484247b5e61d59b53249ba864b763cc0d576617ea84921fcfc303e0007f51ef96691b6d42a7845370226eac1819057b877df85cad429ea7c9ee83f500e58607a50e88da9635abc5a5e0b2844092109e7f1454d53a55319f28393ff800bfa8ed8aeb162666833879f7b258180ff64d3bd8936381f09828464e5ff3030068d4f7b43ce1c16116ac92829f7865698f10ac22db2c81885589c4ac8676cc00e80cbb209d15485731b84887a8807038074f10976b1dcbb6e2300a37af1d2400704088da307b627401bb74d081d397403a086f780f61a6aef71cec25506c520074f90a58d246ddab573d50a65c1c651985bc159178fc9977e76a4084af706f002e53730e85e6794c8352431713c62070809823776cab4bf89592cc29772cf600920170fddd285e904d7c01e3460fa53123c28b0605603428b560acf1c5ed17006003a7456236a0af15f101625c39c2cd37a718e3ca7cd62ddcea88201087c6001a371f4deb3ec932d540e2f0569fc3cd09afd1951c283815fe0c833b55efb300ffa82d8da204ac1a084206005300040006edb13ad81d2006ed6761bdc38840000021050039acc400681a1c2300018000c080a063aa0c49b19bbeecac8ab6dd0093160ca366fe8af97df720013364736601922c1da043a4c1e625b6bf42e3e10081eee79c08fe9cb3b981be2783d33ec6d193dccbb5c1f9010c0784010b1260008305e4709421366de9707a1044e351280f085821c734791cee80b8a47c8b2e0088447d07bc5c22ea35d3efd38a87a85732db71acffa28f71f56a5d00bb8a5000bb3cbf3500fb1fa69b3879892566e18856830b6eb85ab7573ee189be598d2400a7a3b7915a21a0679fddbc8a7567ff207ab7763f914b8feb56a9ed5d59cb410008c4b7d164b1e1431e3f8f80044a319ef3f18ca3ce883027f6c7b22fe8806300188d59d9c5c80a25ecdeae318399059d69f9fa63b98bc21d7a9710a746a47c006ea00930eb7727c593f2fec61a0826288438c36cda0a4abb8d45ad7b8771930084630102f902578308275048618401448a5a83017cea3308a9eb9c057009ee00939db19bde0456fe36930df491a604ae4da4630c13cd791ee832febb57fe5300d532bd754979374001a09544db046f7bfc1605d973cfd829208e6d651fc82e00312abdf61efd38b997cc2da07950789a4b8a2ce310501b927a8b88abcdb90500a79d0b970cdafc52a55e65a242f86a0883f0637e825208945016bc97dd402e00dd6e571818772c2a71cafd95418529ffdbe04380fd60ad5bb5c449c1838ad800d88e45283593daac72b5e9cb097b58a9eb2bbdaa25a0564ef8f319b287088b005dcf487d5e70e9ee93e6e01ec30d55d5f99bc45f330bd41bc69882d9a7767c000af5ea127d9e81792fa0f4e1d2f22345ba1812942ef5d8f79b49305f35e6ca00a04b8995830fc2543c597471d9ca21ec0affabe586f06f29a5b1df1d266468008479378401207a58952a9d44f6457c749b1e4f6a16cac00060047802c08a81009febf3e7697def23fff91459f00000759e19cfce47ed54a88614648dc3f19a005980097007ddfca43295f4c0854a87623820dd9f2ce4ef61bee265cfe16e8d001e0f10f4008ec8256b7e4a828753bb8b46fe2f70507207c2f58e2ff5edd875003c5581d3ba8a7c0b1bdbd8fdfdf2b9f579bd772f39f626f0b015869365e9c10041388734b0a4403a1fa4a0778a8b0fb477f7727caa6e0c7b4266d2ff3ab2b20056f63866c8c6a50aab480dfc38446755349101cb20c582c224462c3912d358004dce2855518a7e72195f3bba071f255f9f0c6ac1cc6c2d11fb155ee02721e400abe67f19027066a3ae34e58cc671e65472db5b21e564c3725c9ce49966cb5f0052bb2e1eda70565b4bae2cd9b1420fe20b21b43d71bc7e8734fca9177d5b5b0041f71c553fa075c7156180921b21095c2d1374c6b28326a34ae81f694c74d1003e7eac0665c947f86c82021382f02694b8af4fa4feabaa02a09d146e4f871e00a4a0a41c048084a66f42c03a42694f7753ccf7c72deed9b09448c1c78d0ae9008d48aceb1c99ea40a41ebe0fa033e75f66f32c662b84448047ba0be38988af0023dad65b8abe28d85c2eaa23b4881ccb847eaf3d4a437ef56d510a4affacc000e5904be48c3cceea8a9daf001f8649110fd340364b42d79d1d09a034396b5300d4d70a84c838e03223cbfc2fb9cd481eae414bb8407523fdd7cc6c0803b90e00840802530da620e8947e2049fc7f4961fe41c7e0da810f07da1eace7f044af00d081f8583dbe9cd3fec001a03f281e4f7e8f3771d260892afe9865f470735d00ff972f37ec66ab11dd54167a7ea03c6bcb12886e4d71d5a74174bd3e4c3b4300d9eb8f391da76c12b1ee11c920e22502556377f70173129c18a80d0af854730055a76aecbc9d1601c001a0f014df2236ed0a7a6ab861a2821f317ca5eb6d5000708eab77a4162e8a6a5932c2a05114e255aea4c5e6b23f1ebd3c5a229ab45d00b2dc48fbab0ed1e16606ba04a2410f8084025e66c08301d024d14f14cd328500fb8a60447715de92fc37279e247b5aea5775959fbc2557cc8789bc1bf90a23009d9a9100c47bf4831e3c0800c470e9309459a501443118e1386e01a081e7a800da746b9e8b02054131d0aadf1f45b3c398a54c76707faa27d8e7a69d43a05700bed942ef51575626d6acd2a572e7bb5b0b12b3df07447db842b103a1651e2200c38402d464281499203e4bfe6a369f74e6d4d43bcb78608ff34e8e131ab82100d89e8cb4e945d9a0671ed118ff87ae7cab6249f451eb5e22d91985e83b164700ac3ab02a9ed4cc8903f9028f82120d8401447f328398968080b902243b635c00e405f5e1716c55c2d9d9fc1e38465e9301db167ba1483ab900142ed3c5757f00141f53a5e3fe51ed055568655a1be46a5d5978c507a5ea0dadddd7010000b80002020701a3f499aa090100010200060100030201038c5152230600010405010002060001060501ff76c662b1e25cb67d7365191b55813d8cd3fdac025304b1003d606c6592fe26d9189534d203386dab6511212cb719af0c005460b292f6d500f23accee551b5d1bb15bf48f5d3b09ba81138cafa07ae24402263f94caae500042e33c4fbb806195bf96ba623d6e00a0bd53e8148873f8ad82120e83f05cac0080b844a9059cbb91ce256e13c52dedc978499823c7ef720dd0be0f142e3fcb00701e0d0993ef3678ea5f71344e48ee60b34afa44515035751be0c8103df9ed00625968692609a079e6017be8b7a735369fdf50efa452eedb3c9a23794bc8ed00300512c467a2060e02f901f58308275082119183602990840d2523288311150092947ca0b75e67e33c0014325b739a8d019c4fe445f080b901843511b2f200000000a959b5a66d0e33274ea7f2fe975cb8603557250000dce9a2afa75eb6fe007bf48b4713ef0ebec5fdc977b1c26a1dd42f5dc3235760ca00010002642ec000c080a07e296e57aa2051b01edcc22dced84f3fd443c40a761be818c3b9898600393bd9eda04ab04936c231aae38e74c81eb6c5549a9fecbe439018086bd9de00f5c9caff9e9e1db8f478d3415380a0fbaaeeb0e30fd2271d5b379b7824684000435868cc856bc7dce829830343cf46eea06ff17d2020663125bba39a836dd20040f7e44af3867f3363f0410baf420f243046f90290831697c408038402249e0059f06e0a053c5da2052b76f9cb7eeb80040dddfd2de8c6c9416f0ce9785bb2009ee281e0039dc8a2013e17e3111fb55ce1641ace6b15bb2dc8a9cd0db93f2c00032c9b8f973d09c1a9ff0ea27a217b3749190d755b933f878f807a35067d1b00fcdc53476cbfea5dcd878efdf5c9a03d900bb25d8064218a984f86bef04979005fdb5c474cdd08c3000213f474cb4e65a983016ca056f62dccfbc899c41a8d00fa526ce4ac979254e594a804629570cf23f388a0ed6c02f90458830827502100830767c7941231deb6f5749ef6ce6943a275a1d3e7486f4eae8801b353cf4c00bdc8a3b903e414d530774002002d5ef3bcf095add3b7f29434a6897ce3a9ed00d744a101d48b45c341dfba90db27188b44eb48bf556e579ce469a439169f5500388201b33519d8fc400a737461726761746556320f6a756d7065722e6578630068616e670d1eb573c188a3c080a08f79245ed4f53283f667e8014a6207ebe300d9e7beff01747829a6747ef5c67768a07c3b9c5abf2040596a782c7c252ea00008bb8cbae764333e3eb9476ec2797184162a833014c884015073d9c45cc85100d07d316671d763bdb227a6dbd95d0bbf8080c080a0bf2fc9f7a565b56414b3008f42e7239428c2caba4c7b0f554d68e3f37f79fdde3ca01e63d05398d3cc6300b472f83b711a3128db83a9441e0aad022552b5c0ac2dca36f9022f82e53a8400047058a78303874ebec837e0423400703a0c6bb27c03f21715de7c84f1642c000ac7df9c6f040d6e31e1dac0589cbd5e781a10c888a27345bcbea01162a54800d50db00b3df6d31d845e172c92ba0a64a85314a02efb6ddf30ea42f3f8702f008401312d00827b54942fc617e933a52713247ce25730f6695920b3befe87ea007aa67b2d0000836252573c82fa00e5ed78be10b0c24f4237335af137a37ec0003ec1a69d3e1befddb59e1da00b02ccc52a880d9440cb0eeac30cc2070af82d002d8fc1efcaca42597803a4e7d9f8aa418403a415478305f89e94058e99e56400c3a600bdea7f082aba09a877023e0e80b84202c39c21c6187348fb54869f10007dce8f66137f3b3424a4ff920c8235030001574e0172efe18f9e7542965b550041b67c46270f6bd213738235010001579b0100e891d20f798666159d5200c100ee8cbeb4c080dd015cfe5462e892ed0d4787f3b73b58afa01b26bcbf94c59700ec4a0b838a9d8b453accb7acb566ff6a24a2a401cbf9466db3f9022f82f160008403ec5710830458f3a7a27cf420f4a60c21381b7133e3de5802f996a9515b00b62cf820bd33da99ae21ed7c67299761ab46515262a0453eb8e20e3f60856c007c239a02c9828c6dcd33c108944aacab3926413a2a1375398401205f89c5480095d2cd0280cfb3f2ab2721470d0d10c3ce058a1ac8feb304bfb9436de1e03f00f1db169f1a8f41402606dbeb8841adc487bb280bdfb416dc8ef778ccc64f7c00f6bf134ead511dc51aea763323993aa60466daa32b24c758ae97bfcc921b3800ed085c1cc3a013b191c9136218498449eda9943ce94ec66cb58d7fd5f84380001b77b3cf8f4e51a0539ea5984ea1f06fbd5160c5c1426b93ba90fd847f91c900086cb2a2e02da3267c3a7ffcd68accfae40180f89ffc295ed401a3c2a398f60016dc5ed675e7a3c85d35b77e33120e097cba18770adf3efb0001f999c0e6dd0089818f10d8467bd637561c451b5c0b2fd4db810eef972184c21769cb8d3b72001cec35e805b3dfb2c3565c451e86a022c67409228dfba8ee5fbaf0c80675ca009b92c50a755b3a4ada5a01e835d6e8def8aa428401b46d7f8305f876a8ceb50007509ad4af9e93b33d86425574e813f19b859c00011f9513145144205cb40400f3a9287f7aa014b45d0ff484d8338a2c1cfdbb74cdae0445db96bb0112a91900dac87f26d8e4d63764bd1e1c6a76cca9ba0939010c77e771115783075900cd0024b70f55b101fe324a2a839b2eb40d35518f10cef530d2a2594cd49193a0790005e8fe62808beac7e25a21b7213a1656a0ad239768c3c1b5b343910dc7b2a10002f902138308275081a78201908403c144b48302da37f55bec9cafdbe8730f00096aa55dad6d22d44099df010123fbef450ffff5433e2b3d8211706e6102aa0094710000000023a1af6b61c49302f59ee58cf255954cedacb4c7f539fce21a006688af0b59201fea0f474ba01131937f77e1dba751252951c022e6accde06d00bdef8e7aff18653b3979696e5ef9042d7a839107f394aaa78e8c4241990b4c00e159e105da08129345946a80b903c4ac9650d8010220032000a40c49ccbefd0002176dc1d27a681b66e500000084fc6f78658c3db434bb1f673003233d5e16001a8cbc6eda4409ffc4f5f8d3967ecff87b3d2771c8769b099dcdc3f8c44bf80056ca77eb3fefe3725dc33bccb54edefc3d9f764faaae8378809bb8815c08d300c59eb0c7d1529ad76900000000002442966c68a3aad0ca7171e1ea47aac10d002f500ec09a12c92e3fb418e1cf169f8f418f1880a0679f9af5ec3e273a79e20086cb21d89267c0c1d3f42016816c9505cf003e96c7651e9ed67af31a9eb65b00d06dec9ab0f33405aa7caf240f4b6377862fdd87ca6cd4489c3cafe7a9d2a0003c57505ca7096d627ed83fc4f958e1cd809f9557a285892ffb9918efe276a400b02874f34a549db30b54872660f9564c8db6193890b3ca83ffcce0362ecd6600315f6df8886b9af2d6ab32d7f534f2d478dee3d886806ca079778fb9edd8ac005dc68fbe65e26c86a917d851fc824c4572b67a3a7a2a2d5f0f8a84039a0b580083047673e400da2810766fa5fff26c25628a29aa5e6385ebaf9af6979c902c000e8421dc4808c98908bfa6e36c7244ff8ba053ef35e8627268509802f2dcf500b97675038ea64f7e7e2078135955f7c9349c7bf9012f830c80708402d415d10083035f30943c2269811836af69497e5f486a85d7316753cf6280b8c43161b70020000000a81a17e000605ec2be5e8341b310b1bf2b924e3a31d7843212e9ed0082b490df80705c58eafd70366cd96dfc89ae4fa06dd334e2f31620b9f4894e0065dcf886adcccdacb039aabd4bcedd4f9b65a6a35271b8000000056bc75e2d00631012828c00c3a063d3feffe25aa6e4718e5f04e7b372f3514b7d611a159e00e25790254085fa6ebea016ef6ddc401eef675dfe0b077807c9eb6e3d7195c40021c7b2d0866275c4fa136c7201421e8480c4a054a784712d81648fd31035e800c3ed3bb7c7516d7228dfd5e3682e88751b7b9a90a071d603ea99ecd90b747700537c5de5c4934cbd03ea4ea686480fec3771a9b221f47300a100001ba8fcca00869d490c5a1ef1d157b7bde383bdb11d1fbd4bf0be8038bd80e7a4dfef1b13002c229e80b8439f762aa033342579a0f56b57232ffeed8a6f1635f3003a884b00361c30b20dfe39745793dc7401246865c88f2ef25f003b9ac3a04220eb58840030c09174d9e303ff3b6e4ced9a96697af68b9e39fc9feb1134dec4a02fdd9e004746c67acfa7e14a0b1466cc94ffb9fc5f30426da4de4b4152b01aff987500006d2bb643618c3ebc14dd26eec1c4a00a09b3db569b6649735017c5d71f357200dff4e5f4fe6f303740486e52b6a598bca051a3ffc0d61aac4ee64f655657e900416e098f383d32d996d65a33c21284662ba1766a0f51bafc40353b650020e900feabc3a04c2b9a20088aaf14253556a0343ca3e2574d1a2360bec862e10ee7003fb06e321aa01ef4f03a226a03aee6eff733120e58a21e60c3a484e5337dd700e4c297c769d8e777016fa85c0fd3df6cfe06a49561c4a0bd722f2274c9651f0079c5ffbbd21b166d588f6cd06e21017752a21e5db3711649a02f34f303704400ea104b7a7b4fedebf2636a6c83b9ece585ff467adfd135903b2b78808e9292000858cfda5ef282e4ad1b737b423be405dbccd64c36e260d8c7b01e27fdc014004305a0440d56a47cfe6d5bd885cde4b6e4f113a5a981d6acad6302c4f14e5500766f1bda796a000226090c39e6c592192b3e3585193b7ade081b80992a035d003153f621f60d8cb4986069d7a3a155c6e2ab72a01fee49d43cda0082584a4800f2154ba759218ad85b5787183b4e86d251447aa8be7ab706c97fecc3a04ed8007b6733af25450f122d454d1fc0c44a464362405ea5fa539711055658a57ba0000a48425b4350788d7fd97f71770463e3d20bc55ba8d4144a36f995f317d0dd00d57b18271742331c72157e6c70e1c4a06e0445e7841f0fc93bc2de9eaf5f8400a9bb290042c543e82dd847beb5bda3df3fa032c4528d144bba78ff95698faf00e506b67c04b14261ba1c718371a4f3abd987737c9e01ecdbe0188889309c98009bd3ac67ae726469c440bf3f7238059666d01fdc7a29b8ea7e70a042943d12001f949a4fe26fe96a1d184d0692de270920b43539fbca47262f8bd5f27d7dc40038a46e483b5800000ff61d1ba4f35ed66dabd1bf3593bb1d42f6a21308a5f300bb91cc64b78f79f7b2bbcc04a05c071128dfc07082a0ff61ca6df4ff85359200dcb708f1aa664a75066e16bb90bb7e013a0120b4c1e10d49cd9bb402faf08700c4a015056e4b328474b9533bcf55f421ec0490748d8b9248bd430439b1891a005c5c4da054d79d65d92b681f1e39d8ee907dec50747a697ab8fa15bbb8eaca005aa58d38341fb3987b21bb8d0ff38d1c881ebcd5c86f1845e999a4782ae7330078e7c3ae3886d4100f2e8ac0a6a00fbb2785bf3b4d532380040a76b80000f0008ac850de22512063c93fa25b7e96a1f88b7b0edaeb94aaa9ea898ae0b7d380005af555af3a2e3bdf06d22c80a4379607f50000001cfb9983f91c8e45ea73d30076d87b9a7ad0128d3af7455aa359f58e73bc240534fa49a022dff9341d4f26001a37ef645e07f5e880002bd0653620e5958c67947d766f1f31f8aa438402c9000d0a8305f470c699155199d74fec1440ac7b4fa8e1fe45cb0e2abb8f15406e00e52cd8b29f0f4f6ef3df178b0ba0128b8498bbe12e07e47b0250da4adff54900a1108db05cd04e5fe815411d0d5d95415f362979ab664e6334b6554ae6e8ec001e32b5966c0e977a8080c001a0137572f7fbba732e836eb11bb1ab35df326f001b19470fa3c3997c5b800c270546a00eb1e36085ea767b057b756e336b565d00d10ad945089e39440680347efd0b44249283819146840dcb8d088311c0523d00a991aa396ca0ac3f1d5850f6d6e0ded4256595a42fe6aa718ae9e8a23e36da00f32d168252eec5445997794202208d852b7298bd004c4b40c001a059b5884600920d153e80bf08fa3647f9cc3fbfa3bd03a657abe570d2e0f5974386a0381700a402b8743fcfe995e27e17b68a1d91756c3e0607de1b6d933e50a63785ac2000a91e77a0073e40e5b6b80e984a769273b9c275d14378f155e46594e614519900b1792e013f894340b6ce01a026dc567715ef23867a4a0b30867b7bc3b9bd5f003fb47d985f1dd95bfc322f026e3b84012065b4e33be0017bf0eee09bf9dfd10004ffb1acb88a6b42704d8432f87f0f804fd0d5e0b60a00d458000075e75f68008f563dc16590e570f97b542fa87931af2fede45b52a8d00e31f8098d5edbb200d4719b61d92acc41f56245749f064e6ab07e121379949c3833d1f14e275df60015525cdb880d97234ccbe13116f690ef3a39c3521c7f1a25d33e04e6163dc7007a6a270e76c6a4c0283af82c3e6a57b37145272f163ca06342760cd7972b8000c41855759da3ded8c85ada67e3313fc24a283c98b98edc883c860ea0bde9010072ac6a4cc38fce675639fd8fa5df0dc1be6bcfe5e884756094c75824f78a5e004a661d7a67d2468dc6ab7165c7661ccbfdd957e9ae4edf9c36c95b1bc3a0de00772f3af0c12d17131d49d6fbca5d7043e859710337a26ef0e56bfb6e8e0b9700a035523218c5fa793a13a5ea1dc3ab2a9d11c8cb9767c57186875dd46f119300862bdf8403e7b5fbdaceef32a475b3d50699a03c4645a4168c28818e21352800a21c9e49feabe16a2e1602673bf90ca7e883117984a038599177674bb0ee4d003c4b7447f66a0069b006183ae7d84a7090d456f4027b6fcec9756001a0c84e002bb67a6c0861952c8522611c7e4a9612edb17c36f808c82c852fe577ff27a0003d3e655336823d2b4862688c7695ef0efc7239402754d556d26363795d43090019f8aa44840319e9578304cd36f3a358021798df778462725f88c546d91ee9008f079bef2c9392832588d9dd7d4c81cdc289956b93a056f8e925c86b17b6d50089bba7bf69a4a5e93bbb73ea95056d7c93026dade01abb02f9041682b894840001e3c1dc840364ab7a8307a120944a295f37a7ca6d2842349cca5560731221006519aa80b903a4c980753900800260000301c000006d52a6843d933c2e2af70085015a1258c70001e59f02050002040708010603600a4f29f2d04f2b894f2d0010a378602f9b40328e91000000049c480f667e79a86bbb21253c42123b9e220093f1eb66a9b3fcd47bc145bbb8aa7dd725beedb637b6d67ad606906010554100aa6e5e963bff7673afc67683562c7a8137aaee1e78f3caf7422386ad9651e6004f85d1e1dd43ffad01c1ead17a9be3ed8cb4ede926c705c54de902c66dc29500b7de49b5b90fb780962aac342d8e720bf804027765f60771bc895b6006f7f500fa6486669528274f5b03ab73757de32abd92017f05f8df300f4cd790a9248400bc8a36200518397bd7560ecbaeab32578840d8800d62bca8694cdf920b9089002387251c1fbb0c5cf4bedfd9ba39120fba2bd8443a20d78787b168d88773d3009587180cda78313a594c726b3c708ece30dee45d9b46c001a07da5ef12315300db39f1e1e4cc19e7e052657aeef42369a198d1dfa7e787e0e886a03b03ff77002e0d7b3d65fca39002df7f8e6bb2d00099997c45041aa9e87b994fd3f8ac03008401bc0eb68302614594f301805be1df81102c957f6d4ce29d2b8c056b2a8000b844095ea7b3e79ca44408dae5a57ea2a9594532f1e84d2edaa4a448194a6e001fb067128049784dc405c52c77998e4ab12238c7ba40d6c94875baa059800b0053ff8c28f7578dc43bd7c0b2f1fdbe854e91790a991c82a662c44c2e573b840004a9c11083036726aecc426fe86d6c5129ccf042f46c170d1b0c2fac50837c008ed340181c06800222cb6561f5cb542fc0d120baf9a00d14e96569b50a8a45008df298b5295973c5ab282b3b0c98e588c029729d83761697c58402d43238f100c4a04ed50ec01e0a8d5e62d96ca0f27000d5502fc5618ba668edd2df2c40cc00504edba07a6c8d9f4af903b7b112f26165355ff74494686a6870e4dee665e4005e535efa3bf8aa458401c2d7328305d945a8cf543b184e97e131c53dc8375f0038b26ec52b85b003d854ad7948fc67e6dfd2b4195627cc6cddb6a03979df910051e392187437786ed564a2d008ccf2cedc82c56f7d1ddeb0bc04cd6ff8ac7c00015eb5945a34d58de6991a1d6758f6695bbe34a13b1af57d0231f73f23c151003ff9e67417c1600c72d4d86ffb66c1da6004416a5a9ff7d71b6a491b49a00d00a5acac5f2189b7ea8c0dcb1594deb51917cac6b16e1a07eb868d11e5b56e7e00c68402d405c6453db9435d72640004caba8fac1dc6441845e3b0543cc5821f003fbdb60087a081a029269c3be6b9fd4c19cede4ba965a4f1a9a9467ce93302008544f92ba8d1fd51b721997e760398da4632d75f01a00306ed599ceaaa8ff2009a30be7395c340dc30002e767da642396c7a65d30ccc46a03ab7c2ccb021ea00282e5c35356488e0d72044c9d87678c1ed360060f9bc8c6c2602f902368308002750066483fc512d83049f9328f2cc6a06028f1b3f55af828fe0626ddc2fa900d331a0dc235aa468dc7c49131b4028d78d145628a345f95b3001a0845f0d35008b1e2494e7cb704d8c82098c62dca221b8aa2cb2005e4d1c6c8eebc3a0074600f9e1c741da2c04fe0947a7564082c43977111352ae2f0da9e82bea3b2ebcf800ac178402faf080830120b79488a0e09d40b021c0590cf106b7f87a909b9c50008c3e2ef2dcb2af9b0bd20e8dc1ae8260e7e53c7c151712011613d5ca70bb1000f663a0280b8e0b586a6dedffa2f99d0508f79647cea47597a28cf5817346b200bf1c843df903dd7d04dab09480b9037400d5a9e80231f73f02c0ee0000000300e860554e49535741505f56a0014a4353441cbd60a0832ef722aa491e80e2890042f1e9d3f54877cd2550518cfa6d36afb1a27500c5217ada2ed1c51f581325001a87e7490025a033629b2deb07198718590517dbb84106527170ae0dcfc1a800d114f33b978b90caf90210831697c78402d3f03d8306dde401a4f124d33ad3007d9446e9dad9862eaded7fe58f3231d54c86adfae7e5d0e402076d3660029400282b01000d8821a140f8ec34595939addf25e6cb1fd6ebf0552b261465a87700f7734a8f8782a82fb00ad93af82e5ea9fe7c2ee4701c31961b1a7e68a046bc00e0c8cf7d6046b30f467111b57d6919b11686c2afcbdff2337998f71af5a00200f90494065e83f0495a02656c948c36353db4f3a2dddeed38405199d13dcf8a001b8280b9042409e3b0ea3043ad9c9e01664bc3a68477f0870df35dc4bff80200a0060901e2a07a27cd000000a09f1028554122ab3c7eaafe6b23fe7c5765d2005bd9096b6c153047f1ba4d5fe1623839d1577958d9b171ed0c7dc1eebb9172002118562c7b0badaf0a88e5e6bf1dee79e8c54f3a06000000890241e849f9cd005b050e1d0309c6af0787c8fad607ab841b40cf425611a96c62b258319c2f0800ae5b6d950d5ea2de08ce7672f322e3648e3350479faf639287b96b99b132660087790ba86a01eeaa996c34bd2dea988b084f144cd1020dd79fef8bd7df174c007512236bf8defd82e98fba751d30da1169f6744d7f5fe3142758c8f9cc62ae003c0289a8ad5108ea31582ed6dfc080a0fc6cfb653d2cae1077bc48820c0744007367e1256a10713263b9ba715290d39e1da0406e7064b2bd83777fbc501b4c00d902a73b7c50353a22aada45fcb4945ec90579c8e700d4587aab188600312d0076e54ec6da9c86f31b7a1cba88fb144053a7660f5ad7cc2c18af545201656c00b656dbd7ea6563db4d2dee2a9dc14517cb63721df75a4e5a81e7a2d43c902900ada54b9ce10788a6c55072668b0b568c95a0132d3efdf1287a0f82ce66278800ce505fde8f52a46ce3644835b6969b6c9afd4202f9021b0683899a8f8401dd006b058303005d880473693f43d5e4500101217451b0d7bd9790314378e994fc00ee7f17eb902093efe3d965ecc8083acd10aec5106f11a078adf9f39374578800d0dc70b808d33e957202aabd11bc1a71691cda805c296446f9013583067e7900103070944e422b0acb2bd7e3ac70b5c0e5eb806e86a9403886335d14c02dca00b8c464e4ac270040005631acb9a95a8d5b6267ce907a0b9f8c4226c3d924070090024ad11b5e2c71efa65f78d2841416450d50a51e9ccea1c4a1797885269500f99ccca070d91b233a151968d92ae98c2eb9aaeabcf1968747b62b270d14cd00953db90b7df86d0584012057ee8252089409ba32ae6f001b9b62f8e349e98100f08aa1a9e5c087019327ef5a250e80da0cc78ab10b969aef5e9bedf9fa2377008d8a8744dba56b02c95792c642ea2d5fa05339de840253b8263df8df238a9b0041dc212e923a6ca34d10fad243739eff7259f86d398401e092b47bc5004df2002f1c1ab19cb6b18f8ff04de06d7bc58703074ec254b329f50c02c7b864a9740066dcda9bbf3096d85c4be0fe59a06724cbaedcd3c4f7b257a07b9b0e0a42b200de44c5e6977de2e2a1d2d975fa6322d22ecc7db76779e14cbcdbf8ac7e011e009bcf8643d6454342f7851dc5673167ad6c4f5cd244748e8e6f2e876d97d76c00e628fdb0f2a03fe65d711863418fc7332ec24b3ecb7976c6094ba889e08fbd0001fc53c9697356f8911182b5859487627c7e586441eef9ee3c28b66662e89700513f338706906439bbc400a4f9068677681a15a6707126dc8071ca5038beae00d0a229d54214ab7b9f4821099c4aaf3fbe24d083f6aceeaf581cacc7bca01f00ba06aa41333f395fab8e916f6d22c07908e6365ecf23a7be0295a5b9c4c22700229250747e393f4beed42abd3e99c6cb4896d83fd5bb0c25391daceb35aa370021d95ed44c08115f1a882eb2a0428600e378b5b28c2cf8333cadc5fcd9d48600c55c4f1f5b90ff541f1c34bffa78f9056d180503ee9480b905048aa74f0515006ccf91e338681a449360273e97000000022076d4632853fb165cf7c7e7fad50092dac70f4fe10100419f4cd6363769f7ee1a9d3f55bc42ca24b716f5011a8100b3ebd1a65b0edec6178c8e573bfb8ee0823b7565b06246502f9267be402ba0000ec1191f7218f3567a70dc6511abd48bf7abc64315b4f21fb35af9f35a002e004e02f86d8401e3a747840303fac382520894275805d5887db35644e3467bb1004c3185cac48d1d2ba0be0802127fc0f25db5324b9dd8884e5bc53c7781c66000cb660f228599d4dda03fd4b7be17a7911eb2877e11f5f92f8ca828773cd67c00823b00134d8844a73ba3f901467f8402625a00830800d0b8de83bd37f900010003cf8643071b6d338fb3984db35b6af10401020500060209000003040004010000024637d496c7c56e670608bd40e0d1f257518ac4204a604fad9d79c573ee00b50abe1e34f886daf55bb040142d21c82a60551cc20f9da04329ce84c5aa0900cde2dcf03aa5f694d0b25a52ed7b9ef5c07634f072291317b8238b886321f7007da2671058d075805a7dabcd25a8cd0d595520886dc9bce31718660782f15e006ca069b9976f623e518329787b2e4b885d4a6c022ef43731d534cde03f370d0033f17d02f8d501f68401faf98d84033f57798305840b9411fcfe756c05ad43008e312a7fd934381537d3cffe80b86469328dec0094a755fa46b56ddf19321a00234962635480af82b8c001a0257ae833f9b3dfb3878cbec7f2734dc3f36dff00d112945dc8f4c847fcaae32fa0a00d7ec3f6ec0180724cee71cf7dccc8b66f0090ecbffe845746f834bf9e3a6584562410739ce6913e6614b844583808be8d00584175a3285e4cb64b70c9017d57347a2f2136f1ac615fdc9797a0480c785500c07c860b351609fb18dbcc7ff197ef7fd4ed1bdd5c042dc3da1dc6a2618404003ed45583047a640adc8937640057c9adaebb0c7dca4ef0357dba181afe3f7700f250b5a428e37688fd722f2baa1ce9b02590198feda0766ad120e31fd7df050004832fb043da912c53a19d688260340ebc890071f50b1802f905d30b768196008402d0cb6a830f6fba94a06568773a247657e7b89bba465014cf857020938000b9056435f104068155a436760681d71b803c226fe6068301d5c9c576909d0100d1a94a2052e11fcce70de807a7f973eddb0d603ccc4313f902024002035300000801000001000100024000e4707ea9bd023faaa32926fce6be95ea2c51cb4f00cb60836d320c42000bb8530004000040d05a83b2d3821d25af2b0992bf227b00350860ea80bad47382caf6000bb800c001a0f0530996c368abba815e15bedb0050104741b1cb52832b0877527b108a714154a4a07968c77223480ea3a81f5f003f25ce07302be76bf28ff6c1cee66ad4203c47a134f8aa468404514985830500f365c8c7e8f4e31cca964be13e5564c3b06684a5e61ddb759a6540f333ff2500b9ab19474a1c4ea00c84505adfea9349d2c7d4866be08c5e58757766217af6005e56e902a05793f73c44f8682b37f0f1aa2965961751395f26c3c5cf30f999004e35e404340375c59cd4ba5bf671e3a9376062b5efde82ec961dde40cce94c00c7ffa022da6e4238f9fbfede77118b861bd36cb66f1de32d9b51a6baf1521900216a47e002f901f611938403c74e8e841e27b410830f5538ec843e256c02300085c5988d161de6abd63c046e24a139b4caed532de4a034bfd4cced1716f764006c5dd237e5a01d69637c9cfee3ed0993a130c001a08ee9ab3f8e0f947777b9007d60ae55fc45b58fe13a8d3e9acdd8f6c1eb65321606a0193da5ca5f539723003ca8eb827594398b4b8a80a21fc65c89823a8598dd50ebdbf903978202eb0300f4859469460570c93f9de5e2edbc3052bf10125f0ca22d8806eccddb2eeb800000b90324b0652ee600400115652636f3898f550b257b89926d5566821c32e100681a1cd500000000681a15d04359a1b9c47115070eafdbb4202f4a1fbaf3e300f7bd1fd6e406ea2519724e02a8c1bc9d480552616e676f00006c27d05a3fed00904176b6c06596e88462d37fa5d6661c49dc63f54e22bd3c69c1a06c92fa82007ec4906ac02ee684900f94a573cefc66fa133bc6e47fa10811391bfa3c840100e3b9398403040a5afc2d1cd25058b844927d5a546f0ff66911e94c9158f0da00bfd126fcf52e70fa3782b2cd2fccb0b8538c33195b8d184d8283ca7364a01f0018d4ed6f73c401e885bfe51c03daf4c1492229f0c1da38a85646e0641846c10002f8ae83082750879c82cb17ff75a4b698e3ec95e608ac0f22a03b8368e05f005dc001a0b2af81c2bcd654ea94eacc32d6999546095e0cd7f5eeffffd66657002998086625a0470726445cf4ab38640aad0d167cf5ae2459d7487f28da587200b3fa81f74ffd6702f901b48308275004c42e7f830272da9404bafd4206d38600bbe2ebf6cc0e9d120712c6abe880b9014418dfeae6feb321c4c2fbc415f3e5004d77bb51bb92e3f1c9a47039ccb97975dda94e1a6a44e8460201c080a0315400f704ea8abc3f5f201a95a5024df49454435fd6e5c2b9307ef9a2b0e7c8bda0007075f0cb52d6856c02ca97d3e516f37ad4391de82a9b381342897059d5402500fdcf8a97a080a0373d00d6d3991e6264caee50ca705ba246c41d03c58e00cb00273a9801f33b27b0a039880fd24a58e455a3f5f789c8636615db24c00bbece00513f0655991ecc62829c341916bb9d4c5d55f86ec8e83564451a655dbe9f63006e557a47be6eb9267e2f6142c8eda9e29396ea13ea7987894a10177f6fae340083a00e23ab1fa4b80272aa52097fc77aff60d4e95a1e0cc723ded32c8c55ea00889098f88d1982ba3c9400000000aa467eba42a3d604b3d74d63b2b6c6cb8700246139ca800000a0093bbea6863d99af09933901b7cfd3c7fda1dd8f21c75300ca2d5cf582fe6765e8bb09f2f1dc40cb12570a1831177a31ee8446c9443781000af7e3730650cf712438a043740a432f154af01a6676076f246d3a9dfcb82b00d7abc14921ef398916630c6df8ac300605623010fa799560152c1fff17eb370052f86af85e28b1cc0836b70f60903f978f1dffefa0294ee36361d7ffad3da60093efd5579633eeebd2e8f8e91b3977fca3ac29be3e0525879c7d9a222380a00050f3a1a6bc18c112df03a4e912ccb897191a8e0d1483279f16acc5f53759ed006da00294aa140102a4a0f883537b480da26567ac5dd2a594f748f973682b8c00a831df26b42082eee4634ef9325c047f63db29429440dd29c221552a94551f006e7a60f2fb59c831707e588d9c93d6a04df43476a15d9c028cd290007f3c4a00e4624db1fcd4a34a99bdb142391c4293b74731fb99fce546fb5b9c600e44cc002f9f1fab4e9999feb1f74f52152104df83ec0d288e842f408fc6bb40566549006bef3cc43c359a77a03c7a5ae3a24f2af759b3d3933a82b7fd622bf83aa1dc00fa2151399d8b217cef0037335197add5e7d683f9b1312e04fd80408865e4a5007ef39ec1bece474def74666bc4a7f44356eb54104964015484d8c8cea2515900605075a02345b8c2c6706c7d443346a3798ac79b2dde82cf3869774ceafd8c00ce4b94bf3af86e2552089462c1b8e7a92d8d94cdc9e0d33af2f575cadb08ac0088015181ff25a98000a3bd844a03c603cb6edf10ef74b6cd1be7061fd15b5700f4622827d0011bcd874da03d74e11b2c747cf0278dd798802ec506c4a572a1003c3e05812f3670d843a0e769f904533109107286271355e6bc518da9caebe100301bd69f75e3a684e2d68c41d382d40a79b1ebca1999b8bba65a379f9df1e300a454f6b963f554bdc58af7b92ee69b06056230a4b101013c34a9f3a395089600583d7cfd77b6bf829454b37ed9fffb06bab436401b49346ea049ff143e2c67006daf453a8e499146fabe7ffb188adaafaf28434fcbfccb9ec3f73b69fd51b6006d222ae6bc0292d477823464b2ef79db9d205bcb712ff5810c3c2c0a217e090028b00d8e9fbefb7298020c8f8fe3afedd7a056975ead7afc066d2d08c7d21c00c4daec031f5797d6487e36c56786f075b2e7fa02f90401f78401d524d184030019801783063fc494c2730c06f3ec562d4e1ff63e04d4c75f417c336680b90400843593564c681a171e040101060c8002e06005ba40068b8772082b8e032b5300040001f4024d2bca73221379024abfa04253040001f453a73bc64a864b9a2d00c1a346dddef139d1402a2f0600400605e0b35884ffffc001a04aa9059c3c48002eed0ba385ad9974f7a84a31e09e4d940b5e0fd08e32d1388344a02907463a0071b47d2f48c1bc2a41b35de4a7b683f72dbd4ac91549a8a6d5c5673df8cc040084030291a0830d88889480b86480500d5b6f50593f8764393023959cedb3cf009a30d7a4161997bd9cd20058190c612310c1632419e2d2e93f2fafdffc91840095c69eb475a8a055dc2d2e2e0031c38fdb6bbcd58599b64a5693195727423300f9db38022c5c237527cbe27fb7608f01a00c534f982340dc7a6ac78578483900ecc8034d83a2f6d84949aaa446362a132adba03593a672c3ebe66a39a2f27a007ed8ae7fcb35aa7ce5397704645524692c20a80a8225b38403c77272841e2a00012a830f4f3994f4e147db314947fc1275a8cbb6cde48c510cd8cf80b901e400ff024c7b071c1924130b2922399de7a2cebc872bc149e4fc03ec592a8a19800027713f92ede001937a07f95aead3e2de8dbd848cb51a11d47cab010020600400a817c80ba43b7400b9b0c080a04892d03f81b9905cc179e3b43631ac4561b40047b03b3754c820f8ce5be8d8b2c8a02c06247af10d8aebdc4df0e554a6970000bf659d62615a6a956a8e7de08baa5ed628bd8c77fc534e80a0d68d9ef01eef00bc3b797698d759313a3310bffcc84caf1d2823d0ab4808738457a06c9ef7e600f68b350ca8158f6c1559ac3fee5ab08993fa90c7d6fee6399847328002f8b2008308275008833f0aab84014772b48301081a94f610a9dfb7c89644979b4a0f0027063e9e7d7cda32c080a05a5b632f7ee5d2c735d64215ca1c141175e919f000b8b72d3a4a2ef84acac21894a00a8a77ee11eeea01f0f696ab4dee1aaa733f004fd18ef63c91f7eb1e5603ba892802f9013525b429ab2683028299b8c4763d0074c60080bfaf5af021b8887782a987be7a0bc586de2d0f320101c001a017ae004486cfc03d863ecc6fc20f3e2da3e54d97550c8a4443fdc728ede4bf9fc5a00048fe9dc1f121ed91788892f46fbd25b02232a48d8a019e0c1758274c967bb70009f8cf83067e7a8402d40a9883056d1080b8642b28b30000759a0a5a4d658300f100ee3273a4bfcba9c695848da08ac4cc75b3b52f24be9faccff61426378f0063af6c2be6b55d1b71a04ef3dbb15777d76573bcb86a580f7162b925311d5a00c66fe035d8d80b526fd59f7b8401205d4a9e0e36d2a740351a2ff5c27da52700fa7cecce9dd896ca15fd29c1190c1df665dbf32fd77aec0e29ba4bd67e36af00a01c20105304fe82253d5752a77b8faac456ec88177125b1fc05385db7b87a008ec87c9502489645cf4d5f1e096ef149c88b61ebd017244df77325431cab48002bf777a5a851e516798e9997f71b3a6231c995a0706b128a24e3539d96d89e0025bb74dd230d308a1200a1a4ddd533d396edcbfb2302f8740bc5c453ed830a00da02949a99d959144064a45509c2ec8008b6ef2fd8e5628084d389800fc08000a0ee0e9b547a86dcae50961fac7ab98f5708482a33c31b42ad01c34203f80e0010a6a07310504835e606714848f82813d4c8d879f89cf85624aa375a4650320084e25fed7d9d0b2647f901ad0ffb278b35b7d43d60fc9f0d838ef9c1c50df7000bdd6a458134944109aaec618927422a7af1fea07d6dee4c3b92839f8af095001ed1a0b8d7c04717501e5ef9a38c1693b2879492a87ee72523b264437e612700f89a30b60d7d25d7d6df5623b3da90f5372ff2e9e6bd2dccb2c688bc7b0c0300a028c7bcc3a0741b85ccc6e3f2faf3af03aa85cf1a10f63ac8819f263b935400ed6d37a44b1e1ef88e8180470de4df820000a02c51c89217334b6398968e6700ec2112ecfe7e1169b14a51f1b9a9dda75d1be9e4a82858c1fc41ced36f8fae0005df22f02f76332c2225430c4937a07bba48afd8faa05c47c80d7e73a346b5004fbb5fb30e493228407819f736d3bfc72444810f6e6775f8ad8209fd4570a600f520c3a083776d69b14043f394255e3bcdc4eaaca72349f603f200fbc6dde60086e6d48835a0318e0c1f677f9cdc4ee95a141dea2a8b71f8ebc33b07dbf24e003e95b5a00a536109834198f78401956fa38302b84b010a75aa0a101e10000c007dbb0a43bb84948f2402cc6acc307d0bd6776fc714957789cdf9c08d26efa30031538e21712d8aa07eb79e3db0860a1bd5b6ea06f49d52ee71e2def8c152a300b5962f1cfc5a840afe02f90482bdde0185174876e8005653502a209a57d35300a7700de49ccc37e39b04a4019768d7a545c6e495ccb220eb65b886ed00016e00b40100060301080204070905019bfcc09c00e41ccf246c27b838bb57ba2b7000542afa6ddbc6dcfe2e233edc54eb84ea7c68ad6a0e642da1ea0c72f8fedee4009e5bd8910e67725a5915b14ff945e0d9e0f3b21070040bd776262e7ac9293e00d635f68739cda5681da8c686546186001c822d918c81e7ea5c573c5966433e00ec705ef8c8efea6ee541f8390c336c3c1a7104d0c10051b8935deaa5d4909d00f8cf6feba939b0399dcd1c9bc25d91746d7d6b28def5b78ca37ba74059ea46002324b70c64ae58a855b7b436bcb0dd9a564d8f40d6a4efb8e2b029e5af6ee800ed7dfba60046bdc42baa93e091cde975560480e737d13fc11940b41af8096e00b8a97692134c9bb9048236735a21d9289cf33f5d6de60b48fb064ba0df373300e51a4b4206dbc001a00f838163366c2725524093b12c95c6f1ae62c772227f00595989691c23ad2a1924a05ec1a9aaffc5ede8396d40131d2ee9f79315742b00ec919dca1052dc261270b02a299b1871da313eb08339c929cbbcfc250a908c00432b2fcff5662d395c596fbdfa404904f3baa38ca02661c1bc72a71582f2d10095e88628966a81579c4addea427e536e924d5dfa5110f9018f83015e3883fd002df7830f4240946d422522e50a76fcdb3e71913736da6d43e6609902b90124001b9eba01148ded7584520000000000c002010127107761786afab6e496e6bf003ebe56fc2ea71cd02d7d0100e753102962b8c2b0d212457c4d0bd082ea72e60000d7f433a489cd7f923ccc5c4ea045acbacb370afe2b1d8026ba6b837aa4f5005047403da80c3f580e8c33b975bdccf8ae8301d73a2021c4816535f5c027ec00ba7cbe8e0fcdc5ecdcc280b844f923cc280301000bb80bb815af1d78b58c4000067543a4a8d7b77decd671587cedba0b3ce7e8e18a49fbdf2c7b65971769f00068d600d7a04a295d6ccf38b95e1dd33efddd99378900a978e600a9976cc81600442edd837950f901ce820f41083ae194c9c35e593842c3d5e71304b2291e20004583226e2a80b90164412658200000000112020027004f00cd00e500e900ed0000f10101011153045300903c3c00d100d1feec4e40c170ef3736dc9a29389f00b8cd7ed1240900d100d1001e001e151a20544bd3dc05d0557723acdf95a90700cf34f2461fa00a8b6f2bc2f1a46ca85ce8e527ee2829e15a4e3713a06b7f2a00c3cc3ffde5ab76d7dc266444014966057828054305f07a602de1cf7cc0188300fd2df660bde431f2b42aade84c9a9287a4b937c8e76908704c3296fd75d012003b9e9387a04a6ddaf669e86cd2518cffb8a2678dcf6ff19328603304ed58010088cfebb514e7f8c682c4f7200b20941c40be10077f686fdfd9e6373c5611d0002061ab3480b81bd17dccd44edd60ad480226e52925f122438d29687c813d74001e2f938f4ac377128810e217b1b76c662b1e25cb67d7365191b55813d8cd3f00dac0202e1dd85e1abeadb925b9afcfaf95bf0ba9c7340b5f40f5388ac8a3af00bbc29d6b2e01f753372bcea7fc3fc4249abf8db23b5b723eb290a0594e1edc00b055ff6f07effb9107dbcaa51ac956b0d218f7670102f65c64073907f8c68200d2edd57df81f8ce3b1399314cf228371ed380b08b58a5bb9d4864c7158ec2a00596e26a043dca809927b2c9c40bfbe3ec7b91356c9615c9df906b326383fa4000f76d530f9f9020f82047b84010aa51283030ced010168831a006856e99f1c00309883655bd08730b1276414b23d6808dd907639267f4759329622bc0516a00045946733cded39135f38710c627f392971d6000739f131a5fc2f8ff7cf949a008d64648e3326d51333aa7f4fa7de201dfd592a2fd2c001a07bb411592fc31f00b13cd8ac022f8023f1096ded3cee1184e57e755add0648ae3da06d7edcad2100cc6fef5dac24992bbe8ff9d9b3160bfaaded8ba77c190f4cecb20202f8dd0100f88401d536398403199ea68305573088060c66eb1618bbf8b864bcc3c2555800896eb81354df5b5f362016be45ac9b6475ee5f2af83ae19ee7731af3d1a59f00a008121b994c587cb9637712677e57301fd49c2405ed9237b049ac185c422f0066a9f902af82120f84032e7335441eb4e9728bdf0f412b4e9e65e00202090100cc2c6c779e8fa8cd033eb67bda038b6ed1de060001070501020a000101080500ff8c4d4f5b72ac658145f3c91a305b7425e0c3304b95b0c398cf6d296faa55001291a9f3bf02a68c7a3067bf30cb7ffaa3af08b24e559eab7592472889661800052d6b1df972087d91c09da0771669761659ab5cc78e9fe4bdc01f192e4fee00a50225cef00465dc8e26e6a8352a7fc48013550e6a598d5ea1e1713d06c09000eef037146abc9d5794b7eef2d8b7d415660a8738bba03f36d8811de8963c350040b2150289582006b571097d5fc5f1818e667dc01fd540f906128083f04014008306e23a94f0bee04e03a3a9d3448e9a7eb6dbb141174326e48609184e72a00000b905a459e50f20c0ac932cac7b4d8f7c31792082e2e8f3cfe99c10a0050000000002440240c0014488d527dc20c08e902f808b3f28bfd278f9953a19314e00410754b5090caa372c00dd50e3144696f61a187a0412210e8a0180084e44ee00f19b40996a14f0e0055f156ca97daef501aaa5ce118b9bef5014bc52154c810041252419034dfb3ebae659b3980b1ad7e715a0586d3a2fa555654f02734a58002f50ade7f1c4f3c6b9ecd741c700ae2d5527b0dbf86c82011a84039387008200ded8c4a02692891cb09d3dac126e3f48ff8d980bda0372345fc11c8c820ae6003878085f9da02aaf459534a9c710d11bfde9046153283a8bc3361da5bc741a0007003602a22cb2f9019083015e398403dfd24024004992274c10fb26c68178004f2bc6bc70dbfb68223694183f4dad471c0bd83c8773422d16337ea00130c300c63d05ec2b0d5eaaf0500206f5dc5e5bbd8bfea29f48d384d285dfbac8f8af0083035e9712dd785c378bf0059edecb110271c388c56fa97a86d1327d05349b001e91c791b4355954d645226878ead3c8a005b00f2ccce417986a55b7a7cfab00a7e387e7a327eb3bc31c7b7b201073d9a281f8ac3e011ead52bebb97069747006313ae2b3383f40d4afd4ad9d3574bdda9e76b591bebc9ff4ef7b87529edbb00f27ef24514cd3eb78c2f131fdff2d3c992a055ca0e091b68169858f736641900431c6807adb6b45a51594a76bf39945a80cae1f8b982db9b4f000121c6a9f700f07bf0080b87761786afab6e496e6bf3ebe56fc2ea71cd02d7d90e34a5a1f20004f5d078be4e3d2b0ddc4a2043885ec6ddb198ac78972d9f5cd94646d5604f006334ff6bc3a051afc908f38074b39b8255dffc00c9cd5c9e1db286df22662f0080a985a73d7487a078d39f360910159a33c3023a8214d2ebd1c9e5ca302b4600168e7d0b1adb9e2d3a9484fe831172d0b9de32ddc1cbfe155369ec7c5ea42700b4877e095960d0ddc6c58b0465bfbcf9220d9c0f782533439e2ab9669226600057c3cebd912100086470c001a002a3ac4ddec5275e11679da4e5681c9363a8005ea0c33c5d4c92663396fd4bf880a021e8626ac4e701e06be370a2e3546edd00fa4a5d3c9a7853271584c4cdfdcf3bbbf9038d3f0714399480b9032473fc44005702d4025d01008404e439ff3307982697eb425e07382ba7c466c9e000ebc80000f0f800f8e800fa27ca6ea5a97861fe50f0bd18b34e8698d63b62995f1d2900b30110a13985f3cb9e68fce7a16f250ae2a94638bf20c74e9044d347e57b820027e1ac915c641e6286b71b000139041f1b366fe33f9a5a79de5120f2aee257007ebcf000fee0681a1746c80b65949d4f50d4f800e85713fef037df882f1fdd005e320e39b6baa8317ec6f0171d1f49980201000300f93eece7db80000000f800c001011a060300f903008a03008a04011e002003013e03008a7160570bb15300edd0ea1775ec2b2ac9b65f1ab61b02015802016c050020f9609f080101898d007132bb7ff880f00100f860f8020101aa0301ae060200700301b00301b302010058060301b503008a600201da0500600002060707c80b830d10ad35330301f40005008003021500020a0b0302260500a0030215eda49bce2f38d284f839be1f004f2e23e6c7cc7dbd0200700202500500c0000506070803026d050080020070000200480500e002000001140140014001580180018901a101aa016c0000400100b701d1018d01000001d101da000005000001ee01f4000006002001be01c1000000080020021d02260000070020024702500264026d8e029a9a02116d9b430600bf5bb25a2070075d795171e251b6dabe6dd98f896a891301007b92a008af6f008a3b334d02ce6f96991df5c3da9ff5024aa9e24b2181a608440508cf1a02f900025525b584fe95329c130c44c07155d4d5893bb280454a4e88ddea6d7e0c3300bf67cd5dfe649a0dbf5c9f0b2d04a1de14f8b3da1d5f81df3c9a0e019f820200470ebc0a53dbbaff18773b5a6d1dd8a0903e316b1796d5cf48d48216f4a03300564dc34d95facc7dc378f83375ba8983d5fdf0005d5e460dca9d010b6b474f0002f90ba98245918402ef72df0b71b0944aaf2844c5420bf979d5bc2cf883ef0002db07e59080b90b37b7a16200067273455477737470756653544f4e77650000756e694554485f46554e44414d454e54414c002abc4166870196a5e6847001004966fd6c56f38dd1ddec35a6fca8fc1d3f2ead982928269e52d2df79d1c0d7003116def75184a8e59c2a104bb547d37a2e245899ee8097058ddb6745bbe1d5007be81b53f557c799c5209c13bceb0c2761125d75ea6715c65e4f4ca54cfa8b00861d3f8b565556ce4a73c154c118c44855a8bc7e6947d1e0f77b0dfb2882d500538ca55c4f1cb4c105b9df797917090031a44f53221851360dd19f3a32fa9600b0100f6b128a6561b711dbdb1bb5e2f86f6e3d5ee22e31a17a1e0522fd239000aebb942ecc5e80521c31448d89ccbc03706e6c8000d4a4b70efce5984666f8005713316860571fa562bae9a7d4d8d22fe6e4d94d650ad70062b0be739d0f920077080a15fb1d57247cdf5d8dac22b7651b0ee89c572e541a0786ddafc96694005a43d9f166f6a4f8140bbf22f08d824f209350192c66aad8929bf673debef1003a1af95ab36f3453bb207e40594ab296def24c1ce877c956cededddc6619610096b409238b17d3d7724441b2b15b36c4041708327d7b7beb686fa2d5f7a13a0035df6e8b735ff77312f8b67414087b111a4d0125705f1b2aed3222984bf10f0060d24eaa9e1ee89ed4e45cc74a499127e87d4a8dcbb59de1d1b7fbe1ed4db100e734c02b82193cbabdce26ae38adb3f7c6c21c0546012805f77fd36df59bff00ca7a74fc33c2489d842239851db466c095a1e258715aa0de1b8ca3208eb71d0003a94ac92c4e746f379a64cf16321ec331a09cc33cd31f66d90a5d24b0187000ce1cd851a9f2f5ac36573f5519c0d27c539da111ac0a508c9322b4f9fcd8a00085940e095bb3b526349e57f29513617768426b19a316ed73e43c72d907611500b1a44aed1bd1ad0c0d03ebd0b40e827f027ff63748d3a3a096a2920cdc150f009067a16c68612124c8660f5ee3a413fa99cfe884a4e8737128c3cfc574c840008f66c23afd48eb0eb357b41cb8955b61ef599e06e80e27fd97e862d527ff2b000a555328dae903ca3b84a6acb51db94fb5b7fbbfe536e9e788e219480fb5f8002a7098aec6f91813e5ab6877d88d1b4c5d05a2f4616115041c6e4080a42ba5007b44063efc2d5e8956a0179d1f26a6ea260a56045231c2602e1fc69980abc000ab9a30f05cb1d5e01a7b831c43ffc779cf1b2bc0a3dded8c45bec3d458c05900a0b35b6f8f42d82a79d0df25ae51c4b3d94898fc6ec5775d4c5f6426c06c8e0003885736d633e97db542d5e31955c6e4d6c2edead37ca46b771cf3e1d38ce900a928f450c34c7f6a086fc7464dd40e750ea90f4f844edca1078627fb04deea001ce67f7fde36ecafc8a8a13b78911d83cf61fc9668fd0cff377d6f3a9778c20051b20350a5f9565e358c67d2000742b1a09cdec7a24cbeffd41ae3a1235023003797ea3ee78bc08b94ce28747512230e3e9ad621b25b0c99e718f0a1495e2c001c2cf65dbd404db3a1bfced323c55c6bd210154fce2143a9b8c4600122de1800063e6e471c4a0b2ede23c0e7bbef9682efb43edd9eba5843fffba69fd3758800e3be991ea86aa8e98ecc451dc82740a26d1ebbfe189a3548d890081d4dd27500f3bde60cbdd71867cd600fbfb241e480069449f44a7e2ea3a3dd3f8cd0c0bd0069b871eb3fb51d77d447ebf8d5f7e2997ade6cb50656aebd253ffea68323030063f9e8752f145ba7b36b8807e1f6a870ad24228846c95666eb3b8c204a47be004c216306fcad02a357c2df111c00123137343635343030373436363323302e00382e3023646174612d7061636b616765732d7772617070657200002900000200ed57011e0000c080a0efd50d74417a6ab59768b1a4950eacbe8efaa9ce772b00da9057f1df5facdbf8dba01040cab05c264479f084feaca1ff522da9fed0fb00a2cd3679c18b4bb9d641c3b6f8ad82121083f05fc55ed8b2021003d85248530024abf859671808565695fa490d78e72baf9d1ebe4036a07b2a0d48641f44450013d5a9ac183ba5b9ec89cc25d3ac5635154ebb800d5b425c3b83fd40f5771800875c1ae2808e017d368198eb2b7d6972f4669d93f3bd256e1661ab3246925600c94ba050148b0458d0a3fdf745c43b7617102cd8eaea254c0200ffb3c2ab5200a1d9dff43a00cd91c64d664170a0964432e000f77f8ae6d0693a4d2663660a009407bce7a0255e055d24221dd484a065b1ab998c596453644e79dc296db1dc00f194651177aff892a1a832accdfdd7971900d377b0a971f066c51cbff2c3a000a7e0fe5bc7b3522bd4ba1e9f65461e25002201464e306eb0b0ac2741310daf00dba06ad9dda936b5ad587ccd4881af98cb0a54a06824a906f3ebd02b115088006b121ff8b882c5e04f0000cdb62c777089d0eb1769f0376df43abb1212916700258a6ea0b85a91596a6aafc998a7859ee1f615a04e746c7f3a1dc1cebe607100e9d1e5ec76e5108d5d46dc8b29b313577dc51fa8fbf906120106eb054d2f5000e552a7836e527894fe37b4fcda5de2de4edaed35ca8bdf1f137997cd096359005f24cfdbb9a0287ef9f6788b35fcace050eeb8207fbe9e5731c260d6a1259a00d62ead65bcc0fa2bbf267ef82c15d3876798f7739b753cad614c3d44ef0e4b007157406942326168f369ed5184b73ea0515e9fcec5dac9718e63d5d7c0492e009252c2cf383d6e56c02039e27cefa6e44a9204b7adef9de6da6cdac1263eb300e11324f811afc0b81c4af1ab8d5e9d32c01193cdf95cbfa01fee9631c90d7b00de9cbaea0db909dd35f2f6d9b7095e62ab54b81a189b84101df86882d593840001e0bf8a82520894b880d4637443106859ee957666a6c55a7dcac176803bc400505caf290ddf3888562757be9f76ae86ae79189f298c3b5ff9df0193a9d6a0007f71b2bb0923ed4e02f9b8b138cafb35c8e9f8860af4813e7142b9d2c8019f009eb61e29014e085048ac468c0f39dc68e32b963dc8a452077aa080a026bf0f004bb606f42508575f273bc50d478ad0aa3c96aa1ea7a9092159d48bf73da01f005561e88677147a4664b555171affe59225a08e7eb80878656fb17a6097b481008b840c367ae683038724c1fa17291e8c8e6066d0ad10e457696843b718184b00ab5b887e48717149b223a482caef8046932104733974b558a034b3c5daeac900e559a3a6a762cb5422338367020b1162f22db34f71f4e932f97cc1a58892630082559e24d1153d9f492554064c9f052a22fb768dee5860a702ba736c38e6660050930001e5a40100060102050804090307742a8298742b6140742c9dd42d51001b2f40f631a2d02ee64a799aa852674791a99400c347159b1a905166b883c3003392189a1c945656157ae08d6cf127c226b93911d5a9605cf78b0ea472fd5500f02cc0d2628e0606edffe27777da724f536b7a050df598a1bf5b6b28c819ab00547b15f7a306934322920db7361124ecb71da25dd0526ad7447373fb48659c00b3808c4f1bfa520b55f1416b6c63b5b357e9f41a09d9cf2aaa475091fffcbf000140ab173eaaba13a8961137165e4eb2007b4f4d6aeaae87021f978ef49ead00194cea46a708c1c3ad5489670e4c6662df530d50b086bec2841f686ce138b30092f6b9180b181f9d3188b35726d31d15873cc63a6e5db4282c8c6c718ce87300e97e5354ef135b2e90d81f853902d9c080a07c01fc24de9c15c79d0023df1c004e980c46d4e366b34faeab807728482194a0e0a06651368ea88228505501fe00dffcba3b8e06b0db7e7fd71d126c4c99ee69ea006081a885548302cf76f5ed00da98050901a062f8e815fca741d8a20e3baf068cec3055b05a209b31599215007ca0ab0ae0b76ea02eec3269337088afdcbfc5d27b990f63343513a718e0610014be5ca05a4137c854e084045a2d97830678dec1f9ae6307788d86c3739f8c0017e75d4659ffd369aebea8ca257316e7e49d57f0ab1051761d2a3d6904842e008c48e2a01d4f484eb15c8dce716f1af962c3d29b12e875cab1cebcf35da2240075aee01895f8f8821dc084065e21148314b31e942e231f5ff27d713bafd0cc00354b10ac2f62b0ed290cb88e17dfbfd600000145203d5d45d0a40bb800090b00b80009b9cb055bcb94aaf70860db31051dc63b27fa0f06870ea2267bba820900909f29dfa066e15ad6cf0f28b379948d2df8ea28f5cf2f08a92682ae88799e00abbaec21476cf8aa4784093ce73a8304cd1ef09b411939920100e892d3df9c00ecf7e8ab45e8ae4ab4cdb64c52fa2356c4043ffd779a26181717c06c61a03d0032657d8fbea11a673f854c0f6810471ef7e372cd9237c4a0b620448895aeec0002f8c082c8b584025bb84b8305be6a4f00014457c738200480c080a0ad503000fc88504fe32c03cd0babdabc8a2e2e15bca670c4f5993a746359bcbabda06800a936312b8576db5b077e7431447d3fde08dfb8ec5920533ab3c861410c680600f8de82130a8401a77ac5830624e494fb92b99eb2938049ab6b64d3d6a18f0500618722e480b8746c05cd2b020f4240010f36830f424004000f30a7059abd3e00b201a180709b071c9a639538eff7913479642f097435a08b71156df665476900138ebcf0be5b31f99266a079fa78202af4d7157f1bd3d2660d318fe8deb5ea0065208d05e0948022de0cdffdf8b982c7eda99d4f4a1ea2b07141320b19d92d00bb81cdb1af1e020156191eff0eac2763ab93a634477032f85fa063d87240e400ce94fed222ca388154d4893d955eac3e227932fcc0543161d2e4e73c840cc6003beb83036710c1fa1bd1365489552b520b40ea68d66ea7a61e7cab600689cb00cbc08efd5404634a6b94acebe531cf25c2451670a06dca049aa27727440ed700d93e42a00168d38c077d632e02630086a98defcb26ce2cba387727153733af00a583e357022d1afd1e8beb33f38c564857a68214a3747d02b9be62994591a0005881f7ac1ec10d0bf38e1489cd823abd7ec5808c7dfc67c13113632bdfef330025f8911243e72b1b9c16327e91724b72ecc49015d390f43bdbd6274071f89b0055221fc149fd3376c4a9dcb7d0a0461fb736d5482985dcc217820ff28d340e00ef224aa106b2e50a3181abd6c774a39e5628a5a9ed9258de15cf5dfc8f85de0060836613b02a716a0001da3e80c092eccbfe8a3d19aa5352db170001e58d0100050208070406090303d7ef9e03d927cf74828263a838b7c00942a65b5559f20020181142851fca84360a2984f4857e5764fc2f8b3e76de04480706890455be002dabb7cd88b5bd5ed957a52b55433a3a075965f1c692975ecd328b42c0aba500f22eb72230980245828d668b9179b251d93a10e7e746e3d0baa7d472285f08009d0ddd2f47345a5f78a0dbb9657698e2366190d913a2b9a774bed70d6b574f006cd1aaec44797cf610807856fbfe95721a8f98904dad7907ee71dc95a54816001b4972ea3d676075cb0cb41571dad57f45d1d6a0fe179b7da52911b1935b0f00eed8072ca296fcfa24b28ae4f04792d98039b5ed8e59c26162c215927a7749000d73025f34627298b18695baec77263868f10b61f601330da9810da24ce547009bc001a0c075225437e723406102f13cd65bb4506e752c1c7b0564abb923c700091fa65f40a0096982daa3f5aa082f5bf2451a62b746fc1b5f3284a78fe13c006438fb8a88b049f8f8821dc18404f2119b8314b3b24976936d424738c4a0c000a36c4fa68dedf0ca3b7399a9ab0fbc8ac1d08b8c882cf0692e5e5fa6a6d7c900a004aad031a9c7c9a23913a4e9105e4402400685497bbec74c146b9f5d669c00108bf8aa488408749be08304cd12f1b01737901839e77ef972b5a59aed204d001ef8a1c9a47043896d9d04878b6dec2ebffa39ff96a0714b2063129c1c2ac00039409021c36059b2f79e5daf47042079711e170a0fba70d2ee84025babf8830005beb6c00391b08940399dd9958474be65982185fc5610c0738a0a0fc67434009620dcf6331247a4b523a052f3502107470970fb73253a79e7f23e33c7ea0400048d5cd2ba94d2d29f96a5714483d966968401f9d61566c6f1f905fc157c3b00d8bd66b0b201180bd3ab9a606ff275e253b4abb912a8cb0045d0400c6869d300f4b18b7e14fe5fdc30f4a26fa04f8e4f0797f97cf48234497591dce98fd32700d46e17822635ebe7d73d255344faf8aa0283f05d4e82c9c894144c035c46ba009dfff26e242c0a89a35fe44a62ea8fb9bc29b9bda70efbed6998b1c7d0c0ea00a12c0c8ca07c075bda67a42a1a820c542e160d68d6f10606e3733e3f3f4fd100d4635564f4b0f906ac0383f05d4e80b90644c0e40360e001cf95641bd1061100016aea06c6681a182b004449404b7c01d1e9c28571979801846365acb22b0800138df8859fb51ceb0dffb59d048041c8c077b087393c743e41a9a04e23767d000e875bb67cd84d7587d17d9f2f9ab4b3824f0b0ecb40fae794c29ddf62840c00077f39c24d5d22651c906406edcf9cf0acabac6a0ca1c6855a5e7e89cd9f7c00dd4ece618cec867f32ffd0e0ab1f8a0948a02ac22627d49744e7b0331847ef00a649314644ee96a7ce62d9d678c10b2cb8317102f904178d1e85e8d4a5105200d9a4fac326934c593b451dda6f53bba053577f91ed8a261aca8c68e64d8dd400a42cf0f90001e5920204000508070203090601290d0d0016290d3e5a54290d00406c155c930cc00e3b1dad440d40fb284b7615590f05cff4924ff2d78b3f69009e68c798258bc93ff43054d3a22021e2eae784c5d48561783afd0043e9b88b003d3b34774978758ad560c3a676e977d132cb6e134e263e9eec5fcfe3b8f12d00e302a2a5d5b7e6125253e4c0140a0206ba43a7aab13c1d30797e85a84d255e00de73699a6aada165ba0e068c0085cef52859e640261497fd614c2cd58ed6cc00d8961e4f684b26c8fd35741e973cd956601f996c141acbeb742a771fda0ddf00731f5bca15c89558f1a80ae14926b6898e0fb18f3a7c6b9fdee50c8ba45d3f003c716f89a27e4a6ae8094df411e31299867510325ce8da0638491a69e26a7d0022b1553d6639b0ffcfe5f7a259264f5784fbb62e6eb478f3c080a0d2b6df2c009fb2f87ec291f4362a908bb60fa47d152c639786711364465690d1f3a066a4002c56cd91fe1bfceb8e084c2dd2ee58b1dbfa19556bc405b6b850d73d9307650062253c756d081af3eb377ee2dcc821cd9b14f4c080a0404ac650402bedfe74001d21b79f949bb66f7b3e04f0f45e64fec89b0c2bcee06aa05bfd3d1e97966d00d6f8ca42632c5ce17e95d993800b1f7f4e8cc7b85c5397819af8aa498407320082db8304cd08ebe36e368e1ac9b76d45eb0a777529d3ffed846905ec24ce9b00608d1824a3a6cdd3f0ea4e6d1fa005be1f844a8b6ab398b4ca1a502275b226009b04348d2b7a02aa8200c04f7685e3f8f8821dc2063b4ab20fc6ca54eb77c300debb239240e9275a2eac735590454aa88b0b8546fab9fc87cfee65fe44a0550058bf9d8a029482ffa73ecca1593b30f379e704eeb940e4c7d9d5353e634629009483f04be38303ccfbf5648f55f01640b01b46a3fc0e3b4028ce36357ad2060087d663f23b3da5a45422edf25a52c5f29b5b8f3e3bdd2c1437f83e71c38b73008cf76bbc84027598f4a13113b2bef0c5eeae017245185ae6d5829b40f64aa2001631ad490987c3e9d12b6e0e3a0101c5254914797e98f0d5175466b45b10a100da3cc25af760832178e2309f823636286c63a7bea8edfd5ca056f6ba98270e0093eed9480dd0bf3fcdffcfbc4e890ada70baec146cc83e1af04c8008674147001d873def7446b9f7e114f4af07d4a4efd3d2b4884380140eefe2d9d66d2d7d00a255645528bdcbd51cd1bcf296165663318ab349a61c5ec4642a5ecce08bd700028d877b8c691debd1a7d961dbce52782625308890471f7544013276527246003cf5beafbb4e6c9fffcb16660834ecfd71d4c18e6d487f25c9719b683e075800fd30604dfcdddb574b6b24285a9d6e5ee32cd8835fe0a6783171e73329b51e004555f9739e55c998cd71c0ef37d439a1a62d0f2291215b99564965cddfa01400c68f809fb9a1037d14b5dcfae59d6894f5066813edd6b013adfb609bc11f5f00c4f884025b54e28305b5014529a3e4dcb3c0c001a09fc5d57b8a5d5a9d00af00a712361acbe2f2afed7d49843fe3357c78b928dd45dea04117772b7a8fc4de00c2ceafff83c3289f18778b12ffd5f43cd2bf0176836f0c3cf8de82130be7c000565a178e7aa105e1664f29ea986d509a33bed3e8a0b8bf24e52da61a7094f200a33e4d05d3c0aea06cf15f80469bd43923018dfda3568a28ed94ecd6e43b1700c1515421b5e740904e2d976281575bc5cee21c4c039e81c402d724e6472b0700e8adfe3f852fe4d5a3881fe8eb7c26e4fca0732c88d021de382be81870c5c30099c24a4a9b0d0c32a0c1a6735f60b47d9ff07df8b882db9caa0de10b49c310009f9e38313ca7cf94a9da00bdf9f0d9ea93bf643814d14ff380804e54abb210003fa05957ab4a18d6187ee23b7d338da47983240e2fcad8efbea0756a7aa29d00f3ce8d8c840a59618a83036734c2691f20274be09a81d4d7e6d8f60915eb5f00ad19e8428d20b260f66d6f192b83dd6a64ccb55890a34fa88d69eb26a0261300c6fbe78b5ec8ce4bc5bb062e5b4a9562463a53b7a7d5d4c7b1cb5eb6efff0200f91794067f83f062970813ca172474032c9e525a6489a1df5ff1ae077faf62008e43b7f52298ef0e034004c04007c00000000009400a0c0d0f1012131513ea00233b929d900000003b5517d796010050a9cf674158962ea0aab75d78155f53008cd0bcb773b35307b4054ee0a804ab4051752900126baf06c11eba5c57319600a294c773d097941683eb7abc0cea7aa24fc85d4d8fbb28118213d788364afa00980038f6de4531b3b537f44477339da2cb317ce947b6e611811b018f2ed655007817eeb863bda3ca32133eadbb172bae2a0367a7c650ebd419e6a8468f25f800252e99ca9478a9c0411289cc5593015cf6ca84829c84f8fb2815e25243b4b90050342cff0ba33e45538c8374c72ec43edd372fd159a8142e5770abfbc09a9e00a3f04364579e273c2d9e4fed656ca29c9ec51c52711b13074c13dac866402400e3004d601367c7d06bf9b922d1e377bbc3032fbc2b0b075ddc8a494bdcddff00d1e82b7c0d35e9215fa3d67e6acba8cd890f44a0d2629349320258e935d84e00be0e6c953746c76e5d1c1483d7662ce3540e34f9405ad09d9f08ce0ce5b6790010449699118164b007cf84d074d3963d386d5d873529c21c50847cee4798780030475e77e339ff044a3c5314bcd63cc27443f147a82164868b1b151388002800ba1085caf40365c8c69d91571cb963069bebddd9c51e1081ff9d2f29a6775500be137e7e3d3b950aeaf051fb60f019d5021e71eb77d691bec34d98f500eb7f00436bd1c59aa7bf565ab1ec4593162b94a63fb2a88c6585e457d2477146c05e00d16006c25c51fe0eb532c73a5d280b56c535ab2ae487dde458b971e43cf6fa00fcaa6abdaa521e7e2f3478eda49cd34df7c48e0619ef7fb71723da17868bd700ff404e7f363d3b058ef2c913ec89437b086b09e24f8892557a407201ea061f00579413c1605a964adc59f83312d9e6b0ec3075cc3d331decda43d74cb9827100ae326a48b07f4c677ecac24a18013c87a402a36a178750fbd9cb7fc577e5f400ac4771f6bd30b5c0c505c690f642c389ad14cb7e75e10b516e7f4b412411c100f095aadf0259439ab9f04d82dbc8a69e9daf5d886a24b3e5190000008a348900ec1aa60f0caa70c003b862221f8d35267a4b8815fec872330a18c6229993a8009b05598c1508c8a790f19c91902ca80eed5af7831cdbf37262b22d2b13891600bcbc582484341adef113d76b2b64595b345596fe44023a47472b87db796e7b00650649fb4e8a71fb9f47a1b706744107a4d666d49b7d66adcd480b21891f1a00115e671a159c660b00c0140b8355c980b76e994a69a6421c1be70807fe25bf000da78dceb994fcd5f19338b8861f023354ded0461cff5fe80a87f83f3619e000121251475dfd075b0a271ad17b3905daa6c5e3b2a3187e515cfe13681ddaf600decb8f70cc50fa1a1c1c0138de0004c9082eb9b3daec3352e4774b3d3227e80070e5458cbd97d2861c46c6196349b762b2b1a20e2f08c134aca9da0737b1050010aa20030f07503b1e12002f410eca7d4363f99daa1b1d3e31141a5c2fe0fd00e436629a03b112038a1c8704b6c1245bad1b0e0d7544db6f12457b89432f5a00edf2cca358c64109176cafe9ede47248195bf4fad4e76ddbbff6c2181592c7006ba8d9102b57d61f1cc080a0f324f8c8e8bd92881d25d44e752adf5105b86f00c49a53a00c20d442cc94eb1165a03a274bf80beaf0be120de5c20f1a02889900f3c20a127923a86baae9ec7106cc6b1d83f06029840210cfa8ab1008254a920037d20170d04f9bc5adeda54fd164c1f6bc49ccb530c5b46a8a6ad146defa5b008016d47da57bb8ad6d6df58ccb3d4ba045795b56657d1bb65d09b4db5e27b900f223d2f550d074b6d1cb227980bead96025566135d033941d8eae41339989c008052681a16f9c001a03fe50b593c39a8ddc84c1b901962a60f5e49b495bcec00d6183e89edd5112cb579a00f434563d4f72fd8e05a7f474153487c23a0fc520027448b1c072a8b76777992bb3b83fd2c02b381e2869e0db002fdc7a2045a3900aa39c001bc72c9fa3a8cdab6a8fce340db48d58031dd15ce54a064fd5855cc00e41dd45647443ad724d52f8ff7ef3d9639742eef042790ad6e4558f8c682c800b65d0000000831285b4800000757e5fe4f237dd5a5f600020b806efdbff2a1004a7c8e15944d1f4a48f9f95f663a404566bf83399e4f750728d1ef57008aed00da00e7110e3836a043c1f687ab39fc112bf4207e160f6fa7341d3a981f2403006925b4bf820dd80ed62d5175a39fe177d2a587a0bd53cf97d298a0126fd75a007aacade1ed8eed27f1a0c836cf2d600b02b1267b2eb327ed4208ec77f8b88200c7ee4fb2f19612519eb82c8417f0ca315ce55799420b3a665c38b75326a881009b87b24cf6c9e55458fb9ca030e79eaab8ebc9ad9513a82c2f7b742560f9f2001b3c0a50ca653289c0d70eefd3f8ae83035e98798a271714df6a7bfa182a6a00f12c4c81eeae756aed19f5c853562a83e492d17888ce50a05e74eabe973522002c7133ebf7fc20c62ede0e00014e6bf5fcc71bc83bc4218bc0e18404e85704008303671ec269217433309bec89e775fc03f2f9b06c022acffdcef0ea63d84b0006e4811aff85092637e5235fefb6123d5b2a9208a00a07933aed88bd0bbc7a0073bd9d015c696fe371f1e40f1d403d082f7ff699b21fe53d840368029fc4ed0071aab25fa47cf65cc2270b270017fc9bc2c964681ad7ffebd3b461dfed9db000bd56a57431dac8e7e4c2e0581cb62c2ca041e149c3f1f8cd170a6563266f73008a72a620d0b8cd2a1d721a1544502d70a52e02f902824593640245012914d10097400196a5e720000001f6fb5e93b0f2f8d6516b821e689b1873bce3559913004cc8f8bc69cba88d41069d4f215d88cb81959805f592cc905cfa88e770b0210011f4886a429006018ec74a3b1b3b72216dca4af65ef93a3dbd1214a7a705f5007a922b3436a91e11035beb5bf95506b523c2f0a4469c78fcccd1c77d46c536001ee29edbe9a98c64e2d0a233e4e7cb1c0225936c2717e89cf503c24c01e524000b91398c5e41553174f554082558c945643a505e3ef7f671d69abdf2947e7c00305730e4e48812da066198deb0083da2429f1c0003313035323701a097ada600fa377b29cbeda57de30e6e3c7540f649d2d9fe24a72afc127f08e93767a0050081461b11c41d8dc3d37da6d527006168ec17dfa24241fe72dda4aac020e85500f8de82130c840148748b83062f30000f3c2d0f42400404566bf83399e4f75000728d1ef57008aedda00e71010f4047059b644a81ca048331f1d10b0f84440700c7ab87aeb72358683aa3bf306e7085f3e59c1a44bd612a45061576624d91a0000f27e2a44808706981ddc4146491755982e63f3c1b1758ad2d77444e9468e300e23c84015ce29e00a9d9efcee17830c4a0c459278f4862868ddc4e78d5ac160062f3ae7ed0c0e5ec5a686e890ee76d90a7e7a0607c709e97b5313f64f76cce00928b7a8f2104e186287827622b82ed111aba61d9f8b982c4f94fa96c60e5d7009f58c4a0185bc8302278b397b15036569bd4276b64b26e73cf107b8f87cb17008ebc079610a03e80949e3a9ac915727cc208e9cafcef15923ded5df8718f40009c27cf7a40d3c8d2ef84025bc6658305b52745cf5ced9ff1e0b9ecb953ca360091138b6e200b768d0a641035f6bc777db63de774d38745928b23a00376c5c300de8c11b9c6b665c11e3b364d43d1771bb4ebb34d9f8f7422750b073df8de8200105c0610f601c1a39806d60229f1e6e02ab12e633e5d5696758a3aa45f07fe001b73201d7c6a12798cc6afa29293a0170e04470700549e555af65029c39301000aa425b3b57f152300772ed60c9994c72ec48272d12639afbecf80a035d28800d63c66585ec13817f9e190f5fdd7c86844b9a0dffaaa4bab5ddf4e0f87a05900460d4f25d77349998789d9b9f7caaf3dbfa46e4d8be38ac725a9f6075c311500638410766400830366ffc4ed73febea0a08e74dfdcd0ff3bec80580fdfe0fd00ced4c22c34ae519ebf19e61903528ca1fa3728b7859678bea0091be84bed8400701c37d8e25ba0131f10783cc7211544109641ecc186184e7d383d01f6a50500870e70a0e9a8f2a210a04cbade993f2e12bed36fa6f36fc5c1e9928555ac560057d6ed27dba067458f2dec27736768e7bd1e71bbef7b1e86e5795754e8812f0003e87558bc43eb02f9025a830827501f84016074cc8402a4dab883047ebc940001f0a31698c4d065659b9bdc21b3610292a1c506866511177d5632c9ca436e008af7fbf315273a18f1c0f96479fe12e3e742881b76b54232f25dcb9220400200c001a0529c18684df8995470a19ee74eb8184f7f39a01196f08a1c436d1788006957edf8a0621c90b8c4c4c868c904314d29a84190837c55e404bbc9f732e9009f8aa475e88377d1272f830f38eb820c827d3da68c8c850d9c02fb07cab5f700590fbb3a3e80a0862ee353ac494f457a11255ff3b21fa6a8816eb3bbbb318800179a78877432b320a0088f9641ab9bc5c79c2670251e038162d8ae64e63f480094bdb0137cb663b13f1e8d8416497d20830366d5c4fff3c5385fa109a513630003f218027315121fa3c0ad93060fcf03dbcac1dc3adb3abc2d251b40927803002dd456f4b2a0030696f2238458a3eae984ab0c4a61cec8310517322320e7e400043c66c3fd2393f902cf828b638402b3d8598306d3569480e38291e06339d1000aab483c65695d004dbd5c6980b902642cc408600156027f1f567fa0681a1a000a1f01f98001609f034dddd9c6464e827f648110dda4b6a3a4284402ef68ff000413e5f58a92df6614071779ac20029d791559d3e343638397f2443fa6a07100057ad5a08caaf208b1c04dfbce65a73c0e2ea5854f209c01afb142b494d2660002f903f3bd27c66a96e320d3f61807ed656df80696fc0c09ce2880b90384400002e001a00a82560b554f0fdf0f0ea22252a27a4e0001e58c0606000501040900020929046f7cf02905e057c6071f8ebf084e2c40092af9d691ef5aa61b4df300221318726f4df2cafce9a2de4f3dc454c313f4de484b06247f27cbede72afc004aeac3141c93a58ea42acc4c57707b013522a39d6cbcea4d76e69442481db200a65d82ffb9c16f9c6056561051f2c6998bc02bb3d296fd67c3366400412e8e00a47a3cf4dade7a2fb7cd479a6e63f1ec75d4537bb681cc78b95c8f9e43030700cd30757fd587f6deed9bdfe142eb10a1e038239e9fbecea7685495e62914cb0044890a263617610602b9cd5cb5507002816493255efbd139a64d6e89a503250037d4344be204b8c12e72dc7158d48a9c52cd5a209be2d38d37dd867b10699b00631fdeea041882184222d4ca2438e6eadc9e941677084913e02a51186a74080046c001a0b5ccba9d602253874745dc063aaea61a18dff5dd64994b43d58cee00d7eebb2b86a07437bb579d880d755c21c0574a6534985e05c6a1a894585367003de646ebfdc31af901cf820f1a8402b3e4af01cb819b8de48b900fe357f32700f6c4a0be4f93add3bcfa1ee919bdee80424f3a79424421efbac8bd268b9e0d0049451a0ca0483d6129bf0b783ab1e5e8b0b3c5a0378ce332089a445dc30afa00a43899b42c0b42ae111b91450c0693b80716febe32e96b150115a25809f5fc00756427d26871b772c3a00166d92006ebadbced33790cc30348ca6a2e1b846d00e2127e48abc837c10ab5dae28402c852c283067849c500613367a30df1ebb8006418054b17091ff32d7e67bb29575d319d254fe327ad02864f414b58a088190089c7ffdfa0653faedd5baa942e083d4bcb6052f142713042dd72e551638fb0001c74481699e1f8f8821dc384049c55ca8314aac6de31e8d36cffa6c3a0ede4005dadfe09e7d77dbf9775cbaf182bbcc3aab8db7f061dfc33369a08cc26dda000084e81122dc59f2aed7955554fcde1c5740296c77abdf44d86329a3e73aecd007ef8aa4a840e16dba08304ccd8ef6a733b2188204f995f5add3aed43b30c4b004f76cf3604ba48d57a6ae285df13a280c3aa74701aa04841ac3efd60423a2e0073c936db82eb2158f33c051d9fab23df1076ed9dcaa4e5db9de589949f349800f68e862fb90d91bea36e85ac76bbc1929713dca0b048ccccb0eaf1e29436b00036a073431cebd901af27f1752d15bcb70f32171492f75722ae3f21f48700a200a980ae0d8401a77d97830624d602bee8ed328e9b9224a44efc35e275cc1d3a0036615bcaadf1087030056a88f04c0b1b84ab37ad8d27a07662242b604ea10f00a2176d838ea5431335efcff9a97ea4d781a877d4b85150f7f8b982c5e1ac020032872dd918c3a0e652b4c5f9f121e30fdd1345d7eb84e6d5888f67e9a881120020b130549495b2c5a0709273b7f9f4850415d668d9350a65fc6d3d2749a48900606ba233a36a145205eff9022f82e53e8410f32799830366f6c4ede3c0f95f00e0a66eb6cc991c1808727e43d4dee96a2d75466902d2b5dec6849b6c9500930037482a9727dca19b0e81a0079d61307d0c03ff4a0723268d7c39743121e2e70030bb550931be6a181ff54fe66782de160f775cfe4b95a8926c1f63e6e853b600b1170ac001a0c834b422815b79208ed45322b9fb0b4c41a4e329ff722e2b7400ef652d019f6103a02835b88ca311f991d04a3b6cb9abbbdde3e222f920c9890074dee91845ac3d8b81f8868202c68401312d00827d1094391e7c679d29bd94000d63be94ad22a25d25b5a6048702051ae478e7e897024c5ff2a58d32cb06a9008a00db4911cbea212f5653008fa24ee05e3ff377d70a2ce40cf6950cdb4be100d18b8d063c00e00ec59c733b918ea03fc303a2664fc41c3ffe8b17895a351100f00b73b0ca3ae0f3b9827875c940af5b43840312f032974f5e59ca55a412120012754db4ff13a9775efcb10f2bf54c98245365ce415c7072b5de302c5224cf00f40b10dd37a052349a65d77d48b4f2e11641815b052cf0485346350b86edb5001c6a4a1bce1f1f1b31427cdfd198c44bb40f57f6d793a9e2c98946553aa4200080f5b3dba9ecdb9b70dba076c9de2155d00a3b3509584bd8e85284ceb1422c00c96ffae2d4a52e2b7fcffdf1f8f8821dc46ea022cc7681108ec4a0372eeed300f38e124d9856d3a3f8523cf0a82db7dead2e486c961349459ffeb70da067a900bb412f26ffb92e2a0f7798cf3b1582301fba1bddad46b3bed2d2c42277e83c0083fd46877bfbc7ee6b9d8bdd68cfdf5654dd739356dc84db245061c09b9ef800a8bc8cca1f996f95a028e7ea1364cee96474c01504c6dca6ca2fa2f8d4747100dfe6358b177bc621ee3df8aa4b840bc5fd808304cce6d0780849248522a3030014f64c4ba076d986faeb58795e373354e5ea10b344d9092f62c5fcc2ee52a0003cac9122c7d1f8c3f2de70e07ce1a13bde47f5fe61901ce0879fb2c7c51bfb0004f8dd82130e062881029be1f5b611f15583104ec39f2036b1558d6ad93073004ec3e419d20d461c033b83151236fff4e21a348a4c4ca073a761bc0394eec700b35cb70e0b97270c77eb187e9ae011f9a3a3d6183ddc41a83e026b426ddf2800c4c0a65ea40772410ce5dca5c7c226448e7b09882fb343e63787d5745ec16a001893e4a02422e968f74d4d578886cb46cfd37a32fe3c131e762c67b0a07627004c4166ceeec7ef84025bd2bbe6668ed29b9e2080a0a3e56455ff35757694d10000bca784d2b71f4e9c7a6a8ab86770cfd267e3742f64a061d05e7237d2638800f8666f618b3d83a5c71265aeaa98c370c4d724c32636f35af8b882d2f00142006e5cdf32b40008438e0da810f07da1eace7f044afd081f8583dbe9cd382e0100bbf6ffca8529f2385651347d2923e7e57d98e901159afe0ce6793dd41ca34700bd5c022bb768039c40cda117305c84c2bb737504f2ccccb39f7a53ffa244a80049c1ebfae3b717edf79fa02be2d2acfaae396afe3685f14fa55e65902672e8006d29252f121d05d713f9c6fdf8b882c4fa026c347aad608f603b9ceb1722d5001a71d4373ce8ab7dfad1f33a2fbeb8aefd367c7f961d0905c13ea03d2188030061a0405e0637382156b1aad843bdfaaeb9c15ed36721c87f9371b0f4f8b98200db9ea7b8133882e420c4a0e1dd0e834c89365f83526f5ad95ff91bf231108300355a8c0278495087fdb5a5c2a032c3f4085f12c841c724593ce673e2eb57a3004eeb4dfde1574ac169a323b23bfcf164840e5cca78c5505ea7e95fabf233a2006f0e9723e543697198edcfe5aef69c88d55f666de6ad7d4734fdeb298d7bf700017a00a038b25a3151e56096f2b2133be902cb50b09a109508d90ecf8569200079cc60cff9997d34971d68c001a97986240d344928296ec6dd3aff1d723ec30078f30b287e093df16e61a0138ecd2830242232f6bf3f50b0a66f34a76c69f100c554f4da2f3a1e9a219a54b23f00f6037ddb4c53905d343f5252af631cde8000eccd715ba080e62f4403f1586305fde2c57176af3306a00ac9e2969aef489b00684d18240c49833c051daacd6ab0ee43fdb2baf058c9d60cf8cc2f0492cef60035b2495be33c14ef2f3d4aab4943d6d67c1807f7f6be47d86c063eeb434ae1006618c0cff73f4d07de69e2a1aa6e7eea064a5716a00a664ac19cac31bc7ab500d5911600c21ee146eb398c9740eb8c7323dc14501ba6f901ce820f4483fd46008601d1835e3bc15c05d0e5db9766c3a095eac6e1ed5ff8ec92119bbca72627000347989f0db55564cae0fe4dc007d9bba6a07fe35c9b83b924346d78c00c270069dc498d8c6c23c1d0b10e41e9fb31d26503a3f8b882c8b700f9a8b0e0d1b50070f03bcf26daf00bc2644da94e1daff830a75f2d160046949d61459b819066006288a00eee7404311727a3ae1f3e46876a9ab87a24c5ee1852ba560c4d672f0095b811b21cc4a04be218d36757af95405ac6f11a49a47d2c40e224e1a049d800772081ce8b6c9051a049afc0ba915fa43dc568789abb6c186fc0c891bc5044006fcc1bd3eb2083ff56c59583f0650f8303cc289ee6bd5629165c3bf0e47b9d00d8a16602e1208717f44b3ecfa61f4ae5df6496db09454378a186e974673475003f4618a10dd2cee106072722ea74551c0fa19e65ce1b17d52461701ea385120062c875413049f5adbef7059b9fbed84f8e014ddcee89c81b54d4823efb3543006d9d3fff0bf5e3670ca14d671970052633844e4daddb15f906e479b196bc15001366c41e1c4b640037a7d0fcea47afaf8863e04b0adedb65da0ebc2e6066340008f2681eed784206ad34cff60d02051f6bc630c890dd2caeeae60cfe64fa9a00d10f3dcb330bc22f121bf92dc8a748a1375550d6691456138048a26a4eb1a600eb767522cd7038e7bae27dd350ccc2500f265ab4d40507280ca61f25c345f400cc6c2707498cf4b0b705c73bf284d69630439cdcb48baf4e622eced4b19b3f00de75aca44f8fa336296b733961d878f505f6fe44f8033fbb1409b0538e69da00ca4452c27800c4a004eb3422125b5ac1e864fd8700a516ec9807201c103b7f001ab4f3cfe54ffd9300a03d58398973e7d8cc7a238cb85fb3b15ea367569fd200a47016f624dd12fa73fcf202f901314b43a40428e2bc1c780050d6295b605500ef6f75d217ef7412c080a0430e04162dcd864f403c4cbf21f752bd32613c1a00baf20d31f0bc195d8bb94141a0029c5cfb0ba92b2991a8d2441494bec7e4b200f55db7d61bf227c679ae40917cf502f901015e4000fa859d9f379cc0c001a000ae8d450be65e2bc99522561dbdb768dae041649ea63db1193fae7c212ba5b100a9a0417a7f510fe239141e484ce090ad5edb62d4df55696ca60a26bec81f1a00383e6102f8bcc7f000c6152e96a732a0c080a015aa53c9c0478b8509f380dd00f8b6c7064282619dbfa9d3157a1a8b194d72997da02dddf3e5ad68b002cfd6000f1e4c78a3c4cbccab2fa6056222ca6c1bb23f638d7402f901d20f45dd230d0069a6a27aa6072d1e7fdeecc001a00baf695abedfc2d68edf27dafbf0da645400cffe80226ec3c99492389109ff9702a00d707855158536d1b0b014a26c7ac700977482cbf55101cae6a6ffd277c32ade641dde15ccde134fe92d9355301dea00cb82c86d38633c47bd10a83424ad34ce72a7f3aea04aa1429ccfdcb0df4907009c1667b08011d13df9d92a74f5c7c53c801190d8fa308e8405502b5083036f009cae281daea4b4110608da788831c234ed316e69edfedd29dcdd2682b278cb00a7fd224f793de7c616a030fbcf070e591710ebfd2c61c2b87abd2f4f5d068700ec114a0096d1c15fb014032fc9ba841bfea401a029b34fb53ab6129315f4dd005a2aefdaa7762691e6f59ac89e72ecad7af9765cada04dd15d7fd359495351009fbe60d93226e7ebf4cb88fc1baa020d67bba30d7aaf97f8aa4c8403736ceb008304b9a878a0a818367e25cc04740e7647bcd9dabc5e9d6260a439ad5ebac70065cbbde9641a915dab3f8afaa05ef2ff0c8963847f5e75fa14bb26072665c700bdd7fe1b33cd2bb73468667d5893f903cf82dae384031302b38307af439477007b58ed517a72e2efbaea90b5d2b996a0df764680b9036412b8ba530000000600270f03c51953beceffe00de0b6b3a7062e10180303681a1817fe07a255c34200636236004a3812cca0962c0d3941e6a6248b3e4d564a69d05a57a0251b6f4000b3db9a3870d41654618d8f56c004d443ddaef3f1ea48632bb8d4aa609683f00068c28303cd9d94cffa8b54d01962bfda2c6c06229f576e8a38972c2bd72cb900d63763c4ed694abf27c7facde8854b54fd4a30eb6649f334beba42950612870068f6e15265473158d4163496d48edca1093737c3702354fc0ea0e2390aee9a0047a3dfffc8281f054a4ab2052ef19858c02f14808524efc69004db1e861b17000308141b4a25168bbee04023579092429639487ef2cf05b86400c3ad5e944100b8a1e7dbb306be01b4c17474deb19e6dd6281c56470e369d1598ff06ea890000ae1bf770d7c9bd60a173cd9076073b7d57002370e53214bf5788022163f112003594f1cfb7fa0dce8729bb9f9df35531d4ba5923b3c3fcc9604b38532b503e00e92722eda1389db0aee3997cbecc01b7e56af46652285708470d11ed65adb8003fcc84899a680717cc49126f016eadcd1cd0bd3c14f0a0cdc50e0705d88672005b70bd5d89604c4f5500409afb6722540f469912a8ba6495e1784569ed6615002dce6871a2a50fd266f5568e744fc8b0bc54805a97c6513b983c387ed309f30065a528c5b27efa2eef1903e570764333a05cd6083eb871984dec8a16b820e100ddc14587cdfb38279ca569da73b9fb0be23f41cfd900a9fdaae864ecc350c7005c5e54a9fef22c4b7a40170c07ad20c84e98f83e3d690aa078dfe77957c32300483025967e79c69ad42b3ce63fb74676298461daee4d23cb4d02f87202ba83000c6798840150ef7f829490c001a091a619861cb7d87a3d4bc3c44019f671c60021f159842c1e164eab1c801e916df0a00a14602c497fb6f71d3f962b1921ae000ffe50281e4179b808b4d1ec5544d198f4bddf30413d396eb0611807840d8f0050af4ed61dd04b90acb9e797cc2c7c3637dfcbcc1808d0c20001e591060100000204060903050de1ed4c400de2b44c86bc63e0c8fb1d0caf3dda6068a8a544008c23e34b85c2f2c6a3121175b7eaea2048205ba22f4421498c5d2d81b73a84002cbdbeeff3bcd307624a8d0d1987bc874029572d86f3fb997636897b0d0b1900b64ca87c3a1e418831761c99111c4c7662298dd35911c06318f2d129ddc05c000b842b15b1aa5b6fe0d0a671ac0875c46f5ce751cb941e2216ee5ee8692233005a92a74b00ff4c252b4977ccece824a0b8ec069130b2d1d81ecc3d56f63119008af108efe81f0aeda4bf38045d28b4ae326b51c84765a8d8a91e086441358b00b921e8bb6c1cb6032db9b078faae561f4eb97858b4649a02fe4a91d540090e001b4666c38d3455027e11e9287ed0e43a3af028c5001ffb358bc88b369c5ec00080a0841fd62145c37ade32cf398e09517c2b81b7f7c825c3ef82ef50bd680500ab3e0ea0703846c234a30cd9f2aac6270dc0f433389d16a84ea3bd5f4a19e900d8668fd27291078401c373ae84034489dbab6f72da09a088a57a254fabee3d0086d14d0ddc8d93d4307e25aafc5ea0336b15e797dc1d0001e59102030706000002040105090808842b0a1d7d88c8c8faa58a71b739f6b6910f48d3f8c5088b00447d1fb35a4d5d80243b14c13ca3ec7eaebb7fa13658112d69b9599ae9e6690049603a9b0bfbf5b5551e21dcec7589c796f85c78c40a497268e80e3476ccfb0000bf01bed12696463fe217e95848e57e341f94ead3a17d43e6b802ae81006c0094291ac1a5f084c6ff181cffd3ba55c4509797531b6fcf435c407036d32b29000a49952a150fcfc96b65c2602e7efc2ba67163795862e844bdc209d55b563e001d14ea868052670c2faeaf74b864671362bc9c3f96bb38c43a93f88d15f6f5002aa51d5c11a29b521a2ab7d3dcf9586ea1b0bb7c87a9582136d46af79178c8006e565205bf1f79819f09e8281d11cb4ada16ea09613545dd81c42cc4c41d3600dd7b66375602bd21de36098959c033d9c001a0405d06667d5033f75fdee603005d4fabe05e1acfb0799c0b4ec392e2101bfec00aa002df948368cfd38a126a0047ee1f0fccbf2664f3f77085915e2198cd7c609b15d1282613814ba7ef814600d9b44296e793074632b5529901ad6a8e98be9e31ee600bed2c56d58ba4000100e59c050607000302040801000125198100012541890001255da66991260527008808deb29e196364442fe8521284c50ee502a496f51f04bb3caa1b1493867e00c0fba9aac4cdca1e991b20831833d49ff7e54611b92dc81256508379a4554400a55d6fba838fe4c2fc779ac5fda1724b60f4bc05858955faa0c15aa9ba659000583aed287330568b695cebcc53b69c36f520ab40d02bbe28825271eb991e8f00b9a33aa7fc723a1558f321a388851ac87cfdf104fb15fe7670ac2bcbdde49000d323cb67131e72f30f1b21b61841ea754b03555df8ee60f26e4f4d2ce994af00b5f3ede71613601b605cbd19e335079f60be1fd147d68cdb8c8cae90d1857700f5196970215780671e5f123b5e424a87d3a8a3a64e59621b8678e89537579f00a25afc9214207d2936bbc4c001a0e04391693599501ee102f7f6d702824a9a00e72067b420aa66a6ace4b73308d1b8a01d1e301fe92cadb0ce12d4cebbe52f0021df13b09dc8b63dca1e5e9c4827387f5f6887ad6f6612fc5eda7bff7ed2eb008f39dbff051ec080a0701f7e3fa6e87c5ed7b13e8a9eb41bbfdcdca9f2af8a00eac7852954c487022957a03160e47c8cee21a33b576c499f42701df811610900dcf4b095101424ce443c2c89f9011583067e7f8401207db5831029235f472a00b97db8a4e82b3810d0c8b1551b2b52d330d105e6a843037b305c3aa38294ff00ab521139827cbd771aa050a29dd7a7b24a25f31124744d91494f59e8ca781f00f53558b85ac436365572a92583b718ec8401d796294db635499c83fc12765f00378469ddafe64dbb896fed91d2b10e4348c45ca54491dc352d08a8c786c81400776dec5b14df72dacda801a06287791b06ed9f1b58a7323554b2bb47f4ed0a0003191195288f1fdfe970c66673088401c380038403449630cbd4cb4116dee500273cbeb36f26edb3640055cd3400caeede7065357f1fa8d4613ec173eb9f000001e5950108020906000401030705088c17b23a80088c450e2ac04a0278806300f65fba72e73a407c52b6819c3983d9a05b94800c11def4d25f4daba414de9d00867de0db143221b63b025d48c12a620acb99e22c598b6f6681d76a281b08e5003dfa9f7c5c77424aab352c787075282cb5a35960502376ef54834a952d891b001aa098c65452d3d3413f5988affa05481c0eb01b02443372515cf62e3cda09009c1497e958b9465d5846686d954108ccc6cb339d12027529e76a02f185766400b77cbb7bc4b709b9c78f88c2bf1f8f112334a3c0b4d405505607a77f4c786a00785a8b1e091ef4d36e1833316931302b3540349b2d3a02f1880f2a169eb22c0011e76ad0474ab8a33029b7bdca937647e65ccff2bdc5e344ee3d7a69e7ce2e00f125fc28b13633b853fc5b3e50e11d9d11fad382f43fcc092a46f8c001a0050038ee64a3d0ea6712b920cb25c669d56ef46273a8a14cdbd3901d26b461239300a07b4af1fc27166bed59e756ac1a1e2515b73513b8c770bf5764fde93d9b5e008d8c882aa8e449aae3e22372792c0a8601300c00304c00a03006407f131980000c425118478228908b7d14800b432e1a160a1c101a1a0e0a0c080e12240c08000c0e161c202c0c49a69230180c62380aa3b494758f008028947a8c85f6699300b0ceb19a4c941cb7ef7eda828342212312c48b26a76c620b0d1f48c947242a0017bc4458de8dcbbc91729554df112b9ea38e6b9d619b11853944076ca14a16003709b2ef19678b0f7c918eb36b31b0b216664c1a2027441a743d8067a860b9009da0ee301a5634269e506e4d9f9ae774aa38c2f0ff4467d04707b45b47460100c13f7675ce7b7ebf443f80eb9186519d0a23c316d1cc128c8601ec92788a5700af87907eaae8d59305cfd3c9ba4d62857e9ac51568ecd8f96b131e819d83a0008666c04cc01cd0a5801750d05dfe6524af80a502a49933e9a9bfccf6d59b8400c4457bc6cf59ecf6c1c82c5127723c1be57e4d770ffb7f896096a0ca9311e3007750282eb904605a25bc42c361006037cebb3faf7e07842477d8a0934f5ae5008cda71ee7c67b3dc1a74048c3a0f080dc39f5ebc49d5e4d3c083b59b4920b3006513d9b793caf61c9618db5e8c0c13276b0921a098a45e1fceb8ab2f47bdd9007dcd39151bb10df602252d24a2b92bedd1017ea97120cb8272c0acb8c105d0000fa0714085408d778929c5d9ce4b6cb27ac3e03a1590ea7d06058e5ef74c7b00e422b64718c76bc15af3a30cc065894aacc34511822a7db000bf1213a40dee006e19cb700d2ec8ee21d3be120102a13b6928840b975d59876d079dd908754600a5a249ec020db0c61e30d1a1eda60fb132d6679aa0562145591af0babe09c400c6bcfc43c936377a8902890aa91ba8b2956dfd9f4b0d5f071c6e748661c023003fe01b76606caef1e0de49836aba51323bb0a20df637628088f2bad91deda0005a543930bd883d471addb2b491a4a99bd6c00a51170b9fc818c372cdceb47e000092d029707bc0ed1e10bcaab0beae9271a4870ec0663ff021dd587f649712001ee4281dce02b7b7d0240bc0894d07c7357fa8963492120ff0bcd24aec37c000a3e652516afa9a14d238b885e54411150313a64891e649db4e1bcc0ec87c4b00e9c42fb4358da407c1f0f3e6880c35c8162a012318f955a4e7b64c9eedbb00009754ed281dec8f9366a3fbaf8942af2622029cdeb578b923ebf0d2b12c99b80060d48e0c45342b95df8f92a7480704ec20ed03ce7eff9851c9355a892caf8b0015061ddac77edc94cfa4e1f56f1011f593c2c185e7299650954184761a9eb20004ea6dcbdb98ddc1c43230bdcf7782a9f7833c8fd331ee64e11d2b48d80099007d937734038cbf8e4001b2f75e817efe3002e33288fb5d684da3ba0dc80cd1001dfb62334e8707300865c20248773621a04dfc0697b3dd01a264acde8c7b3600de6c47ce4ac7595187cbd847a0bf8a42234fec3ad092b27184e8635e3220de004b0cd9b2400611ab124090937e9a8b649edac1c089cc6fd83daac9904b6b110037b5bfb8765f27fd2e06f2ac512f4ed0934343f4a94044b269ffe00961a45900a2dda652b6b6fb89d9c3a2899d73ee2c3014a3d08da5f9563345714b23141800b231593002f5f096225ae5ae4c580b11440569626586cd97b7a2b7c2ed674600384da26b3c01b6407a4c24d3590e94d8933641c1f5e1226d569c38d8423e48001420cdaeb4f667135e318359ef03fda41fa7e7f5c004bd60eaeb23f43d875f0061800ef8a0305351a5580abb6530a8595b0136fa40fe8308b6509ca5634a2a00b901d6dfc7ff0b573e0fe0d57d406c7f9903c75e799cfd2382b50027d3f7910050d702c6c1eaae739f2b6b0f1c8cb5dd487f0d751f10cce20f6ef0904149b4007eb35b1180ff7d103e7215690c5b7b2bbdf8cb945648b46d305ee3feb556ac00323b0862183987b93092634b3902cff1ab29bccb38be3e4800d8bbf7fb6bab003351a0a00327a62ec33b0adfed5d9c837cdc9ff4f26df499d60df86285488200cb782ea4a2633ab845535fdea0e1a7d3a172c11791e88711ade6c280702daa002e50b1b40183805c40434085031eede23e0d15b904cac08607936066df1b6500625fb87336286033a247f6c6f9f1b41659c333220ac9e980d56bce3ee5a7840080efe35937bd701d3aa545baebe8fc23ac4aca6c1c9b2eefb0bd960a4fa5a300809ced950feab86a10390ef23a090c3a0f0876b549552e11d2c8283162a428007d916c7cc723dcb508493e7a9c0b418bd2fe529713bc26bca0ae69ba0673ad005067476333b4501cf191d174b2949432d16a808b5441c095cca0b828ac1426004928ff19879f46845d7bf090803ebbe9900121147111a981e233cfe13f4e0f00e88a82114c2ff5c3cd12a2ecd7833b60ed413a3de906143d251d6cd9683ef500818f31f251e6ae7cb54334d2e10d8b12b51bde20c863120c2a75198103e138001d0e7de603a11263ae1fc55e7322b70606088fcd1c697aef75c06956830dd9003a0ff3ad8b78cf18bf7c2910b073d87436b0ac8a8dd8f7a931b9af47b086b5005408bab9579ac4061810d10c83999d1226346700f1f01fd4aa0e904b88485c000bd339a00a40c8522753ea16caa3b443e0aef2f118e76fdcc4d4ea405a032200b02a0bb67d86273abff9e3b5a3c5883054bb01888f8657313be53e160798bf0069f955131a708586717920e06c43bd142ec029bd434113675e9c09d909173500d80e81ec14cce408c27106d55c307c0e35d303177c5cbe0c35fec8146310a400c21e416e3bcef1ce3b9b0308f919fa28fc724d370f2976f41299c60c1832d2002a1c4a5202ee259205f448239766c5a0cbe1559e509ba2106c3087714d3ffe0001f5f462414a1501eb1fb0aac9e245508784813d511130e461cc0fd6bd275000b303d52aa97a4158a605aa0e5864da024270fd8740b5057acede58089a0c080094872768e4676a73aef5a6d29038b89d2980b8b52e8ed7a0be4fe8bafc837600ab819f681a4bdd072345645931d5888cffd4905adf27a226babeec2f7ae85100d41cc1dffcad686b2d8d2f6969be6afad4303f09015e65139489ed333af42d00a2736a5e11a9db16fa2277a006f5c465cbec3b0baa76eb2fcdcbe2845ef61d00e1834a7f641e621940a190391b2ae419ebfeb008ea0f61068108c1c5931d5a0020cb0893011fecfa4d34fd119a15d31e0dff70795d8f4af610c81e9f0b97ca0039e6c465059f93fc469c204e2bbe4cf380779906e15385b02d25440a761cf600c4b922148060950c471a869ab93bb275a3233f7036cc96d2795d120312981700de90b0db4a9c83930a967a02e333c7e6b1817301ec9b524b0250a36b6c4b2600eee8b3a9617b6502572ae118d0c0de72043ebf6fa57be1ae6788d26962c2eb005cd173f9182d9222701ecf128cb8427aceb59e49226678361da442a2c5ae6f003240c61289f88d2e0bf23d8d6779fe85faa1884c41c5388788aa03570e166d0045eee76e0ff26d3a32727c35e3e25b3490518d4c6cb9c69d9c7f49840163070064dc9c7b8cbf64d2c169c16b6fd70d28328374580cf228a0c6740584c05783003125fb950df82181c14dc0759dd084b0bb96b7f8eb1611d2bd646c7ec8c8f10063663925c3776894fdc638959ddb495d3a2889c0c278780dd6576678c8d133007320c4d4743b881a32d12cf34f3d24a73534633b235e7ba003fa5ccb79add1004445ffaef39839e8faa53c2d2b11671fd704b18df8f33c801abd94bcb82e4900c384d13e93aa1750068612fb64c0e3406c901c9e636c926d35f0f3c569de2b001df068172784ef5c2034dc34a7832e93c30580470113ae11f9074d7a210ebc00bd517901b136a09a9dbdc9cf6e0ad7de5cb02e35750b5c889b388f891a30e400a40b3495899770edeac0ff97906bcf6b79f5c6ab569b48b3a3430476747e1f0043a95552ad1d5cb9f3f8877335e98c6d6d970265b87680a743ac800a1d20b400038b3670081720dbe13f13d59d271b707c196cb743e787fc3207ce1c20b44800b80c26e42de6e625843f4268c9c9da12463126412a8d567671cac048d439d30020e58c6f9ece92e020a3002ac415c18c4bc065a868621f7e0e01e6252ae9a800a4241449f61cf5a50436fb6aabc06224093e0be56f524773c552ca9827d5b3008f31609a146d5d39bbb470b251878a7dc284781975fc0539453538729d180800d839eace062e6500c67d490d3da2e110fb1a055aa6a889762ee9cf8172bfec006bb1eb1adacc5f463a990f23a1c3f41c4070121de0b882bb550e13999840e800c7c60a46352d98e7c2c2f9a0d103c5f562ca31e2ccc300cf710d4878273aa7007f8064993666d9e6c337daf95c8908819bc4e0eb7224f80108141b22e70b5e0033308f0cf5f8be3f5ed4b114c3df8b81256898f165af9767dd6b4b9a975aed00d39dc08286c00e714d12a1cafb4640bb1cb1f8dc61d94ce3e0c04711a61f8000fe8c30bc1abb6308853037ad3d1e05e3368af7f9611b0dacfeba47fe2cfb4e0036c912c59b24825a01051002e2311966f803e80b56e5fcd3db22afb0224d2400e8ef8a16f68a802f348c118c54651af107252d4c0a0c99aedb46c150fc7a26008618550b0d5ac14b3dcd6914a800a0b60fe60bfef810bed856761f541a056e0085ccadbab683477627cecd8f8e9032a299b813b7d16618ebba2eb13beac1e6000ecfdc6e1490f02dfb2d58064f78f5899e54931b2645a0f37aac6435397fa900d89f71e95ddaa2a1021fe8848e92198661bfcd907d4c4277243af0fada2c1c009994ac224c4a62051c9d9cc480d841060ce205e72403d8e284377a0878b1cf00fe3c072d5c0a20f337b469f8fd8f8f07ea48b842961ec12c04bbafcabcbb7900129a31cd368c078538e280fb323985e11a0ed31f993d6399f64b092bc7430d0088412228dc71303c8fccc4ac9d00a9dc05edfa55719263147a977478c1fc89002342aa4e24a0e30b03f532c830528c13ba850cf3c9200de50616b057254a1200869f7edd8995169383d9795ef1132581800963f3886a900c2a13b3506505cd0053cff2f8524b19cc0acc8e40d02a6d767d16e6bf7162343620e0926ea35ee40031a204a6a20f8cc5d4b84e62c4e1c483b4e0e9e5a383c74058de7391804e8000610957470231ef4548e8015c9318f9e35e7b1056fb6106d4fb66b1d6e6205d0076ad7e7aea9e4493d8b8738705b6064eb03b48983802d62b3d69517e6d6cc100aad981a4b27ce06054086704d9e1885d1ace76bcf519a69d1029d33aafd11c00266f78d8be61172438987f1c8fd756cdc428e1b47abb4901b13fee14a118e400091b1a3a6942c3bd3794ac456aa83fe6d9365e99608f90389cccb550a1c32100c085a2886b2a883fba92b9e585ec4d859074a24cba311b0aad3968316f3ff40076ac6b869f719c717019311e57c6b3662299af26c707bfc46346b171305f3000b5598f074b1736c25e0c9c56fa918347731c8cd0066a9e0d263890d230200c00f118c2293c91e3d89e6d74f70a9c40bafc7154da0903f07128bd1528dee005001e15bc20bdf77199edeea524b644a217edc7de2077c6174422a37d6adcb453008b8dc9bd4e5f58efb6a1f31364240fda30ddb048545a2be2f838185a7a113c00642392b17ef3d4b94ede95a2732b987853c34591de8a904858e28f7ba0f331000b1ebc64042f8830765860714f1099b123c73f0b0120508a5f30d4066cf056007a27505e606447400e79c66a2a122235b13a3b7895dae014e0da262e086dec0053bbd66cf9b1e488178d1a8d456c6aa052439977a8ea997bf8ab2da98341280000800bf45144073999eb6dd5c0af3940a0f9d8401fa81c8c2941f7ac03960700db7885e34c5bf440a5e0b898a9ebcc02e683dcac50270c99bb9c5200b803ea00f1b796fa00deb08b38f3082481c0a1a6a05a6afa018c618ff6e92defa0d93900a042706ce6d3225b015403c775ab1b00e830067471b6352d06815538a8f1e30030bf1a2b8b9b63be0322f2ee5c592253e27e12e2cd1519388d5ec4a45e1029008ab2213503d3e3ebda8d1427b89331c05a01bdc7f11b0d13c38483e1f9c8db008c875ef30377ff944974260e9f364d9741b50150a40f68da03382edac68cfe00bbecc19ce23a6839e601b14ba6605dc0b96a5d43e15a1eaecbe0cc40cc29a00007b0435a6da6075c520735144032fc5e8673258abbe8171873a01426659f69000e82bee09317c7f02a23761f3c58b41fdc8110575442705b9fc80163a30df700ee7bcea0ead45ca55348eded3a410dc00a89d13d892857dd70786fa9a949f9005d3023a21d5797d630f110502347fc8483ce0989c7fdf90dadb1032c377ba30022660966e323caee6a634cd30f2c3ffe7240f13109de6aa6c4d498dfb8abcd001f22179827f6239158907a8e566bfd253f9c02aa94c08d572cb3fb6c920f1000bbc130595a3cc715a499bdcf3a3a4460107013047408d0c99efdad15ae779a005c639f83f20900f121408ee37e9f3fdcb358967d6702c09cb6bce0bed49d81005f20d5d2d386a48b89a2c10986588d0b4e54265dba8860da8102aacb45d07400f15f7544fa4a0341e5a0c843f60c0aa8108c1c7fedf7f7cf67cc3708080e1800de2020706dce091300b390183b8cb6850ca2ea0c1ab9fe68403abec50997dc002e7c10b45062291ecae6a80d326d60c6c01a24ef4a927a26297e0fafc8f10a00bb90eda312d7ef7f8141ae8aea2490b8e001e18b1c754590b1ca0500e6c98a00f85f1b81c5078267c69a9b3d5c012fe14adf12067b3f65b204c9b021b08d4100a9af89e755524a6deaf0d2418a0951eb17a9731b6e24298117bc8b6a21708b000c986367a0b39c6c8b183dd0f71c463f88443b4b278000459115b6ad69cd4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +} \ No newline at end of file diff --git a/common/testdata/blobdata.txt b/common/testdata/blobdata.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/common/utils/ethereum.go b/common/utils/ethereum.go index 7741817dff..a39a09f49f 100644 --- a/common/utils/ethereum.go +++ b/common/utils/ethereum.go @@ -1,20 +1,23 @@ package utils -import "crypto/sha256" +import ( + "crypto/sha256" + "fmt" -// CalculateVersionedBlobHash computes the versioned hash for blob data -// Following Ethereum's approach where: -// version = 0x01 -// hash = sha256(blob) -// versionedHash = version + hash[1:] -func CalculateVersionedBlobHash(blobData []byte) [32]byte { - // Step 1: Compute SHA-256 hash of the blob data - hash := sha256.Sum256(blobData) - - // Step 2: Create versioned hash (version byte + hash[1:]) - var versionedHash [32]byte - versionedHash[0] = 0x01 // Version byte - copy(versionedHash[1:], hash[1:]) - - return versionedHash -} \ No newline at end of file + "github.com/scroll-tech/go-ethereum/crypto/kzg4844" +) + +// CalculateVersionedBlobHash calculate the kzg4844 versioned blob hash from a blob +func CalculateVersionedBlobHash(blob kzg4844.Blob) ([32]byte, error) { + // calculate kzg4844 commitment from blob + commit, err := kzg4844.BlobToCommitment(&blob) + if err != nil { + return [32]byte{}, fmt.Errorf("failed to get blob commitment, err: %w", err) + } + + // calculate kzg4844 versioned blob hash from blob commitment + hasher := sha256.New() + vh := kzg4844.CalcBlobHashV1(hasher, &commit) + + return vh, nil +} diff --git a/common/utils/ethereum_test.go b/common/utils/ethereum_test.go index d8f97cf3ec..eaed61563b 100644 --- a/common/utils/ethereum_test.go +++ b/common/utils/ethereum_test.go @@ -1,8 +1,53 @@ -package blob_uploader +package utils -import "testing" +import ( + "encoding/hex" + "encoding/json" + "fmt" + "os" + "testing" -// testCalculateVersionedBlobHash test function CalculateVersionedBlobHash -func testCalculateVersionedBlobHash(t *testing.T) { - -} \ No newline at end of file + "github.com/scroll-tech/go-ethereum/crypto/kzg4844" +) + +type BlobData struct { + VersionedBlobHash string `json:"versionedBlobHash"` + BlobData string `json:"blobData"` +} + +// TestCalculateVersionedBlobHash tests the CalculateVersionedBlobHash function +func TestCalculateVersionedBlobHash(t *testing.T) { + // Read the test data + data, err := os.ReadFile("../testdata/blobdata.json") + if err != nil { + t.Fatalf("Failed to read blobdata.json: %v", err) + } + + var blobData BlobData + if err := json.Unmarshal(data, &blobData); err != nil { + t.Fatalf("Failed to parse blobdata.json: %v", err) + } + + fmt.Println(blobData.BlobData) + blobBytes, err := hex.DecodeString(blobData.BlobData) + if err != nil { + t.Fatalf("Failed to decode blob data: %v", err) + } + + // Convert []byte to kzg4844.Blob + var blob kzg4844.Blob + copy(blob[:], blobBytes) + + // Calculate the hash + calculatedHashBytes, err := CalculateVersionedBlobHash(blob) + if err != nil { + t.Fatalf("Failed to calculate versioned blob hash: %v", err) + } + + calculatedHash := hex.EncodeToString(calculatedHashBytes[:]) + + if calculatedHash != blobData.VersionedBlobHash { + t.Fatalf("Hash mismatch: got %s, want %s", calculatedHash, blobData.VersionedBlobHash) + } + +} diff --git a/rollup/internal/controller/blob_uploader/blob_uploader.go b/rollup/internal/controller/blob_uploader/blob_uploader.go index 0b0fc03cf9..863ae632f6 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader.go @@ -2,14 +2,17 @@ package blob_uploader import ( "context" - "crypto/sha256" "fmt" "github.com/prometheus/client_golang/prometheus" + "github.com/scroll-tech/da-codec/encoding" + "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/crypto/kzg4844" "github.com/scroll-tech/go-ethereum/log" "gorm.io/gorm" "scroll-tech/common/types" + "scroll-tech/common/utils" "scroll-tech/rollup/internal/config" "scroll-tech/rollup/internal/orm" @@ -23,6 +26,8 @@ type BlobUploader struct { s3Uploader *S3Uploader batchOrm *orm.Batch + chunkOrm *orm.Chunk + l2BlockOrm *orm.L2Block metrics *blobUploaderMetrics } @@ -43,6 +48,8 @@ func NewBlobUploader(ctx context.Context, db *gorm.DB, cfg *config.BlobUploaderC cfg: cfg, s3Uploader: s3Uploader, batchOrm: orm.NewBatch(db), + chunkOrm: orm.NewChunk(db), + l2BlockOrm: orm.NewL2Block(db), } blobUploader.metrics = initblobUploaderMetrics(reg) @@ -52,18 +59,94 @@ func NewBlobUploader(ctx context.Context, db *gorm.DB, cfg *config.BlobUploaderC func (b *BlobUploader) UploadBlobToS3() { // get un-uploaded batches from database in ascending order by their index. - dbBatches, err := b.batchOrm.GetFirstUnuploadedAndFailedBatch(b.ctx, types.BlobStoragePlatformS3) + dbBatch, err := b.batchOrm.GetFirstUnuploadedAndFailedBatch(b.ctx, types.BlobStoragePlatformS3) if err != nil { log.Error("Failed to fetch unuploaded batch", "err", err) return } // nothing to do if we don't have any pending batches - if dbBatches == nil { + if dbBatch == nil { return } - // upload data to s3 bucket - b.s3Uploader.UploadData(b.ctx, ) + // construct blob + var blob *kzg4844.Blob + codecVersion := encoding.CodecVersion(dbBatch.CodecVersion) + switch codecVersion { + case encoding.CodecV7: + blob, err = b.constructBlobCodecV7(dbBatch) + if err != nil { + log.Error("failed to construct constructBlobCodecV7 payload for V7", "codecVersion", codecVersion, "batch index", dbBatch.Index, "err", err) + return + } + default: + log.Error("unsupported codec version in UploadBlobToS3", "codecVersion", codecVersion, "batch index", dbBatch.Index) + return + } + + // calculate versioned blob hash + versionedBlobHash, err := utils.CalculateVersionedBlobHash(*blob) + if err != nil { + log.Error("failed to versioned blob hash", "batch index", dbBatch.Index, "err", err) + return + } + + // upload blob data to s3 bucket + key := common.Bytes2Hex(versionedBlobHash[:]) + err = b.s3Uploader.UploadData(b.ctx, blob[:], key) + if err != nil { + log.Error("failed to upload blob data to AWS S3", "batch index", dbBatch.Index, "versioned blob hash", key, "err", err) + return + } + + // update db status + } +func (b *BlobUploader) constructBlobCodecV7(dbBatch *orm.Batch) (*kzg4844.Blob, error) { + var dbChunks []*orm.Chunk + + // Verify batches compatibility + dbChunks, err := b.chunkOrm.GetChunksInRange(b.ctx, dbBatch.StartChunkIndex, dbBatch.EndChunkIndex) + if err != nil { + return nil, fmt.Errorf("failed to get chunks in range: %v", err) + } + + // check codec version + var batchBlocks []*encoding.Block + for _, dbChunk := range dbChunks { + if dbBatch.CodecVersion != dbChunk.CodecVersion { + return nil, fmt.Errorf("batch codec version is different from chunk codec version, batch index: %d, chunk index: %d, batch codec version: %d, chunk codec version: %d", dbBatch.Index, dbChunk.Index, dbBatch.CodecVersion, dbChunk.CodecVersion) + } + + blocks, err := b.l2BlockOrm.GetL2BlocksInRange(b.ctx, dbChunk.StartBlockNumber, dbChunk.EndBlockNumber) + if err != nil { + return nil, fmt.Errorf("failed to get blocks in range for batch %d: %w", dbBatch.Index, err) + } + + batchBlocks = append(batchBlocks, blocks...) + } + + encodingBatch := &encoding.Batch{ + Index: dbBatch.Index, + ParentBatchHash: common.HexToHash(dbBatch.ParentBatchHash), + PrevL1MessageQueueHash: common.HexToHash(dbBatch.PrevL1MessageQueueHash), + PostL1MessageQueueHash: common.HexToHash(dbBatch.PostL1MessageQueueHash), + Blocks: batchBlocks, + } + + version := encoding.CodecVersion(dbBatch.CodecVersion) + codec, err := encoding.CodecFromVersion(version) + if err != nil { + return nil, fmt.Errorf("failed to get codec from version %d, err: %w", dbBatch.CodecVersion, err) + } + + daBatch, err := codec.NewDABatch(encodingBatch) + if err != nil { + return nil, fmt.Errorf("failed to create DA batch: %w", err) + } + + return daBatch.Blob(), nil + +} diff --git a/rollup/internal/controller/blob_uploader/aws_s3_sender.go b/rollup/internal/controller/blob_uploader/s3_sender.go similarity index 100% rename from rollup/internal/controller/blob_uploader/aws_s3_sender.go rename to rollup/internal/controller/blob_uploader/s3_sender.go From 385d97cb49da37699384f209cb84507d16a6599d Mon Sep 17 00:00:00 2001 From: Morty Date: Tue, 3 Jun 2025 01:22:13 +0800 Subject: [PATCH 03/31] chores --- .../migrate/migrations/00027_ blob_upload.sql | 23 +++++- .../00028_add_blob_upload_indexes.sql | 20 ----- rollup/Makefile | 4 + rollup/cmd/blob_uploader/app/app.go | 63 ++-------------- rollup/conf/config.json | 9 +++ rollup/internal/config/l2.go | 5 +- .../controller/blob_uploader/blob_uploader.go | 39 ++++++---- rollup/internal/orm/blob_upload.go | 74 ++++++++++++------- 8 files changed, 114 insertions(+), 123 deletions(-) delete mode 100644 database/migrate/migrations/00028_add_blob_upload_indexes.sql diff --git a/database/migrate/migrations/00027_ blob_upload.sql b/database/migrate/migrations/00027_ blob_upload.sql index 94cca6b392..292784a982 100644 --- a/database/migrate/migrations/00027_ blob_upload.sql +++ b/database/migrate/migrations/00027_ blob_upload.sql @@ -2,11 +2,14 @@ -- +goose StatementBegin CREATE TABLE blob_upload ( - batch_index BIGINT NOT NULL, + batch_index BIGINT NOT NULL, - platform TEXT NOT NULL, - status SMALLINT NOT NULL, - updated_at TIMESTAMP NOT NULL DEFAULT now(), + platform SMALLINT NOT NULL, + status SMALLINT NOT NULL, + +-- metadata + updated_at TIMESTAMP NOT NULL DEFAULT now(), + deleted_at TIMESTAMP(0) DEFAULT NULL PRIMARY KEY (batch_index, platform), FOREIGN KEY (batch_index) REFERENCES batch(index) @@ -14,6 +17,18 @@ CREATE TABLE blob_upload ( COMMENT ON COLUMN blob_upload.status IS 'undefined, pending, uploaded, failed'; +CREATE INDEX IF NOT EXISTS idx_blob_upload_batch_index ON blob_upload(batch_index) WHERE deleted_at IS NULL; + +CREATE INDEX IF NOT EXISTS idx_blob_upload_platform ON blob_upload(platform) WHERE deleted_at IS NULL; + +CREATE INDEX IF NOT EXISTS idx_blob_upload_status ON blob_upload(status) WHERE deleted_at IS NULL; + +CREATE INDEX IF NOT EXISTS idx_blob_upload_updated_at ON blob_upload(updated_at) WHERE deleted_at IS NULL; + +CREATE INDEX IF NOT EXISTS idx_blob_upload_status_platform ON blob_upload(status, platform) WHERE deleted_at IS NULL; + +CREATE INDEX IF NOT EXISTS idx_blob_upload_batch_index_status_platform ON blob_upload(batch_index, status, platform) WHERE deleted_at IS NULL; + -- +goose StatementEnd -- +goose Down diff --git a/database/migrate/migrations/00028_add_blob_upload_indexes.sql b/database/migrate/migrations/00028_add_blob_upload_indexes.sql deleted file mode 100644 index da5644af9c..0000000000 --- a/database/migrate/migrations/00028_add_blob_upload_indexes.sql +++ /dev/null @@ -1,20 +0,0 @@ --- +goose Up --- +goose StatementBegin - --- Add index on status for faster filtering by status -CREATE INDEX idx_blob_upload_status ON blob_upload(status); - --- Add index on updated_at for faster sorting and filtering by time -CREATE INDEX idx_blob_upload_updated_at ON blob_upload(updated_at); - --- Add index on (batch_index, status) for faster filtering by both fields -CREATE INDEX idx_blob_upload_batch_index_status ON blob_upload(batch_index, status); - --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -DROP INDEX IF EXISTS idx_blob_upload_status; -DROP INDEX IF EXISTS idx_blob_upload_updated_at; -DROP INDEX IF EXISTS idx_blob_upload_batch_index_status; --- +goose StatementEnd \ No newline at end of file diff --git a/rollup/Makefile b/rollup/Makefile index fd05ccd944..0f63a98c83 100644 --- a/rollup/Makefile +++ b/rollup/Makefile @@ -11,6 +11,7 @@ mock_abi: rollup_bins: ## Builds the Rollup bins. go build -o $(PWD)/build/bin/gas_oracle ./cmd/gas_oracle/ go build -o $(PWD)/build/bin/rollup_relayer ./cmd/rollup_relayer/ + go build -o $(PWD)/build/bin/blob_uploader ./cmd/blob_uploader/ gas_oracle: ## Builds the gas_oracle bin go build -o $(PWD)/build/bin/gas_oracle ./cmd/gas_oracle/ @@ -18,6 +19,9 @@ gas_oracle: ## Builds the gas_oracle bin rollup_relayer: ## Builds the rollup_relayer bin go build -o $(PWD)/build/bin/rollup_relayer ./cmd/rollup_relayer/ +blob_uploader: ## Builds the blob_uploader bin + go build -o $(PWD)/build/bin/blob_uploader ./cmd/blob_uploader/ + test: go test -v -race -coverprofile=coverage.txt -covermode=atomic -p 1 $(PWD)/... diff --git a/rollup/cmd/blob_uploader/app/app.go b/rollup/cmd/blob_uploader/app/app.go index eb95bc16cf..0b3ac1b08d 100644 --- a/rollup/cmd/blob_uploader/app/app.go +++ b/rollup/cmd/blob_uploader/app/app.go @@ -9,7 +9,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/scroll-tech/da-codec/encoding" - "github.com/scroll-tech/go-ethereum/ethclient" "github.com/scroll-tech/go-ethereum/log" "github.com/urfave/cli/v2" @@ -19,9 +18,7 @@ import ( "scroll-tech/common/version" "scroll-tech/rollup/internal/config" - "scroll-tech/rollup/internal/controller/relayer" - "scroll-tech/rollup/internal/controller/watcher" - rutils "scroll-tech/rollup/internal/utils" + "scroll-tech/rollup/internal/controller/blob_uploader" ) var app *cli.App @@ -67,36 +64,12 @@ func action(ctx *cli.Context) error { registry := prometheus.DefaultRegisterer observability.Server(ctx, db) - // Init l2geth connection - l2client, err := ethclient.Dial(cfg.L2Config.Endpoint) - if err != nil { - log.Crit("failed to connect l2 geth", "config file", cfgFile, "error", err) - } - - genesisPath := ctx.String(utils.Genesis.Name) - genesis, err := utils.ReadGenesis(genesisPath) - if err != nil { - log.Crit("failed to read genesis", "genesis file", genesisPath, "error", err) - } - // sanity check config - if cfg.L2Config.RelayerConfig.BatchSubmission == nil { - log.Crit("cfg.L2Config.RelayerConfig.BatchSubmission must not be nil") - } - if cfg.L2Config.RelayerConfig.BatchSubmission.MinBatches < 1 { - log.Crit("cfg.L2Config.RelayerConfig.SenderConfig.BatchSubmission.MinBatches must be at least 1") - } - if cfg.L2Config.RelayerConfig.BatchSubmission.MaxBatches < 1 { - log.Crit("cfg.L2Config.RelayerConfig.SenderConfig.BatchSubmission.MaxBatches must be at least 1") - } - if cfg.L2Config.BatchProposerConfig.MaxChunksPerBatch <= 0 { - log.Crit("cfg.L2Config.BatchProposerConfig.MaxChunksPerBatch must be greater than 0") - } - if cfg.L2Config.ChunkProposerConfig.MaxL2GasPerChunk <= 0 { - log.Crit("cfg.L2Config.ChunkProposerConfig.MaxL2GasPerChunk must be greater than 0") + if cfg.L2Config.BlobUploaderConfig == nil { + log.Crit("cfg.L2Config.BlobUploaderConfig must not be nil") } - l2relayer, err := relayer.NewLayer2Relayer(ctx.Context, l2client, db, cfg.L2Config.RelayerConfig, genesis.Config, relayer.ServiceTypeL2RollupRelayer, registry) + blobUploader, err := blob_uploader.NewBlobUploader(ctx.Context, db, cfg.L2Config.BlobUploaderConfig, registry) if err != nil { log.Crit("failed to create l2 relayer", "config file", cfgFile, "error", err) } @@ -106,31 +79,7 @@ func action(ctx *cli.Context) error { log.Crit("min codec version must be greater than or equal to CodecV7", "minCodecVersion", minCodecVersion) } - chunkProposer := watcher.NewChunkProposer(subCtx, cfg.L2Config.ChunkProposerConfig, minCodecVersion, genesis.Config, db, registry) - batchProposer := watcher.NewBatchProposer(subCtx, cfg.L2Config.BatchProposerConfig, minCodecVersion, genesis.Config, db, registry) - bundleProposer := watcher.NewBundleProposer(subCtx, cfg.L2Config.BundleProposerConfig, minCodecVersion, genesis.Config, db, registry) - - l2watcher := watcher.NewL2WatcherClient(subCtx, l2client, cfg.L2Config.Confirmations, cfg.L2Config.L2MessageQueueAddress, cfg.L2Config.WithdrawTrieRootSlot, genesis.Config, db, registry) - - // Watcher loop to fetch missing blocks - go utils.LoopWithContext(subCtx, 2*time.Second, func(ctx context.Context) { - number, loopErr := rutils.GetLatestConfirmedBlockNumber(ctx, l2client, cfg.L2Config.Confirmations) - if loopErr != nil { - log.Error("failed to get block number", "err", loopErr) - return - } - l2watcher.TryFetchRunningMissingBlocks(number) - }) - - go utils.Loop(subCtx, time.Duration(cfg.L2Config.ChunkProposerConfig.ProposeIntervalMilliseconds)*time.Millisecond, chunkProposer.TryProposeChunk) - - go utils.Loop(subCtx, time.Duration(cfg.L2Config.BatchProposerConfig.ProposeIntervalMilliseconds)*time.Millisecond, batchProposer.TryProposeBatch) - - go utils.Loop(subCtx, 10*time.Second, bundleProposer.TryProposeBundle) - - go utils.Loop(subCtx, 2*time.Second, l2relayer.ProcessPendingBatches) - - go utils.Loop(subCtx, 15*time.Second, l2relayer.ProcessPendingBundles) + go utils.Loop(subCtx, 2*time.Second, blobUploader.UploadBlobToS3) // Finish start all blob-uploader functions. log.Info("Start blob-uploader successfully", "version", version.Version) @@ -145,7 +94,7 @@ func action(ctx *cli.Context) error { return nil } -// Run rollup relayer cmd instance. +// Run blob uploader cmd instance. func Run() { if err := app.Run(os.Args); err != nil { _, _ = fmt.Fprintln(os.Stderr, err) diff --git a/rollup/conf/config.json b/rollup/conf/config.json index 6d8089127c..fbc942f6ee 100644 --- a/rollup/conf/config.json +++ b/rollup/conf/config.json @@ -104,6 +104,15 @@ "bundle_proposer_config": { "max_batch_num_per_bundle": 20, "bundle_timeout_sec": 36000 + }, + "blob_uploader_config": { + "start_batch": 0, + "aws_s3_config": { + "bucket": "blob-data", + "region": "us-west-2", + "access_key": "ACCESSKEY", + "secret_key": "SECRETKEY" + } } }, "db_config": { diff --git a/rollup/internal/config/l2.go b/rollup/internal/config/l2.go index 39385e30d8..a87f77d2c9 100644 --- a/rollup/internal/config/l2.go +++ b/rollup/internal/config/l2.go @@ -51,7 +51,8 @@ type BundleProposerConfig struct { // BlobUploaderConfig loads blob_uploader configuration items. type BlobUploaderConfig struct { - AWSS3Config *AWSS3Config `json:"aws_s3_config"` + StartBatch uint64 `json:"start_batch"` + AWSS3Config *AWSS3Config `json:"aws_s3_config"` } // AWSS3Config loads s3_uploader configuration items. @@ -60,4 +61,4 @@ type AWSS3Config struct { Region string `json:"region"` AccessKey string `json:"access_key"` SecretKey string `json:"secret_key"` -} \ No newline at end of file +} diff --git a/rollup/internal/controller/blob_uploader/blob_uploader.go b/rollup/internal/controller/blob_uploader/blob_uploader.go index 863ae632f6..f9ca695050 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader.go @@ -25,9 +25,11 @@ type BlobUploader struct { cfg *config.BlobUploaderConfig s3Uploader *S3Uploader - batchOrm *orm.Batch - chunkOrm *orm.Chunk - l2BlockOrm *orm.L2Block + + blobUploadOrm *orm.BlobUpload + batchOrm *orm.Batch + chunkOrm *orm.Chunk + l2BlockOrm *orm.L2Block metrics *blobUploaderMetrics } @@ -44,12 +46,13 @@ func NewBlobUploader(ctx context.Context, db *gorm.DB, cfg *config.BlobUploaderC } blobUploader := &BlobUploader{ - ctx: ctx, - cfg: cfg, - s3Uploader: s3Uploader, - batchOrm: orm.NewBatch(db), - chunkOrm: orm.NewChunk(db), - l2BlockOrm: orm.NewL2Block(db), + ctx: ctx, + cfg: cfg, + s3Uploader: s3Uploader, + batchOrm: orm.NewBatch(db), + chunkOrm: orm.NewChunk(db), + l2BlockOrm: orm.NewL2Block(db), + blobUploadOrm: orm.NewBlobUpload(db), } blobUploader.metrics = initblobUploaderMetrics(reg) @@ -85,10 +88,10 @@ func (b *BlobUploader) UploadBlobToS3() { return } - // calculate versioned blob hash + // calculate versioned blob hash versionedBlobHash, err := utils.CalculateVersionedBlobHash(*blob) if err != nil { - log.Error("failed to versioned blob hash", "batch index", dbBatch.Index, "err", err) + log.Error("failed to calculate versioned blob hash", "batch index", dbBatch.Index, "err", err) return } @@ -97,11 +100,21 @@ func (b *BlobUploader) UploadBlobToS3() { err = b.s3Uploader.UploadData(b.ctx, blob[:], key) if err != nil { log.Error("failed to upload blob data to AWS S3", "batch index", dbBatch.Index, "versioned blob hash", key, "err", err) + // Update status to failed + if err = b.blobUploadOrm.InsertOrUpdateBlobUpload(b.ctx, dbBatch.Index, types.BlobStoragePlatformS3, types.BlobUploadStatusFailed); err != nil { + log.Error("failed to update blob upload status to failed", "batch index", dbBatch.Index, "err", err) + } + return + } + + // Update status to uploaded + if err = b.blobUploadOrm.InsertOrUpdateBlobUpload(b.ctx, dbBatch.Index, types.BlobStoragePlatformS3, types.BlobUploadStatusUploaded); err != nil { + log.Error("failed to update blob upload status to uploaded", "batch index", dbBatch.Index, "err", err) return } - // update db status - + b.metrics.rollupBlobUploaderUploadToS3Total.Inc() + log.Info("Successfully uploaded blob to S3", "batch index", dbBatch.Index, "versioned blob hash", key) } func (b *BlobUploader) constructBlobCodecV7(dbBatch *orm.Batch) (*kzg4844.Blob, error) { diff --git a/rollup/internal/orm/blob_upload.go b/rollup/internal/orm/blob_upload.go index 2a09ebe996..d0663a3078 100644 --- a/rollup/internal/orm/blob_upload.go +++ b/rollup/internal/orm/blob_upload.go @@ -6,6 +6,7 @@ import ( "time" "gorm.io/gorm" + "gorm.io/gorm/clause" "scroll-tech/common/types" ) @@ -15,7 +16,7 @@ type BlobUpload struct { db *gorm.DB `gorm:"-"` BatchIndex uint64 `json:"batch_index" gorm:"column:batch_index;primaryKey"` - Platform string `json:"platform" gorm:"column:platform;primaryKey"` + Platform int16 `json:"platform" gorm:"column:platform;primaryKey"` Status int16 `json:"status" gorm:"column:status"` UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` } @@ -31,15 +32,19 @@ func (*BlobUpload) TableName() string { } // InsertBlobUpload inserts a new blob upload record into the database. -func (o *BlobUpload) InsertBlobUpload(ctx context.Context, batchIndex uint64, platform string, status types.BlobUploadStatus) error { +func (o *BlobUpload) InsertBlobUpload(ctx context.Context, batchIndex uint64, platform types.BlobStoragePlatform, status types.BlobUploadStatus, dbTX ...*gorm.DB) error { blobUpload := &BlobUpload{ BatchIndex: batchIndex, - Platform: platform, + Platform: int16(platform), Status: int16(status), UpdatedAt: time.Now(), } - db := o.db.WithContext(ctx) + db := o.db + if len(dbTX) > 0 && dbTX[0] != nil { + db = dbTX[0] + } + db = db.WithContext(ctx) if err := db.Create(blobUpload).Error; err != nil { return fmt.Errorf("BlobUpload.InsertBlobUpload error: %w, batch index: %v, platform: %v", err, batchIndex, platform) } @@ -47,8 +52,12 @@ func (o *BlobUpload) InsertBlobUpload(ctx context.Context, batchIndex uint64, pl } // UpdateBlobUploadStatus updates the status of a blob upload record. -func (o *BlobUpload) UpdateBlobUploadStatus(ctx context.Context, batchIndex uint64, platform string, status types.BlobUploadStatus) error { - db := o.db.WithContext(ctx) +func (o *BlobUpload) UpdateBlobUploadStatus(ctx context.Context, batchIndex uint64, platform types.BlobStoragePlatform, status types.BlobUploadStatus, dbTX ...*gorm.DB) error { + db := o.db + if len(dbTX) > 0 && dbTX[0] != nil { + db = dbTX[0] + } + db = db.WithContext(ctx) db = db.Model(&BlobUpload{}) db = db.Where("batch_index = ? AND platform = ?", batchIndex, platform) @@ -63,8 +72,30 @@ func (o *BlobUpload) UpdateBlobUploadStatus(ctx context.Context, batchIndex uint return nil } +// InsertOrUpdateBlobUpload inserts a new blob upload record or updates the existing one. +func (o *BlobUpload) InsertOrUpdateBlobUpload(ctx context.Context, batchIndex uint64, platform types.BlobStoragePlatform, status types.BlobUploadStatus, dbTX ...*gorm.DB) error { + db := o.db + if len(dbTX) > 0 && dbTX[0] != nil { + db = dbTX[0] + } + db = db.WithContext(ctx) + blobUpload := &BlobUpload{ + BatchIndex: batchIndex, + Platform: int16(platform), + Status: int16(status), + UpdatedAt: time.Now(), + } + if err := db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "batch_index"}, {Name: "platform"}}, + DoUpdates: clause.AssignmentColumns([]string{"status", "updated_at"}), + }).Create(blobUpload).Error; err != nil { + return fmt.Errorf("BlobUpload.InsertOrUpdateBlobUpload error: %w, batch index: %v, platform: %v", err, batchIndex, platform) + } + return nil +} + // GetBlobUploadByBatchIndexAndPlatform retrieves a blob upload record by batch index and platform. -func (o *BlobUpload) GetBlobUploadByBatchIndexAndPlatform(ctx context.Context, batchIndex uint64, platform string) (*BlobUpload, error) { +func (o *BlobUpload) GetBlobUploadByBatchIndexAndPlatform(ctx context.Context, batchIndex uint64, platform types.BlobStoragePlatform) (*BlobUpload, error) { db := o.db.WithContext(ctx) db = db.Model(&BlobUpload{}) db = db.Where("batch_index = ? AND platform = ?", batchIndex, platform) @@ -79,42 +110,31 @@ func (o *BlobUpload) GetBlobUploadByBatchIndexAndPlatform(ctx context.Context, b return &blobUpload, nil } -// GetPendingBlobUploads retrieves all pending blob upload records. -func (o *BlobUpload) GetPendingBlobUploads(ctx context.Context) ([]*BlobUpload, error) { +// GetPendingBlobUploadsByPlatform retrieves all pending blob upload records by platform. +func (o *BlobUpload) GetPendingBlobUploadsByPlatform(ctx context.Context, platform types.BlobStoragePlatform) ([]*BlobUpload, error) { db := o.db.WithContext(ctx) db = db.Model(&BlobUpload{}) - db = db.Where("status = ?", types.BlobUploadStatusPending) + db = db.Where("status = ? AND platform = ?", types.BlobUploadStatusPending, platform) db = db.Order("batch_index ASC") var blobUploads []*BlobUpload if err := db.Find(&blobUploads).Error; err != nil { - return nil, fmt.Errorf("BlobUpload.GetPendingBlobUploads error: %w", err) + return nil, fmt.Errorf("BlobUpload.GetPendingBlobUploadsByPlatform error: %w", err) } return blobUploads, nil } -// GetFailedBlobUploads retrieves all failed blob upload records. -func (o *BlobUpload) GetFailedBlobUploads(ctx context.Context) ([]*BlobUpload, error) { +// GetFailedBlobUploadsByPlatform retrieves all failed blob upload records by platform. +func (o *BlobUpload) GetFailedBlobUploadsByPlatform(ctx context.Context, platform types.BlobStoragePlatform) ([]*BlobUpload, error) { + db := o.db.WithContext(ctx) db = db.Model(&BlobUpload{}) - db = db.Where("status = ?", types.BlobUploadStatusFailed) + db = db.Where("status = ? AND platform = ?", types.BlobUploadStatusFailed, platform) db = db.Order("batch_index ASC") var blobUploads []*BlobUpload if err := db.Find(&blobUploads).Error; err != nil { - return nil, fmt.Errorf("BlobUpload.GetFailedBlobUploads error: %w", err) + return nil, fmt.Errorf("BlobUpload.GetFailedBlobUploadsByPlatform error: %w", err) } return blobUploads, nil } - -// DeleteBlobUpload deletes a blob upload record. -func (o *BlobUpload) DeleteBlobUpload(ctx context.Context, batchIndex uint64, platform string) error { - db := o.db.WithContext(ctx) - db = db.Model(&BlobUpload{}) - db = db.Where("batch_index = ? AND platform = ?", batchIndex, platform) - - if err := db.Delete(&BlobUpload{}).Error; err != nil { - return fmt.Errorf("BlobUpload.DeleteBlobUpload error: %w, batch index: %v, platform: %v", err, batchIndex, platform) - } - return nil -} From 2acce16ff2c627a7fda5e0a9b004748fc79b2f2e Mon Sep 17 00:00:00 2001 From: Morty Date: Tue, 3 Jun 2025 01:33:04 +0800 Subject: [PATCH 04/31] feat: add start batch --- build/dockerfiles/blob_uploader.Dockerfile | 30 +++++++++++++++++++ .../blob_uploader.Dockerfile.dockerignore | 5 ++++ .../controller/blob_uploader/blob_uploader.go | 2 +- rollup/internal/orm/batch.go | 4 +-- 4 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 build/dockerfiles/blob_uploader.Dockerfile create mode 100644 build/dockerfiles/blob_uploader.Dockerfile.dockerignore diff --git a/build/dockerfiles/blob_uploader.Dockerfile b/build/dockerfiles/blob_uploader.Dockerfile new file mode 100644 index 0000000000..d884350763 --- /dev/null +++ b/build/dockerfiles/blob_uploader.Dockerfile @@ -0,0 +1,30 @@ +# Download Go dependencies +FROM scrolltech/go-rust-builder:go-1.22-rust-nightly-2023-12-03 as base + +WORKDIR /src +COPY go.work* ./ +COPY ./rollup/go.* ./rollup/ +COPY ./common/go.* ./common/ +COPY ./coordinator/go.* ./coordinator/ +COPY ./database/go.* ./database/ +COPY ./tests/integration-test/go.* ./tests/integration-test/ +COPY ./bridge-history-api/go.* ./bridge-history-api/ +RUN go mod download -x + +# Build blob_uploader +FROM base as builder + +RUN --mount=target=. \ + --mount=type=cache,target=/root/.cache/go-build \ + cd /src/rollup/cmd/blob_uploader/ && CGO_LDFLAGS="-ldl" go build -v -p 4 -o /bin/blob_uploader + +# Pull blob_uploader into a second stage deploy ubuntu container +FROM ubuntu:20.04 + +RUN apt update && apt install vim netcat-openbsd net-tools curl ca-certificates -y + +ENV CGO_LDFLAGS="-ldl" + +COPY --from=builder /bin/blob_uploader /bin/ +WORKDIR /app +ENTRYPOINT ["blob_uploader"] \ No newline at end of file diff --git a/build/dockerfiles/blob_uploader.Dockerfile.dockerignore b/build/dockerfiles/blob_uploader.Dockerfile.dockerignore new file mode 100644 index 0000000000..8734d3f9b6 --- /dev/null +++ b/build/dockerfiles/blob_uploader.Dockerfile.dockerignore @@ -0,0 +1,5 @@ +assets/ +docs/ +l2geth/ +rpc-gateway/ +*target/* \ No newline at end of file diff --git a/rollup/internal/controller/blob_uploader/blob_uploader.go b/rollup/internal/controller/blob_uploader/blob_uploader.go index f9ca695050..762d1deaf0 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader.go @@ -62,7 +62,7 @@ func NewBlobUploader(ctx context.Context, db *gorm.DB, cfg *config.BlobUploaderC func (b *BlobUploader) UploadBlobToS3() { // get un-uploaded batches from database in ascending order by their index. - dbBatch, err := b.batchOrm.GetFirstUnuploadedAndFailedBatch(b.ctx, types.BlobStoragePlatformS3) + dbBatch, err := b.batchOrm.GetFirstUnuploadedAndFailedBatch(b.ctx, b.cfg.StartBatch, types.BlobStoragePlatformS3) if err != nil { log.Error("Failed to fetch unuploaded batch", "err", err) return diff --git a/rollup/internal/orm/batch.go b/rollup/internal/orm/batch.go index 1175122387..b5fc16a233 100644 --- a/rollup/internal/orm/batch.go +++ b/rollup/internal/orm/batch.go @@ -265,11 +265,11 @@ func (o *Batch) GetBatchByIndex(ctx context.Context, index uint64) (*Batch, erro // GetFirstUnuploadedAndFailedBatch retrieves the first batch that either hasn't been uploaded to any blob storage service // or has failed upload status. The batch must have a commit_tx_hash (committed). -func (o *Batch) GetFirstUnuploadedAndFailedBatch(ctx context.Context, platform types.BlobStoragePlatform) (*Batch, error) { +func (o *Batch) GetFirstUnuploadedAndFailedBatch(ctx context.Context, startBatch uint64, platform types.BlobStoragePlatform) (*Batch, error) { db := o.db.WithContext(ctx) db = db.Model(&Batch{}) db = db.Joins("LEFT JOIN blob_upload ON blob_upload.batch_index = batch.index") - db = db.Where("batch.commit_tx_hash IS NOT NULL") + db = db.Where("batch.commit_tx_hash IS NOT NULL AND batch.index >= ?", startBatch) db = db.Where("blob_upload.batch_index IS NULL OR blob_upload.status = ?", types.BlobUploadStatusFailed) db = db.Where("blob_upload.platform = ?", platform) db = db.Order("batch.index ASC") From e8e7fb15a6fa30e9ab9f8e90c2edd0cdb0357b94 Mon Sep 17 00:00:00 2001 From: Morty Date: Tue, 3 Jun 2025 02:33:16 +0800 Subject: [PATCH 05/31] fix: app.go --- rollup/cmd/blob_uploader/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rollup/cmd/blob_uploader/main.go b/rollup/cmd/blob_uploader/main.go index ffe2d3a65c..8b70dbcacb 100644 --- a/rollup/cmd/blob_uploader/main.go +++ b/rollup/cmd/blob_uploader/main.go @@ -1,6 +1,6 @@ package main -import "scroll-tech/rollup/cmd/rollup_relayer/app" +import "scroll-tech/rollup/cmd/blob_uploader/app" func main() { app.Run() From b9fa8c8cb3c6cc27b5406fa466cbcd069576e9d8 Mon Sep 17 00:00:00 2001 From: Morty Date: Tue, 3 Jun 2025 03:52:49 +0800 Subject: [PATCH 06/31] fix: database migrate --- database/migrate/migrations/00027_ blob_upload.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrate/migrations/00027_ blob_upload.sql b/database/migrate/migrations/00027_ blob_upload.sql index 292784a982..b874d08892 100644 --- a/database/migrate/migrations/00027_ blob_upload.sql +++ b/database/migrate/migrations/00027_ blob_upload.sql @@ -9,7 +9,7 @@ CREATE TABLE blob_upload ( -- metadata updated_at TIMESTAMP NOT NULL DEFAULT now(), - deleted_at TIMESTAMP(0) DEFAULT NULL + deleted_at TIMESTAMP(0) DEFAULT NULL, PRIMARY KEY (batch_index, platform), FOREIGN KEY (batch_index) REFERENCES batch(index) From a8c913fec4aae7cacec348a08d08c569f184c0a4 Mon Sep 17 00:00:00 2001 From: Morty Date: Tue, 3 Jun 2025 04:04:56 +0800 Subject: [PATCH 07/31] fix: database migrate --- .../00027_alter_batch_add_primary_key_index.sql | 9 +++++++++ .../{00027_ blob_upload.sql => 00028_ blob_upload.sql} | 0 2 files changed, 9 insertions(+) create mode 100644 database/migrate/migrations/00027_alter_batch_add_primary_key_index.sql rename database/migrate/migrations/{00027_ blob_upload.sql => 00028_ blob_upload.sql} (100%) diff --git a/database/migrate/migrations/00027_alter_batch_add_primary_key_index.sql b/database/migrate/migrations/00027_alter_batch_add_primary_key_index.sql new file mode 100644 index 0000000000..3ce3377300 --- /dev/null +++ b/database/migrate/migrations/00027_alter_batch_add_primary_key_index.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE batch ADD PRIMARY KEY (index); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE batch DROP CONSTRAINT batch_pkey; +-- +goose StatementEnd \ No newline at end of file diff --git a/database/migrate/migrations/00027_ blob_upload.sql b/database/migrate/migrations/00028_ blob_upload.sql similarity index 100% rename from database/migrate/migrations/00027_ blob_upload.sql rename to database/migrate/migrations/00028_ blob_upload.sql From 6c68f3c9c663726ad9b5955d95e980084c6a8be3 Mon Sep 17 00:00:00 2001 From: Morty Date: Tue, 3 Jun 2025 04:10:19 +0800 Subject: [PATCH 08/31] debug logs --- rollup/internal/controller/blob_uploader/blob_uploader.go | 1 + 1 file changed, 1 insertion(+) diff --git a/rollup/internal/controller/blob_uploader/blob_uploader.go b/rollup/internal/controller/blob_uploader/blob_uploader.go index 762d1deaf0..777d7638cd 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader.go @@ -61,6 +61,7 @@ func NewBlobUploader(ctx context.Context, db *gorm.DB, cfg *config.BlobUploaderC } func (b *BlobUploader) UploadBlobToS3() { + log.Info("Try to UploadBlobToS3") // get un-uploaded batches from database in ascending order by their index. dbBatch, err := b.batchOrm.GetFirstUnuploadedAndFailedBatch(b.ctx, b.cfg.StartBatch, types.BlobStoragePlatformS3) if err != nil { From e84a2b8557c430f2035bcccbf1164c4283f4d1a4 Mon Sep 17 00:00:00 2001 From: Morty Date: Tue, 3 Jun 2025 04:12:52 +0800 Subject: [PATCH 09/31] debug logs --- rollup/internal/controller/blob_uploader/blob_uploader.go | 1 + 1 file changed, 1 insertion(+) diff --git a/rollup/internal/controller/blob_uploader/blob_uploader.go b/rollup/internal/controller/blob_uploader/blob_uploader.go index 777d7638cd..ff6f3f6832 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader.go @@ -71,6 +71,7 @@ func (b *BlobUploader) UploadBlobToS3() { // nothing to do if we don't have any pending batches if dbBatch == nil { + log.Info("not found any un-uploaded batches") return } From d623baf792c1c2955d08eedb069046d75f265cf9 Mon Sep 17 00:00:00 2001 From: Morty Date: Tue, 3 Jun 2025 04:47:57 +0800 Subject: [PATCH 10/31] fix: GetFirstUnuploadedAndFailedBatch --- rollup/internal/orm/batch.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rollup/internal/orm/batch.go b/rollup/internal/orm/batch.go index b5fc16a233..756939f5b1 100644 --- a/rollup/internal/orm/batch.go +++ b/rollup/internal/orm/batch.go @@ -268,10 +268,9 @@ func (o *Batch) GetBatchByIndex(ctx context.Context, index uint64) (*Batch, erro func (o *Batch) GetFirstUnuploadedAndFailedBatch(ctx context.Context, startBatch uint64, platform types.BlobStoragePlatform) (*Batch, error) { db := o.db.WithContext(ctx) db = db.Model(&Batch{}) - db = db.Joins("LEFT JOIN blob_upload ON blob_upload.batch_index = batch.index") + db = db.Joins("LEFT JOIN blob_upload ON blob_upload.batch_index = batch.index AND blob_upload.platform = ?", platform) db = db.Where("batch.commit_tx_hash IS NOT NULL AND batch.index >= ?", startBatch) db = db.Where("blob_upload.batch_index IS NULL OR blob_upload.status = ?", types.BlobUploadStatusFailed) - db = db.Where("blob_upload.platform = ?", platform) db = db.Order("batch.index ASC") db = db.Limit(1) From 6a499fa30f2fe4342b5ce77b493b476cebe2c9d5 Mon Sep 17 00:00:00 2001 From: Morty Date: Tue, 3 Jun 2025 05:24:52 +0800 Subject: [PATCH 11/31] rm logs --- rollup/internal/controller/blob_uploader/blob_uploader.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rollup/internal/controller/blob_uploader/blob_uploader.go b/rollup/internal/controller/blob_uploader/blob_uploader.go index ff6f3f6832..bf968096c4 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader.go @@ -61,7 +61,6 @@ func NewBlobUploader(ctx context.Context, db *gorm.DB, cfg *config.BlobUploaderC } func (b *BlobUploader) UploadBlobToS3() { - log.Info("Try to UploadBlobToS3") // get un-uploaded batches from database in ascending order by their index. dbBatch, err := b.batchOrm.GetFirstUnuploadedAndFailedBatch(b.ctx, b.cfg.StartBatch, types.BlobStoragePlatformS3) if err != nil { @@ -98,7 +97,7 @@ func (b *BlobUploader) UploadBlobToS3() { } // upload blob data to s3 bucket - key := common.Bytes2Hex(versionedBlobHash[:]) + key := common.BytesToHash(versionedBlobHash[:]).Hex() err = b.s3Uploader.UploadData(b.ctx, blob[:], key) if err != nil { log.Error("failed to upload blob data to AWS S3", "batch index", dbBatch.Index, "versioned blob hash", key, "err", err) From b6bf8052e3599dd14783c84b38db214b115cbc1d Mon Sep 17 00:00:00 2001 From: Morty Date: Tue, 3 Jun 2025 20:11:54 +0800 Subject: [PATCH 12/31] fix: ci --- common/types/db.go | 6 +++--- coordinator/internal/types/prover.go | 1 + rollup/go.mod | 8 ++++---- .../controller/blob_uploader/blob_uploader_metrics.go | 8 ++++---- rollup/internal/controller/watcher/proposer_tool.go | 3 ++- rollup/internal/orm/blob_upload.go | 2 +- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/common/types/db.go b/common/types/db.go index 1e18be8b2c..2f68f0c52c 100644 --- a/common/types/db.go +++ b/common/types/db.go @@ -360,9 +360,9 @@ type BlobStoragePlatform int const ( // BlobStoragePlatformUndefined indicates an undefined platform BlobStoragePlatformUndefined BlobStoragePlatform = iota - // BlobStoragePlatformS3 represents AWS S3 + // BlobStoragePlatformS3 represents AWS S3 BlobStoragePlatformS3 - // BlobUploadStatusUploaded represents storage blockchain Arweave + // BlobStoragePlatformArweave represents storage blockchain Arweave BlobStoragePlatformArweave ) @@ -375,4 +375,4 @@ func (s BlobStoragePlatform) String() string { default: return fmt.Sprintf("Unknown BlobStoragePlatform (%d)", int32(s)) } -} \ No newline at end of file +} diff --git a/coordinator/internal/types/prover.go b/coordinator/internal/types/prover.go index ab603bc235..048fac00a2 100644 --- a/coordinator/internal/types/prover.go +++ b/coordinator/internal/types/prover.go @@ -2,6 +2,7 @@ package types import ( "fmt" + "scroll-tech/common/types/message" ) diff --git a/rollup/go.mod b/rollup/go.mod index f5895963bd..72ee556bea 100644 --- a/rollup/go.mod +++ b/rollup/go.mod @@ -4,6 +4,10 @@ go 1.22 require ( github.com/agiledragon/gomonkey/v2 v2.12.0 + github.com/aws/aws-sdk-go-v2 v1.36.3 + github.com/aws/aws-sdk-go-v2/config v1.29.14 + github.com/aws/aws-sdk-go-v2/credentials v1.17.67 + github.com/aws/aws-sdk-go-v2/service/s3 v1.80.0 github.com/consensys/gnark-crypto v0.16.0 github.com/crate-crypto/go-kzg-4844 v1.1.0 github.com/gin-gonic/gin v1.9.1 @@ -22,10 +26,7 @@ require ( require ( github.com/VictoriaMetrics/fastcache v1.12.2 // indirect - github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect - github.com/aws/aws-sdk-go-v2/config v1.29.14 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect @@ -35,7 +36,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect - github.com/aws/aws-sdk-go-v2/service/s3 v1.80.0 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect diff --git a/rollup/internal/controller/blob_uploader/blob_uploader_metrics.go b/rollup/internal/controller/blob_uploader/blob_uploader_metrics.go index a041400013..252aa11766 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader_metrics.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader_metrics.go @@ -8,22 +8,22 @@ import ( ) type blobUploaderMetrics struct { - rollupBlobUploaderUploadToS3Total prometheus.Counter + rollupBlobUploaderUploadToS3Total prometheus.Counter } var ( initBlobUploaderMetricsOnce sync.Once - l1RelayerMetric *blobUploaderMetrics + blobUploaderMetric *blobUploaderMetrics ) func initblobUploaderMetrics(reg prometheus.Registerer) *blobUploaderMetrics { initBlobUploaderMetricsOnce.Do(func() { - l1RelayerMetric = &blobUploaderMetrics{ + blobUploaderMetric = &blobUploaderMetrics{ rollupBlobUploaderUploadToS3Total: promauto.With(reg).NewCounter(prometheus.CounterOpts{ Name: "rollup_blob_uploader_upload_to_s3_total", Help: "The total number of upload blob to S3 run total", }), } }) - return l1RelayerMetric + return blobUploaderMetric } diff --git a/rollup/internal/controller/watcher/proposer_tool.go b/rollup/internal/controller/watcher/proposer_tool.go index e0c7bc3bc0..9ab53d91ce 100644 --- a/rollup/internal/controller/watcher/proposer_tool.go +++ b/rollup/internal/controller/watcher/proposer_tool.go @@ -14,9 +14,10 @@ import ( "github.com/scroll-tech/go-ethereum/params" "gorm.io/gorm" + "scroll-tech/database/migrate" + "scroll-tech/common/database" "scroll-tech/common/utils" - "scroll-tech/database/migrate" "scroll-tech/rollup/internal/config" "scroll-tech/rollup/internal/orm" diff --git a/rollup/internal/orm/blob_upload.go b/rollup/internal/orm/blob_upload.go index d0663a3078..cd51d0e999 100644 --- a/rollup/internal/orm/blob_upload.go +++ b/rollup/internal/orm/blob_upload.go @@ -126,7 +126,7 @@ func (o *BlobUpload) GetPendingBlobUploadsByPlatform(ctx context.Context, platfo // GetFailedBlobUploadsByPlatform retrieves all failed blob upload records by platform. func (o *BlobUpload) GetFailedBlobUploadsByPlatform(ctx context.Context, platform types.BlobStoragePlatform) ([]*BlobUpload, error) { - + db := o.db.WithContext(ctx) db = db.Model(&BlobUpload{}) db = db.Where("status = ? AND platform = ?", types.BlobUploadStatusFailed, platform) From 03410b72d2b024108551e74442c90afb60fcd8cf Mon Sep 17 00:00:00 2001 From: Morty Date: Tue, 3 Jun 2025 20:18:11 +0800 Subject: [PATCH 13/31] fix: database test --- database/migrate/migrate_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/database/migrate/migrate_test.go b/database/migrate/migrate_test.go index 91697ef311..90cc005c8d 100644 --- a/database/migrate/migrate_test.go +++ b/database/migrate/migrate_test.go @@ -59,20 +59,20 @@ func testResetDB(t *testing.T) { cur, err := Current(pgDB) assert.NoError(t, err) // total number of tables. - assert.Equal(t, int64(26), cur) + assert.Equal(t, int64(28), cur) } func testMigrate(t *testing.T) { assert.NoError(t, Migrate(pgDB)) cur, err := Current(pgDB) assert.NoError(t, err) - assert.Equal(t, int64(26), cur) + assert.Equal(t, int64(28), cur) } func testRollback(t *testing.T) { version, err := Current(pgDB) assert.NoError(t, err) - assert.Equal(t, int64(26), version) + assert.Equal(t, int64(28), version) assert.NoError(t, Rollback(pgDB, nil)) From 030742cb789fd72c649430bbd1bf0c6786afbcc1 Mon Sep 17 00:00:00 2001 From: Morty Date: Wed, 4 Jun 2025 00:26:04 +0800 Subject: [PATCH 14/31] feat: support multi codec version --- .../controller/blob_uploader/blob_uploader.go | 86 ++++++++++++------- .../blob_uploader/blob_uploader_metrics.go | 13 ++- 2 files changed, 65 insertions(+), 34 deletions(-) diff --git a/rollup/internal/controller/blob_uploader/blob_uploader.go b/rollup/internal/controller/blob_uploader/blob_uploader.go index bf968096c4..b89fb9d9d2 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader.go @@ -75,20 +75,15 @@ func (b *BlobUploader) UploadBlobToS3() { } // construct blob - var blob *kzg4844.Blob codecVersion := encoding.CodecVersion(dbBatch.CodecVersion) - switch codecVersion { - case encoding.CodecV7: - blob, err = b.constructBlobCodecV7(dbBatch) - if err != nil { - log.Error("failed to construct constructBlobCodecV7 payload for V7", "codecVersion", codecVersion, "batch index", dbBatch.Index, "err", err) - return - } - default: - log.Error("unsupported codec version in UploadBlobToS3", "codecVersion", codecVersion, "batch index", dbBatch.Index) + blob, err := b.constructBlobCodec(dbBatch) + if err != nil { + log.Error("failed to construct constructBlobCodec payload ", "codecVersion", codecVersion, "batch index", dbBatch.Index, "err", err) + b.metrics.rollupBlobUploaderUploadToS3FailedTotal.Inc() return } + // calculate versioned blob hash versionedBlobHash, err := utils.CalculateVersionedBlobHash(*blob) if err != nil { @@ -101,6 +96,7 @@ func (b *BlobUploader) UploadBlobToS3() { err = b.s3Uploader.UploadData(b.ctx, blob[:], key) if err != nil { log.Error("failed to upload blob data to AWS S3", "batch index", dbBatch.Index, "versioned blob hash", key, "err", err) + b.metrics.rollupBlobUploaderUploadToS3FailedTotal.Inc() // Update status to failed if err = b.blobUploadOrm.InsertOrUpdateBlobUpload(b.ctx, dbBatch.Index, types.BlobStoragePlatformS3, types.BlobUploadStatusFailed); err != nil { log.Error("failed to update blob upload status to failed", "batch index", dbBatch.Index, "err", err) @@ -111,47 +107,77 @@ func (b *BlobUploader) UploadBlobToS3() { // Update status to uploaded if err = b.blobUploadOrm.InsertOrUpdateBlobUpload(b.ctx, dbBatch.Index, types.BlobStoragePlatformS3, types.BlobUploadStatusUploaded); err != nil { log.Error("failed to update blob upload status to uploaded", "batch index", dbBatch.Index, "err", err) + b.metrics.rollupBlobUploaderUploadToS3FailedTotal.Inc() return } - b.metrics.rollupBlobUploaderUploadToS3Total.Inc() + b.metrics.rollupBlobUploaderUploadToS3SuccessTotal.Inc() log.Info("Successfully uploaded blob to S3", "batch index", dbBatch.Index, "versioned blob hash", key) } -func (b *BlobUploader) constructBlobCodecV7(dbBatch *orm.Batch) (*kzg4844.Blob, error) { +func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, error) { var dbChunks []*orm.Chunk - // Verify batches compatibility dbChunks, err := b.chunkOrm.GetChunksInRange(b.ctx, dbBatch.StartChunkIndex, dbBatch.EndChunkIndex) if err != nil { return nil, fmt.Errorf("failed to get chunks in range: %v", err) } // check codec version - var batchBlocks []*encoding.Block for _, dbChunk := range dbChunks { if dbBatch.CodecVersion != dbChunk.CodecVersion { return nil, fmt.Errorf("batch codec version is different from chunk codec version, batch index: %d, chunk index: %d, batch codec version: %d, chunk codec version: %d", dbBatch.Index, dbChunk.Index, dbBatch.CodecVersion, dbChunk.CodecVersion) } + } - blocks, err := b.l2BlockOrm.GetL2BlocksInRange(b.ctx, dbChunk.StartBlockNumber, dbChunk.EndBlockNumber) - if err != nil { - return nil, fmt.Errorf("failed to get blocks in range for batch %d: %w", dbBatch.Index, err) + var encodingBatch *encoding.Batch + codecVersion := encoding.CodecVersion(dbBatch.CodecVersion) + switch codecVersion { + case encoding.CodecV0, encoding.CodecV1, encoding.CodecV2, encoding.CodecV3, encoding.CodecV4, encoding.CodecV5, encoding.CodecV6: + chunks := make([]*encoding.Chunk, len(dbChunks)) + for i, c := range dbChunks { + blocks, getErr := b.l2BlockOrm.GetL2BlocksInRange(b.ctx, c.StartBlockNumber, c.EndBlockNumber) + if getErr != nil { + return nil, fmt.Errorf("failed to get blocks in range for batch %d: %w", dbBatch.Index, err) + } + chunks[i] = &encoding.Chunk{Blocks: blocks} + } + + encodingBatch = &encoding.Batch{ + Index: dbBatch.Index, + TotalL1MessagePoppedBefore: dbChunks[0].TotalL1MessagesPoppedBefore, + ParentBatchHash: common.HexToHash(dbBatch.ParentBatchHash), + Chunks: chunks, } - batchBlocks = append(batchBlocks, blocks...) - } - - encodingBatch := &encoding.Batch{ - Index: dbBatch.Index, - ParentBatchHash: common.HexToHash(dbBatch.ParentBatchHash), - PrevL1MessageQueueHash: common.HexToHash(dbBatch.PrevL1MessageQueueHash), - PostL1MessageQueueHash: common.HexToHash(dbBatch.PostL1MessageQueueHash), - Blocks: batchBlocks, + case encoding.CodecV7: + var batchBlocks []*encoding.Block + for _, dbChunk := range dbChunks { + if dbBatch.CodecVersion != dbChunk.CodecVersion { + return nil, fmt.Errorf("batch codec version is different from chunk codec version, batch index: %d, chunk index: %d, batch codec version: %d, chunk codec version: %d", dbBatch.Index, dbChunk.Index, dbBatch.CodecVersion, dbChunk.CodecVersion) + } + + blocks, err := b.l2BlockOrm.GetL2BlocksInRange(b.ctx, dbChunk.StartBlockNumber, dbChunk.EndBlockNumber) + if err != nil { + return nil, fmt.Errorf("failed to get blocks in range for batch %d: %w", dbBatch.Index, err) + } + + batchBlocks = append(batchBlocks, blocks...) + } + + encodingBatch = &encoding.Batch{ + Index: dbBatch.Index, + ParentBatchHash: common.HexToHash(dbBatch.ParentBatchHash), + PrevL1MessageQueueHash: common.HexToHash(dbBatch.PrevL1MessageQueueHash), + PostL1MessageQueueHash: common.HexToHash(dbBatch.PostL1MessageQueueHash), + Blocks: batchBlocks, + } + default: + log.Error("unsupported codec version in UploadBlobToS3", "codecVersion", codecVersion, "batch index", dbBatch.Index) + return nil, fmt.Errorf("unsupported codec version, batch index: %d, batch codec version: %d, %w", dbBatch.Index, codecVersion, err) } - - version := encoding.CodecVersion(dbBatch.CodecVersion) - codec, err := encoding.CodecFromVersion(version) + + codec, err := encoding.CodecFromVersion(codecVersion) if err != nil { return nil, fmt.Errorf("failed to get codec from version %d, err: %w", dbBatch.CodecVersion, err) } @@ -163,4 +189,4 @@ func (b *BlobUploader) constructBlobCodecV7(dbBatch *orm.Batch) (*kzg4844.Blob, return daBatch.Blob(), nil -} +} \ No newline at end of file diff --git a/rollup/internal/controller/blob_uploader/blob_uploader_metrics.go b/rollup/internal/controller/blob_uploader/blob_uploader_metrics.go index 252aa11766..293705c258 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader_metrics.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader_metrics.go @@ -8,7 +8,8 @@ import ( ) type blobUploaderMetrics struct { - rollupBlobUploaderUploadToS3Total prometheus.Counter + rollupBlobUploaderUploadToS3SuccessTotal prometheus.Counter + rollupBlobUploaderUploadToS3FailedTotal prometheus.Counter } var ( @@ -19,9 +20,13 @@ var ( func initblobUploaderMetrics(reg prometheus.Registerer) *blobUploaderMetrics { initBlobUploaderMetricsOnce.Do(func() { blobUploaderMetric = &blobUploaderMetrics{ - rollupBlobUploaderUploadToS3Total: promauto.With(reg).NewCounter(prometheus.CounterOpts{ - Name: "rollup_blob_uploader_upload_to_s3_total", - Help: "The total number of upload blob to S3 run total", + rollupBlobUploaderUploadToS3SuccessTotal: promauto.With(reg).NewCounter(prometheus.CounterOpts{ + Name: "rollup_blob_uploader_upload_to_s3_success_total", + Help: "The total number of upload blob to S3 run success total", + }), + rollupBlobUploaderUploadToS3FailedTotal: promauto.With(reg).NewCounter(prometheus.CounterOpts{ + Name: "rollup_blob_uploader_upload_to_s3_failed_total", + Help: "The total number of upload blob to S3 run failed total", }), } }) From b1a8ba73c6e06813395d6925fd5f93ecddc596cc Mon Sep 17 00:00:00 2001 From: Morty Date: Wed, 4 Jun 2025 02:11:08 +0800 Subject: [PATCH 15/31] fix: ci --- .../controller/blob_uploader/blob_uploader.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/rollup/internal/controller/blob_uploader/blob_uploader.go b/rollup/internal/controller/blob_uploader/blob_uploader.go index b89fb9d9d2..43cb1081b9 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader.go @@ -83,7 +83,6 @@ func (b *BlobUploader) UploadBlobToS3() { return } - // calculate versioned blob hash versionedBlobHash, err := utils.CalculateVersionedBlobHash(*blob) if err != nil { @@ -132,7 +131,7 @@ func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, er var encodingBatch *encoding.Batch codecVersion := encoding.CodecVersion(dbBatch.CodecVersion) - switch codecVersion { + switch codecVersion { case encoding.CodecV0, encoding.CodecV1, encoding.CodecV2, encoding.CodecV3, encoding.CodecV4, encoding.CodecV5, encoding.CodecV6: chunks := make([]*encoding.Chunk, len(dbChunks)) for i, c := range dbChunks { @@ -142,7 +141,7 @@ func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, er } chunks[i] = &encoding.Chunk{Blocks: blocks} } - + encodingBatch = &encoding.Batch{ Index: dbBatch.Index, TotalL1MessagePoppedBefore: dbChunks[0].TotalL1MessagesPoppedBefore, @@ -156,15 +155,15 @@ func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, er if dbBatch.CodecVersion != dbChunk.CodecVersion { return nil, fmt.Errorf("batch codec version is different from chunk codec version, batch index: %d, chunk index: %d, batch codec version: %d, chunk codec version: %d", dbBatch.Index, dbChunk.Index, dbBatch.CodecVersion, dbChunk.CodecVersion) } - + blocks, err := b.l2BlockOrm.GetL2BlocksInRange(b.ctx, dbChunk.StartBlockNumber, dbChunk.EndBlockNumber) if err != nil { return nil, fmt.Errorf("failed to get blocks in range for batch %d: %w", dbBatch.Index, err) } - + batchBlocks = append(batchBlocks, blocks...) } - + encodingBatch = &encoding.Batch{ Index: dbBatch.Index, ParentBatchHash: common.HexToHash(dbBatch.ParentBatchHash), @@ -176,7 +175,7 @@ func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, er log.Error("unsupported codec version in UploadBlobToS3", "codecVersion", codecVersion, "batch index", dbBatch.Index) return nil, fmt.Errorf("unsupported codec version, batch index: %d, batch codec version: %d, %w", dbBatch.Index, codecVersion, err) } - + codec, err := encoding.CodecFromVersion(codecVersion) if err != nil { return nil, fmt.Errorf("failed to get codec from version %d, err: %w", dbBatch.CodecVersion, err) @@ -188,5 +187,4 @@ func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, er } return daBatch.Blob(), nil - -} \ No newline at end of file +} From ef88fef62a6fbef8e8bb816a611c76814100fbfc Mon Sep 17 00:00:00 2001 From: Morty Date: Wed, 4 Jun 2025 02:25:04 +0800 Subject: [PATCH 16/31] fix: address comments --- .../controller/blob_uploader/blob_uploader.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/rollup/internal/controller/blob_uploader/blob_uploader.go b/rollup/internal/controller/blob_uploader/blob_uploader.go index 43cb1081b9..597bd44e94 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader.go @@ -80,6 +80,9 @@ func (b *BlobUploader) UploadBlobToS3() { if err != nil { log.Error("failed to construct constructBlobCodec payload ", "codecVersion", codecVersion, "batch index", dbBatch.Index, "err", err) b.metrics.rollupBlobUploaderUploadToS3FailedTotal.Inc() + if updateErr := b.blobUploadOrm.InsertOrUpdateBlobUpload(b.ctx, dbBatch.Index, types.BlobStoragePlatformS3, types.BlobUploadStatusFailed); updateErr != nil { + log.Error("failed to update blob upload status to failed", "batch index", dbBatch.Index, "err", updateErr) + } return } @@ -87,6 +90,11 @@ func (b *BlobUploader) UploadBlobToS3() { versionedBlobHash, err := utils.CalculateVersionedBlobHash(*blob) if err != nil { log.Error("failed to calculate versioned blob hash", "batch index", dbBatch.Index, "err", err) + b.metrics.rollupBlobUploaderUploadToS3FailedTotal.Inc() + // Update status to failed + if updateErr := b.blobUploadOrm.InsertOrUpdateBlobUpload(b.ctx, dbBatch.Index, types.BlobStoragePlatformS3, types.BlobUploadStatusFailed); updateErr != nil { + log.Error("failed to update blob upload status to failed", "batch index", dbBatch.Index, "err", updateErr) + } return } @@ -137,7 +145,7 @@ func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, er for i, c := range dbChunks { blocks, getErr := b.l2BlockOrm.GetL2BlocksInRange(b.ctx, c.StartBlockNumber, c.EndBlockNumber) if getErr != nil { - return nil, fmt.Errorf("failed to get blocks in range for batch %d: %w", dbBatch.Index, err) + return nil, fmt.Errorf("failed to get blocks in range for batch %d: %w", dbBatch.Index, getErr) } chunks[i] = &encoding.Chunk{Blocks: blocks} } @@ -152,10 +160,6 @@ func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, er case encoding.CodecV7: var batchBlocks []*encoding.Block for _, dbChunk := range dbChunks { - if dbBatch.CodecVersion != dbChunk.CodecVersion { - return nil, fmt.Errorf("batch codec version is different from chunk codec version, batch index: %d, chunk index: %d, batch codec version: %d, chunk codec version: %d", dbBatch.Index, dbChunk.Index, dbBatch.CodecVersion, dbChunk.CodecVersion) - } - blocks, err := b.l2BlockOrm.GetL2BlocksInRange(b.ctx, dbChunk.StartBlockNumber, dbChunk.EndBlockNumber) if err != nil { return nil, fmt.Errorf("failed to get blocks in range for batch %d: %w", dbBatch.Index, err) @@ -173,7 +177,7 @@ func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, er } default: log.Error("unsupported codec version in UploadBlobToS3", "codecVersion", codecVersion, "batch index", dbBatch.Index) - return nil, fmt.Errorf("unsupported codec version, batch index: %d, batch codec version: %d, %w", dbBatch.Index, codecVersion, err) + return nil, fmt.Errorf("unsupported codec version, batch index: %d, batch codec version: %d", dbBatch.Index, codecVersion) } codec, err := encoding.CodecFromVersion(codecVersion) From 1d8a48ad30c238a44758e1961765591b14c39ed1 Mon Sep 17 00:00:00 2001 From: Morty Date: Wed, 4 Jun 2025 03:47:16 +0800 Subject: [PATCH 17/31] fix: constructBlobCodec --- .../blob_uploader/arweave_sender.go | 1 + .../controller/blob_uploader/blob_uploader.go | 24 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 rollup/internal/controller/blob_uploader/arweave_sender.go diff --git a/rollup/internal/controller/blob_uploader/arweave_sender.go b/rollup/internal/controller/blob_uploader/arweave_sender.go new file mode 100644 index 0000000000..7fb47f826d --- /dev/null +++ b/rollup/internal/controller/blob_uploader/arweave_sender.go @@ -0,0 +1 @@ +package blob_uploader diff --git a/rollup/internal/controller/blob_uploader/blob_uploader.go b/rollup/internal/controller/blob_uploader/blob_uploader.go index 597bd44e94..915a3a89a9 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader.go @@ -137,19 +137,19 @@ func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, er } } + chunks := make([]*encoding.Chunk, len(dbChunks)) + for i, c := range dbChunks { + blocks, getErr := b.l2BlockOrm.GetL2BlocksInRange(b.ctx, c.StartBlockNumber, c.EndBlockNumber) + if getErr != nil { + return nil, fmt.Errorf("failed to get blocks in range for batch %d: %w", dbBatch.Index, getErr) + } + chunks[i] = &encoding.Chunk{Blocks: blocks} + } + var encodingBatch *encoding.Batch codecVersion := encoding.CodecVersion(dbBatch.CodecVersion) switch codecVersion { case encoding.CodecV0, encoding.CodecV1, encoding.CodecV2, encoding.CodecV3, encoding.CodecV4, encoding.CodecV5, encoding.CodecV6: - chunks := make([]*encoding.Chunk, len(dbChunks)) - for i, c := range dbChunks { - blocks, getErr := b.l2BlockOrm.GetL2BlocksInRange(b.ctx, c.StartBlockNumber, c.EndBlockNumber) - if getErr != nil { - return nil, fmt.Errorf("failed to get blocks in range for batch %d: %w", dbBatch.Index, getErr) - } - chunks[i] = &encoding.Chunk{Blocks: blocks} - } - encodingBatch = &encoding.Batch{ Index: dbBatch.Index, TotalL1MessagePoppedBefore: dbChunks[0].TotalL1MessagesPoppedBefore, @@ -171,12 +171,12 @@ func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, er encodingBatch = &encoding.Batch{ Index: dbBatch.Index, ParentBatchHash: common.HexToHash(dbBatch.ParentBatchHash), + Chunks: chunks, PrevL1MessageQueueHash: common.HexToHash(dbBatch.PrevL1MessageQueueHash), PostL1MessageQueueHash: common.HexToHash(dbBatch.PostL1MessageQueueHash), Blocks: batchBlocks, } default: - log.Error("unsupported codec version in UploadBlobToS3", "codecVersion", codecVersion, "batch index", dbBatch.Index) return nil, fmt.Errorf("unsupported codec version, batch index: %d, batch codec version: %d", dbBatch.Index, codecVersion) } @@ -190,5 +190,9 @@ func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, er return nil, fmt.Errorf("failed to create DA batch: %w", err) } + if daBatch.Blob() == nil { + return nil, fmt.Errorf("codec version doesn't support blob, batch index: %d, batch codec version: %d, err: %w", dbBatch.Index, dbBatch.CodecVersion, err) + } + return daBatch.Blob(), nil } From 057052557055866efcf0cc1641ae67615de570d7 Mon Sep 17 00:00:00 2001 From: Morty <70688412+yiweichi@users.noreply.github.com> Date: Wed, 4 Jun 2025 15:26:16 +0800 Subject: [PATCH 18/31] Update rollup/cmd/blob_uploader/app/app.go Co-authored-by: colin <102356659+colinlyguo@users.noreply.github.com> --- rollup/cmd/blob_uploader/app/app.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/rollup/cmd/blob_uploader/app/app.go b/rollup/cmd/blob_uploader/app/app.go index 0b3ac1b08d..3ecb8137d4 100644 --- a/rollup/cmd/blob_uploader/app/app.go +++ b/rollup/cmd/blob_uploader/app/app.go @@ -74,11 +74,6 @@ func action(ctx *cli.Context) error { log.Crit("failed to create l2 relayer", "config file", cfgFile, "error", err) } - minCodecVersion := encoding.CodecVersion(ctx.Uint(utils.MinCodecVersionFlag.Name)) - if minCodecVersion < encoding.CodecV7 { - log.Crit("min codec version must be greater than or equal to CodecV7", "minCodecVersion", minCodecVersion) - } - go utils.Loop(subCtx, 2*time.Second, blobUploader.UploadBlobToS3) // Finish start all blob-uploader functions. From a434b2c736692e64319ccaf8e8fdf5624e3239e4 Mon Sep 17 00:00:00 2001 From: Morty <70688412+yiweichi@users.noreply.github.com> Date: Wed, 4 Jun 2025 15:30:51 +0800 Subject: [PATCH 19/31] Apply suggestions from code review Co-authored-by: colin <102356659+colinlyguo@users.noreply.github.com> --- rollup/cmd/blob_uploader/app/app.go | 3 --- .../controller/blob_uploader/blob_uploader_metrics.go | 6 +++--- rollup/internal/orm/blob_upload.go | 3 --- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/rollup/cmd/blob_uploader/app/app.go b/rollup/cmd/blob_uploader/app/app.go index 3ecb8137d4..3f6c884803 100644 --- a/rollup/cmd/blob_uploader/app/app.go +++ b/rollup/cmd/blob_uploader/app/app.go @@ -31,13 +31,10 @@ func init() { app.Usage = "The Scroll Blob Uploader" app.Version = version.Version app.Flags = append(app.Flags, utils.CommonFlags...) - app.Flags = append(app.Flags, utils.RollupRelayerFlags...) app.Commands = []*cli.Command{} app.Before = func(ctx *cli.Context) error { return utils.LogSetup(ctx) } - // Register `rollup-relayer-test` app for integration-test. - utils.RegisterSimulation(app, utils.RollupRelayerApp) } func action(ctx *cli.Context) error { diff --git a/rollup/internal/controller/blob_uploader/blob_uploader_metrics.go b/rollup/internal/controller/blob_uploader/blob_uploader_metrics.go index 293705c258..efdb25c083 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader_metrics.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader_metrics.go @@ -17,16 +17,16 @@ var ( blobUploaderMetric *blobUploaderMetrics ) -func initblobUploaderMetrics(reg prometheus.Registerer) *blobUploaderMetrics { +func initBlobUploaderMetrics(reg prometheus.Registerer) *blobUploaderMetrics { initBlobUploaderMetricsOnce.Do(func() { blobUploaderMetric = &blobUploaderMetrics{ rollupBlobUploaderUploadToS3SuccessTotal: promauto.With(reg).NewCounter(prometheus.CounterOpts{ Name: "rollup_blob_uploader_upload_to_s3_success_total", - Help: "The total number of upload blob to S3 run success total", + Help: "The total number of upload blob to S3 runs success total", }), rollupBlobUploaderUploadToS3FailedTotal: promauto.With(reg).NewCounter(prometheus.CounterOpts{ Name: "rollup_blob_uploader_upload_to_s3_failed_total", - Help: "The total number of upload blob to S3 run failed total", + Help: "The total number of upload blob to S3 runs failed total", }), } }) diff --git a/rollup/internal/orm/blob_upload.go b/rollup/internal/orm/blob_upload.go index cd51d0e999..5fca94c418 100644 --- a/rollup/internal/orm/blob_upload.go +++ b/rollup/internal/orm/blob_upload.go @@ -37,7 +37,6 @@ func (o *BlobUpload) InsertBlobUpload(ctx context.Context, batchIndex uint64, pl BatchIndex: batchIndex, Platform: int16(platform), Status: int16(status), - UpdatedAt: time.Now(), } db := o.db @@ -63,7 +62,6 @@ func (o *BlobUpload) UpdateBlobUploadStatus(ctx context.Context, batchIndex uint updates := map[string]interface{}{ "status": status, - "updated_at": time.Now(), } if err := db.Updates(updates).Error; err != nil { @@ -83,7 +81,6 @@ func (o *BlobUpload) InsertOrUpdateBlobUpload(ctx context.Context, batchIndex ui BatchIndex: batchIndex, Platform: int16(platform), Status: int16(status), - UpdatedAt: time.Now(), } if err := db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "batch_index"}, {Name: "platform"}}, From 4f9a98efe2bc58f8569e3841db013bd854cc68d3 Mon Sep 17 00:00:00 2001 From: Morty Date: Thu, 5 Jun 2025 00:14:55 +0800 Subject: [PATCH 20/31] fix: address comments --- common/utils/ethereum_test.go | 1 - database/migrate/migrate_test.go | 6 +- ...blob_upload.sql => 00027_ blob_upload.sql} | 8 +- ...0027_alter_batch_add_primary_key_index.sql | 9 -- go.work | 4 +- go.work.sum | 74 ++++++++++++-- rollup/cmd/blob_uploader/app/app.go | 1 - rollup/go.mod | 27 ++++- rollup/go.sum | 62 ++++++++++++ .../blob_uploader/arweave_sender.go | 11 +++ .../controller/blob_uploader/blob_uploader.go | 11 ++- rollup/internal/orm/batch.go | 4 +- rollup/internal/orm/blob_upload.go | 99 ++----------------- 13 files changed, 187 insertions(+), 130 deletions(-) rename database/migrate/migrations/{00028_ blob_upload.sql => 00027_ blob_upload.sql} (81%) delete mode 100644 database/migrate/migrations/00027_alter_batch_add_primary_key_index.sql diff --git a/common/utils/ethereum_test.go b/common/utils/ethereum_test.go index eaed61563b..54af9243cf 100644 --- a/common/utils/ethereum_test.go +++ b/common/utils/ethereum_test.go @@ -28,7 +28,6 @@ func TestCalculateVersionedBlobHash(t *testing.T) { t.Fatalf("Failed to parse blobdata.json: %v", err) } - fmt.Println(blobData.BlobData) blobBytes, err := hex.DecodeString(blobData.BlobData) if err != nil { t.Fatalf("Failed to decode blob data: %v", err) diff --git a/database/migrate/migrate_test.go b/database/migrate/migrate_test.go index 90cc005c8d..8ab457bc48 100644 --- a/database/migrate/migrate_test.go +++ b/database/migrate/migrate_test.go @@ -59,20 +59,20 @@ func testResetDB(t *testing.T) { cur, err := Current(pgDB) assert.NoError(t, err) // total number of tables. - assert.Equal(t, int64(28), cur) + assert.Equal(t, int64(27), cur) } func testMigrate(t *testing.T) { assert.NoError(t, Migrate(pgDB)) cur, err := Current(pgDB) assert.NoError(t, err) - assert.Equal(t, int64(28), cur) + assert.Equal(t, int64(27), cur) } func testRollback(t *testing.T) { version, err := Current(pgDB) assert.NoError(t, err) - assert.Equal(t, int64(28), version) + assert.Equal(t, int64(27), version) assert.NoError(t, Rollback(pgDB, nil)) diff --git a/database/migrate/migrations/00028_ blob_upload.sql b/database/migrate/migrations/00027_ blob_upload.sql similarity index 81% rename from database/migrate/migrations/00028_ blob_upload.sql rename to database/migrate/migrations/00027_ blob_upload.sql index b874d08892..5826fb8543 100644 --- a/database/migrate/migrations/00028_ blob_upload.sql +++ b/database/migrate/migrations/00027_ blob_upload.sql @@ -8,11 +8,11 @@ CREATE TABLE blob_upload ( status SMALLINT NOT NULL, -- metadata - updated_at TIMESTAMP NOT NULL DEFAULT now(), - deleted_at TIMESTAMP(0) DEFAULT NULL, + created_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP(0) DEFAULT NULL - PRIMARY KEY (batch_index, platform), - FOREIGN KEY (batch_index) REFERENCES batch(index) + PRIMARY KEY (batch_index, platform) ); COMMENT ON COLUMN blob_upload.status IS 'undefined, pending, uploaded, failed'; diff --git a/database/migrate/migrations/00027_alter_batch_add_primary_key_index.sql b/database/migrate/migrations/00027_alter_batch_add_primary_key_index.sql deleted file mode 100644 index 3ce3377300..0000000000 --- a/database/migrate/migrations/00027_alter_batch_add_primary_key_index.sql +++ /dev/null @@ -1,9 +0,0 @@ --- +goose Up --- +goose StatementBegin -ALTER TABLE batch ADD PRIMARY KEY (index); --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -ALTER TABLE batch DROP CONSTRAINT batch_pkey; --- +goose StatementEnd \ No newline at end of file diff --git a/go.work b/go.work index b42509f9b4..4cbfe753ee 100644 --- a/go.work +++ b/go.work @@ -1,6 +1,4 @@ -go 1.22 - -toolchain go1.22.2 +go 1.22.4 use ( ./bridge-history-api diff --git a/go.work.sum b/go.work.sum index 12ee3bc22f..36f423a758 100644 --- a/go.work.sum +++ b/go.work.sum @@ -555,12 +555,16 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOC github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v6 v6.1.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= +github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= @@ -572,6 +576,7 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794/go.mod h1:7e+I0LQFUI9AXWxOfsQROs9xPhoJtbsyWcjJqDd4KPY= github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= @@ -646,8 +651,6 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.30.6/go.mod h1:PudwVKUTApfm0nYaPutOXa github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= -github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= -github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -656,6 +659,7 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bitly/go-hostpool v0.1.0 h1:XKmsF6k5el6xHG3WPJ8U0Ku/ye7njX7W81Ng7O2ioR0= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bitset v1.14.2/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bketelsen/crypt v0.0.4 h1:w/jqZtC9YD4DS/Vp9GhWfWcCpuAL58oTnLoI8vE9YHU= @@ -667,11 +671,7 @@ github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40 h1:y4B3+GPxKlrigF1ha github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= -github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= -github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= 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/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= @@ -686,6 +686,7 @@ github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= 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.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= @@ -716,7 +717,14 @@ github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XP github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v1.1.1/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= +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/compose-spec/compose-go v1.20.0 h1:h4ZKOst1EF/DwZp7dWkb+wbTVE4nEyT9Lc89to84Ol4= github.com/compose-spec/compose-go v1.20.0/go.mod h1:+MdqXV4RA7wdFsahh/Kb8U0pAJqkg7mr4PM9tFKU8RM= @@ -766,6 +774,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma 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.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-ipa v0.0.0-20230601170251-1830d0757c80/go.mod h1:gzbVz57IDJgQ9rLQwfSk696JGWof8ftznEL9GoAv3NI= +github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c h1:/ovYnF02fwL0kvspmy9AuyKg1JhdTRUgPw4nUxd9oZM= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= @@ -777,10 +786,8 @@ github.com/dchest/blake512 v1.0.0 h1:oDFEQFIqFSeuA34xLtXZ/rWxCXdSjirjzPhey5EUvmA github.com/dchest/blake512 v1.0.0/go.mod h1:FV1x7xPPLWukZlpDpWQ88rF/SFwZ5qbskrzhLMB92JI= github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/deepmap/oapi-codegen v1.6.0 h1:w/d1ntwh91XI0b/8ja7+u5SvA4IFfM0UNNLmiDR1gg0= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU= @@ -801,11 +808,13 @@ github.com/docker/cli-docs-tool v0.6.0/go.mod h1:zMjqTFCU361PRh8apiXzeAZ1Q/xupbI github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= +github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0/go.mod h1:56wL82FO0bfMU5RvfXoIwSOP2ggqqxT+tAfNEIyxuHw= github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48 h1:iZOop7pqsg+56twTopWgwCGxdB5SI2yDO8Ti7eTRliQ= github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf h1:Yt+4K30SdjOkRoRRm3vYNQgR+/ZIy0RmeUDZo7Y8zeQ= github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= +github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7 h1:tYwu/z8Y0NkkzGEh3z21mSWggMg4LwLRFucLS7TjARg= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= @@ -824,10 +833,12 @@ github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/Ir github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= +github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/c-kzg-4844/bindings/go v0.0.0-20230126171313-363c7d7593b4 h1:B2mpK+MNqgPqk2/KNi1LbqwtZDy5F7iy0mynQiBr8VA= github.com/ethereum/c-kzg-4844/bindings/go v0.0.0-20230126171313-363c7d7593b4/go.mod h1:y4GA2JbAUama1S4QwYjC2hefgGLU8Ul0GMtL/ADMF1c= github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= +github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0/go.mod h1:D9AJLVXSyZQXJQVk8oh1EwjISE+sJTn2duYIZC0dy3w= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= @@ -836,7 +847,9 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +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/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs= github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c h1:CndMRAH4JIwxbW8KYq6Q+cGWcGHz0FjGR3QqcInWcW0= github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY= github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY= @@ -856,6 +869,7 @@ github.com/getkin/kin-openapi v0.61.0 h1:6awGqF5nG5zkVpMsAih1QH4VgzS8phTxECUWIFo github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getsentry/sentry-go v0.11.0 h1:qro8uttJGvNAMr5CLcFI9CHR0aDzXl0Vs3Pmw/oTPg8= github.com/getsentry/sentry-go v0.11.0/go.mod h1:KBQIxiZAetw62Cj8Ri964vAEWVdgfaUCn30Q3bCvANo= +github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd h1:r04MMPyLHj/QwZuMJ5+7tJcBr1AQjpiAK/rZWRrQT7o= @@ -864,6 +878,7 @@ github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 h1:gclg6gY70GLy github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-chi/chi/v5 v5.0.0 h1:DBPx88FjZJH3FsICfDAfIfnb7XxKIYVGG6lOPlhENAg= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= 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 h1:b+9H1GAsx5RsjvDFLoS5zkNBzIQMuVKUYQDmxU3N5XE= @@ -875,6 +890,7 @@ github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -915,6 +931,7 @@ github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+Licev github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 h1:utua3L2IbQJmauC5IXdEA547bcoU5dozgQAfc8Onsg4= @@ -1055,7 +1072,10 @@ github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= +github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/holiman/uint256 v1.3.0/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150 h1:vlNjIqmUZ9CMAWsbURYl3a6wZbw7q5RHVvlXTNS/Bs8= @@ -1125,13 +1145,16 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5 h1:PJr+ZMXIecYc github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef h1:2jNeR4YUziVtswNP9sEFAI913cVrzH85T+8Q6LpYbT0= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= +github.com/karalabe/hid v1.0.1-0.20240306101548-573246063e52/go.mod h1:qk1sX/IBgppQNcGCRoj90u6EGC056EBoIc1oEjCWla8= github.com/karalabe/usb v0.0.0-20211005121534-4c5740d64559 h1:0VWDXPNE0brOek1Q8bLfzKkvOzwbQE/snjGojlCr8CY= github.com/karalabe/usb v0.0.0-20211005121534-4c5740d64559/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/karalabe/usb v0.0.2 h1:M6QQBNxF+CQ8OFvxrT90BA0qBOXymndZnk5q235mFc4= github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/kataras/blocks v0.0.7/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I= github.com/kataras/golog v0.1.7/go.mod h1:jOSQ+C5fUqsNSwurB/oAHq1IFSb0KI3l6GMa7xB6dZA= +github.com/kataras/golog v0.1.8/go.mod h1:rGPAin4hYROfk1qT9wZP6VY2rsb4zzc37QpdPjdkqVw= github.com/kataras/iris/v12 v12.2.0-beta5/go.mod h1:q26aoWJ0Knx/00iPKg5iizDK7oQQSPjbD8np0XDh6dc= +github.com/kataras/iris/v12 v12.2.0/go.mod h1:BLzBpEunc41GbE68OUaQlqX4jzi791mx5HU04uPb90Y= github.com/kataras/pio v0.0.11/go.mod h1:38hH6SWH6m4DKSYmRhlrCJ5WItwWgCVrTNU62XZyUvI= github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= @@ -1145,9 +1168,11 @@ github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfM github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5 h1:2U0HzY8BJ8hVwDKIzp7y4voR9CX/nvcfymLmg2UiOio= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6 h1:KAZ1BW2TCmT6PRihDPpocIy1QTtsAsrx6TneU/4+CMg= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada h1:3L+neHp83cTjegPdCiOxVOJtRIy7/8RldvMTsyPYH10= @@ -1166,9 +1191,11 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/labstack/echo/v4 v4.2.1 h1:LF5Iq7t/jrtUuSutNuiEWtB5eiHfZ5gSe2pcu5exjQw= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= +github.com/labstack/echo/v4 v4.10.0/go.mod h1:S/T/5fy/GigaXnHTkh0ZGe4LpkkQysvRjFMSUTkDRNQ= github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4= @@ -1188,6 +1215,7 @@ github.com/lyft/protoc-gen-star/v2 v2.0.3 h1:/3+/2sWyXeMLzKd1bX+ixWKgEMsULrIivpD github.com/lyft/protoc-gen-star/v2 v2.0.3/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailgun/raymond/v2 v2.0.46/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= +github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd h1:HvFwW+cm9bCbZ/+vuGNq7CRWXql8c0y8nGeYpqmpvmk= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -1209,12 +1237,15 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104 h1:d8RFOZ2IiFtFWBcKEHAFYJcPTf0wY5q0exFNJZVWa1U= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= +github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4= github.com/microsoft/go-mssqldb v1.6.0 h1:mM3gYdVwEPFrlg/Dvr2DNVEgYFG7L42l+dGc67NNNpc= github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -1267,6 +1298,7 @@ github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86 h1:D6paGObi5Wud7xg83MaEFyjxQB1W5bz5d0IFppr+ymk= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c h1:bY6ktFuJkt+ZXkX0RChQch2FtHpWQLVS8Qo1YasiIVk= @@ -1298,6 +1330,7 @@ github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= @@ -1318,15 +1351,24 @@ github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7/go.mod h1:IToEjHuttnUzwZI5KBSM/LOOW3qLbbrHOEfp3SbECGY= +github.com/protolambda/bls12-381-util v0.1.0/go.mod h1:cdkysJTRpeFeuUVx/TXGDQNMTiRAalk1vQw3TYTHcE4= +github.com/protolambda/messagediff v1.4.0/go.mod h1:LboJp0EwIbJsePYpzh5Op/9G1/4mIztMRYzzwR0dR2M= +github.com/protolambda/zrnt v0.32.2/go.mod h1:A0fezkp9Tt3GBLATSPIbuY4ywYESyAuc/FFmPKg8Lqs= +github.com/protolambda/ztyp v0.2.2/go.mod h1:9bYgKGqg3wJqT9ac1gI2hnVb0STQq7p/1lapqrqY1dU= +github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48/go.mod h1:4pWaT30XoEx1j8KNJf3TV+E3mQkaufn7mf+jRNb/Fuk= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52 h1:RnWNS9Hlm8BIkjr6wx8li5abe0fr73jljLycdfemTp0= @@ -1371,6 +1413,7 @@ github.com/segmentio/kafka-go v0.2.0 h1:HtCSf6B4gN/87yc5qTl7WsxPKQIIGXLPPM1bMCPO github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 h1:aSISeOcal5irEhJd1M+IrApc0PdcN7e7Aj4yuEnOrfQ= github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= @@ -1438,6 +1481,7 @@ github.com/valyala/fasthttp v1.40.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxn github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vektah/gqlparser/v2 v2.4.5 h1:C02NsyEsL4TXJB7ndonqTfuQOL4XPIu0aAWugdmTgmc= github.com/vektah/gqlparser/v2 v2.4.5/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0= github.com/veraison/go-cose v1.0.0-rc.1 h1:4qA7dbFJGvt7gcqv5MCIyCQvN+NpHFPkW7do3EeDLb8= @@ -1550,6 +1594,7 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= @@ -1557,6 +1602,7 @@ golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1571,6 +1617,7 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 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-20220426173459-3bcf042a4bf5/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= @@ -1599,6 +1646,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1638,6 +1686,7 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= 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= @@ -1659,7 +1708,6 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= @@ -1719,6 +1767,7 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -1751,6 +1800,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb 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.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1801,8 +1851,10 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= @@ -1954,6 +2006,8 @@ gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNj gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 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/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/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= diff --git a/rollup/cmd/blob_uploader/app/app.go b/rollup/cmd/blob_uploader/app/app.go index 3f6c884803..6ee84e11aa 100644 --- a/rollup/cmd/blob_uploader/app/app.go +++ b/rollup/cmd/blob_uploader/app/app.go @@ -8,7 +8,6 @@ import ( "time" "github.com/prometheus/client_golang/prometheus" - "github.com/scroll-tech/da-codec/encoding" "github.com/scroll-tech/go-ethereum/log" "github.com/urfave/cli/v2" diff --git a/rollup/go.mod b/rollup/go.mod index 72ee556bea..e4c3aa58be 100644 --- a/rollup/go.mod +++ b/rollup/go.mod @@ -1,6 +1,6 @@ module scroll-tech/rollup -go 1.22 +go 1.22.4 require ( github.com/agiledragon/gomonkey/v2 v2.12.0 @@ -42,17 +42,24 @@ require ( github.com/aws/smithy-go v1.22.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect - github.com/btcsuite/btcd v0.20.1-beta // indirect + github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd // indirect + github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect + github.com/btcsuite/btcd/btcutil v1.1.5 // indirect github.com/bytedance/sonic v1.10.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.0 // indirect github.com/consensys/bavard v0.1.29 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/ethereum/c-kzg-4844 v1.0.3 // indirect + github.com/ethereum/go-ethereum v1.14.7 // indirect + github.com/everFinance/ethrpc v1.0.4 // indirect + github.com/everFinance/goether v1.1.9 // indirect + github.com/everFinance/gojwk v1.0.0 // indirect github.com/fjl/memsize v0.0.2 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect @@ -64,7 +71,7 @@ require ( github.com/go-playground/validator/v10 v10.15.5 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/uuid v1.6.0 // indirect @@ -76,6 +83,7 @@ require ( github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/iden3/go-iden3-crypto v0.0.17 // indirect + github.com/inconshreveable/log15 v2.16.0+incompatible // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect @@ -84,6 +92,7 @@ require ( github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/leodido/go-urn v1.2.4 // indirect + github.com/linkedin/goavro/v2 v2.13.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -96,7 +105,9 @@ require ( github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/gomega v1.27.1 // indirect + github.com/panjf2000/ants/v2 v2.10.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/permadao/goar v1.1.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.4.0 // indirect @@ -111,6 +122,7 @@ require ( github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/scroll-tech/zktrie v0.8.4 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/smartystreets/assertions v1.13.1 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect @@ -120,6 +132,9 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/supranational/blst v0.3.13 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/tidwall/gjson v1.17.3 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.9.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect @@ -131,13 +146,15 @@ require ( go.uber.org/multierr v1.9.0 // indirect golang.org/x/arch v0.5.0 // indirect golang.org/x/crypto v0.32.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect + golang.org/x/term v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/h2non/gentleman.v2 v2.0.5 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/urfave/cli.v1 v1.20.0 // indirect diff --git a/rollup/go.sum b/rollup/go.sum index a7bf6c02ef..55c1716747 100644 --- a/rollup/go.sum +++ b/rollup/go.sum @@ -51,11 +51,27 @@ github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3M github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd h1:js1gPwhcFflTZ7Nzl7WHaOTlTr5hIrR4n1NM4v9n4Kw= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= +github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= +github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= @@ -67,6 +83,7 @@ github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -88,11 +105,23 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X6GqbFowYdYdI0L9bwxL07jyPZIdepyZ0= github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/ethereum/c-kzg-4844 v1.0.3 h1:IEnbOHwjixW2cTvKRUlAAUOeleV7nNM/umJR+qy4WDs= github.com/ethereum/c-kzg-4844 v1.0.3/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/go-ethereum v1.14.7 h1:EHpv3dE8evQmpVEQ/Ne2ahB06n2mQptdwqaMNhAT29g= +github.com/ethereum/go-ethereum v1.14.7/go.mod h1:Mq0biU2jbdmKSZoqOj29017ygFrMnB5/Rifwp980W4o= +github.com/everFinance/ethrpc v1.0.4 h1:Ww+qr8D93Id5QkyG5Mvw58edu5tqy0sL6hDP0IfhYsE= +github.com/everFinance/ethrpc v1.0.4/go.mod h1:cQipdwW4kM1v8C+q8Z+jDDXwL7a3KngvNk9Yo+lbXpI= +github.com/everFinance/goether v1.1.9 h1:Y/zz/chv0CmoXz119J3ZK4WbGoHnMjm/IDH5qwKrvVU= +github.com/everFinance/goether v1.1.9/go.mod h1:QhUIRE3g4CPN4+OGz96pIwguyRH1hZfYo2gAUSY00Qw= +github.com/everFinance/gojwk v1.0.0 h1:le/oI2NgXlrqg3MHU6ka+V30EWcD7TD6+Ilh+go7924= +github.com/everFinance/gojwk v1.0.0/go.mod h1:icXSXsIdpAczlpAtSljQlmABkMTRZENr73KHmo0GOGc= github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -144,6 +173,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +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.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= @@ -177,9 +208,13 @@ github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/iden3/go-iden3-crypto v0.0.17 h1:NdkceRLJo/pI4UpcjVah4lN/a3yzxRUGXqxbWcYh9mY= github.com/iden3/go-iden3-crypto v0.0.17/go.mod h1:dLpM4vEPJ3nDHzhWFXDjzkn1qHoBeOT/3UEhXsEsP3E= +github.com/inconshreveable/log15 v2.16.0+incompatible h1:6nvMKxtGcpgm7q0KiGs+Vc+xDvUXaBqsPKHWKsinccw= +github.com/inconshreveable/log15 v2.16.0+incompatible/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= 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/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -205,6 +240,8 @@ github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzW github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/linkedin/goavro/v2 v2.13.0 h1:L8eI8GcuciwUkt41Ej62joSZS4kKaYIUdze+6for9NU= +github.com/linkedin/goavro/v2 v2.13.0/go.mod h1:KXx+erlq+RPlGSPmLF7xGo6SAbh8sCQ53x064+ioxhk= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -243,13 +280,18 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 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/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754= github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw= +github.com/panjf2000/ants/v2 v2.10.0 h1:zhRg1pQUtkyRiOFo2Sbqwjp0GfBNo9cUY2/Grpx1p+8= +github.com/panjf2000/ants/v2 v2.10.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/permadao/goar v1.1.1 h1:C1Sv/v57SOAb59qn/zH9UXgE9u+3e6bYIqZ8ocrJt1E= +github.com/permadao/goar v1.1.1/go.mod h1:HgUtU74aB557YRhFav63BI8Z23X9ehT7upia0ZhSL5s= github.com/pkg/errors v0.8.0/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= @@ -293,6 +335,8 @@ github.com/scroll-tech/zktrie v0.8.4 h1:UagmnZ4Z3ITCk+aUq9NQZJNAwnWl4gSxsLb2Nl7I github.com/scroll-tech/zktrie v0.8.4/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk= 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 v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.13.1 h1:Ef7KhSmjZcK6AVf9YbJdvPYG9avaF0ZxudX+ThRdWfU= github.com/smartystreets/assertions v1.13.1/go.mod h1:cXr/IwVfSo/RbCSPhoAPv73p3hlSdrBH/b3SdnW/LMY= @@ -319,8 +363,10 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -334,6 +380,15 @@ github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKt github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tidwall/gjson v1.6.3/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0= +github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= +github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= @@ -366,7 +421,9 @@ 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-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -383,6 +440,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -408,6 +466,8 @@ golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -439,6 +499,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/h2non/gentleman.v2 v2.0.5 h1:ckmb6cLxL2DDk7WN7LSdxXDq7jNkOicFg4JZ4ZnDNuE= +gopkg.in/h2non/gentleman.v2 v2.0.5/go.mod h1:A1c7zwrTgAyyf6AbpvVksYtBayTB4STBUGmdkEtlHeA= 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/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= diff --git a/rollup/internal/controller/blob_uploader/arweave_sender.go b/rollup/internal/controller/blob_uploader/arweave_sender.go index 7fb47f826d..796fc3fabd 100644 --- a/rollup/internal/controller/blob_uploader/arweave_sender.go +++ b/rollup/internal/controller/blob_uploader/arweave_sender.go @@ -1 +1,12 @@ package blob_uploader + +import ( + "fmt" + "math/big" + "github.com/permadao/goar/schema" + "github.com/permadao/goar" +) + +type ArweaveUploader struct { + +} \ No newline at end of file diff --git a/rollup/internal/controller/blob_uploader/blob_uploader.go b/rollup/internal/controller/blob_uploader/blob_uploader.go index 915a3a89a9..d5f8d39e6d 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader.go @@ -55,14 +55,19 @@ func NewBlobUploader(ctx context.Context, db *gorm.DB, cfg *config.BlobUploaderC blobUploadOrm: orm.NewBlobUpload(db), } - blobUploader.metrics = initblobUploaderMetrics(reg) + blobUploader.metrics = initBlobUploaderMetrics(reg) return blobUploader, nil } func (b *BlobUploader) UploadBlobToS3() { + // skip upload if s3 uploader is not configured + if b.s3Uploader == nil { + return + } + // get un-uploaded batches from database in ascending order by their index. - dbBatch, err := b.batchOrm.GetFirstUnuploadedAndFailedBatch(b.ctx, b.cfg.StartBatch, types.BlobStoragePlatformS3) + dbBatch, err := b.batchOrm.GetFirstUnuploadedAndFailedBatchByPlatform(b.ctx, b.cfg.StartBatch, types.BlobStoragePlatformS3) if err != nil { log.Error("Failed to fetch unuploaded batch", "err", err) return @@ -191,7 +196,7 @@ func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, er } if daBatch.Blob() == nil { - return nil, fmt.Errorf("codec version doesn't support blob, batch index: %d, batch codec version: %d, err: %w", dbBatch.Index, dbBatch.CodecVersion, err) + return nil, fmt.Errorf("codec version doesn't support blob, batch index: %d, batch codec version: %d", dbBatch.Index, dbBatch.CodecVersion) } return daBatch.Blob(), nil diff --git a/rollup/internal/orm/batch.go b/rollup/internal/orm/batch.go index 756939f5b1..53386812c5 100644 --- a/rollup/internal/orm/batch.go +++ b/rollup/internal/orm/batch.go @@ -263,9 +263,9 @@ func (o *Batch) GetBatchByIndex(ctx context.Context, index uint64) (*Batch, erro return &batch, nil } -// GetFirstUnuploadedAndFailedBatch retrieves the first batch that either hasn't been uploaded to any blob storage service +// GetFirstUnuploadedAndFailedBatchByPlatform retrieves the first batch that either hasn't been uploaded to any blob storage service // or has failed upload status. The batch must have a commit_tx_hash (committed). -func (o *Batch) GetFirstUnuploadedAndFailedBatch(ctx context.Context, startBatch uint64, platform types.BlobStoragePlatform) (*Batch, error) { +func (o *Batch) GetFirstUnuploadedAndFailedBatchByPlatform(ctx context.Context, startBatch uint64, platform types.BlobStoragePlatform) (*Batch, error) { db := o.db.WithContext(ctx) db = db.Model(&Batch{}) db = db.Joins("LEFT JOIN blob_upload ON blob_upload.batch_index = batch.index AND blob_upload.platform = ?", platform) diff --git a/rollup/internal/orm/blob_upload.go b/rollup/internal/orm/blob_upload.go index 5fca94c418..79daa3838a 100644 --- a/rollup/internal/orm/blob_upload.go +++ b/rollup/internal/orm/blob_upload.go @@ -15,10 +15,15 @@ import ( type BlobUpload struct { db *gorm.DB `gorm:"-"` - BatchIndex uint64 `json:"batch_index" gorm:"column:batch_index;primaryKey"` - Platform int16 `json:"platform" gorm:"column:platform;primaryKey"` - Status int16 `json:"status" gorm:"column:status"` - UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` + // blob upload + BatchIndex uint64 `json:"batch_index" gorm:"column:batch_index;primaryKey"` + Platform int16 `json:"platform" gorm:"column:platform;primaryKey"` + Status int16 `json:"status" gorm:"column:status"` + + // metadata + CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` + UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` + DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"column:deleted_at;default:NULL"` } // NewBlobUpload creates a new BlobUpload database instance. @@ -31,45 +36,6 @@ func (*BlobUpload) TableName() string { return "blob_upload" } -// InsertBlobUpload inserts a new blob upload record into the database. -func (o *BlobUpload) InsertBlobUpload(ctx context.Context, batchIndex uint64, platform types.BlobStoragePlatform, status types.BlobUploadStatus, dbTX ...*gorm.DB) error { - blobUpload := &BlobUpload{ - BatchIndex: batchIndex, - Platform: int16(platform), - Status: int16(status), - } - - db := o.db - if len(dbTX) > 0 && dbTX[0] != nil { - db = dbTX[0] - } - db = db.WithContext(ctx) - if err := db.Create(blobUpload).Error; err != nil { - return fmt.Errorf("BlobUpload.InsertBlobUpload error: %w, batch index: %v, platform: %v", err, batchIndex, platform) - } - return nil -} - -// UpdateBlobUploadStatus updates the status of a blob upload record. -func (o *BlobUpload) UpdateBlobUploadStatus(ctx context.Context, batchIndex uint64, platform types.BlobStoragePlatform, status types.BlobUploadStatus, dbTX ...*gorm.DB) error { - db := o.db - if len(dbTX) > 0 && dbTX[0] != nil { - db = dbTX[0] - } - db = db.WithContext(ctx) - db = db.Model(&BlobUpload{}) - db = db.Where("batch_index = ? AND platform = ?", batchIndex, platform) - - updates := map[string]interface{}{ - "status": status, - } - - if err := db.Updates(updates).Error; err != nil { - return fmt.Errorf("BlobUpload.UpdateBlobUploadStatus error: %w, batch index: %v, platform: %v", err, batchIndex, platform) - } - return nil -} - // InsertOrUpdateBlobUpload inserts a new blob upload record or updates the existing one. func (o *BlobUpload) InsertOrUpdateBlobUpload(ctx context.Context, batchIndex uint64, platform types.BlobStoragePlatform, status types.BlobUploadStatus, dbTX ...*gorm.DB) error { db := o.db @@ -84,54 +50,9 @@ func (o *BlobUpload) InsertOrUpdateBlobUpload(ctx context.Context, batchIndex ui } if err := db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "batch_index"}, {Name: "platform"}}, - DoUpdates: clause.AssignmentColumns([]string{"status", "updated_at"}), + DoUpdates: clause.AssignmentColumns([]string{"status"}), }).Create(blobUpload).Error; err != nil { return fmt.Errorf("BlobUpload.InsertOrUpdateBlobUpload error: %w, batch index: %v, platform: %v", err, batchIndex, platform) } return nil } - -// GetBlobUploadByBatchIndexAndPlatform retrieves a blob upload record by batch index and platform. -func (o *BlobUpload) GetBlobUploadByBatchIndexAndPlatform(ctx context.Context, batchIndex uint64, platform types.BlobStoragePlatform) (*BlobUpload, error) { - db := o.db.WithContext(ctx) - db = db.Model(&BlobUpload{}) - db = db.Where("batch_index = ? AND platform = ?", batchIndex, platform) - - var blobUpload BlobUpload - if err := db.First(&blobUpload).Error; err != nil { - if err == gorm.ErrRecordNotFound { - return nil, nil - } - return nil, fmt.Errorf("BlobUpload.GetBlobUploadByBatchIndexAndPlatform error: %w, batch index: %v, platform: %v", err, batchIndex, platform) - } - return &blobUpload, nil -} - -// GetPendingBlobUploadsByPlatform retrieves all pending blob upload records by platform. -func (o *BlobUpload) GetPendingBlobUploadsByPlatform(ctx context.Context, platform types.BlobStoragePlatform) ([]*BlobUpload, error) { - db := o.db.WithContext(ctx) - db = db.Model(&BlobUpload{}) - db = db.Where("status = ? AND platform = ?", types.BlobUploadStatusPending, platform) - db = db.Order("batch_index ASC") - - var blobUploads []*BlobUpload - if err := db.Find(&blobUploads).Error; err != nil { - return nil, fmt.Errorf("BlobUpload.GetPendingBlobUploadsByPlatform error: %w", err) - } - return blobUploads, nil -} - -// GetFailedBlobUploadsByPlatform retrieves all failed blob upload records by platform. -func (o *BlobUpload) GetFailedBlobUploadsByPlatform(ctx context.Context, platform types.BlobStoragePlatform) ([]*BlobUpload, error) { - - db := o.db.WithContext(ctx) - db = db.Model(&BlobUpload{}) - db = db.Where("status = ? AND platform = ?", types.BlobUploadStatusFailed, platform) - db = db.Order("batch_index ASC") - - var blobUploads []*BlobUpload - if err := db.Find(&blobUploads).Error; err != nil { - return nil, fmt.Errorf("BlobUpload.GetFailedBlobUploadsByPlatform error: %w", err) - } - return blobUploads, nil -} From 1ba262a2ad64cd24f9667f5b264473dcad1f3b6f Mon Sep 17 00:00:00 2001 From: Morty Date: Fri, 6 Jun 2025 00:03:30 +0800 Subject: [PATCH 21/31] fix: remove left join --- common/utils/ethereum_test.go | 1 - .../migrate/migrations/00027_ blob_upload.sql | 4 +- .../blob_uploader/arweave_sender.go | 11 ------ .../controller/blob_uploader/blob_uploader.go | 2 +- rollup/internal/orm/batch.go | 37 +++++++++++++------ 5 files changed, 29 insertions(+), 26 deletions(-) diff --git a/common/utils/ethereum_test.go b/common/utils/ethereum_test.go index 54af9243cf..96ed5fd638 100644 --- a/common/utils/ethereum_test.go +++ b/common/utils/ethereum_test.go @@ -3,7 +3,6 @@ package utils import ( "encoding/hex" "encoding/json" - "fmt" "os" "testing" diff --git a/database/migrate/migrations/00027_ blob_upload.sql b/database/migrate/migrations/00027_ blob_upload.sql index 5826fb8543..16ac1d9f36 100644 --- a/database/migrate/migrations/00027_ blob_upload.sql +++ b/database/migrate/migrations/00027_ blob_upload.sql @@ -10,7 +10,7 @@ CREATE TABLE blob_upload ( -- metadata created_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, - deleted_at TIMESTAMP(0) DEFAULT NULL + deleted_at TIMESTAMP(0) DEFAULT NULL, PRIMARY KEY (batch_index, platform) ); @@ -23,6 +23,8 @@ CREATE INDEX IF NOT EXISTS idx_blob_upload_platform ON blob_upload(platform) WHE CREATE INDEX IF NOT EXISTS idx_blob_upload_status ON blob_upload(status) WHERE deleted_at IS NULL; +CREATE INDEX IF NOT EXISTS idx_blob_upload_created_at ON blob_upload(created_at) WHERE deleted_at IS NULL; + CREATE INDEX IF NOT EXISTS idx_blob_upload_updated_at ON blob_upload(updated_at) WHERE deleted_at IS NULL; CREATE INDEX IF NOT EXISTS idx_blob_upload_status_platform ON blob_upload(status, platform) WHERE deleted_at IS NULL; diff --git a/rollup/internal/controller/blob_uploader/arweave_sender.go b/rollup/internal/controller/blob_uploader/arweave_sender.go index 796fc3fabd..7fb47f826d 100644 --- a/rollup/internal/controller/blob_uploader/arweave_sender.go +++ b/rollup/internal/controller/blob_uploader/arweave_sender.go @@ -1,12 +1 @@ package blob_uploader - -import ( - "fmt" - "math/big" - "github.com/permadao/goar/schema" - "github.com/permadao/goar" -) - -type ArweaveUploader struct { - -} \ No newline at end of file diff --git a/rollup/internal/controller/blob_uploader/blob_uploader.go b/rollup/internal/controller/blob_uploader/blob_uploader.go index d5f8d39e6d..3917f702b2 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader.go @@ -67,7 +67,7 @@ func (b *BlobUploader) UploadBlobToS3() { } // get un-uploaded batches from database in ascending order by their index. - dbBatch, err := b.batchOrm.GetFirstUnuploadedAndFailedBatchByPlatform(b.ctx, b.cfg.StartBatch, types.BlobStoragePlatformS3) + dbBatch, err := b.batchOrm.GetFirstUnuploadedBatchByPlatform(b.ctx, b.cfg.StartBatch, types.BlobStoragePlatformS3) if err != nil { log.Error("Failed to fetch unuploaded batch", "err", err) return diff --git a/rollup/internal/orm/batch.go b/rollup/internal/orm/batch.go index 53386812c5..ca0bfc28bf 100644 --- a/rollup/internal/orm/batch.go +++ b/rollup/internal/orm/batch.go @@ -263,25 +263,38 @@ func (o *Batch) GetBatchByIndex(ctx context.Context, index uint64) (*Batch, erro return &batch, nil } -// GetFirstUnuploadedAndFailedBatchByPlatform retrieves the first batch that either hasn't been uploaded to any blob storage service +// GetFirstUnuploadedBatchByPlatform retrieves the first batch that either hasn't been uploaded to corresponding blob storage service // or has failed upload status. The batch must have a commit_tx_hash (committed). -func (o *Batch) GetFirstUnuploadedAndFailedBatchByPlatform(ctx context.Context, startBatch uint64, platform types.BlobStoragePlatform) (*Batch, error) { +func (o *Batch) GetFirstUnuploadedBatchByPlatform(ctx context.Context, startBatch uint64, platform types.BlobStoragePlatform) (*Batch, error) { db := o.db.WithContext(ctx) - db = db.Model(&Batch{}) - db = db.Joins("LEFT JOIN blob_upload ON blob_upload.batch_index = batch.index AND blob_upload.platform = ?", platform) - db = db.Where("batch.commit_tx_hash IS NOT NULL AND batch.index >= ?", startBatch) - db = db.Where("blob_upload.batch_index IS NULL OR blob_upload.status = ?", types.BlobUploadStatusFailed) - db = db.Order("batch.index ASC") + db = db.Model(&BlobUpload{}) + db = db.Where("platform = ? AND status = ?", platform, types.BlobUploadStatusUploaded) + db = db.Order("batch_index DESC") db = db.Limit(1) - var batch Batch - if err := db.First(&batch).Error; err != nil { + var blobUpload BlobUpload + var BatchIndex uint64 + if err := db.First(&blobUpload).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, nil + BatchIndex = startBatch + } else { + return nil, fmt.Errorf("Batch.GetLatestSuccessfulBlobUploadIndex error: %w", err) } - return nil, fmt.Errorf("Batch.GetFirstUnuploadedAndFailedBatch error: %w", err) + } else { + BatchIndex = blobUpload.BatchIndex + 1 } - return &batch, nil + + batch, err := o.GetBatchByIndex(ctx, BatchIndex) + if err != nil { + return nil, fmt.Errorf("Batch.GetLatestSuccessfulBlobUploadIndex error: %w", err) + } + + if len(batch.CommitTxHash) == 0 { + log.Debug("got uncommitted un-uploaded batch", "index", batch.Index, "platform", int16(platform)) + return nil, nil + } + + return batch, nil } // InsertBatch inserts a new batch into the database. From 94c8e4a0aaefab754ddf06ad4343046a1215662c Mon Sep 17 00:00:00 2001 From: Morty Date: Fri, 6 Jun 2025 00:41:49 +0800 Subject: [PATCH 22/31] fix: ci --- rollup/go.mod | 27 +++++----------------- rollup/go.sum | 62 --------------------------------------------------- 2 files changed, 5 insertions(+), 84 deletions(-) diff --git a/rollup/go.mod b/rollup/go.mod index e4c3aa58be..72ee556bea 100644 --- a/rollup/go.mod +++ b/rollup/go.mod @@ -1,6 +1,6 @@ module scroll-tech/rollup -go 1.22.4 +go 1.22 require ( github.com/agiledragon/gomonkey/v2 v2.12.0 @@ -42,24 +42,17 @@ require ( github.com/aws/smithy-go v1.22.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect - github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd // indirect - github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect - github.com/btcsuite/btcd/btcutil v1.1.5 // indirect + github.com/btcsuite/btcd v0.20.1-beta // indirect github.com/bytedance/sonic v1.10.1 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.0 // indirect github.com/consensys/bavard v0.1.29 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/ethereum/c-kzg-4844 v1.0.3 // indirect - github.com/ethereum/go-ethereum v1.14.7 // indirect - github.com/everFinance/ethrpc v1.0.4 // indirect - github.com/everFinance/goether v1.1.9 // indirect - github.com/everFinance/gojwk v1.0.0 // indirect github.com/fjl/memsize v0.0.2 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect @@ -71,7 +64,7 @@ require ( github.com/go-playground/validator/v10 v10.15.5 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/uuid v1.6.0 // indirect @@ -83,7 +76,6 @@ require ( github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/iden3/go-iden3-crypto v0.0.17 // indirect - github.com/inconshreveable/log15 v2.16.0+incompatible // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect @@ -92,7 +84,6 @@ require ( github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/leodido/go-urn v1.2.4 // indirect - github.com/linkedin/goavro/v2 v2.13.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -105,9 +96,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/gomega v1.27.1 // indirect - github.com/panjf2000/ants/v2 v2.10.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/permadao/goar v1.1.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.4.0 // indirect @@ -122,7 +111,6 @@ require ( github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/scroll-tech/zktrie v0.8.4 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect - github.com/shopspring/decimal v1.4.0 // indirect github.com/smartystreets/assertions v1.13.1 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect @@ -132,9 +120,6 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/supranational/blst v0.3.13 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect - github.com/tidwall/gjson v1.17.3 // indirect - github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.0 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.9.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect @@ -146,15 +131,13 @@ require ( go.uber.org/multierr v1.9.0 // indirect golang.org/x/arch v0.5.0 // indirect golang.org/x/crypto v0.32.0 // indirect - golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/protobuf v1.33.0 // indirect - gopkg.in/h2non/gentleman.v2 v2.0.5 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/urfave/cli.v1 v1.20.0 // indirect diff --git a/rollup/go.sum b/rollup/go.sum index 55c1716747..a7bf6c02ef 100644 --- a/rollup/go.sum +++ b/rollup/go.sum @@ -51,27 +51,11 @@ github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3M github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= -github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd h1:js1gPwhcFflTZ7Nzl7WHaOTlTr5hIrR4n1NM4v9n4Kw= -github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= -github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= -github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= -github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= -github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= -github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= -github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= -github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= @@ -83,7 +67,6 @@ github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -105,23 +88,11 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X6GqbFowYdYdI0L9bwxL07jyPZIdepyZ0= github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/ethereum/c-kzg-4844 v1.0.3 h1:IEnbOHwjixW2cTvKRUlAAUOeleV7nNM/umJR+qy4WDs= github.com/ethereum/c-kzg-4844 v1.0.3/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/ethereum/go-ethereum v1.14.7 h1:EHpv3dE8evQmpVEQ/Ne2ahB06n2mQptdwqaMNhAT29g= -github.com/ethereum/go-ethereum v1.14.7/go.mod h1:Mq0biU2jbdmKSZoqOj29017ygFrMnB5/Rifwp980W4o= -github.com/everFinance/ethrpc v1.0.4 h1:Ww+qr8D93Id5QkyG5Mvw58edu5tqy0sL6hDP0IfhYsE= -github.com/everFinance/ethrpc v1.0.4/go.mod h1:cQipdwW4kM1v8C+q8Z+jDDXwL7a3KngvNk9Yo+lbXpI= -github.com/everFinance/goether v1.1.9 h1:Y/zz/chv0CmoXz119J3ZK4WbGoHnMjm/IDH5qwKrvVU= -github.com/everFinance/goether v1.1.9/go.mod h1:QhUIRE3g4CPN4+OGz96pIwguyRH1hZfYo2gAUSY00Qw= -github.com/everFinance/gojwk v1.0.0 h1:le/oI2NgXlrqg3MHU6ka+V30EWcD7TD6+Ilh+go7924= -github.com/everFinance/gojwk v1.0.0/go.mod h1:icXSXsIdpAczlpAtSljQlmABkMTRZENr73KHmo0GOGc= github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -173,8 +144,6 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -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.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= @@ -208,13 +177,9 @@ github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/iden3/go-iden3-crypto v0.0.17 h1:NdkceRLJo/pI4UpcjVah4lN/a3yzxRUGXqxbWcYh9mY= github.com/iden3/go-iden3-crypto v0.0.17/go.mod h1:dLpM4vEPJ3nDHzhWFXDjzkn1qHoBeOT/3UEhXsEsP3E= -github.com/inconshreveable/log15 v2.16.0+incompatible h1:6nvMKxtGcpgm7q0KiGs+Vc+xDvUXaBqsPKHWKsinccw= -github.com/inconshreveable/log15 v2.16.0+incompatible/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= 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/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -240,8 +205,6 @@ github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzW github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/linkedin/goavro/v2 v2.13.0 h1:L8eI8GcuciwUkt41Ej62joSZS4kKaYIUdze+6for9NU= -github.com/linkedin/goavro/v2 v2.13.0/go.mod h1:KXx+erlq+RPlGSPmLF7xGo6SAbh8sCQ53x064+ioxhk= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -280,18 +243,13 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 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/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754= github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw= -github.com/panjf2000/ants/v2 v2.10.0 h1:zhRg1pQUtkyRiOFo2Sbqwjp0GfBNo9cUY2/Grpx1p+8= -github.com/panjf2000/ants/v2 v2.10.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/permadao/goar v1.1.1 h1:C1Sv/v57SOAb59qn/zH9UXgE9u+3e6bYIqZ8ocrJt1E= -github.com/permadao/goar v1.1.1/go.mod h1:HgUtU74aB557YRhFav63BI8Z23X9ehT7upia0ZhSL5s= github.com/pkg/errors v0.8.0/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= @@ -335,8 +293,6 @@ github.com/scroll-tech/zktrie v0.8.4 h1:UagmnZ4Z3ITCk+aUq9NQZJNAwnWl4gSxsLb2Nl7I github.com/scroll-tech/zktrie v0.8.4/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk= 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 v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= -github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.13.1 h1:Ef7KhSmjZcK6AVf9YbJdvPYG9avaF0ZxudX+ThRdWfU= github.com/smartystreets/assertions v1.13.1/go.mod h1:cXr/IwVfSo/RbCSPhoAPv73p3hlSdrBH/b3SdnW/LMY= @@ -363,10 +319,8 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -380,15 +334,6 @@ github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKt github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/tidwall/gjson v1.6.3/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0= -github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= -github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= @@ -421,9 +366,7 @@ 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-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -440,7 +383,6 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -466,8 +408,6 @@ golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -499,8 +439,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/h2non/gentleman.v2 v2.0.5 h1:ckmb6cLxL2DDk7WN7LSdxXDq7jNkOicFg4JZ4ZnDNuE= -gopkg.in/h2non/gentleman.v2 v2.0.5/go.mod h1:A1c7zwrTgAyyf6AbpvVksYtBayTB4STBUGmdkEtlHeA= 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/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= From bf8a149146dfc5af3a8a8003a1ce71d157bea1b1 Mon Sep 17 00:00:00 2001 From: Morty Date: Fri, 6 Jun 2025 00:43:09 +0800 Subject: [PATCH 23/31] fix: typo --- go.work.sum | 4 ++++ rollup/internal/orm/batch.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/go.work.sum b/go.work.sum index 36f423a758..c09efd6876 100644 --- a/go.work.sum +++ b/go.work.sum @@ -671,7 +671,9 @@ github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40 h1:y4B3+GPxKlrigF1ha github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= 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/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= @@ -788,6 +790,8 @@ github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsP github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/deepmap/oapi-codegen v1.6.0 h1:w/d1ntwh91XI0b/8ja7+u5SvA4IFfM0UNNLmiDR1gg0= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU= diff --git a/rollup/internal/orm/batch.go b/rollup/internal/orm/batch.go index ca0bfc28bf..4510e6321a 100644 --- a/rollup/internal/orm/batch.go +++ b/rollup/internal/orm/batch.go @@ -264,7 +264,7 @@ func (o *Batch) GetBatchByIndex(ctx context.Context, index uint64) (*Batch, erro } // GetFirstUnuploadedBatchByPlatform retrieves the first batch that either hasn't been uploaded to corresponding blob storage service -// or has failed upload status. The batch must have a commit_tx_hash (committed). +// The batch must have a commit_tx_hash (committed). func (o *Batch) GetFirstUnuploadedBatchByPlatform(ctx context.Context, startBatch uint64, platform types.BlobStoragePlatform) (*Batch, error) { db := o.db.WithContext(ctx) db = db.Model(&BlobUpload{}) From c4c765fd7bb621abb2f1852e89824e54d3d6e8de Mon Sep 17 00:00:00 2001 From: Morty Date: Fri, 6 Jun 2025 01:23:21 +0800 Subject: [PATCH 24/31] perfect logs --- rollup/internal/controller/blob_uploader/blob_uploader.go | 2 +- rollup/internal/orm/batch.go | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/rollup/internal/controller/blob_uploader/blob_uploader.go b/rollup/internal/controller/blob_uploader/blob_uploader.go index 3917f702b2..3a80f3aaec 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader.go @@ -75,7 +75,7 @@ func (b *BlobUploader) UploadBlobToS3() { // nothing to do if we don't have any pending batches if dbBatch == nil { - log.Info("not found any un-uploaded batches") + log.Debug("no pending batches to upload") return } diff --git a/rollup/internal/orm/batch.go b/rollup/internal/orm/batch.go index 4510e6321a..14a65576dd 100644 --- a/rollup/internal/orm/batch.go +++ b/rollup/internal/orm/batch.go @@ -285,12 +285,8 @@ func (o *Batch) GetFirstUnuploadedBatchByPlatform(ctx context.Context, startBatc } batch, err := o.GetBatchByIndex(ctx, BatchIndex) - if err != nil { - return nil, fmt.Errorf("Batch.GetLatestSuccessfulBlobUploadIndex error: %w", err) - } - - if len(batch.CommitTxHash) == 0 { - log.Debug("got uncommitted un-uploaded batch", "index", batch.Index, "platform", int16(platform)) + if err != nil || len(batch.CommitTxHash) == 0 { + log.Debug("got batch not ready for blob uploading", "batch_index", batch.Index, "platform", platform.String()) return nil, nil } From 8ac3181018ccfe0a2c8ce2def0c6ba286be93d3c Mon Sep 17 00:00:00 2001 From: Morty Date: Fri, 6 Jun 2025 01:24:58 +0800 Subject: [PATCH 25/31] fix: typo --- rollup/internal/orm/batch.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rollup/internal/orm/batch.go b/rollup/internal/orm/batch.go index 14a65576dd..f8461a5237 100644 --- a/rollup/internal/orm/batch.go +++ b/rollup/internal/orm/batch.go @@ -273,20 +273,20 @@ func (o *Batch) GetFirstUnuploadedBatchByPlatform(ctx context.Context, startBatc db = db.Limit(1) var blobUpload BlobUpload - var BatchIndex uint64 + var batchIndex uint64 if err := db.First(&blobUpload).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - BatchIndex = startBatch + batchIndex = startBatch } else { return nil, fmt.Errorf("Batch.GetLatestSuccessfulBlobUploadIndex error: %w", err) } } else { - BatchIndex = blobUpload.BatchIndex + 1 + batchIndex = blobUpload.BatchIndex + 1 } - batch, err := o.GetBatchByIndex(ctx, BatchIndex) + batch, err := o.GetBatchByIndex(ctx, batchIndex) if err != nil || len(batch.CommitTxHash) == 0 { - log.Debug("got batch not ready for blob uploading", "batch_index", batch.Index, "platform", platform.String()) + log.Debug("got batch not ready for blob uploading", "batch_index", batchIndex, "platform", platform.String()) return nil, nil } From 6d08239f2d5b220d1947c69743c0224b953aa80f Mon Sep 17 00:00:00 2001 From: Morty Date: Fri, 6 Jun 2025 01:44:42 +0800 Subject: [PATCH 26/31] fix: logs --- rollup/internal/orm/batch.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/rollup/internal/orm/batch.go b/rollup/internal/orm/batch.go index f8461a5237..5e036ac6f7 100644 --- a/rollup/internal/orm/batch.go +++ b/rollup/internal/orm/batch.go @@ -278,15 +278,23 @@ func (o *Batch) GetFirstUnuploadedBatchByPlatform(ctx context.Context, startBatc if errors.Is(err, gorm.ErrRecordNotFound) { batchIndex = startBatch } else { - return nil, fmt.Errorf("Batch.GetLatestSuccessfulBlobUploadIndex error: %w", err) + return nil, fmt.Errorf("Batch.GetFirstUnuploadedBatchByPlatform error: %w", err) } } else { batchIndex = blobUpload.BatchIndex + 1 } batch, err := o.GetBatchByIndex(ctx, batchIndex) - if err != nil || len(batch.CommitTxHash) == 0 { - log.Debug("got batch not ready for blob uploading", "batch_index", batchIndex, "platform", platform.String()) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + log.Debug("got batch not proposed for blob uploading", "batch_index", batchIndex, "platform", platform.String()) + return nil, nil + } + return nil, fmt.Errorf("Batch.GetFirstUnuploadedBatchByPlatform error: %w", err) + } + + if len(batch.CommitTxHash) == 0 { + log.Debug("got batch not committed for blob uploading", "batch_index", batchIndex, "platform", platform.String()) return nil, nil } From 66ebecfc4d6bb332811b509658c8af473e09c806 Mon Sep 17 00:00:00 2001 From: Morty Date: Fri, 6 Jun 2025 01:56:23 +0800 Subject: [PATCH 27/31] feat: add blob-uploader docker ci --- .github/workflows/docker.yml | 45 ++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index b32e7ceed1..b42071a911 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -99,6 +99,51 @@ jobs: ${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }} ${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:latest + blob_uploader: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + - name: check repo and create it if not exist + env: + REPOSITORY: blob-uploader + run: | + aws --region ${{ env.AWS_REGION }} ecr describe-repositories --repository-names ${{ env.REPOSITORY }} && : || aws --region ${{ env.AWS_REGION }} ecr create-repository --repository-name ${{ env.REPOSITORY }} + - name: Build and push + uses: docker/build-push-action@v3 + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + REPOSITORY: blob-uploader + IMAGE_TAG: ${{ github.ref_name }} + with: + context: . + file: ./build/dockerfiles/blob_uploader.Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: | + scrolltech/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }} + scrolltech/${{ env.REPOSITORY }}:latest + ${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }} + ${{ env.ECR_REGISTRY }}/${{ env.REPOSITORY }}:latest + rollup-db-cli: runs-on: ubuntu-latest steps: From 4c5a1bf6d85700d6c6ab0c8084cf8088602406d0 Mon Sep 17 00:00:00 2001 From: Morty Date: Sun, 8 Jun 2025 21:44:33 +0800 Subject: [PATCH 28/31] fix: address comments --- common/utils/{ethereum.go => blob.go} | 0 common/utils/{ethereum_test.go => blob_test.go} | 0 database/migrate/migrations/00027_ blob_upload.sql | 4 ---- rollup/internal/controller/blob_uploader/blob_uploader.go | 4 +++- 4 files changed, 3 insertions(+), 5 deletions(-) rename common/utils/{ethereum.go => blob.go} (100%) rename common/utils/{ethereum_test.go => blob_test.go} (100%) diff --git a/common/utils/ethereum.go b/common/utils/blob.go similarity index 100% rename from common/utils/ethereum.go rename to common/utils/blob.go diff --git a/common/utils/ethereum_test.go b/common/utils/blob_test.go similarity index 100% rename from common/utils/ethereum_test.go rename to common/utils/blob_test.go diff --git a/database/migrate/migrations/00027_ blob_upload.sql b/database/migrate/migrations/00027_ blob_upload.sql index 16ac1d9f36..673a9e37a7 100644 --- a/database/migrate/migrations/00027_ blob_upload.sql +++ b/database/migrate/migrations/00027_ blob_upload.sql @@ -23,10 +23,6 @@ CREATE INDEX IF NOT EXISTS idx_blob_upload_platform ON blob_upload(platform) WHE CREATE INDEX IF NOT EXISTS idx_blob_upload_status ON blob_upload(status) WHERE deleted_at IS NULL; -CREATE INDEX IF NOT EXISTS idx_blob_upload_created_at ON blob_upload(created_at) WHERE deleted_at IS NULL; - -CREATE INDEX IF NOT EXISTS idx_blob_upload_updated_at ON blob_upload(updated_at) WHERE deleted_at IS NULL; - CREATE INDEX IF NOT EXISTS idx_blob_upload_status_platform ON blob_upload(status, platform) WHERE deleted_at IS NULL; CREATE INDEX IF NOT EXISTS idx_blob_upload_batch_index_status_platform ON blob_upload(batch_index, status, platform) WHERE deleted_at IS NULL; diff --git a/rollup/internal/controller/blob_uploader/blob_uploader.go b/rollup/internal/controller/blob_uploader/blob_uploader.go index 3a80f3aaec..3bb3770be4 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader.go @@ -154,7 +154,9 @@ func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, er var encodingBatch *encoding.Batch codecVersion := encoding.CodecVersion(dbBatch.CodecVersion) switch codecVersion { - case encoding.CodecV0, encoding.CodecV1, encoding.CodecV2, encoding.CodecV3, encoding.CodecV4, encoding.CodecV5, encoding.CodecV6: + case encoding.CodecV0: + return nil, fmt.Errorf("codec version 0 doesn't support blob, batch index: %d", dbBatch.Index) + case encoding.CodecV1, encoding.CodecV2, encoding.CodecV3, encoding.CodecV4, encoding.CodecV5, encoding.CodecV6: encodingBatch = &encoding.Batch{ Index: dbBatch.Index, TotalL1MessagePoppedBefore: dbChunks[0].TotalL1MessagesPoppedBefore, From 4c988a9b7a625d50a8b6443412dbcbfd00f168be Mon Sep 17 00:00:00 2001 From: Morty Date: Mon, 9 Jun 2025 16:46:05 +0800 Subject: [PATCH 29/31] fix: address comments --- .../controller/blob_uploader/blob_uploader.go | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/rollup/internal/controller/blob_uploader/blob_uploader.go b/rollup/internal/controller/blob_uploader/blob_uploader.go index 3bb3770be4..3b6c6b7591 100644 --- a/rollup/internal/controller/blob_uploader/blob_uploader.go +++ b/rollup/internal/controller/blob_uploader/blob_uploader.go @@ -96,7 +96,7 @@ func (b *BlobUploader) UploadBlobToS3() { if err != nil { log.Error("failed to calculate versioned blob hash", "batch index", dbBatch.Index, "err", err) b.metrics.rollupBlobUploaderUploadToS3FailedTotal.Inc() - // Update status to failed + // update status to failed if updateErr := b.blobUploadOrm.InsertOrUpdateBlobUpload(b.ctx, dbBatch.Index, types.BlobStoragePlatformS3, types.BlobUploadStatusFailed); updateErr != nil { log.Error("failed to update blob upload status to failed", "batch index", dbBatch.Index, "err", updateErr) } @@ -109,14 +109,14 @@ func (b *BlobUploader) UploadBlobToS3() { if err != nil { log.Error("failed to upload blob data to AWS S3", "batch index", dbBatch.Index, "versioned blob hash", key, "err", err) b.metrics.rollupBlobUploaderUploadToS3FailedTotal.Inc() - // Update status to failed - if err = b.blobUploadOrm.InsertOrUpdateBlobUpload(b.ctx, dbBatch.Index, types.BlobStoragePlatformS3, types.BlobUploadStatusFailed); err != nil { - log.Error("failed to update blob upload status to failed", "batch index", dbBatch.Index, "err", err) + // update status to failed + if updateErr := b.blobUploadOrm.InsertOrUpdateBlobUpload(b.ctx, dbBatch.Index, types.BlobStoragePlatformS3, types.BlobUploadStatusFailed); updateErr != nil { + log.Error("failed to update blob upload status to failed", "batch index", dbBatch.Index, "err", updateErr) } return } - // Update status to uploaded + // update status to uploaded if err = b.blobUploadOrm.InsertOrUpdateBlobUpload(b.ctx, dbBatch.Index, types.BlobStoragePlatformS3, types.BlobUploadStatusUploaded); err != nil { log.Error("failed to update blob upload status to uploaded", "batch index", dbBatch.Index, "err", err) b.metrics.rollupBlobUploaderUploadToS3FailedTotal.Inc() @@ -143,12 +143,14 @@ func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, er } chunks := make([]*encoding.Chunk, len(dbChunks)) + var allBlocks []*encoding.Block // collect blocks for CodecV7 for i, c := range dbChunks { blocks, getErr := b.l2BlockOrm.GetL2BlocksInRange(b.ctx, c.StartBlockNumber, c.EndBlockNumber) if getErr != nil { return nil, fmt.Errorf("failed to get blocks in range for batch %d: %w", dbBatch.Index, getErr) } chunks[i] = &encoding.Chunk{Blocks: blocks} + allBlocks = append(allBlocks, blocks...) } var encodingBatch *encoding.Batch @@ -165,23 +167,13 @@ func (b *BlobUploader) constructBlobCodec(dbBatch *orm.Batch) (*kzg4844.Blob, er } case encoding.CodecV7: - var batchBlocks []*encoding.Block - for _, dbChunk := range dbChunks { - blocks, err := b.l2BlockOrm.GetL2BlocksInRange(b.ctx, dbChunk.StartBlockNumber, dbChunk.EndBlockNumber) - if err != nil { - return nil, fmt.Errorf("failed to get blocks in range for batch %d: %w", dbBatch.Index, err) - } - - batchBlocks = append(batchBlocks, blocks...) - } - encodingBatch = &encoding.Batch{ Index: dbBatch.Index, ParentBatchHash: common.HexToHash(dbBatch.ParentBatchHash), Chunks: chunks, PrevL1MessageQueueHash: common.HexToHash(dbBatch.PrevL1MessageQueueHash), PostL1MessageQueueHash: common.HexToHash(dbBatch.PostL1MessageQueueHash), - Blocks: batchBlocks, + Blocks: allBlocks, } default: return nil, fmt.Errorf("unsupported codec version, batch index: %d, batch codec version: %d", dbBatch.Index, codecVersion) From b6e7c3ea62a25a7883697b65f7be53592e08df6d Mon Sep 17 00:00:00 2001 From: Morty Date: Mon, 9 Jun 2025 17:25:46 +0800 Subject: [PATCH 30/31] fix: use unique key --- database/migrate/migrations/00027_ blob_upload.sql | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/database/migrate/migrations/00027_ blob_upload.sql b/database/migrate/migrations/00027_ blob_upload.sql index 673a9e37a7..a771ac0e1e 100644 --- a/database/migrate/migrations/00027_ blob_upload.sql +++ b/database/migrate/migrations/00027_ blob_upload.sql @@ -10,11 +10,12 @@ CREATE TABLE blob_upload ( -- metadata created_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, - deleted_at TIMESTAMP(0) DEFAULT NULL, - - PRIMARY KEY (batch_index, platform) + deleted_at TIMESTAMP(0) DEFAULT NULL ); +CREATE UNIQUE INDEX IF NOT EXISTS batch_index_platform_uindex +ON blob_upload(batch_index, platform) WHERE deleted_at IS NULL; + COMMENT ON COLUMN blob_upload.status IS 'undefined, pending, uploaded, failed'; CREATE INDEX IF NOT EXISTS idx_blob_upload_batch_index ON blob_upload(batch_index) WHERE deleted_at IS NULL; From 19b59b9003099b50928e6ffa8d8f88453c96ab21 Mon Sep 17 00:00:00 2001 From: yiweichi Date: Tue, 10 Jun 2025 08:45:40 +0000 Subject: [PATCH 31/31] =?UTF-8?q?chore:=20auto=20version=20bump=E2=80=89[b?= =?UTF-8?q?ot]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/version/version.go b/common/version/version.go index 7f2043871f..a3ad5efcd6 100644 --- a/common/version/version.go +++ b/common/version/version.go @@ -5,7 +5,7 @@ import ( "runtime/debug" ) -var tag = "v4.5.21" +var tag = "v4.5.22" var commit = func() string { if info, ok := debug.ReadBuildInfo(); ok {