From 2353bc7cc72c2f10c8b505c31f02dde6601421d3 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Thu, 26 Oct 2023 03:19:07 -0400 Subject: [PATCH] verkle: verkle package (#2923) * verkle: initial commit * verkle: scaffold verkle trie implementation and types * verkle: fix installation issues * verkle: move rust verkle wasm to verkle package * verkle: remove package json and readme from rust verkle wasm dir * util: add some bigint LE utils for verkle * verkle: refactor constants and cryptographic methods to the verkle package * verkle: remove unnecessary constants & methods * verkle: verkle node classes and types * verkle: get method and rawNode utils * verkle: handle case where array item is not found * verkle: wip findPath method * verkle: wip * verkle: fromRawNode method * verkle: update packages * verkle: minor adjustments * verkle: wip * verkle: add test case for put and get * verkle: wip insertstem and related methods for verkle nodes * verkle: internal node test * verkle: default values and minor fixes to internalNode * verkle: make internal verkle node options optional * verkle: leafNode basic test * verkle: remove unused import * verkle: setDepth method for leafNode and commented out code for create method * verkle: wip * verkle: add Point interface * verkle: general cleanup and improvements * verkle: minor test adjustments * verkle: readme and testing related updates * verkle: capital V for verkle workflow * Update packages/verkle/README.md Co-authored-by: Scorbajio * Update packages/verkle/README.md Co-authored-by: Scorbajio * Update packages/verkle/README.md Co-authored-by: Scorbajio * Update packages/verkle/README.md Co-authored-by: Scorbajio * verkle: use Lock from util package * client: undo removal of link * util: update byte<>int conversion helpers * verkle: use Uint8Array instead of hexstrings for db * verkle: add imports to example * verkle: update db to Uint8Arrays * verkle: revert to using strings as keys for cache * verkle: update crypto import * verkle: adjust Key and Value encoding in create method * verkle: adjust value encoding in db del method * verkle: add missing key and value encoding opts to batch options * verkle: remove extra line --------- Co-authored-by: acolytec3 Co-authored-by: Scorbajio --- .../package--ethereumjs-verkle.md | 7 + .github/workflows/verkle-build.yml | 42 ++ README.md | 10 + package-lock.json | 25 + packages/util/src/bytes.ts | 56 ++ packages/verkle/.c8rc.json | 4 + packages/verkle/.eslintrc.cjs | 14 + packages/verkle/.gitignore | 1 + packages/verkle/.npmignore | 2 + packages/verkle/.prettierignore | 6 + packages/verkle/CHANGELOG.md | 11 + packages/verkle/README.md | 112 ++++ packages/verkle/package.json | 60 ++ packages/verkle/src/db/checkpoint.ts | 270 +++++++++ packages/verkle/src/db/index.ts | 1 + packages/verkle/src/index.ts | 2 + packages/verkle/src/node/baseVerkleNode.ts | 33 ++ packages/verkle/src/node/index.ts | 4 + packages/verkle/src/node/internalNode.ts | 120 ++++ packages/verkle/src/node/leafNode.ts | 85 +++ packages/verkle/src/node/types.ts | 49 ++ packages/verkle/src/node/util.ts | 30 + .../src/rust-verkle-wasm/LICENSE_APACHE | 176 ++++++ .../verkle/src/rust-verkle-wasm/LICENSE_MIT | 25 + .../rust-verkle-wasm/rust_verkle_wasm.d.ts | 18 + .../src/rust-verkle-wasm/rust_verkle_wasm.js | 340 ++++++++++++ .../rust-verkle-wasm/rust_verkle_wasm_bg.wasm | Bin 0 -> 342524 bytes .../rust_verkle_wasm_bg.wasm.d.ts | 9 + packages/verkle/src/types.ts | 118 ++++ packages/verkle/src/util/bytes.ts | 25 + packages/verkle/src/util/crypto.ts | 40 ++ packages/verkle/src/util/index.ts | 4 + packages/verkle/src/util/tasks.ts | 59 ++ packages/verkle/src/util/walkController.ts | 145 +++++ packages/verkle/src/verkleTrie.ts | 516 ++++++++++++++++++ .../verkle/test/node/internalNode.spec.ts | 52 ++ packages/verkle/test/node/leafNode.spec.ts | 42 ++ packages/verkle/test/verkle.spec.ts | 60 ++ packages/verkle/tsconfig.benchmarks.json | 4 + packages/verkle/tsconfig.json | 7 + packages/verkle/tsconfig.prod.cjs.json | 13 + packages/verkle/tsconfig.prod.esm.json | 13 + packages/verkle/typedoc.cjs | 6 + packages/verkle/vitest.config.browser.ts | 7 + 44 files changed, 2623 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/package--ethereumjs-verkle.md create mode 100644 .github/workflows/verkle-build.yml create mode 100644 packages/verkle/.c8rc.json create mode 100644 packages/verkle/.eslintrc.cjs create mode 100644 packages/verkle/.gitignore create mode 100644 packages/verkle/.npmignore create mode 100644 packages/verkle/.prettierignore create mode 100644 packages/verkle/CHANGELOG.md create mode 100644 packages/verkle/README.md create mode 100644 packages/verkle/package.json create mode 100644 packages/verkle/src/db/checkpoint.ts create mode 100644 packages/verkle/src/db/index.ts create mode 100644 packages/verkle/src/index.ts create mode 100644 packages/verkle/src/node/baseVerkleNode.ts create mode 100644 packages/verkle/src/node/index.ts create mode 100644 packages/verkle/src/node/internalNode.ts create mode 100644 packages/verkle/src/node/leafNode.ts create mode 100644 packages/verkle/src/node/types.ts create mode 100644 packages/verkle/src/node/util.ts create mode 100644 packages/verkle/src/rust-verkle-wasm/LICENSE_APACHE create mode 100644 packages/verkle/src/rust-verkle-wasm/LICENSE_MIT create mode 100644 packages/verkle/src/rust-verkle-wasm/rust_verkle_wasm.d.ts create mode 100644 packages/verkle/src/rust-verkle-wasm/rust_verkle_wasm.js create mode 100644 packages/verkle/src/rust-verkle-wasm/rust_verkle_wasm_bg.wasm create mode 100644 packages/verkle/src/rust-verkle-wasm/rust_verkle_wasm_bg.wasm.d.ts create mode 100644 packages/verkle/src/types.ts create mode 100644 packages/verkle/src/util/bytes.ts create mode 100644 packages/verkle/src/util/crypto.ts create mode 100644 packages/verkle/src/util/index.ts create mode 100644 packages/verkle/src/util/tasks.ts create mode 100644 packages/verkle/src/util/walkController.ts create mode 100644 packages/verkle/src/verkleTrie.ts create mode 100644 packages/verkle/test/node/internalNode.spec.ts create mode 100644 packages/verkle/test/node/leafNode.spec.ts create mode 100644 packages/verkle/test/verkle.spec.ts create mode 100644 packages/verkle/tsconfig.benchmarks.json create mode 100644 packages/verkle/tsconfig.json create mode 100644 packages/verkle/tsconfig.prod.cjs.json create mode 100644 packages/verkle/tsconfig.prod.esm.json create mode 100644 packages/verkle/typedoc.cjs create mode 100644 packages/verkle/vitest.config.browser.ts diff --git a/.github/ISSUE_TEMPLATE/package--ethereumjs-verkle.md b/.github/ISSUE_TEMPLATE/package--ethereumjs-verkle.md new file mode 100644 index 0000000000..fbe58a562d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/package--ethereumjs-verkle.md @@ -0,0 +1,7 @@ +--- +name: 'Package: @ethereumjs/verkle' +about: Create issue for @ethereumjs/verkle package +title: '' +labels: 'package: verkle' +assignees: '' +--- diff --git a/.github/workflows/verkle-build.yml b/.github/workflows/verkle-build.yml new file mode 100644 index 0000000000..5517c34353 --- /dev/null +++ b/.github/workflows/verkle-build.yml @@ -0,0 +1,42 @@ +name: Verkle +on: + push: + branches: [master, develop] + tags: ['*'] + pull_request: + types: [opened, reopened, synchronize] + workflow_dispatch: + +env: + cwd: ${{github.workspace}}/packages/verkle + +defaults: + run: + working-directory: packages/verkle + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + test-verkle: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18] + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - run: npm ci --omit=peer + working-directory: ${{github.workspace}} + + - run: npm run lint + - run: npm run test:node # Only run node tests for now until vitest browser test issues are sorted out diff --git a/README.md b/README.md index 9e838ae38c..8d51866e2b 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Below you can find a list of the packages included in this repository. | [@ethereumjs/trie][trie-package] | [![NPM Package][trie-npm-badge]][trie-npm-link] | [![Trie Issues][trie-issues-badge]][trie-issues-link] | [![Actions Status][trie-actions-badge]][trie-actions-link] | [![Code Coverage][trie-coverage-badge]][trie-coverage-link] | | [@ethereumjs/tx][tx-package] | [![NPM Package][tx-npm-badge]][tx-npm-link] | [![Tx Issues][tx-issues-badge]][tx-issues-link] | [![Actions Status][tx-actions-badge]][tx-actions-link] | [![Code Coverage][tx-coverage-badge]][tx-coverage-link] | | [@ethereumjs/util][util-package] | [![NPM Package][util-npm-badge]][util-npm-link] | [![Util Issues][util-issues-badge]][util-issues-link] | [![Actions Status][util-actions-badge]][util-actions-link] | [![Code Coverage][util-coverage-badge]][util-coverage-link] | +| [@ethereumjs/verkle][verkle-package] | [![NPM Package][verkle-npm-badge]][verkle-npm-link] | [![VM Issues][verkle-issues-badge]][verkle-issues-link] | [![Actions Status][verkle-actions-badge]][verkle-actions-link] | [![Code Coverage][verkle-coverage-badge]][verkle-coverage-link] | | [@ethereumjs/vm][vm-package] | [![NPM Package][vm-npm-badge]][vm-npm-link] | [![VM Issues][vm-issues-badge]][vm-issues-link] | [![Actions Status][vm-actions-badge]][vm-actions-link] | [![Code Coverage][vm-coverage-badge]][vm-coverage-link] | | [@ethereumjs/wallet][wallet-package] | [![NPM Package][wallet-npm-badge]][wallet-npm-link] | [![StateManager Issues][wallet-issues-badge]][wallet-issues-link] | [![Actions Status][wallet-actions-badge]][wallet-actions-link] | [![Code Coverage][wallet-coverage-badge]][wallet-coverage-link] | @@ -235,6 +236,15 @@ Most packages are [MPL-2.0](=18" + } + }, + "packages/verkle/node_modules/lru-cache": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.0.tgz", + "integrity": "sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw==", + "engines": { + "node": "14 || >=16.14" + } + }, "packages/vm": { "name": "@ethereumjs/vm", "version": "7.0.0", diff --git a/packages/util/src/bytes.ts b/packages/util/src/bytes.ts index f573fea903..b4a5536011 100644 --- a/packages/util/src/bytes.ts +++ b/packages/util/src/bytes.ts @@ -444,5 +444,61 @@ export const concatBytes = (...arrays: Uint8Array[]): Uint8Array => { return result } +/** + * @notice Convert a Uint8Array to a 32-bit integer + * @param {Uint8Array} bytes The input Uint8Array from which to read the 32-bit integer. + * @param {boolean} littleEndian True for little-endian, undefined or false for big-endian. + * @throws {Error} If the input Uint8Array has a length less than 4. + * @return {number} The 32-bit integer read from the input Uint8Array. + */ +export function bytesToInt32(bytes: Uint8Array, littleEndian: boolean = false): number { + if (bytes.length < 4) { + throw new Error('The input Uint8Array must have at least 4 bytes.') + } + const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength) + return dataView.getInt32(0, littleEndian) +} + +/** + * @notice Convert a Uint8Array to a 64-bit bigint + * @param {Uint8Array} bytes The input Uint8Array from which to read the 64-bit bigint. + * @param {boolean} littleEndian True for little-endian, undefined or false for big-endian. + * @throws {Error} If the input Uint8Array has a length less than 8. + * @return {bigint} The 64-bit bigint read from the input Uint8Array. + */ +export function bytesToBigInt64(bytes: Uint8Array, littleEndian: boolean = false): bigint { + if (bytes.length < 8) { + throw new Error('The input Uint8Array must have at least 8 bytes.') + } + const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength) + return dataView.getBigInt64(0, littleEndian) +} + +/** + * @notice Convert a 32-bit integer to a Uint8Array. + * @param {number} value The 32-bit integer to convert. + * @param {boolean} littleEndian True for little-endian, undefined or false for big-endian. + * @return {Uint8Array} A Uint8Array of length 4 containing the integer. + */ +export function int32ToBytes(value: number, littleEndian: boolean = false): Uint8Array { + const buffer = new ArrayBuffer(4) + const dataView = new DataView(buffer) + dataView.setInt32(0, value, littleEndian) + return new Uint8Array(buffer) +} + +/** + * @notice Convert a 64-bit bigint to a Uint8Array. + * @param {bigint} value The 64-bit bigint to convert. + * @param {boolean} littleEndian True for little-endian, undefined or false for big-endian. + * @return {Uint8Array} A Uint8Array of length 8 containing the bigint. + */ +export function bigInt64ToBytes(value: bigint, littleEndian: boolean = false): Uint8Array { + const buffer = new ArrayBuffer(8) + const dataView = new DataView(buffer) + dataView.setBigInt64(0, value, littleEndian) + return new Uint8Array(buffer) +} + // eslint-disable-next-line no-restricted-imports export { bytesToUtf8, equalsBytes, utf8ToBytes } from 'ethereum-cryptography/utils.js' diff --git a/packages/verkle/.c8rc.json b/packages/verkle/.c8rc.json new file mode 100644 index 0000000000..52eb43c23b --- /dev/null +++ b/packages/verkle/.c8rc.json @@ -0,0 +1,4 @@ +{ + "extends": "../../config/.c8rc.json", + "include": ["src/**/*.ts"] +} diff --git a/packages/verkle/.eslintrc.cjs b/packages/verkle/.eslintrc.cjs new file mode 100644 index 0000000000..884b3d6ebe --- /dev/null +++ b/packages/verkle/.eslintrc.cjs @@ -0,0 +1,14 @@ +module.exports = { + extends: '../../config/eslint.cjs', + parserOptions: { + project: ['./tsconfig.json', './tsconfig.benchmarks.json'], + }, + overrides: [ + { + files: ['benchmarks/*.ts'], + rules: { + 'no-console': 'off', + }, + }, + ], +} diff --git a/packages/verkle/.gitignore b/packages/verkle/.gitignore new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/packages/verkle/.gitignore @@ -0,0 +1 @@ + diff --git a/packages/verkle/.npmignore b/packages/verkle/.npmignore new file mode 100644 index 0000000000..55c65cf8bd --- /dev/null +++ b/packages/verkle/.npmignore @@ -0,0 +1,2 @@ +test/ +src/ \ No newline at end of file diff --git a/packages/verkle/.prettierignore b/packages/verkle/.prettierignore new file mode 100644 index 0000000000..9fa0fb74e1 --- /dev/null +++ b/packages/verkle/.prettierignore @@ -0,0 +1,6 @@ +node_modules +.vscode +dist +.nyc_output +*.json +docs \ No newline at end of file diff --git a/packages/verkle/CHANGELOG.md b/packages/verkle/CHANGELOG.md new file mode 100644 index 0000000000..459edbc1b3 --- /dev/null +++ b/packages/verkle/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +(modification: no type change headlines) and this project adheres to +[Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [0.0.1] - 2023-10-15 + +- Initial development release diff --git a/packages/verkle/README.md b/packages/verkle/README.md new file mode 100644 index 0000000000..d5440ff799 --- /dev/null +++ b/packages/verkle/README.md @@ -0,0 +1,112 @@ +# @ethereumjs/verkle + +[![NPM Package][verkle-npm-badge]][verkle-npm-link] +[![GitHub Issues][verkle-issues-badge]][verkle-issues-link] +[![Actions Status][verkle-actions-badge]][verkle-actions-link] +[![Code Coverage][verkle-coverage-badge]][verkle-coverage-link] +[![Discord][discord-badge]][discord-link] + +| Implementation of [Verkle Trees](https://ethereum.org/en/roadmap/verkle-trees/) as specified in [EIP-6800](https://eips.ethereum.org/EIPS/eip-6800) | +| --------------------------------------------------------------------------------------------------------------------------------------------------- | + +> Verkle trees are a cryptographic data structure proposed for use in Ethereum to optimize storage and transaction verification. They combine features of Merkle Patricia Tries and Vector Commitment Trees to offer efficient data verification with smaller proof sizes. The goal is to improve scalability and efficiency in Ethereum's network operations. + +This package is currently in early alpha and is a work in progress. It is not intended for use in production environments, but rather for research and development purposes. Any help in improving the package is very much welcome. + +## Installation + +To obtain the latest version, simply require the project using `npm`: + +```shell +npm install @ethereumjs/verkle +``` + +## Usage + +This class implements the basic [Modified Merkle Patricia Trie](https://ethereum.org/en/developers/docs/data-structures-and-encoding/patricia-merkle-trie/) in the `Trie` base class, which you can use with the `useKeyHashing` option set to `true` to create a trie which stores values under the `keccak256` hash of its keys (this is the Trie flavor which is used in Ethereum production systems). + +Checkpointing functionality to `Trie` through the methods `checkpoint`, `commit` and `revert`. + +It is best to select the variant that is most appropriate for your unique use case. + +### Initialization and Basic Usage + +```typescript +import { VerkleTrie } from '@ethereumjs/verkle' +import { bytesToUtf8, utf8ToBytes } from 'ethereumjs/util' + +const trie = new VerkleTrie() + +async function test() { + await trie.put(utf8ToBytes('test'), utf8ToBytes('one')) + const value = await trie.get(utf8ToBytes('test')) + console.log(value ? bytesToUtf8(value) : 'not found') // 'one' +} + +test() +``` + +## Proofs + +### Verkle Proofs + +The EthereumJS Verkle package is still in its infancy, and as such, it does not currently support Verkle proof creation and verification. Support for Verkle proofs will be added eventually. + +## Examples + +You can find additional examples complete with detailed explanations [here](./examples/README.md). + +## Browser + +With the breaking release round in Summer 2023 we have added hybrid ESM/CJS builds for all our libraries (see section below) and have eliminated many of the caveats which had previously prevented frictionless browser usage. + +It is now easily possible to run a browser build of one of the EthereumJS libraries within a modern browser using the provided ESM build. For a setup example see [./examples/browser.html](./examples/browser.html). + +## API + +### Docs + +Generated TypeDoc API [Documentation](./docs/README.md) + +### Hybrid CJS/ESM Builds + +With the breaking releases from Summer 2023 we have started to ship our libraries with both CommonJS (`cjs` folder) and ESM builds (`esm` folder), see `package.json` for the detailed setup. + +If you use an ES6-style `import` in your code, files from the ESM build will be used: + +```typescript +import { EthereumJSClass } from '@ethereumjs/[PACKAGE_NAME]' +``` + +If you use Node.js-specific `require`, the CJS build will be used: + +```typescript +const { EthereumJSClass } = require('@ethereumjs/[PACKAGE_NAME]') +``` + +Using ESM will give you additional advantages over CJS beyond browser usage like static code analysis / Tree Shaking, which CJS cannot provide. + +## References + +- Wiki + - [Overview of verkle tries](https://ethereum.org/en/roadmap/verkle-trees/) + - [Verkle tries General Resource](https://verkle.info/) + +## EthereumJS + +See our organizational [documentation](https://ethereumjs.readthedocs.io) for an introduction to `EthereumJS` as well as information on current standards and best practices. If you want to join for work or carry out improvements on the libraries, please review our [contribution guidelines](https://ethereumjs.readthedocs.io/en/latest/contributing.html) first. + +## License + +[MPL-2.0]() + +[discord-badge]: https://img.shields.io/static/v1?logo=discord&label=discord&message=Join&color=blue +[discord-link]: https://discord.gg/TNwARpR +[verkle-npm-badge]: https://img.shields.io/npm/v/@ethereumjs/verkle.svg +[verkle-npm-link]: https://www.npmjs.com/package/@ethereumjs/verkle +[verkle-issues-badge]: https://img.shields.io/github/issues/ethereumjs/ethereumjs-monorepo/package:%20verkle?label=issues +[verkle-issues-link]: https://github.com/ethereumjs/ethereumjs-monorepo/issues?q=is%3Aopen+is%3Aissue+label%3A"package%3A+verkle" +[verkle-actions-badge]: https://github.com/ethereumjs/ethereumjs-monorepo/workflows/Trie/badge.svg +[verkle-actions-link]: https://github.com/ethereumjs/ethereumjs-monorepo/actions?query=workflow%3A%22Trie%22 +[verkle-coverage-badge]: https://codecov.io/gh/ethereumjs/ethereumjs-monorepo/branch/master/graph/badge.svg?flag=verkle +[verkle-coverage-link]: https://codecov.io/gh/ethereumjs/ethereumjs-monorepo/tree/master/packages/verkle diff --git a/packages/verkle/package.json b/packages/verkle/package.json new file mode 100644 index 0000000000..ae2e074494 --- /dev/null +++ b/packages/verkle/package.json @@ -0,0 +1,60 @@ +{ + "name": "@ethereumjs/verkle", + "version": "0.0.1", + "description": "Implementation of verkle tries as used in Ethereum.", + "keywords": [ + "verkle", + "trie", + "ethereum" + ], + "homepage": "https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/verkle#readme", + "bugs": { + "url": "https://github.com/ethereumjs/ethereumjs-monorepo/issues?q=is%3Aissue+label%3A%22package%3A+verkle%22" + }, + "repository": { + "type": "git", + "url": "https://github.com/ethereumjs/ethereumjs-monorepo.git" + }, + "license": "MPL-2.0", + "author": "EthereumJS Team", + "contributors": [ + { + "name": "Gabriel Rocheleau", + "url": "https://github.com/gabrocheleau" + } + ], + "main": "dist/cjs/index.js", + "type": "commonjs", + "module": "dist/esm/index.js", + "exports": { + ".": { + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js" + } + }, + "files": [ + "dist", + "src" + ], + "scripts": { + "build": "../../config/cli/ts-build.sh", + "clean": "../../config/cli/clean-package.sh", + "coverage": "npx vitest run --coverage.enabled --coverage.reporter=lcov", + "docs:build": "typedoc --options typedoc.cjs", + "examples": "ts-node ../../scripts/examples-runner.ts -- verkle", + "lint": "../../config/cli/lint.sh", + "lint:diff": "../../config/cli/lint-diff.sh", + "lint:fix": "../../config/cli/lint-fix.sh", + "prepublishOnly": "../../config/cli/prepublish.sh", + "test": "npx vitest run", + "tsc": "../../config/cli/ts-compile.sh" + }, + "dependencies": { + "@ethereumjs/rlp": "5.0.0", + "@ethereumjs/util": "9.0.0", + "lru-cache": "^10.0.0" + }, + "engines": { + "node": ">=18" + } +} diff --git a/packages/verkle/src/db/checkpoint.ts b/packages/verkle/src/db/checkpoint.ts new file mode 100644 index 0000000000..6755796cc9 --- /dev/null +++ b/packages/verkle/src/db/checkpoint.ts @@ -0,0 +1,270 @@ +import { KeyEncoding, ValueEncoding, bytesToHex, hexToBytes } from '@ethereumjs/util' +import { LRUCache } from 'lru-cache' + +import type { Checkpoint, CheckpointDBOpts } from '../types.js' +import type { BatchDBOp, DB, DelBatch, PutBatch } from '@ethereumjs/util' + +/** + * DB is a thin wrapper around the underlying levelup db, + * which validates inputs and sets encoding type. + */ +export class CheckpointDB implements DB { + public checkpoints: Checkpoint[] + public db: DB + public readonly cacheSize: number + + // Starting with lru-cache v8 undefined and null are not allowed any more + // as cache values. At the same time our design works well, since undefined + // indicates for us that we know that the value is not present in the + // underlying trie database as well (so it carries real value). + // + // Solution here seems therefore adequate, other solutions would rather + // be some not so clean workaround. + // + // (note that @ts-ignore doesn't work since stripped on declaration (.d.ts) files) + protected _cache?: LRUCache + + _stats = { + cache: { + reads: 0, + hits: 0, + writes: 0, + }, + db: { + reads: 0, + hits: 0, + writes: 0, + }, + } + + /** + * Initialize a DB instance. + */ + constructor(opts: CheckpointDBOpts) { + this.db = opts.db + this.cacheSize = opts.cacheSize ?? 0 + // Roots of trie at the moment of checkpoint + this.checkpoints = [] + + if (this.cacheSize > 0) { + this._cache = new LRUCache({ + max: this.cacheSize, + updateAgeOnGet: true, + }) + } + } + + /** + * Flush the checkpoints and use the given checkpoints instead. + * @param {Checkpoint[]} checkpoints + */ + setCheckpoints(checkpoints: Checkpoint[]) { + this.checkpoints = [] + + for (let i = 0; i < checkpoints.length; i++) { + this.checkpoints.push({ + root: checkpoints[i].root, + keyValueMap: new Map(checkpoints[i].keyValueMap), + }) + } + } + + /** + * Is the DB during a checkpoint phase? + */ + hasCheckpoints() { + return this.checkpoints.length > 0 + } + + /** + * Adds a new checkpoint to the stack + * @param root + */ + checkpoint(root: Uint8Array) { + this.checkpoints.push({ keyValueMap: new Map(), root }) + } + + /** + * Commits the latest checkpoint + */ + async commit() { + const { keyValueMap } = this.checkpoints.pop()! + if (!this.hasCheckpoints()) { + // This was the final checkpoint, we should now commit and flush everything to disk + const batchOp: BatchDBOp[] = [] + for (const [key, value] of keyValueMap.entries()) { + if (value === undefined) { + batchOp.push({ + type: 'del', + key: hexToBytes(key), + opts: { + keyEncoding: KeyEncoding.Bytes, + }, + }) + } else { + batchOp.push({ + type: 'put', + key: hexToBytes(key), + value, + opts: { keyEncoding: KeyEncoding.Bytes, valueEncoding: ValueEncoding.Bytes }, + }) + } + } + await this.batch(batchOp) + } else { + // dump everything into the current (higher level) diff cache + const currentKeyValueMap = this.checkpoints[this.checkpoints.length - 1].keyValueMap + for (const [key, value] of keyValueMap.entries()) { + currentKeyValueMap.set(key, value) + } + } + } + + /** + * Reverts the latest checkpoint + */ + async revert() { + const { root } = this.checkpoints.pop()! + return root + } + + /** + * @inheritDoc + */ + async get(key: Uint8Array): Promise { + const keyHex = bytesToHex(key) + if (this._cache !== undefined) { + const value = this._cache.get(keyHex) + this._stats.cache.reads += 1 + if (value !== undefined) { + this._stats.cache.hits += 1 + return value + } + } + + // Lookup the value in our diff cache. We return the latest checkpointed value (which should be the value on disk) + for (let index = this.checkpoints.length - 1; index >= 0; index--) { + if (this.checkpoints[index].keyValueMap.has(keyHex)) { + return this.checkpoints[index].keyValueMap.get(keyHex) + } + } + // Nothing has been found in diff cache, look up from disk + const value = await this.db.get(key, { + keyEncoding: KeyEncoding.Bytes, + valueEncoding: ValueEncoding.Bytes, + }) + this._stats.db.reads += 1 + if (value !== undefined) { + this._stats.db.hits += 1 + } + this._cache?.set(keyHex, value) + if (this.hasCheckpoints()) { + // Since we are a checkpoint, put this value in diff cache, + // so future `get` calls will not look the key up again from disk. + this.checkpoints[this.checkpoints.length - 1].keyValueMap.set(keyHex, value) + } + + return value + } + + /** + * @inheritDoc + */ + async put(key: Uint8Array, value: Uint8Array): Promise { + const keyHex = bytesToHex(key) + if (this.hasCheckpoints()) { + // put value in diff cache + this.checkpoints[this.checkpoints.length - 1].keyValueMap.set(keyHex, value) + } else { + await this.db.put(key, value, { + keyEncoding: KeyEncoding.Bytes, + valueEncoding: ValueEncoding.Bytes, + }) + this._stats.db.writes += 1 + + if (this._cache !== undefined) { + this._cache.set(keyHex, value) + this._stats.cache.writes += 1 + } + } + } + + /** + * @inheritDoc + */ + async del(key: Uint8Array): Promise { + const keyHex = bytesToHex(key) + if (this.hasCheckpoints()) { + // delete the value in the current diff cache + this.checkpoints[this.checkpoints.length - 1].keyValueMap.set(keyHex, undefined) + } else { + // delete the value on disk + await this.db.del(key, { + keyEncoding: KeyEncoding.Bytes, + }) + this._stats.db.writes += 1 + + if (this._cache !== undefined) { + this._cache.set(keyHex, undefined) + this._stats.cache.writes += 1 + } + } + } + + /** + * @inheritDoc + */ + async batch(opStack: BatchDBOp[]): Promise { + if (this.hasCheckpoints()) { + for (const op of opStack) { + if (op.type === 'put') { + await this.put(op.key, op.value) + } else if (op.type === 'del') { + await this.del(op.key) + } + } + } else { + const convertedOps = opStack.map((op) => { + const convertedOp = { + key: op.key, + value: op.type === 'put' ? op.value : undefined, + type: op.type, + opts: op.opts, + } + if (op.type === 'put') return convertedOp as PutBatch + else return convertedOp as DelBatch + }) + await this.db.batch(convertedOps) + } + } + + stats(reset = true) { + const stats = { ...this._stats, size: this._cache?.size ?? 0 } + if (reset) { + this._stats = { + cache: { + reads: 0, + hits: 0, + writes: 0, + }, + db: { + reads: 0, + hits: 0, + writes: 0, + }, + } + } + return stats + } + + /** + * @inheritDoc + */ + shallowCopy(): CheckpointDB { + return new CheckpointDB({ db: this.db, cacheSize: this.cacheSize }) + } + + open() { + return Promise.resolve() + } +} diff --git a/packages/verkle/src/db/index.ts b/packages/verkle/src/db/index.ts new file mode 100644 index 0000000000..63e8f6b033 --- /dev/null +++ b/packages/verkle/src/db/index.ts @@ -0,0 +1 @@ +export * from './checkpoint.js' diff --git a/packages/verkle/src/index.ts b/packages/verkle/src/index.ts new file mode 100644 index 0000000000..1965d0f02f --- /dev/null +++ b/packages/verkle/src/index.ts @@ -0,0 +1,2 @@ +export * from './types.js' +export * from './verkleTrie.js' diff --git a/packages/verkle/src/node/baseVerkleNode.ts b/packages/verkle/src/node/baseVerkleNode.ts new file mode 100644 index 0000000000..01aee3ebfe --- /dev/null +++ b/packages/verkle/src/node/baseVerkleNode.ts @@ -0,0 +1,33 @@ +import { RLP } from '@ethereumjs/rlp' + +import { type VerkleNodeInterface, type VerkleNodeOptions, type VerkleNodeType } from './types.js' + +import type { Point } from '../types.js' + +export abstract class BaseVerkleNode implements VerkleNodeInterface { + public commitment: Point + public depth: number + + constructor(options: VerkleNodeOptions[T]) { + this.commitment = options.commitment + this.depth = options.depth + } + + abstract commit(): Point + + // Hash returns the field representation of the commitment. + hash(): Uint8Array { + throw new Error('Not implemented') + } + + abstract insert(key: Uint8Array, value: Uint8Array, nodeResolverFn: () => void): void + + abstract raw(): Uint8Array[] + + /** + * @returns the RLP serialized node + */ + serialize(): Uint8Array { + return RLP.encode(this.raw()) + } +} diff --git a/packages/verkle/src/node/index.ts b/packages/verkle/src/node/index.ts new file mode 100644 index 0000000000..c786638cde --- /dev/null +++ b/packages/verkle/src/node/index.ts @@ -0,0 +1,4 @@ +export * from './baseVerkleNode.js' +export * from './internalNode.js' +export * from './leafNode.js' +export * from './types.js' diff --git a/packages/verkle/src/node/internalNode.ts b/packages/verkle/src/node/internalNode.ts new file mode 100644 index 0000000000..9371744124 --- /dev/null +++ b/packages/verkle/src/node/internalNode.ts @@ -0,0 +1,120 @@ +import { equalsBytes } from '@ethereumjs/util' + +import { POINT_IDENTITY } from '../util/crypto.js' + +import { BaseVerkleNode } from './baseVerkleNode.js' +import { LeafNode } from './leafNode.js' +import { NODE_WIDTH, VerkleNodeType } from './types.js' + +import type { Point } from '../types.js' +import type { VerkleNode, VerkleNodeOptions } from './types.js' + +export class InternalNode extends BaseVerkleNode { + // Array of references to children nodes + public children: Array + public copyOnWrite: Record + public type = VerkleNodeType.Internal + + /* TODO: options.children is not actually used here */ + constructor(options: VerkleNodeOptions[VerkleNodeType.Internal]) { + super(options) + this.children = options.children ?? new Array(NODE_WIDTH).fill(null) + this.copyOnWrite = options.copyOnWrite ?? {} + } + + commit(): Point { + throw new Error('Not implemented') + } + + cowChild(_: number): void { + // Not implemented yet + } + + setChild(index: number, child: VerkleNode) { + this.children[index] = child + } + + static fromRawNode(rawNode: Uint8Array[], depth: number): InternalNode { + const nodeType = rawNode[0][0] + if (nodeType !== VerkleNodeType.Internal) { + throw new Error('Invalid node type') + } + + // The length of the rawNode should be the # of children, + 2 for the node type and the commitment + if (rawNode.length !== NODE_WIDTH + 2) { + throw new Error('Invalid node length') + } + + // TODO: Generate Point from rawNode value + const commitment = rawNode[rawNode.length - 1] as unknown as Point + + return new InternalNode({ commitment, depth }) + } + + static create(depth: number): InternalNode { + const node = new InternalNode({ + commitment: POINT_IDENTITY, + depth, + }) + + return node + } + + getChildren(index: number): VerkleNode | null { + return this.children?.[index] ?? null + } + + insert(key: Uint8Array, value: Uint8Array, resolver: () => void): void { + const values = new Array(NODE_WIDTH) + values[key[31]] = value + this.insertStem(key.slice(0, 31), values, resolver) + } + + insertStem(stem: Uint8Array, values: Uint8Array[], resolver: () => void): void { + // Index of the child pointed by the next byte in the key + const childIndex = stem[this.depth] + + const child = this.children[childIndex] + + if (child instanceof LeafNode) { + this.cowChild(childIndex) + if (equalsBytes(child.stem, stem)) { + return child.insertMultiple(stem, values) + } + + // A new branch node has to be inserted. Depending + // on the next byte in both keys, a recursion into + // the moved leaf node can occur. + const nextByteInExistingKey = child.stem[this.depth + 1] + const newBranch = InternalNode.create(this.depth + 1) + newBranch.cowChild(nextByteInExistingKey) + this.children[childIndex] = newBranch + newBranch.children[nextByteInExistingKey] = child + child.depth += 1 + + const nextByteInInsertedKey = stem[this.depth + 1] + if (nextByteInInsertedKey === nextByteInExistingKey) { + return newBranch.insertStem(stem, values, resolver) + } + + // Next word differs, so this was the last level. + // Insert it directly into its final slot. + const leafNode = LeafNode.create(stem, values) + + leafNode.setDepth(this.depth + 2) + newBranch.cowChild(nextByteInInsertedKey) + newBranch.children[nextByteInInsertedKey] = leafNode + } else if (child instanceof InternalNode) { + this.cowChild(childIndex) + return child.insertStem(stem, values, resolver) + } else { + throw new Error('Invalid node type') + } + } + + // TODO: go-verkle also adds the bitlist to the raw format. + raw(): Uint8Array[] { + throw new Error('not implemented yet') + // return [new Uint8Array([VerkleNodeType.Internal]), ...this.children, this.commitment] + } +} diff --git a/packages/verkle/src/node/leafNode.ts b/packages/verkle/src/node/leafNode.ts new file mode 100644 index 0000000000..dd11308d82 --- /dev/null +++ b/packages/verkle/src/node/leafNode.ts @@ -0,0 +1,85 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { BaseVerkleNode } from './baseVerkleNode.js' +import { NODE_WIDTH, VerkleNodeType } from './types.js' + +import type { Point } from '../types.js' +import type { VerkleNodeOptions } from './types.js' + +export class LeafNode extends BaseVerkleNode { + public stem: Uint8Array + public values: Uint8Array[] + public c1: Point + public c2: Point + public type = VerkleNodeType.Leaf + + constructor(options: VerkleNodeOptions[VerkleNodeType.Leaf]) { + super(options) + + this.stem = options.stem + this.values = options.values + this.c1 = options.c1 + this.c2 = options.c2 + } + + static create(stem: Uint8Array, values: Uint8Array[]): LeafNode { + throw new Error('Not implemented') + } + + static fromRawNode(rawNode: Uint8Array[], depth: number): LeafNode { + const nodeType = rawNode[0][0] + if (nodeType !== VerkleNodeType.Leaf) { + throw new Error('Invalid node type') + } + + // The length of the rawNode should be the # of values (node width) + 5 for the node type, the stem, the commitment and the 2 commitments + if (rawNode.length !== NODE_WIDTH + 5) { + throw new Error('Invalid node length') + } + + const stem = rawNode[1] + // TODO: Convert the rawNode commitments to points + const commitment = rawNode[2] as unknown as Point + const c1 = rawNode[3] as unknown as Point + const c2 = rawNode[4] as unknown as Point + const values = rawNode.slice(5, rawNode.length) + + return new LeafNode({ depth, stem, values, c1, c2, commitment }) + } + commit(): Point { + throw new Error('Not implemented') + } + + getValue(index: number): Uint8Array | null { + return this.values?.[index] ?? null + } + + insert(key: Uint8Array, value: Uint8Array, nodeResolverFn: () => void): void { + const values = new Array(NODE_WIDTH) + values[key[31]] = value + this.insertStem(key.slice(0, 31), values, nodeResolverFn) + } + + insertMultiple(key: Uint8Array, values: Uint8Array[]): void { + throw new Error('Not implemented') + } + + insertStem(key: Uint8Array, value: Uint8Array[], resolver: () => void): void { + throw new Error('Not implemented') + } + + // TODO: go-verkle also adds the bitlist to the raw format. + raw(): Uint8Array[] { + return [ + new Uint8Array([VerkleNodeType.Leaf]), + this.stem, + this.commitment.bytes(), + this.c1.bytes(), + this.c2.bytes(), + ...this.values, + ] + } + + setDepth(depth: number): void { + this.depth = depth + } +} diff --git a/packages/verkle/src/node/types.ts b/packages/verkle/src/node/types.ts new file mode 100644 index 0000000000..db975a5e5b --- /dev/null +++ b/packages/verkle/src/node/types.ts @@ -0,0 +1,49 @@ +import type { Point } from '../types.js' +import type { InternalNode } from './internalNode.js' +import type { LeafNode } from './leafNode.js' + +export enum VerkleNodeType { + Internal, + Leaf, +} + +export interface TypedVerkleNode { + [VerkleNodeType.Internal]: InternalNode + [VerkleNodeType.Leaf]: LeafNode +} + +export type VerkleNode = TypedVerkleNode[VerkleNodeType] + +export interface VerkleNodeInterface { + commit(): Point + hash(): any + serialize(): Uint8Array +} + +interface BaseVerkleNodeOptions { + // Value of the commitment + commitment: Point + depth: number +} + +interface VerkleInternalNodeOptions extends BaseVerkleNodeOptions { + // Children nodes of this internal node. + children?: VerkleNode[] + + // Values of the child commitments before the trie is modified by inserts. + // This is useful because the delta of the child commitments can be used to efficiently update the node's commitment + copyOnWrite?: Record +} +interface VerkleLeafNodeOptions extends BaseVerkleNodeOptions { + stem: Uint8Array + values: Uint8Array[] + c1: Point + c2: Point +} + +export interface VerkleNodeOptions { + [VerkleNodeType.Internal]: VerkleInternalNodeOptions + [VerkleNodeType.Leaf]: VerkleLeafNodeOptions +} + +export const NODE_WIDTH = 256 diff --git a/packages/verkle/src/node/util.ts b/packages/verkle/src/node/util.ts new file mode 100644 index 0000000000..16726076dc --- /dev/null +++ b/packages/verkle/src/node/util.ts @@ -0,0 +1,30 @@ +import { RLP } from '@ethereumjs/rlp' + +import { InternalNode } from './internalNode.js' +import { LeafNode } from './leafNode.js' +import { type VerkleNode, VerkleNodeType } from './types.js' + +export function decodeRawNode(raw: Uint8Array[]): VerkleNode { + const nodeType = raw[0][0] + const depth = 0 + switch (nodeType) { + case VerkleNodeType.Internal: + return InternalNode.fromRawNode(raw, depth) + case VerkleNodeType.Leaf: + return LeafNode.fromRawNode(raw, depth) + default: + throw new Error('Invalid node type') + } +} + +export function decodeNode(raw: Uint8Array) { + const decoded = RLP.decode(Uint8Array.from(raw)) as Uint8Array[] + if (!Array.isArray(decoded)) { + throw new Error('Invalid node') + } + return decodeRawNode(decoded) +} + +export function isRawNode(node: Uint8Array | Uint8Array[]): node is Uint8Array[] { + return Array.isArray(node) && !(node instanceof Uint8Array) +} diff --git a/packages/verkle/src/rust-verkle-wasm/LICENSE_APACHE b/packages/verkle/src/rust-verkle-wasm/LICENSE_APACHE new file mode 100644 index 0000000000..1b5ec8b78e --- /dev/null +++ b/packages/verkle/src/rust-verkle-wasm/LICENSE_APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/packages/verkle/src/rust-verkle-wasm/LICENSE_MIT b/packages/verkle/src/rust-verkle-wasm/LICENSE_MIT new file mode 100644 index 0000000000..f436a5ae91 --- /dev/null +++ b/packages/verkle/src/rust-verkle-wasm/LICENSE_MIT @@ -0,0 +1,25 @@ +Copyright (c) 2018 Kevaundray Wedderburn + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/packages/verkle/src/rust-verkle-wasm/rust_verkle_wasm.d.ts b/packages/verkle/src/rust-verkle-wasm/rust_verkle_wasm.d.ts new file mode 100644 index 0000000000..956249d273 --- /dev/null +++ b/packages/verkle/src/rust-verkle-wasm/rust_verkle_wasm.d.ts @@ -0,0 +1,18 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * @param {Uint8Array} address_tree_index + * @returns {any} + */ +export function pedersen_hash(address_tree_index: Uint8Array): any +/** + * @param {Uint8Array} js_root + * @param {Uint8Array} js_proof + * @param {Map} js_key_values + * @returns {any} + */ +export function verify_update( + js_root: Uint8Array, + js_proof: Uint8Array, + js_key_values: Map +): any diff --git a/packages/verkle/src/rust-verkle-wasm/rust_verkle_wasm.js b/packages/verkle/src/rust-verkle-wasm/rust_verkle_wasm.js new file mode 100644 index 0000000000..75d2edf53f --- /dev/null +++ b/packages/verkle/src/rust-verkle-wasm/rust_verkle_wasm.js @@ -0,0 +1,340 @@ +let imports = {} +imports['__wbindgen_placeholder__'] = module.exports +let wasm +const { TextEncoder, TextDecoder } = require(`util`) + +const heap = new Array(32).fill(undefined) + +heap.push(undefined, null, true, false) + +function getObject(idx) { + return heap[idx] +} + +let heap_next = heap.length + +function dropObject(idx) { + if (idx < 36) return + heap[idx] = heap_next + heap_next = idx +} + +function takeObject(idx) { + const ret = getObject(idx) + dropObject(idx) + return ret +} + +function debugString(val) { + // primitive types + const type = typeof val + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}` + } + if (type == 'string') { + return `"${val}"` + } + if (type == 'symbol') { + const description = val.description + if (description == null) { + return 'Symbol' + } else { + return `Symbol(${description})` + } + } + if (type == 'function') { + const name = val.name + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})` + } else { + return 'Function' + } + } + // objects + if (Array.isArray(val)) { + const length = val.length + let debug = '[' + if (length > 0) { + debug += debugString(val[0]) + } + for (let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]) + } + debug += ']' + return debug + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)) + let className + if (builtInMatches.length > 1) { + className = builtInMatches[1] + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val) + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')' + } catch (_) { + return 'Object' + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}` + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className +} + +let WASM_VECTOR_LEN = 0 + +let cachedUint8Memory0 = new Uint8Array() + +function getUint8Memory0() { + if (cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer) + } + return cachedUint8Memory0 +} + +let cachedTextEncoder = new TextEncoder('utf-8') + +const encodeString = + typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view) + } + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg) + view.set(buf) + return { + read: arg.length, + written: buf.length, + } + } + +function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg) + const ptr = malloc(buf.length) + getUint8Memory0() + .subarray(ptr, ptr + buf.length) + .set(buf) + WASM_VECTOR_LEN = buf.length + return ptr + } + + let len = arg.length + let ptr = malloc(len) + + const mem = getUint8Memory0() + + let offset = 0 + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset) + if (code > 0x7f) break + mem[ptr + offset] = code + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset) + } + ptr = realloc(ptr, len, (len = offset + arg.length * 3)) + const view = getUint8Memory0().subarray(ptr + offset, ptr + len) + const ret = encodeString(arg, view) + + offset += ret.written + } + + WASM_VECTOR_LEN = offset + return ptr +} + +let cachedInt32Memory0 = new Int32Array() + +function getInt32Memory0() { + if (cachedInt32Memory0.byteLength === 0) { + cachedInt32Memory0 = new Int32Array(wasm.memory.buffer) + } + return cachedInt32Memory0 +} + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) + +cachedTextDecoder.decode() + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)) +} + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1) + const idx = heap_next + heap_next = heap[idx] + + heap[idx] = obj + return idx +} +/** + * @param {Uint8Array} address_tree_index + * @returns {any} + */ +module.exports.pedersen_hash = function (address_tree_index) { + const ret = wasm.pedersen_hash(addHeapObject(address_tree_index)) + return takeObject(ret) +} + +let stack_pointer = 32 + +function addBorrowedObject(obj) { + if (stack_pointer == 1) throw new Error('out of js stack') + heap[--stack_pointer] = obj + return stack_pointer +} +/** + * @param {Uint8Array} js_root + * @param {Uint8Array} js_proof + * @param {Map} js_key_values + * @returns {any} + */ +module.exports.verify_update = function (js_root, js_proof, js_key_values) { + try { + const ret = wasm.verify_update( + addHeapObject(js_root), + addHeapObject(js_proof), + addBorrowedObject(js_key_values) + ) + return takeObject(ret) + } finally { + heap[stack_pointer++] = undefined + } +} + +function handleError(f, args) { + try { + return f.apply(this, args) + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)) + } +} + +module.exports.__wbindgen_object_drop_ref = function (arg0) { + takeObject(arg0) +} + +module.exports.__wbg_log_035529d7f1f4615f = function (arg0, arg1) { + console.log(getStringFromWasm0(arg0, arg1)) +} + +module.exports.__wbindgen_is_null = function (arg0) { + const ret = getObject(arg0) === null + return ret +} + +module.exports.__wbg_new_abda76e883ba8a5f = function () { + const ret = new Error() + return addHeapObject(ret) +} + +module.exports.__wbg_stack_658279fe44541cf6 = function (arg0, arg1) { + const ret = getObject(arg1).stack + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len0 = WASM_VECTOR_LEN + getInt32Memory0()[arg0 / 4 + 1] = len0 + getInt32Memory0()[arg0 / 4 + 0] = ptr0 +} + +module.exports.__wbg_error_f851667af71bcfc6 = function (arg0, arg1) { + try { + console.error(getStringFromWasm0(arg0, arg1)) + } finally { + wasm.__wbindgen_free(arg0, arg1) + } +} + +module.exports.__wbg_get_57245cc7d7c7619d = function (arg0, arg1) { + const ret = getObject(arg0)[arg1 >>> 0] + return addHeapObject(ret) +} + +module.exports.__wbg_next_aaef7c8aa5e212ac = function () { + return handleError(function (arg0) { + const ret = getObject(arg0).next() + return addHeapObject(ret) + }, arguments) +} + +module.exports.__wbg_done_1b73b0672e15f234 = function (arg0) { + const ret = getObject(arg0).done + return ret +} + +module.exports.__wbg_value_1ccc36bc03462d71 = function (arg0) { + const ret = getObject(arg0).value + return addHeapObject(ret) +} + +module.exports.__wbg_from_7ce3cb27cb258569 = function (arg0) { + const ret = Array.from(getObject(arg0)) + return addHeapObject(ret) +} + +module.exports.__wbg_entries_ff7071308de9aaec = function (arg0) { + const ret = getObject(arg0).entries() + return addHeapObject(ret) +} + +module.exports.__wbg_buffer_3f3d764d4747d564 = function (arg0) { + const ret = getObject(arg0).buffer + return addHeapObject(ret) +} + +module.exports.__wbg_newwithbyteoffsetandlength_d9aa266703cb98be = function (arg0, arg1, arg2) { + const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0) + return addHeapObject(ret) +} + +module.exports.__wbg_new_8c3f0052272a457a = function (arg0) { + const ret = new Uint8Array(getObject(arg0)) + return addHeapObject(ret) +} + +module.exports.__wbg_set_83db9690f9353e79 = function (arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0) +} + +module.exports.__wbg_length_9e1ae1900cb0fbd5 = function (arg0) { + const ret = getObject(arg0).length + return ret +} + +module.exports.__wbindgen_debug_string = function (arg0, arg1) { + const ret = debugString(getObject(arg1)) + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc) + const len0 = WASM_VECTOR_LEN + getInt32Memory0()[arg0 / 4 + 1] = len0 + getInt32Memory0()[arg0 / 4 + 0] = ptr0 +} + +module.exports.__wbindgen_throw = function (arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)) +} + +module.exports.__wbindgen_memory = function () { + const ret = wasm.memory + return addHeapObject(ret) +} + +const path = require('path').join(__dirname, 'rust_verkle_wasm_bg.wasm') +const bytes = require('fs').readFileSync(path) + +const wasmModule = new WebAssembly.Module(bytes) +const wasmInstance = new WebAssembly.Instance(wasmModule, imports) +wasm = wasmInstance.exports +module.exports.__wasm = wasm diff --git a/packages/verkle/src/rust-verkle-wasm/rust_verkle_wasm_bg.wasm b/packages/verkle/src/rust-verkle-wasm/rust_verkle_wasm_bg.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a5d4fd983019df2b884ff7f56863c100ec63f2ca GIT binary patch literal 342524 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/OZ0Uyt~0fQ1JkfEkC(z)pfpK+_-{ zU&tuYhZ}@h9-KYJxTmjI5{NX#J#%!|tzZ?7sCQkYNe}rdxRx@Cph|RuAW2!BVd;eT zARgEn)ec8`j%%~TlD}k$2LqETKpERd{x(;{UV6UBSD0*SgDt-y;3>uok~-*RUg(|n z69m>7^)#zkMi&JtRh{uAIE*A^ABRmHiBwl}95UhwnYBVKxM;va?0x~l(#aQ=`O<3= zU!}LPGH#p)a`+S<@K5%m>-twu;>XvLTl}wei$nBF{OB-K-XmNRZmaxcRDnZ78h;X; ztB~0mg6lEyn}e4nUUN=Ybehlf6T@l40mi84YO2K6w-TFNkmx$Dqi_LZv!_&rggFNc zdmxrGN|{JA*P`~ipw}{T9l4dWv-;imR2;sAj{r|aPOx)3Oo(6*DV;^H6RsgU&`f7V z4kZ(q=uhjMjA97Ahe+n*5${10_-MH0NnnIM(I~yJP6Th*Mx4VE8vC+{7Lbe1pipk1 zo!l6ctNIA86T3rWQZtQNcr{W(u?LbB5QLtqi7?Ij2)5|OeA zC?P%-JLIxy?C3+;;mc!(`h2qz9b1di&+PLhzaEwHPGD-b_SENVYYZemBcCr|#uSdy zU6D~C5oK_anglQ-7}n?0eOo!cHXVuyW(KuG+Q)B!I>MT9ybb-I(S`RjW&lnXO(NnS zR}%+P#Nsgc2`*8u{+6Jw`co`0zo89P?S|hNj4ta_QZD$s9_Q+gE)(M-sdl~y2EVo3 zMfnj3!;Cx#e|!B>wPid_dl7k%&Z0Hk9fmH7Dm9TK4>7a>R74=A@mr1+q9|wi*8e}xW1hb+ z>nAyb2h^neQ(4n)#$-wMDS8hyHtdhesJ_eNy?iJ};9Z<Y@mw$mZE+yHifQ zG8LuZE2$Jk6if1MM|YC<+mfZFUbB3WVf`)c;b}v}AZrydwW7O>dKcYa#QUEcB%IMb zhk4-?x6N(o7;`N)g$)7fr3hR^YXz-Q@h#znAu83Mvgz!9J(6W zrzxl+z!e6mldmxtUel5_A_`4S*SLcuri0>d(6cagAJ3FyfX5-yLoPH&2EOF@{o0L2 zZzx-LAvbx3Uj#WW6X5Cn+KnjOJaQqyE1sz?OiDmHP>$~XP=+wJQmv-Sy9$V_={qCGA7+&&g!~}q*J4Y1hkf5+V zL9X!{*@pOyO@+ZH>X^@+M9ljE~et5a<+t+6K`gbh%pi`ago%jPW|iK*(w+D zAk9)@HnvYoMYN2x`89ygFO>BLI0SPe-h3h6gnGmCH_@9L=T5k2IBDh+dmK>KIA?MpN@P!zXCx`1VQYw{z%$#Afsf!&yxBz7&dPjCwNW5NWl#>)%KOI^xG zUeR`mTO1Vm-1ujRMq*SU8eITStcLqR4G|^+0YDog$5$dS#aClkRVf)ig`ekyq!3|v zW(Y|!Q**ScyDT^rqQ~G=5iBtPheTv`>LJy`-7;t`e4b8txD3&%6iC`l5^7%CB*nu0 zwgRwRH3CXO#s-^$t^xq)Env-&uegEVs$C}~O{&*d_hU#Ym%>i?;v1B(^{zJ|S6#mM zrJ`A}iWbqXQDMRApuF*-;Y5rvIXKs$*iy-WVRa>rX35K0fuft08K4fu&{pq;`dv1` zRLc`DYLg*K|AVmLDh5T>%~SZ%r-$kV(W{NT1kHe5209IXNCx{y7CfcSUhI2k!^x+T zSvr-LO*NmR9zpXt7h8omM_5I@+@^#Uq)ibmW}?DMlv9h$^7=%JVfo1wAqC=Tu(}pc zj4Aq9V+bBfHqmw--0CA!OvrEw*dkP`nPcm1buQc%%GdsQDwift476X+fy`vuK`;3v z>_+cHZY)|d>Zgq)W%z<6w$ckc{Du6mJ)`nfnJQm_{cuW#UqWp&S)6bgfp0+W)q7%n z()I14*#(yuoZ2ZFrr7^a$b%y;G2xiwbWjrxjA+nh6He2{h}nX_VUJZmkX7*&{-O`_ z7nL=cXB@K}jK6Lv76uzWns?GuclrYtV~9@{+g>d;CM0OcC~sQc8|`qb`g%E2NPK*9 zWU;w5#tQ8LWnwqcnmL<1SL7y~d?-W^UnKqW;n=V5HPVw)jM%J1pZGixA<{`^I-pIX z88Ql2>6DHMS~xAGlXRA)lZ4NpNz>{uql0qzlb&ZLG0M%JZFsBPB`$5*#-mF!z7EsW z7!W;c4GdkkVXXm#Tm0hG%_ZFQopA>S@-@7;=;XqcUV-I;S)6VuL8UYOqoPGdRNKg+ zup-YhNg*p{)*QC;Hy5L%BTF;ePk*EHX%7s;S8;Kr&SzxwoesyR^Ea>7d0Z(g zb^fMRJO7;mu?qEsa~NKQ;U+)r*H3yG^z+j%WBYhG_IZ?LkWoOln_q?(braVo>Jzx` z4)wwBxr?qVyBtpX@?^X`IeOV>D=+us<$iiOjN|w?bnMF*7QS4>%h>Yqf4@h!H72QI=W@zx<GU6b@=2tG(I~p?VzLYx*SvbQqsbnpTgH%m zvBz2TfHCQrUX~(8iB6z91S%8W2jS|Yr!l+IifpqexS<%5gJTz>epmPNKI5m3sGL;0 zC}&wr9qWNqWwFLkCyn*N;`^>SRuL}nJ#?LOs+wuP++?@$a%^cJ^7&4iHiBvvaulco z;OehoLSBPSHL8YeIy=E$FWhj zD5_s~Y_4K)+pmUYnoYhrO+JUo0pSnY2s9N!pV|?1uev|zsE0KLIXMRSRk-~QHxpSnZeJb7oFsXk-E`cmlUKG>mJr z#bSIzUIRLwGt;d*^<7_E(3X6155v~!V4xHC1tc38y!CTJ3oY%mew1H0qr8G|#{<-L z1DPD|LPdIrYg@~$)xF;1LOg-nCBHr&!P~_)Oi|Vk#xr-fp$(iIaplg@6}z#YK*fR7 zTLEJE1RvWEQOgc3jF$ykAfO~^s5$55aw9n4p(cuPH+`)?=@p_L^`5VDHopkRoyVc~ z)=}9EQ_jIpgsv}Vx>t`(GO~zm#5})Z7~ulL*oz+^)R9A@*F9Sxrvtq=QdHG7_-=|` zqZL5=DS90bVhYMzy0Z`}7nVe?!7)0$A4Sft624v%tIsQ<_@Sq30Q)fKMd5MtgHhyXd*3Y48N=!HE0sYf*PD%(9bf| z1Q%9;T^DM;vu=MJ6che9d~J>l_Di^+a6}#IkHcrTqCYNP8u!PIuUD6J=GqI%-!`^b zU3Xi1ntXMmb?VHox#lc+;=a*V`)S>A*Y17SEjPaB@4W5@zxVYAuYLQO zbFa?_c;v?3JSlvWba(ENH+}7auYcm+tInI7zcL@mIevpx#X9a{m0xG>##`^b;X{A= z2e0_gbMJ4C(o=(8a?HJYl+21b=@erICtnKQ53+=D!>4@R;6~)p68?wRXU~mbCkGK#N->#Ek_gGXW`-s$Kq;Z?0U7=#Y`~z*>77}|HGY+v z5g{q53l;D{^%iJFiI)?O{I)Y5(f0xzaoNb!IgzCdhIO{>dnOzu>tFkU9)+t1cGn~v zq3qaS8Z+St-$b4>;U$fN{lTnwrb!k874Qi=pGtqySd(x>V_dc|6OPD?jTEdE5{~pe zxptIjmd=KhaFo50WbkGUF)H~5PYu;eIK$_Mc8L356qGJ_haO-#uDGUPrRtf^*H9&S zfYv!j=Biq9Zh)R6z4<3@udjK>tv-V%{~VU-DRxcivSkGQNbIVI#_fDlUs(cxMd3j! zhR1lt?Y6hTe|xyD2nwKVdhF(`Fnk)^q6|x_Y;KCE$$M_)i|U0*gGg#1YENWe-HAP6 z33A6S_Hcqvv-1@E*$ira1U7ud3bBvUKQpj&F+>Iyo+Y!}pb}dcmtiDv)AO8x#qqis zwHa7hg!LN{){hLVeyZOP(p-@0+mgl*s%{cmE2U&b-SSp+z3UYS@yM*Y=MrT4)_SKG z^gmva)gGurh5@?IAkTu1nF1XZ@1-TM@XvIYd|%B2m;r}d2)}gW*asMi>W`UwXgJJm zeon7K4HMMHve8a}GSFHMCyk`+ECYiyyr7%E1OJLZj8y>tjkz1Q%q*eXH zW03qg3}J2MiZB!C%^^?lRydk4iGeT?2<$Tw0~S~fTSy@Kt9#X*Fs^W*IeDr-@hs_} zsJ{*MD!q0yarPA4wRCi$!dty^BJhO=|Ew^DoDOjxA9Au~T?54P70kDJ(I{{l!%NpLi_A%7u#dD>Cb*z{p*e>?#vSMi zCg9YHDxNg5quI9=tm&b2@z~N0S|%s%k5PUXEqwi zgiIc4$F_|0(qN=y9SM2fi}w!wp5nn2$HORh%`mYuHtu5*l<_3 z8;UKt9sER2=JQXgz7^_b!BdBpOZB?R1ZYo?75YM3QJqu_!z1zDM z-S}Do%j8W4gAEt`!=Rb(|1&S+#h=0t-m52Hrr$mkN{V3c%y5d!6h$>u8q&9Q-^Zyr z6g-9xo<+k-dn&b@J3N69z+l6<=P^eAQqTNTXuGISux-^UHDb5IFo2*)C89W(I`7=` zocX{q5wJ`IEFd{z^;Z}DdUel zmao@7!SILa*7F!Ek@44=e(lP-#{FI7#}Bgnz-<~P%A$2a6d>;pmU(}7J#zKnm;m|S zkqD3j-6U7}>RNKujZl!O&l#5<esA8pe_UlOBUPhVUVoT>v-R$jE_xg;$tBz8FwsEGWHjsM7?8#lZEvw83e|iTE|qegbn=d>bGYcoW8mT1&~_`UT~M6F z4kK3EF4-W%Ciw=D2t9-$C~|UsQ=lqYUg>E*kO4bdi1()Q7%pQ(Mfqks5aXsd= zamC}9HXd_i?0y_0<6A{hEZd3{x(ImSKgj7UaZu{VzEYA`uVBzPoj#bmuA3rUn|j&QnMY6payYJMk#g$( zY$NxPvg8b?=~mCg4}GAy6FLbwM`+?t!ib4IKf{rg&y%CSJK)aj4&a!NWH`&&=&6^SG!19@$A!LS$oR7LlSLrV=(LHP;IiCOpmaz-jp(sF){NR zf#@rv_iba);0^~pJtpu?^sE$%A(w5xdLXR9Eh)9byJv2%AN*dMol??uB2ZI;00+C2 zkcer!OF3r@6Xb}kMr>%Sr+TZSi2+Zs&oO`b@Q)?NSQ0SJ3|%L)nxXk6keIfLMnSKpP!)G3zk+7dz4gF^mc0z#?)B43$EoQAT}omT3)jS zF2`hx5<6;kzMRR{JLD#NRc&~Ya zDe_~_Q}(3ccpT^fvQ?AEx3v;l?w~3bBUQ00^)~5}P<@~wGA^<^7tgmxE)=HEfXyQW z*!~8Rc6Cw_{UA3p?UO*NnJI~0^_M(_QA%RIIAoNZ77X|@cEo^8~M%Mfbv7@hfudFxH|8XUzA^7!E2Dcw$fVRt!T|T5V)xq}`TWxH_EReHlgI30Q_{ zi~1bsLYW0q_(>!scR&JQ4fa$?n`UPUCUUMYVyD{A2Q~nMH&R89l+=RzAjrG=N?W}Z zls8NmmPVVuNZ~FycbA7?_MjLh_h8Xf=U>cTCG*>yaFI^t4w&Ki(3MiX*J(+dj~V1r zq0D*0*F#ra0n}cCpW|ex?;Bl;E}v1p#0hj-`GSZLrnD{d)^5*1sp`-8hI5$b%y8o` zY$D%fff59r%lL6eo2X&@EKZWx<*W>QW>e)Y3NqRsbgDN2V)Z>cwiwLr^;z$qg*Rs6 zj;uhq3I4R$0FIZnu|=erG15}ns535A|F<*Z%%%kMbH++A=e1#`{Z#J`XA8lmL{A$X zCyv1*Tp`xwRo^6v>%7!AFw^1VM0+Uwu*?O5t6*#e9Q?l%t14(J7bsj_qIs1M*1z|$ zg--oc-rMiNEg#3nG*5ocn=(QSZXO9ncwL?&ma6|(l$_O|M4D;KQX6h!K0H=9xifLN zdU?&|)sXVX_h+OW@Zg@m%}6Ul8_?AiMWyZlK-0lfhm9^a`u-5zC zm9*Y52gy41Ug_OAjnpZt%R*p0ZnfP?1gGab0lo2o>!q7>hDF&%V4xJTN~WDf*}y`_5yn zz(=2YHupdL7aaG`ekp51JN3PC;0e#=`q%E|`jhhI zbIz?7KYIchxv>RS{e}I6SWd6s`)B;zUetg0X?}0(R<~MNv%|Zee8c~?FBGW-*E||-OQ!>3Xa&1>z3w%HuYOOq&T z)Tzn3h>Fa+=SrsKI;?DcqoqOu$@ywb zPQs*-z?s{^oQRkxa72;)}Grv~-#I;V=y&EEyJQ@JhxqlR{GAZ8UkT zG-J|7oAdZ+OU7D?gRq(P7cd_J{%kXs$)u9zvXp(K-D`Lt-*#f%5&lhqd<*DY}{0i=s$7M zkaj+OQF@0#%!T{1E#|ZO!ex5bHKH!S?C%bt8$r}1x>YGTN ztbeY4fS=$)KQONWFUvgr*Y0Y~q|sP{0VgUy8`%bgM(J$z`3VE^XMvy`T4un=qKnE?vzo7_^l z$6-2r+vb%zyzfa(hZi=Fb@;)}^?u6jt*67cZC*!*Ntau-!}~WsIvu_|WkD|+;rZ#N z!@Eq>RU$jW3mfa*q`s`D!Tw>GV#!wVsSSMKnFg^rG@!+*Qx9G+OIzn!MLu@zb#-rejp!(gvxcwM4}=DX(n zC69hpa$f8oZD;>(&CXt4tl3+t8IGv=N_M$lC%?L!&0i1OU(z~P?eBd@&9Bz(@6BuX z_ongw9-u}UjC?)Ad-HnwOK#(;{oPj_?f9-OkZwK6-<26$ZrO2l#k#t9Nzl@4+zFL(c2rRwVGR+S%`w zj}A4zrMD8d8mU)Tj&tYz6oDa>xt{*MrMC`tMHbMi9lok}v>iTPqr02<=y4p~#WhBE z@u)|a1QaWIUBPAyI7gQ_$J_|Rp5PG2W-xu4JC{&GRhe6 z#?-kkjU>vBi4cd`t7EVbe^42&HdeAin4#sX*Q4b#BUJB`Q9hFpfA^#hZ7pZ{VkrN( zr8M??)m^$R<|(*W#7;|G97Y#3XRw~C4^5)f_^eA%#Sj+K0Tm8RL!2O5hE3<=3m7%H z_GU94B!R#Q#Zl}g^sC1b5dOWZQN@+o2ULI+RK_p&$hPLtJ!P=EvFNbWQ;9BnhW)xL zR;l(<-)tktLv?0)+65$i$iJ{+3r(em*cX)1t8R#zb$rj^*m^fmP8Buqt-;N}h-Yj3C zzk~Wu$&;dciV0paKFk)}2J=(;4Y0&9ij6*N?*`R~6iG9l&5v=sXG#*jCwDg&1cuOm z;Fb=-PMoKfPSe54u?Z3S;JInHX%P@?oQa%WSJpa!ka>=NR(C_vzSwIUpyz^}?gPVo zaxg~S7zkRCrxVdaY(2iWJ6srlnR~|s z4ekUi?)`Ls@=bse^!(&Nj{eb=wfh$tFa;lK`q#0;jEU=62pYu11?lnY6yuS8G<^@pcQbF&+1SU*DKFR zKc}akQ_~MMf9E2-CmdV4p{i|0irI*~?n{qFIJV?1RXJN9xcgRcWv+M}%3;Ha92sCM z__t{8N9A-pI|G9YH=Gd@hz49w%Z{Jc0XK3*s@YIW5$q<)wP|2%(kEU)c;Tt_flKlB z86S8>UfGh>fx7s2mDL z#ng33Y@p##lr#(u^=6AG#vWxU15Az#9o|mie3}N%IENp5?@9x!EQsv7CT7{gVEB3SQ zrl`tiicP#t&Z6Nx$K^$znLU-w5hw7_9P@|-4@<+XF{LJrT5GO9WdnloWoTyo*!^D$b0lz z^B$w=#OJ5~Jcj~JVX$;ozy6faj#6yyI-bKA z+bQ!WhA~c1469dAkTFF?`gIyJvmW3$QD?NY&ggIg(K=()t!MsX83jjsUc=^yBBY@w zMqTGYlp*a)B&daVWx+CnTY$te+W!d@I@4pW{wWHXeIrl@!@e&@zkK|)1} zw-p96o$l{B9GN_JgQ3)8TEwHZ^S9ss<@9KE-Kcgxd1MLyH+>c*8kX*hA(e zkP88`7t3i3*P8mx+^AkdWf!*&mj$`lfj@+6^DZ&}iHqLPr!PwH&~*@IMk+GaX_78i zR~sIdp)3v8YAVF@`GFj+g*~`=XD>x%Z9(sOk$Xu$MV@^c&iX<`Q%J~R%OQZIl ze-egVq_A_;m(M}7{razgDk8^(>8kL= zv;z3lk*61pyL%LSB{COz63~{eA7ZrO^44ERe|=+_>(9cT1Yg~1Ovod$y2O#4LQW7U zGZvy=?#PC*BxeGOI{gW?$6kO&EVtYxPAHP48A<2RaPE4?f@=K1cjoZ=u@Bus9=i8^ z>An5K3>}X?217@e!i4~vQM9M+!Lc^XZ8?-0Q7;EwXs`=4@?TKnZn~uN?vJi{-Q-GL zLS>g4wUv3)E-}?IHTE(!(wdB4-34llc^yAeqbmP$YP3Yk5^D4UPX;yKdCXm64|011 zwa=GlLu$(>7?ZK-QhEMk(4)^EbC3FMj|PVsqy7`4Lz8WXrVi7g$;YBY$4+yw@rLG# z!_Zv!vFOpU)7(Vcqv|k2H1Sw;=-7v7NryODCU&j5(y)ccq({d-L|YQ;{_=aj^sf&? zb6Xw@&ixlTzZzkrYJ~qHe6<+H>NH3J(qUaQsJP}PEBcZWf_|&&f;t;cTdN0y&lB?6!U5&}2x}D5N zjBl@2mxLqM8r(jSYVEL~aE0gZNmrLX%Hr;O@WGg$Ok=xgjMd^Iu6`(DkZIHLf^%FJ z(I{VuQa}89j`AG+z%&8b4;2Z5ox}&Oo#d@6;vgEl*i{Mqi^;=OkiZ~uj8I(IKR-Qv zySjT#O}OZ`f9Mlq5;5KQ27gk4q3T*D7`K>bv0(H(L#J-tVnVbZ+ge^oYfuj;K3|lt!Z1ZyPhH?;q7(Wif7xBniLn?M z4jW|9Is2sQGZWL(w-v>Put0)CP(Ne?nmhuvs1H&p%N`RunF(`uio*Ia@3BIk*4b#9 zH`9M&hEHNshMlp2jmN2SFg-XShSxt+OnIg|uxN;2D&mzJm(;`;QZ5-2 z_a)o;zACNkCi<4Sg}bTNupLno))g)s_}KKql0Hz4yX`}?;UGNDdvM1w>#3(Cf%GT4 zcg~!BsCn`TSRqcn|z1jCM4=q!=15F``X9N#fhqNf%V_U)r#6;bxB z=}l(Rkfz*(N#Y(wOf{IXUg)Bppa=tNjTMrcm@_=69}a^MPt%adQyIGtU_=vaf{1zR z#NaF^I*-=~Ju!PD^b~J)Xjpp<6m*^!@h1Wfuf>0IlUsa7hz@BOWg-#)Mx*w<25-_+ zd6US+eIDQC8}q!;cmY##2XHuP4Jj#^o#Nq`aN^@jVM~faX|Gn{gE1a%2^R74vgRKV za&!gub%aIHZFre;JaVwzK*aIG4RPVQ)xG`d>X0GbU=8sI6|3{74cq%3&&Q6Qp?Y*? z=yUK4-6$;Am|+uA@R8JpC>*Td$G7?QELu~qPzcL959^4>bJ}$1m)H4~PuD{QX z$kIs$dfc#+@rbDo5*QMkuHEXMa0r|jTf*QVs8VcHW(+e8#Oc!u+=UNc?@*YEgs|jGZDr@QG%l%hhkn9w%T=4h`LOQ(JQ%4o$J(EitZTeG zkhWrf(w83X{(Stnj89rSuy%jammcl@+G z#Bu2lYa*2hYtEnarK9W*vD2uJ*qXG2t~Mv>M0!ulzTm9{#!_4>1srAMGBTN4*fL=X zhS<^fx|wy6uzR_<){JbBV0w9CY(OR3gkjkLJh7X^MDW9;kdGc*>6=Sm>EQ229(R*( zwP9~YY9cJfWixUV=3CmMsyXu3RMXpTeM*$ljr4{=3Qy9%gO}+xi7u*x)Ps<%2~FXE->k|2(Z)yOfOXbG7v$?b?Xa59`vb&f)%gvAz#;etmh+rtIpji zb1X`LVr4Mc2tCzBy{|}KVL9(eAO3`U}k_~ zRRfR4HI-_0qE#QmX#KOS9k};lc)Y6vW_q%fOUTolH53`bTxfIEx_QsOHk&OhKHO>D zdLG#KaF7#_<_r4ERZ8o9zn`5@Y03Yf_tLO!t5dL&kb7~WR0O&tD`}+bh3es{Y{c47 zq(f+9HV@@J3-7fn;yEyjA)|7-h}&R;vvAunSbu^6 z9LCqLEl+d6$x7?(0`k?;9Fk|jYzGq@B5txmegi4!YxXa%!Cj+!=Hs-zTfSaxrOqu` z(X^~{i_2Ct9lmpr3ggXCtgT(}RBgkd|$o z)D5NvUzv)di_fc1xnQQ}i%eU{Ri_A^!IXhwKMu(svKeiX>MwHs5L}1nk|~m{4=Z%j zPhDra+PFfH5G?zx4APhk4o|W~BgZz`yrnfrNLnL+ihKDaNH1E6 zqYJ@1X*)&FL zWump8WOtyn0j)#kwR`A@;FuXeYNg8DscrwP(m?AHPtC%POLz1#Yd8W-0U6QgIS)y?_XNfMk zYh4DN;Ek&o>ASjyLCEh&H4N5avHw754b<0~s2^azGbugm4-U=G0^ZAT}D%fQA^=k}a>ZTc+*Rm;htlMgUuVRbfI;_NDWJEMvcByJ-=NxqH`59mBE z5q}`UYbuF?a3pR`FCIsQ7)m%z;`Rdtt9*)!>WgW=GgFqc0M5M4ft(O2vcfAJ_z!c1qqa)BEtl?Z^T18F+~}d=c++_xZtIXLpdH z!dHC*VHz~iS>YYSPNXh8e}-Y~8Xs@$rXz}F5>pBdfi=#dA#@?hGEt!#4_>y2o+#zb z>m(8qS;oE0xidFWOE~ttPL_X;B~GWgLDj{9%g~cjfj%~=9V21PFe$=Y2e<;wUb;!z zxN~ctL;|$8js0mJ0&JI)QSIs7-eDPo_3&8*ps7Q(EpsclD{myc4`DPP)4`~7H4#U* zUfgplWP;w5xJj4n%)^GV8 zi$fh!kUVj$04Ms+NR-tZLW>D)A-_NNBGV2hFni~8Xtv>F$!>~84q8@e7K9fyKip1) zaTj&Ci<_tU?%1{m$GuOl`^op|lSSE2Mc!MZ9rA_~XQd;CG%)S@oGB}^quUt$jhX?D z_Dqk-i{wXT8IlR@daDSwcd9w4A?@ZG3QTG%CBf}M;D&k5GN!x|nG1U?R31k9m!SR8 z>XNMSwXMKQvRLEkSOxX@<;CiSKZt<`n-cR)J{A+oa;CD`u@du*JVF7WL&1tEm_WU8 zt)Kd*m=~_(VWvI1^AvvmyRi!e%i5iE=dw!(M=3Xd&V*|>>f_-pGSRMlts6iMnKUk( zfpZ%&XX1&RvN{NQg))^CYqfcE&*Zi59Yi*A1Z!7zFzB7lA`&F*{9m?F~x<@XLrRu+uC_6yP8S)$=#yQ}NqSyBe#r z2+xC1rq8eww&sXhZ}6sJfBZ{MkMt!!yq+(m9TMQyekm9@c>!2;6eppHDI|8Mo_|35 ziQ9ZKlJ=Z3`D(|F5Tjnln(r#gKns*JWntXW$z#wL$UOSuFBvvR)R&ds9S46&Umnb|l&BsyP%MOR zR>L!`V~KN&!$jRV3;pX3R$|8=knXIFd=5N;DqIki3+sZ!PUHuX?dBp6#-8pQIdT;= z$ZYnK9I9y0<^t=r=_IYmsN%HQEfSJpay!J;lRJJPcA;=(Htzrejbd)+9Vdx7@=(({ zU`#VLX%SClA$&cuCz+WvSN$cn`Dcn`L*#s+Af#AGM>-+ui&liHTyUnFWw7u#%9g5f zhihj*l-oA$Ta z{)W!b_zInAS`P7Z*v@>a2%`ZFuZf*A8Wi$FyoFV(!awZKmF^bPRXuf<_pf8rSe;q8 zI+H|OsUvoO=Rm8BI!WqhE8d%~?#D;OgMLC5B@~ zJq|CAm+BbREn17j0&q57QIthefXMeoRfUK)BiZYj>bqnkcO!EZ}+yMv+BLgrc+V6_~9Z8jT3WTG{POcrCenEGaEh8 z^we;Zgu9LcxDjvdISPVmaeIz}pemiul>}>4FAfvn85Ft3%nPxV5Gl6n92B;1)Y~LJr%j_hZ2^ys(z)K=zJ7xFSDz zuuPSO_h^RB>L6jghUh0&q8LctY1eSI!qnm?l%tH6Bj`)KmiB`V(?)#?!kW_-^0O@0 zhpdiESN`z0cV*!yx^hc4{hPSQ*bF&t#lL^ImuJw)%?wJ#zr!W$3>rEDDNd5~bH*tY zx*7Pk`fkH+oxS#KoRS#%x}RcWEdszVe5)o z1Yq8J47F2ZohTB3oT94b$B1BZQF#)<+s68i!t0WBOIDN1gNH$G*kutzOwP>dFXl-? zht}9?;I_@{?-27Mcj&gw^o0)f>sy|56k840&FifbQjV(Wr#_LE+4wB!%)(~WIQq8l zNqjESls~HNg=j+}FiHulWyQ`1;Zs(9LdU@zR&~;l;$KwGs{WEY&RQ_fJ^aMghctb9 zZW9>qp4sY06r2cA5cGmNTR96?C; zLV|p>kgUQ>1fRl6;SBL>wIMi0Y__$jtckPY7uraoHy72~xou_ee1kU%97_@C;xqJ) zKH^VG(%Dv1*WP9pl_1oUf3n+*6KHjX@S>toI0F$ooN*88w zviL^iAJTji40=hb_%P>%M>s|hzHUVb)rv6?g4ufW&sQiHp*SCR^}~L{)wd_784~YG zs%&yYtI~unYp}AYw^|ONJ5wNHxH<^?)f9>)3&?iIg;qY7kwOs`eG=%jAEJ6*nN-S# z@cez2;lRo8HT@Y|^+@#U7oq+lpkPo69W84We0{M?sVwv+B*{%ni%tgME>L8Kf=(#) z=>?O0e8J)r8oq3i_Hd*qwd|1^9ByjQHb`_KQE^=6MT=5ic0KFsV96qT6X4g@DefQF zFBwSTUMyL}aDeb!gV2zjbSVdj3B4NBghU#s@9#eDpuR2h)<(m&;s9h&eZN=Ein`q) zj|!BMF!{^ywUwsZWG#)6Kdrl)olN&g1uD!czduY0i0TT1jN%efA@W<8NRpiV7rTn` zA=L-13MbO_fdntzJ<6F;wi#<^z)C^>a zYk;bUNB=PXBL|WnJ`_)CwXx!mB1ip*hUg~B9S)Y-SLtaloT>bv+DUW49Nqy`k~nqs zpSimjI@0H~XiDoS?ScOk@O}G@S4JX5;Afd$U;In3$<7QFir#pu4n-Ef9Lz`7jYZ_E zN~0Z6pDggE1oKFBns(-=!tuvs@k#!}dKq>EYhH^0vfZW6g^gPHn<`79&lS%suVf1a z9xJadSJ-J<>YOUU=?ijObfOk~!3G`FuBa{pq7yC=Td)u{{bK z#(I%%Q;<0ma1wmHKv`Rl@GzE{yTpRW;4n^yEZEXQnap8e@nN`)Z4n7OVlXy3 zPSpQRJ6SbbifnDX5>ebwTT0}HAqK$)r_bl2o#J`hm=%Ruz*Yu7?1Fqei}RwnPIat$ z@xVf3)TNC|?;gKE<)He`@eBK#lXGY$84G`!r4{Zb?BS$9NIYP36q>^2%0^c3+wq9e zzPIZ<8jBd+6Bn{z-g|8mF>-7BM)Xa}%a~EbV)4D;wV{6{xfT(Pm27yKZp-lw5p%TLkFpauw?c?)srIPS{(m>DD* zArylbVg0%Kjpt=ng_=~86*7ah|J0qIZf_!k~wI#*#D! zHm4h_W)GQ@5iOSwSK5?JEct7E1}PfuGNava#cSYA{$7=Tugt%@`Azyj_3KW{UBq1F zo^X^l+Hxr8SK}Kod+WdY2AV^7R}yAq_X)7ft;e*5<7?uBl`Zvq9)=4Mm>gI482U14oB*Z9i8`2MA1L~mFQ{?e z&H)witXN(_Qi>D&nA@PJ_E94?5h_O4!0=k z0gqr@P)u0yhX`nk?S@^YavulW-{H}y{^GdZC8vHiS|N?a#C%Ow2}WwoVEbG!zttDr zBw8GR<3WAPoyoUWN?t*ugCkxt!+U$f{kK;xfy__L4iG_BS$&a7ERI;QYqc@4HH5dY zzF9Sd2?6VXBII>&E9=IW(#Gs!Rf4 zXfIlw6N>fL1sEBlDP)SVAGeyCi#P!z4F^46>lZ$GT_p7JA&9D^NI-!#F4yu2pR6lGXsBG0$Ww*<^*)%lkA*NpC<8 z)?NTz$!4W9t?3yr$%Utf=I24u%`G45B**mF3t=}*Q~HFP(|?qy14lsi09OA-eNd~z z#yIj1I`j^D8d}pZc8`#q_28#**PIoe3Vno*>d=ET*rlioEktaKO^>?CSG|pskm=t? zD1~1*5P4!YmRa@uVIy-$0o6hU=9O{55m>yL$zfXK3ZOsZ2taSPNmxJVp_{j$-!J*4 z^3gzbWT1a4V_gPOBzE?UnX;z7$R`)Xw?b|~7aJSxOokCG5 zqF*SzsNqx(aln{W_vQ=n08%96T%n1?6+|vy3Mq!rK!iTCdzDRLTvZRF3n%u0&iWuQ z)t`ibw7%+vPjR+qKVvXYocLW?DTI7v%Md6+_G>a;HMr_y$P!^6ti^AL5Db)9Sw%;2 z#K4bp)M}#%?KzXvz(PLf8?q-~98{l$ko@?dQ+-1+YZyv+j8I*J1>QMA_y0U2y6^hh z+URak2A*CB0wl`+lMf|?pA!gQT#j%q!~?n5q^||SVaMYLzaE6MFOr)hWrW`;zXsuV z164vewQK4~uyrX)ty7=|)0O3DPg=|nBD}lP93MPg>{gHKneKWNq)(baPF^?2(n_mL> zZyYbcFFyjnSxkDQfZO~M!2h?y1Ae0-6>3nFhPRjFHH7IP@&QNU6xoWxF(ytGq#7N3 z8JbX{t}HkqDQHL$59PHYRi~S8j$Mn#l;Db66M&W|+-V19lB~?W4k0PA_N~woK4}&5 zyLGYZvs~tXHQ9%x?Dn}Y_?*0X*>f;HR;kO5PAj)W7BQ5*8JREzM=a~l`C7NSf^J6w zclUD6omhPeUp}RA*W(YHPoY!DG-l_Y?0{|8H6bc&uEH5esD~2KkA2j!lvr} zD4rR8)Wk*}G9I{ivwz6Z+JLf&%fbD4aj3JZiVtsVwy%1|_Sk$@86V!wh@ZoHSf5UMu>7(=a^4H-|VL?V?m`AK5~b>k%FWX2g&6N+jQc*r)bhln%D`D zMw&_asHK?%SPo8bIDN9=zP0;wJ7=2+A6_@OX6_oGK$vU@6iICe9p~NTB*$OqqoT2TqXQbM z9>Sv8GAz40@G<$hL~YUMZ9%YDE;(%^<~&(Zi8!t6!I390C4SO(IwJ}ds*@X9nA#DF z1zGJ7>_qi?nETf98HrZiFCcA{yc(^dO*GjsDnZFYwUigAT(ZkdX!J(qpV0`8jy(Sv zfYM`IEk(-7{!W*(QLgUJl$utv=h!2Y=i+3mF5Q68iRq7eetzds|1Ce`0tP;=y8q46IIg_uELq0m9U?Ak7US9b5sd z9DRzz4?q@t?~t90=2ug|XG}8W#X1mpjKK8GjV@tJ$06r9NU%69O$n`pIvVwCFc!7aK%e)^!v_S$!HzTnF#PIk>T1*@!9y z*pY-s1M@jgRiNWqwTSwr7Z_N$g0mJK?H{KQ)iX*gp#!LumWg}?feL)89V^e2WwZE8 zRK!lBzGZ#S6)D~TE<|Gh9Id|ehi?koAq@P*1GGSEAy$^{V~ACWQpXn zdB`uUGszG7s4nj!*-*nm^T~Tig{7;$+*2vgy6k#Mewxy-5aE=XI{+VCk|eE z%Gp0D2w!&?2>a&)Ap&@C+#tk<9l2}Ri#fEo#&%R6>8fGFwogqCKDi^K6s8aa3Vjho zT)G`q=E-nQXiDw}WzD2BBf#t2Uh2&&>I=nER`YR2F3sne^>M)%>SN(E>jLZO)R;#d ztlKDBov?Mv`mmL%Tpt7Hd&eqj0w(hjsR;&bBA%QThS92pq%X9oNZHeZWKYj8*AE7V zst50GVC=vd$wMlu?@lC=C#HuX2}^?9J2=zQx+Xkf2ynRfkNYt-((*kYG+7~@OmAse z4}F#YuU;CzA`8aP)rYHt|EH=kDVr^;F@@U;Cm-gZs4m4X;Xay|lRT@RXC6Or^+p&q z+2>vz2ZrN#A5sy@kxqD7qF#6Lf;-H&oZ zm#PP_+L2B8#7q>!A?6%M<%x7c^c?p3Bfw@Xnv6wm?Fj*MUFP7K`^12S>e#=adeEh! zfNo_UqGXYeo#YJYzN8PD45beX0Of!cy4C*_R}@FqzJyZah9#4?a*QZBRcKwnku);= zpJ$Y(15_8OS^blRqEZSTuLAq{+VGonR0Y0wZwa-rjk)*RnEOO`mX7U0{bN5WoIQrt zBdizkcUxpka(W=oA@<|xCJ7L%ptJ6be8Ixo=|+qmUMjKxiU|7ifs}I-kK)pfoI)Zx zyv@C8HypX%y+!%`-WKZY4s_UYuj*-u0utx3X4aQo~}d4Ccd9sfky}H z7oR||e*Osr>*t<8u-^Fug7pheAXvXLf;Dd~xcJT|03UeV!+Q4<2(N$f1cLRhClIV( zdIG`v<(~+w*jAHyk0cfykvcKSIt7YJU5q=e@200JyBeUOl%{1ysOsooMh6Cyq<)FU*kIDZ&$;W$SDF7~$I#)_&ITyWuFt`pp_t$iK? zilx{iaDOiw%jB}hB(DxSKn_EFA2E<7TZ578)c1^Ca0p4qY@aZ?@dE6t#-B3RewMBK5?LW_xJ@@JM9|3 zFyEYS6M#BfjZI(ulgmV3+qH;^&QP3`5F zKvW{u-4Lum+(!ZDYEKC&nF-Q-vExZ4R=4xHS6_1p&h^+JOwR_l(^I{))u6%gcm*eY zxagCZF~<&MM<&->@_cWGyk$64xui#&;%E?N64m4-FrM|~mmHGI!|*ezIsZov%1lo}O?Acxtu;ClJuS`Q>1Y9Ue|4AHD;DPe$GHWS4MNJB_ zkD5%1l7dte^HVr5w!{IGZ-+-|`A~bga&dw;|N1d8S~KURC<~F z$!{j*!?_Kz#q@(&&t3e=xp|fV?%KU)VgBMv;g>RvNc-8Hd0YrpBxMJcv{d#o^jpiT zbklV`#o?LqpbssJy)#O&)jM&!9!Q!OSdxtL^6OZkKrygX^txGJQPg)_Z5BSx&a^b7 zsIT}vFMx{rdmP2mkjDX-sW$f*n5j0mV77(1-zhdr2qtl{SZ3vVTBkkw1@&ix2*529 zB8JBw6><2J)07%KJja-Oaw5dqocJE@`WR;Aqhc}`84rfX5;XxNve@8`?<{oce^uX! z+zBN$%HMI}7OuyIH{~H`g7ia9n3RlTw8Xr?TkPOhm>SFtCd!v_pi8O{Unqqad z1AIkU!m>fW--Ah4Q%;q*QAaLzui7wEN~qkgPi5UG{&ktol0N4=t}8N7Epw9uJKb4Z zZx|a~q_dohSSQhm07K&s%&j9|D}%2QWevV2ms^>yMdOLDu~=Zl*I4ftvtli1p7~e- zb7_|AIZLx-MrVIFIFeEjRiP;jIN}UxQQaQ)Eu+fmnd*;YTR?B~d=yaPq>U!=R>YNP z$yVMiIixhq)q!Y~v_paT16T+kjsp>`3*-8MxOxNxhH6gAQTa|4MQI(n%3GAc-kaP# z=?ZG3Yt2?8&KmpOoUc?bbi7(D>=w>KEt@j^Prg4W=Tefg&Fz&1 z(BV**jXhNVhoeuJD9M7HsLcI^kk!k<3`=QgIPI|k^M79xOcK@>NV0^?hV-+dQiX)C5!k zti@VY??)_hpE2_3kew*{Ydw8UQRaetE+{FTB?jt^jU5>3Fw zHUFG;d^;6TOQ+SHO5&(<5Nj%*v20BxY10bMQ+X(@sT@Qr^+PPM4B5FVbk7KCSpm9l z9|3fKevHsDrz3ZWXdcoigpLf&tn;=6)e7aS%IcqyQ0nvMzfl=4E7a`5gL9`%!5Ru-l^VZ z`#}@aJ2L_oTha?g$KWUbNoEn%hl&y+biIe+WeygkE%ln1Ra3C6)v2+5Bn5bqw3 zAxp&7kI+^9wa$21FLty9UZ1CJww|0<0q(Gif6WvApfWEMzr9Xtt_3H_Hh^KO9M+n9 zD-)Ix1)$fh4G=Z2!9e75j#-y~luUu~)z7ln5F^>&;<{b8nbL4Cz?pEz~}hP?p8o#@&AD5;L46i^*&fab&rD-)BW-+!nK0rLeAoO zJ#Ls~P?U+i;a%=@XA_5-NVk7RZM~jP3sQIGVuoeK;sxI5M3I)zqEDec*SSX4Zdwk} zG+^^_xsQ~WaM3-R6(ouCIDutw$TLmGuv;bbNsIjsIja5$DJNSptpxm8{{ikqu>*5P zyvG64-k&XLz5dDR5ntfF_67S5cX_YeC8!o#p{df5o$&KfFn4RYPymZ_679m` zoO9(fLp^(ldOtyb+}g^#fn+$cYHV704AZ;W5#JiJqD-nWI8rNRpPrc_(QsJE)_U_o zEEkQ1ngTad1VSRiidT-?PCY~HF>3OsDnFg)P2&Y=)TrNehIlg)3C6C7d6CY>mLZ=?9jP){-GG)3^ z(u*QksO&R_zI4gyy|0_KnK{Zc;YcsK+xjgzAzm8@%MrCOW#tn6mY6;gggns#OI0!Bt^iIo}KFiE@og z3c^vUCs&(TR(F*FP}4bVLAdLeR9#IAdRHJ~hAu5#kFlUv&3HvLs2G;(-{Ydx!Pv`n z+DX?Y8e}bOkda*3Ac(7+H&cxsmaK=vIa|)gNs$Xv6V*$FsgY$G%^^KDwuPyo z0xawx220=vRaGCD2;WuEu?zg2H3%4DXJJjpLy+Lq#twA`j}tt(y7aNb6B0x;$RELv zWL~NTI<-=L3>crB3GPA{c_=oz;;ynO>9T|F1;|WzfaMUDK8w??Lwv>?%`4OOhyHXy zXLfzhCl@-^U%2%nW1XCl`u3)?f{0y~3|dB^s0Y>mGC-n@NpVy#YW-n90}73AZ4#4n z&BREgl(9ykGZnSu0-}f!8X;ed3`#_i#fx}9)UlT*-E_&Ps~0>5A3hhM8HD8@3RLYF zP}aIp$kA`AKh6p{sFaR7&riFL2sw)XOb0sdn4@feOAPfCe2LI?s{uL)+tDKE@zl7s z2b#;-GH75x89jzyS@L?GgdRLo%PpA=%@kDqMQCAcKoiy1BP5Ud684$`NuE2Pi35EV zt3rsYOQ}6oh9U)byK3ixsj%;QB@|#P?3n7gGB8)H7nr|V6`1?SfVt$DfO+kDf%)pH zz1q?f5>gtjS&dm<hSZ0p^)keIu* z_GGn(-gfKxFW)NfxaDeqymc~ZlgL_1cEMd5UdaP!UBUTV|M{-CfToa^foP3;G+zL# zHVZ+G>RJ5m37c{`BzGpgGC}hD0m%Zu@%#ffKDv+`mc_hbz2tK5s^s$BF>-m;IJxZo z!{B$f?4^(E;&*RaFEID53e0!LfZ26S!0cWxFn_%&F#j+H%;m=f%$4f}<{PU5^Y>%G zTy{*rym`IAd~;P`4vqn{^O%5n<9dNvTostV8w2Lm#{|rAmXWVq3mA;GamN)pC`p8& z%!VqfW*a*j1$PS1pr%gP_11G!Wm40Rqt@!_W{C{19BuP?)0br0V?~>9hzpjSVu{h7 zFEhQZCkd|!{@Vs+(0~31zJBI*xxd_QTYI#%Ir7$kTX;W{@2C1XtD(-;lX?NUbXs@z znccJMufHE&@;~yRUj7Ge;AA)4C7aV+9&E3ph+z)hO#f!%g}|1@>aTZE&jpJgnNod% zO;C4oTHV&gBXR{tn0zxZeU7x?sjSXX-jQNZCT$zD|9z97Ov62%oc^8ETRiY=YckNu zuk#WsQ70u1z$Z_cuZIq4Ar?l%Yyz&ZBX80oC-4>y3X{RoUW?TwlLM}8nL$zKx6;BF z9MctIlqT}nR0l{Gy0Rb!*1f2ltq1iKCD?{K!4ON~3!J-Tex7|L1Lw+5`*gScL?$a= zyK}{b!`=&tLA_x6n8Fo(c-M-;6JcR0L=-AlOlM24@#F?V>aBrc zF#)*mD@*9eG0I^pz^R;fs~JOfnD_o*N!Ti4Rykl5Wu?expscTBUk~)*6k?n4%qCkx zMtPdK0;p7^UC}DqR>~dd)KB)j%_X3^r03{s={_dcq)v#)g`xM!g~PmQ{5xP8a-F_R zAn4xz*W8x?R#Bb%&&<6y+YK=(pl%mWt8&?c=xdv+n~jwuAJrW4&J~1 zQad4ruFd0X*_6W@9zp@sC#{rajj6|{Z6LK{ypPB>)w~CGLW_M59kZ`ic}Dk`2Se%Q z8QWu?v2dPR(dZ@0HjhCtx$e}6!{j4)NHYotnJ4lIh$=b+9f3>71z*s(3ffL80>Jr^ zBZP194Kk%6KxB9rY#`kJ?vL)^FB*)pN(iITJ6kY|-PL>53 z*AuT8M*ttJ@`u6hEy9Oz4qEoJK(yOGNf7efl3fJK-n0yN0FI68Wy^5DvgM0B#~Vah zyCkDJ1&nB~X?^JkShsyLcb4yb|bGJs&x{}2ughP}rX$(jWz{KPI5VhiCAG8cK;W1(yZoQTtjio|gM5l$OVGKrS!EA1YH& z2xP)7=p4im92DjdBu@!Z`UIW>cTPv!L>vW-xKRM6rDW7gm2}2Wswvz9fc9emoK0?q zfdStqfwP&4t5typ=n5^iZuFv-Sj~+xTJPW$+Mrl3EF}` zA!v6#{Tq)up8ZYnMfTNp4i$lN77Sj2`9;)BGy)34Q3O&^LFft_AD?nP00 zdN&phn+6$^1!mB213;8J0+quM;y)l?ONp3HSJx8{gv&2-w<&LHP+j z^?^Y_MTAV2ldMhVk-)Emhh*2EIzh7O9X{|O;d=$iF1$D}Bnd?f{8Q7fT@74#OiA{B$Jo!=zk+uEa1fmIZ7 zkQvy+l`bE93o(EPIWZOt9Ak(U?;UVr9pxy^<=xxcdGk>EWtj8z?^6}Aa1zz>8Ht3porL?SJl#$z1lFR>6{y-uhB2pgr8qG^cY z@?3G4Ei4+-zKY?+9-rFwOdCHWvl&0)KdkH0RfW#s_T>{%!Q2pegNFqRXcU$?(Vny?t3>3te!Hjz=q5 zD;&g?D#-wguiMjtrkg_ur6wfN5L@F?A#;M9D$b^EZ zXj2`1MQ|WL-7GVo3AKO-5T!)Qroi^8<*0_l2;c>0y{v8|RJ%M%We{t0zz}E{A>1bSXM3Pj8{J<&zSy&8OupPQP-)+PC0uo@2 zy^edBhh$fdmxUIj7j+g`kaQLZfmqLckq(0ss#FVenL;9H^3qqq4-$nilz9<=yek{5WRZ&RjD=<_l+9t%WuoGxAY5xv`Od%SIEq<9B+DLdM4EA!g1J{M1 zf>L5wD+O$zf)QOAFc5PXx!s5RxkQ_??Dy2rQT@T_h-8gM!zHxyaKJ75Sx@4|=fb%H znG$u!SFS$17B;~X-SyW&1t8MU9*Vq%y&)YqmuBCEs$deczmlG1xHtZuDg#a_k!T|K zRwjiQQ9}3HDg_*~qypY(yidhd1q; zzQu5)Hsf@sIA!B2&xYUB2D)iq7T zp@@TGi4_U6iXt@hmK#87bkgEX($3d*%`>0N_%>$D8Fz!dHjbs4`31Y!XNI@ zKyY*el~ZLQ3L{yUWwK#q&_n1nBwT!Q=W<|&)Cp=Tz3SrJTB>98yg6Y<2|ei?Y|orh zIc?xCq(bnv5R)()l-Pk+h8oF`KVsVRz!&J?Lq0dH^?O4HgGV4(8x0pQ`=OmI16+U{ zOhCXtooZzCau7A8oU7*YzA53n{{jv&a7VbD74D{+d=i{VZTS!9hNCOKvF>h zh(4(P$l`rb(3z7%Dgiq+00rat*oSOYpyQ_f zE~XVTBt|JBfQ}%QWu-!d_q&BiFhYO6X1bku!DLYgzkvb6?#W@|4{W0 zm?gVE@JRKK2&wvqJJ}J!K5Tdtyq^8@ID{5q6axi@bMPcWSsD!a4TjW!;`orqRfFIJ zY*29%iD9NRoFfm|u7hfXO|!ML4otQWhGKWDmcfAjI`y~#y(gOB;8rGCr#*C|u~ID{ z89UILgCCoxdmbBaGB949#^#_~d-ii}ZSUvPnxHjKoN4QMBzwfNv1$>4fqRR{-Uubp zbEkng63HoFD2Adk!&9YXcpbk<35SY1mqD7o(+?;w*~DzQ5E$>i{2CV2pAdlx)G}3o zCf@QUq@3`zsvVy`Zuw=Ktbc!T3lTgpmXb8P_x^Vqe_-ME?zp7!%znZ&qGyPBOU?_z zh=qV7CsY_u1@A&|bCF%SlUKTvB~}MnK=jKP70(rbGc3FVJVY3!J8!uUyCLkIvHcKsG3@DFMzvCShK9X|EVi5TxD-xh(cfuy`Zu=%E?HM$(i(psfHznb; ziXMSVk{&U*76AHV#1LDgeNh6GQr0x$MUl4+72^kEcg!O~}0!u?+Y@Z@fD+bL+F-jJFS( z^0tCzbB}~D7V(L)Zjao_F>*v{orY#A&0vZm?p8JvVKd$x1})s}puPSb;=n-;+Ab7U zgXZ0#Hya=6e$d=akE)h@uGDdQWZVMYa7ZwLaeI?3$+&rU!i%^}szqe6Dkxq-(`8f( zB>^OO8=`kx3CS`kcr5aSObX(@fR3JwOuR-;-#br?S7QoAYy}t|7a=|gEa&6%v}#!) z(1v(ciCEs`Jc4lNlc>Y{JvbZtLOLJYN8w0!$AYsgTw&oqKu=4E5i)U3fV*o~o8jiT zd;S-gLsb}^P&%YqpXi7BdLg^Ve(}Ec!DQBL#^Lb~_3OhKpeo!7d>C0OO#1h(<4(Gc?lKumK@N0HX>%e2%}06g?-U z5!6#&XCJjm8U#KMwY4BgS`Uog2#HJPvR&2dr<%Rbs|S_HWd^K^N=|imU~?*zVaz*a zdbRC|(QKlI?YT$MQeQAd*Ty;~vbM{oV?~bQ$h({G^#oI7$nVF&*$IW^&#3LbHEjF4$VF`h{fR^~W6T!whC02{Ug1>4D zj6QBbqOwSpkf+yQpPL1+QLT6*;0ltcM%SRtj>sk7_{ob%r-+xCs9z# zlfr-WLao~=fyxIJHDQIRse4tZBQVH*$!m#@d8|Xx`0)~J5C=j+ni3-hgM@%>vbxVy z!@CJzOHfK3$c|zE0ae3JA!`O`j|fF8g!UL@$dPQ%AqEhR9YO)@GQ8ZxZy$g(>8+cr zR7MVG{3w$nT2|vAB8Tj2GVJd%3L2|ZIs$!B*c&#RD6^F5uD?^SQ-HG-2~nSB`?w*n z1FWBl%G=RaB$grs&%PtNYI~NTL1+Q60D$ob9mn5N0gM9dP`U$KDl!9GDu{8wCPkqY z#U^VP`e;PYRLcz0rlb$chP6V3I-DaT7A0Y+w8qoW?rwRQT!BUld?E!n;9lw*m}s;h zqs7R2wfcfAwsDy4+c-QL44RCcW1MbImf*gZ2ES2vM1^gTAC%ljgC4ueAT1t$2}xS) z$-vBDVAcWSq`C`Qn%g^p_)}x0`>AmNkr;@3wtyfKnwdG4D|uOYzKm@RQiJC@8o!A49-l#Ii@lM0>UD z!986|V!Ff=M#n@Lrg%zrcsxLiT!^3p_R!a?jG_{V7*k^kWcAi3!-+&ZhAkJlvD!!A zXY<#&xC5(w%Qy6Dhv2XQ31q^$--LJ~i6TOh>I#m7Kf|Nm=l}+yKL|IMf(yqv2f~f; zlyQ)65#>zxlI4g=g7AtrYp}z?h0V}S5#sI|UJ_v-q~KsNNPOsTi}twzMByCT1`uL%Xd6IW*`aL!0b7T*0Ys(sseyA~0tYhVdpe**rhND| z9I627)9Gs^Wlh0VDc{&hA*Qs_N)cut0k{?-)9GIfpDtSt1d}w<_Z@u@Fw-Z{`;4E6 z3Ock6Al&BAHh@@~L)*ZPL)`!()cVwev%u?sjUZp3WMU&wVhKr}a?OCyg^+AgGc@>_hTpKC;SPmnb zl@&td0K{P9(J3n*+;9(EwB++{*nR8+$b$STQX>~A7%p{x)QsSU73c_$70ym!zzm9o3MP~*NK#gXYq-0=A~x{BVJMnd7#tyV`(`TyO&aJl=;%4>q(}@xf@!Cam1Tq~1d)HDp0^#Ug(jpx|0<^?uR0_E`-Gupn*8lhdM1@(q4(psGvAX$@OM-2b=+%S>mgE?=47d_!cITGrLHt zOv5+`p0U5wv=v3dq2uc71{ep@$KIZ|GF1V?Ls~Fq${ZRvQ2~d#h~ty*k&}nMXjNR& zY%6+}juG+f1X_H@suKN>njQM-@8@8LO%4!sInER=(dEGT_`Ok&n)Yk8_(rH#ZLgLc z*PJX2uIyedyIojs?y>I1wcfI$QA#Z>lbFzEdi&+tOsfJl2KUR=Q^S7&*{I^bAci}x z&uqhf3Xw1J^)RjB&+Wk=6RN=Y0wH`$%&t2Khr$mGen6`Y1uQX>mFn4@h0p#WA}%SB zxMq)by>@dcbwM6og-zKgp%hzSFhqf%Nv!owdMtxVvOHi+1+aa~T|i;w41zIw|&M5S8a)+;#V&X7?m_3%>s&97aFbq#w|t;lKbz zlG+-DXN*-VL$4ST2%#&VIFPVID-Yj4RY221mMHOiF^Y~CBJO)q9-l>sFbEE%Bi_1Kpr7sw>fr!W?j#06y_J#n`5IjnEc3`6Mqf&S$*>wZvF;baNp8T)JLNX#CaiWz{alvK6t9H%&wg8tLPHN4ISCR zK(9>9Ju>0jZf`upRsjt^glUWBC9xgto&!}YPv}*(syiZQkG1lIUsWr=Te+WFxA?WH zFI?@nR%TYUZtYm>95q4t>R2}h%DOvnY~iN&(e_L7y%%LP_s;fX!hVj?@5D)em3fD= zaq+l&(E)FYKasw8DsmRq7;znNfGxOKb5KQw!&=S*vdo|p(2;jd+qMK;x!9$FO3h=Z%z0RiZw1=8L2=!M@@Aq3xANIW_ zaypEme9D2?%57Ql4G-FH4I%+BoJin(6CETS_F6INV_^`pLBi$hP(2OQdwn1B)BMTv zNi!x7$5Fb&(@ zpJ^AOv;7L=29QspiG=9}AnP=N0$JY%od~ZO7#S8|@)TwDV z{phqim0XUR+QZt|yb}2&5=a)7UR}^JWm79bk{C1hWomQ$hm*yov7S^Yo?3`1SVQX! z?xMfv_IoYxDEa6ZzV_B{&M{bP&Lh#}+?AA5!-r+=~hf5Ja7P z4C@}F%+I>Gyq6=qtqOOqdJcv26Ga)ajh^E%mx8PdE(yM&76P3{7KEsfKxql5K=5S_B!Llip@O5F6&t%&@ItTCkT1LIAvk@Fl<={NCN4Ldt=z$2>^zZGN_TGnyOr5 z#XXeTF~HqpyKS(g^{H4Z{CSy(ZRumN9hSQ#?BaxxzO}Y}pu2aF`@*0eiuF7qn^N2z za8>lRYr8Vt?FZ4d7sBpl;9$3{?FYt?y91Nm$5MBO+zT_F9;!7tzm7=G{ zKWzlK8lYzIrU7S3*KmjX0wZ&ZVP}DEz}DfduW5M0^_?R+hvgujPdU{tA)0xEwPJ|~ zO=VLZX=bbbW-MrAtG0{65ZZ1Rl^6W-D_T)m#8@g{dYvGI4qpc5*Nl~21)0Q-nmP}J z*mN^a8$LV>WL`mCa^`xVHdxjq1`#ve%kUIs%VNfcbiYl}2As*ZmKU5OYv)erYXQgeL5pVV|XK1+sX%zeRsjl7hMS zK}lc;zl%Hx5&{SsDI4wDtzk&?{7A8yvsU>~t-S0+=FueBP`Qvfpauyex<@jpXuSO^ z8BULr5~D2W02ni4=1PRfM}<*yp-4RBObKLKDv-+8umviP^T;op2xU_V~IOmq6NqX?F~H3Lb&I2 zH0|!iraOP{xFo#zh-VE-J}?AF?7zc@!K$R^6&yXKu#qE1Cu$!B#97f3IM8|%2F72F z7WlXL@5LV=+a&~q8AJ$RHPL`fGAq>XoP>m^J&#xlBB^=~MHocjGi*0^M~_EO)VdKl zfSJJ{P>S0N?9up<)=Iq9*vZkVkGYU@Yw-yv&wA`1a{;j!WQ0)iA0=f`D#nuVU;yN3 z10hNn5X{pKd^gL@gFnKXrR#Up+W_Ssu}^-Iph>cHt&+;KS_*l>%+bGC3$1Bb3LTpoxcU6T6m02K#v+Y$ro0 z%LADO4ZXqyTpw~k3Mcnoh7&-RfYJI`1K^Qr+~R5YQ$p(*+&ux%Oj!p*dfUg}z~2NG1?B_Vp1V!mqT0vQof2)MWoQ*S?Nt?qP${+Sh1s;WUb+&EG7trlEN2P*cI~9Y zq!BGMFu~ka)7tv1f$4T-T!=mP;E*xE#Fx5pvJTXBA)E+t;VU@N#I-tczmj6OUo-@Tqykiyu#P8OI~S;L@52bi87_Z(&HWA zi6_I1;k2mr3yiRmP&j*F;nFqI3Wk1X8dz)0b1dn|nSen<5{z0IoYskavJM9@ZbPTp^#r<`$wn_yA$q%e+-NMARm z(a>UVf)lS$gb0?X$VSgl(9E{5DMyf4_UcH$t=)oNKL$5*OH>^kB2o+olaNGu$G!8@yn7yJU5tECTC<`S_SX;v z%K7a=@gXcJtpOE{vE*Zfz-;?l-~o!yw(Ig*jQyohIGEEyoF&8d(m}gi*Fvv!D&(bu zg}Q}L#7hzZfluo<_8)xsj_cSfs`ARh09y2tZ(JxvE+Ca)soECHdj{9jM95p(4aFXU zU)o{uifRLp1wLfVgm=OvMq4<9&?StK>~4dYuo)qm?r?V_jy@1^uE=0>?k6;qpGI^i zr{nR@xOM-{r?WwND6A<%4TG~dYSM65*pw}q;yF+PptS>-4{X)|;!r_U&y}bapCGz6 zjKa2IUxOt_J<9&rBcAw5h2>(%b__sClFq+lM%|aG%K4NRVTpJrXwqhijB9`B75_gD zFpGn*Nb!eQd}6S4sb4r>HT{WK_P=O)MA_{Fy|P!5Iw86w&~#gS)7#DHiC{*^3rwK! z>tKU~{XWKzw=1A&gKI&{z(2EwD=0tpT9N}hgHZk))%FZ!A*Z2ds^1+Eq|E9KJZ318 zMEIi$oaf8{uRN77fQ3RSiFPt!H)?W;@1-EWM>Ly#GI=7%By71RY`GjH#~y%Y#B6KQ zY#Y!bh5HC7S*C@?fjHv~V}PNJ%bx27&BJ$t`aucH4LO-o;&`=Gt;~cdi9vNlK6XdB zFKqRt2;dlJf?YH0zotg_TW)*L<)~8!X_C{vdZEgkjj3gC&qfbL=r5GfqC{lc&q)9c zq)z$elV7LF`ZDc1)Iu}uE#c@%q}mXuRCtO|^cQZxT8-K-KwHLYvVR-GuT*~$s&_Za z0#g&01_J{gft37P*7?KqEebfU68X?(VDF45;`! zHvpfwKf!*;!aO-*yAZ>LRM5TQf>v&Ni~=~0zVsTw)k-BX7S()d7J6GNYMkp}l>&kA zm5xL-rxRWhZ|p}gTA~rz>wK%45DRo)7C9A4?2pE{f|9;~orZW2fpNwo85j(VQo9|A zUFgDsNaH(jpp&GtJSdNY3BpI&o)plMtyQdKy$PtHu|k}Z<+R7}wkq4(0ff5?>(BK* z_=yuA)g}SnFo@9WDUPlZ{!W?4aL0R*i&!0#P=S1;w9v3B_@4~CXuBplgUpB_-6vSi zyK7QJ5XRl_BhhjGCIc(2xSn~E9!!n36Z3T zK(uD!hhh|>($?SFCZkV&DMulahXnChsfFc+Ofo*+q3ZIqA{KG~N3D)tm6A1ig>2`s zf0-iYAH%)VTn@LL{X{s*#fJDv{0KUzweAU6!f7csL-^Z!2NoQ<1&lzP&O`X5)mThJ z2kJKfYz%x8}5hD@;l7H2^>HoCDy59HoKI> zWgK`wgnG^=(Hu1Bh{sjN%G#zY)m+VcZyaby;i!uZ{BuAg(Mv|mGhxv6-rBxv_sn~>PkfJa`H&%3n51Mk^1yIH4VwjVcQa{h91KqIf-+-7O z*AY2-n8y=TORb277JDc0(rp!2f?@xgYZigkAWvEC`DA(6y9DLz>5`o}yy5J=rfk2W zV`beafn4od#VI=g`EE#%aBGv2MMwFdV^}dFf`UYKIL)8H_N!z0m_iTE!1GpyI?b8b z1rC9cW*waWYy)2~cZ{u<0e~w`#i!bt#ooaanDHNo_X@89G7d{)Br7-u8Ni?9^YO?E z^9@W&*w#*a6VxTXM*aAokHR1hIO~@usmAcF#I)#OdCgb{X5pM3h9{6m#aQb_W)lwt z3-}LM)(UN&VyAzQ5{*)+a&PswmVjGILacNkH%MV@RIZJ|T$1o}ZIoQY*qD$DPk4vW zJ{<>!0NVgrm!3*LG1fi5#9a0Ka^U`64&C3&VQOrFSD_xr-bjwh9-z9YZ5(3S@tJ0q z)mt9oO{Gr1W!it+*6FRJ{kL$}b|@=p|1CVuV%yg>OWJ?C6K9zO+m*Ec7QWdIZzb)& z-Qjr+bj_0X-@Y)YBUF<1-@;ei0cJ`2Z#Q@9k)-{%@O5^8O49yYcwIZZm9+nMd#AFJ z_TR#(+yN>{`)}b*@9MB7RTmvfJ)N-TljoCyp^>7_Csum56+RK{kNMs zK_zMbE&SabV3xH1c6+C?lJ?*3=+v&H16IHahPd0T>p;&BHuB49#P8Yv`_??g`RxEn5dtZCv)yAZY@_?hmlkby{% z#WIKr#lAZHPnMKSQA4lfUvVpJl< zB4IFi>?lIeb;p=I_k{8eGLbD1w;wy;31#Gd_B4$?Zb%rn{Sr778zrES?R-f+iYG)d zay<_y^g~7ge9esxLK&QK1we7LqFK5_)xAB7Mn;3-v5zzd3LrfSOXR$dpRvh4sh?W@OdK$kx#h0&4#y1`lYBWRPjAN{f0?P!X(8}q$tOyKVSq0m28A}-f`%O9rIkZR~9jeGQ zAi$_bF*rYjsN*NL2NRDY!BRMu!!aSj7u0R`DB)nW=>xNWy1SH69izcK*8m8P^$;4fO~-J^fw!nQ+i-6H4hG>(XKCbWY0ol>_ zzgIBGfG>*50Dl-(zjwjLE}aPTAgX8z?(M>88h*GrUa!{;rpkS1idQN{vY>lMOBOk! zU+w_3o#EdNRBwftvrfqG)6t%e4xTQxL4lNP^n)4l1nPiS*ZeffuLA9(VK zEBNZsJNCV}`^(?B>a1wUFy)F~^J_B=Ypk`+h~DMK5{YXL%0(vpIjs)FKJ)%fuf3JVGfOAD%s<0Y}8 z@%d%ri}LDfmo&$k*W|_O>Kd!^63tb4RgHCZ@hUcv$Xk+Zj>q#F8mr^E%?XaaaULf7 zV#_en@!J)ac=ghF?y`98(q&0!(j+GludC6?aZZ6Qf0b8Wr?2g?KERV(7jMWJ!`iA7 zNiX5S@p_73L;#1b=d7?{3<;!f^3uJ7Q+nya4qyZIYz?XYR$-9h)z0F1O&p!_@|K3x z&9SDOF^e5cN6c9~x3M9<*jW{;Yl$C{X^5?=T^dWuR2cWbTTuxZ=V0sx;P*cXwy6k! zCu~pPIue(Oi_!J`%Ujl1AJ1DFThiQEwJcs2kG16GR>hi^Hs&?Qm)0hdKt&?q(%R&* zmL<7WjrHRS;#F0}g=J;cOUmL^rG-U6(1t`~UA!{h+}zk)*%WK2t*Tts*tlX`er`c- zi4+G`17X_$M;+S!2Cjj)FjVqKcrc{~&_nP?tt2Pe=+xBK)j6w|p>}6UOHEC@*;s@C z{^7`X9Ik_%)I_pcpsH=mtF3RUOK>e+ih55&ojCx81eCB59$r9eBNf-@FsB-{#F~bx zyvBwqL9U~)uVD__zaG~ZTn&v*ya6-g)HJp`u0xg`;iWo{~prWxDvG(XeYTW?!+%j#v2l(=Br}~r=c!m+_*i?9Qx$7qwq>zZaVOTWhWS_3Hnt=b*-04BMih97p}W0q>6qq*o&C1ky>QhXvBNB7IaK{WQ{CE4scmq{jx*KSjDQkbV*A zlLP6OktWXR@~dOq=Vyg0@Cb{LSScEE#@|{tg)r8+F25J7B2)fTmv8FtsWuzp3NwbZ`fT;zYbpo*UW}ps3e%yglMC&Ot1^Gn1XA(W1Be!7iPhH)=`9sIt4U)DorsJNM7?7=gR z>%+K6yJ{PnT0kij;V2%jn!t0NuLSpmjg$ryP}2_JT|-M<9Z3rxHv$z!W>F~WC8`8P zNouM=keX{_b+rJNH3^nNC|jCb<}}u@h6G_43yzs|+Mk0kIYPfZ%EUZ?<*E$Z;Mjc= zzoQjw$VloJYjA|4I|4^-!%}Gtd?1;%KVWn#%y{To640ilA%0O41Y}5pAm7qQkl%)u z`X%vZ$5{u>n)LN{{BjMA#4mZyGF;>x`;#@=koQo?8oN+$P}VpU!<4{~2y_RVy zdR11%>*~f8!qJ#JQL=tv1nA7efaiX2$D5 z=!AVZ&GBSQGe8ckF&~NR)(F2xalzM7_&r!Tw_S&X9FGOeQLi4TaBmj|oMWDmu`n5{ zT2Y>XzpirH=cmi=23+R>o`cn8b$PAJ>U3XqSzS)+vO2AGS)Kl@x~#58>#|+P9z+(h z$z&(r(0MP3s5$GJ7d)e~nOc4&2)=U?_#DeloaCD!{5q~g9pwMr*HIU*QB_Qyr0Vd? zt@p}7UWzw2#Of4pD5oM4Zw4l}G&IL!Rm);apsH2DKA~a8_Yj26c5RQ4vUi5x#u5-{ z;^Y@BlQabN`YQ%^)a!LR19Bw!M>lj-X=RCIydLilKTOF8J=3H^tDWVb{ndcuT#Rit zesy}Wm+oGd?86M8PW~N8F8v((uOxI&Da|=t@fPYb4lbQ`aX&4Po`HMnSNi=d+#erE z&+e2y73sXd`}w#(F_2z>d*X$z?=0Mp#nl!6tK&_O*s@@i1EzR}G_bdpQorn_C`Vqc z5URcrf?0AI#4>QZc9sZ{j}=s|hJ~iGU4M;HP-FvRVfBlbJJmblk%_!{dlc`2pG4i|KISLmrhX;4ka;I66OGM~Gb@*@ftf40tlbm}PAg9m>JjSE z^*-U%tJAL{eO%!ETHI#^(${x-{~Xekg;ph%ZG-wC#14_w59ylfMqs;iNJn|?khZUs ztaeSKUFm_qCk7eV2V+{3b7IZSu{9W%PXJ>Te!1?cn{li;Uc9)j`#{+~yV%u@=|0F? zes@$yyR1%s18`C&`|JQD>S~n<(KvsoZIE_FFc9PeI-P|y*S}8ZAx(Oy)1^og|8#mH z(p<+nJq2mX6FNN=X~Iq$kd}#aa6hKgGt$hHaP>vLuNe0E$oEOK6O`}2V4Ue-BN=pb zXB$aVtg1G-#%Tlvtf^}xuWv;j@{P}qKdY!jLX)hlX>P2qtc9po*#Pw|UM&jTt!Rhi z#I@Ex`0BSOjEt>h4!4a(sGqhI;#to$r`Nh&ta-(_ni_Z~iafoprZ!$zoye<)*jgt) zXtxRNY8!#cb`R2zrw#f+U&AicO3d|NZ8R$DaSStA6&^x|6OiThx5h5&s<-yziXbfAgDJ zsRxn|$Dxj{yuG$zxw7mWf4q~my0Li$5HPt6nnrbQZK9G^vz#&gfDvFFjSb_fAYf75 zhuccjeT`6p>+41;`^o=9e@QQu_pD$1XWh5Hypb+55M#w77NboES08XdXYGyoRBZTK zwxE1_$VRm;maJMPJu}`!--tsYj4Anr?(0h2<}!`Hx8Gfr^Zk^6Cs&4tC5@}D-)F2^ zW?8FV$y`vm^rhEp|3y=aQMIh4VMRgq57~5^`4IU&kglmcm3>ZRH05mZn1~5^^{7*CwH=A8|MNp(wB6EHa&xAg-4E! z!2(y`l%#95x^`7U1U2w4(9vKvY7bbT7OW zy{fP0ZgEhIitIVAq_k{Faan#@VF{e!MFj7xVEV>z7o+KV!NRM9eN$sj3}7M7fZsmyd(|>vvM_v^3?N~ zX@V*1WPcF@J`4;I$j_Z11r#uqG?mKueVA<uJ{@!c+jiW#3Xti)PqMvG{%9PJzC)*Ey)~5 zy@q-i43P5ue@L9 z0CwVnPLoqi?UW9_=X`K|==@7?AADccDSa{0g7030R&Q{P^L?ms&07-2`6s9~F&k}C z+G&@-yG9(nfV{(zhn9rTN*{t+z#h~~$yrAn>9p1g@alm4@K7KPWr#|84TX9@o6J!- z&-te0+XtK$y4o|0z2+C)dgH(-V8>Hp*yu5~L7cn`7x7M~AHY3n7&TN)Lnh+BA7EMn zBEMpsib?|;E9>AR+zxmaqn-7*2#-!*g?nmpI(;qfiCa3&HNbJ!Y1d0H{h##ZUw&xm zkD5Pdm{|VNyt}SgFz}3x%Oc-;;5S!}8us%Y=YgR+^$b6fzybJ3TN2B_TOj5!IIpE1 zfpf-&6K%2l^h4h}i=E`>I!;x>0ofu6=3{7c1^RF~E{;Q283>l-IO1g|F;cc3Uqt4MzB4heqNS9m=Bs$Zz%6bEINWPMK zAj^2KY0iGsnRYADX0#i$5}D{1Ed!sP_vt?C&L1y~ym?1@+M8uXvo5=B%))#An&=Dd zoQ%4H{18dwQ`F5itXU!BpFdpxrH?vAf8kXtvRtum+`cQ!U;g1fYxis6@a|iO-TBlj zH{SQ(AD8uv+a1NsB{3|^=GC#KFpI>htMZpDsV-SEzN%nJ5tN+jqT<3Og=HeNBN~*R z5nzP2&#MRr#ZZlHxyq|Q27r-&(jxpJ0Q_Ly?tRB@duR74&P|EM&JSvq{4w^ypWisP zaXQ}5z|~*!{Y=!wKaLsYmxIN*p5*&&c^&0@KN1O~Hwjfl?0JYXV<^^A#BSO7wygVM z_$43cc|LlLr~9SF+DuV__NYr$gg9%)wSRk&t~Hadl!9Wvm4Z_8E}3~ zVYp~{IZe;rP9J2z-|`s)D>q*E{l|B|F>Gk-oZ;tHAM@P>@05R(aml0qJM6a)esJD_ z?BWNa=)H}%=?65}uN`{Rcx8v)fj&F9bb7Iu?gMAjS$m@t%3NzMNJ-<6^)q0XzG#Bd zc(h$w_cdsj?n*uDYpW^8${s7oZ#iSSX4$Xs+HXB;HwUvlNZ|{Lm8A9@#*b2`PX8YF z$KvV=XqUbm0^m9t(%%D&m4M~q5Y{$+KbrZ#yKkRx{O()tH|NdzL)GMG>u+3m^7(^4 z{rRhpJ{0}h%5md={EeZL&&i*A+EWh~UZ1Ex^23(D{Cq_8z3HRg{bh;q(xuD$Q4;Hi zzLUbns{$^^`b2%+su;q!5idvUjO_FIONpYd=cE58;nL|-aL-s7{^kGlJ2Qv>^{sn< z`trmF&rKOoy7!DLhrYdF-@=Oj9rVqr>vD_x3D^VrM>m&t{_ab^{^+sal|H`v zitk+d&x-#$KhpM4{kQMGZ1<^;{Qm3jW*lfm9|%MH2_HI=Clv~jT#X%|hciY1_knfUe&|F%_J>cN7A@@(q-GZ!kS*nIfRk(E&eb>xxs7-S3(Z%uV{?gGILCZhl`JvF!7oXBmNAD`_By*R> zlR0B@;R-!Ihw__)INTFyIZZm1V?L(<6XWlIlXG(u)>HSkPYtIZ`r?9s|8&%$*!u0E`Z&kA0LUDw}oJMOyv5@6Aq2<@I4uHL9I)aP~C&r6G$ zHl?E$(+_m5dksT=MOSVD?GTqYw=T+lt=e`HlDi!+66f#3m4QpA zpTRwC#ODQ`Edx%|cBS(qk){t;r?2qd_hnuvX@B1H^43%}FMH57`KC_4k9%r9{V-4M zd=EQv8XDqt;6M|g8*uaL>KKCP$bA-1vyG)_k6JFZjlPWA1<0?*jS-+IEZg9l<5exq ziP}|hY|e{St!$}9;7EN-GJX-V964R>r2XuRG=WY)f6C3xYk_mCsY!zJ8FaA%(VJ{d zm}>>vqs^0eM6I_k{i7zT`^WX#m1i_~afk`({2m9cJl*|LKg?eDt1M9$k2mSKvtwo` znPrOCH=Vu>X z2h!*msKBk2`Jb6I^b`Q`J-|RM;7DAQF6waUv90fvCTE-7s5ETOhtAi8`!oHG|1%J+ zmzdpv2%g&NX|ZH%ZewzKLu1R*WeXAQ5}z4w0DY-)xy`M-+~1A^+EEpUyoCS~&lR5# zXS45!zI3kgh~9?Ze)2dkRiKQMEq9PC3&wk*r9KzsS+unTeX9ookS;93$tDtvlvoB5 zxq@LyF{ciPj3FFX$MTfUQ|FHy?1Z&Pg*3-j<75jG@@mw{^>;tu;2c%eH6}=_Ya3QI zt^haP96vU6eBR;+J@RIW9eqSKz)C*IHcbaDhzA zyDK}T*CWkmqvzYkm+|`*Tvy@Rfa_{py&0#)C_5T?`*WN&qR!wrU4!~{->${|MqKPW zpIwLRtGGzh^s`O4{~9h`=Igk>5!W|xc|++eX@vP{b-bGS_{TB4N$xt9y&2Dfb!|qv z5A<7S?S)eARxx;7R;3?4*WA+3KwhtuSDd56Q@=!yO;j~Dk+=Nj3|q7`;JTdC*eNnZ zvdO-+1xni~LK#{%u-BN8^~hdcuBt3Ahpxn3)(B(s&J1dTxw4igD5y|%$P8ZdCnOw+ zDR*${T1?bwQKfy1?ExCk&TAYirhE zOmRjN4@Go%D568s4e}>XFlHLpa!>~&<2twD=OaBs9?FuW8*E3XCu3d*1=5qebZ{K0 z338nE9NdokEx5FG?+)C52Ujr9cagr+dxjsG)4T9{H!fYzJ-ELY7t8UF^L`($Gut)u z4)c3I-c1EM;hZ(vqdUBNK+17j#L~uQnwaNNYM$4UoL6(YOjkRzoZeV7n0d2AR?;i-$yjVHOukqyj31fBv#H8rQPv1tK7 zo!3yerX7H3FtAi%yUQ913)tATW_H32K5I{$&GW;iu%n(OfBHojti+UNaKeW9wN1M1 zX^pEJ-0JFPROqgPZo}>{J-gw|L>v#NHo~67!?wH|oQCIuw&Q^Z{F&_s!OUt*Bm>Fz z5*XRIt_c+^h}ZBahWYX4`dSdc#)fI}21pEOZ3bg>KzOke36H6bk!IJl)U`irfL+ly z0`cHiXln?7MmPtyE!7*>o~`KhXy6ts-PLfq<8-syq?o?_a@bXcd`BZs=kh)W3X}aC$nm6hXv}m8298avtkJt8@S?pvDn|%zu2oIuaAfG;>5ININNu10|oWM zT;+s>h^#G%>rwAKv`r614z7oAX*up;+;7A6NT=tIBCY49JGooS^gi&&pL3qFO=RMi zJS+vs6Txqc$JZkEF)6IDnJe=^houROC@Q>$bI}!+ntECYHlwYfcwddSh;L(pLH68m z;N*Z*jA;gMf!2lj;#LGcLf$t}mUGmGU#{t2$uG?>E66Xv z$?gR_N_j#-aY0EzX+ar}SS&0oEGisdh(nhPOA1R1%Zl=g3W^Ggii*Y;O(-fZDk&;0 zDjT0azF>SI&Sb{<@Z*cemy9nRUp66sLcxT>2}KjePna;FctXj9(g|h7`Najrg~dh1 zn;uX=!O0 z_AUdUGBj3(ipr3wtW1rYUi<%r{`~~kv$(n)52agRD{Z_cR+otHy-t(vvF{?k+=q((mBz9fjbIw-nSH9(*m8aCKls@C4K*)InTY zhu>_!4(_l~H9UsrTn+lYDzDywY&R!i}7fVZ4qq?8E=zm->zp3%#bjcEMSz z8*xl9#SvK_Vos=S@B|m0!=pjWNn5P+ijnxGE^@ZF?#|22&Aot|!aZ2I!aJ;e_H!|C z59OjL*MwifrPsm|oY>w#e-yi$!<#WUO9YgHog{rl*)1qD5M>_1FX#GE{Bk^W9aCVp;BASYk~<$c%+CFMjgr>$2M`1ujn*oIr1~Z3`HU-DOPGqT59^> zj1kdcnMV(>2WAcm+o8h_J3M`ec~p3)IV^N^>Tq*}HEM_xIw>>`AD7Gz6GQ9`%Bku+%)3%0|%Wp=U<=X=ACl>1(knVf8AGa z_}YDsJn^IFo`3PBKkWSTJ|jH%urUSWOUh54H2c&G)_)Z*AA90Q&%gA_t2_UU@460@ zx8)~KpE3K?3#;SnZ}`@2FTV2XfWc$%X!hCXoqu6vb^N*;?n9C1Ui{3(> zcgnfx`s#0gH*52cfByU{ug;!3@9c9fti1ZG*FW-u zA3pue^FMuK@Q|ap|ntH$O?r*nc+w%nqm$- zGBP)Hci^(oe~YTZpEv-g408-ccx7Y4GK+6EloQq zvTontp=o)8$Az*6WesZG7+!btu;`H+Zj9tbPENH34oz=;Dm$6k`s<@JBdz-)t$)mX z|F%#``r7l4Xx*OH`m;#J(33+MDWz#M(lS$$(PKjAhR;cFy>jS?j3McB!mZb&JaA{` zQQ?9+!fStfd}?MS(t5YO_M=qOIWYxquM4+66&fBIG{8tPO?1wRq^4SFY3Wu*Bx(%| z+vZ^Fu*l(qk1&t4jTxxu*8|uMKXjeEM(iox9}2PxdWbbj!DL^G?V)^R_K_eD}_K?tSoyC!fXl zZH^pSe#-PS?!M>cpKnPWc69b}lTUef_j~)Edp_(Od))CkMWyAlPMtG<;hAR<_ZC;h zYgQyKy6lQ;?!5ouZEdeT^l(GtQ(wO@`;tf~JT6odGV^j<*NqGn3>pz0oqkN@q{y`J zz!O{VPZ=E^9nMJ`ADugOZAto&jI^OAPcI8qrKRT&iDZR_N6d=S@M)2}a7Jo+YK3z` zI5WLCR314xHJq6`e|AaHfTGmgw2ZaKFF5U_v=fIMef)?cj!K`42Br-dmYR_=EA52z zmgtnpC#IYn$w)aP#f;dYNb9vrj+vE~(R%lV+0&yLDFY5KPsu1A8$PP_@rl(7GiRk| z%$PoWR@%Y=vr{u#Kc0~>GBk5`NoZhNMoL*~#@gaxsV9d&jGV~f>$aU2KP_CAI=F%;ar54^^}jwb{X6fj zEgEEwOc@wXTf6b<@QTQQPl$} z${Yz;iqi%nYkF$y&yLGjpVA`@_h@+~t(hR>`Ae0Kem2PUWL%Wrv>fsr?iV$$VQ}q2 zGEotW78<|JT4d~a`^<;_6gzwO&a87@+jYk|j`6p1_Lx_kYc%0FO?=+j zwD2E3Etr3#ZN@>lEXxcZYaTQFyl8oPx;Zp#remQ;P6|y-J8`J#;G6PhI1LmkHNzTd z;+x;56;4Bz4C`pqvdTaa!WJl^d5je@qfAGT!92nm0(ybms5{L}4P{uzm?xubCQ9a@ zdej<LK8G)o4W zXfYBsN2i-LVKW6lS;MSw$PN#{ZHhU_#Mp;MTF2nO3d>AQGp%U42|8`ISlQ;PP}oX0 zQ$oMPAOPA_)@Y@rWLReYsDf}l(h)N!J=1c~Ni$T6mnax2PqVDeA#;G4$_7K$^A(2q zlPn{2ow?XCQfe(DY-Tvtdy@l_3r#2F1M145b|@BWs#N%z>$)$WPMPnWH$Wm@DQp zt-k=a6#Ndi&P?Nz()4=SGk3@K9rVKK`ybSY{@ELe+ + + /** + * A `Uint8Array` for the root of a previously stored trie + */ + root?: Uint8Array + + /** + * Store the root inside the database after every `write` operation + */ + useRootPersistence?: boolean + + /** + * LRU cache for trie nodes to allow for faster node retrieval. + * + * Default: 0 (deactivated) + */ + cacheSize?: number +} + +export type VerkleTrieOptsWithDefaults = VerkleTrieOpts & { + useRootPersistence: boolean + cacheSize: number +} + +export interface CheckpointDBOpts { + /** + * A database instance. + */ + db: DB + + /** + * Cache size (default: 0) + */ + cacheSize?: number +} + +export type Checkpoint = { + // We cannot use a Uint8Array => Uint8Array map directly. If you create two Uint8Arrays with the same internal value, + // then when setting a value on the Map, it actually creates two indices. + keyValueMap: Map + root: Uint8Array +} + +export type FoundNodeFunction = ( + nodeRef: Uint8Array, + node: VerkleNode | null, + key: Uint8Array, + walkController: WalkController +) => void + +export const ROOT_DB_KEY = utf8ToBytes('__root__') diff --git a/packages/verkle/src/util/bytes.ts b/packages/verkle/src/util/bytes.ts new file mode 100644 index 0000000000..9a5b83da93 --- /dev/null +++ b/packages/verkle/src/util/bytes.ts @@ -0,0 +1,25 @@ +/** + * Compares two byte arrays and returns the count of consecutively matching items from the start. + * + * @function + * @param {Uint8Array} bytes1 - The first Uint8Array to compare. + * @param {Uint8Array} bytes2 - The second Uint8Array to compare. + * @returns {number} The count of consecutively matching items from the start. + */ +export function matchingBytesLength(bytes1: Uint8Array, bytes2: Uint8Array): number { + let count = 0 + + // The minimum length of both arrays + const minLength = Math.min(bytes1.length, bytes2.length) + + for (let i = 0; i < minLength; i++) { + if (bytes1[i] === bytes2[i]) { + count++ + } else { + // Stop counting as soon as a mismatch is found + break + } + } + + return count +} diff --git a/packages/verkle/src/util/crypto.ts b/packages/verkle/src/util/crypto.ts new file mode 100644 index 0000000000..8143765b7b --- /dev/null +++ b/packages/verkle/src/util/crypto.ts @@ -0,0 +1,40 @@ +import { type Address, concatBytes, int32ToBytes, setLengthLeft, toBytes } from '@ethereumjs/util' + +import * as rustVerkleWasm from '../rust-verkle-wasm/rust_verkle_wasm.js' + +import type { Point } from '../types.js' + +export function pedersenHash(input: Uint8Array): Uint8Array { + const pedersenHash = rustVerkleWasm.pedersen_hash(input) + + if (pedersenHash === null) { + throw new Error( + 'pedersenHash: Wrong pedersenHash input. This might happen if length is not correct.' + ) + } + + return pedersenHash +} + +/** + * @dev Returns the tree key for a given address, tree index, and sub index. + * @dev Assumes that the verkle node width = 256 + * @param address The address to generate the tree key for. + * @param treeIndex The index of the tree to generate the key for. + * @param subIndex The sub index of the tree to generate the key for. + * @return The tree key as a Uint8Array. + */ +export function getTreeKey(address: Address, treeIndex: number, subIndex: number): Uint8Array { + const address32 = setLengthLeft(address.toBytes(), 32) + + const treeIndexB = int32ToBytes(treeIndex, true) + + const input = concatBytes(address32, treeIndexB) + + const treeKey = concatBytes(pedersenHash(input).slice(0, 31), toBytes(subIndex)) + + return treeKey +} + +// TODO: Replace this by the actual value of Point().Identity() from the Go code. +export const POINT_IDENTITY = new Uint8Array(32).fill(0) as unknown as Point diff --git a/packages/verkle/src/util/index.ts b/packages/verkle/src/util/index.ts new file mode 100644 index 0000000000..972e26b441 --- /dev/null +++ b/packages/verkle/src/util/index.ts @@ -0,0 +1,4 @@ +export * from './bytes.js' +export * from './crypto.js' +export * from './tasks.js' +export * from './walkController.js' diff --git a/packages/verkle/src/util/tasks.ts b/packages/verkle/src/util/tasks.ts new file mode 100644 index 0000000000..26dc56a96a --- /dev/null +++ b/packages/verkle/src/util/tasks.ts @@ -0,0 +1,59 @@ +interface Task { + priority: number + fn: Function +} + +export class PrioritizedTaskExecutor { + /** The maximum size of the pool */ + private maxPoolSize: number + /** The current size of the pool */ + private currentPoolSize: number + /** The task queue */ + private queue: Task[] + + /** + * Executes tasks up to maxPoolSize at a time, other items are put in a priority queue. + * @class PrioritizedTaskExecutor + * @private + * @param maxPoolSize The maximum size of the pool + */ + constructor(maxPoolSize: number) { + this.maxPoolSize = maxPoolSize + this.currentPoolSize = 0 + this.queue = [] + } + + /** + * Executes the task or queues it if no spots are available. + * When a task is added, check if there are spots left in the pool. + * If a spot is available, claim that spot and give back the spot once the asynchronous task has been resolved. + * When no spots are available, add the task to the task queue. The task will be executed at some point when another task has been resolved. + * @private + * @param priority The priority of the task + * @param fn The function that accepts the callback, which must be called upon the task completion. + */ + executeOrQueue(priority: number, fn: Function) { + if (this.currentPoolSize < this.maxPoolSize) { + this.currentPoolSize++ + fn(() => { + this.currentPoolSize-- + if (this.queue.length > 0) { + this.queue.sort((a, b) => b.priority - a.priority) + const item = this.queue.shift() + this.executeOrQueue(item!.priority, item!.fn) + } + }) + } else { + this.queue.push({ priority, fn }) + } + } + + /** + * Checks if the taskExecutor is finished. + * @private + * @returns Returns `true` if the taskExecutor is finished, otherwise returns `false`. + */ + finished(): boolean { + return this.currentPoolSize === 0 + } +} diff --git a/packages/verkle/src/util/walkController.ts b/packages/verkle/src/util/walkController.ts new file mode 100644 index 0000000000..1a8bbdbc4f --- /dev/null +++ b/packages/verkle/src/util/walkController.ts @@ -0,0 +1,145 @@ +import { InternalNode, LeafNode } from '../node/index.js' + +import { PrioritizedTaskExecutor } from './tasks.js' + +import type { VerkleNode } from '../node/types.js' +import type { FoundNodeFunction } from '../types.js' +import type { VerkleTrie } from '../verkleTrie.js' + +/** + * WalkController is an interface to control how the trie is being traversed. + */ +export class WalkController { + readonly onNode: FoundNodeFunction + readonly taskExecutor: PrioritizedTaskExecutor + readonly trie: VerkleTrie + private resolve: Function + private reject: Function + + /** + * Creates a new WalkController + * @param onNode - The `FoundNodeFunction` to call if a node is found. + * @param trie - The `VerkleTrie` to walk on. + * @param poolSize - The size of the task queue. + */ + private constructor(onNode: FoundNodeFunction, trie: VerkleTrie, poolSize: number) { + this.onNode = onNode + this.taskExecutor = new PrioritizedTaskExecutor(poolSize) + this.trie = trie + this.resolve = () => {} + this.reject = () => {} + } + + /** + * Async function to create and start a new walk over a trie. + * @param onNode - The `FoundNodeFunction to call if a node is found. + * @param trie - The trie to walk on. + * @param root - The root key to walk on. + * @param poolSize - Task execution pool size to prevent OOM errors. Defaults to 500. + */ + static async newWalk( + onNode: FoundNodeFunction, + trie: VerkleTrie, + root: Uint8Array, + poolSize?: number + ): Promise { + const strategy = new WalkController(onNode, trie, poolSize ?? 500) + await strategy.startWalk(root) + } + + private async startWalk(root: Uint8Array): Promise { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + this.resolve = resolve + this.reject = reject + let node + try { + node = await this.trie.lookupNode(root) + } catch (error) { + return this.reject(error) + } + this.processNode(root, node, new Uint8Array(0)) + }) + } + + /** + * Run all children of a node. Priority of these nodes are the key length of the children. + * @param node - Node to retrieve all children from of and call onNode on. + * @param key - The current `key` which would yield the `node` when trying to get this node with a `get` operation. + */ + allChildren(node: VerkleNode, key: Uint8Array = new Uint8Array()) { + if (node instanceof LeafNode) { + return + } + + const children = node.children.map((nodeRef, index) => ({ + keyExtension: index, + nodeRef, + })) + + for (const child of children) { + if (child.nodeRef !== null) { + const childKey = new Uint8Array([...key, child.keyExtension]) + this.pushNodeToQueue(child.nodeRef.hash(), childKey) + } + } + } + + /** + * Push a node to the queue. If the queue has places left for tasks, the node is executed immediately, otherwise it is queued. + * @param nodeRef - Push a node reference to the event queue. This reference is a 32-byte keccak hash of the value corresponding to the `key`. + * @param key - The current key. + * @param priority - Optional priority, defaults to key length + */ + pushNodeToQueue(nodeRef: Uint8Array, key: Uint8Array = new Uint8Array(0), priority?: number) { + this.taskExecutor.executeOrQueue( + priority ?? key.length, + async (taskFinishedCallback: Function) => { + let childNode + try { + childNode = await this.trie.lookupNode(nodeRef) + } catch (error: any) { + return this.reject(error) + } + taskFinishedCallback() // this marks the current task as finished. If there are any tasks left in the queue, this will immediately execute the first task. + this.processNode(nodeRef, childNode as VerkleNode, key) + } + ) + } + + /** + * Push the child of an internal node to the event queue. + * @param node - The node to select a children from. Should be an InternalNode. + * @param key - The current key which leads to the corresponding node. + * @param childIndex - The child index to add to the event queue. + * @param priority - Optional priority of the event, defaults to the total key length. + */ + pushChildrenAtIndex( + node: InternalNode, + key: Uint8Array = new Uint8Array(0), + childIndex: number, + priority?: number + ) { + if (!(node instanceof InternalNode)) { + throw new Error('Expected internal node') + } + const childRef = node.getChildren(childIndex) + if (!childRef) { + throw new Error('Could not get node at childIndex') + } + const childKey = new Uint8Array([...key, childIndex]) + this.pushNodeToQueue(childRef.hash(), childKey, priority ?? childKey.length) + } + + private processNode( + nodeRef: Uint8Array, + node: VerkleNode | null, + key: Uint8Array = new Uint8Array(0) + ) { + this.onNode(nodeRef, node, key, this) + if (this.taskExecutor.finished()) { + // onNode should schedule new tasks. If no tasks was added and the queue is empty, then we have finished our walk. + this.resolve() + } + } +} diff --git a/packages/verkle/src/verkleTrie.ts b/packages/verkle/src/verkleTrie.ts new file mode 100644 index 0000000000..51e90ac917 --- /dev/null +++ b/packages/verkle/src/verkleTrie.ts @@ -0,0 +1,516 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { + KeyEncoding, + Lock, + MapDB, + ValueEncoding, + bytesToHex, + equalsBytes, + hexToBytes, + zeros, +} from '@ethereumjs/util' + +import { CheckpointDB } from './db/checkpoint.js' +import { InternalNode } from './node/internalNode.js' +import { LeafNode } from './node/leafNode.js' +import { decodeNode, decodeRawNode, isRawNode } from './node/util.js' +import { + type Proof, + ROOT_DB_KEY, + type VerkleTrieOpts, + type VerkleTrieOptsWithDefaults, +} from './types.js' +import { WalkController, matchingBytesLength } from './util/index.js' + +import type { VerkleNode } from './node/types.js' +import type { FoundNodeFunction, Point } from './types.js' +import type { BatchDBOp, DB, PutBatch } from '@ethereumjs/util' + +interface Path { + node: VerkleNode | null + remaining: Uint8Array + stack: VerkleNode[] +} + +/** + * The basic verkle trie interface, use with `import { VerkleTrie } from '@ethereumjs/verkle'`. + */ +export class VerkleTrie { + protected readonly _opts: VerkleTrieOptsWithDefaults = { + useRootPersistence: false, + cacheSize: 0, + } + + /** The root for an empty trie */ + EMPTY_TRIE_ROOT: Uint8Array + + /** The backend DB */ + protected _db!: CheckpointDB + protected _hashLen: number + protected _lock = new Lock() + protected _root: Uint8Array + + /** + * Creates a new verkle trie. + * @param opts Options for instantiating the verkle trie + * + * Note: in most cases, the static {@link VerkleTrie.create} constructor should be used. It uses the same API but provides sensible defaults + */ + constructor(opts?: VerkleTrieOpts) { + if (opts !== undefined) { + this._opts = { ...this._opts, ...opts } + } + + this.database(opts?.db) + + this.EMPTY_TRIE_ROOT = zeros(32) + this._hashLen = this.EMPTY_TRIE_ROOT.length + this._root = this.EMPTY_TRIE_ROOT + + if (opts?.root) { + this.root(opts.root) + } + } + + static async create(opts?: VerkleTrieOpts) { + const key = ROOT_DB_KEY + + if (opts?.db !== undefined && opts?.useRootPersistence === true) { + if (opts?.root === undefined) { + opts.root = await opts?.db.get(key, { + keyEncoding: KeyEncoding.Bytes, + valueEncoding: ValueEncoding.Bytes, + }) + } else { + await opts?.db.put(key, opts.root, { + keyEncoding: KeyEncoding.Bytes, + valueEncoding: ValueEncoding.Bytes, + }) + } + } + + return new VerkleTrie(opts) + } + + database(db?: DB) { + if (db !== undefined) { + if (db instanceof CheckpointDB) { + throw new Error('Cannot pass in an instance of CheckpointDB') + } + + this._db = new CheckpointDB({ db, cacheSize: this._opts.cacheSize }) + } + + return this._db + } + + /** + * Gets and/or Sets the current root of the `trie` + */ + root(value?: Uint8Array | null): Uint8Array { + if (value !== undefined) { + if (value === null) { + value = this.EMPTY_TRIE_ROOT + } + + if (value.length !== this._hashLen) { + throw new Error(`Invalid root length. Roots are ${this._hashLen} bytes`) + } + + this._root = value + } + + return this._root + } + + /** + * Checks if a given root exists. + */ + async checkRoot(root: Uint8Array): Promise { + try { + const value = await this.lookupNode(root) + return value !== null + } catch (error: any) { + if (error.message === 'Missing node in DB') { + return equalsBytes(root, this.EMPTY_TRIE_ROOT) + } else { + throw error + } + } + } + + /** + * Gets a value given a `key` + * @param key - the key to search for + * @param throwIfMissing - if true, throws if any nodes are missing. Used for verifying proofs. (default: false) + * @returns A Promise that resolves to `Uint8Array` if a value was found or `null` if no value was found. + */ + async get(key: Uint8Array, throwIfMissing = false): Promise { + const node = await this.findLeafNode(key, throwIfMissing) + if (node !== null) { + const keyLastByte = key[key.length - 1] + + // The retrieved leaf node contains an array of 256 possible values. + // The index of the value we want is at the key's last byte + return node.values?.[keyLastByte] ?? null + } + + return null + } + + /** + * Stores a given `value` at the given `key` or do a delete if `value` is empty + * (delete operations are only executed on DB with `deleteFromDB` set to `true`) + * @param key - the key to store the value at + * @param value - the value to store + * @returns A Promise that resolves once value is stored. + */ + async put(key: Uint8Array, value: Uint8Array): Promise { + await this._db.put(key, value) + + // Find or create the leaf node + const leafNode = await this.findLeafNode(key, false) + if (leafNode === null) { + // If leafNode is missing, create it + // leafNode = LeafNode.create() + throw new Error('Not implemented') + } + + // Walk up the trie and update internal nodes + let currentNode: VerkleNode = leafNode + let currentKey = leafNode.stem + let currentDepth = leafNode.depth + + while (currentDepth > 0) { + const parentKey = currentKey.slice(0, -1) + const parentIndex = currentKey[currentKey.length - 1] + const parentNode = InternalNode.create(currentDepth) + parentNode.children[parentIndex] = currentNode + await this._db.put(parentKey, parentNode.serialize()) + + currentNode = parentNode + currentKey = parentKey + currentDepth-- + } + + this._root = currentNode.hash() + } + + /** + * Tries to find a path to the node for the given key. + * It returns a `stack` of nodes to the closest node. + * @param key - the search key + * @param throwIfMissing - if true, throws if any nodes are missing. Used for verifying proofs. (default: false) + */ + async findPath(key: Uint8Array, throwIfMissing = false): Promise { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + const stack: VerkleNode[] = [] + + const onFound: FoundNodeFunction = async (_, node, keyProgress, walkController) => { + if (node === null) { + return reject(new Error('Path not found')) + } + const keyRemainder = key.slice(matchingBytesLength(keyProgress, key)) + stack.push(node) + + if (node instanceof InternalNode) { + if (keyRemainder.length === 0) { + // we exhausted the key without finding a node + resolve({ node, remaining: new Uint8Array(0), stack }) + } else { + const childrenIndex = keyRemainder[0] + const childNode = node.getChildren(childrenIndex) + if (childNode === null) { + // There are no more nodes to find and we didn't find the key + resolve({ node: null, remaining: keyRemainder, stack }) + } else { + // node found, continue search from children + walkController.pushChildrenAtIndex(node, keyProgress, childrenIndex) + } + } + } else if (node instanceof LeafNode) { + // The stem of the leaf node should be the full key minus the last byte + const stem = key.slice(0, key.length - 1) + if (equalsBytes(stem, node.stem)) { + // keys match, return node with empty key + resolve({ node, remaining: new Uint8Array(0), stack }) + } else { + // reached leaf but keys don't match + resolve({ node: null, remaining: keyRemainder, stack }) + } + } + } + + // walk trie and process nodes + try { + await this.walkTrie(this.root(), onFound) + } catch (error: any) { + if (error.message === 'Missing node in DB' && !throwIfMissing) { + // pass + } else { + reject(error) + } + } + + // Resolve if walkTrie finishes without finding any nodes + resolve({ node: null, remaining: new Uint8Array(0), stack }) + }) + } + + /** + * Walks a trie until finished. + * @param root + * @param onFound - callback to call when a node is found. This schedules new tasks. If no tasks are available, the Promise resolves. + * @returns Resolves when finished walking trie. + */ + async walkTrie(root: Uint8Array, onFound: FoundNodeFunction): Promise { + await WalkController.newWalk(onFound, this, root) + } + + /** + * Tries to find the leaf node leading up to the given key, or null if not found. + * @param key - the search key + * @param throwIfMissing - if true, throws if any nodes are missing. Used for verifying proofs. (default: false) + */ + async findLeafNode(key: Uint8Array, throwIfMissing = false): Promise { + const { node } = await this.findPath(key, throwIfMissing) + if (!(node instanceof LeafNode)) { + if (throwIfMissing) { + throw new Error('leaf node not found') + } + return null + } + return node + } + + /** + * Creates the initial node from an empty tree. + * @private + */ + protected async _createInitialNode(key: Uint8Array, value: Uint8Array): Promise { + throw new Error('Not implemented') + } + + /** + * Retrieves a node from db by hash. + */ + async lookupNode(node: Uint8Array | Uint8Array[]): Promise { + if (isRawNode(node)) { + return decodeRawNode(node) + } + const value = await this._db.get(node) + if (value !== undefined) { + return decodeNode(value) + } else { + // Dev note: this error message text is used for error checking in `checkRoot`, `verifyProof`, and `findPath` + throw new Error('Missing node in DB') + } + } + + /** + * Updates a node. + * @private + * @param key + * @param value + * @param keyRemainder + * @param stack + */ + protected async _updateNode( + k: Uint8Array, + value: Uint8Array, + keyRemainder: Uint8Array, + stack: VerkleNode[] + ): Promise { + throw new Error('Not implemented') + } + + /** + * Saves a stack of nodes to the database. + * + * @param key - the key. Should follow the stack + * @param stack - a stack of nodes to the value given by the key + * @param opStack - a stack of levelup operations to commit at the end of this function + */ + async saveStack( + key: Uint8Array, + stack: VerkleNode[], + opStack: PutBatch[] + ): Promise { + throw new Error('Not implemented') + } + + /** + * Formats node to be saved by `levelup.batch`. + * @private + * @param node - the node to format. + * @param topLevel - if the node is at the top level. + * @param opStack - the opStack to push the node's data. + * @param remove - whether to remove the node + * @returns The node's hash used as the key or the rawNode. + */ + _formatNode( + node: VerkleNode, + topLevel: boolean, + opStack: PutBatch, + remove: boolean = false + ): Uint8Array { + throw new Error('Not implemented') + } + + /** + * The given hash of operations (key additions or deletions) are executed on the trie + * (delete operations are only executed on DB with `deleteFromDB` set to `true`) + * @example + * const ops = [ + * { type: 'del', key: Uint8Array.from('father') } + * , { type: 'put', key: Uint8Array.from('name'), value: Uint8Array.from('Yuri Irsenovich Kim') } + * , { type: 'put', key: Uint8Array.from('dob'), value: Uint8Array.from('16 February 1941') } + * , { type: 'put', key: Uint8Array.from('spouse'), value: Uint8Array.from('Kim Young-sook') } + * , { type: 'put', key: Uint8Array.from('occupation'), value: Uint8Array.from('Clown') } + * ] + * await trie.batch(ops) + * @param ops + */ + async batch(ops: BatchDBOp[]): Promise { + throw new Error('Not implemented') + } + + /** + * Saves the nodes from a proof into the trie. + * @param proof + */ + async fromProof(proof: Proof): Promise { + throw new Error('Not implemented') + } + + /** + * Creates a proof from a trie and key that can be verified using {@link Trie.verifyProof}. + * @param key + */ + async createProof(key: Uint8Array): Promise { + throw new Error('Not implemented') + } + + /** + * Verifies a proof. + * @param rootHash + * @param key + * @param proof + * @throws If proof is found to be invalid. + * @returns The value from the key, or null if valid proof of non-existence. + */ + async verifyProof( + rootHash: Uint8Array, + key: Uint8Array, + proof: Proof + ): Promise { + throw new Error('Not implemented') + } + + /** + * The `data` event is given an `Object` that has two properties; the `key` and the `value`. Both should be Uint8Arrays. + * @return Returns a [stream](https://nodejs.org/dist/latest-v12.x/docs/api/stream.html#stream_class_stream_readable) of the contents of the `trie` + */ + createReadStream(): any { + throw new Error('Not implemented') + } + + /** + * Returns a copy of the underlying trie. + * + * Note on db: the copy will create a reference to the + * same underlying database. + * + * Note on cache: for memory reasons a copy will not + * recreate a new LRU cache but initialize with cache + * being deactivated. + * + * @param includeCheckpoints - If true and during a checkpoint, the copy will contain the checkpointing metadata and will use the same scratch as underlying db. + */ + shallowCopy(includeCheckpoints = true): VerkleTrie { + const trie = new VerkleTrie({ + ...this._opts, + db: this._db.db.shallowCopy(), + root: this.root(), + cacheSize: 0, + }) + if (includeCheckpoints && this.hasCheckpoints()) { + trie._db.setCheckpoints(this._db.checkpoints) + } + return trie + } + + /** + * Persists the root hash in the underlying database + */ + async persistRoot() { + if (this._opts.useRootPersistence) { + await this._db.put(ROOT_DB_KEY, this.root()) + } + } + + /** + * Finds all nodes that are stored directly in the db + * (some nodes are stored raw inside other nodes) + * called by {@link ScratchReadStream} + * @private + */ + protected async _findDbNodes(onFound: () => void): Promise { + throw new Error('Not implemented') + } + + /** + * Is the trie during a checkpoint phase? + */ + hasCheckpoints() { + return this._db.hasCheckpoints() + } + + /** + * Creates a checkpoint that can later be reverted to or committed. + * After this is called, all changes can be reverted until `commit` is called. + */ + checkpoint() { + this._db.checkpoint(this.root()) + } + + /** + * Commits a checkpoint to disk, if current checkpoint is not nested. + * If nested, only sets the parent checkpoint as current checkpoint. + * @throws If not during a checkpoint phase + */ + async commit(): Promise { + if (!this.hasCheckpoints()) { + throw new Error('trying to commit when not checkpointed') + } + + await this._lock.acquire() + await this._db.commit() + await this.persistRoot() + this._lock.release() + } + + /** + * Reverts the trie to the state it was at when `checkpoint` was first called. + * If during a nested checkpoint, sets root to most recent checkpoint, and sets + * parent checkpoint as current. + */ + async revert(): Promise { + if (!this.hasCheckpoints()) { + throw new Error('trying to revert when not checkpointed') + } + + await this._lock.acquire() + this.root(await this._db.revert()) + await this.persistRoot() + this._lock.release() + } + + /** + * Flushes all checkpoints, restoring the initial checkpoint state. + */ + flushCheckpoints() { + this._db.checkpoints = [] + } +} diff --git a/packages/verkle/test/node/internalNode.spec.ts b/packages/verkle/test/node/internalNode.spec.ts new file mode 100644 index 0000000000..f27b375a86 --- /dev/null +++ b/packages/verkle/test/node/internalNode.spec.ts @@ -0,0 +1,52 @@ +import { equalsBytes, randomBytes } from '@ethereumjs/util' +import { assert, describe, it } from 'vitest' + +import { NODE_WIDTH, VerkleNodeType } from '../../src/node/index.js' +import { InternalNode } from '../../src/node/internalNode.js' +import { POINT_IDENTITY } from '../../src/util/crypto.js' + +import type { Point } from '../../src/types.js' + +describe('verkle node - internal', () => { + it('constructor should create an internal node', async () => { + const commitment = randomBytes(32) + const depth = 2 + const node = new InternalNode({ commitment: commitment as unknown as Point, depth }) + + assert.equal(node.type, VerkleNodeType.Internal, 'type should be set') + assert.ok( + equalsBytes(node.commitment as unknown as Uint8Array, commitment), + 'commitment should be set' + ) + assert.equal(node.depth, depth, 'depth should be set') + + // Children nodes should all default to null. + assert.equal(node.children.length, NODE_WIDTH, 'number of children should equal verkle width') + assert.ok( + node.children.every((child) => child === null), + 'every children should be null' + ) + }) + + it('create method should create an internal node', async () => { + const depth = 3 + const node = InternalNode.create(depth) + + assert.equal(node.type, VerkleNodeType.Internal, 'type should be set') + assert.ok( + equalsBytes( + node.commitment as unknown as Uint8Array, + POINT_IDENTITY as unknown as Uint8Array + ), + 'commitment should be set to point identity' + ) + assert.equal(node.depth, depth, 'depth should be set') + + // Children nodes should all default to null. + assert.equal(node.children.length, NODE_WIDTH, 'number of children should equal verkle width') + assert.ok( + node.children.every((child) => child === null), + 'every children should be null' + ) + }) +}) diff --git a/packages/verkle/test/node/leafNode.spec.ts b/packages/verkle/test/node/leafNode.spec.ts new file mode 100644 index 0000000000..251c4cc6c3 --- /dev/null +++ b/packages/verkle/test/node/leafNode.spec.ts @@ -0,0 +1,42 @@ +import { equalsBytes, randomBytes } from '@ethereumjs/util' +import { assert, describe, it } from 'vitest' + +import { VerkleNodeType } from '../../src/node/index.js' +import { LeafNode } from '../../src/node/leafNode.js' + +import type { Point } from '../../src/types.js' + +describe('verkle node - leaf', () => { + it('constructor should create an leaf node', async () => { + const commitment = randomBytes(32) + const c1 = randomBytes(32) + const c2 = randomBytes(32) + const stem = randomBytes(32) + const values = [randomBytes(32), randomBytes(32)] + const depth = 2 + const node = new LeafNode({ + c1: c1 as unknown as Point, + c2: c2 as unknown as Point, + commitment: commitment as unknown as Point, + depth, + stem, + values, + }) + + assert.equal(node.type, VerkleNodeType.Leaf, 'type should be set') + assert.ok( + equalsBytes(node.commitment as unknown as Uint8Array, commitment), + 'commitment should be set' + ) + assert.ok(equalsBytes(node.c1 as unknown as Uint8Array, c1), 'c1 should be set') + assert.ok(equalsBytes(node.c2 as unknown as Uint8Array, c2), 'c2 should be set') + assert.ok(equalsBytes(node.stem, stem), 'stem should be set') + assert.ok( + values.every((value, index) => equalsBytes(value, node.values[index])), + 'values should be set' + ) + assert.equal(node.depth, depth, 'depth should be set') + }) + + it.todo('create method should create an leaf node') +}) diff --git a/packages/verkle/test/verkle.spec.ts b/packages/verkle/test/verkle.spec.ts new file mode 100644 index 0000000000..6bccd73918 --- /dev/null +++ b/packages/verkle/test/verkle.spec.ts @@ -0,0 +1,60 @@ +import { equalsBytes, hexToBytes } from '@ethereumjs/util' +import { assert, describe, it } from 'vitest' + +import { VerkleTrie } from '../src/verkleTrie.js' + +// Testdata from https://github.com/gballet/go-ethereum/blob/kaustinen-with-shapella/trie/verkle_test.go +const presentKeys = [ + '0x318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d01', + '0xe6ed6c222e3985050b4fc574b136b0a42c63538e9ab970995cd418ba8e526400', + '0x18fb432d3b859ec3a1803854e8cceea75d092e52d0d4a4398d13022496745a02', + '0x318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d02', + '0x18fb432d3b859ec3a1803854e8cceea75d092e52d0d4a4398d13022496745a04', + '0xe6ed6c222e3985050b4fc574b136b0a42c63538e9ab970995cd418ba8e526402', + '0xe6ed6c222e3985050b4fc574b136b0a42c63538e9ab970995cd418ba8e526403', + '0x18fb432d3b859ec3a1803854e8cceea75d092e52d0d4a4398d13022496745a00', + '0x18fb432d3b859ec3a1803854e8cceea75d092e52d0d4a4398d13022496745a03', + '0xe6ed6c222e3985050b4fc574b136b0a42c63538e9ab970995cd418ba8e526401', + '0xe6ed6c222e3985050b4fc574b136b0a42c63538e9ab970995cd418ba8e526404', + '0x318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d00', + '0x18fb432d3b859ec3a1803854e8cceea75d092e52d0d4a4398d13022496745a01', +].map(hexToBytes) + +// Corresponding values for the present keys +const values = [ + '0x320122e8584be00d000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0300000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + '0x1bc176f2790c91e6000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0xe703000000000000000000000000000000000000000000000000000000000000', +].map(hexToBytes) + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const absentKeys = [ + '0x318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d03', + '0x318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d04', +].map(hexToBytes) + +describe('Verkle trie', () => { + it.todo('should insert and retrieve values', async () => { + const trie = new VerkleTrie() + for (let i = 0; i < presentKeys.length; i++) { + await trie.put(presentKeys[i], values[i]) + } + for (let i = 0; i < presentKeys.length; i++) { + const retrievedValue = await trie.get(presentKeys[i]) + if (retrievedValue === null) { + assert.fail('Value not found') + } + assert.ok(equalsBytes(retrievedValue, values[i])) + } + }) +}) diff --git a/packages/verkle/tsconfig.benchmarks.json b/packages/verkle/tsconfig.benchmarks.json new file mode 100644 index 0000000000..87f297c0ec --- /dev/null +++ b/packages/verkle/tsconfig.benchmarks.json @@ -0,0 +1,4 @@ +{ + "extends": "../../config/tsconfig.prod.cjs.json", + "include": ["benchmarks/*.ts"] +} diff --git a/packages/verkle/tsconfig.json b/packages/verkle/tsconfig.json new file mode 100644 index 0000000000..03ee66c13b --- /dev/null +++ b/packages/verkle/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../config/tsconfig.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src/**/*.ts", "test/**/*.spec.ts"] +} diff --git a/packages/verkle/tsconfig.prod.cjs.json b/packages/verkle/tsconfig.prod.cjs.json new file mode 100644 index 0000000000..fb8608068a --- /dev/null +++ b/packages/verkle/tsconfig.prod.cjs.json @@ -0,0 +1,13 @@ +{ + "extends": "../../config/tsconfig.prod.cjs.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist/cjs", + "composite": true + }, + "include": ["src/**/*.ts"], + "references": [ + { "path": "../rlp/tsconfig.prod.cjs.json" }, + { "path": "../util/tsconfig.prod.cjs.json" } + ] +} diff --git a/packages/verkle/tsconfig.prod.esm.json b/packages/verkle/tsconfig.prod.esm.json new file mode 100644 index 0000000000..e24adc754d --- /dev/null +++ b/packages/verkle/tsconfig.prod.esm.json @@ -0,0 +1,13 @@ +{ + "extends": "../../config/tsconfig.prod.esm.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist/esm", + "composite": true + }, + "include": ["src/**/*.ts"], + "references": [ + { "path": "../rlp/tsconfig.prod.esm.json" }, + { "path": "../util/tsconfig.prod.esm.json" } + ] +} diff --git a/packages/verkle/typedoc.cjs b/packages/verkle/typedoc.cjs new file mode 100644 index 0000000000..701fee055f --- /dev/null +++ b/packages/verkle/typedoc.cjs @@ -0,0 +1,6 @@ +module.exports = { + extends: '../../config/typedoc.cjs', + entryPoints: ['src'], + out: 'docs', + exclude: ['test/**/*.ts'], +} diff --git a/packages/verkle/vitest.config.browser.ts b/packages/verkle/vitest.config.browser.ts new file mode 100644 index 0000000000..9803b3ac44 --- /dev/null +++ b/packages/verkle/vitest.config.browser.ts @@ -0,0 +1,7 @@ +import { configDefaults, defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + exclude: [...configDefaults.exclude], + }, +})