From 82d94bf07afe045b283e72a4a4c390173b85c7b5 Mon Sep 17 00:00:00 2001 From: Arya Tabaie <15056835+Tabaie@users.noreply.github.com> Date: Tue, 14 Jan 2025 12:03:21 -0600 Subject: [PATCH 1/7] refactor: disallow explicit dictionaries for anything other than v0 decompression setup --- prover/cmd/prover/cmd/setup.go | 41 +++++++++++++++++----------------- prover/cmd/prover/main.go | 3 ++- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/prover/cmd/prover/cmd/setup.go b/prover/cmd/prover/cmd/setup.go index b167292db..61676e88a 100644 --- a/prover/cmd/prover/cmd/setup.go +++ b/prover/cmd/prover/cmd/setup.go @@ -3,6 +3,7 @@ package cmd import ( "context" "crypto/sha256" + "errors" "fmt" "io" "os" @@ -32,7 +33,8 @@ import ( type SetupArgs struct { Force bool Circuits string - DictPath string + DictPath string // to be deprecated; only used for compiling v0 blob decompression circuit + DictSize int AssetsDir string ConfigFile string } @@ -66,7 +68,7 @@ func Setup(context context.Context, args SetupArgs) error { // parse inCircuits inCircuits := make(map[circuits.CircuitID]bool) for _, c := range AllCircuits { - inCircuits[circuits.CircuitID(c)] = false + inCircuits[c] = false } _inCircuits := strings.Split(args.Circuits, ",") for _, c := range _inCircuits { @@ -85,6 +87,8 @@ func Setup(context context.Context, args SetupArgs) error { if err != nil { return fmt.Errorf("%s failed to create SRS provider: %w", cmdName, err) } + var foundDecompressionV0 bool // this is a temporary mechanism to make sure we phase out the practice + // of providing entire dictionaries for setup. // for each circuit, we start by compiling the circuit // then we do a sha sum and compare against the one in the manifest.json @@ -98,7 +102,6 @@ func Setup(context context.Context, args SetupArgs) error { logrus.Infof("setting up %s", c) var builder circuits.Builder - var dict []byte extraFlags := make(map[string]any) // let's compile the circuit. @@ -111,21 +114,21 @@ func Setup(context context.Context, args SetupArgs) error { extraFlags["cfg_checksum"] = limits.Checksum() zkEvm := zkevm.FullZkEvm(&limits) builder = execution.NewBuilder(zkEvm) - case circuits.BlobDecompressionV0CircuitID, circuits.BlobDecompressionV1CircuitID: - dict, err = os.ReadFile(args.DictPath) + + case circuits.BlobDecompressionV0CircuitID: + dict, err := os.ReadFile(args.DictPath) if err != nil { return fmt.Errorf("%s failed to read dictionary file: %w", cmdName, err) } + foundDecompressionV0 = true + extraFlags["maxUsableBytes"] = blob_v0.MaxUsableBytes + extraFlags["maxUncompressedBytes"] = blob_v0.MaxUncompressedBytes + builder = v0.NewBuilder(dict) + case circuits.BlobDecompressionV1CircuitID: + extraFlags["maxUsableBytes"] = blob_v1.MaxUsableBytes + extraFlags["maxUncompressedBytes"] = blob_v1.MaxUncompressedBytes + builder = v1.NewBuilder(args.DictSize) - if c == circuits.BlobDecompressionV0CircuitID { - extraFlags["maxUsableBytes"] = blob_v0.MaxUsableBytes - extraFlags["maxUncompressedBytes"] = blob_v0.MaxUncompressedBytes - builder = v0.NewBuilder(dict) - } else if c == circuits.BlobDecompressionV1CircuitID { - extraFlags["maxUsableBytes"] = blob_v1.MaxUsableBytes - extraFlags["maxUncompressedBytes"] = blob_v1.MaxUncompressedBytes - builder = v1.NewBuilder(len(dict)) - } case circuits.PublicInputInterconnectionCircuitID: builder = pi_interconnection.NewBuilder(cfg.PublicInputInterconnection) case circuits.EmulationDummyCircuitID: @@ -138,14 +141,10 @@ func Setup(context context.Context, args SetupArgs) error { if err := updateSetup(context, cfg, args.Force, srsProvider, c, builder, extraFlags); err != nil { return err } - if dict != nil { - // we save the dictionary to disk - dictPath := cfg.BlobDecompressionDictPath(string(c)) - if err := os.WriteFile(dictPath, dict, 0600); err != nil { - return fmt.Errorf("%s failed to write dictionary file: %w", cmdName, err) - } - } + } + if !foundDecompressionV0 && args.DictPath != "" { + return errors.New("explicit provision of a dictionary is only allowed for backwards compatibility with v0 blob decompression") } if !(inCircuits[circuits.AggregationCircuitID] || inCircuits[circuits.EmulationCircuitID]) { diff --git a/prover/cmd/prover/main.go b/prover/cmd/prover/main.go index 073110b08..fcd519b3f 100644 --- a/prover/cmd/prover/main.go +++ b/prover/cmd/prover/main.go @@ -55,7 +55,8 @@ func init() { rootCmd.AddCommand(setupCmd) setupCmd.Flags().BoolVar(&setupArgs.Force, "force", false, "overwrites existing files") setupCmd.Flags().StringVar(&setupArgs.Circuits, "circuits", strings.Join(allCircuitList(), ","), "comma separated list of circuits to setup") - setupCmd.Flags().StringVar(&setupArgs.DictPath, "dict", "", "path to the dictionary file used in blob (de)compression") + setupCmd.Flags().StringVar(&setupArgs.DictPath, "dict", "", "path to the dictionary file used in blob (de)compression (for v0 only)") + setupCmd.Flags().IntVar(&setupArgs.DictSize, "dict-size", 65536, "size in bytes of the dictionary used in blob (de)compression") setupCmd.Flags().StringVar(&setupArgs.AssetsDir, "assets-dir", "", "path to the directory where the assets are stored (override conf)") viper.BindPFlag("assets_dir", setupCmd.Flags().Lookup("assets-dir")) From 191bfc6bc421425d0f2da10e0aa6cbf2f961f192 Mon Sep 17 00:00:00 2001 From: Arya Tabaie <15056835+Tabaie@users.noreply.github.com> Date: Tue, 14 Jan 2025 17:14:20 -0600 Subject: [PATCH 2/7] refactor: pass around dictionary stores instead of individual dictionaries --- prover/backend/aggregation/prove.go | 4 +- prover/backend/blobdecompression/prove.go | 13 +---- prover/circuits/blobdecompression/circuit.go | 7 ++- .../blobdecompression/v0/assign_test.go | 14 ++--- .../circuits/blobdecompression/v0/prelude.go | 7 +-- .../blobdecompression/v1/assign_test.go | 5 +- .../circuits/blobdecompression/v1/circuit.go | 13 ++--- .../blobdecompression/v1/snark_test.go | 4 +- prover/circuits/pi-interconnection/assign.go | 16 +----- .../circuits/pi-interconnection/bench/main.go | 57 ------------------- .../circuits/pi-interconnection/e2e_test.go | 13 +++-- .../test_utils/test_utils.go | 1 - prover/config/config-sepolia-full.toml | 6 +- prover/config/config.go | 24 ++++---- prover/lib/compressor/blob/blob.go | 4 +- prover/lib/compressor/blob/v1/blob_maker.go | 21 ++++--- .../lib/compressor/blob/v1/blob_maker_test.go | 7 ++- prover/lib/compressor/blob/v1/encode_test.go | 2 +- .../blob/v1/test_utils/blob_maker_testing.go | 21 +------ prover/utils/test_utils/test_utils.go | 16 ++++++ 20 files changed, 89 insertions(+), 166 deletions(-) delete mode 100644 prover/circuits/pi-interconnection/bench/main.go diff --git a/prover/backend/aggregation/prove.go b/prover/backend/aggregation/prove.go index b437e8d98..e809095f3 100644 --- a/prover/backend/aggregation/prove.go +++ b/prover/backend/aggregation/prove.go @@ -2,6 +2,7 @@ package aggregation import ( "fmt" + "github.com/consensys/linea-monorepo/prover/lib/compressor/blob/dictionary" "math" "path/filepath" @@ -105,11 +106,10 @@ func makePiProof(cfg *config.Config, cf *CollectedFields) (plonk.Proof, witness. } assignment, err := c.Assign(pi_interconnection.Request{ - DictPath: cfg.BlobDecompression.DictPath, Decompressions: cf.DecompressionPI, Executions: cf.ExecutionPI, Aggregation: cf.AggregationPublicInput(cfg), - }) + }, dictionary.NewStore(cfg.BlobDecompression.DictPaths...)) if err != nil { return nil, nil, fmt.Errorf("could not assign the public input circuit: %w", err) } diff --git a/prover/backend/blobdecompression/prove.go b/prover/backend/blobdecompression/prove.go index 79c927268..f46e21e5f 100644 --- a/prover/backend/blobdecompression/prove.go +++ b/prover/backend/blobdecompression/prove.go @@ -4,8 +4,6 @@ import ( "bytes" "encoding/base64" "fmt" - "os" - blob_v0 "github.com/consensys/linea-monorepo/prover/lib/compressor/blob/v0" blob_v1 "github.com/consensys/linea-monorepo/prover/lib/compressor/blob/v1" @@ -68,14 +66,9 @@ func Prove(cfg *config.Config, req *Request) (*Response, error) { return nil, fmt.Errorf("unsupported blob version: %v", version) } - dictPath := cfg.BlobDecompressionDictPath(string(circuitID)) - - logrus.Infof("reading the dictionary at %v", dictPath) + logrus.Info("reading dictionaries") - dict, err := os.ReadFile(dictPath) - if err != nil { - return nil, fmt.Errorf("error reading the dictionary: %w", err) - } + dictStore := cfg.BlobDecompressionDictStore(string(circuitID)) // This computes the assignment @@ -88,7 +81,7 @@ func Prove(cfg *config.Config, req *Request) (*Response, error) { assignment, pubInput, _snarkHash, err := blobdecompression.Assign( utils.RightPad(blobBytes, expectedMaxUsableBytes), - dict, + dictStore, req.Eip4844Enabled, xBytes, y, diff --git a/prover/circuits/blobdecompression/circuit.go b/prover/circuits/blobdecompression/circuit.go index 6be2bba13..0c78ce846 100644 --- a/prover/circuits/blobdecompression/circuit.go +++ b/prover/circuits/blobdecompression/circuit.go @@ -2,6 +2,7 @@ package blobdecompression import ( "errors" + "github.com/consensys/linea-monorepo/prover/lib/compressor/blob/dictionary" "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" fr381 "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" @@ -19,12 +20,12 @@ func Compile(dictionaryNbBytes int) constraint.ConstraintSystem { // Assign the circuit with concrete data. Returns the assigned circuit and the // public input computed during the assignment. -func Assign(blobData []byte, dict []byte, eip4844Enabled bool, x [32]byte, y fr381.Element) (circuit frontend.Circuit, publicInput fr.Element, snarkHash []byte, err error) { +func Assign(blobData []byte, dictStore dictionary.Store, eip4844Enabled bool, x [32]byte, y fr381.Element) (circuit frontend.Circuit, publicInput fr.Element, snarkHash []byte, err error) { switch blob.GetVersion(blobData) { case 1: - return v1.Assign(blobData, dict, eip4844Enabled, x, y) + return v1.Assign(blobData, dictStore, eip4844Enabled, x, y) case 0: - return v0.Assign(blobData, dict, eip4844Enabled, x, y) + return v0.Assign(blobData, dictStore, eip4844Enabled, x, y) } err = errors.New("decompression circuit assignment : unsupported blob version") return diff --git a/prover/circuits/blobdecompression/v0/assign_test.go b/prover/circuits/blobdecompression/v0/assign_test.go index 3c99b858f..71a5c7442 100644 --- a/prover/circuits/blobdecompression/v0/assign_test.go +++ b/prover/circuits/blobdecompression/v0/assign_test.go @@ -23,7 +23,11 @@ import ( ) func TestBlobV0(t *testing.T) { - resp, blobBytes, dict := mustGetTestCompressedData(t) + dict := lzss.AugmentDict(test_utils.GetDict(t)) + dictStore, err := dictionary.SingletonStore(dict, 0) + assert.NoError(t, err) + + resp, blobBytes := mustGetTestCompressedData(t, dictStore) circ := v0.Allocate(dict) logrus.Infof("Building the constraint system") @@ -46,7 +50,7 @@ func TestBlobV0(t *testing.T) { givenSnarkHash, err := utils.HexDecodeString(resp.SnarkHash) assert.NoError(t, err) - a, _, snarkHash, err := blobdecompression.Assign(blobBytes, dict, true, x, y) + a, _, snarkHash, err := blobdecompression.Assign(blobBytes, dictStore, true, x, y) assert.NoError(t, err) _, ok := a.(*v0.Circuit) assert.True(t, ok) @@ -64,9 +68,7 @@ func TestBlobV0(t *testing.T) { // mustGetTestCompressedData is a test utility function that we use to get // actual compressed data from the -func mustGetTestCompressedData(t *testing.T) (resp blobsubmission.Response, blobBytes []byte, dict []byte) { - dict = lzss.AugmentDict(test_utils.GetDict(t)) - +func mustGetTestCompressedData(t *testing.T, dictStore dictionary.Store) (resp blobsubmission.Response, blobBytes []byte) { respJson, err := os.ReadFile("sample-blob.json") assert.NoError(t, err) @@ -75,8 +77,6 @@ func mustGetTestCompressedData(t *testing.T) (resp blobsubmission.Response, blob blobBytes, err = base64.StdEncoding.DecodeString(resp.CompressedData) assert.NoError(t, err) - dictStore, err := dictionary.SingletonStore(dict, 0) - assert.NoError(t, err) _, _, _, err = blob.DecompressBlob(blobBytes, dictStore) assert.NoError(t, err) diff --git a/prover/circuits/blobdecompression/v0/prelude.go b/prover/circuits/blobdecompression/v0/prelude.go index fa00d73e7..0a66c4c7e 100644 --- a/prover/circuits/blobdecompression/v0/prelude.go +++ b/prover/circuits/blobdecompression/v0/prelude.go @@ -46,7 +46,7 @@ func MakeCS(dict []byte) constraint.ConstraintSystem { // Assign the circuit with concrete data. Returns the assigned circuit and the // public input computed during the assignment. // @alexandre.belling should we instead compute snarkHash independently here? Seems like it doesn't need to be included in the req received by Prove -func Assign(blobData, dict []byte, eip4844Enabled bool, x [32]byte, y fr381.Element) (assignment frontend.Circuit, publicInput fr.Element, snarkHash []byte, err error) { +func Assign(blobData []byte, dictStore dictionary.Store, eip4844Enabled bool, x [32]byte, y fr381.Element) (assignment frontend.Circuit, publicInput fr.Element, snarkHash []byte, err error) { const maxCLen = blob.MaxUsableBytes const maxDLen = blob.MaxUncompressedBytes @@ -56,11 +56,6 @@ func Assign(blobData, dict []byte, eip4844Enabled bool, x [32]byte, y fr381.Elem return } - dictStore, err := dictionary.SingletonStore(dict, 0) - if err != nil { - err = fmt.Errorf("failed to create dictionary store %w", err) - return - } header, uncompressedData, _, err := blob.DecompressBlob(blobData, dictStore) if err != nil { err = fmt.Errorf("decompression circuit assignment : could not decompress the data : %w", err) diff --git a/prover/circuits/blobdecompression/v1/assign_test.go b/prover/circuits/blobdecompression/v1/assign_test.go index 8bf6c2ea1..e42b26747 100644 --- a/prover/circuits/blobdecompression/v1/assign_test.go +++ b/prover/circuits/blobdecompression/v1/assign_test.go @@ -30,7 +30,7 @@ func prepare(t require.TestingT, blobBytes []byte) (c *v1.Circuit, a frontend.Ci dictStore, err := dictionary.SingletonStore(blobtestutils.GetDict(t), 1) assert.NoError(t, err) - _, payload, _, err := blobcompressorv1.DecompressBlob(blobBytes, dictStore) + _, payload, _, dict, err := blobcompressorv1.DecompressBlob(blobBytes, dictStore) assert.NoError(t, err) resp, err := blobsubmission.CraftResponse(&blobsubmission.Request{ @@ -50,8 +50,7 @@ func prepare(t require.TestingT, blobBytes []byte) (c *v1.Circuit, a frontend.Ci y.SetBytes(b) blobBytes = append(blobBytes, make([]byte, blobcompressorv1.MaxUsableBytes-len(blobBytes))...) - dict := blobtestutils.GetDict(t) - a, _, snarkHash, err := blobdecompression.Assign(blobBytes, dict, true, x, y) + a, _, snarkHash, err := blobdecompression.Assign(blobBytes, dictStore, true, x, y) assert.NoError(t, err) _, ok := a.(*v1.Circuit) diff --git a/prover/circuits/blobdecompression/v1/circuit.go b/prover/circuits/blobdecompression/v1/circuit.go index a04669002..405e6a07c 100644 --- a/prover/circuits/blobdecompression/v1/circuit.go +++ b/prover/circuits/blobdecompression/v1/circuit.go @@ -247,18 +247,13 @@ func Compile(dictionaryLength int) constraint.ConstraintSystem { } } -func AssignFPI(blobBytes, dict []byte, eip4844Enabled bool, x [32]byte, y fr381.Element) (fpi FunctionalPublicInput, err error) { +func AssignFPI(blobBytes []byte, dictStore dictionary.Store, eip4844Enabled bool, x [32]byte, y fr381.Element) (fpi FunctionalPublicInput, dict []byte, err error) { if len(blobBytes) != blob.MaxUsableBytes { err = fmt.Errorf("decompression circuit assignment : invalid blob length : %d. expected %d", len(blobBytes), blob.MaxUsableBytes) return } - dictStore, err := dictionary.SingletonStore(dict, 1) - if err != nil { - err = fmt.Errorf("failed to create dictionary store %w", err) - return - } - header, payload, _, err := blob.DecompressBlob(blobBytes, dictStore) + header, payload, _, dict, err := blob.DecompressBlob(blobBytes, dictStore) if err != nil { return } @@ -294,9 +289,9 @@ func AssignFPI(blobBytes, dict []byte, eip4844Enabled bool, x [32]byte, y fr381. return } -func Assign(blobBytes, dict []byte, eip4844Enabled bool, x [32]byte, y fr381.Element) (assignment frontend.Circuit, publicInput fr377.Element, snarkHash []byte, err error) { +func Assign(blobBytes []byte, dictStore dictionary.Store, eip4844Enabled bool, x [32]byte, y fr381.Element) (assignment frontend.Circuit, publicInput fr377.Element, snarkHash []byte, err error) { - fpi, err := AssignFPI(blobBytes, dict, eip4844Enabled, x, y) + fpi, dict, err := AssignFPI(blobBytes, dictStore, eip4844Enabled, x, y) if err != nil { return } diff --git a/prover/circuits/blobdecompression/v1/snark_test.go b/prover/circuits/blobdecompression/v1/snark_test.go index d70724cc1..778e923f2 100644 --- a/prover/circuits/blobdecompression/v1/snark_test.go +++ b/prover/circuits/blobdecompression/v1/snark_test.go @@ -55,7 +55,7 @@ func TestParseHeader(t *testing.T) { for _, blobData := range blobs { - header, _, blocks, err := blob.DecompressBlob(blobData, dictStore) + header, _, blocks, _, err := blob.DecompressBlob(blobData, dictStore) assert.NoError(t, err) assert.LessOrEqual(t, len(blocks), MaxNbBatches, "too many batches") @@ -347,7 +347,7 @@ func TestDictHash(t *testing.T) { dict := blobtestutils.GetDict(t) dictStore, err := dictionary.SingletonStore(blobtestutils.GetDict(t), 1) assert.NoError(t, err) - header, _, _, err := blob.DecompressBlob(blobBytes, dictStore) // a bit roundabout, but the header field is not public + header, _, _, _, err := blob.DecompressBlob(blobBytes, dictStore) // a bit roundabout, but the header field is not public assert.NoError(t, err) circuit := testDataDictHashCircuit{ diff --git a/prover/circuits/pi-interconnection/assign.go b/prover/circuits/pi-interconnection/assign.go index a817c4736..9e231e262 100644 --- a/prover/circuits/pi-interconnection/assign.go +++ b/prover/circuits/pi-interconnection/assign.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/base64" "fmt" + "github.com/consensys/linea-monorepo/prover/lib/compressor/blob/dictionary" "hash" "github.com/consensys/linea-monorepo/prover/crypto/mimc" @@ -13,7 +14,6 @@ import ( decompression "github.com/consensys/linea-monorepo/prover/circuits/blobdecompression/v1" "github.com/consensys/linea-monorepo/prover/circuits/internal" "github.com/consensys/linea-monorepo/prover/circuits/pi-interconnection/keccak" - "github.com/consensys/linea-monorepo/prover/lib/compressor/blob" public_input "github.com/consensys/linea-monorepo/prover/public-input" "github.com/consensys/linea-monorepo/prover/utils" "github.com/sirupsen/logrus" @@ -24,12 +24,9 @@ type Request struct { Decompressions []blobsubmission.Response Executions []public_input.Execution Aggregation public_input.Aggregation - // Path to the compression dictionary. Used to extract the execution data - // for each execution. - DictPath string } -func (c *Compiled) Assign(r Request) (a Circuit, err error) { +func (c *Compiled) Assign(r Request, dictStore dictionary.Store) (a Circuit, err error) { internal.RegisterHints() keccak.RegisterHints() utils.RegisterHints() @@ -56,13 +53,6 @@ func (c *Compiled) Assign(r Request) (a Circuit, err error) { return } - // @alex: We should pass that as a parameter. And also (@arya) pass a list - // of dictionnary because this function. - dict, err := blob.GetDict(r.DictPath) - if err != nil { - return Circuit{}, fmt.Errorf("could not find the dictionnary: path=%v err=%v", r.DictPath, err) - } - // For Shnarfs and Merkle Roots hshK := c.Keccak.GetHasher() @@ -111,7 +101,7 @@ func (c *Compiled) Assign(r Request) (a Circuit, err error) { fpi decompression.FunctionalPublicInput sfpi decompression.FunctionalPublicInputSnark ) - if fpi, err = decompression.AssignFPI(blobData[:], dict, p.Eip4844Enabled, x, y); err != nil { + if fpi, _, err = decompression.AssignFPI(blobData[:], dictStore, p.Eip4844Enabled, x, y); err != nil { return } execDataChecksums = append(execDataChecksums, fpi.BatchSums...) // len(execDataChecksums) = index of the first execution associated with the next blob diff --git a/prover/circuits/pi-interconnection/bench/main.go b/prover/circuits/pi-interconnection/bench/main.go deleted file mode 100644 index e136255d4..000000000 --- a/prover/circuits/pi-interconnection/bench/main.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "fmt" - "time" - - "github.com/consensys/linea-monorepo/prover/utils/test_utils" - - "github.com/consensys/gnark-crypto/ecc" - "github.com/consensys/gnark/backend/plonk" - "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/frontend/cs/scs" - "github.com/consensys/gnark/test/unsafekzg" - pi_interconnection "github.com/consensys/linea-monorepo/prover/circuits/pi-interconnection" - pitesting "github.com/consensys/linea-monorepo/prover/circuits/pi-interconnection/test_utils" - "github.com/consensys/linea-monorepo/prover/config" - - "github.com/consensys/linea-monorepo/prover/protocol/compiler/dummy" - "github.com/stretchr/testify/assert" -) - -func main() { - var b test_utils.FakeTestingT - req := pitesting.AssignSingleBlockBlob(b) - - c, err := pi_interconnection.Compile(config.PublicInput{ - MaxNbDecompression: 400, - MaxNbExecution: 400, - ExecutionMaxNbMsg: 16, - L2MsgMerkleDepth: 5, - L2MsgMaxNbMerkle: 10, - }, dummy.Compile) // note that the solving/proving time will not reflect the wizard proof or verification - assert.NoError(b, err) - - a, err := c.Assign(req) - assert.NoError(b, err) - - c.Circuit.UseGkrMimc = true - - cs, err := frontend.Compile(ecc.BLS12_377.ScalarField(), scs.NewBuilder, c.Circuit, frontend.WithCapacity(40_000_000)) - assert.NoError(b, err) - - kzgc, kzgl, err := unsafekzg.NewSRS(cs) - assert.NoError(b, err) - - pk, _, err := plonk.Setup(cs, kzgc, kzgl) - assert.NoError(b, err) - - secondsStart := time.Now().Unix() - - w, err := frontend.NewWitness(&a, ecc.BLS12_377.ScalarField()) - assert.NoError(b, err) - _, err = plonk.Prove(cs, pk, w) - assert.NoError(b, err) - - fmt.Println(time.Now().Unix()-secondsStart, "seconds") -} diff --git a/prover/circuits/pi-interconnection/e2e_test.go b/prover/circuits/pi-interconnection/e2e_test.go index ca53f69ae..50e9c4673 100644 --- a/prover/circuits/pi-interconnection/e2e_test.go +++ b/prover/circuits/pi-interconnection/e2e_test.go @@ -5,6 +5,7 @@ package pi_interconnection_test import ( "encoding/base64" "fmt" + "github.com/consensys/linea-monorepo/prover/lib/compressor/blob/dictionary" "slices" "testing" @@ -47,7 +48,10 @@ func TestSingleBlockBlobE2E(t *testing.T) { compiled, err := pi_interconnection.Compile(cfg, dummy.Compile) assert.NoError(t, err) - a, err := compiled.Assign(req) + dictStore, err := dictionary.SingletonStore(blobtesting.GetDict(t), 1) + assert.NoError(t, err) + + a, err := compiled.Assign(req, dictStore) assert.NoError(t, err) cs, err := frontend.Compile(ecc.BLS12_377.ScalarField(), scs.NewBuilder, compiled.Circuit, frontend.WithCapacity(3_000_000)) @@ -112,7 +116,6 @@ func TestTinyTwoBatchBlob(t *testing.T) { req := pi_interconnection.Request{ Decompressions: []blobsubmission.Response{*blobResp}, Executions: execReq, - DictPath: "../../lib/compressor/compressor_dict.bin", Aggregation: public_input.Aggregation{ FinalShnarf: blobResp.ExpectedShnarf, ParentAggregationFinalShnarf: blobReq.PrevShnarf, @@ -209,7 +212,6 @@ func TestTwoTwoBatchBlobs(t *testing.T) { req := pi_interconnection.Request{ Decompressions: []blobsubmission.Response{*blobResp0, *blobResp1}, Executions: execReq, - DictPath: "../../lib/compressor/compressor_dict.bin", Aggregation: public_input.Aggregation{ FinalShnarf: blobResp1.ExpectedShnarf, ParentAggregationFinalShnarf: blobReq0.PrevShnarf, @@ -255,6 +257,9 @@ func testPI(t *testing.T, req pi_interconnection.Request, options ...testPIOptio slackIterationNum := len(cfg.slack) * len(cfg.slack) slackIterationNum *= slackIterationNum + dictStore, err := dictionary.SingletonStore(blobtesting.GetDict(t), 1) + assert.NoError(t, err) + var slack [4]int for i := 0; i < slackIterationNum; i++ { @@ -277,7 +282,7 @@ func testPI(t *testing.T, req pi_interconnection.Request, options ...testPIOptio compiled, err := pi_interconnection.Compile(cfg, dummy.Compile) assert.NoError(t, err) - a, err := compiled.Assign(req) + a, err := compiled.Assign(req, dictStore) assert.NoError(t, err) assert.NoError(t, test.IsSolved(compiled.Circuit, &a, ecc.BLS12_377.ScalarField())) diff --git a/prover/circuits/pi-interconnection/test_utils/test_utils.go b/prover/circuits/pi-interconnection/test_utils/test_utils.go index 36f1162ed..c678f1673 100644 --- a/prover/circuits/pi-interconnection/test_utils/test_utils.go +++ b/prover/circuits/pi-interconnection/test_utils/test_utils.go @@ -48,7 +48,6 @@ func AssignSingleBlockBlob(t require.TestingT) pi_interconnection.Request { merkleRoots := aggregation.PackInMiniTrees(test_utils.BlocksToHex(execReq.L2MessageHashes)) return pi_interconnection.Request{ - DictPath: "../../lib/compressor/compressor_dict.bin", Decompressions: []blobsubmission.Response{*blobResp}, Executions: []public_input.Execution{execReq}, Aggregation: public_input.Aggregation{ diff --git a/prover/config/config-sepolia-full.toml b/prover/config/config-sepolia-full.toml index b27aff0f1..452cbae71 100644 --- a/prover/config/config-sepolia-full.toml +++ b/prover/config/config-sepolia-full.toml @@ -1,7 +1,7 @@ environment = "sepolia" version = "4.0.0" # TODO @gbotrel hunt all version definitions. assets_dir = "./prover-assets" -log_level = 4 # TODO @gbotrel will be refactored with new logger. +log_level = 4 # TODO @gbotrel will be refactored with new logger. [controller] retry_delays = [0, 1] @@ -15,7 +15,7 @@ requests_root_dir = "/home/ubuntu/sepolia-testing-full/prover-execution" [blob_decompression] prover_mode = "full" requests_root_dir = "/home/ubuntu/sepolia-testing-full/prover-compression" -dict_path = "lib/compressor/compressor_dict.bin" +dict_paths = ["lib/compressor/compressor_dict.bin"] [aggregation] prover_mode = "full" @@ -134,4 +134,4 @@ BLOCK_L2_L1_LOGS = 16 BLOCK_TRANSACTIONS = 200 BIN_REFERENCE_TABLE = 262144 SHF_REFERENCE_TABLE = 4096 -INSTRUCTION_DECODER = 512 \ No newline at end of file +INSTRUCTION_DECODER = 512 diff --git a/prover/config/config.go b/prover/config/config.go index 2a0ee91e3..13eb40302 100644 --- a/prover/config/config.go +++ b/prover/config/config.go @@ -2,6 +2,7 @@ package config import ( "fmt" + "github.com/consensys/linea-monorepo/prover/lib/compressor/blob/dictionary" "os" "path" "path/filepath" @@ -218,13 +219,13 @@ type BlobDecompression struct { // ProverMode stores the kind of prover to use. ProverMode ProverMode `mapstructure:"prover_mode" validate:"required,oneof=dev full"` - // DictPath is an optional parameters allowing the user to specificy explicitly - // where to look for the compression dictionary. If the input is not provided + // DictPaths is an optional parameters allowing the user to specify explicitly + // where to look for the compression dictionaries. If the input is not provided // then the dictionary will be fetched in ///compression_dict.bin. // // We stress that the feature should not be used in production and should - // only be used in E2E testing context. - DictPath string `mapstructure:"dict_path"` + // only be used in E2E testing context. TODO @Tabaie @alexandre.belling revise this warning, seems to no longer apply + DictPaths []string `mapstructure:"dict_paths"` } type Aggregation struct { @@ -280,15 +281,16 @@ type PublicInput struct { } -// BlobDecompressionDictPath returns the filepath where to look for the blob -// decompression dictionary file. If provided in the config, the function returns -// in priority the provided [BlobDecompression.DictPath] or it returns a +// BlobDecompressionDictStore returns a decompression dictionary store +// loaded from paths specified in [BlobDecompression.DictPaths]. +// If no such path is provided, it loads one from the // prover assets path depending on the provided circuitID. -func (cfg *Config) BlobDecompressionDictPath(circuitID string) string { +func (cfg *Config) BlobDecompressionDictStore(circuitID string) dictionary.Store { - if len(cfg.BlobDecompression.DictPath) > 0 { - return cfg.BlobDecompression.DictPath + paths := cfg.BlobDecompression.DictPaths + if len(paths) == 0 { + paths = []string{filepath.Join(cfg.PathForSetup(circuitID), DefaultDictionaryFileName)} } - return filepath.Join(cfg.PathForSetup(string(circuitID)), DefaultDictionaryFileName) + return dictionary.NewStore(paths...) } diff --git a/prover/lib/compressor/blob/blob.go b/prover/lib/compressor/blob/blob.go index 89e7dbdaf..0d1e5d0e3 100644 --- a/prover/lib/compressor/blob/blob.go +++ b/prover/lib/compressor/blob/blob.go @@ -23,7 +23,7 @@ func GetVersion(blob []byte) uint16 { return 0 } -func GetDict(dictPath string) ([]byte, error) { +func LoadDict(dictPath string) ([]byte, error) { return os.ReadFile(dictPath) } @@ -43,7 +43,7 @@ func DecompressBlob(blob []byte, dictStore dictionary.Store) ([]byte, error) { _, _, blocks, err = v0.DecompressBlob(blob, dictStore) blockDecoder = v0.DecodeBlockFromUncompressed case 1: - _, _, blocks, err = v1.DecompressBlob(blob, dictStore) + _, _, blocks, _, err = v1.DecompressBlob(blob, dictStore) blockDecoder = v1.DecodeBlockFromUncompressed default: return nil, errors.New("unrecognized blob version") diff --git a/prover/lib/compressor/blob/v1/blob_maker.go b/prover/lib/compressor/blob/v1/blob_maker.go index fd93d6493..de8fa80ac 100644 --- a/prover/lib/compressor/blob/v1/blob_maker.go +++ b/prover/lib/compressor/blob/v1/blob_maker.go @@ -115,7 +115,7 @@ func (bm *BlobMaker) Written() int { func (bm *BlobMaker) Bytes() []byte { if bm.currentBlobLength > 0 { // sanity check that we can always decompress. - header, rawBlocks, _, err := DecompressBlob(bm.currentBlob[:bm.currentBlobLength], bm.dictStore) + header, rawBlocks, _, _, err := DecompressBlob(bm.currentBlob[:bm.currentBlobLength], bm.dictStore) if err != nil { var sbb strings.Builder fmt.Fprintf(&sbb, "invalid blob: %v\n", err) @@ -302,23 +302,22 @@ func (bm *BlobMaker) Equals(other *BlobMaker) bool { } // DecompressBlob decompresses a blob and returns the header and the blocks as they were compressed. -func DecompressBlob(b []byte, dictStore dictionary.Store) (blobHeader *Header, rawPayload []byte, blocks [][]byte, err error) { +func DecompressBlob(b []byte, dictStore dictionary.Store) (blobHeader *Header, rawPayload []byte, blocks [][]byte, dict []byte, err error) { // UnpackAlign the blob b, err = encode.UnpackAlign(b, fr381.Bits-1, false) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } // read the header blobHeader = new(Header) read, err := blobHeader.ReadFrom(bytes.NewReader(b)) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to read blob header: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to read blob header: %w", err) } // retrieve dict - dict, err := dictStore.Get(blobHeader.DictChecksum[:], 1) - if err != nil { - return nil, nil, nil, err + if dict, err = dictStore.Get(blobHeader.DictChecksum[:], 1); err != nil { + return nil, nil, nil, nil, err } b = b[read:] @@ -326,7 +325,7 @@ func DecompressBlob(b []byte, dictStore dictionary.Store) (blobHeader *Header, r // decompress the data rawPayload, err = lzss.Decompress(b, dict) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to decompress blob body: %w", err) + return nil, nil, nil, nil, fmt.Errorf("failed to decompress blob body: %w", err) } offset := 0 @@ -335,7 +334,7 @@ func DecompressBlob(b []byte, dictStore dictionary.Store) (blobHeader *Header, r batchOffset := offset for offset < batchOffset+batchLen { if blockLen, err := ScanBlockByteLen(rawPayload[offset:]); err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } else { blocks = append(blocks, rawPayload[offset:offset+blockLen]) offset += blockLen @@ -343,11 +342,11 @@ func DecompressBlob(b []byte, dictStore dictionary.Store) (blobHeader *Header, r } if offset != batchOffset+batchLen { - return nil, nil, nil, errors.New("incorrect batch length") + return nil, nil, nil, nil, errors.New("incorrect batch length") } } - return blobHeader, rawPayload, blocks, nil + return blobHeader, rawPayload, blocks, dict, nil } // WorstCompressedBlockSize returns the size of the given block, as compressed by an "empty" blob maker. diff --git a/prover/lib/compressor/blob/v1/blob_maker_test.go b/prover/lib/compressor/blob/v1/blob_maker_test.go index cfb008f49..9b43b7f6e 100644 --- a/prover/lib/compressor/blob/v1/blob_maker_test.go +++ b/prover/lib/compressor/blob/v1/blob_maker_test.go @@ -7,6 +7,7 @@ import ( cRand "crypto/rand" "encoding/binary" "encoding/hex" + "github.com/consensys/linea-monorepo/prover/utils/test_utils" "math/big" "math/rand" "os" @@ -17,7 +18,7 @@ import ( "github.com/consensys/linea-monorepo/prover/lib/compressor/blob/encode" v1 "github.com/consensys/linea-monorepo/prover/lib/compressor/blob/v1" - "github.com/consensys/linea-monorepo/prover/lib/compressor/blob/v1/test_utils" + v1Testing "github.com/consensys/linea-monorepo/prover/lib/compressor/blob/v1/test_utils" fr381 "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" @@ -55,7 +56,7 @@ func testCompressorSingleSmallBatch(t *testing.T, blocks [][]byte) { dict, err := os.ReadFile(testDictPath) assert.NoError(t, err) dictStore, err := dictionary.SingletonStore(dict, 1) - _, _, blocksBack, err := v1.DecompressBlob(bm.Bytes(), dictStore) + _, _, blocksBack, _, err := v1.DecompressBlob(bm.Bytes(), dictStore) assert.NoError(t, err) assert.Equal(t, len(blocks), len(blocksBack), "number of blocks should match") // TODO compare the blocks @@ -484,7 +485,7 @@ func init() { panic(err) } - if testBlocks, err = test_utils.LoadTestBlocks(filepath.Join(rootPath, "testdata/prover-v2/prover-execution/requests")); err != nil { + if testBlocks, err = v1Testing.LoadTestBlocks(filepath.Join(rootPath, "testdata/prover-v2/prover-execution/requests")); err != nil { panic(err) } diff --git a/prover/lib/compressor/blob/v1/encode_test.go b/prover/lib/compressor/blob/v1/encode_test.go index 9e36a05bd..fdb5d9b68 100644 --- a/prover/lib/compressor/blob/v1/encode_test.go +++ b/prover/lib/compressor/blob/v1/encode_test.go @@ -247,7 +247,7 @@ func decompressBlob(b []byte) ([][][]byte, error) { if err != nil { return nil, err } - header, _, blocks, err := v1.DecompressBlob(b, dictStore) + header, _, blocks, _, err := v1.DecompressBlob(b, dictStore) if err != nil { return nil, fmt.Errorf("can't decompress blob: %w", err) } diff --git a/prover/lib/compressor/blob/v1/test_utils/blob_maker_testing.go b/prover/lib/compressor/blob/v1/test_utils/blob_maker_testing.go index ca04fbf8e..7507fdb49 100644 --- a/prover/lib/compressor/blob/v1/test_utils/blob_maker_testing.go +++ b/prover/lib/compressor/blob/v1/test_utils/blob_maker_testing.go @@ -5,7 +5,7 @@ import ( "crypto/rand" "encoding/binary" "encoding/json" - "errors" + "github.com/consensys/linea-monorepo/prover/utils/test_utils" "os" "path/filepath" "strings" @@ -125,7 +125,7 @@ func ConsecutiveBlobs(t require.TestingT, n ...int) [][]byte { } func TestBlocksAndBlobMaker(t require.TestingT) ([][]byte, *v1.BlobMaker) { - repoRoot, err := GetRepoRootPath() + repoRoot, err := test_utils.GetRepoRootPath() assert.NoError(t, err) testBlocks, err := LoadTestBlocks(filepath.Join(repoRoot, "testdata/prover-v2/prover-execution/requests")) assert.NoError(t, err) @@ -141,23 +141,8 @@ func GetDict(t require.TestingT) []byte { return dict } -// GetRepoRootPath assumes that current working directory is within the repo -func GetRepoRootPath() (string, error) { - wd, err := os.Getwd() - if err != nil { - return "", err - } - const repoName = "linea-monorepo" - i := strings.LastIndex(wd, repoName) - if i == -1 { - return "", errors.New("could not find repo root") - } - i += len(repoName) - return wd[:i], nil -} - func getDictForTest() ([]byte, error) { - repoRoot, err := GetRepoRootPath() + repoRoot, err := test_utils.GetRepoRootPath() if err != nil { return nil, err } diff --git a/prover/utils/test_utils/test_utils.go b/prover/utils/test_utils/test_utils.go index 74616356b..473152f2a 100644 --- a/prover/utils/test_utils/test_utils.go +++ b/prover/utils/test_utils/test_utils.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "encoding/hex" "encoding/json" + "errors" "fmt" "hash" "io" @@ -347,3 +348,18 @@ func spaceOutFromRight(s string) string { } return bb.String() } + +// GetRepoRootPath assumes that current working directory is within the repo +func GetRepoRootPath() (string, error) { + wd, err := os.Getwd() + if err != nil { + return "", err + } + const repoName = "linea-monorepo" + i := strings.LastIndex(wd, repoName) + if i == -1 { + return "", errors.New("could not find repo root") + } + i += len(repoName) + return wd[:i], nil +} From 502c97e6615fbd3c1d94340cdd9e65c94a09e870 Mon Sep 17 00:00:00 2001 From: Arya Tabaie <15056835+Tabaie@users.noreply.github.com> Date: Thu, 16 Jan 2025 00:45:07 -0600 Subject: [PATCH 3/7] refactor: use config.DictStore in aggregation backend like in decompression --- prover/backend/aggregation/prove.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/prover/backend/aggregation/prove.go b/prover/backend/aggregation/prove.go index e809095f3..e85d8f76f 100644 --- a/prover/backend/aggregation/prove.go +++ b/prover/backend/aggregation/prove.go @@ -2,7 +2,6 @@ package aggregation import ( "fmt" - "github.com/consensys/linea-monorepo/prover/lib/compressor/blob/dictionary" "math" "path/filepath" @@ -109,7 +108,7 @@ func makePiProof(cfg *config.Config, cf *CollectedFields) (plonk.Proof, witness. Decompressions: cf.DecompressionPI, Executions: cf.ExecutionPI, Aggregation: cf.AggregationPublicInput(cfg), - }, dictionary.NewStore(cfg.BlobDecompression.DictPaths...)) + }, cfg.BlobDecompressionDictStore(string(circuits.PublicInputInterconnectionCircuitID))) // TODO: Should we use aggregation or decompression ID instead? if err != nil { return nil, nil, fmt.Errorf("could not assign the public input circuit: %w", err) } From 28d9bf956ec45fa409564f47191b521fbb8ab323 Mon Sep 17 00:00:00 2001 From: Arya Tabaie <15056835+Tabaie@users.noreply.github.com> Date: Thu, 16 Jan 2025 00:48:20 -0600 Subject: [PATCH 4/7] fix: use decompressor v1 id to load dictionaries in aggregation backend --- prover/backend/aggregation/prove.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prover/backend/aggregation/prove.go b/prover/backend/aggregation/prove.go index e85d8f76f..2f6b81950 100644 --- a/prover/backend/aggregation/prove.go +++ b/prover/backend/aggregation/prove.go @@ -108,7 +108,7 @@ func makePiProof(cfg *config.Config, cf *CollectedFields) (plonk.Proof, witness. Decompressions: cf.DecompressionPI, Executions: cf.ExecutionPI, Aggregation: cf.AggregationPublicInput(cfg), - }, cfg.BlobDecompressionDictStore(string(circuits.PublicInputInterconnectionCircuitID))) // TODO: Should we use aggregation or decompression ID instead? + }, cfg.BlobDecompressionDictStore(string(circuits.BlobDecompressionV1CircuitID))) // TODO @Tabaie: when there is a version 2, input the compressor version to use here if err != nil { return nil, nil, fmt.Errorf("could not assign the public input circuit: %w", err) } From acd1fd9ad023babac6c11d5a0055c26c50e760fb Mon Sep 17 00:00:00 2001 From: Arya Tabaie <15056835+Tabaie@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:19:36 -0600 Subject: [PATCH 5/7] feat: add new dictionary --- .../lib/compressor/dict-25-01-15-INCOMPLETE.bin | Bin 0 -> 65476 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 prover/lib/compressor/dict-25-01-15-INCOMPLETE.bin diff --git a/prover/lib/compressor/dict-25-01-15-INCOMPLETE.bin b/prover/lib/compressor/dict-25-01-15-INCOMPLETE.bin new file mode 100644 index 0000000000000000000000000000000000000000..2422d663c8d04c60feddbe7907da9cabaa9777f3 GIT binary patch literal 65476 zcmbTe2_RJ8_W=Ih%-HvRD`W{p_MJkMMA?@__9gpHkufv&B{GpMWGO{uNu)9)d(vVl zDT+!VYnGD#d&Bh6=c~`}|Nq}|?wNb;yW6|>o^#JR_bC=jP42!R=PAjkJA~BuD(Vv* zuPwTt^2@1PK%JWenZ6jK&L#}hT5jg;~c*nm$D+wfFIZ>L7 zIL&L+MVGqE5C8xI0Dyx2D4{<%Q2bI)aEVoF?2}HM1(>~*v01a7ybhm=%Y2xCUv2`49a(#&LZT9m&| z8?#;{=5!+7xm4)qb>!-2Nu@^cRaleTzW(l;9McSr!J zTrm*YHO%|)-*dqf(C)wK0M7Ry`#7BE(T=v&nE%Wy_<@n&Ks&XNbm-m*rq&&b%g)!2 zU$t7*Lo6p?K2c&gp5e%XPkIpnmY;?n&l?*&IMDy%W}13F+t)beI{?*zMvpM=li!~z z*y#Idkcw&SoXCCvARixSE~Bo=~1HUVhf#e7JNNKj!^s;xvjy9dvpO^{yVFU7le$ z2sBn@dOLAHSITV-tDZ8WOq!VyCeGSWzAv&S{ofjHbgU9;{B76OLWZs)mJJ~qhO7pM zczD491|C>MvjaY&vX1_#Mrp~ij_Dfe%&oW6_qC+Z#kF@%3B!HR#!JM7p?uh^#`oVg zo;WgLdbG=bffZf%8v8(T1pv8?DsxBFAJW$7vx$DtJ2xESH*pI99p!RLMwgh4C;Pn5 zU!>zUO4OjSqw@4fw@!SZ0RU9BG_qOL``n;SUcKsrLws)!cnG)}Q*&u^R^-NXnNyJGQlW ziks0!4>M^iy9;*?6!IOuiTOnDx|Pm)mYLvnIYmiPlp*xMAO)rFOU->S9iDN3J5UNN zsWI;G=JktY`60i9#%TR|oHFwGVFUXlYc89~#;H+h7ObK+`UWA_ta7_F!^u@yx|azZ z`$IGtRt~XSj|Exo^PakV;8MeR96JN&B`g@T3lGH!1Cs@m4BSY-0K z;#M?J0LoA0#MYJtQB$0^pgd|Ob#i=7_cqnXdgF_G*&lh4%@e*{l*`MC;2xdfamRSQ zWD%57kIz~GQ!pXS%miT#vUMi`?nDzsuEIF-(Odnix4t8mBjEXB?EiaSe!x<)v*Amv zZmH(VhN+5OEmH50b+2vd|D+zaH~LUp)XFSAVtZC`G-GuUUwj&nL2P7b2N%*j z9^y`F8FWaWON=^Mc%J_ZbGzOKk&5$}f5ATm{l+*-V3crWf9}G4)(;Q%?!iZUn6OdQ zxx7O|>la>F2LG@FEYh$GUyvrH?DFjt{;AutO}w~&e*dF+8gxBEC?Fxf5H1Tb3$Iih z{jnVY44{Fg#J?CA`lR}xucWOww@4sZ%|L^pStAbA7wz~YkqMv)Aco9aGju^|wqd#EMph}_uI=C)Us61u$#_QI}J?0Ik&(3}R=z6i> zVaEndgaP<7D@TQ1ZHp?CsUvw^63%x*@|k~=%tlt^r_HRO+}cmL4`}-s+Ui+P{cJD= zwQJ`)nehiFWs(sq$O{k=qX2O)**q^8oW)XxapK4Q@4tC!mT)6n_~XtC6{nvcZ)mqc z*CU-Q*4ypj3K-e!fnW_(AbX0P6&!3k9Y?JxwyDHx;uLZ z@;f>EdisQU!KjO~GqHho+Hhtk5Gn$lhD=?vwgb?nb|CbxfCtINF3Uj^keF>6%pNyy z3=N(i)W|68?fU%^o*XzcC0KP}B7! zRlbZI&+^fqCYW0$28(TnxM9jU#`gZ`Z^>!rhX6Amo9Rw#d;fD6ZEY)QPnGFl-1U-@ zgGV{DIne7E3#Euh*}r}mVL8J;(e<(cWTroo>JTf`R`6Q@0UW|2!5#*uu`>Sm@Ev-MhzsSY z>rF|*J9DGluMO{QcoU1<^}5))>03RukZvKg4xzQBgx;)i`v^SawLO89I_(Ez&fZSW z{sJljJ^@}n(%uT*&SA=d0WR|L&Vgr?z5Tt;ggGgN*eg0K$oVqXRlz%Yq_$X;dcJ|7UoC@Fj^lx>MiSpMA!oUw;}&>tPmDd2JxP)(U#UFs$}U0Q{(?-^kbBQ_@BF2b|`* z5gP{HRw3^Dc3*aCs;dh`M8L#90)eXDiNkJRe|fMo-@~@KO%br)m2zEw7+;R~)YVY-4xN=aVNn^$4cZ>PeQc5*s9b;^4K$;f9g~ zY~DQ?sU69`uHpC)E96SVy4yDQBcFKyi~2N+XS(>*d0AD(!fn1K<3+)Veob+wyD{1d z`qsCBdUDh=!J0w9KqgZ5gM?S{%_|b!XEqN=b^mN@n7TK3yI$VrzKMj#I}-Jm4Sx1q z>Jw=@yv+egRz5&YN0;o~>5ygNIhRp|A9}K~aN7|CbL&(?47?vlfjArvWMXpwkRcWj z2zCGvI_B2a5Q@WLqM?Y*+}bexf}x?IIkf#^?D()CGn85kXD)&=Ye1Q!jG!O2IE)>C z3*Z_wQB2neVg&_DDkeDcRQX95EMl1#wDBI|v}fO=lCS5O ziChvFTR=Nh8(5<2QBHNVl2pW+c7ky&Rlx=6F1cTgfJjLZKgu#3$mZzGg$%__Ov3g1&j9RC%5n^)1#n zFezkUzqm;SPVmO*K7O&dSi#Y8w5kSxgGq#p!);Y_F;$wuc^m^%9r3K$w;jD8-eL^5 zBY+A=ekwbL2(Y|3G(PXSiXNrTES>Z7^bn7*J+mx3&PqYdMQ8~wsXVoPyybS`LuH=s zYL*+iQ9M*!ju7XsflC4@aY)tK(=Z6a(hohy=e_oxf5p{tKQ6k@oGPox2_^mcBf6eC z=9Gn*7?29DS3V3AB!hKYpieN5+MxMdA>`eo?X(0X6l1exxU!5s_WU0o1;1Y-+?2%-)d3f~ z59XShz8q{fd%;9={XSo^bsJ~UgF9A1$GKlL`MH}r;`9#AO#WE48+le{dnta$>DQgb zVk#8f=Z9MLyPM^5tYUZ0%aPF_4VL!aYp54UmmJ-V*?s(2-2C^rU7n$&W+G4NlF+}z zAtZ@z8@fbToI3x%ByJ5^RgU9ILvK=Z&WXT}=yz$9sk4c#7N;av^(e>?( zixD4!x*2uo&dA+}uAH@9j6v6IrAzJ}*`2ZW{AeI*_Mx`9pk@PIV8|vN5%_s)&TVl0 z|BsEY2)$g5l6Whi-_ZG#W{K|faYV|e0fWyH7N||kA!A-5~ZHvGyLX2i~=V%6*;R_vrV^tC%KmXGnf>f zow>QHCVI0~i(Z~zYh=g6+{`q@U8_}moSN?3aF~C|lFGc#S3LJ-@1WM((6iT$>$l$! z&s?WfdD1Zx#+bG1@I5MlBkqs$<}Zr#=Vv#h=45ogwH$0kFuqr+<{~NyviychN23*P zYo73FSjdaI!AzcT_jgkur1P;(4EoYycmzg(GoO4e`K#&5AIh~ZiE4@jn%nlz1lw7y z)?Hb&te;&cJx?`4!nTiT?6yxQrP82b3?V={jakBy@$%!ic4Y<-2YHX8nd{={0gr*> zuY*P2sX~Gobm~ZH0>Pn26nngOU2}D^+1TxMXO}gPTpJzRd9FON;M>$?9}drH#Bw;i z{cw}v=u`vH5HK{n0RMyK0lRe!d#Yg?yxG{(_D0#$-ZQtxK#Y?}4Js%U2`9o(u8NHd+bQ z9)-t|4=oYF6)df6F8=z}lq4<9EE@e$t5LGol+5Wm_|woXiu>5gKSLT1INo!7XLneg z0_Dnw`+Nt?ethd%{TxlCyE;i3Ld^5v*326?`u}37WRIU80%Mj0)h?WisCv_eB#Z;K zCY=}&)w8RED4XbFUt+n6@F_>r%O}V?@OV(5nTa-ZzERpID=RIdAT1*&BdaW>Bn4+N zc6M?0_qX?i$Woz>QVvqe4vI>SQc^lv2c5MQl%2Ju9b}ai56K_WQqa^^mQ$8hl9Q8$ zo;&0TjSCw6tIKY+gN-)#qm)T054Y{&FlU7$V%ZmdW9RB|$b$rb7@#qDG5ob+)3fg< zKMubiPOB-Y_@#$Z-PHzhz_yv!EO5^=6^8hZhYwYV?w+3LY&v)=fL@sr2_jG+-KlFv zNDzQ_dXNj+F_f|u0aWvhComP5X0EQFW^Q1z?=ED1dnA0(Nh+A=Ke2CY8p>v zs+#(JIUkVs;#^MD!`zc0K?VtBCSwD+9flyab%H*Ar9=-yca?xg2|sMTKSdtGF4s z9jk>(Kmc<%WT34iX3i8}ck6iZuN&791$nIA$76Ym3yOE3(6~|`Edp_FUE`G)q!ZSE zT}2!4?lDW_6T$aj3nZr!%Vmzm?)xkNHp}wfd&zOXd zxJ#124dM=TZlts@uI_R*`Q=GlX(@q2b5IU6nplgXurFFrp5>^yX$@b=^slruKcC=aadGjWBACetDQzG#9Gv;O~(6dC-#pb)j^?sncO;FF1cv5KI^@8k3 zj8TEo0j!nu~?2iK- z^_*`5Q#a~SDAqnHBJzo*gpv0PMun_Hnc-2 zFxmrR7N_LxMs}bb?YPmotoDF}g*8mI2juO6qfm_+M)rVKTB<$JPy7L^9Otc1p-t@p zi&T4XmpODO4{+?&T9Htp6glwmCA+*>EUQ3+U z>1%iW>Ck)BNhljQ)s0wABd!cR95U`|8Vs_rpp^n%xMA_~ijY^U^=iA4joIUIp{w;@ z&~;AI$(Kn=G1rAg*q;5@0MiW$zmz*3s=J%*YpZmrh>~2qlw#47B6#1@-SvcEdz>2S z;U_S1AkuB`Sd-7uGrU^rZL%Dk~h? zi~QlikQKwJnC=%Q0xXJ20N=^5$Eu54~KGaClhpN))tnyr?uI_W#R0X zYso*m+#^)W^rvLvjdJZYwl@CT?x5-Th`4mx#2viTL+P@whP3aK{lX!QOci0Efd!36 z@o7s3#NQ3RdfIyR%+62G)3m(grp?jWGkD|2$&=krq$4l7EKYd!KU;VwII;j~16oc8 zGb6UP{g*T440tjm@lDeYQ|EiO4+ZqIn77jZ#5XwXW?E%S)991Fr|$31s8F^qGU+sr zil5$^ovXyPx{A4^+PdIFE%0ONrC;V?G(lgjBEepgoY1N{RR!hFoRF%=JC2j=> zP5SVF-Vq_Mt(lpan3zqct4u>`&)3<$5?fNqAcN-oKf#vkTWfhPy779Du~o-vm2yH# z>{k+%!p^{D)Z>tYVQ?!uWG1Ucc%k|2{EB zsHY77%G=a(ihuy^UA`8xpa63~EHwV;sqZ{vLC4KYl1^C=)7%dLZTgiB^6{ zQDS!Ty#hgi{9-Lu^S*9tvhgn3N7fKWz6U*vMR>5{kZSwHU?7D>7@Ocf$@2~P7Wrf9 zce3sOm|80z);}B>OI>Pkpko=RN8uh>nx2CjseeZEpr_s;CvV-uUap%vGT~}gS%}C{ z{zq@mhM7CH@JM6@qn<)0F<9Rmm4`zLjIqIh7fUt&4*&C9|7p(oT$hG~CPtf>t}j2| z2KUDz-k;)BbLXr_jEU>OwK%m0Ki>%>q_EgB{|52s$Az#XC(eI6O982*?JY<&I3~Ko zz(~+V82(4l_zxOP+WV(vaEY!gl(^Oqlolog?PSWCRHoYb(jb2GW`F0|Iyt`_#PV0T zpSN*{FH>YNpvBTqI{(FypmD>I;0o8l3vgwGxC`g!oC~#Y&OLrF+NbbpETlB)=duAb zr3ge<8!Sa3KfaG&pKV~=YsrtBay?l>QdxxD^bWchU(flLj2Un}t8)Y{39x)-%i(|~ zqC{YkzaO%Fc(;dDPGqQ$PiIcF`_TkSIEV`Gu@x_r(EGeIa(p_8$oI&W2-k>@fEUYt z+ddp9?BFz42Z~d0Ot$oH@cj>G_j$h}RI9Hoxe?YQ3ZIBjdTh#!^`JI~FP0iEniQA^{9=A(b%ng^#@y{) zuOk+{K@$vG-tol6`G%c?@<%&~2D_c|lOfRVzd?iy+ygzedy}FDjTX5KL~RF!W}aTx z{X!(}Dg0J{j8y*C7(<%iLkSE#tEFUR{1`knlSMRc~|LgeuP=8+*@&w|QpwUtHS zeUn!g8dq$wXPLo6FWv=jf> zrZG9bc=&_?*S!0+rD*)(aELtOrAFpn_6tLP*0RPk z(hL*eEHuE95b*#!vYs#qz??r$)ae4JettZm@%_bH6PaM2_0yt_M|Q*{{j1o2{RE}A zCh!7m3%0VkluPYt%lj?zA6_ndqG+z8e_Y%6xB+n`LLN$-AeicOzjN@D)V`?Om+*@| z9`~YeVxAz0?QP;N)oDLmlvxlZksT4tE5B6LGPQ!ZZ~#a7namxFNG>EA=SLispHbh? zRdaT?0Sq3TbdTBaJg7%FiMN6H#(c_uQ~Wu0ygsvP>ZxA!{LFi9PV3hPfx8x%RHPjrJ7nS5I9*1;r^_g#O}(WZ zfcf*FX$M$iVLJ%OZh^*tMo~BrdL2=)DiintKMC1OVbkD-j+2kejt_1OLg?%wsZsxL zVj0t$0REiz-8JMQO?rL#ufF^Ye)u^aJq7hYB>xS?5n$%ILU2GLSX z7PMKBPjNVRUOdfARPe}^;w?H}aosGj_cQeT;ZRHa$z!A->>awU#fEwRfvv6MxY$y) zze6p0w+YJThQYJ)FfU`>t@I#{6lzhiyZcqfnt4ASKkB9tt-J@O$0B2$?!ER2AgO`A z%lK47qlQM?w4y$Iz*+`AZV5fg7uiyOOZOo?V);BgqBS_=9YHP_2w>I>@jtltjXBuN z=9e)b$m&u=K}D+s-X^ zYx`^QJ^C3x?1oMZR@tnZhg&TbXA^~jcSdipe8VM^CuXSY^V(@;N8+Nsq6s1|FUdqF zRVf>Ozr^$6Sp!qI!fWi?+#gOAsQ@_}1C7ewkpQ=x`bFKZ94FN(Q8U^@g8fDZ_Sgf3 zS&=0l7GsHxfoq>^O-32LM_kZwcl{NP5~Vj*Dv#LOs%`Mqt!X{%-_wJPsc*A{^qh@b zyqBcA-0wo7BUWcN#E?m)LYE||JaxfWHz*OMB1&36RM()%dkGq93`mYchT|bm>Tvuy zEIAh~zRqJmdu_el8bF<`MULELg zTgU`UF9+d3fmV8!@=n8N0GT`&$$*(WoB)v?PC%+v8sZGtU)G@tStOr}#+7_#U zv(%Fj&oEc-ky)HAK7L(oi^`&*^jcUF--0u@_-lgjWPqQIF|HUo55gMts0%nG1(hWX z?673i<=bj#p%EAhkYO!?M}hB1(du4nYx{S}V=*^s=j-2*d&R)%+Xi8S%4Q*rZAn`a zEn)l~v#ndDlZ&b=B56y@S6*LvJofPCtayR5%ID0Yi7wAbQ^fKV)Nn%_VwGVZ3>2}H zd)fYS>T2FNb@43egY>4-#qIgzPBP3JRuIq(ht%I|4Ff04oXLNv`w4F@obB^mdDr|Q zB=?-Um+0#qyRzI@5cHU%jX0#X&si+7JOtun}VI6>Sc9LA0Z%%v&K=J8pLsAUrbjZafSx$y7+&*3 zwL0p&WX{9%oM71iua7i=2hF#aN4IgZMNXK}#-4Q?XU=kEP?!s(p9&q3=QB%oTA~G#Yy(M8xV{pg@r}{ZLjyV6GaW zU3Qmr#f)NO`hkVP=whCSWw8zGP`l=!H;wTQ#UT%0h13ZUz|zOv#(&YT`F4qJp7*Zp za3f3Qw+H^EDNAT*8PrTP#*yX~Bxga-qS5aJd|H1VO=HNkKG)K;#N~E*uX`Y3IRmEQ zD0z6_z+fDUbVWYa1 z@qgADL{x539`Qcy>=@|m^q+bEPWDl z;9J+sbQ_855ehVKs=E+}#zFp#GH~r-PjQswR3~wi*^nrsaYB6d^4zJT)%q@oL&*}e zmUW!B3lWWSJxRkTi?O}C05_3u!MJ-ncGkbT@qKMLWbU$3aS{IR#aq8_$}~JOu#<39 ztJyJgNXHb)dihR+h#w=pSAY7Mi`5?!E%&LzZ4=VU@Pq`>{|Mmeznl~{o*_)R>EAlj zWB=px&X+owZHzX5ZVo)oa}VN5c(5BVaX0Eh8seq)y7>RRL{cN4nV^urdoRh7L^U+Pt7X6 zyuJ41$eSYC@Wpc$2?#Hdc$oL_Rp9>*-3FkswcF535w}3}XzADPG4DjCNRPfOQ{OOL zqPE*G0k?>p{qf!i84 zers<9UQ8#1JvdV0_Vawmb2`(D=6lBPh)wJoIM8~jVEA1j(J2MKxp$2h@+uqPaCLS5 z(g|;3=$Vt}mb^_HcNYFY2>0($0J8NSoTGqADglpx- zlefGa_KCjvwQ;*=9=#E;PYOTfj_1-4FsXAD8C_MNriKEzF#gU8&$CyoV0Y`A5yU&k z7xgNGsKdlFX{%Uc%*?*o{pa~k1yy{$#%>myb$+`x{gXy~?4O_igHfYNNtE{e-7Q3G zC@JNCJjz?y)c5(bojiF9COTX3JuDLtbQE0&oQ8 zyo1E5@-U4%;)}&D6{zgOWwVbRa`q{UhE6H<@Zz7vQ!4?dbqejjlTg+ay)Qp}QCGWe$Hw z2^lHKrN{^V1=%gV10a_q=y#yIqX(3tsjqJZ{j&E8gnmd%Z3KY`g66d7G?^T4fSR{u@kWz2xHZ9uDNKJYn|Bex?pXHJu^(JwY@kP#^Xtda^?=Py_V{F%XazZgJ_z=x}3cS(>G7x8@aGc z+&6|B^66Eb9J+($Yu76nB=>Dlz(fwC(uK*81GiGRg{Ej#MR-mAuU-286ha7f>#^sE zGSNoQJ|J_1T-vE8`z|0tm+ccC%?PSBf#$ zn$NX+o|TuVG?ik()<$DT4FD1iGX)HP!Oh0s{1&+zA9~-t^iz4PJNMzb&!RS))?LDz zTwA$|mmfK)%#Oa$!}eDhU9bEG%~qmE4{8xdPUe>hg94&QPgfANf12XgN=Z3#@&0~R z_AFqREt=?h4zV<%`YTEv(u&bEMcLEj{NvmQxz1ywVedYgw-)v^!@s@zXeOgEF|cV5ThEAF=+Q4nl~hGsuR zj4FWlxy>uKU}XUsT@MP|!A|=SzcsaAU*}>^H6wa}@K_Kzu%X=#v^}uL4p6oOl)elN z-u{fGe7A_xY8nJEe!BQ$qb0A8J^g%f3hS7IlMSSWf=O_&glVr6J# zHWs`?c7uGwNNM?xH3YgR_!(ajDSHWi0fe((OfE&>S*q_D$!fLJmZ&scD4&2DJ`6X! z7l-V*0-eixuGnHhdL{fh$CBA=Gc~>GMd zr#<9J3DgpEs3l}La_2fQ?weCCCW-Lkth2md9i@O*tBShjFC%aMxKxu}T#Ne}Tf47VQT#J8Tsx3zCB>PKjPOZTwA+^gQR=;3zLN>DS+4@2!M2 zN{_ZDCv|sr=SF`W3oRtn=64aL0%%6bt*sLG8;(J{lS2wz+})C0>gr*X96ViFS0Eh{ z@qoUAZezoNjlftkNf~@V4|KIizHR`_cgzY&V(nh+Xe&fR?kz05MW^9w5-0x~FfhAWz-b!`oHF@NeAm;>95ro`5i*#v(YD@jss1em2Uh z`26s|+g%3Vs@Z2I1*M?@HQxOuoz$~^lHRtg=nWnT1_W?)*xf(bdM@GsdQT(8IcKxW z&{~Jp)>x49I*wy@Ef5A6EZyXL{AY~@A8J?H_v*g zO@*cVw?S@kEz^^EIV8HnCh{Z3>-yDn<8Jc=z$ODqb9LQUvq4(9u=L3>aliI1r+1di zVk4Z5`~jevNSJlx{a;zs8iR?FF*AeUH$D;SFTI1KY!|z}f0PkZVW$N2-r9UUtTVbd zT^4}FPmt-fRnuAgbnn@-lfK2NkKX-!dTGc21Q2h`_ucQ?%N3a2Mb+AW`Ej?i?~NsY z#SIUrQ-V8|zRoA^n>%{gRfMB>_I_#l0}`L4fV}QRy+O1zWYfI>4K7Bc8CTV73lIU8 z`ami^;n7c?C&7^e6S0hqO#wH0-rb}hN7qw0tzIhnVuBMD+qKYMYyd;)0rn#G{RH*FwH3IBuRlqyf@7%|5tgTktrUO+VQ1_&^F zP2IN1-7h6*)4chG2t?U$Hu(XI0FMzJXyMTn;$}q3POt9!sdTwrp>*9ozo}JwW7IdG zh?M*&1EW*cxAG6sLj`{FpSn{_x6;iP3L9I^o|;X`oDx(jeEa!$kii^G50=o^?Oyd0 z?eVIC95nXE*S|UcE?>?!+nUkiuq1h@aXzD)MG_v1vCM7FT`i5c((8qy^$ z!=<=J4iYxwMmQ#0L~NYu!NGE(H}P&oC#%}0=;}HJ#%c~LTNCFbox#n~I5jSO+UDg4 z>KBYYndOW;J5}{rIG|Nf1;40#vfp>Y=&_U!NARkL4J;c@-*|ts%wfzrWDlLj1sfi8 znBoW;uJykHVk=Rp%^#t3rAaVK?-sW@z4Qo`%FH2{jze+>dSX`k@TMS2H?lz`E{^YG znXOQ8$}jp4Xf%%Vz)Uo8-!bC!{!;dnInb_mf7NX|ed>n8jH%FMGlGawxj4kDl_{uhTgGR}T)IbSP|*>XJkg2u7P4(%hXxQ%~V z_vpVVyS8rS#iyX>YIC$|1(d?@PD5%&4?LEgt6+PTay>}Cp;JEffcQG(Lf_hA4zdTg z`>_B#55LOi-uXh*TWuYKf8&JCSHbKnoc@HpMP};NY(pAsU7ro%!vQK}8b{k(M8G00 zu@Hry`vvB?x1rP7`I7*c5|s8nEzP2|VdBHvUkrRp=^t&0JYQzw!Q0{~RjzZ3pJd$o zenvkv+}Q=!NW7k)&H(H~LhOF?dIH4n|G9*=KeTDd1gB510mr#w*_wS1Rn@sQuXS4HZCtcU72GXoIo=+w?l?2t^u;(Y=&i#yg#!n`!!8 zgDDt)N4ybPRL>f>zB6~Jf0)G_w$<4wFq+8t{=8ZE3dLelP75-RG>su~gst>! z7bguIV**dJO70WG{{H#t|D$U^%Cabu_Y z1YP|w9f83oEW+|I{;0sYebw`M$KER|wMb!hqTjH=HVe>c$9mWkQdw|(V9A}$v*YGu z$d0>*c8A*=Ck%)UlIRp8B7Pg{mxDc`FU4&ah;VR3k*$7v_BbKf@p1!;KYLF(woG{=8U`pGv^8z)DjP|uZ&Wv%7*>Kd@ky)Ce1x-GQ6x^iSD|!A#pL{P%Sk85nw@3 zb6X=Mw35%h#SppM*2a@?@VbeOIxXqfMyz?&07m#IPNM;`Hp>#SPgef)wE=z#pMMK( zFnyPHBR{h7)paUEKS$XgckUK;P8GxHq=5XK|M3Ox zg|r7vEF6#Np7paE;eoCO3lQ(f!xtssn|!~nIN^)L4j5VLu^S*->94e$E1!5)clFzr zH}JCMHt-9rP_3NDT^DqQtK96`4prz45Sc6rI^2qnw7}pAwAzaO5c= z(7uxr5{IR0D8N@-y~#4=AnOZ!5~_1yYh!Y0psGUGBZ|dCgg-zf&cf^yjYDyGcf+6$ zmZE?xvPK}O)6p+_`qt+~o;zO8%df`Lu;u4eL%xp${kOo)$Yb`YQ#X=_a|jh=WCb}( zh!rHf=Q-mL758Fba2|5>eL{5fg@m=k&n*~6CvWls zBM*tT-|N9XlRTu|!2}l3*5nq)OQS#KWbqiiZ;yRG^ufAS zXNpf5p}E~?kVQDoAYjk`S|l@G{R@&FL;@`V0OriLMwm;8g?v7vJ|d`Oaq3059D@#r@<7e-*hG< z-SL#$J{MDh)qE1!ZNi5y4SAAzpY%<)sSA>oYPj)YzO#T$_maudu9=QHJNnxDY2S7K zt87;{_^!@I5H>b)GEqp;^0f)$b3H1(H!=1J76=o9)>&Pp z*s>ote8H?imN@MWbdWN!5j^(3zWzSJ&IdQ|@*K4H^t5mfbTfc1plxmC-*8rvCjGdt zv%h_ykAHxhyDurT_D$U2L{XqN={Fp6yk0~7#Qjd`C>U!V`2Fi&$Z)CV(=omE-@^@) zOQMRYof=@Q;~09PMqN!)@fp;8SebeRhcr$vhe0(~t&IjR`aM4X`@!8i%7VS#2=mBi zO91scQ0JWl9f7|(_`wB30Y0UzuUCqm9ihN7%xDHg4sF>rg7+E;=yi&j?myeHYyHG@ zmj4o;8)Eqx`~tuT93lYw90o&>b9^p-F*fA~VaoFOg8|1`;ivRk)a?0?_eAmH@(bsU zngGDXwf#+gtK&Du5>JUBQMG>rNvd0jPS-UH}^82!J$Fe;h*)yn2$U%Vg~Q6j6rcrf%*9rN$v z|Ap{0dAS~I-?UeBkeRc!xe-U|KLF7bFpyZnKlz?#=O>&>f<@=yt`#H$mZ8}S$5LUG zi6yJ4!82bTlj@S3^G)g;eo#Ys?flsKOGw>KJb{8pwMvRW{mn`pIBIX7IMgtIBBwc{ zIm}4+&=JwG&Fg{)NUiV8C5ofr<1fHcHcR2R`>rG80@AOTEkkXzq56cZ$MUbywTDIAX$G+-YdSy(a!SOR% z?16)1qhu;h_XCp5kWmX!sLrkQ1vcKVt30`}!x!3xR5&Z|eM{nGf$8%2aEz@v^~ZiE zX*Md4qxPSd`wKI-`*>^-*L2+X^klE$%J$t;=RyJT%AZbKE7*#Y&sbZ%sDc#x02#!3 zpkUE)8dzCBE8KN*#c8%M?P2<^xSc2SCsxlgPi1p3ZZ0L%$M z1ISfqszv|?Vps&*9=u`xq9>am+thU0m5~`ME;BB}b0E5&Ogmk}a}F-~9L>OMifE&2 zHuS8D3WtJnm4n}lj3t~Wuw6%soRT+W&}hAzf|$Qcxm}u?c%1PnBZHryW{&{NeT0rD z#o0N>|M~PKcsn0oAvH2zVo>a5sHU7Ry|#vVg%*Fcu{#g|lxx85;DY&&WcO#!VY+rL z>YTJ-Z`%|2ZMytU!1o{DFFtR(n=&2K+;dBvM@Z~&v;D;NNSUC|3JfQpE$k({fgI-z zcg+)ffTweOh_4X1Vk3N~6csBzROwvg@(T+B%7h7p==p{MDfXGY&oGG<{ajXYuZSuK zk*Nh7c?SDcEV+~u9z}`Pw|SD8F(+(!=$Oy=gLmv#+n~eA*waVCz5r2e=&P^2_t$)F zHJcUXsa9WXr_%?TlP_Ps%Gv#?!_#+uuV$$4_2+#ofhF7dMCSf{EF)w#bCE@%yXNrW zix-ZYYixLH?v*wX4q}1$ zjgVyxEZGhOj*L$^^Y0%C$!rEeqWrS8WImDQ9XnZuGm7roXnp8=wb%EO`}M8#obmU^ z$r^4oCtqWIU?r*>S{+yX$F|)w6|Hz_eaFR~nm4-NvG}iN;D^hEZ~>-|joNZ(f= zJx}l)4~og)ld$E1IhJacqx=3a4#Y6&_~4Awr(P_EVnSvZ!pXXu)3%mJVAVX7F)!2M zd6-?!0jbR@EBu^5qI;N+MRfmYmvDTiUd-{_JCP?glHu)Z$uOnH*a%6GyTmYBLe47e z7(4!0!CfOc=fj!C_x?BqlFA{)uATu?Fuqft=k@&_SDx{W>6+wkG281eNE8Eq=}Zn&b%v^b6a}MGA?bG{Um=z$qDqlQEte zOgy)0i^&{cJzhb0V)opEpl@L?7 zo-Kf6cGI&Bo}lee8Gh@rmHuy>Y`Kex@FL4gMVNy@_Te0Z2TkVHJ;3g$aKv&oX1|sH ziMPa0pin~}K!Mbk@HgB*6u?EaP?CzWF|5(ssn8jHlu@1QJ+iId+#=xQcHY*+7fbvo z69!bnA=Rl1U{HpIx8g9+_r#)$^puc?hV;`14}Uxz8aTJP6;tr4g_aGFiG@e&42m4CwXEKC7tiPQdQ}77m zy5?SCpC9^;FLq}Hk8po%y8|#%z&$3G-Angt>TSitZ*!sLDVf8X32LLV6stVi>#>FB z!-;z>!4TPJV&U?dj&fg?$cnSsK#^HFE#xysNe5{Iu32C~NmYEkDSO_BZ`5_&s%z^s zZ{GQ)xYd}$QXpJI8L^s8Q0C0b7jI%??fsS}Dw)Dh0^@Dd4yj=_sO`wlwqHdnxy{P$@nn+A5({=1b!)RhIit|Aa7U`*kPtto5xPCBt{%#tp%&Kebbyf|Z#8_5%NwY@|&ly+n zyl>BOZ@%sJQ!)Aa#l2T&9a{?7MHf#PEDBbC!@{4rAUh9z=7Kip_h&AOHm@_Gp-(k! zf2E9=3UvbkfDBq1&?RPOa^fNrR>orAas|a+yFg>_s4mOw_9(1o1)Rnxh~n7Jx)oqi zuPpFqZ?s?*ocT4@BKZ2=3kW#24n%&0^?{1UiaJn{8=-&0qJnpEx_9n1Kffp9*o5Xt zz_ne|d~0UPy$UF$QWtyHBuABeD(5i_&WN6!tNYdak5$2`OQ-hHPxl5U=Pk_;i2ap( zQu-o(C1U?taP4beg9QdE>2B}k8VntMJK4h`2liOcM;|yhy|#2_;BE2)5nx_F#BcY3 z_5X*n?|`TBdjr4Ao>^JR%HErlSw=-ssI2Rnkr^4`y7w9pp{qhh(vq15Mcpz=AtPyu zP$CtwTK?yKb#?oGZ{Oel|9^d)^StLd`#txZ=bZBlI(rVdJN{wSfZ9nYz_Dc?6dsE6 z0-RW(SPzdVz?ZeGW6COUS;ds5ik`NvzJ`XDzP`4uj)uOP)=HEWHB=yuhMt}#P$s3N zuA#20t)&g(*ulFl7IA7>^nDfz{cvh1D~ylyRMXbg(8TF_sOx%Yd1>KvH8gNoEpHEf zZJ-<2+Z(5?h1FG4*Hp*pYI|vTXlQHcVYRe%aN6o#p1L?sEPQ1{&GO$q1%dMP+wb?3 z9-Fa}B%(Y$Jc2S4BjEOkV`99BV^9tudXyfU@$xy?0Bi0y{dXBZwB?7_M4H(~Y;s=( zR4$@*p<+MTUbgPa@>soY?Jh%x;|r6mY7_riHB{cM9~7Q%q#L8C3gpMi()Bl_PM)(i zVy;<4auSh)?W8r`)5b*QRti~PV(cMQ0;)Cj^G!Yo`qx7_@L=#ma|~)Szd&OU@gun2 zBq|szU`L54??RH&mDNCQW<>UHigrC=MZ1Kk6j(_6pLQ7%#~z>MMl9<#`pzT5JT^C| z5W9u>O;D?2@b~sedDJu*>;$G963dauBao8=t@8`xXFtzc=vhk<%(=~a10uE9x;Dhr z^{*C%W}g3gC{0GmesN#KICI{ebrHKS$c14}j07u=9aIW)5DBe#XAE+t;1hf8H?f~U zX8^X0CHN=zd@DojdINFZ&G%A1#`Eo!HEcR50B#=C0MeO+Huh+QoMs~W1tZz_=xq69 zul$~oYwWGO7l8p0nE}9*fy6K#J+p?ke?gXzd5XsMO~>2xH{x!#X-Dqw7O#nSdc8I0 zg4OLLUMoqHhQ?xc6|*Ya$dtqMm})q$4_qto&ptk5?-NlrWXd&D7kh?U1YB)rnD>U> z+J@*xjB>P&N4E}3)~MSw?>(C?v|agV z)E+V8#Y5`8n~DHR{89m>cP5R|CobqYT&jSs>iPt53WV8YE-pxIiwfNqAUx@j zU9jdFC?Tfv8BUe>{Vwldz4wgIK(wn#9+f13^F@C1S3N8Dv#4dspgLPz-@YtW;|~I8 z1m7DadDM8|jGXp#+vR)351*OmP%E_RP5w^dTl^=tCK+an(W&woHaDIy}hYRK$^I9fKu!~(4@IUo(&(%a2bryEHnouDk7W!vwdZAF{2bKd;U6H>2FI*#k&lO? z3%C$yp#tHd3zV(thgpJa-b&80Y`?v$R(B8S)T^Tz5383f9ybm@{3hx0y_E?{_qWIA z0P-1(TYGlSXEnamyeNN}8{=;Y0I>)6px*S;Cimk72M^(9ZRRqa`tD^uD*$6r2pWq> zeiEwE^%CSJp+Kbo4qi8eS z!I$ufV}g6aSxo1;gmK6br)(a0X{5%fbhSn$h+kjT((*Dj%cJ4>g~K|PFpkh9OLw+S zMskYP#%KPTZ&Oey3A@ftccgJ(8yJ)i27MRCH!Pxz2uM*9W4yL85f!+APrCY6{0kmz z@ugs%*0958c}i>waPNDi)GB=hxtWgaH1XK<6cM~k(8HbWrNaMU=11d?iW;VENowDF z8_k9ffX;^YjT=d5z0*KnnBM6fM0V*VvgkF0=A4-Aosc%%=bqlVjh)i!r@+si1lyqz zCraPw_ovBti`HZBxQ`}5k!@NHl1$}!hMc|$%i2J|Mc>O*cgXP}EJTwVITwof1n~8j zP2$W*Kjsa;`vn!@OO3luG*NpF^0zP(YF3eCZUJi&kOQqNQhg(A#3XW%n<>#?_$sRN zd6so}GFQkIT$!C6o1-+Qg!N4ccnjO>w!; zDDTWli!jskahH3q+gBR&b+Ov-r#kFA2ZW!@--5+$kq^$8j?5=K+4x0Fk|RR0KF<5bhnc@?fP{1u zLhZv^{s{0q>AgE(L+XjOO}wKY#w_9X{j=>f?K58I-GiFtW~sjlq_|GUy(^N8Za@!UxhV{ z#JyLU4|3KJS=>y?^b}73CPrz?qFi_kfXd+U`pyP1$HfnxoU&04Sgp>6eLy-f228(k z9VcNzHI<1R<+RMU;EI~xdY6a35xmfs3cg)z$bIxOgCq&V_cRSybC`|-al=ykv~ z?bvg4NXXnIOmpgFj3D_stcOz&?6T?UB7ln*+xT(s7P(blEl%nha&sK135v;ySwv?L zw4RZeT-MkUIpaQ)G3C*swI?{S<)=9^&&I{P>ASzP5X^R-JasqtxkXzXZ!GLbM_fEr zuy>oF|4bsyN0`#O>92R?mfao!)e4QcUZq< z@gm%hs~2YJjj~DD&MdSABZXI3(9RKf@!`eg=l&N0NHd-jjf{!d`!mF!vO9_$+HJ0P z|ExQ{%l1SW67OO%l{qr|GT_n{sUhj!^9D*khm|S8^U4=bJsyk6d(e#D$(8wnscn0> zFtqCP1<8)-onrppBx_=6XWY6q{e+4N zwZ8xMm#tmUhI>FSg5sL4H(0JKjRbIvcyWc?+rHOQnl{qA(XD5mESz;%d*q`IkNc=@ zW6Lc|TKuNOhr^jrfKsByws3VVftKz@-*ly9nIng)s%ay32BDj$yQepH=LRHRTf)Rw zTU}dAi|60s`J|_qm%TS8*y8N8f&8}Sws|Ukq53qZ1p*Nk4z6^U1)C{1@(fueZ%m9e zXM4yt4l4(~Ox$ZC{+`e%gaC4fPS3Ah6BW%R5w#!L7)^3jkAzZQUD`Kq4!SRBL zC8@%?KH_Z9`7Zv6=z%rv6Z~&3D|Qs4k2~SCk6Si^p`0pR;MSAutaKFdcKeA=-!oZrpo)@_6}KXio}16=oQ6{ zEJ|jG^i*KaJ2ze3v3a%1_Q+2k(l06A{9VNx3`9=x8pYs)&M{ zKLC~9gmj{aaTmES(B_iV!-OyDzKdV%R!yK{^6s_c!DV5M8D7>8(h~xqGqms3s>ewDWNq`wU zsOiee?(6PgVGk^oit_N(USN1NyX`&|2l<3{toF9Zy3I)cU4@1n5kcqQ9qXgw3zfar zR5w?yPTXYgD_3oLjMd=#2o?9ge0pR!H4Qy=nDF3x^YC)ewTbHOLc(Pjw(y*yw<#$p z@WjO{O=2}OS0^$BHITy>l~vxd>J3<-EcL#h;2`;R79W8ouE~bxA5aON20z^p_+MoK z%~C?6L;1J$@9jHslFf4Z!{M%heBpV^Pcz~+gKL?SRT(#$GrIjs4txJH zO5(iFi-XxLF)2(Zvc9~abVL6`;>{~-q!~n2C1KCYRqQdmIJ^EVQ$lBPa$WrF z&UeO7x!>gGlI+xvj2~KeBAa!UG2Gw{&CK_7KXC^~4$kXLw;g?Dzh?7~g**bh3NyB; z($vC1(g3$U^5r8xndY!9(iWwckw-dzv!Ecqus^AKb8ip2&&Eg}m9HnS7w4&Ny_|e` z2mLE6ewr*1Ho=H3$dfU){m&oNT7IrA%zK07hVTvKmV?MNZNZL%xNft4zkE54!0kV- z;Yq>LdtXx4DxG#W^=@J?(DTeWYAJ0P@I>z!*wmp&=mh7BM9ek4|FrCd8G$W(L8TzM z*gt!|+AaQT0p8>tto+5;N4!hsgHb~$`ot=m!yI6Dn++|%NFEZ~#9ajJ5T%O{e@%g3=|u zK-Ha~s$h~n+4m|0z3IGzt zkF8?hof|h1wO)`sXYX?E{nM>UU*(Upn`9L-x>T*QqM^$XOg^|^4a~?f9RZn&64`qt z=WL8s*fAFjQxwjM7S0bnd@mw%th)`Vir?|`r`KwQX1!eun=|e`+!!`VV23^dy#snE zoZf>px|KNDMi28lj6Xp|Y2}Sg33tj~kM~PiL8R`3IT`z|6HD-%xDw$X0Md6-`yWAn zzGB4vpnwrYDTHkJ=NnxQ_RAWUWD8$e&9V;vHQs{W#RmMb=Nb4HVuQ&Wh%V0IZ%Yd` zf~T(m6Qk$_cw_-{eX9d@K#mg;LlTAu?;TJI@&%FDSiiOPNzZKAp(CF@`6=q=*5y14 zv4gLwfrTd^Wb?;K?Mhu4B86_=*&)#;<-sniwqB(#E%{Lre3=K|PXo4Xs<<{GhMm0r z1-c>($FKbDPU1(Yi8dqlMWgOy+0UthnN13K3U*Gy?D@1!5teVjPn&NCLt(o=ps1HM zE*JjJ2pk*!fR7DtuIZJ;2JS-4=IH_26*}?KBcTL1fmKHV&PPOWT}hs0ZZ8Zf`MlcU z?#qnP(+lSXLT59e93Eb5@TSe7+Hx`k5k0V8J+xtePNbmb(wVXrTD(ZYlgOod zip+ZHi;)%{?72O>6Y%X1Ok?N-3=S7gxNl9{C?1CG6%US>4$1ySEB;ujyVz)*v%6t{ zF~X?!tMX%Fc`oRa9_VXssf4UCAs|PH7|nwqGinQHB({EX$QFnfyq^0_v*5Gm0v_fH zQ277^c&`*lozpuo+9$S8nU8aOHv*V06lc9FXPDb)5X3XHeeA;X9Qz#C$ zl1STrdRuu~yq8i^C917UX!qUf=e|CVT1?-k!jm%;2Wg(tC;PwHQ^jd>qXScD!}9$V zZ%;K>;R=|~eT2fZ5qs7gxxd0&P8;(H4eSt~u6*Md*8s&n zd1R=T17FG+F1tdl|B=bB7nJ|>mgOHTq>cOG@$%OROgv={%5|^Ly!eD#umd+upx@_FLGRZz&l0 zEsv&?uSOMQ<@{TYhmcvltb-X;Qq+G|M(fX- zTMMp_dlFN~SNmnp?aPnMCnfI|?u27Rt=kf&xsU~}D6L2gI{Ph&Eb4RQa=!^pRESY{ zA{%$=IuR=kql-V{@O8h45sVbLHHt={d_52fTWYi%&xCaI^YXI@1r87B9;IvtUmd!D zf-+}!hl}{DgY*<;W!p%LUR$$K^D0B{?(15g6>^5N<*db>a_-xY){EH7EL}NRFvjXR zdh4D+^=bLcOkDIi_MfL7O2R_>XTJaPE7f>RUJV>ppAU z81<6kU*__8P}7*IMe1tkd+O+@d8y;nG`zsIxVpZs7Pt^s*Y(uV()8BT*TrgU>gnie z;&9&TYTzzhO<&W)!yBuu=b?sG*HDAA!sF>G8qOf1p1G1R7Lo!)hW*@h6%7OSUPZZD z8)|*%`{83yIC=~ok@bcLvNk*a#7cbhpHozQ-s0n(YmZvw)PRPas4j`Mro#|+h^YBw za&WGB>5(d;y{nmp0RBg5rdw?`E54o+V-}MoKrL$pfC88g*BLL$xkRydY+wmCn6Vg? zFEXV@g#7SR_3;gx6ssQRX5UEZZF#=!S@pXW{1HWKQ@Dq{PP({iOPKN59HY$JZ-*vb2oD z=hkrcr_hjhHp>tq)VPXZz)|vOl7_ejt_SZpug>3WGtTT-F0e_-G)FGf`0x( zL`)1fxuk5nh)~tE`7`!Ad$!K&_k`|slxL`Cm#$y8Uyj;6pQ27HS)MRL9(#LG?LN*k z@%ir0@FszZ=hczdI_jpQ@n!$fuCq;7@@y-nS7a%nIH5dyM6Cn84PCbt-&|U(L|y$9 z8V!YCBS&i6rng`AJI8;1u*5c>N$^M0z4LVTjPxuhLmB+nLqQ;Yf}sdJsyJV4=>9NO zYy{9!6si&&_-n5Cuhg_S3SFaUi%4|>6wC9q!c|yGf|_KLWZu23mVmmTJ111t2+wPd zCB819;{M_5Xt!0g3=}Q>K?9xZSL_&mPj0@K`F*R)Rac#tTP?m^T?;1FQ0RwZB1JO@ zk>@AKu`iS(>eyIM=MR)-)Tpf@eChnu07wb*I_9Ku+TxU5V%6tD76KyIX}0Uc^Ks`f z%fk1UP#l(0ydEDDZEUWyUVCFeG)Hl26h=dAqo&_NGelrLv*;+J&x1q3E#_h;5C8+NN6EeY$)IlT1YU5Bo0PPQ42^Z8?5$OugLCr161`LAW5tQE+02E?wRfyzcTw zXx#1UP0h?M-5}ui#NkL)2yM4-6#X>*v*6gQoig5N>0Daxt!ou8Xz~AqeY`Fxtb|d@ zk13VMY&&{LJxZMLW^Crs`%^2#&`Km)Ajtwj5!oG*QB`vLr@i3dAyLE2lLOker=ZW{ zx3nzh$`EG^-;%=Q{>yvrJFP!ZuBApQObwgU0o)%2*L$+P1igf-M@SsR? zMBeRNHGGd^O5W9XPMekoKNo)j&yG-ac!Pqy0&Q_#*uc$RUglAO*!_Mu+G_p}n8lbj zg4o*axwvz^8wCo;Cu&4vZ^LVVAQ1gW@SeS`oDZAHM8siTKA+j*GLvRA`+n_w6y7ttlMn5)|(~M)jv}aLz7gB9mOU$CDS}1OYKio?))KrD-#KU;D zksLrLZwG@EOG3R>j)olKvh8NT$uz5KEh~62GBM1oihm@u=o#3rL$;fNY?B7-=GeE2 z{FYxly)NA(cuW1k#WK=Is>~1Hxbsw{w>Yb3c+Y1hhLuYQDD@u?W7O;JYjrVD7<^~u zHbLfbEr{pa*w(#}roJKUXr?XJ2v_RUvh}!qTJOHtsrq-#Aq5ZG#KgXAG~T#aJk+u@ z?&BA6n<+(f^iCZnR4;?bfpo?nZr4A&mk-0S^6e=IMjspl62$~rfduN6fOaJWF!>8v z{$*fXRC?d zSnM`M)CEZ50bk19qcGnns3o#kIO$`9OP|ifY;DUi)OF_LmiAeaEdhM@JNz$y%P>?W zfk=OSd@R*!oz9=5S8>->?U3fiN%omOmkJ2?kDslJ3onQ|(BUAtVK z|KxKu6Vzyv%f`Axq0*4j=q)BB^sM`HSZMQmt6`VIsR)B0t}Ge$HEC%bGDf%H$(UKt zQQFMymGM}vJXmALuZ;}I)rTt!QXWCMlp%+DZ3_if-2tE3oHt`A! zSMOOK#aATLDoY^GRzEG%EK>NzU=!%Y893JDy3%6gvxM2IodP^h<0h-N3cs^kNlq8Q z%x||b*##X9pz9zP!+{7ao)P;xVfMsoOIAnJgA8LCev1M@y&*VxX~~vXo0D<{<`a`A9``&YKfFq#4MB^Zd!$j+mVxEo4HDqNX`AD zMQoDaJ%NghWi3dO^|V@$KA$7cE{2fK@JVyvT+BpHp5QUO;IHKa2vX9uHL`QGHG+7S zH;D~_<>0@EIc=JM!RbyPm8IshInSurTUVs}*xyg1z&|g@I6Q(IMeoDHEz-1jeJ+8L z-QO*vptkMJ>3bIIwSpIsRYc5>;$?N>$hvWIaYfX#oL3)fQu)R|Ih?gBwlO%}2(@I* zhueZ0U+;)j9ocHlvx5_SWQe9r016$T83MDh07+z(Eha}ST8>Vw&m&zBb4YldM7TLC zjdum>HEC5H<`Qi~auLkICo{Ih4*P%2);qVp`eXeu4+u~4;tt7#&)GI@niod=yz;o} z%xvMCoFSRd`_v!&I%ngpIrYuAJwD`><(qiNgf&kt*v~mZ)7++Dl0@PYJ$RZEb38a^ z(K<0n!+)GOd2?9id05TI#SjF#tYefMFG;f2-1GNzm@Hq?AlD|CA zu>P;dc-PmSZ*YY1v*5QIxXZ}vuhAKHZkU&+{DFSl;G6e=)tVJQcgOrQ8XbApOGX0z z_(1M9_>7+eldJA`lJC0d^jJG&uNvGmEutP0R-2Jn&O(Cdyh`K~@^+_qj53Fsr(gFx-XlDfpJe+UeD>2&er`bc z1R-LMrZ=ICPXB;%dKf$PJ-Z1x_|hrchxgBq zi@eT4ScZQieck@>UStBgdNR0Yo7GfsPmISk^TsT=8+u&tT`aV$oRnFccdGV;$60Cq z(AZ^5$q8?OPA@B_ffC8DFC$X{`POi0PE{;l-6djtl-#bp&GX=Y4P(#6&Nxx1n*qVP=&7lK+9r zDvLdxQ*jaylJa`-bpEkI#}@k$^y&+hSCTpuQLC40i&?475TUt*48{JJDeYxW>|*Hd z0hX5o^gbXtk;@uAF)PF-los(#=Enp_$c`}K?;T6k!|6(Hn)jIo4=KK=yO(@iNh$Ey z9btY6?sp?SgS#Sk^|Fg`-M!xxaUbPjt@c749gM?#*Cd;{Yc-FG8`f`xN}qfwdVX_>Vl zg&CulxpkO{uqx|4+hC3BsR>BrgO+DbAiEXLRMfNkg80q{my`yXI@rZ)4qkxv zj}mA|I9U;}t;WKZKu*|6_T_FB4;VbMX0Jw(^eZsWj->3@S=lUb1o(OTnRsG@4|s+C ze;AQK4pN&3`R><3*fEby!{%o1PTdbFD@=p}Q7SaNMge{*-hL}=L{vT_bu|^hL8PUj zqo$>y1&cW?OG~Y=8G#7uT6#J_7)nh~OGiyl9}<_c^Kw+1^V=<6d+TSip_lKJ3LZgp z>RCmhca#8*&WX&YOG#)}4GKJW!>eRXJVn0v+&~Ud?SYMxW+vzx%!`c@C!ty&KDTlF zc5>#O1%A~}ymjXOIBR(AfKYTFW?66u{+B01qz$s^OhKT12&>q>z25teE2%?ZfwB-Hn*NywQavRo1&fJk{dsS(-CVgf*yDLHYs+97jMt^&ftR88oP|sJ`#V{15rtZer&O3givm?Aud* z`OQ)55Vhh>wrkgU4e!m^SuuF;>29I0Z>NTs20k5k9$EtH$3G|7@|M-(9)~7E9<3Wh zRg|5;dg$raQwyN$^DZV(m>%q`MGibTmDW~ev-Qb^aCKO7CmN=;7$|g;tpqX}uYhy` zMhX<1GwaV*zIm*Cje!x*H?h4<`CHjR<-70R_~VojiuuQb(!2P_`@dc^&EX#;0_6jt zj5du#?6Q8raH<|5Yn$zygW>*G?q?8gJxverqz6nEEi1 zzOPnb%h>1jXftW@GP7jp&&-tx0Yjn2qVF@}>4#H8SztVNx|rCaCkY*gCs`ng!%HmLaS+1;#5^edhZ9@>YdQ zNlcVCExsRmIryHWwR80)Vu$cyyZ0^Sc&KX$bC6Dcp-c?ia!#Y!|vvzbazai^sf{%P$-XxN?PWPT#7F2Ff zx%D}`=7pr=o4e|5J%60o(Z>A+FAJE0Lo?_dV|9T}6xyH?6z$ueuyRz&26132h&FWV z_FyT+O(zH{V)(+@%1&&c(rtOifbUUmHO^Q~+KB0C!Dk~@8GU>er^nrioqc0g&ErYA zFgr)=Z+SGg1KQ=!OHAJ!X#H?AcuoMJPcn2D`-hiItaJW=KF6L zrAo6T`$u_rg@zr(2LEeW*3ksZGPvo~(bUn<(*_K_aEt*mR{iSNp~BL|XP)B};|z1= zYdGDj_4(|RQRMpYHZd>u~vq0PDSVDb&_cy1gtV^BG zE}YFgbfr}#v+9J#X5Oo7j0W+l-kx`!M(GZZ*0KXDkuTAQZPwm z`m%(X8h4xp!+No)8P%i8Xo`UFq?3@=6<{N zPF~{Jewo(lpNG~ji?X2akkF>eA&?VIIF|tw5Wwv6uYDhu=}@{-0u$w$L=XKw83M)I z$L@bTq$Kd^;*Q;--d%T2TLoXlgW#3t=p_|-Kdoo))ye|Yw=?9kS-)U}j~}QJ+UH^F z^2A-Bd)M)t(6(46VAH2omP=XUzK>^209@Zo58^fI+L_aOMhSeF@?nm?{N@N~D=fB` zv?ddm1(Q>&n&T)z2iI_}ud!4UG1(tAj_LReZ-q;#CH=*>^*wMPILts*HPkEYfC{*Q z2o2Iy@e5K72?y_Cs@nSMT3V_;UV&c9kf}1r9*Qa$kQ$!4SUs!`5L#E$bjzJ(L!lNR z%{&BTItg{jumy5}4h9Ppc`kP;;O%tEd2^=0jL6z@w76>RK(e;A|M}B+L>=i~zrcgP z)C=gST8;(q@M%5V6>;1$uRk!Lo7;G9m#3Y-mT|KJ1xcan0VJA)*4*+Ia)2RkuNm@i zSC4xLp=z)6Y}4J5Tvi5ySDwSkD2dA+ufXOF&0sjZeuW0V%6zZoT&pFrX8+uy+=xpl zF8ZzR3hK}Kw)+3Pj_!?--#vS*pgs?|`>khhea<1wiN97E@o%AxSgRa<7M{lDNJH1atB-31|SEe0o9rDHjOfO z7IzZ)MQigY_o3$G)RH|vvz2=IZu&HS1#(cBk&Bl(p>hle;7AP4_PE8p%hO4hO(F_b z_fNJR=F6+b?FU_92W1--Ny4nZy9;)RVkA8IBAa|V!(H##m!5A*heB3qAChqC0bLAlQl$CVl8>*fMlNFk{=)A;;DzvHl`X0TuTA=-8Kl;^T2Hqk-d|ncDgfAm#_ZN zz=khVJp1307g2o#@p{D1b)E@V#;(wQnSa9)476cta>TVqMZ|{ul-eK0bIp`&hsNNL|@5v#e^Xai-tj^yeD)em=grf@nNA|zm|zlwKfzp)QNrS4zM#=S1;KlE zfcu%Sg3co9al!`@x+nq%z5t25@KsK3&WclkgwI5 z>*@yi=RV{EYPKIVgPDZ7eHU!cZ{MAR$BdqL<`?Pw)2cj2JhTP2gzf9Po~#1bc#Wm4 z`2v`74uV7pC*X7O<->92NE7mtTT?25&nUGUe$4w)mSRtL$TIs=1#Sz@&v<(sE zF)_%qA)``sZ12?E^MrE-_)}!pj(3Mvjmm{qOl+KSaQn)p*e>b|hD?dlpW5!@IFrJk zceYeCxX4b(rl*cR-G*GG&7U4j0ZIb#NDRa8(>?AY>%|ZITnB`XMsF)92+JIh*HNIi znf)ng_H#<}K>xnNyN~N1 zaVFbNaCC6&b2OA5m>mHeM)dZLAei1EGD4P{-re=Dx=_H$H@@57e(^YSZDr3J=}=bq zc52SAiL#;SwVD1&cl@2(@r9h`$M;B>BcH#wodD(#0oJlkO`>$B-EZb z3nC+8njE$$iPyh+w{zR0uNoIZ6fEU74&*0ePP*)`=pCu^R6P)v50xwYbJ(dP^MS~O zznK;inDKb4Ja_-f1-GGdu~)X;Q67Dv$rat|ANtZO_2+9YYm>cZfE|g&wmw=EBt@!u zehdUN!vd6#PfXhC-#!bghOP?qij>Y2f$khu!CJ@N5kT#DHqcoP=^ghA_6~EB3yxIIyo}W zcwg=l_q>pHrGf+usPQ!FL-pAN6-ej#i>^ppqMXHR5;`_w7m-P>`7fdnC}~&}0?L)L zc|1DN<-*YhmiFBP$2Mp9fBXCt;-=LEzds_&LKn@7j8v5(Z$ENDdDi=7cl7|__&e6) zno3X1KY%qKVg@CL+bs!cIKahaQ0$6@pM{f|(9dD4;r(yLo zrK(R2;?18=-n${8I8e{|CJY%Y+>2$QQ<)bP=7Gz817JY1odeXKHq1Kal`8gUrt+ zwj=DwYxYMYp}V(w^gm&;k^t9V-3%GKDd%^_S6*uT>4M*(>7jbdFxJ9QFb(vW22>If zh{?M}0(%v1kxN8IZY6SA)rM2AbWq)^TVwGbo1z8S(@*+A9VQNnK^6C~?o{?Z#1PZh z%5$!&C#cx=)5)qIbypq^;EhH1zccs=*9ACC*vJ5&91D!ozdV$=cG=Y*S9!AMu46FW zVq0;$HCg#5v=OnxlUDbWmifREw{1o!u9KF^PxusRaOKdYzWNFqXd?pUN%)nqeur!7 zpq*pqj^?me-G3G~?*8YyjW~Gs!Ovzc2cHz3dO(S@=|8pmM`N+uSb4*=Sj{V zMf;!_kWM=T_*3N_;hqu|d6BnUNrz$YDQTtD;ku7toq+g{yh)e{j<=BWiHL4AC2u~x zmrYh(baE>6lKjY*h@e~XW}e{GLmEEy@Z2O0`h5W;OE^qo%+&q_ISWK~#_x0~$7HFb z>7Y9Z1kl?y!Kd+gxz@|BYfH`hn#LU`wG3_s?)v8Eq8d}n3}HZQ1QJp;k>J4;wrNvJ zor}MV%?AxfSP3G`Va5!$x+oMh(Elw}g8P(uC7fwK2e~{zs%gUYId_eQF1(L5x_is|*XHzWzV^46qJ2IN739yBy%Q z13f{_Wd%U6VfD{H?4gF!_s{6pZGy)KAiF{zpsq925`>Fgd%d%5tDfE&U!DC79*@7Q zMZyt*jvSk|J0|g>(J~W{uY8-__o&bXY$o8}{GrVx$Z9Q#M`SMj7ZF_}S$(WMBJ!++B(C>m6KMC{8bLusYacf;HfGbWBXYXmYOg*13eS#NZ# zY+FDx_s94B#=S2=o=A9nuUuL}LmYm>PV)EdbNeolg9;HUP+EMmo@^^hAqECdJiZh+ z;3gaH88v(`TldNK>)5OVp z`wF>gPA%szK1v8w&_sCX?xmS$V4cV~6uksL>9eSV| zIHu6>i(tOoAaAfSpWrT^=eIJ;WOZSW*p{64cuYO%muHKWD-`b~N z8=NtjJsyCs=QRiVquJmnznqM5El+OOE66H~=79@>JQF3JxBobFt*w1M~w0?V+TNJ>>8$vVe2d>!AVft-5B0sShnrhwFie1AaS)_^-@iud{8{0{R)L zQe_YC7G-`3l6S72A1$5^3{L^6CT;Q3(N67K*tJ#9E#D3#l#nnn9Z`@&AcUPF_vl;` z@!T|Bbuc`^`dsF%W{J)XPv~r(@WX_xL7aX!>m^G=FFzN=wnk@a4baB_W4b{oBL6(| z)w1Pnxq~hAa-ToTGMq7bAZTIc7g>!dhMKZ5RSfzR6|!s3(6($q>Yio?q34-1VroC< zfhdK-oeS@1@qfUZ-|TqI)qn4In2o6->qEu<;qu_Cl+g)%VA4fnJs_X)M-FbjM;$iY z@yZ+xUX0nPdLc@^N_Fyg4lo3dwDBCRg4|f)q0b_Ig#C|srPN^5r6KdxhqT1h&NvU4 z1rF`*IJD+EOiojCNDFitPw*-H@I=eE_3`+p($q~!=~Nu#9~@@5^0>GDxecN7hlA~2 zGSRyf?8FfW+hiu;A66jhvaZ8%KGvs?uEdQy@$X?iCC|I$R2qL3{LrASVgj)0mz=){ zoKq+w(!GhUEudu1jgc=T9v=l)-S_Uz)7t4*v&vF~Q>rm&rU~Owq3DCo} z>@B;kJzT3i+6!Mwhh$jG&+`9tJUIP_-~er02xb^6drS>WfB^X1iW>5f&O^mPez=~D z!uw+nRz-7KuFWK{@J4Tz$$;^;B6-6z$Ij@pvX$?z z7mz5;OlA_tmuo@q(tR28Zx`t?p16&R@^8ei5LVA`DT2*-=uJyiEz(=h8><0Sp?d3R z>8p8ZVAVW5^*nKU8s6$y4RAcHrmwH#g$43is?Z5Bln8LfVvty*XeJh!x1u~3>|e0R zC*T!9$tD{UiuoI z-a6`Pni@DwJueS!b#HGqtey^5Q^Q+N2Zvj!4dVDhSB%z>GuJV_cST)#pXZC4&PC7` zRhq%5cD?@P2rneUdyIr=IpRa|5F(*8&&9Q-OU^09LW8R#E!U;-1jsj6Td^?#^|%&bj-dG<|Hp7BS8(*M771kCboAA{0cVE32as>`R@czO>1b$b1BML` zT}>UVr<#YZp00)$++aa8Tb+ZA-ObkqueoJajWnw{ye@yVtXe*KrK>3wtx80>hW^r& zdTZ@pG^L_3u%=XMop5?Ucdo9MhqjgnP7kN9?+t|VG&S{b`Wo6^I4y5Aa9O41;pw5V z@(~>P&(~mc@$*lvzMCuBKsok$u+152;qpVlCb8E}1#HbjW?&@Nyw(_Pbk;}*L{>I5 zPbh1Wn7A^5@;a_e2O@Lz8S-do!^=mzQZF4^_jXNo#l9Zfn{0I1-|=#x1F&S?E_9V!`TCYRMc zmhe^fKN2GJ4V(Tr79woLuOqbxqy?EY5XZhSw~rADEun*TV22RM?%W+L>uG1gk-I2nK9}M=q@5l^)AvuZ8Bon&MML^lU$y% zWBmSl;n#3q-zk*adepgdUgA8q>w)0Qj6AHu4;b;wQ6Y>ar?|ctAVj)x^2uzt!MnEv zCM9U&(850Wj!yU;RvtoUzAh_uKI*!tfMDfW!>wv_ST;3eZ5eF_^k7OHYNMGS=F|1+ zqdt_${!R~=Mc@xn2`Cu_eah^K^M{MO1y9uXM&$6Q)Jm)a+8md8DS%PJi^h47a|R{| z?`O+y+1YSJXfayzNVaS9XD9K-%P>J0NI?Kz3Z+fl3(Hlx{jBwJoJo)OF@%2Rccp~k z`!j$Q6r%q6BuvSjMO>DrTl*qQecy;0M_k>#h_odl_xX_cvB$AQE;DBGo!ZaN?)kb; zgIUkuoKxI5wixX803uz|B84|Lg8X4cmB8YGuE*45-xS9@M@cAVmlo6cuc_Yd|4=5D`e+I?cjnC3jkr60>jTyQtllr*Tc zfKPBcsXGcewNA9?0oUGlhc<`0J9_K|zuXYeV+ma9PwFhf8A>fzD!2_n+*G1*1XKz|fd zQk=T&oR!J*oHRQ{iKZkTwNs(!@`On0s}I~)`)gLeh+J{Ou{pnp#&#qa3`&2h3X?>IsX3jN!o zgnGUa9<=mq0FM#kc}Wljj4IXE^=_rjL%8ZTAN#o2K9*9Ut23;1Kg-^)|AT&|jf;?Q zN{!z5O>iX)ExrQM1<-?ZgI5AE?hpuRaLq88OSpY-Iy!El@H!a(zxcK3>fDDpxane+ zdAdPzcSPn+K#L$)Gcj<)k0G*%8!M$!0m?igp73>2ZS|O?B=5X*!ri@(I4e;LU7#BY zm|MTkCIUk#v>l(wTtQY1qZHv#hnAxV4KED=_?}H7Y)UHs?QJ3yu-UI2{FW z)4?~kNT5lTo=pw9a^G}iyg?2q#9*~Ymz-2kanbOGI%Lt$k8f{BQMWyx>`NOt1=`zI z`2?4WZt1O9Wzk4IZTO>Rzh98XR2XJ?hK1~qR3SS?K$HDGC9Cz@SbjlWr>1@?-mCkX ze$oD|v8U-#1HQYh?Z2P=Si&2*N$bm&c4p}`n@HpO}cWmM5Xxj3*)I3z7Le|6i<4F?+^iQ+^XaNKcu~7HQNLK|Yw6{gz1;Ei#{ z(NEo{nGeygkOcU-Q z?@Xzq7HmKttAXzI_l2AgBIc6;IW@C~*?a#-N`jQRti0pW8ed%b6tl=@qRe-;~hGH~mFa$}BlX3YK#4YLd4c$bO?Eq-Nv-bTTu+6WCr0 z8u@&bl=;7PGUFbmTwO$J6H)IAmv2+KHwM$yu#t3bB!Mk>6kJevj+Dz%TT0)LSUD05 zad%{^v7hW3UmgBIQK9XcoA5Oe+BEbod}#6Up@UD~Us~if$ktFy+uoSLRq*KH3SNv9 zzqItZkwP&Z(8z5}<$hXjq;co0%Lt?Kqx^6{QQCq2Ca|cH(5n2>kfRKnU_fYLGj>?J zZG9**(t-%i&7OEA6b2Sm>BFaLEa9w)sGNLq(Rr1NURfhaON_3EQV+{1Pks&HftB9= z!3efel@$QB8UB^+a(<&jkz~xdMl@-#Y)!Y5U~d8hmIx$eO+@)`%R1;$^zOL*ZZQT9 zrDfg6D@vr+ixaLtC)2w&rL#&DJP(|CCe+rDlGx|Kl6%GzqdxW)=3Rj6V-#qyf$X?R z-VPP8@8Eb;so&Ga-j#|1U_)%vzLKk^6;hczVH`g$8=?F6zNf|S8FvI4`x@3cwkIEM z5LslED#*)P!Czm20Pseo&*Wg|D$5qffdeDSIzn$-FEcFTx$n5IaP-oqpu%9=Lu;*2u zqOul|l>`Q15@y0zi^!sLg51{Rx?9h*sqY;x=CZt)_$H!cpaNLZNhUH*qi~HN7APQo zNl#<>-=s3x>fF1;?)JS;Z4ph|y0Gng=gB_VdltXi2iFX!1MyK@e61n&QCJK9#D+xrT#00tQG(Y0i3q_xc z9K!tDzQtX-DmV@dDmRCcKH%ZqC8RQ9i!VAOi^b%+Sw`>~@`iTXKC=+#WQBz`|FhGG zt#thX1f;4-M5MUHvg@4LtHpVm8EF|CHHQ=3^P}&7uA9+W9SWBDN14;j9_jXRnsY~@LDjb?q z2vtpePK*BstoUNjp5>#k|5U#-Y*T&Y`B$Y`E~5*)92rZhkEi+;A+8IH)4#bcHmhcg zJI}bxoqv7!js%m8;*?e`Fn$cQ?@+CDlg~LFP}ZF2=~QKVJ+IAQ78T!(g_Nw2)~Uw) zubDH5w%(hjF7DZ8q^DzNlZ)*<{5&%0a6sb8FF9fm#M5!b`#O= zrG2g!1_qkWS%e_qQx_;#s7t;sXHRumetc04wEXWw-b@C8z3`7QZt&dcl)A!N#=;M- z>S~g(KG-n=k&$#*gsuh$D&sckg}>cqio!XPTO8?fyMWMrsoS>xm=9q_N{}Ih14AU0 zFR$;xvqjCA_Dk8stCC(JVynMD^JeI5!dd|YOc{2Px@1zVGqumY^DO!Ej zj+3X>gm;C0TU=u|lM}ZxW6H!BwtCHRpVeBiWh8UO0e&S;d@kvTmz9*D#)fd0xqIw4 zvJAB;_(L@JO-nKGj+YW_Nv9+=Lpi;hhdjA=a7zpstk1@pIFK1J?l-5b#&YH>B|$K7 zva<4HF1;(j=z1=Tc++2uzz6H%sTvuJO|5z^H!&7H>1V`p}0>yhW=<&;E!X+a_;S-kt z6NRsgxi#eX?3bEh-S%VoL58a@n4*Nki8{cbS$R@YgsYnvj0^UWJiSL*&@2|m+7*9Dx=bNp5WJ+{!Xu*o37uPQ~e_e>4Ik7 z-4}oRttn1wzia-d0!B2l2mxm*p7PhbfS*#BQ~d`5__lHueY~Pvj?jJ3*h)WdGEyLOk4s4&nLkhyzw7$J^_L-Xx79D ze2gR;Un)7ezoqm*>=E91&a;p_t-G+A|cCR)a z15-h;(SRR~PsVryyo;e?1+(Y|iX)~{61+WC^F;%PuEz?}YYyj5t}U8@VpF>24LEWZ zJw=ACfI^sX5bUgZtD95B&NyO|eP-n4=9rF73E+yJiLKro-4oz!VevGHr_W8PY4>f9 z-zaO6X%rQ z(0%LXm1hmtdTS}vV7-)DT(@_Ns3v&~h{PFVXrsFZU{WD6nXZ4zBvZ`uSk>vwQ=cej zm2uZ*)(-EY7y}R8so83orgIOSk-% zwz$}_YXqAQ%$;F+SZrD>s_rh_9e(B}WIk~-M2%lPU>rHQq1m`iqCnmDv382SP_S%7 zos|3Rdn56oX@mI(R&Hd0h$jWefov^Zmr z%MNh27HnzY7nO~O`U$C z$5b2#vy^`lad&&lUB+Z+a%Prvx8TLs7(E^fv4u$@qep6QMQE`4P`bK4KKcY- z4Zzw=1S&c{B!ZtWk))?f)J60}5aUun|E#ynS4&${i{uB4pS9KfNWMPWzRPs=^}wox zA5op8t_fTp>uGBvMY5*Cq&uJ=z4_}LI#rJOo2DZN#?V>ki>(k-W-hCpHw{u!o=PdrA}IA_~_*0To19Hrx!3A z*T1AbmV3GKCB##gbuKX|%Bac~RJQQvOtGn>;bmfwViqmED`Z=|*@z@j+J$Yo?M|Sq z^+AAXchKu;Ar`;|+aa>VnD4Q->@j}+_gk|oKbgqJ%f7j~_Jg-Xc80L}y8;c)eD=9J zYN&gT?fB~IpY>v$Wz7Q5VGB-NtN#V>wR6Y5EaVQ;3Q|fNQQtTI>0uxyXS}~<$eS~4 zhHR9%>EqSFnXU9Bg5E~H+((r zNfl=XlQNX3oI4X(IY+U2Sr|V57}rJe=|+c@c!ZExc=BXqmQTd7L=FEx}r@@y0Ck#ti&I6k%Z177lx+ zgTJ2Kbt_iQb{Q_Ds0y?OOa9EbQ0k>;Z)g;=_e@P8X2PBcgMlrF$Ye`RX#Mh8VvMm3 z`1FJ7GsKz}-HZmBcIT3HQr$W}tpsb4j_~^f<^AFK;d!FqvgVkL$6vav-=6Tw-uqZY z)j@bQ@=7a-fq|~Q&h)HS!%GMBW$>qsxwO_fZr3jB+0tycW9;m1WO$01&cCIPgILOw z;!Uc-ts9FLe9o(899^P6f)gjvuyyWFfFq7yh$9ZEfn$C*Q*ViEOseS_Yv0kmlXbf&ir_iwD-)bPy5JBt>Y#u>V6&xlF_xy ztIw;c2s_PV4Zc_?P>&|MU3N1S=cP_xp%9QDrF$vBdWV-)VB#|kf2VpM@WpBnu779$ zkNbU#)@qDCoa=MJR@2X@RA#lVAu2`edi>+7tFs&l+Y3aW?!sGd5?e`Lj^_Vws-`7k z?@MO#BNrj+tI@&T5O6eN6zmNU4OLi|T zU;nv-cQxKsvU(G{IIJ;SL=~V~5DdE2m|WHmIiH9Lhaa3cfseCW^$QAoaAZ?KdH#^h z86I%h-6s}M(vaec13R`~ZLP*J-(rfL9#WK-Z~O6_YhxlK?AK6vcFGaaw-My9xpg7ck5)^o6&0Z` z1m0IQ`Z9p*g%^Pv;3b5`27O z<=qIIH%a!1fa1d;*fmnmnXt)4$0}}r8#EwKK&L$ ziT*4ckFUL*j^_Nw!O&1)`*zOTgk>gXOBw&|PInQ9)%530Wg8zWrr!g`XV}Z&#Zsnm zjm~`p@0zLn$E)dREcoz<2C-tC$oNu91L=MIp4*BU%6EGU8JbG_)=lZxP%c@UZBOOt zEI(L2!*#&X#L?)+m5H?Q@#5wl^AVa^p2Df3Ux!K_2=n?p8HB^3qeJwip*)Z3byDay zgH_;Qvxy0>%gWIs{#9f`*T}IziKypzj0x z$LMJ45=nrmkf^7w4u%iD1a}88ac-Kb7Ua6pK$FzG=OlNMr zzeOA)Q}I#f*foTvbz3{->$DddNPlK$Iu$*uf}D48b$MLkM5sEu#+rMJU+-TNuh}>x z<6NGzNSqU2#d*S#x!FNb4qHSOu&+6T7wq*)5)l0q(I+o-#ZJci^z~fbQwV->Cw2r{ zhunQSjZ1xVCA_;&Ie3en>+{Az=u!UpxkZ$p3nQH`svgchlZg!=>~6IRe{`i4_J;4?rqT(GGL zACy2k!2j4G`6x6-;Dkkk+dq6SGN|5tT0DNyFU}&-SodLk(%J*5$Jd5D5BzC3=gVVE zIPSacPo7rh+4IEf`+Jej zPgx~7`4#d6%9CR2W5vT(&9h@Df;lOy0&gD$iM)>t1z|VrI&&(lF!S5T(FmKj(=U9E zTbhA%e~wk!`7=kCz2Em!XHn1%%u?3XXNzHyA z?oH}zws!GaJiO7HMm$~V5oxZtvE;Y#z<=Zkk=YTHMbI1uBJg-$ubaZ;L1>J3?KE6K zmmKigFuFaVH0Mn{nW5e@8|Yz7zd_n9YniX!SvXwyUDV4PP=45W*VvP}hvWyb!)xHG z0ZA#2(9<~qdb+PfAO$OxakUR#fCH(ELeZbGN7{B-JvSoz%;=gV9L~SQg z32n}YK-W<`|0D;);*1BI^35Lt**%!e@apR9N}OvjPVwn3flsq z*$pN08;-2Cbpc-QN`5Erc(yxN{?i+ox$kKPscsJ~Yeum3RH5z{G%l>iJu3c85xrmO z=#7?y_N1;t<^3mLc7*GgEA0jyjqzfBk$68t8=`l&-Wqi#-SDhXSd+<`ll5Abbtxq_ z)ImP(eCJ@*r-K#`TxJ11Rw#}pwC7|grRp>tzOg?Eu9GG0hg#2KFgc{+pQ?m~_VaCD z44!q~1zImr_!Z^qf-B4pWJl#XXG=$=6)x#2BZ;PPKDUg)h6{naS8W+QwYM(h!$e?Qd*Tzen%FVbJhY{`$!(gT|c?n+D zu|4K+h(9JO+|=~dooyLUmB4Uv>H_xud4&5$7DavwYuv@xwXa5Kk~vEjI3(gmUvTr= ziIMri_tyWoufNgd?%VliOM5^=VEMj(K4)A-M##^+h;rM*eY6|*!=#*|^cz^yY@CrM zNoOF9~+pYYZ*mVYL5gdH|VbP!KhrFX3 zJ{IZ}^R&2`VI1&o$k&hNIRqX#L1U|Fyqakx$jIq@K_;Kv&4rB^Y?>owrqiuzN2}$? z$jN447yAnM*%G=iJ~Iv(gE=aeZ@-CnuG&?(zZ#Cr?yL$<$|JSWEuV*2vS-FoJ+!9a z250owy!Ks0X8w!nZrd>$PCe2E*&$SS$DbIi<~9E^gY+aLIw)4SW|HW?PnQ z*ZJ_}kyBl}bn{S!*)0XJl8Nhsk4;X;ao(n>etx1>_q6gpAM&(XXwHd;WHpp9WYr}0 zZNnu#{Z!eKIxp}jf`fPBPeLle3jxu2)k0k6yCb=_79F`N0Ua{o%nL<^nA1;FKYiBn zetCIQZZ+fBX@#vT)*1`nHbyi+sf&w_(^Pt#F|@WDS22`3_VkRbxFvSKBRe8o1#xG# zE*qtF9^92Y&U8j*qVc0486jpW;70!JXrhsG|D&UbKuk2Jks~;05Y1-+@`|E4Nk(h} z%a+i1HqNy}nkclz9PoxqV&`8S=yo0K=Acu~oRX}F6U;_WyL~^gob3*5f~D>$sE!Q?9?&hlQ;oWGSM7q`dC z`<;-%nFf&X07yu=V@AV&>%hP}Au4y5@-zn2SR$fdgPqI4{0rO52LpxTzleX;*&%EO zen7ylqo=K_wQQLdFbCJu)zi@fksL{Wr0{UUW(5NU1vNUF4nMh+-_OgTfnK}FoNAr z<&!^2TFKqInnn zuNn|}41iyd#$|ZZk_utl>CddT%MDyLU9S1A@s8FIzlCb%00#Kq@*vv%^~ZArAY+Rm zR9DULw@AeW!jXxofwGAlsW9iy-`8{C6AWP}#`+L-h7p@`PW-?;;>~j1{E=?w{F?(y z^B-7`6-=qYIOAfGmO%>C-e}|l$c^Ii1!NAY5e@wqt#V~iq6vJpJZrvFq_b%TiHjr< z6*Cn*7yoY^MnTW)UT#%>wOkruYuwN)`_7i%Nf!p6+($=7-MqU&3-j8SgwnIbdo{M?k4$=?dO`a|E?L(rn*; zBza1Dtn)8Il>EkxAK?k2Nvp^40{`Hx5GtXgtu&yJ0ipQ}9nbUc!#L$I8$eT*W`jU5 zxS){O_UPsF-a-9aH;Q&hAIIkvapJ4Lc;3z_H*JY(<9fIB{PKMRL71jVJomrs8$rzk z?B@t9>&CF~P-2vCWI$-hQghd}1_ofabaOxmi2$>j`09~>n`wfEzMs}IUqm(GU$${* zoLnPVVJcVhD2+$ZLxhSwG81tadhT;kLx#;#Mdg>hMnPBmqChj=wL~w0V|eY)*qaaS+YqK&{jHJIriGHLa=C!U2X>;d_xZ{9&Uj$k4QHo9Eo3^FKH2yK|k`U(&1Ok!uEs zv%%2Rl* Date: Thu, 16 Jan 2025 15:23:32 -0600 Subject: [PATCH 6/7] fix delete test file --- .../25-01-15-INCOMPLETE.bin} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename prover/lib/compressor/{dict-25-01-15-INCOMPLETE.bin => dict/25-01-15-INCOMPLETE.bin} (100%) diff --git a/prover/lib/compressor/dict-25-01-15-INCOMPLETE.bin b/prover/lib/compressor/dict/25-01-15-INCOMPLETE.bin similarity index 100% rename from prover/lib/compressor/dict-25-01-15-INCOMPLETE.bin rename to prover/lib/compressor/dict/25-01-15-INCOMPLETE.bin From 3aac628f9897c93e3cf7fd3908ad4b97d88f4bde Mon Sep 17 00:00:00 2001 From: Arya Tabaie <15056835+Tabaie@users.noreply.github.com> Date: Fri, 17 Jan 2025 11:22:26 -0600 Subject: [PATCH 7/7] fix CI prover config --- docker/config/prover/v3/prover-config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/config/prover/v3/prover-config.toml b/docker/config/prover/v3/prover-config.toml index 7f889515a..3e1653c9a 100644 --- a/docker/config/prover/v3/prover-config.toml +++ b/docker/config/prover/v3/prover-config.toml @@ -13,7 +13,7 @@ requests_root_dir = "/data/prover/v3/execution" [blob_decompression] prover_mode = "dev" requests_root_dir = "/data/prover/v3/compression" -dict_path = "/opt/linea/prover/lib/compressor/compressor_dict.bin" +dict_paths = ["/opt/linea/prover/lib/compressor/compressor_dict.bin"] [aggregation] prover_mode = "dev"