From 7d60a6e5d1fc4d6065915c3b023657c9b8a8d767 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sat, 29 Jul 2023 10:50:00 -0400 Subject: [PATCH 01/49] verkle: initial commit --- 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 | 610 +++++++++++++++++++++++ packages/verkle/README.md | 281 +++++++++++ packages/verkle/package.json | 75 +++ 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 + 13 files changed, 1036 insertions(+) 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/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 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..027f2549af --- /dev/null +++ b/packages/verkle/CHANGELOG.md @@ -0,0 +1,610 @@ +# 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). + +## 6.0.0-rc.1 - 2023-07-18 + +This is the release candidate (RC1) for the upcoming breaking releases on the various EthereumJS libraries. The associated release notes below are the main source of information on the changeset, also for the upcoming final releases, where we'll just provide change addition summaries + references to these RC1 notes. + +At time of the RC1 releases there is/was no plan for a second RC round and breaking releases following relatively shorty (2-3 weeks) after the RC1 round. Things may change though depending on the feedback we'll receive. + +### Introduction + +This round of breaking releases brings the EthereumJS libraries to the browser. Finally! 🤩 + +While you could use our libraries in the browser libraries before, there had been caveats. + +WE HAVE ELIMINATED ALL OF THEM. + +The largest two undertakings: First: we have rewritten all (half) of our API and elimited the usage of Node.js specific `Buffer` all over the place and have rewritten with using `Uint8Array` byte objects. Second: we went throuh our whole stack, rewrote imports and exports, replaced and updated dependencies all over and are now able to provide a hybrid CommonJS/ESM build, for all libraries. Both of these things are huge. + +Together with some few other modifications this now allows to run each (maybe adding an asterisk for client and devp2p) of our libraries directly in the browser - more or less without any modifications - see the `examples/browser.html` file in each package folder for an easy to set up example. + +This is generally a big thing for Ethereum cause this brings the full Ethereum Execution Layer (EL) protocol stack to the browser in an easy accessible way for developers, for the first time ever! 🎉 + +This will allow for easy-to-setup browser applications both around the existing as well as the upcoming Ethereum EL protocol stack in the future. 🏄🏾‍♂️ We are beyond excitement to see what you guys will be building with this for "Browser-Ethereum". 🤓 + +Browser is not the only thing though why this release round is exciting: default Shanghai hardfork, full Cancun support, significantly smaller bundle sizes for various libraries, new database abstractions, a simpler to use EVM, API clean-ups throughout the whole stack. These are just the most prominent additional things here to mention which will make the developer heart beat a bit faster hopefully when you are scanning to the vast release notes for every of the 15 (!) releases! 🧑🏽‍💻 + +So: jump right in and enjoy. We can't wait to hear your feedback and see if you agree that these releases are as good as we think they are. 🙂 ❤️ + +The EthereumJS Team + +### New Trie Node Cache + +There is a new permanent trie node cache which can be leveraged to make Trie operations significantly faster, see PR [#2667](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2667). Since this also increases base-memory usage of a trie instantiation, this new cache is mainly intended to be used in rather long-lived trie scenarios. + +The new cache can be activated by setting a fitting cache size with the new `cacheSize` option (default: `0` (deactivated)). + +### Hybrid CJS/ESM Build + +We now provide both a CommonJS and an ESM build for all our libraries. 🥳 This transition was a huge undertaking and should make the usage of our libraries in the browser a lot more straight-forward, see PR [#2685](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2685), [#2783](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2783), [#2786](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2786), [#2764](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2764), [#2804](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2804) and [#2809](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2809) (and others). We rewrote the whole set of imports and exports within the libraries, updated or completely removed a lot of dependencies along the way and removed the usage of all native Node.js primitives (like `https` or `util`). + +There are now two different build directories in our `dist` folder, being `dist/cjs` for the CommonJS and `dist/esm` for the `ESM` build. That means that direct imports (which you generally should try to avoid, rather open an issue on your import needs), need an update within your code (do a `dist` or the like code search). + +Both builds have respective separate entrypoints in the distributed `package.json` file. + +A CommonJS import of our libraries can then be done like this: + +```typescript +const { Chain, Common } = require('@ethereumjs/common') +const common = new Common({ chain: Chain.Mainnet }) +``` + +And this is how an ESM import looks like: + +```typescript +import { Chain, Common } from '@ethereumjs/common' +const common = new Common({ chain: Chain.Mainnet }) +``` + +Using ESM will give you additional advantages over CJS beyond browser usage like static code analysis / Tree Shaking which CJS can not provide. + +Side note: along this transition we also rewrote our whole test suite (yes!!!) to now work with [Vitest](https://vitest.dev/) instead of `Tape`. + +### Buffer -> Uint8Array + +With these releases we remove all Node.js specific `Buffer` usages from our libraries and replace these with [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) representations, which are available both in Node.js and the browser (`Buffer` is a subclass of `Uint8Array`). While this is a big step towards interoperability and browser compatibility of our libraries, this is also one of the most invasive operations we have ever done, see the huge changeset from PR [#2566](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2566) and [#2607](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2607). 😋 + +We nevertheless think this is very much worth it and we tried to make transition work as easy as possible. + +#### How to upgrade? + +For this library you should check if you use one of the following constructors, methods, constants or types and do a search and update input and/or output values or general usages and add conversion methods if necessary: + +```typescript +Trie.create() / new Trie() // root constructor option +Trie.root(value?: Uint8Array | null): Uint8Array +Trie.checkRoot(root: Uint8Array): Promise +Trie.get(key: Uint8Array, throwIfMissing = false): Promise +Trie.put(key: Uint8Array, value: Uint8Array): Promise +Trie.del(key: Uint8Array): Promise +Trie.findPath(key: Uint8Array, throwIfMissing = false): Promise +Trie.walkTrie(root: Uint8Array, onFound: FoundNodeFunction): Promise +Trie.lookupNode(node: Uint8Array | Uint8Array[]): Promise +Trie.createProof(key: Uint8Array): Promise +Trie.verifyProof() +Trie.createReadStream() +Trie.hash(msg: Uint8Array): Uint8Array +``` + +So basically the whole API. Lol. 😋 + +We have converted existing Buffer conversion methods to Uint8Array conversion methods in the [@ethereumjs/util](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/util) `bytes` module, see the respective README section for guidance. + +#### Prefixed Hex Strings as Default + +The mixed usage of prefixed and unprefixed hex strings is a constant source of errors in byte-handling code bases. + +We have therefore decided to go "prefixed" by default, see PR [#2830](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2830) and [#2845](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2845). + +The `hexToBytes` and `bytesToHex` methods, also similar methods like `intToHex`, now take `0x`-prefixed hex strings as input and output prefixed strings. The corresponding unprefixed methods are marked as `deprecated` and usage should be avoided. + +Please therefore check you code base on updating and ensure that values you are passing to constructors and methods are prefixed with a `0x`. + +### Other Changes + +- Support for `Node.js 16` has been removed (minimal version: `Node.js 18`), PR [#2859](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2859) +- Breaking: `DB` interface and `MapDB` implementation have been moved to [@ethereumjs/util](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/util/src/db.ts) (for re-usage), PR [#2669](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2669) +- Breaking: The `copy()` method has been renamed to `shallowCopy()` (same underlying state DB), PR [#2826](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2826) + +## 5.0.5 - 2023-04-20 + +- Update ethereum-cryptography from 1.2 to 2.0 (switch from noble-secp256k1 to noble-curves), PR [#2641](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2641) +- Bump `@ethereumjs/util` `@chainsafe/ssz` dependency to 0.11.1 (no WASM, native SHA-256 implementation, ES2019 compatible, explicit imports), PRs [#2622](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2622), [#2564](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2564) and [#2656](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2656) + +## 5.0.4 - 2023-02-27 + +- Pinned `@ethereumjs/util` `@chainsafe/ssz` dependency to `v0.9.4` due to ES2021 features used in `v0.10.+` causing compatibility issues, PR [#2555](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2555) + +## 5.0.3 - 2023-02-21 + +**DEPRECATED**: Release is deprecated due to broken dependencies, please update to the subsequent bugfix release version. + +Maintenance release with dependency updates, PR [#2521](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2521) + +## 5.0.2 - 2022-12-09 + +Maintenance release with dependency updates, PR [#2445](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2445) + +## 5.0.1 - 2022-10-18 + +- Fixed a dependency issue when using `TrieReadStream`, PR [#2318](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2318) +- Fixed a pruning verification issue for a case where only the root key is present as a key but not the root value, PR [#2296](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2296) + +## 5.0.0 - 2022-09-06 + +Final release - tada 🎉 - of a wider breaking release round on the [EthereumJS monorepo](https://github.com/ethereumjs/ethereumjs-monorepo) libraries, see the Beta 1 release notes for the main long change set description as well as the Beta 2, Beta 3 and Release Candidate (RC) 1 release notes for notes on some additional changes ([CHANGELOG](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/trie/CHANGELOG.md)). + +### Trie Pruning + +Some great last minute feature (thanks @faustbrian and @jochem-brouwer on this!) allowing to prune a trie on state root updates with a new `useNodePruning` option (default: `false`), see PR [#2203](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2203). This allows for a much smaller DB footprint for the trie for use cases with frequent state root updates. + +### Other Changes + +- New `setCheckpoints(checkpoints: Checkpoint[])` method to support the manual setting of checkpoints for advanced use cases, PR [#2240](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2240) +- Internal refactor: removed ambiguous boolean checks within conditional clauses, PR [#2249](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2249) + +## 5.0.0-rc.1 - 2022-08-29 + +Release candidate 1 for the upcoming breaking release round on the [EthereumJS monorepo](https://github.com/ethereumjs/ethereumjs-monorepo) libraries, see the Beta 1 release notes for the main long change set description as well as the Beta 2 and 3 release notes for notes on some additional changes ([CHANGELOG](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/trie/CHANGELOG.md)). + +From Beta 3 to RC 1 the `Trie` library has seen the most vast set of changes, thanks again to @faustbrian for this various meaningful refactoring contributions! ❤️ + +There are various substantial structural changes and reworkings which will make working with the Trie library more flexible and robust. Note that these changes will need some attention though on an upgrade depending on your existing usage of the Trie library. + +### Single Trie Class + +There is now one single `Trie` class which contains and exposes the functionality previously split into the three separate classes `Trie` -> `CheckpointTrie` and `SecureTrie`, see PRs [#2214](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2214) and [#2215](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2215). Class inheritance has been removed and the existing functionality has been integrated into one class. This should make it easier to extend the Trie class or customize its behavior without having to "dock" into the previous complicated inheritance structure. + +#### Default Checkpointing Behavior + +The `CheckpointTrie` class has been removed in favor of integrating the functionality into the main `Trie` class and make it a default behaviour. Every Trie instance now comes complete with checkpointing behaviour out of the box, without giving any additional weight or performance penalty if the functionality remains unused. + +#### Secure Trie with an Option + +The `SecureTrie` class has been removed as well. Instead there is a new constructor option `useKeyHashing` - defaulting to `false`. This effectively reduces the level of inheritance dependencies (for example, in the old structure, you could not create a secure trie without the checkpoint functionality which, in terms of logic, do not correlate in any way). This also provides more room to accommodate future design modifications and/or additions if required. + +Updating is a straightforward process: + +```ts +// Old +const trie = new SecureTrie() + +// New +const trie = new Trie({ useKeyHashing: true }) +``` + +### Removed Getter and Setter Functions + +Due to the ambiguity of the `get` and `set` functions (also known as getters and setters), usage has been removed from the library. This is because their ambiguity can create the impression of interacting with a property on a trie instance. + +#### Trie `root` Getter/Setter + +For this reason, a single `root(hash?: Buffer): Buffer` function serves as a replacement for the previous `root` getter and setter and can effectively work to get and set properties, see PR [#2219](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2219). This makes it obvious that you intend to modify an internal property of the trie that is neither accessible or mutable via any other means other than this particular function. + +##### Getter Example + +```tsx +// Old +const trie = new Trie() +trie.root + +// New +const trie = new Trie() +trie.root() +``` + +##### Setter Example + +```tsx +// Old +const trie = new Trie() +trie.root = Buffer.alloc(32) + +// New +const trie = new Trie() +trie.root(Buffer.alloc(32)) +``` + +#### Trie `isCheckpoint` Getter + +The `isCheckpoint` getter function has been removed, see PR [#2218](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2218) The `hasCheckpoints()` function serves as its replacement and offers the same behaviour. + +```tsx +// Old +const trie = new Trie() +trie.isCheckpoint + +// New +const trie = new Trie() +trie.hasCheckpoints() +``` + +### Database Abstraction + +Another significant change is that we dropped support for `LevelDB` out of the box. As a result, you will need to have your own implementation available. + +#### Motivation + +The primary reason for this change is increase the flexibility of this package by allowing developers to select any type of storage for their unique purposes. In addition, this change renders the project far less susceptible to [supply chain attacks](https://en.wikipedia.org/wiki/Supply_chain_attack). We trust that users and developers can appreciate the value of reducing this attack surface in exchange for a little more time spent on their part for the duration of this upgrade. + +#### LevelDB Removal + +Prior to v5, this package shipped with a LevelDB integration out of the box. Finalized within this RC release round we have introduced a database abstraction and therefore no longer ship with the aforementioned LevelDB implementation, see previous Beta CHANGELOGs as well as PR [#2167](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2167). However, for your convenience, we provide all of the necessary steps so that you can integrate it accordingly. + +See our [Upgrade Guide](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/trie/UPGRADING.md) for more instructions on how to use the `Trie` library with LevelDB now. + +### Other Changes + +- Store `opts` in private property with defaults, PR [#2224](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2224) +- **Potentially breaking:** Mark `db` as protected and rename to `_db` to avoid leaky properties, PR [#2221](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2221) +- Replace `get/set` for `key/value` for nodes with function, PR [#2220](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2220) +- Rename `persistRoot` to `useRootPersistence`, PR [#2223](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2223) + +### Maintenance Updates + +- Added `engine` field to `package.json` limiting Node versions to v14 or higher, PR [#2164](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2164) +- Replaced `nyc` (code coverage) configurations with `c8` configurations, PR [#2192](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2192) +- Code formats improvements by adding various new linting rules, see Issue [#1935](https://github.com/ethereumjs/ethereumjs-monorepo/issues/1935) +- Use `micro-bmark` for benchmarks, PR [#2128](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2128) +- Move `Trie#_findValueNodes()` function to `TrieReadStream`, PR [#2186](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2186) +- Replaced `semaphore-async-await` with simpler implementation, PR [#2187](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2187) +- Renamed `Semaphore` to `Lock`, PR [#2234](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2234) + +## 5.0.0-beta.3 - 2022-08-10 + +Beta 3 release for the upcoming breaking release round on the [EthereumJS monorepo](https://github.com/ethereumjs/ethereumjs-monorepo) libraries, see the Beta 1 release notes for the main long change set description as well as the Beta 2 release notes for notes on some additional changes ([CHANGELOG](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/devp2p/CHANGELOG.md)). + +### Root Hash Persistance + +The trie library now comes with a new constructor option `useRootPersistence` (note that the option has been called `persistRoot` up to Beta 3) which is disabled by default but allows to persist state root updates along write operations directly in the DB and therefore omits the need to manually set to a new state root, see PR [#2071](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2071) and PR [#2123](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2123), thanks to @faustbrian for the contribution! ❤️ + +To activate root hash persistance you can set the `useRootPersistence` option on instantiation: + +```typescript +import { Trie, LevelDB } from '@ethereumjs/trie' +import { Level } from 'level' + +const trie = new Trie({ + db: new LevelDB(new Level('MY_TRIE_DB_LOCATION')), + useRootPersistence: true, +}) +``` + +### Other Changes + +- Fix: Pass down a custom hash function for hashing on trie copies, PR [#2068](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2068) + +# 5.0.0-beta.2 - 2022-07-15 + +Beta 2 release for the upcoming breaking release round on the [EthereumJS monorepo](https://github.com/ethereumjs/ethereumjs-monorepo) libraries, see the Beta 1 release notes ([CHANGELOG](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/trie/CHANGELOG.md)) for the main change set description. + +### Removed Default Exports + +The change with the biggest effect on UX since the last Beta 1 releases is for sure that we have removed default exports all accross the monorepo, see PR [#2018](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2018), we even now added a new linting rule that completely disallows using. + +Default exports were a common source of error and confusion when using our libraries in a CommonJS context, leading to issues like Issue [#978](https://github.com/ethereumjs/ethereumjs-monorepo/issues/978). + +Now every import is a named import and we think the long term benefits will very much outweigh the one-time hassle of some import adoptions. + +So if you use the Trie library together with other EthereumJS libraries check if the respetive imports need an update. + +## Custom Hash Function + +There is a new constructor option `hash` which allows to customize the hash function used for secure trie key hashing - see PR [#2043](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2043) - thanks to @libotony for the great contribution on this! ❤️ + +This allows to swap out the applied `keccak256` hash functionality from the [@noble/hashes](https://github.com/paulmillr/noble-hashes) library and e.g. use a faster native implementation or an alternative hash function (the PR contribution e.g. was done with the goal to switch to `blake2b256` hashing). + +**Breaking:** Note that this change made it necessary to switch the current proof functionality methods from static to object-bound member functions. + +So the usage of the following methods change and need to be updated (for all types of tries): + +- `Trie.createProof(trie, myKey)` -> `trie.createProof(myKey)` +- `Trie.verifyProof(trie.root(), myKey, proof)` -> `trie.verifyProof(trie.root(), myKey, proof)` +- `Trie.verifyRangeProof(...)` -> `trie.verifyRangeProof(...)` + +## Other Changes + +- Added `ESLint` strict boolean expressions linting rule, PR [#2030](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2030) + +# 5.0.0-beta.1 - 2022-06-30 + +This release is part of a larger breaking release round where all [EthereumJS monorepo](https://github.com/ethereumjs/ethereumjs-monorepo) libraries (VM, Tx, Trie, other) get major version upgrades. This round of releases has been prepared for a long time and we are really pleased with and proud of the result, thanks to all team members and contributors who worked so hard and made this possible! 🙂 ❤️ + +We have gotten rid of a lot of technical debt and inconsistencies and removed unused functionality, renamed methods, improved on the API and on TypeScript typing, to name a few of the more local type of refactoring changes. There are also broader structural changes like a full transition to native JavaScript `BigInt` values as well as various somewhat deep-reaching refactorings, both within a single package as well as some reaching beyond the scope of a single package. Also two completely new packages - `@ethereumjs/evm` (in addition to the existing `@ethereumjs/vm` package) and `@ethereumjs/statemanager` - have been created, leading to a more modular Ethereum JavaScript VM. + +We are very much confident that users of the libraries will greatly benefit from the changes being introduced. However - along the upgrade process - these releases require some extra attention and care since the changeset is both so big and deep reaching. We highly recommend to closely read the release notes, we have done our best to create a full picture on the changes with some special emphasis on delicate code and API parts and give some explicit guidance on how to upgrade and where problems might arise! + +So, enjoy the releases (this is a first round of Beta releases, with final releases following a couple of weeks after if things go well)! 🎉 + +The EthereumJS Team + +### New Package Name + +**Attention!** This library release aligns (and therefore: changes!) the library name with the other EthereumJS libraries and switches to the new scoped package name format, see PR [#1953](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1953). In this case the library is renamed as follows: + +- `merkle-patricia-tree` -> `@ethereumjs/trie` + +Please update your library references accordingly and install with: + +```shell +npm i @ethereumjs/trie +``` + +### BigInt Introduction / ES2020 Build Target + +With this round of breaking releases the whole EthereumJS library stack removes the [BN.js](https://github.com/indutny/bn.js/) library and switches to use native JavaScript [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) values for large-number operations and interactions. + +This makes the libraries more secure and robust (no more BN.js v4 vs v5 incompatibilities) and generally comes with substantial performance gains for the large-number-arithmetic-intense parts of the libraries (particularly the VM). + +While the Trie library currently has no specific BigInt usage we have generally updated our build target to [ES2020](https://262.ecma-international.org/11.0/) to allow for BigInt support now or for future functionality additions. We feel that some still remaining browser compatibility issues on the edges (old Safari versions e.g.) are justified by the substantial gains this step brings along. + +See [#1671](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1671) and [#1771](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1771) for the core `BigInt` transition PRs. + +### Disabled esModuleInterop and allowSyntheticDefaultImports TypeScript Compiler Options + +The above TypeScript options provide some semantic sugar like allowing to write an import like `import React from "react"` instead of `import * as React from "react"`, see [esModuleInterop](https://www.typescriptlang.org/tsconfig#esModuleInterop) and [allowSyntheticDefaultImports](https://www.typescriptlang.org/tsconfig#allowSyntheticDefaultImports) docs for some details. + +While this is convenient it deviates from the ESM specification and forces downstream users into these options which might not be desirable, see [this TypeScript Semver docs section](https://www.semver-ts.org/#module-interop) for some more detailed argumentation. + +Along the breaking releases we have therefore deactivated both of these options and you might therefore need to adopt some import statements accordingly. Note that you still have got the possibility to activate these options in your bundle and/or transpilation pipeline (but now you also have the option to _not_ do which you didn't have before). + +### Database Changes + +#### Generic DB Interface + +In the last round of breaking release preparation @faustbrian came around the corner and came up with some really great DB-related additions to the Trie library, thanks so much for these super valuable contributions! ❤️ ❤️ ❤️ + +Trie usage has now been decoupled from the tight integration with `LevelDB` and it is now possible to replace the datastore with an own implementation respectively a DB wrapper to an alternative key-value-store solution. + +For this there is now a generic `DB` interface defining five methods `get`, `put`, `del`, `batch` and `copy` which a specific `DB` wrapper needs to implement. For `LevelDB` a wrapper with the same name is included and can be directly used. + +The base trie implementation (`Trie`) as well as all subclass implementations (`CheckpointTrie` and `SecureTrie`) have been reworked to now accept any `DB` interface-compatible wrapper implementations as a datastore `db` option input. This allows to easily switch on the underlying backend. + +The new `DB` interface can be used like this for LevelDB: + +```typescript +import { Trie, LevelDB } from '@ethereumjs/trie' +import { Level } from 'level' + +const trie = new Trie({ db: new LevelDB(new Level('MY_TRIE_DB_LOCATION')) }) +``` + +If no `db` option is provided an in-memory [memory-level](https://github.com/Level/memory-level) data storage will be instantiated and used. (Side note: some internal non-persistent trie operations (e.g. proof trie creation for range proofs) will always use the internal `level` based data storage, so there will be some continued `level` DB usage also when you switch to an alternative data store for permanent trie storage). + +#### Level DB Upgrade / Browser Compatibility + +Along with the DB interface extraction the internal Level DB code has been reworked to now be based and work with the latest Level [v8.0.0](https://github.com/Level/level/releases/tag/v8.0.0) major Level DB release. This allows to use ES6-style `import` syntax to import the `Level` instance and allows for better typing when working with Level DB. + +Because of the upgrade, any `level` implementation compliant with the `abstract-level` interface can be use, including `classic-level`, `browser-level` and `memory-level`. This now makes it a lot easier to use the package in browsers without polyfills for `level`. For some context it is worth to mention that the `level` package itself is starting with the v8 release just a proxy for these other packages and has no functionality itself. + +### API Changes + +Options for the Trie constructor are now also taken in as an options dict like in the other EthereumJS libaries. This makes it easier to add additional options in the future, see PR [#1874](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1874). + +Check your Trie instantiations and see if you use constructor options. In this case you need to update to the new format: + +- `constructor(db?: LevelUp | null, root?: Buffer, deleteFromDB: boolean = false)` -> `constructor(opts: TrieOpts = {})` + +The following deprecated or semi-private methods have been removed, see PR [#1874](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1874) and PR [#1834](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1834). + +- `setRoot()` (use `Trie.root` instead) +- Semi-private `_walkTrie()` and `_lookupNode()` methods (should not but might have been used directly) + +### New File Layout + +The trie source files have been reorganized to provide a more consistent and clean file and folder layout, see PR [#1972](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1972). This might or might not affect you, depending if you use direct file references for importing (if you do you might want to generally switch to use root-level exports from the main `index.ts` file). + +All types and Trie options are now bundled in a dedicated `types.ts` file and there are dedicated folders for the different Trie implementations (`trie/`), DB interfaces and classes (`db/`) and proof-related functionality (`proof/`). Additionally some utility functionality has been moved to the `util/` folder. + +# 4.2.4 - 2022-03-15 + +- New `Trie.verifyRangeProof()` function to check whether the given leaf nodes and edge proof can prove the given trie leaves range is matched with the specific root (useful for snapsync, thanks to @samlior for this generous code contribution ❤️), PR [#1731](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1731) + +# 4.2.3 - 2022-02-01 + +- Dependencies: deduplicated RLP import, PR [#1549](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1549) + +# 4.2.2 - 2021-10-06 + +**Bug Fixes** + +- Adds try-catch for "Missing node in DB" in ReadStream, PR [#1515](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1515) + +## 4.2.1 - 2021-08-17 + +**Bug Fixes** + +- Better error checking for invalid proofs with a differentiation on proofs of non-existence (`Trie.verifyProof()` returns `null`) and invalid proofs where `Trie.verifyProof()` will throw, see [README section](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/trie#merkle-proofs) for further details, PR [#1373](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1373) + +**Maintenance** + +- Remove use of deprecated setRoot, PR [#1376](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1376) + +### Included Source Files + +Source files from the `src` folder are now included in the distribution build, see PR [#1301](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1301). This allows for a better debugging experience in debug tools like Chrome DevTools by having working source map references to the original sources available for inspection. + +## 4.2.0 - 2021-05-20 + +### Changed Delete Behavior: NO Default Node Deletes + +This release changes the behavior on trie node deletes after doing a `commit()` on a checkpoint trie (`CheckpointTrie`). This was the scenario where trie nodes were deleted from the database in older versions of the library. After a long discussion we decided to switch to a more conservative approach and keep the trie nodes in the DB in all scenarios. This now allows for setting back the state root to an older root (e.g. with the `StateManager` included in the `VM`) and still be sure to operate on a consistent and complete trie. This had been reported as a problem in some usage scenarios by third-party users of the library. + +So the default behavior on the trie is now: there are no node deletions happening in all type of setups and usage scenarios, so for a `BaseTrie`, `CheckpointTrie` or `SecureTrie` and working checkpointed or non-checkpointed. + +While this change is not directly breaking, it might nevertheless have side effects depending on your usage scenario. If you use a somewhat larger trie and do a lot of change operations, this will significantly increase the disk space used. We have nevertheless decided to make this a non-breaking release since we don't expect this to be the usual way the trie library is used. Instead the former behavior appeared more as some sort of "bug" when reported by developers integrating the library. + +If you want to switch back to a trie where nodes are deleted (so for non-checkpointed tries directly along a trie operation or for checkpointed tries along a `commit()`) there is a new parameter `deleteFromDB` introduced which can be used to switch to a delete behavior on instantiation (the default is `false` here). + +See: PR [#1219](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1219) + +## 4.1.0 - 2021-02-16 + +This release comes with a reworked checkpointing mechanism (PR [#1030](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1030) and subsequently PR [#1035](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1035)). Instead of copying over the whole DB on checkpoints the operations in between checkpoints are now recorded in memory and either applied in batch on a `Trie.checkpoint()` call or discarded along a `Trie.revert()`. This more fine-grained operational mode leads to a substantial performance gain (up to 50x) when working with larger tries. + +Another performance related bug has been fixed along PR [#127](https://github.com/ethereumjs/merkle-patricia-tree/pull/127) removing an unnecessary double-serialization call on nodes. This gives a general performance gain of 10-20% on putting new values in a trie. + +Other changes: + +## New Features + +A new exported `WalkController` class has been added and `trie.walkTrie()` has been made a public method along. This allows for creating own custom ways to traverse a trie. PR [#135](https://github.com/ethereumjs/merkle-patricia-tree/pull/135) + +### Refactoring, Development and Documentation + +- Better `Trie` code documentation, PR [#125](https://github.com/ethereumjs/merkle-patricia-tree/pull/125) +- Internal `Trie` function reordering & partial retwrite, PR [#125](https://github.com/ethereumjs/merkle-patricia-tree/pull/125) +- Added simple integrated profiling, PR [#128](https://github.com/ethereumjs/merkle-patricia-tree/pull/128) +- Reworked benchmarking to be based on `benchmark.js`, basic CI integration, PR [#130](https://github.com/ethereumjs/merkle-patricia-tree/pull/130) +- Upgrade to `ethereumjs-config` `2.0` libs for linting and formatting, PR [#133](https://github.com/ethereumjs/merkle-patricia-tree/pull/133) +- Switched coverage from `coverall` to `codecov`, PR [#137](https://github.com/ethereumjs/merkle-patricia-tree/pull/137) + +## [4.0.0] - 2020-04-17 + +This release introduces a major API upgrade from callbacks to Promises. + +Example using async/await syntax: + +```typescript +import { BaseTrie as Trie } from 'merkle-patricia-tree' +const trie = new Trie() +async function test() { + await trie.put(Buffer.from('test'), Buffer.from('one')) + const value = await trie.get(Buffer.from('test')) + console.log(value.toString()) // 'one' +} +test() +``` + +### Breaking Changes + +#### Trie methods + +See the [docs](https://github.com/ethereumjs/merkle-patricia-tree/tree/master/docs) for the latest Promise-based method signatures. + +#### Trie.prove renamed to Trie.createProof + +To clarify the method's purpose `Trie.prove` has been renamed to `Trie.createProof`. `Trie.prove` has been deprecated but will remain as an alias for `Trie.createProof` until removed. + +#### Trie raw methods + +`getRaw`, `putRaw` and `delRaw` were deprecated in `v3.0.0` and have been removed from this release. Instead, please use `trie.db.get`, `trie.db.put`, and `trie.db.del`. If using a `SecureTrie` or `CheckpointTrie`, use `trie._maindb` to override the checkpointing mechanism and interact directly with the db. + +#### SecureTrie.copy + +`SecureTrie.copy` now includes checkpoint metadata by default. To maintain original behavior of _not_ copying checkpoint state, pass `false` to param `includeCheckpoints`. + +### Changed + +- Convert trieNode to ES6 class ([#71](https://github.com/ethereumjs/merkle-patricia-tree/pull/71)) +- Merge checkpoint and secure interface with their ES6 classes ([#73](https://github.com/ethereumjs/merkle-patricia-tree/pull/73)) +- Extract db-related methods from baseTrie ([#74](https://github.com/ethereumjs/merkle-patricia-tree/pull/74)) +- \_lookupNode callback to use standard error, response pattern ([#83](https://github.com/ethereumjs/merkle-patricia-tree/pull/83)) +- Accept leveldb in constructor, minor fixes ([#92](https://github.com/ethereumjs/merkle-patricia-tree/pull/92)) +- Refactor TrieNode, add levelup types ([#98](https://github.com/ethereumjs/merkle-patricia-tree/pull/98)) +- Promisify rest of library ([#107](https://github.com/ethereumjs/merkle-patricia-tree/pull/107)) +- Use `Nibbles` type for `number[]` ([#115](https://github.com/ethereumjs/merkle-patricia-tree/pull/115)) +- Upgrade ethereumjs-util to 7.0.0 / Upgrade level-mem to 5.0.1 ([#116](https://github.com/ethereumjs/merkle-patricia-tree/pull/116)) +- Create dual ES5 and ES2017 builds ([#117](https://github.com/ethereumjs/merkle-patricia-tree/pull/117)) +- Include checkpoints by default in SecureTrie.copy ([#119](https://github.com/ethereumjs/merkle-patricia-tree/pull/119)) +- Rename Trie.prove to Trie.createProof ([#122](https://github.com/ethereumjs/merkle-patricia-tree/pull/122)) + +### Added + +- Support for proofs of null/absence. Dried up prove/verify. ([#82](https://github.com/ethereumjs/merkle-patricia-tree/pull/82)) +- Add more Ethereum state DB focused example accessing account values ([#89](https://github.com/ethereumjs/merkle-patricia-tree/pull/89)) + +### Fixed + +- Drop ethereumjs-testing dep and fix bug in branch value update ([#69](https://github.com/ethereumjs/merkle-patricia-tree/pull/69)) +- Fix prove and verifyProof in SecureTrie ([#79](https://github.com/ethereumjs/merkle-patricia-tree/pull/79)) +- Fixed src code links in docs ([#93](https://github.com/ethereumjs/merkle-patricia-tree/pull/93)) + +### Dev / Testing / CI + +- Update tape to v4.10.1 ([#81](https://github.com/ethereumjs/merkle-patricia-tree/pull/81)) +- Org links and git hooks ([#87](https://github.com/ethereumjs/merkle-patricia-tree/pull/87)) +- Use module.exports syntax in util files ([#90](https://github.com/ethereumjs/merkle-patricia-tree/pull/90)) +- Rename deprecated sha3 consts and func to keccak256 ([#91](https://github.com/ethereumjs/merkle-patricia-tree/pull/91)) +- Migrate to Typescript ([#96](https://github.com/ethereumjs/merkle-patricia-tree/pull/96)) +- Fix Travis's xvfb service ([#97](https://github.com/ethereumjs/merkle-patricia-tree/pull/97)) +- Fix test cases and docs ([#104](https://github.com/ethereumjs/merkle-patricia-tree/pull/104)) +- Upgrade CI Provider from Travis to GH Actions ([#105](https://github.com/ethereumjs/merkle-patricia-tree/pull/105)) +- Upgrade test suite to TS ([#106](https://github.com/ethereumjs/merkle-patricia-tree/pull/106)) +- Better document `_formatNode` ([#109](https://github.com/ethereumjs/merkle-patricia-tree/pull/109)) +- Move `failingRefactorTests` to `secure.spec.ts` ([#110](https://github.com/ethereumjs/merkle-patricia-tree/pull/110)) +- Fix test suite typos ([#114](https://github.com/ethereumjs/merkle-patricia-tree/pull/110)) + +[4.0.0]: https://github.com/ethereumjs/merkle-patricia-tree/compare/v3.0.0...v4.0.0 + +## [3.0.0] - 2019-01-03 + +This release comes along with some major version bump of the underlying `level` +database storage backend. If you have the library deeper integrated in one of +your projects make sure that the new DB version plays well with the rest of the +code. + +The release also introduces modern `ES6` JavaScript for the library (thanks @alextsg) +switching to `ES6` classes and clean inheritance on all the modules. + +- Replace `levelup` 1.2.1 + `memdown` 1.0.0 with `level-mem` 3.0.1 and upgrade `level-ws` to 1.0.0, PR [#56](https://github.com/ethereumjs/merkle-patricia-tree/pull/56) +- Support for `ES6` classes, PRs [#57](https://github.com/ethereumjs/merkle-patricia-tree/pull/57), [#61](https://github.com/ethereumjs/merkle-patricia-tree/pull/61) +- Updated `async` and `readable-stream` dependencies (resulting in smaller browser builds), PR [#60](https://github.com/ethereumjs/merkle-patricia-tree/pull/60) +- Updated, automated and cleaned up [API documentation](https://github.com/ethereumjs/merkle-patricia-tree/blob/master/docs/index.md) build, PR [#63](https://github.com/ethereumjs/merkle-patricia-tree/pull/63) + +[3.0.0]: https://github.com/ethereumjs/merkle-patricia-tree/compare/v2.3.2...v3.0.0 + +## [2.3.2] - 2018-09-24 + +- Fixed a bug in verify proof if the tree contains an extension node with an embedded branch node, PR [#51](https://github.com/ethereumjs/merkle-patricia-tree/pull/51) +- Fixed `_scratch` 'leak' to global/window, PR [#42](https://github.com/ethereumjs/merkle-patricia-tree/pull/42) +- Fixed coverage report leaving certain tests, PR [#53](https://github.com/ethereumjs/merkle-patricia-tree/pull/53) + +[2.3.2]: https://github.com/ethereumjs/merkle-patricia-tree/compare/v2.3.1...v2.3.2 + +## [2.3.1] - 2018-03-14 + +- Fix OutOfMemory bug when trying to create a read stream on large trie structures + (e.g. a current state DB from a Geth node), PR [#38](https://github.com/ethereumjs/merkle-patricia-tree/pull/38) +- Fix race condition due to mutated `_getDBs`/`_putDBs`, PR [#28](https://github.com/ethereumjs/merkle-patricia-tree/pull/28) + +[2.3.1]: https://github.com/ethereumjs/merkle-patricia-tree/compare/v2.3.0...v2.3.1 + +## [2.3.0] - 2017-11-30 + +- Methods for merkle proof generation `Trie.prove()` and verification `Trie.verifyProof()` (see [./proof.js](./proof.js)) + +[2.3.0]: https://github.com/ethereumjs/merkle-patricia-tree/compare/v2.2.0...v2.3.0 + +## [2.2.0] - 2017-08-03 + +- Renamed `root` functions argument to `nodeRef` for passing a node reference +- Make `findPath()` (path to node for given key) a public method + +[2.2.0]: https://github.com/ethereumjs/merkle-patricia-tree/compare/v2.1.2...v2.2.0 + +## [2.1.2] - 2016-03-01 + +- Added benchmark (see [./benchmarks/](./benchmarks/)) +- Updated dependencies + +[2.1.2]: https://github.com/ethereumjs/merkle-patricia-tree/compare/v2.1.1...v2.1.2 + +## [2.1.1] - 2016-01-06 + +- Added README, API documentation +- Dependency updates + +[2.1.1]: https://github.com/ethereumjs/merkle-patricia-tree/compare/2.0.3...v2.1.1 + +## [2.0.3] - 2015-09-24 + +- Initial, first of the currently released version on npm + +[2.0.3]: https://github.com/ethereumjs/merkle-patricia-tree/compare/1.1.x...2.0.3 diff --git a/packages/verkle/README.md b/packages/verkle/README.md new file mode 100644 index 0000000000..903b198318 --- /dev/null +++ b/packages/verkle/README.md @@ -0,0 +1,281 @@ +# @ethereumjs/verkle + +[![NPM Package][trie-npm-badge]][trie-npm-link] +[![GitHub Issues][trie-issues-badge]][trie-issues-link] +[![Actions Status][trie-actions-badge]][trie-actions-link] +[![Code Coverage][trie-coverage-badge]][trie-coverage-link] +[![Discord][discord-badge]][discord-link] + +Note: this README has been updated containing the changes from our next breaking release round [UNRELEASED] targeted for Summer 2023. See the README files from the [maintenance-v6](https://github.com/ethereumjs/ethereumjs-monorepo/tree/maintenance-v6/) branch for documentation matching our latest releases. + +| Implementation of the [Modified Merkle Patricia Trie](https://ethereum.org/en/developers/docs/data-structures-and-encoding/patricia-merkle-trie/) as specified in the [Ethereum Yellow Paper](http://gavwood.com/Paper.pdf) | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + +> The modified Merkle Patricia tree (trie) provides a persistent data structure to map between arbitrary-length binary data (byte arrays). It is defined in terms of a mutable data structure to map between 256-bit binary fragments and arbitrary-length binary data. The core of the trie, and its sole requirement in terms of the protocol specification, is to provide a single 32-byte value that identifies a given set of key-value pairs. + +## Installation + +To obtain the latest version, simply require the project using `npm`: + +```shell +npm install @ethereumjs/trie +``` + +## 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 { Trie } from '@ethereumjs/trie' +import { bytesToUtf8, MapDB, utf8ToBytes } from '@ethereumjs/util' + +const trie = new Trie({ db: new MapDB() }) + +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() +``` + +### Use with static constructor + +```typescript +import { Trie } from '@ethereumjs/trie' +import { bytesToUtf8, utf8ToBytes } from '@ethereumjs/util' + +const trie = await Trie.create() + +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() +``` + +When the static `Trie.create` constructor is used without any options, the `trie` object is instantiated with defaults configured to match the Etheruem production spec (i.e. keys are hashed using SHA256). It also persists the state root of the tree on each write operation, ensuring that your trie remains in the state you left it when you start your application the next time. + +### `Trie` Configuration Options + +#### Database Options + +The `DB` opt in the `TrieOpts` allows you to use any database that conforms to the `DB` interface to store the trie data in. We provide several [examples](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/trie/examples) for database implementations. The [level.js](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/trie/examples/level.js) example is used in the `ethereumjs client` while [lmdb.js](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/trie/examples/lmdb.js) is an alternative implementation that uses the popular [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database) as its underlying database. + +If no `db` option is provided, an in-memory database powered by [a Javascript Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) will fulfill this role (imported from `@ethereumjs/util`, see [mapDB](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/util/src/mapDB.ts) module). + +If you want to use an alternative database, you can integrate your own by writing a DB wrapper that conforms to the [`DB` interface](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/util/src/db.ts) (in `@ethereumjs/util`). The `DB` interface defines the methods `get`, `put`, `del`, `batch` and `copy` that a concrete implementation of the `DB` interface will need to implement. + +##### LevelDB + +As an example, to leveage `LevelDB` for all operations then you should create a file with the [following implementation from our recipes](./recipes//level.ts) in your project. Then instantiate your DB and trie as below: + +```typescript +import { Trie } from '@ethereumjs/trie' +import { Level } from 'level' + +import { LevelDB } from './your-level-implementation' + +const trie = new Trie({ db: new LevelDB(new Level('MY_TRIE_DB_LOCATION')) }) +``` + +#### Node Deletion (Pruning) + +By default, the deletion of trie nodes from the underlying database does not occur in order to avoid corrupting older trie states (as of `v4.2.0`). Should you only wish to work with the latest state of a trie, you can switch to a delete behavior (for example, if you wish to save disk space) by using the `useNodePruning` constructor option (see related release notes in the changelog for further details). + +#### Root Persistence + +You can enable persistence by setting the `useRootPersistence` option to `true` when constructing a trie through the `Trie.create` function. As such, this value is preserved when creating copies of the trie and is incapable of being modified once a trie is instantiated. + +```typescript +import { Trie } from '@ethereumjs/trie' + +const trie = await Trie.create({ + useRootPersistence: true, +}) +``` + +## Proofs + +### Merkle Proofs + +The `createProof` and `verifyProof` functions allow you to verify that a certain value does or does not exist within a Merkle Patricia Tree with a given root. + +#### Proof-of-Inclusion + +The following code demonstrates how to construct and subsequently verify a proof that confirms the existence of the key `test` (which corresponds with the value `one`) within the given trie. This is also known as inclusion, hence the name 'Proof-of-Inclusion.' + +```typescript +import { Trie } from '@ethereumjs/trie' +import { bytesToUtf8, utf8ToBytes } from '@ethereumjs/util' + +const trie = new Trie() + +async function test() { + await trie.put(utf8ToBytes('test'), utf8ToBytes('one')) + const proof = await trie.createProof(utf8ToBytes('test')) + const value = await trie.verifyProof(trie.root(), utf8ToBytes('test'), proof) + console.log(value ? bytesToUtf8(value) : 'not found') // 'one' +} + +test() +``` + +#### Proof-of-Exclusion + +The following code demonstrates how to construct and subsequently verify a proof that confirms that the key `test3` does not exist within the given trie. This is also known as exclusion, hence the name 'Proof-of-Exclusion.' + +```typescript +import { Trie } from '@ethereumjs/trie' +import { bytesToUtf8, utf8ToBytes } from '@ethereumjs/util' + +const trie = new Trie() + +async function test() { + await trie.put(utf8ToBytes('test'), utf8ToBytes('one')) + await trie.put(utf8ToBytes('test2'), utf8ToBytes('two')) + const proof = await trie.createProof(utf8ToBytes('test3')) + const value = await trie.verifyProof(trie.root(), utf8ToBytes('test3'), proof) + console.log(value ? bytesToUtf8(value) : 'null') // null +} + +test() +``` + +#### Invalid Proofs + +If `verifyProof` detects an invalid proof, it will throw an error. While contrived, the below example illustrates the resulting error condition in the event a prover tampers with the data in a merkle proof. + +```typescript +import { Trie } from '@ethereumjs/trie' +import { bytesToUtf8, utf8ToBytes } from '@ethereumjs/util' + +const trie = new Trie() + +async function test() { + await trie.put(utf8ToBytes('test'), utf8ToBytes('one')) + await trie.put(utf8ToBytes('test2'), utf8ToBytes('two')) + const proof = await trie.createProof(utf8ToBytes('test2')) + proof[1].reverse() + try { + const value = await trie.verifyProof(trie.root(), utf8ToBytes('test2'), proof) + console.log(value ? bytesToUtf8(value) : 'not found') // results in error + } catch (err) { + console.log(err) // Missing node in DB + } +} + +test() +``` + +### Range Proofs + +You may use the `Trie.verifyRangeProof()` function to confirm if the given leaf nodes and edge proof possess the capacity to prove that the given trie leaves' range matches the specific root (which is useful for snap sync, for instance). + +## 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 a 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 can not provide. + +### Buffer -> Uint8Array + +With the breaking releases from Summer 2023 we have removed all Node.js specific `Buffer` usages from our libraries and replace these with [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) representations, which are available both in Node.js and the browser (`Buffer` is a subclass of `Uint8Array`). + +We have converted existing Buffer conversion methods to Uint8Array conversion methods in the [@ethereumjs/util](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/util) `bytes` module, see the respective README section for guidance. + +### BigInt Support + +With the 5.0.0 release, [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) takes the place of [BN.js](https://github.com/indutny/bn.js/). + +BigInt is a primitive that is used to represent and manipulate primitive `bigint` values that the number primitive is incapable of representing as a result of their magnitude. `ES2020` saw the introduction of this particular feature. Note that this version update resulted in the altering of number-related API signatures and that the minimal build target is now set to `ES2020`. + +## Benchmarking + +You will find two simple **benchmarks** in the `benchmarks` folder: + +- `random.ts` runs random `PUT` operations on the tree, and +- `checkpointing.ts` runs checkpoints and commits between `PUT` operations + +A third benchmark using mainnet data to simulate real load is also being considered. + +You may run benchmarks using: + +```shell +npm run benchmarks +``` + +To run a **profiler** on the `random.ts` benchmark and generate a flamegraph with [0x](https://github.com/davidmarkclements/0x), you may use: + +```shell +npm run profiling +``` + +0x processes the stacks and generates a profile folder (`.0x`) containing [`flamegraph.html`](https://github.com/davidmarkclements/0x/blob/master/docs/ui.md). + +## References + +- Wiki + - [Ethereum Trie Specification](https://github.com/ethereum/wiki/wiki/Patricia-Tree) +- Blog posts + - [Ethereum's Merkle Patricia Trees - An Interactive JavaScript Tutorial](https://rockwaterweb.com/ethereum-merkle-patricia-trees-javascript-tutorial/) + - [Merkling in Ethereum](https://blog.ethereum.org/2015/11/15/merkling-in-ethereum/) + - [Understanding the Ethereum Trie](https://easythereentropy.wordpress.com/2014/06/04/understanding-the-ethereum-trie/) (This is worth reading, but mind the outdated Python libraries) +- Videos + - [Trie and Patricia Trie Overview](https://www.youtube.com/watch?v=jXAHLqQthKw&t=26s) + +## 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 +[trie-npm-badge]: https://img.shields.io/npm/v/@ethereumjs/trie.svg +[trie-npm-link]: https://www.npmjs.com/package/@ethereumjs/trie +[trie-issues-badge]: https://img.shields.io/github/issues/ethereumjs/ethereumjs-monorepo/package:%20trie?label=issues +[trie-issues-link]: https://github.com/ethereumjs/ethereumjs-monorepo/issues?q=is%3Aopen+is%3Aissue+label%3A"package%3A+trie" +[trie-actions-badge]: https://github.com/ethereumjs/ethereumjs-monorepo/workflows/Trie/badge.svg +[trie-actions-link]: https://github.com/ethereumjs/ethereumjs-monorepo/actions?query=workflow%3A%22Trie%22 +[trie-coverage-badge]: https://codecov.io/gh/ethereumjs/ethereumjs-monorepo/branch/master/graph/badge.svg?flag=trie +[trie-coverage-link]: https://codecov.io/gh/ethereumjs/ethereumjs-monorepo/tree/master/packages/trie diff --git a/packages/verkle/package.json b/packages/verkle/package.json new file mode 100644 index 0000000000..1c71bfb374 --- /dev/null +++ b/packages/verkle/package.json @@ -0,0 +1,75 @@ +{ + "name": "@ethereumjs/verkle", + "version": "1.0.0-rc.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 -- trie", + "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-rc.1", + "@ethereumjs/util": "9.0.0-rc.1", + "@types/readable-stream": "^2.3.13", + "lru-cache": "^10.0.0", + "ethereum-cryptography": "^2.1.2", + "readable-stream": "^3.6.0" + }, + "devDependencies": { + "@ethereumjs/genesis": "0.1.0-rc.1", + "@types/benchmark": "^1.0.33", + "abstract-level": "^1.0.3", + "level": "^8.0.0", + "level-legacy": "npm:level@^7.0.0", + "level-mem": "^6.0.1", + "levelup": "^5.1.1", + "lmdb": "^2.5.3", + "memory-level": "^1.0.0", + "micro-bmark": "0.2.0" + }, + "engines": { + "node": ">=18" + } +} 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'], +} From 99c208e46b4eb609ace5f23242bab7f1c767b73c Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sat, 29 Jul 2023 10:50:27 -0400 Subject: [PATCH 02/49] verkle: scaffold verkle trie implementation and types --- packages/verkle/src/db/checkpoint.ts | 275 ++++++++++++++ packages/verkle/src/db/index.ts | 1 + packages/verkle/src/index.ts | 2 + packages/verkle/src/node/index.ts | 2 + packages/verkle/src/node/types.ts | 0 packages/verkle/src/node/verkleNode.ts | 70 ++++ packages/verkle/src/types.ts | 79 ++++ packages/verkle/src/util/lock.ts | 42 +++ packages/verkle/src/verkleTrie.ts | 447 +++++++++++++++++++++++ packages/verkle/vitest.config.browser.ts | 7 + 10 files changed, 925 insertions(+) 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/index.ts create mode 100644 packages/verkle/src/node/types.ts create mode 100644 packages/verkle/src/node/verkleNode.ts create mode 100644 packages/verkle/src/types.ts create mode 100644 packages/verkle/src/util/lock.ts create mode 100644 packages/verkle/src/verkleTrie.ts create mode 100644 packages/verkle/vitest.config.browser.ts diff --git a/packages/verkle/src/db/checkpoint.ts b/packages/verkle/src/db/checkpoint.ts new file mode 100644 index 0000000000..7027eae1b0 --- /dev/null +++ b/packages/verkle/src/db/checkpoint.ts @@ -0,0 +1,275 @@ +import { + KeyEncoding, + ValueEncoding, + bytesToUnprefixedHex, + unprefixedHexToBytes, +} 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 + // 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) { + // @ts-ignore + 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: unprefixedHexToBytes(key), + }) + } else { + batchOp.push({ + type: 'put', + key: unprefixedHexToBytes(key), + value, + }) + } + } + 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 = bytesToUnprefixedHex(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 valueHex = await this.db.get(keyHex, { + keyEncoding: KeyEncoding.String, + valueEncoding: ValueEncoding.String, + }) + this._stats.db.reads += 1 + if (valueHex !== undefined) { + this._stats.db.hits += 1 + } + const value = valueHex !== undefined ? unprefixedHexToBytes(valueHex) : undefined + 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 = bytesToUnprefixedHex(key) + const valueHex = bytesToUnprefixedHex(value) + if (this.hasCheckpoints()) { + // put value in diff cache + this.checkpoints[this.checkpoints.length - 1].keyValueMap.set(keyHex, value) + } else { + await this.db.put(keyHex, valueHex, { + keyEncoding: KeyEncoding.String, + valueEncoding: ValueEncoding.String, + }) + 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 = bytesToUnprefixedHex(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(keyHex, { + keyEncoding: KeyEncoding.String, + }) + 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: bytesToUnprefixedHex(op.key), + value: op.type === 'put' ? bytesToUnprefixedHex(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/index.ts b/packages/verkle/src/node/index.ts new file mode 100644 index 0000000000..4570353feb --- /dev/null +++ b/packages/verkle/src/node/index.ts @@ -0,0 +1,2 @@ +export * from './types.js' +export * from './verkleNode.js' diff --git a/packages/verkle/src/node/types.ts b/packages/verkle/src/node/types.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/verkle/src/node/verkleNode.ts b/packages/verkle/src/node/verkleNode.ts new file mode 100644 index 0000000000..4b2773f0a6 --- /dev/null +++ b/packages/verkle/src/node/verkleNode.ts @@ -0,0 +1,70 @@ +export interface VerkleNodeOptions { + node: InternalNode | LeafNode +} + +export class VerkleNode { + // TODO?: Directly make the VerkleNode either an InternalNode of LeafNode instead of having a node property + public node: InternalNode | LeafNode + + constructor(options: VerkleNodeOptions) { + this.node = options.node + } + // Commit computes the commitment of the node. The + // result (the curve point) is cached. + commit(): Point { + throw new Error('Not implemented') + } + + getCommitment(): Point { + return this.node.commitment + } + + getDepth(): number { + return this.node.depth + } + + // Hash returns the field representation of the commitment. + hash(): any { + throw new Error('Not implemented') + } + + // TODO: Turn these two into typeguards? + isInternalNode(): boolean { + throw new Error('Not implemented') + } + + isLeafNode(): boolean { + throw new Error('Not implemented') + } + + // Serialize encodes the node to RLP. + serialize(): Uint8Array { + throw new Error('Not implemented') + } +} + +type Point = any + +interface BaseNode { + // Node depth in the trie, in bits + depth: number + + // Value of the commitment + commitment: Point +} + +// Represents an internal node (a non-leaf node) +export interface InternalNode extends BaseNode { + // List of child nodes of this internal node. + children: VerkleNode[] + + // Values of the child commitments before the trie is modified by inserts. + copyOnWrite: Record +} + +export interface LeafNode extends BaseNode { + stem: Uint8Array + values: Uint8Array[] + c1: Point + c2: Point +} diff --git a/packages/verkle/src/types.ts b/packages/verkle/src/types.ts new file mode 100644 index 0000000000..6ceab55bd3 --- /dev/null +++ b/packages/verkle/src/types.ts @@ -0,0 +1,79 @@ +import { utf8ToBytes } from '@ethereumjs/util' + +// import type { BranchNode, ExtensionNode, LeafNode } from './node/index.js' +// import type { WalkController } from './util/walkController.js' +import type { DB } from '@ethereumjs/util' + +export type VerkleTrieNode = any + +export type Nibbles = number[] + +// Branch and extension nodes might store +// hash to next node, or embed it if its len < 32 +export type EmbeddedNode = Uint8Array | Uint8Array[] + +export type Proof = Uint8Array[] + +export type FoundNodeFunction = ( + nodeRef: Uint8Array, + node: VerkleTrieNode | null, + key: Nibbles, + walkController: any +) => void + +export interface VerkleTrieOpts { + /** + * A database instance. + */ + db?: DB + + /** + * A `Uint8Array` for the root of a previously stored trie + */ + root?: Uint8Array + + /** + * Store the root inside the database after every `write` operation + */ + useRootPersistence?: boolean + + /** + * Flag to prune the trie. When set to `true`, each time a value is overridden, + * unreachable nodes will be pruned (deleted) from the trie + */ + useNodePruning?: boolean + + /** + * LRU cache for trie nodes to allow for faster node retrieval. + * + * Default: 0 (deactivated) + */ + cacheSize?: number +} + +export type VerkleTrieOptsWithDefaults = VerkleTrieOpts & { + useRootPersistence: boolean + useNodePruning: 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 const ROOT_DB_KEY = utf8ToBytes('__root__') diff --git a/packages/verkle/src/util/lock.ts b/packages/verkle/src/util/lock.ts new file mode 100644 index 0000000000..d0f0a9151a --- /dev/null +++ b/packages/verkle/src/util/lock.ts @@ -0,0 +1,42 @@ +// Based on https://github.com/jsoendermann/semaphore-async-await/blob/master/src/Semaphore.ts +export class Lock { + private permits: number = 1 + private promiseResolverQueue: Array<(v: boolean) => void> = [] + + /** + * Returns a promise used to wait for a permit to become available. This method should be awaited on. + * @returns A promise that gets resolved when execution is allowed to proceed. + */ + public async acquire(): Promise { + if (this.permits > 0) { + this.permits -= 1 + return Promise.resolve(true) + } + + // If there is no permit available, we return a promise that resolves once the semaphore gets + // signaled enough times that permits is equal to one. + return new Promise((resolver) => this.promiseResolverQueue.push(resolver)) + } + + /** + * Increases the number of permits by one. If there are other functions waiting, one of them will + * continue to execute in a future iteration of the event loop. + */ + public release(): void { + this.permits += 1 + + if (this.permits > 1 && this.promiseResolverQueue.length > 0) { + // eslint-disable-next-line no-console + console.warn('Lock.permits should never be > 0 when there is someone waiting.') + } else if (this.permits === 1 && this.promiseResolverQueue.length > 0) { + // If there is someone else waiting, immediately consume the permit that was released + // at the beginning of this function and let the waiting function resume. + this.permits -= 1 + + const nextResolver = this.promiseResolverQueue.shift() + if (nextResolver) { + nextResolver(true) + } + } + } +} diff --git a/packages/verkle/src/verkleTrie.ts b/packages/verkle/src/verkleTrie.ts new file mode 100644 index 0000000000..605d23eb94 --- /dev/null +++ b/packages/verkle/src/verkleTrie.ts @@ -0,0 +1,447 @@ +import { + KeyEncoding, + MapDB, + ValueEncoding, + bytesToUnprefixedHex, + bytesToUtf8, + equalsBytes, + unprefixedHexToBytes, +} from '@ethereumjs/util' + +import { CheckpointDB } from './db/checkpoint.js' +import { + type EmbeddedNode, + type FoundNodeFunction, + type Nibbles, + type Proof, + ROOT_DB_KEY, + type VerkleTrieOpts, + type VerkleTrieOptsWithDefaults, +} from './types.js' +import { Lock } from './util/lock.js' + +import type { LeafNode, VerkleNode } from './node/verkleNode.js' +import type { BatchDBOp, DB, PutBatch } from '@ethereumjs/util' + +interface Path { + node: VerkleNode | null + remaining: Nibbles + stack: VerkleNode[] +} + +/** + * The basic verkle trie interface, use with `import { VerkleTrie } from '@ethereumjs/verkle'`. + */ +export class VerkleTrie { + protected readonly _opts: VerkleTrieOptsWithDefaults = { + useRootPersistence: false, + useNodePruning: 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 ?? new MapDB()) + + this.EMPTY_TRIE_ROOT = new Uint8Array() // TODO + 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) { + const rootHex = await opts?.db.get(bytesToUnprefixedHex(key), { + keyEncoding: KeyEncoding.String, + valueEncoding: ValueEncoding.String, + }) + opts.root = rootHex !== undefined ? unprefixedHexToBytes(rootHex) : undefined + } else { + await opts?.db.put(bytesToUnprefixedHex(key), bytesToUnprefixedHex(opts.root), { + keyEncoding: KeyEncoding.String, + valueEncoding: ValueEncoding.String, + }) + } + } + + 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 { + throw new Error('Not implemented') + } + + /** + * 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 + * @param value + * @returns A Promise that resolves once value is stored. + */ + async put(key: Uint8Array, value: Uint8Array): Promise { + throw new Error('Not implemented') + } + + /** + * Deletes a value given a `key` from the trie + * (delete operations are only executed on DB with `deleteFromDB` set to `true`) + * @param key + * @returns A Promise that resolves once value is deleted. + */ + async del(key: Uint8Array): Promise { + throw new Error('Not implemented') + } + + /** + * 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 { + throw new Error('Not implemented') + } + + /** + * 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 { + throw new Error('Not implemented') + } + + /** + * Executes a callback for each node in the trie. + * @param onFound - callback to call when a node is found. + * @returns Resolves when finished walking trie. + */ + async walkAllNodes(onFound: OnFound): Promise { + throw new Error('Not implemented') + } + + /** + * Executes a callback for each value node in the trie. + * @param onFound - callback to call when a node is found. + * @returns Resolves when finished walking trie. + */ + async walkAllValueNodes(onFound: OnFound): Promise { + throw new Error('Not implemented') + } + + /** + * 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 { + throw new Error('Not implemented') + } + + /** + * Updates a node. + * @private + * @param key + * @param value + * @param keyRemainder + * @param stack + */ + protected async _updateNode( + k: Uint8Array, + value: Uint8Array, + keyRemainder: Nibbles, + stack: VerkleNode[] + ): Promise { + throw new Error('Not implemented') + } + + /** + * Deletes a node from the trie. + * @private + */ + protected async _deleteNode(k: 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: Nibbles, stack: VerkleNode[], opStack: BatchDBOp[]): 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: BatchDBOp[], + remove: boolean = false + ): Uint8Array | (EmbeddedNode | null)[] { + 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(this.appliedKey(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: FoundNodeFunction): Promise { + throw new Error('Not implemented') + } + + /** + * Returns the key practically applied for trie construction + * depending on the `useKeyHashing` option being set or not. + * @param key + */ + protected appliedKey(key: Uint8Array) { + throw new Error('Not implemented') + } + + protected hash(msg: Uint8Array): Uint8Array { + 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/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], + }, +}) From 38ca580e9631246c0b939cdf911143daa2476271 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sat, 29 Jul 2023 11:45:24 -0400 Subject: [PATCH 03/49] verkle: fix installation issues --- package-lock.json | 40 +++++++++++++++++++++++++++++++ packages/verkle/src/node/index.ts | 1 - packages/verkle/src/node/types.ts | 0 packages/verkle/src/verkleTrie.ts | 38 ++++------------------------- 4 files changed, 45 insertions(+), 34 deletions(-) delete mode 100644 packages/verkle/src/node/types.ts diff --git a/package-lock.json b/package-lock.json index ab286d18a3..d7b9979e3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1039,6 +1039,10 @@ "resolved": "packages/util", "link": true }, + "node_modules/@ethereumjs/verkle": { + "resolved": "packages/verkle", + "link": true + }, "node_modules/@ethereumjs/vm": { "resolved": "packages/vm", "link": true @@ -16049,6 +16053,42 @@ } } }, + "packages/verkle": { + "name": "@ethereumjs/verkle", + "version": "1.0.0-rc.1", + "license": "MPL-2.0", + "dependencies": { + "@ethereumjs/rlp": "5.0.0-rc.1", + "@ethereumjs/util": "9.0.0-rc.1", + "@types/readable-stream": "^2.3.13", + "ethereum-cryptography": "^2.1.2", + "lru-cache": "^10.0.0", + "readable-stream": "^3.6.0" + }, + "devDependencies": { + "@ethereumjs/genesis": "0.1.0-rc.1", + "@types/benchmark": "^1.0.33", + "abstract-level": "^1.0.3", + "level": "^8.0.0", + "level-legacy": "npm:level@^7.0.0", + "level-mem": "^6.0.1", + "levelup": "^5.1.1", + "lmdb": "^2.5.3", + "memory-level": "^1.0.0", + "micro-bmark": "0.2.0" + }, + "engines": { + "node": ">=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-rc.1", diff --git a/packages/verkle/src/node/index.ts b/packages/verkle/src/node/index.ts index 4570353feb..ae0e9fd229 100644 --- a/packages/verkle/src/node/index.ts +++ b/packages/verkle/src/node/index.ts @@ -1,2 +1 @@ -export * from './types.js' export * from './verkleNode.js' diff --git a/packages/verkle/src/node/types.ts b/packages/verkle/src/node/types.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/verkle/src/verkleTrie.ts b/packages/verkle/src/verkleTrie.ts index 605d23eb94..e8f84b8bae 100644 --- a/packages/verkle/src/verkleTrie.ts +++ b/packages/verkle/src/verkleTrie.ts @@ -20,8 +20,8 @@ import { } from './types.js' import { Lock } from './util/lock.js' -import type { LeafNode, VerkleNode } from './node/verkleNode.js' -import type { BatchDBOp, DB, PutBatch } from '@ethereumjs/util' +import type { VerkleNode } from './node/verkleNode.js' +import type { BatchDBOp, DB } from '@ethereumjs/util' interface Path { node: VerkleNode | null @@ -179,34 +179,6 @@ export class VerkleTrie { throw new Error('Not implemented') } - /** - * 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 { - throw new Error('Not implemented') - } - - /** - * Executes a callback for each node in the trie. - * @param onFound - callback to call when a node is found. - * @returns Resolves when finished walking trie. - */ - async walkAllNodes(onFound: OnFound): Promise { - throw new Error('Not implemented') - } - - /** - * Executes a callback for each value node in the trie. - * @param onFound - callback to call when a node is found. - * @returns Resolves when finished walking trie. - */ - async walkAllValueNodes(onFound: OnFound): Promise { - throw new Error('Not implemented') - } - /** * Creates the initial node from an empty tree. * @private @@ -218,7 +190,7 @@ export class VerkleTrie { /** * Retrieves a node from db by hash. */ - async lookupNode(node: Uint8Array | Uint8Array[]): Promise { + async lookupNode(node: Uint8Array | Uint8Array[]): Promise { throw new Error('Not implemented') } @@ -374,7 +346,7 @@ export class VerkleTrie { * called by {@link ScratchReadStream} * @private */ - protected async _findDbNodes(onFound: FoundNodeFunction): Promise { + protected async _findDbNodes(onFound: () => void): Promise { throw new Error('Not implemented') } @@ -383,7 +355,7 @@ export class VerkleTrie { * depending on the `useKeyHashing` option being set or not. * @param key */ - protected appliedKey(key: Uint8Array) { + protected appliedKey(key: Uint8Array): Uint8Array { throw new Error('Not implemented') } From 30ecc260947769521eb854ccac5d939cfa632464 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Fri, 4 Aug 2023 09:11:05 -0400 Subject: [PATCH 04/49] verkle: move rust verkle wasm to verkle package --- .../src/rust-verkle-wasm/LICENSE_APACHE | 176 +++++++++ .../verkle/src/rust-verkle-wasm/LICENSE_MIT | 25 ++ .../verkle/src/rust-verkle-wasm/README.md | 22 ++ .../verkle/src/rust-verkle-wasm/package.json | 17 + .../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 + 8 files changed, 607 insertions(+) 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/README.md create mode 100644 packages/verkle/src/rust-verkle-wasm/package.json 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 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/README.md b/packages/verkle/src/rust-verkle-wasm/README.md new file mode 100644 index 0000000000..76250314bf --- /dev/null +++ b/packages/verkle/src/rust-verkle-wasm/README.md @@ -0,0 +1,22 @@ +# Quick Start + +## To compile Rust to Wasm + +For ESM + +- run `wasm-pack build`. You should see a `pkg` folder appear. + +For a NodeJS module + +- run `wasm-pack build --target nodejs`. + +## To call Rust from Javascript as WASM + +For an ESM example, use below + +- cd into `js_code` +- run `npm install`. You should see a `node_modules` folder appear. +- run `node -r esm stateless_update.js` you should see a Uint8Array in the console. + +For NodeJS + Typescript, import in any `.ts` file as below: +`import wasm from 'path/to/rust-verkle-wasm/pkg/rust_verkle_wasm'` diff --git a/packages/verkle/src/rust-verkle-wasm/package.json b/packages/verkle/src/rust-verkle-wasm/package.json new file mode 100644 index 0000000000..d51d453080 --- /dev/null +++ b/packages/verkle/src/rust-verkle-wasm/package.json @@ -0,0 +1,17 @@ +{ + "name": "rust-verkle-wasm", + "collaborators": [ + "Kevaundray Wedderburn " + ], + "version": "0.1.0", + "license": "MIT or APACHE", + "files": [ + "rust_verkle_wasm_bg.wasm", + "rust_verkle_wasm.js", + "rust_verkle_wasm.d.ts", + "LICENSE_APACHE", + "LICENSE_MIT" + ], + "main": "rust_verkle_wasm.js", + "types": "rust_verkle_wasm.d.ts" +} 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+ Date: Fri, 4 Aug 2023 14:04:25 -0400 Subject: [PATCH 05/49] verkle: remove package json and readme from rust verkle wasm dir --- .../verkle/src/rust-verkle-wasm/README.md | 22 ------------------- .../verkle/src/rust-verkle-wasm/package.json | 17 -------------- 2 files changed, 39 deletions(-) delete mode 100644 packages/verkle/src/rust-verkle-wasm/README.md delete mode 100644 packages/verkle/src/rust-verkle-wasm/package.json diff --git a/packages/verkle/src/rust-verkle-wasm/README.md b/packages/verkle/src/rust-verkle-wasm/README.md deleted file mode 100644 index 76250314bf..0000000000 --- a/packages/verkle/src/rust-verkle-wasm/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Quick Start - -## To compile Rust to Wasm - -For ESM - -- run `wasm-pack build`. You should see a `pkg` folder appear. - -For a NodeJS module - -- run `wasm-pack build --target nodejs`. - -## To call Rust from Javascript as WASM - -For an ESM example, use below - -- cd into `js_code` -- run `npm install`. You should see a `node_modules` folder appear. -- run `node -r esm stateless_update.js` you should see a Uint8Array in the console. - -For NodeJS + Typescript, import in any `.ts` file as below: -`import wasm from 'path/to/rust-verkle-wasm/pkg/rust_verkle_wasm'` diff --git a/packages/verkle/src/rust-verkle-wasm/package.json b/packages/verkle/src/rust-verkle-wasm/package.json deleted file mode 100644 index d51d453080..0000000000 --- a/packages/verkle/src/rust-verkle-wasm/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "rust-verkle-wasm", - "collaborators": [ - "Kevaundray Wedderburn " - ], - "version": "0.1.0", - "license": "MIT or APACHE", - "files": [ - "rust_verkle_wasm_bg.wasm", - "rust_verkle_wasm.js", - "rust_verkle_wasm.d.ts", - "LICENSE_APACHE", - "LICENSE_MIT" - ], - "main": "rust_verkle_wasm.js", - "types": "rust_verkle_wasm.d.ts" -} From 2d715240ab29551f0e3f6b90ec7d765eef5cc47b Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Fri, 4 Aug 2023 14:04:37 -0400 Subject: [PATCH 06/49] util: add some bigint LE utils for verkle --- packages/util/src/bytes.ts | 61 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/packages/util/src/bytes.ts b/packages/util/src/bytes.ts index 3f47b4afd3..770e044f45 100644 --- a/packages/util/src/bytes.ts +++ b/packages/util/src/bytes.ts @@ -429,5 +429,66 @@ export const concatBytes = (...arrays: Uint8Array[]): Uint8Array => { return result } +/** + * @notice Read a 32-bit little-endian integer from a Uint8Array + * @param {Uint8Array} bytes The input Uint8Array from which to read the 32-bit integer. + * @return {number} The 32-bit little-endian integer read from the input Uint8Arrays. + */ +export function readInt32LE(bytes: Uint8Array): number { + return (bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24)) >>> 0 +} + +/** + * @notice Read a 64-bit little-endian bigint from a Uint8Array + * @param {Uint8Array} bytes The input Uint8Array from which to read the 64-bit bigint. + * @return {bigint} The 64-bit little-endian bigint read from the input Uint8Arrays. + */ +export function readBigInt64LE(bytes: Uint8Array): bigint { + const lo = BigInt((bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24)) >>> 0) + const hi = BigInt((bytes[4] | (bytes[5] << 8) | (bytes[6] << 16) | (bytes[7] << 24)) >>> 0) + + return (hi << BigInt(32)) | lo +} + +/** + * @notice Write a 32-bit little-endian number to a Uint8Array. + * @param {number} number The number value to write to the Uint8Array. + * @return {Uint8Array} A Uint8Array of length 32 containing the 32-bit little-endian number. + */ +export function writeInt32LE(number: number): Uint8Array { + const bytes = new Uint8Array(32) + + bytes[0] = number & 0xff + bytes[1] = (number >> 8) & 0xff + bytes[2] = (number >> 16) & 0xff + bytes[3] = (number >> 24) & 0xff + + return bytes +} + +/** + * @notice Write a 64-bit little-endian bigint to a Uint8Array. + * @param {bigint} bigint The bigint value to write to the Uint8Array. + * @return {Uint8Array} A Uint8Array of length 32 containing the 64-bit little-endian bigint. + */ +export function writeBigInt64LE(bigint: bigint): Uint8Array { + const bytes = new Uint8Array(32) + + const lo = BigInt.asUintN(32, bigint) + const hi = BigInt.asUintN(32, bigint >> BigInt(32)) + + bytes[0] = Number(lo & BigInt(0xff)) + bytes[1] = Number((lo >> BigInt(8)) & BigInt(0xff)) + bytes[2] = Number((lo >> BigInt(16)) & BigInt(0xff)) + bytes[3] = Number((lo >> BigInt(24)) & BigInt(0xff)) + + bytes[4] = Number(hi & BigInt(0xff)) + bytes[5] = Number((hi >> BigInt(8)) & BigInt(0xff)) + bytes[6] = Number((hi >> BigInt(16)) & BigInt(0xff)) + bytes[7] = Number((hi >> BigInt(24)) & BigInt(0xff)) + + return bytes +} + // eslint-disable-next-line no-restricted-imports export { bytesToUtf8, equalsBytes, utf8ToBytes } from 'ethereum-cryptography/utils.js' From 4145ef3f028d7ee3f3ce1303aa12e08459eef027 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Fri, 4 Aug 2023 14:04:59 -0400 Subject: [PATCH 07/49] verkle: refactor constants and cryptographic methods to the verkle package --- packages/verkle/src/util/constants.ts | 17 +++++++ packages/verkle/src/util/crypto.ts | 65 +++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 packages/verkle/src/util/constants.ts create mode 100644 packages/verkle/src/util/crypto.ts diff --git a/packages/verkle/src/util/constants.ts b/packages/verkle/src/util/constants.ts new file mode 100644 index 0000000000..674b315c0c --- /dev/null +++ b/packages/verkle/src/util/constants.ts @@ -0,0 +1,17 @@ +/** + * Tree key export constants. + */ +export const VERSION_LEAF_KEY = 0 +export const BALANCE_LEAF_KEY = 1 +export const NONCE_LEAF_KEY = 2 +export const CODE_KECCAK_LEAF_KEY = 3 +export const CODE_SIZE_LEAF_KEY = 4 + +export const HEADER_STORAGE_OFFSET = 64 +export const CODE_OFFSET = 128 +export const VERKLE_NODE_WIDTH = 256 +export const MAIN_STORAGE_OFFSET = 256 ** 31 + +const PUSH_OFFSET = 95 +export const PUSH1 = PUSH_OFFSET + 1 +export const PUSH32 = PUSH_OFFSET + 32 diff --git a/packages/verkle/src/util/crypto.ts b/packages/verkle/src/util/crypto.ts new file mode 100644 index 0000000000..fa55469b13 --- /dev/null +++ b/packages/verkle/src/util/crypto.ts @@ -0,0 +1,65 @@ +import { type Address, concatBytes, setLengthLeft, toBytes, writeInt32LE } from '@ethereumjs/util' + +import * as rustVerkleWasm from '../rust-verkle-wasm/rust_verkle_wasm.js' + +import { + BALANCE_LEAF_KEY, + CODE_KECCAK_LEAF_KEY, + CODE_OFFSET, + CODE_SIZE_LEAF_KEY, + NONCE_LEAF_KEY, + VERKLE_NODE_WIDTH, + VERSION_LEAF_KEY, +} from './constants.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 +} + +function getTreeKey(address: Address, treeIndex: number, subIndex: number): Uint8Array { + const address32 = setLengthLeft(address.toBytes(), 32) + + const treeIndexB = writeInt32LE(treeIndex) + + const input = concatBytes(address32, treeIndexB) + + const treeKey = concatBytes(pedersenHash(input).slice(0, 31), toBytes(subIndex)) + + return treeKey +} + +export function getTreeKeyForVersion(address: Address) { + return getTreeKey(address, 0, VERSION_LEAF_KEY) +} + +export function getTreeKeyForBalance(address: Address) { + return getTreeKey(address, 0, BALANCE_LEAF_KEY) +} + +export function getTreeKeyForNonce(address: Address) { + return getTreeKey(address, 0, NONCE_LEAF_KEY) +} + +export function getTreeKeyForCodeHash(address: Address) { + return getTreeKey(address, 0, CODE_KECCAK_LEAF_KEY) +} + +export function getTreeKeyForCodeSize(address: Address) { + return getTreeKey(address, 0, CODE_SIZE_LEAF_KEY) +} + +export function getTreeKeyForCodeChunk(address: Address, chunkId: number) { + return getTreeKey( + address, + Math.floor((CODE_OFFSET + chunkId) / VERKLE_NODE_WIDTH), + (CODE_OFFSET + chunkId) % VERKLE_NODE_WIDTH + ) +} From 59030e5b5efc1ee55d1df07e1c807e34bfb7149f Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Tue, 8 Aug 2023 09:09:44 -0400 Subject: [PATCH 08/49] verkle: remove unnecessary constants & methods --- packages/verkle/src/util/constants.ts | 17 ---------- packages/verkle/src/util/crypto.ts | 48 +++++---------------------- packages/verkle/src/util/index.ts | 2 ++ 3 files changed, 11 insertions(+), 56 deletions(-) delete mode 100644 packages/verkle/src/util/constants.ts create mode 100644 packages/verkle/src/util/index.ts diff --git a/packages/verkle/src/util/constants.ts b/packages/verkle/src/util/constants.ts deleted file mode 100644 index 674b315c0c..0000000000 --- a/packages/verkle/src/util/constants.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Tree key export constants. - */ -export const VERSION_LEAF_KEY = 0 -export const BALANCE_LEAF_KEY = 1 -export const NONCE_LEAF_KEY = 2 -export const CODE_KECCAK_LEAF_KEY = 3 -export const CODE_SIZE_LEAF_KEY = 4 - -export const HEADER_STORAGE_OFFSET = 64 -export const CODE_OFFSET = 128 -export const VERKLE_NODE_WIDTH = 256 -export const MAIN_STORAGE_OFFSET = 256 ** 31 - -const PUSH_OFFSET = 95 -export const PUSH1 = PUSH_OFFSET + 1 -export const PUSH32 = PUSH_OFFSET + 32 diff --git a/packages/verkle/src/util/crypto.ts b/packages/verkle/src/util/crypto.ts index fa55469b13..61db99d41f 100644 --- a/packages/verkle/src/util/crypto.ts +++ b/packages/verkle/src/util/crypto.ts @@ -2,16 +2,6 @@ import { type Address, concatBytes, setLengthLeft, toBytes, writeInt32LE } from import * as rustVerkleWasm from '../rust-verkle-wasm/rust_verkle_wasm.js' -import { - BALANCE_LEAF_KEY, - CODE_KECCAK_LEAF_KEY, - CODE_OFFSET, - CODE_SIZE_LEAF_KEY, - NONCE_LEAF_KEY, - VERKLE_NODE_WIDTH, - VERSION_LEAF_KEY, -} from './constants.js' - export function pedersenHash(input: Uint8Array): Uint8Array { const pedersenHash = rustVerkleWasm.pedersen_hash(input) @@ -24,7 +14,15 @@ export function pedersenHash(input: Uint8Array): Uint8Array { return pedersenHash } -function getTreeKey(address: Address, treeIndex: number, subIndex: number): Uint8Array { +/** + * @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 = writeInt32LE(treeIndex) @@ -35,31 +33,3 @@ function getTreeKey(address: Address, treeIndex: number, subIndex: number): Uint return treeKey } - -export function getTreeKeyForVersion(address: Address) { - return getTreeKey(address, 0, VERSION_LEAF_KEY) -} - -export function getTreeKeyForBalance(address: Address) { - return getTreeKey(address, 0, BALANCE_LEAF_KEY) -} - -export function getTreeKeyForNonce(address: Address) { - return getTreeKey(address, 0, NONCE_LEAF_KEY) -} - -export function getTreeKeyForCodeHash(address: Address) { - return getTreeKey(address, 0, CODE_KECCAK_LEAF_KEY) -} - -export function getTreeKeyForCodeSize(address: Address) { - return getTreeKey(address, 0, CODE_SIZE_LEAF_KEY) -} - -export function getTreeKeyForCodeChunk(address: Address, chunkId: number) { - return getTreeKey( - address, - Math.floor((CODE_OFFSET + chunkId) / VERKLE_NODE_WIDTH), - (CODE_OFFSET + chunkId) % VERKLE_NODE_WIDTH - ) -} diff --git a/packages/verkle/src/util/index.ts b/packages/verkle/src/util/index.ts new file mode 100644 index 0000000000..fdd64eb362 --- /dev/null +++ b/packages/verkle/src/util/index.ts @@ -0,0 +1,2 @@ +export * from './crypto.js' +export * from './lock.js' From 489f51d726eb110c47081775dee5badcc30a1695 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Tue, 8 Aug 2023 21:20:14 -0400 Subject: [PATCH 09/49] verkle: verkle node classes and types --- packages/verkle/src/node/baseVerkleNode.ts | 28 +++++++++ packages/verkle/src/node/index.ts | 5 +- packages/verkle/src/node/internalNode.ts | 22 +++++++ packages/verkle/src/node/leafNode.ts | 26 ++++++++ packages/verkle/src/node/types.ts | 52 ++++++++++++++++ packages/verkle/src/node/verkleNode.ts | 70 ---------------------- packages/verkle/src/types.ts | 25 +------- 7 files changed, 134 insertions(+), 94 deletions(-) create mode 100644 packages/verkle/src/node/baseVerkleNode.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 delete mode 100644 packages/verkle/src/node/verkleNode.ts diff --git a/packages/verkle/src/node/baseVerkleNode.ts b/packages/verkle/src/node/baseVerkleNode.ts new file mode 100644 index 0000000000..5e2f95d4aa --- /dev/null +++ b/packages/verkle/src/node/baseVerkleNode.ts @@ -0,0 +1,28 @@ +import type { CommitmentPoint } from '../types' +import type { VerkleNodeInterface, VerkleNodeOptions, VerkleNodeType } from './types' + +export abstract class BaseVerkleNode implements VerkleNodeInterface { + // TODO?: Directly make the VerkleNode either an InternalNode of LeafNode instead of having a node property + public commitment: CommitmentPoint + public depth: number + public type: T + + constructor(options: VerkleNodeOptions[T]) { + this.commitment = options.commitment + this.depth = options.depth + this.type = options.type as T + } + // Commit computes the commitment of the node. The + // result (the curve point) is cached. + abstract commit(): CommitmentPoint + + // Hash returns the field representation of the commitment. + hash(): any { + throw new Error('Not implemented') + } + + // Serialize encodes the node to RLP. + serialize(): Uint8Array { + throw new Error('Not implemented') + } +} diff --git a/packages/verkle/src/node/index.ts b/packages/verkle/src/node/index.ts index ae0e9fd229..c786638cde 100644 --- a/packages/verkle/src/node/index.ts +++ b/packages/verkle/src/node/index.ts @@ -1 +1,4 @@ -export * from './verkleNode.js' +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..716b9ceeb7 --- /dev/null +++ b/packages/verkle/src/node/internalNode.ts @@ -0,0 +1,22 @@ +import { BaseVerkleNode } from './baseVerkleNode.js' + +import type { CommitmentPoint } from '../types.js' +import type { LeafNode } from './leafNode.js' +import type { VerkleNodeOptions, VerkleNodeType } from './types.js' + +export class InternalNode extends BaseVerkleNode { + public children: Array + public copyOnWrite: Record + + constructor(options: VerkleNodeOptions[VerkleNodeType.Internal]) { + super(options) + this.children = options.children + this.copyOnWrite = options.copyOnWrite + } + + commit(): Uint8Array { + throw new Error('Not implemented') + // const commit = TODO + // this.commit = commit + } +} diff --git a/packages/verkle/src/node/leafNode.ts b/packages/verkle/src/node/leafNode.ts new file mode 100644 index 0000000000..1ccb07fc86 --- /dev/null +++ b/packages/verkle/src/node/leafNode.ts @@ -0,0 +1,26 @@ +import { BaseVerkleNode } from './baseVerkleNode.js' + +import type { CommitmentPoint } from '../types' +import type { VerkleNodeOptions, VerkleNodeType } from './types' + +export class LeafNode extends BaseVerkleNode { + public stem: Uint8Array + public values: Uint8Array[] + public c1: CommitmentPoint + public c2: CommitmentPoint + + constructor(options: VerkleNodeOptions[VerkleNodeType.Leaf]) { + super(options) + + this.stem = options.stem + this.values = options.values + this.c1 = options.c1 + this.c2 = options.c2 + } + + commit(): Uint8Array { + throw new Error('Not implemented') + // const commit = TODO + // this.commit = commit + } +} diff --git a/packages/verkle/src/node/types.ts b/packages/verkle/src/node/types.ts new file mode 100644 index 0000000000..9d95000702 --- /dev/null +++ b/packages/verkle/src/node/types.ts @@ -0,0 +1,52 @@ +import type { CommitmentPoint } from '../types' +import type { InternalNode } from './internalNode' +import type { LeafNode } from './leafNode' + +export enum VerkleNodeType { + Internal = 'Internal', + Leaf = 'Leaf', +} + +export interface VerkleNode { + [VerkleNodeType.Internal]: InternalNode + [VerkleNodeType.Leaf]: LeafNode +} + +export interface VerkleNodeInterface { + commit(): Uint8Array + hash(): any + serialize(): Uint8Array +} + +interface BaseVerkleNodeOptions { + // Node depth in the trie, in bits + depth: number + + // Value of the commitment + commitment: CommitmentPoint + + // Node type (leaf or internal) + type: VerkleNodeType +} + +interface VerkleInternalNodeOptions extends BaseVerkleNodeOptions { + // List of child nodes of this internal node. + children: Array + + // 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 + type: VerkleNodeType.Internal +} +interface VerkleLeafNodeOptions extends BaseVerkleNodeOptions { + stem: Uint8Array + values: Uint8Array[] + c1: CommitmentPoint + c2: CommitmentPoint + type: VerkleNodeType.Leaf +} + +export interface VerkleNodeOptions { + [VerkleNodeType.Internal]: VerkleInternalNodeOptions + [VerkleNodeType.Leaf]: VerkleLeafNodeOptions +} diff --git a/packages/verkle/src/node/verkleNode.ts b/packages/verkle/src/node/verkleNode.ts deleted file mode 100644 index 4b2773f0a6..0000000000 --- a/packages/verkle/src/node/verkleNode.ts +++ /dev/null @@ -1,70 +0,0 @@ -export interface VerkleNodeOptions { - node: InternalNode | LeafNode -} - -export class VerkleNode { - // TODO?: Directly make the VerkleNode either an InternalNode of LeafNode instead of having a node property - public node: InternalNode | LeafNode - - constructor(options: VerkleNodeOptions) { - this.node = options.node - } - // Commit computes the commitment of the node. The - // result (the curve point) is cached. - commit(): Point { - throw new Error('Not implemented') - } - - getCommitment(): Point { - return this.node.commitment - } - - getDepth(): number { - return this.node.depth - } - - // Hash returns the field representation of the commitment. - hash(): any { - throw new Error('Not implemented') - } - - // TODO: Turn these two into typeguards? - isInternalNode(): boolean { - throw new Error('Not implemented') - } - - isLeafNode(): boolean { - throw new Error('Not implemented') - } - - // Serialize encodes the node to RLP. - serialize(): Uint8Array { - throw new Error('Not implemented') - } -} - -type Point = any - -interface BaseNode { - // Node depth in the trie, in bits - depth: number - - // Value of the commitment - commitment: Point -} - -// Represents an internal node (a non-leaf node) -export interface InternalNode extends BaseNode { - // List of child nodes of this internal node. - children: VerkleNode[] - - // Values of the child commitments before the trie is modified by inserts. - copyOnWrite: Record -} - -export interface LeafNode extends BaseNode { - stem: Uint8Array - values: Uint8Array[] - c1: Point - c2: Point -} diff --git a/packages/verkle/src/types.ts b/packages/verkle/src/types.ts index 6ceab55bd3..5ff40c35b4 100644 --- a/packages/verkle/src/types.ts +++ b/packages/verkle/src/types.ts @@ -1,26 +1,12 @@ import { utf8ToBytes } from '@ethereumjs/util' -// import type { BranchNode, ExtensionNode, LeafNode } from './node/index.js' -// import type { WalkController } from './util/walkController.js' import type { DB } from '@ethereumjs/util' -export type VerkleTrieNode = any - -export type Nibbles = number[] - -// Branch and extension nodes might store -// hash to next node, or embed it if its len < 32 -export type EmbeddedNode = Uint8Array | Uint8Array[] +// Curve point of a commitment +export type CommitmentPoint = Uint8Array export type Proof = Uint8Array[] -export type FoundNodeFunction = ( - nodeRef: Uint8Array, - node: VerkleTrieNode | null, - key: Nibbles, - walkController: any -) => void - export interface VerkleTrieOpts { /** * A database instance. @@ -37,12 +23,6 @@ export interface VerkleTrieOpts { */ useRootPersistence?: boolean - /** - * Flag to prune the trie. When set to `true`, each time a value is overridden, - * unreachable nodes will be pruned (deleted) from the trie - */ - useNodePruning?: boolean - /** * LRU cache for trie nodes to allow for faster node retrieval. * @@ -53,7 +33,6 @@ export interface VerkleTrieOpts { export type VerkleTrieOptsWithDefaults = VerkleTrieOpts & { useRootPersistence: boolean - useNodePruning: boolean cacheSize: number } From 0a6232ed7ccb7865562cbb9e2b3e4f7648fd20e7 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Tue, 8 Aug 2023 22:21:15 -0400 Subject: [PATCH 10/49] verkle: get method and rawNode utils --- packages/verkle/src/node/util.ts | 31 ++++++++++++ packages/verkle/src/verkleTrie.ts | 84 +++++++++++++++++-------------- 2 files changed, 77 insertions(+), 38 deletions(-) create mode 100644 packages/verkle/src/node/util.ts diff --git a/packages/verkle/src/node/util.ts b/packages/verkle/src/node/util.ts new file mode 100644 index 0000000000..4d37f1d670 --- /dev/null +++ b/packages/verkle/src/node/util.ts @@ -0,0 +1,31 @@ +import { RLP } from '@ethereumjs/rlp' + +import type { VerkleNode } from './types' + +export function decodeRawNode(raw: Uint8Array[]): VerkleNode { + // TODO: Differentiate between LeafNode and InternalNode and build accordingly + throw new Error('Not implemented') + // if (raw.length === 17) { + // return BranchNode.fromArray(raw) + // } else if (raw.length === 2) { + // const nibbles = bytesToNibbles(raw[0]) + // if (isTerminator(nibbles)) { + // return new LeafNode(LeafNode.decodeKey(nibbles), raw[1]) + // } + // return new ExtensionNode(ExtensionNode.decodeKey(nibbles), raw[1]) + // } else { + // throw new Error('Invalid node') + // } +} + +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/verkleTrie.ts b/packages/verkle/src/verkleTrie.ts index e8f84b8bae..2e7a9afa68 100644 --- a/packages/verkle/src/verkleTrie.ts +++ b/packages/verkle/src/verkleTrie.ts @@ -2,16 +2,16 @@ import { KeyEncoding, MapDB, ValueEncoding, - bytesToUnprefixedHex, - bytesToUtf8, + bytesToHex, equalsBytes, - unprefixedHexToBytes, + hexToBytes, + zeros, } from '@ethereumjs/util' import { CheckpointDB } from './db/checkpoint.js' +import { decodeNode, decodeRawNode, isRawNode } from './node/util.js' import { type EmbeddedNode, - type FoundNodeFunction, type Nibbles, type Proof, ROOT_DB_KEY, @@ -20,8 +20,9 @@ import { } from './types.js' import { Lock } from './util/lock.js' -import type { VerkleNode } from './node/verkleNode.js' -import type { BatchDBOp, DB } from '@ethereumjs/util' +import type { LeafNode } from './node/leafNode.js' +import type { VerkleNode } from './node/types.js' +import type { BatchDBOp, DB, PutBatch } from '@ethereumjs/util' interface Path { node: VerkleNode | null @@ -35,7 +36,6 @@ interface Path { export class VerkleTrie { protected readonly _opts: VerkleTrieOptsWithDefaults = { useRootPersistence: false, - useNodePruning: false, cacheSize: 0, } @@ -61,7 +61,7 @@ export class VerkleTrie { this.database(opts?.db ?? new MapDB()) - this.EMPTY_TRIE_ROOT = new Uint8Array() // TODO + this.EMPTY_TRIE_ROOT = zeros(32) this._hashLen = this.EMPTY_TRIE_ROOT.length this._root = this.EMPTY_TRIE_ROOT @@ -75,13 +75,13 @@ export class VerkleTrie { if (opts?.db !== undefined && opts?.useRootPersistence === true) { if (opts?.root === undefined) { - const rootHex = await opts?.db.get(bytesToUnprefixedHex(key), { + const rootHex = await opts?.db.get(bytesToHex(key), { keyEncoding: KeyEncoding.String, valueEncoding: ValueEncoding.String, }) - opts.root = rootHex !== undefined ? unprefixedHexToBytes(rootHex) : undefined + opts.root = rootHex !== undefined ? hexToBytes(rootHex) : undefined } else { - await opts?.db.put(bytesToUnprefixedHex(key), bytesToUnprefixedHex(opts.root), { + await opts?.db.put(bytesToHex(key), bytesToHex(opts.root), { keyEncoding: KeyEncoding.String, valueEncoding: ValueEncoding.String, }) @@ -144,8 +144,17 @@ export class VerkleTrie { * @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 { - throw new Error('Not implemented') + 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] + } + + return null } /** @@ -160,22 +169,21 @@ export class VerkleTrie { } /** - * Deletes a value given a `key` from the trie - * (delete operations are only executed on DB with `deleteFromDB` set to `true`) - * @param key - * @returns A Promise that resolves once value is deleted. + * 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 del(key: Uint8Array): Promise { + async findPath(key: Uint8Array, throwIfMissing = false): Promise { throw new Error('Not implemented') } /** - * Tries to find a path to the node for the given key. - * It returns a `stack` of nodes to the closest node. + * 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 findPath(key: Uint8Array, throwIfMissing = false): Promise { + async findLeafNode(key: Uint8Array, throwIfMissing = false): Promise { throw new Error('Not implemented') } @@ -191,7 +199,16 @@ export class VerkleTrie { * Retrieves a node from db by hash. */ async lookupNode(node: Uint8Array | Uint8Array[]): Promise { - throw new Error('Not implemented') + 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') + } } /** @@ -226,7 +243,11 @@ export class VerkleTrie { * @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: Nibbles, stack: VerkleNode[], opStack: BatchDBOp[]): Promise { + async saveStack( + key: Nibbles, + stack: VerkleNode[], + opStack: PutBatch[] + ): Promise { throw new Error('Not implemented') } @@ -242,7 +263,7 @@ export class VerkleTrie { _formatNode( node: VerkleNode, topLevel: boolean, - opStack: BatchDBOp[], + opStack: PutBatch, remove: boolean = false ): Uint8Array | (EmbeddedNode | null)[] { throw new Error('Not implemented') @@ -336,7 +357,7 @@ export class VerkleTrie { */ async persistRoot() { if (this._opts.useRootPersistence) { - await this._db.put(this.appliedKey(ROOT_DB_KEY), this.root()) + await this._db.put(ROOT_DB_KEY, this.root()) } } @@ -350,19 +371,6 @@ export class VerkleTrie { throw new Error('Not implemented') } - /** - * Returns the key practically applied for trie construction - * depending on the `useKeyHashing` option being set or not. - * @param key - */ - protected appliedKey(key: Uint8Array): Uint8Array { - throw new Error('Not implemented') - } - - protected hash(msg: Uint8Array): Uint8Array { - throw new Error('Not implemented') - } - /** * Is the trie during a checkpoint phase? */ From 248eb9063c9c2c84597bf6c86cbf702109d65af3 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Wed, 9 Aug 2023 08:48:03 -0400 Subject: [PATCH 11/49] verkle: handle case where array item is not found --- packages/verkle/src/verkleTrie.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/verkle/src/verkleTrie.ts b/packages/verkle/src/verkleTrie.ts index 2e7a9afa68..b000f81a08 100644 --- a/packages/verkle/src/verkleTrie.ts +++ b/packages/verkle/src/verkleTrie.ts @@ -151,7 +151,7 @@ export class VerkleTrie { // 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] + return node.values?.[keyLastByte] ?? null } return null From fe4ec16974575acd174e95a645aacbdd19379971 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Thu, 10 Aug 2023 13:48:33 -0400 Subject: [PATCH 12/49] verkle: wip findPath method --- packages/verkle/src/types.ts | 8 ++++ packages/verkle/src/verkleTrie.ts | 65 ++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/packages/verkle/src/types.ts b/packages/verkle/src/types.ts index 5ff40c35b4..abf76e084e 100644 --- a/packages/verkle/src/types.ts +++ b/packages/verkle/src/types.ts @@ -1,5 +1,6 @@ import { utf8ToBytes } from '@ethereumjs/util' +import type { VerkleNode } from './node' import type { DB } from '@ethereumjs/util' // Curve point of a commitment @@ -55,4 +56,11 @@ export type Checkpoint = { root: Uint8Array } +export type FoundNodeFunction = ( + nodeRef: Uint8Array, + node: VerkleNode | null, + key: Uint8Array, + walkController: any // todo +) => void + export const ROOT_DB_KEY = utf8ToBytes('__root__') diff --git a/packages/verkle/src/verkleTrie.ts b/packages/verkle/src/verkleTrie.ts index b000f81a08..402ea67e81 100644 --- a/packages/verkle/src/verkleTrie.ts +++ b/packages/verkle/src/verkleTrie.ts @@ -9,6 +9,7 @@ import { } from '@ethereumjs/util' import { CheckpointDB } from './db/checkpoint.js' +import { LeafNode } from './node/leafNode.js' import { decodeNode, decodeRawNode, isRawNode } from './node/util.js' import { type EmbeddedNode, @@ -20,8 +21,8 @@ import { } from './types.js' import { Lock } from './util/lock.js' -import type { LeafNode } from './node/leafNode.js' import type { VerkleNode } from './node/types.js' +import type { FoundNodeFunction } from './types.js' import type { BatchDBOp, DB, PutBatch } from '@ethereumjs/util' interface Path { @@ -175,7 +176,67 @@ export class VerkleTrie { * @param throwIfMissing - if true, throws if any nodes are missing. Used for verifying proofs. (default: false) */ async findPath(key: Uint8Array, throwIfMissing = false): Promise { - throw new Error('Not implemented') + // 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 = targetKey.slice(matchingNibbleLength(keyProgress, targetKey)) + stack.push(node) + + if (node instanceof BranchNode) { + if (keyRemainder.length === 0) { + // we exhausted the key without finding a node + resolve({ node, remaining: [], stack }) + } else { + const branchIndex = keyRemainder[0] + const branchNode = node.getBranch(branchIndex) + if (!branchNode) { + // there are no more nodes to find and we didn't find the key + resolve({ node: null, remaining: keyRemainder, stack }) + } else { + // node found, continuing search + // this can be optimized as this calls getBranch again. + walkController.onlyBranchIndex(node, keyProgress, branchIndex) + } + } + } else if (node instanceof LeafNode) { + if (doKeysMatch(keyRemainder, node.key())) { + // keys match, return node with empty key + resolve({ node, remaining: [], stack }) + } else { + // reached leaf but keys dont match + resolve({ node: null, remaining: keyRemainder, stack }) + } + } else if (node instanceof ExtensionNode) { + const matchingLen = matchingNibbleLength(keyRemainder, node.key()) + if (matchingLen !== node.key().length) { + // keys don't match, fail + resolve({ node: null, remaining: keyRemainder, stack }) + } else { + // keys match, continue search + walkController.allChildren(node, keyProgress) + } + } + } + + // 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: [], stack }) + }) } /** From ae43467437a86ecb6eaaedc88661778bbcf34b51 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Fri, 11 Aug 2023 13:19:48 -0400 Subject: [PATCH 13/49] verkle: wip --- packages/verkle/src/node/baseVerkleNode.ts | 27 ++-- packages/verkle/src/node/internalNode.ts | 33 ++++- packages/verkle/src/node/leafNode.ts | 40 +++++- packages/verkle/src/node/types.ts | 26 ++-- packages/verkle/src/types.ts | 5 +- packages/verkle/src/util/bytes.ts | 25 ++++ packages/verkle/src/util/index.ts | 3 + packages/verkle/src/util/tasks.ts | 59 +++++++++ packages/verkle/src/util/walkController.ts | 143 +++++++++++++++++++++ packages/verkle/src/verkleTrie.ts | 81 ++++++------ 10 files changed, 371 insertions(+), 71 deletions(-) create mode 100644 packages/verkle/src/util/bytes.ts create mode 100644 packages/verkle/src/util/tasks.ts create mode 100644 packages/verkle/src/util/walkController.ts diff --git a/packages/verkle/src/node/baseVerkleNode.ts b/packages/verkle/src/node/baseVerkleNode.ts index 5e2f95d4aa..266b7f71e8 100644 --- a/packages/verkle/src/node/baseVerkleNode.ts +++ b/packages/verkle/src/node/baseVerkleNode.ts @@ -1,28 +1,35 @@ +import { RLP } from '@ethereumjs/rlp' + import type { CommitmentPoint } from '../types' -import type { VerkleNodeInterface, VerkleNodeOptions, VerkleNodeType } from './types' +import type { + TypedVerkleNode, + VerkleNodeInterface, + VerkleNodeOptions, + VerkleNodeType, +} from './types' export abstract class BaseVerkleNode implements VerkleNodeInterface { - // TODO?: Directly make the VerkleNode either an InternalNode of LeafNode instead of having a node property public commitment: CommitmentPoint - public depth: number - public type: T constructor(options: VerkleNodeOptions[T]) { this.commitment = options.commitment - this.depth = options.depth - this.type = options.type as T } - // Commit computes the commitment of the node. The - // result (the curve point) is cached. + abstract commit(): CommitmentPoint + abstract fromValuesArray(rawNode: Uint8Array[]): TypedVerkleNode[T] + // Hash returns the field representation of the commitment. hash(): any { throw new Error('Not implemented') } - // Serialize encodes the node to RLP. + abstract raw(): Uint8Array[] + + /** + * @returns the RLP serialized node + */ serialize(): Uint8Array { - throw new Error('Not implemented') + return RLP.encode(this.raw()) } } diff --git a/packages/verkle/src/node/internalNode.ts b/packages/verkle/src/node/internalNode.ts index 716b9ceeb7..8d4d919124 100644 --- a/packages/verkle/src/node/internalNode.ts +++ b/packages/verkle/src/node/internalNode.ts @@ -1,12 +1,14 @@ import { BaseVerkleNode } from './baseVerkleNode.js' +import { NODE_WIDTH, VerkleNodeType } from './types.js' import type { CommitmentPoint } from '../types.js' -import type { LeafNode } from './leafNode.js' -import type { VerkleNodeOptions, VerkleNodeType } from './types.js' +import type { VerkleNodeOptions } from './types.js' export class InternalNode extends BaseVerkleNode { - public children: Array + // Array of references to children nodes + public children: CommitmentPoint[] public copyOnWrite: Record + public type = VerkleNodeType.Internal constructor(options: VerkleNodeOptions[VerkleNodeType.Internal]) { super(options) @@ -19,4 +21,29 @@ export class InternalNode extends BaseVerkleNode { // const commit = TODO // this.commit = commit } + + fromValuesArray(rawNode: Uint8Array[]): 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') + } + + const children = rawNode.slice(1, NODE_WIDTH + 2) + const commitment = rawNode[rawNode.length - 1] + + return new InternalNode({ children, commitment, copyOnWrite: {} }) + } + + getChildren(index: number): Uint8Array | null { + return this.children?.[index] ?? null + } + // TODO: go-verkle also adds the bitlist to the raw format. + raw(): Uint8Array[] { + 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 index 1ccb07fc86..30af72eba0 100644 --- a/packages/verkle/src/node/leafNode.ts +++ b/packages/verkle/src/node/leafNode.ts @@ -1,13 +1,15 @@ import { BaseVerkleNode } from './baseVerkleNode.js' +import { NODE_WIDTH, VerkleNodeType } from './types' import type { CommitmentPoint } from '../types' -import type { VerkleNodeOptions, VerkleNodeType } from './types' +import type { VerkleNodeOptions } from './types' export class LeafNode extends BaseVerkleNode { public stem: Uint8Array public values: Uint8Array[] public c1: CommitmentPoint public c2: CommitmentPoint + public type = VerkleNodeType.Leaf constructor(options: VerkleNodeOptions[VerkleNodeType.Leaf]) { super(options) @@ -23,4 +25,40 @@ export class LeafNode extends BaseVerkleNode { // const commit = TODO // this.commit = commit } + + fromValuesArray(rawNode: Uint8Array[]): 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] + const commitment = rawNode[2] + const c1 = rawNode[3] + const c2 = rawNode[4] + const values = rawNode.slice(5, rawNode.length) + + return new LeafNode({ stem, values, c1, c2, commitment }) + } + + getValue(index: number): Uint8Array | null { + return this.values?.[index] ?? null + } + + // TODO: go-verkle also adds the bitlist to the raw format. + raw(): Uint8Array[] { + return [ + new Uint8Array([VerkleNodeType.Leaf]), + this.stem, + this.commitment, + this.c1, + this.c2, + ...this.values, + ] + } } diff --git a/packages/verkle/src/node/types.ts b/packages/verkle/src/node/types.ts index 9d95000702..fa488508bb 100644 --- a/packages/verkle/src/node/types.ts +++ b/packages/verkle/src/node/types.ts @@ -1,17 +1,19 @@ import type { CommitmentPoint } from '../types' -import type { InternalNode } from './internalNode' -import type { LeafNode } from './leafNode' +import type { InternalNode } from './internalNode.js' +import type { LeafNode } from './leafNode.js' export enum VerkleNodeType { - Internal = 'Internal', - Leaf = 'Leaf', + Internal, + Leaf, } -export interface VerkleNode { +export interface TypedVerkleNode { [VerkleNodeType.Internal]: InternalNode [VerkleNodeType.Leaf]: LeafNode } +export type VerkleNode = TypedVerkleNode[VerkleNodeType] + export interface VerkleNodeInterface { commit(): Uint8Array hash(): any @@ -19,34 +21,28 @@ export interface VerkleNodeInterface { } interface BaseVerkleNodeOptions { - // Node depth in the trie, in bits - depth: number - // Value of the commitment commitment: CommitmentPoint - - // Node type (leaf or internal) - type: VerkleNodeType } interface VerkleInternalNodeOptions extends BaseVerkleNodeOptions { - // List of child nodes of this internal node. - children: Array + // List of children node references of this internal node. + children: Uint8Array[] // 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 - type: VerkleNodeType.Internal } interface VerkleLeafNodeOptions extends BaseVerkleNodeOptions { stem: Uint8Array values: Uint8Array[] c1: CommitmentPoint c2: CommitmentPoint - type: VerkleNodeType.Leaf } export interface VerkleNodeOptions { [VerkleNodeType.Internal]: VerkleInternalNodeOptions [VerkleNodeType.Leaf]: VerkleLeafNodeOptions } + +export const NODE_WIDTH = 256 diff --git a/packages/verkle/src/types.ts b/packages/verkle/src/types.ts index abf76e084e..c2f6200092 100644 --- a/packages/verkle/src/types.ts +++ b/packages/verkle/src/types.ts @@ -1,6 +1,7 @@ import { utf8ToBytes } from '@ethereumjs/util' import type { VerkleNode } from './node' +import type { WalkController } from './util/walkController' import type { DB } from '@ethereumjs/util' // Curve point of a commitment @@ -57,10 +58,10 @@ export type Checkpoint = { } export type FoundNodeFunction = ( - nodeRef: Uint8Array, + nodeRef: CommitmentPoint, node: VerkleNode | null, key: Uint8Array, - walkController: any // todo + 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/index.ts b/packages/verkle/src/util/index.ts index fdd64eb362..0df1587385 100644 --- a/packages/verkle/src/util/index.ts +++ b/packages/verkle/src/util/index.ts @@ -1,2 +1,5 @@ +export * from './bytes.js' export * from './crypto.js' export * from './lock.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..c10590bc10 --- /dev/null +++ b/packages/verkle/src/util/walkController.ts @@ -0,0 +1,143 @@ +import { InternalNode, LeafNode } from '../node/index.js' + +import { PrioritizedTaskExecutor } from './tasks.js' + +import type { VerkleNode } from '../node/types.js' +import type { CommitmentPoint, 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) { + const childKey = new Uint8Array([...key, child.keyExtension]) + this.pushNodeToQueue(child.nodeRef, 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, childKey, priority ?? childKey.length) + } + + private processNode( + nodeRef: CommitmentPoint, + 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 index 402ea67e81..93374462ff 100644 --- a/packages/verkle/src/verkleTrie.ts +++ b/packages/verkle/src/verkleTrie.ts @@ -9,25 +9,25 @@ import { } 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 EmbeddedNode, - type Nibbles, type Proof, ROOT_DB_KEY, type VerkleTrieOpts, type VerkleTrieOptsWithDefaults, } from './types.js' +import { WalkController, matchingBytesLength } from './util/index.js' import { Lock } from './util/lock.js' import type { VerkleNode } from './node/types.js' -import type { FoundNodeFunction } from './types.js' +import type { CommitmentPoint, FoundNodeFunction } from './types.js' import type { BatchDBOp, DB, PutBatch } from '@ethereumjs/util' interface Path { node: VerkleNode | null - remaining: Nibbles + remaining: Uint8Array stack: VerkleNode[] } @@ -161,8 +161,8 @@ export class VerkleTrie { /** * 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 - * @param value + * @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 { @@ -184,42 +184,34 @@ export class VerkleTrie { if (node === null) { return reject(new Error('Path not found')) } - const keyRemainder = targetKey.slice(matchingNibbleLength(keyProgress, targetKey)) + const keyRemainder = key.slice(matchingBytesLength(keyProgress, key)) stack.push(node) - if (node instanceof BranchNode) { + if (node instanceof InternalNode) { if (keyRemainder.length === 0) { // we exhausted the key without finding a node - resolve({ node, remaining: [], stack }) + resolve({ node, remaining: new Uint8Array(0), stack }) } else { - const branchIndex = keyRemainder[0] - const branchNode = node.getBranch(branchIndex) - if (!branchNode) { - // there are no more nodes to find and we didn't find the key + 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, continuing search - // this can be optimized as this calls getBranch again. - walkController.onlyBranchIndex(node, keyProgress, branchIndex) + // node found, continue search from children + walkController.pushChildrenAtIndex(node, keyProgress, childrenIndex) } } } else if (node instanceof LeafNode) { - if (doKeysMatch(keyRemainder, node.key())) { + // 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: [], stack }) + resolve({ node, remaining: new Uint8Array(0), stack }) } else { - // reached leaf but keys dont match + // reached leaf but keys don't match resolve({ node: null, remaining: keyRemainder, stack }) } - } else if (node instanceof ExtensionNode) { - const matchingLen = matchingNibbleLength(keyRemainder, node.key()) - if (matchingLen !== node.key().length) { - // keys don't match, fail - resolve({ node: null, remaining: keyRemainder, stack }) - } else { - // keys match, continue search - walkController.allChildren(node, keyProgress) - } } } @@ -235,17 +227,34 @@ export class VerkleTrie { } // Resolve if walkTrie finishes without finding any nodes - resolve({ node: null, remaining: [], stack }) + 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 { - throw new Error('Not implemented') + const { node } = await this.findPath(key, throwIfMissing) + if (!(node instanceof LeafNode)) { + if (throwIfMissing) { + throw new Error('findLeafNode: leaf node not found') + } + return null + } + return node } /** @@ -259,7 +268,7 @@ export class VerkleTrie { /** * Retrieves a node from db by hash. */ - async lookupNode(node: Uint8Array | Uint8Array[]): Promise { + async lookupNode(node: CommitmentPoint | Uint8Array[]): Promise { if (isRawNode(node)) { return decodeRawNode(node) } @@ -283,20 +292,12 @@ export class VerkleTrie { protected async _updateNode( k: Uint8Array, value: Uint8Array, - keyRemainder: Nibbles, + keyRemainder: Uint8Array, stack: VerkleNode[] ): Promise { throw new Error('Not implemented') } - /** - * Deletes a node from the trie. - * @private - */ - protected async _deleteNode(k: Uint8Array, stack: VerkleNode[]): Promise { - throw new Error('Not implemented') - } - /** * Saves a stack of nodes to the database. * From 4afb94374071c6f57dd2ab01284072b0e9b780d2 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Fri, 11 Aug 2023 14:44:40 -0400 Subject: [PATCH 14/49] verkle: fromRawNode method --- packages/verkle/src/node/baseVerkleNode.ts | 11 ++------- packages/verkle/src/node/internalNode.ts | 2 +- packages/verkle/src/node/leafNode.ts | 8 +++---- packages/verkle/src/node/types.ts | 2 +- packages/verkle/src/node/util.ts | 26 ++++++++++------------ 5 files changed, 20 insertions(+), 29 deletions(-) diff --git a/packages/verkle/src/node/baseVerkleNode.ts b/packages/verkle/src/node/baseVerkleNode.ts index 266b7f71e8..6168a4650f 100644 --- a/packages/verkle/src/node/baseVerkleNode.ts +++ b/packages/verkle/src/node/baseVerkleNode.ts @@ -1,12 +1,7 @@ import { RLP } from '@ethereumjs/rlp' -import type { CommitmentPoint } from '../types' -import type { - TypedVerkleNode, - VerkleNodeInterface, - VerkleNodeOptions, - VerkleNodeType, -} from './types' +import type { CommitmentPoint } from '../types.js' +import type { VerkleNodeInterface, VerkleNodeOptions, VerkleNodeType } from './types.js' export abstract class BaseVerkleNode implements VerkleNodeInterface { public commitment: CommitmentPoint @@ -17,8 +12,6 @@ export abstract class BaseVerkleNode implements Verkle abstract commit(): CommitmentPoint - abstract fromValuesArray(rawNode: Uint8Array[]): TypedVerkleNode[T] - // Hash returns the field representation of the commitment. hash(): any { throw new Error('Not implemented') diff --git a/packages/verkle/src/node/internalNode.ts b/packages/verkle/src/node/internalNode.ts index 8d4d919124..03c974ad65 100644 --- a/packages/verkle/src/node/internalNode.ts +++ b/packages/verkle/src/node/internalNode.ts @@ -22,7 +22,7 @@ export class InternalNode extends BaseVerkleNode { // this.commit = commit } - fromValuesArray(rawNode: Uint8Array[]): InternalNode { + static fromRawNode(rawNode: Uint8Array[]): InternalNode { const nodeType = rawNode[0][0] if (nodeType !== VerkleNodeType.Internal) { throw new Error('Invalid node type') diff --git a/packages/verkle/src/node/leafNode.ts b/packages/verkle/src/node/leafNode.ts index 30af72eba0..fd4d6f0da9 100644 --- a/packages/verkle/src/node/leafNode.ts +++ b/packages/verkle/src/node/leafNode.ts @@ -1,8 +1,8 @@ import { BaseVerkleNode } from './baseVerkleNode.js' -import { NODE_WIDTH, VerkleNodeType } from './types' +import { NODE_WIDTH, VerkleNodeType } from './types.js' -import type { CommitmentPoint } from '../types' -import type { VerkleNodeOptions } from './types' +import type { CommitmentPoint } from '../types.js' +import type { VerkleNodeOptions } from './types.js' export class LeafNode extends BaseVerkleNode { public stem: Uint8Array @@ -26,7 +26,7 @@ export class LeafNode extends BaseVerkleNode { // this.commit = commit } - fromValuesArray(rawNode: Uint8Array[]): LeafNode { + static fromRawNode(rawNode: Uint8Array[]): LeafNode { const nodeType = rawNode[0][0] if (nodeType !== VerkleNodeType.Leaf) { throw new Error('Invalid node type') diff --git a/packages/verkle/src/node/types.ts b/packages/verkle/src/node/types.ts index fa488508bb..6bbff370b8 100644 --- a/packages/verkle/src/node/types.ts +++ b/packages/verkle/src/node/types.ts @@ -1,4 +1,4 @@ -import type { CommitmentPoint } from '../types' +import type { CommitmentPoint } from '../types.js' import type { InternalNode } from './internalNode.js' import type { LeafNode } from './leafNode.js' diff --git a/packages/verkle/src/node/util.ts b/packages/verkle/src/node/util.ts index 4d37f1d670..1991793d0b 100644 --- a/packages/verkle/src/node/util.ts +++ b/packages/verkle/src/node/util.ts @@ -1,21 +1,19 @@ import { RLP } from '@ethereumjs/rlp' -import type { VerkleNode } from './types' +import { InternalNode } from './internalNode.js' +import { LeafNode } from './leafNode.js' +import { type VerkleNode, VerkleNodeType } from './types.js' export function decodeRawNode(raw: Uint8Array[]): VerkleNode { - // TODO: Differentiate between LeafNode and InternalNode and build accordingly - throw new Error('Not implemented') - // if (raw.length === 17) { - // return BranchNode.fromArray(raw) - // } else if (raw.length === 2) { - // const nibbles = bytesToNibbles(raw[0]) - // if (isTerminator(nibbles)) { - // return new LeafNode(LeafNode.decodeKey(nibbles), raw[1]) - // } - // return new ExtensionNode(ExtensionNode.decodeKey(nibbles), raw[1]) - // } else { - // throw new Error('Invalid node') - // } + const nodeType = raw[0][0] + switch (nodeType) { + case VerkleNodeType.Internal: + return InternalNode.fromRawNode(raw) + case VerkleNodeType.Leaf: + return LeafNode.fromRawNode(raw) + default: + throw new Error('Invalid node type') + } } export function decodeNode(raw: Uint8Array) { From 6b7be6f73107e3385b08f054d9cf18533e79014a Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Fri, 11 Aug 2023 23:24:47 -0400 Subject: [PATCH 15/49] verkle: update packages --- package-lock.json | 8 ++++---- packages/verkle/package.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9d1963239b..c393a31653 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15828,18 +15828,18 @@ }, "packages/verkle": { "name": "@ethereumjs/verkle", - "version": "1.0.0-rc.1", + "version": "1.0.0", "license": "MPL-2.0", "dependencies": { - "@ethereumjs/rlp": "5.0.0-rc.1", - "@ethereumjs/util": "9.0.0-rc.1", + "@ethereumjs/rlp": "5.0.0", + "@ethereumjs/util": "9.0.0", "@types/readable-stream": "^2.3.13", "ethereum-cryptography": "^2.1.2", "lru-cache": "^10.0.0", "readable-stream": "^3.6.0" }, "devDependencies": { - "@ethereumjs/genesis": "0.1.0-rc.1", + "@ethereumjs/genesis": "0.1.0", "@types/benchmark": "^1.0.33", "abstract-level": "^1.0.3", "level": "^8.0.0", diff --git a/packages/verkle/package.json b/packages/verkle/package.json index 1c71bfb374..38097f45a0 100644 --- a/packages/verkle/package.json +++ b/packages/verkle/package.json @@ -1,6 +1,6 @@ { "name": "@ethereumjs/verkle", - "version": "1.0.0-rc.1", + "version": "1.0.0", "description": "Implementation of verkle tries as used in Ethereum.", "keywords": [ "verkle", @@ -50,15 +50,15 @@ "tsc": "../../config/cli/ts-compile.sh" }, "dependencies": { - "@ethereumjs/rlp": "5.0.0-rc.1", - "@ethereumjs/util": "9.0.0-rc.1", + "@ethereumjs/rlp": "5.0.0", + "@ethereumjs/util": "9.0.0", "@types/readable-stream": "^2.3.13", "lru-cache": "^10.0.0", "ethereum-cryptography": "^2.1.2", "readable-stream": "^3.6.0" }, "devDependencies": { - "@ethereumjs/genesis": "0.1.0-rc.1", + "@ethereumjs/genesis": "0.1.0", "@types/benchmark": "^1.0.33", "abstract-level": "^1.0.3", "level": "^8.0.0", From 45b5b2245f08b2f1479d4bb720e587b73ccd81c0 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Fri, 11 Aug 2023 23:25:08 -0400 Subject: [PATCH 16/49] verkle: minor adjustments --- packages/verkle/src/verkleTrie.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/verkle/src/verkleTrie.ts b/packages/verkle/src/verkleTrie.ts index 93374462ff..25c3b0f3fe 100644 --- a/packages/verkle/src/verkleTrie.ts +++ b/packages/verkle/src/verkleTrie.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { KeyEncoding, MapDB, @@ -250,7 +251,7 @@ export class VerkleTrie { const { node } = await this.findPath(key, throwIfMissing) if (!(node instanceof LeafNode)) { if (throwIfMissing) { - throw new Error('findLeafNode: leaf node not found') + throw new Error('leaf node not found') } return null } @@ -306,7 +307,7 @@ export class VerkleTrie { * @param opStack - a stack of levelup operations to commit at the end of this function */ async saveStack( - key: Nibbles, + key: Uint8Array, stack: VerkleNode[], opStack: PutBatch[] ): Promise { @@ -327,7 +328,7 @@ export class VerkleTrie { topLevel: boolean, opStack: PutBatch, remove: boolean = false - ): Uint8Array | (EmbeddedNode | null)[] { + ): Uint8Array { throw new Error('Not implemented') } From 56d8aa3964dc393f8f34f5509469038e742425df Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sat, 26 Aug 2023 13:11:36 -0400 Subject: [PATCH 17/49] verkle: wip --- packages/verkle/src/node/baseVerkleNode.ts | 10 +++++++++- packages/verkle/src/node/internalNode.ts | 9 +++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/verkle/src/node/baseVerkleNode.ts b/packages/verkle/src/node/baseVerkleNode.ts index 6168a4650f..6d30e46bac 100644 --- a/packages/verkle/src/node/baseVerkleNode.ts +++ b/packages/verkle/src/node/baseVerkleNode.ts @@ -1,7 +1,13 @@ import { RLP } from '@ethereumjs/rlp' +import { + NODE_WIDTH, + type VerkleNodeInterface, + type VerkleNodeOptions, + type VerkleNodeType, +} from './types.js' + import type { CommitmentPoint } from '../types.js' -import type { VerkleNodeInterface, VerkleNodeOptions, VerkleNodeType } from './types.js' export abstract class BaseVerkleNode implements VerkleNodeInterface { public commitment: CommitmentPoint @@ -17,6 +23,8 @@ export abstract class BaseVerkleNode implements Verkle throw new Error('Not implemented') } + abstract insert(key: Uint8Array, value: Uint8Array, nodeResolverFn: () => void): void + abstract raw(): Uint8Array[] /** diff --git a/packages/verkle/src/node/internalNode.ts b/packages/verkle/src/node/internalNode.ts index 03c974ad65..5a2806a0e8 100644 --- a/packages/verkle/src/node/internalNode.ts +++ b/packages/verkle/src/node/internalNode.ts @@ -42,6 +42,15 @@ export class InternalNode extends BaseVerkleNode { getChildren(index: number): Uint8Array | 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(key) + // TODO: go-verkle also adds the bitlist to the raw format. raw(): Uint8Array[] { return [new Uint8Array([VerkleNodeType.Internal]), ...this.children, this.commitment] From a45ed5071599512b108fd59b0edcf6a11e778818 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Thu, 28 Sep 2023 21:05:24 -0400 Subject: [PATCH 18/49] verkle: add test case for put and get --- packages/verkle/test/verkle.spec.ts | 59 +++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 packages/verkle/test/verkle.spec.ts diff --git a/packages/verkle/test/verkle.spec.ts b/packages/verkle/test/verkle.spec.ts new file mode 100644 index 0000000000..cbc87add4f --- /dev/null +++ b/packages/verkle/test/verkle.spec.ts @@ -0,0 +1,59 @@ +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) + +const absentKeys = [ + '0x318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d03', + '0x318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d04', +].map(hexToBytes) + +describe('Verkle trie', () => { + it('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])) + } + }) +}) From 655786b9218957c2df14eee79dcd6c77ce70ad3d Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Thu, 28 Sep 2023 21:08:59 -0400 Subject: [PATCH 19/49] verkle: wip insertstem and related methods for verkle nodes --- packages/verkle/src/node/baseVerkleNode.ts | 2 + packages/verkle/src/node/internalNode.ts | 75 +++++++++++++++++++--- packages/verkle/src/node/leafNode.ts | 29 +++++++-- packages/verkle/src/node/types.ts | 5 +- packages/verkle/src/util/crypto.ts | 3 + 5 files changed, 97 insertions(+), 17 deletions(-) diff --git a/packages/verkle/src/node/baseVerkleNode.ts b/packages/verkle/src/node/baseVerkleNode.ts index 6d30e46bac..b766ee136e 100644 --- a/packages/verkle/src/node/baseVerkleNode.ts +++ b/packages/verkle/src/node/baseVerkleNode.ts @@ -11,9 +11,11 @@ import type { CommitmentPoint } from '../types.js' export abstract class BaseVerkleNode implements VerkleNodeInterface { public commitment: CommitmentPoint + public depth: number constructor(options: VerkleNodeOptions[T]) { this.commitment = options.commitment + this.depth = options.depth } abstract commit(): CommitmentPoint diff --git a/packages/verkle/src/node/internalNode.ts b/packages/verkle/src/node/internalNode.ts index 5a2806a0e8..5c79c36a65 100644 --- a/packages/verkle/src/node/internalNode.ts +++ b/packages/verkle/src/node/internalNode.ts @@ -1,18 +1,24 @@ +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 { CommitmentPoint } from '../types.js' -import type { VerkleNodeOptions } from './types.js' +import type { VerkleNode, VerkleNodeOptions } from './types.js' export class InternalNode extends BaseVerkleNode { // Array of references to children nodes - public children: CommitmentPoint[] + 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 + this.children = new Array(NODE_WIDTH).fill(null) this.copyOnWrite = options.copyOnWrite } @@ -22,7 +28,15 @@ export class InternalNode extends BaseVerkleNode { // this.commit = commit } - static fromRawNode(rawNode: Uint8Array[]): InternalNode { + cowChild(index: 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') @@ -33,13 +47,18 @@ export class InternalNode extends BaseVerkleNode { throw new Error('Invalid node length') } - const children = rawNode.slice(1, NODE_WIDTH + 2) + const children: VerkleNode[] = [] // rawNode.slice(1, NODE_WIDTH + 2) const commitment = rawNode[rawNode.length - 1] - return new InternalNode({ children, commitment, copyOnWrite: {} }) + return new InternalNode({ children, commitment, copyOnWrite: {}, depth }) + } + + static create(depth: number): InternalNode { + const node = new InternalNode({ children: [], commitment: POINT_IDENTITY, copyOnWrite, depth }) + return node } - getChildren(index: number): Uint8Array | null { + getChildren(index: number): VerkleNode | null { return this.children?.[index] ?? null } @@ -49,7 +68,47 @@ export class InternalNode extends BaseVerkleNode { this.insertStem(key.slice(0, 31), values, resolver) } - insertStem(key) + 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[] { diff --git a/packages/verkle/src/node/leafNode.ts b/packages/verkle/src/node/leafNode.ts index fd4d6f0da9..6c2117f056 100644 --- a/packages/verkle/src/node/leafNode.ts +++ b/packages/verkle/src/node/leafNode.ts @@ -20,13 +20,9 @@ export class LeafNode extends BaseVerkleNode { this.c2 = options.c2 } - commit(): Uint8Array { - throw new Error('Not implemented') - // const commit = TODO - // this.commit = commit - } + static create(stem: Uint8Array, values: Uint8Array): LeafNode {} - static fromRawNode(rawNode: Uint8Array[]): LeafNode { + static fromRawNode(rawNode: Uint8Array[], depth: number): LeafNode { const nodeType = rawNode[0][0] if (nodeType !== VerkleNodeType.Leaf) { throw new Error('Invalid node type') @@ -43,13 +39,32 @@ export class LeafNode extends BaseVerkleNode { const c2 = rawNode[4] const values = rawNode.slice(5, rawNode.length) - return new LeafNode({ stem, values, c1, c2, commitment }) + return new LeafNode({ depth, stem, values, c1, c2, commitment }) + } + commit(): Uint8Array { + throw new Error('Not implemented') + // const commit = TODO + // this.commit = commit } 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 [ diff --git a/packages/verkle/src/node/types.ts b/packages/verkle/src/node/types.ts index 6bbff370b8..ee6889f6e0 100644 --- a/packages/verkle/src/node/types.ts +++ b/packages/verkle/src/node/types.ts @@ -23,11 +23,12 @@ export interface VerkleNodeInterface { interface BaseVerkleNodeOptions { // Value of the commitment commitment: CommitmentPoint + depth: number } interface VerkleInternalNodeOptions extends BaseVerkleNodeOptions { - // List of children node references of this internal node. - children: Uint8Array[] + // 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 diff --git a/packages/verkle/src/util/crypto.ts b/packages/verkle/src/util/crypto.ts index 61db99d41f..b271a3a765 100644 --- a/packages/verkle/src/util/crypto.ts +++ b/packages/verkle/src/util/crypto.ts @@ -33,3 +33,6 @@ export function getTreeKey(address: Address, treeIndex: number, subIndex: number 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) From af0b3050d21b81bdcaecf44f08635edfbfad9ba5 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 1 Oct 2023 18:05:16 -0400 Subject: [PATCH 20/49] verkle: internal node test --- .../verkle/test/node/internalNode.spec.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 packages/verkle/test/node/internalNode.spec.ts diff --git a/packages/verkle/test/node/internalNode.spec.ts b/packages/verkle/test/node/internalNode.spec.ts new file mode 100644 index 0000000000..6257f75b1c --- /dev/null +++ b/packages/verkle/test/node/internalNode.spec.ts @@ -0,0 +1,44 @@ +import { equalsBytes, hexToBytes, 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' + +describe('verkle node - internal', () => { + it('constructor should create an internal node', async () => { + const commitment = randomBytes(32) + const depth = 2 + const node = new InternalNode({ commitment, depth }) + + assert.equal(node.type, VerkleNodeType.Internal, 'type should be set') + assert.ok(equalsBytes(node.commitment, 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, POINT_IDENTITY), + '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' + ) + }) +}) From eac20718f5bf160c8b743ce5afdc56e637414f9f Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 1 Oct 2023 21:02:57 -0400 Subject: [PATCH 21/49] verkle: default values and minor fixes to internalNode --- packages/verkle/src/node/internalNode.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/verkle/src/node/internalNode.ts b/packages/verkle/src/node/internalNode.ts index 5c79c36a65..f05c8086dc 100644 --- a/packages/verkle/src/node/internalNode.ts +++ b/packages/verkle/src/node/internalNode.ts @@ -18,8 +18,8 @@ export class InternalNode extends BaseVerkleNode { /* TODO: options.children is not actually used here */ constructor(options: VerkleNodeOptions[VerkleNodeType.Internal]) { super(options) - this.children = new Array(NODE_WIDTH).fill(null) - this.copyOnWrite = options.copyOnWrite + this.children = options.children ?? new Array(NODE_WIDTH).fill(null) + this.copyOnWrite = options.copyOnWrite ?? {} } commit(): Uint8Array { @@ -47,14 +47,17 @@ export class InternalNode extends BaseVerkleNode { throw new Error('Invalid node length') } - const children: VerkleNode[] = [] // rawNode.slice(1, NODE_WIDTH + 2) const commitment = rawNode[rawNode.length - 1] - return new InternalNode({ children, commitment, copyOnWrite: {}, depth }) + return new InternalNode({ commitment, depth }) } static create(depth: number): InternalNode { - const node = new InternalNode({ children: [], commitment: POINT_IDENTITY, copyOnWrite, depth }) + const node = new InternalNode({ + commitment: POINT_IDENTITY, + depth, + }) + return node } @@ -112,6 +115,7 @@ export class InternalNode extends BaseVerkleNode { // TODO: go-verkle also adds the bitlist to the raw format. raw(): Uint8Array[] { - return [new Uint8Array([VerkleNodeType.Internal]), ...this.children, this.commitment] + throw new Error('not implemented yet') + // return [new Uint8Array([VerkleNodeType.Internal]), ...this.children, this.commitment] } } From 725e978ca2a85635af3a253930844e1253fb8559 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 1 Oct 2023 21:03:16 -0400 Subject: [PATCH 22/49] verkle: make internal verkle node options optional --- packages/verkle/src/node/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/verkle/src/node/types.ts b/packages/verkle/src/node/types.ts index ee6889f6e0..ab9cecba7f 100644 --- a/packages/verkle/src/node/types.ts +++ b/packages/verkle/src/node/types.ts @@ -28,11 +28,11 @@ interface BaseVerkleNodeOptions { interface VerkleInternalNodeOptions extends BaseVerkleNodeOptions { // Children nodes of this internal node. - children: VerkleNode[] + 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 + copyOnWrite?: Record } interface VerkleLeafNodeOptions extends BaseVerkleNodeOptions { stem: Uint8Array From 4188471e66abc551bf91cf153f974cea61a99b3e Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 1 Oct 2023 21:24:30 -0400 Subject: [PATCH 23/49] verkle: leafNode basic test --- packages/verkle/test/node/leafNode.spec.ts | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 packages/verkle/test/node/leafNode.spec.ts diff --git a/packages/verkle/test/node/leafNode.spec.ts b/packages/verkle/test/node/leafNode.spec.ts new file mode 100644 index 0000000000..edf53ea375 --- /dev/null +++ b/packages/verkle/test/node/leafNode.spec.ts @@ -0,0 +1,30 @@ +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' + +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, c2, commitment, depth, stem, values }) + + assert.equal(node.type, VerkleNodeType.Leaf, 'type should be set') + assert.ok(equalsBytes(node.commitment, commitment), 'commitment should be set') + assert.ok(equalsBytes(node.c1, c1), 'c1 should be set') + assert.ok(equalsBytes(node.c2, 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') +}) From d8e3e96f08082f3ead71f106eeabe972f87ef84f Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 1 Oct 2023 21:24:42 -0400 Subject: [PATCH 24/49] verkle: remove unused import --- packages/verkle/test/node/internalNode.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/verkle/test/node/internalNode.spec.ts b/packages/verkle/test/node/internalNode.spec.ts index 6257f75b1c..eda3660b26 100644 --- a/packages/verkle/test/node/internalNode.spec.ts +++ b/packages/verkle/test/node/internalNode.spec.ts @@ -1,4 +1,4 @@ -import { equalsBytes, hexToBytes, randomBytes } from '@ethereumjs/util' +import { equalsBytes, randomBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' import { NODE_WIDTH, VerkleNodeType } from '../../src/node/index.js' From b79070e73bbda1c5dd40581e8c6e878a43f17a5e Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 1 Oct 2023 21:25:29 -0400 Subject: [PATCH 25/49] verkle: setDepth method for leafNode and commented out code for create method --- packages/verkle/src/node/leafNode.ts | 63 +++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/packages/verkle/src/node/leafNode.ts b/packages/verkle/src/node/leafNode.ts index 6c2117f056..3b7f5a645a 100644 --- a/packages/verkle/src/node/leafNode.ts +++ b/packages/verkle/src/node/leafNode.ts @@ -20,7 +20,64 @@ export class LeafNode extends BaseVerkleNode { this.c2 = options.c2 } - static create(stem: Uint8Array, values: Uint8Array): LeafNode {} + static create(stem: Uint8Array, values: Uint8Array[]): LeafNode { + throw new Error('Not implemented') + + /* + const cfg = GetConfig(); + +// C1. +const c1poly: Fr[] = new Array(); +const c1: Point = new Point(); +let count: number; +let err: Error; +[count, err] = fillSuffixTreePoly(c1poly, values.slice(0, NodeWidth / 2)); +if (err) { + return [null, err]; +} +const containsEmptyCodeHash: boolean = + c1poly.length >= EmptyCodeHashSecondHalfIdx && + c1poly[EmptyCodeHashFirstHalfIdx].Equal(EmptyCodeHashFirstHalfValue) && + c1poly[EmptyCodeHashSecondHalfIdx].Equal(EmptyCodeHashSecondHalfValue); +if (containsEmptyCodeHash) { + // Clear out values of the cached point. + c1poly[EmptyCodeHashFirstHalfIdx] = FrZero; + c1poly[EmptyCodeHashSecondHalfIdx] = FrZero; + // Calculate the remaining part of c1 and add to the base value. + const partialc1: Point = cfg.CommitToPoly(c1poly, NodeWidth - count - 2); + c1.Add(EmptyCodeHashPoint, partialc1); +} else { + c1.Copy(cfg.CommitToPoly(c1poly, NodeWidth - count)); +} + +// C2. +const c2poly: Fr[] = new Array(); +[count, err] = fillSuffixTreePoly(c2poly, values.slice(NodeWidth / 2)); +if (err) { + return [null, err]; +} +const c2: Point = cfg.CommitToPoly(c2poly, NodeWidth - count); + +// Root commitment preparation for calculation. +stem = stem.slice(0, StemSize); // enforce a 31-byte length +const poly: Fr[] = new Array(); +poly[0].SetUint64(1); +err = StemFromBytes(poly[1], stem); +if (err) { + return [null, err]; +} +toFrMultiple([poly[2], poly[3]], [c1, c2]); + +return { + // depth will be 0, but the commitment calculation + // does not need it, and so it won't be free. + values: values, + stem: stem, + commitment: cfg.CommitToPoly(poly, NodeWidth - 4), + c1: c1, + c2: c2, +}; */ + } static fromRawNode(rawNode: Uint8Array[], depth: number): LeafNode { const nodeType = rawNode[0][0] @@ -76,4 +133,8 @@ export class LeafNode extends BaseVerkleNode { ...this.values, ] } + + setDepth(depth: number): void { + this.depth = depth + } } From 98263fecff8f9669de229ae51b2aebd1df3dd501 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Fri, 6 Oct 2023 12:38:26 -0400 Subject: [PATCH 26/49] verkle: wip --- packages/verkle/src/verkleTrie.ts | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/verkle/src/verkleTrie.ts b/packages/verkle/src/verkleTrie.ts index 25c3b0f3fe..5e163dd0b5 100644 --- a/packages/verkle/src/verkleTrie.ts +++ b/packages/verkle/src/verkleTrie.ts @@ -167,7 +167,30 @@ export class VerkleTrie { * @returns A Promise that resolves once value is stored. */ async put(key: Uint8Array, value: Uint8Array): Promise { - throw new Error('Not implemented') + // Create the leaf node + // const leafNode = new LeafNode({value:} new Map([[key, value]])) + await this._db.put(key, value) + // const leafKey = getLeafKey(key) + // await this.putNode(leafKey, leafNode) + + // Walk up the trie and update internal nodes + let currentNode = leafNode + let currentKey = leafKey + let currentDepth = this.maxKeyLength + + while (currentDepth > 0) { + const parentKey = currentKey.slice(0, -1) + const parentIndex = currentKey[currentKey.length - 1] + const parentNode: VerkleNode[] | null = InternalNode.create() + parentNode.children[parentIndex] = currentNode + await this.putNode(parentKey, parentNode) + + currentNode = parentNode + currentKey = parentKey + currentDepth-- + } + + this.root = currentNode } /** From db9661c3aeef1f851a1c6e6c60aaf7ea6a5199da Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sat, 14 Oct 2023 23:15:04 -0400 Subject: [PATCH 27/49] verkle: add Point interface --- packages/verkle/src/types.ts | 57 ++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/packages/verkle/src/types.ts b/packages/verkle/src/types.ts index c2f6200092..3a16ead202 100644 --- a/packages/verkle/src/types.ts +++ b/packages/verkle/src/types.ts @@ -4,8 +4,59 @@ import type { VerkleNode } from './node' import type { WalkController } from './util/walkController' import type { DB } from '@ethereumjs/util' -// Curve point of a commitment -export type CommitmentPoint = Uint8Array +// Field representation of a commitment +export interface Fr {} + +// Elliptic curve point representation of a commitment +export interface Point { + // Bytes returns the compressed serialized version of the element. + bytes(): Uint8Array + // BytesUncompressed returns the uncompressed serialized version of the element. + bytesUncompressed(): Uint8Array + + // SetBytes deserializes a compressed group element from buf. + // This method does all the proper checks assuming the bytes come from an + // untrusted source. + setBytes(bytes: Uint8Array): void + + // SetBytesUncompressed deserializes an uncompressed group element from buf. + setBytesUncompressed(bytes: Uint8Array, trusted: boolean): void + + // computes X/Y + mapToBaseField(): Point + + // mapToScalarField maps a group element to the scalar field. + mapToScalarField(field: Fr): void + + // Equal returns true if p and other represent the same point. + equal(secondPoint: Point): boolean + + // SetIdentity sets p to the identity element. + setIdentity(): Point + + // Double sets p to 2*p1. + double(point1: Point): Point + + // Add sets p to p1+p2. + add(point1: Point, point2: Point): Point + + // Sub sets p to p1-p2. + sub(point1: Point, point2: Point): Point + + // IsOnCurve returns true if p is on the curve. + isOnCurve(): boolean + + normalise(): void + + // Set sets p to p1. + set(): Point + + // Neg sets p to -p1. + neg(): Point + + // ScalarMul sets p to p1*s. + scalarMul(point1: Point, scalarMont: Fr): Point +} export type Proof = Uint8Array[] @@ -58,7 +109,7 @@ export type Checkpoint = { } export type FoundNodeFunction = ( - nodeRef: CommitmentPoint, + nodeRef: Point, node: VerkleNode | null, key: Uint8Array, walkController: WalkController From 3acc7633447c7e6ddc31bfa78c0d120731d3aa66 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sat, 14 Oct 2023 23:31:12 -0400 Subject: [PATCH 28/49] verkle: general cleanup and improvements --- packages/verkle/src/node/baseVerkleNode.ts | 15 ++--- packages/verkle/src/node/internalNode.ts | 13 ++-- packages/verkle/src/node/leafNode.ts | 77 ++++------------------ packages/verkle/src/node/types.ts | 12 ++-- packages/verkle/src/node/util.ts | 5 +- packages/verkle/src/types.ts | 2 +- packages/verkle/src/util/crypto.ts | 4 +- packages/verkle/src/util/walkController.ts | 12 ++-- packages/verkle/src/verkleTrie.ts | 29 ++++---- 9 files changed, 60 insertions(+), 109 deletions(-) diff --git a/packages/verkle/src/node/baseVerkleNode.ts b/packages/verkle/src/node/baseVerkleNode.ts index b766ee136e..01aee3ebfe 100644 --- a/packages/verkle/src/node/baseVerkleNode.ts +++ b/packages/verkle/src/node/baseVerkleNode.ts @@ -1,16 +1,11 @@ import { RLP } from '@ethereumjs/rlp' -import { - NODE_WIDTH, - type VerkleNodeInterface, - type VerkleNodeOptions, - type VerkleNodeType, -} from './types.js' +import { type VerkleNodeInterface, type VerkleNodeOptions, type VerkleNodeType } from './types.js' -import type { CommitmentPoint } from '../types.js' +import type { Point } from '../types.js' export abstract class BaseVerkleNode implements VerkleNodeInterface { - public commitment: CommitmentPoint + public commitment: Point public depth: number constructor(options: VerkleNodeOptions[T]) { @@ -18,10 +13,10 @@ export abstract class BaseVerkleNode implements Verkle this.depth = options.depth } - abstract commit(): CommitmentPoint + abstract commit(): Point // Hash returns the field representation of the commitment. - hash(): any { + hash(): Uint8Array { throw new Error('Not implemented') } diff --git a/packages/verkle/src/node/internalNode.ts b/packages/verkle/src/node/internalNode.ts index f05c8086dc..9371744124 100644 --- a/packages/verkle/src/node/internalNode.ts +++ b/packages/verkle/src/node/internalNode.ts @@ -6,13 +6,13 @@ import { BaseVerkleNode } from './baseVerkleNode.js' import { LeafNode } from './leafNode.js' import { NODE_WIDTH, VerkleNodeType } from './types.js' -import type { CommitmentPoint } 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 copyOnWrite: Record public type = VerkleNodeType.Internal /* TODO: options.children is not actually used here */ @@ -22,13 +22,11 @@ export class InternalNode extends BaseVerkleNode { this.copyOnWrite = options.copyOnWrite ?? {} } - commit(): Uint8Array { + commit(): Point { throw new Error('Not implemented') - // const commit = TODO - // this.commit = commit } - cowChild(index: number): void { + cowChild(_: number): void { // Not implemented yet } @@ -47,7 +45,8 @@ export class InternalNode extends BaseVerkleNode { throw new Error('Invalid node length') } - const commitment = rawNode[rawNode.length - 1] + // TODO: Generate Point from rawNode value + const commitment = rawNode[rawNode.length - 1] as unknown as Point return new InternalNode({ commitment, depth }) } diff --git a/packages/verkle/src/node/leafNode.ts b/packages/verkle/src/node/leafNode.ts index 3b7f5a645a..38b75d66c7 100644 --- a/packages/verkle/src/node/leafNode.ts +++ b/packages/verkle/src/node/leafNode.ts @@ -1,14 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { BaseVerkleNode } from './baseVerkleNode.js' import { NODE_WIDTH, VerkleNodeType } from './types.js' -import type { CommitmentPoint } 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: CommitmentPoint - public c2: CommitmentPoint + public c1: Point + public c2: Point public type = VerkleNodeType.Leaf constructor(options: VerkleNodeOptions[VerkleNodeType.Leaf]) { @@ -22,61 +23,6 @@ export class LeafNode extends BaseVerkleNode { static create(stem: Uint8Array, values: Uint8Array[]): LeafNode { throw new Error('Not implemented') - - /* - const cfg = GetConfig(); - -// C1. -const c1poly: Fr[] = new Array(); -const c1: Point = new Point(); -let count: number; -let err: Error; -[count, err] = fillSuffixTreePoly(c1poly, values.slice(0, NodeWidth / 2)); -if (err) { - return [null, err]; -} -const containsEmptyCodeHash: boolean = - c1poly.length >= EmptyCodeHashSecondHalfIdx && - c1poly[EmptyCodeHashFirstHalfIdx].Equal(EmptyCodeHashFirstHalfValue) && - c1poly[EmptyCodeHashSecondHalfIdx].Equal(EmptyCodeHashSecondHalfValue); -if (containsEmptyCodeHash) { - // Clear out values of the cached point. - c1poly[EmptyCodeHashFirstHalfIdx] = FrZero; - c1poly[EmptyCodeHashSecondHalfIdx] = FrZero; - // Calculate the remaining part of c1 and add to the base value. - const partialc1: Point = cfg.CommitToPoly(c1poly, NodeWidth - count - 2); - c1.Add(EmptyCodeHashPoint, partialc1); -} else { - c1.Copy(cfg.CommitToPoly(c1poly, NodeWidth - count)); -} - -// C2. -const c2poly: Fr[] = new Array(); -[count, err] = fillSuffixTreePoly(c2poly, values.slice(NodeWidth / 2)); -if (err) { - return [null, err]; -} -const c2: Point = cfg.CommitToPoly(c2poly, NodeWidth - count); - -// Root commitment preparation for calculation. -stem = stem.slice(0, StemSize); // enforce a 31-byte length -const poly: Fr[] = new Array(); -poly[0].SetUint64(1); -err = StemFromBytes(poly[1], stem); -if (err) { - return [null, err]; -} -toFrMultiple([poly[2], poly[3]], [c1, c2]); - -return { - // depth will be 0, but the commitment calculation - // does not need it, and so it won't be free. - values: values, - stem: stem, - commitment: cfg.CommitToPoly(poly, NodeWidth - 4), - c1: c1, - c2: c2, -}; */ } static fromRawNode(rawNode: Uint8Array[], depth: number): LeafNode { @@ -91,14 +37,15 @@ return { } const stem = rawNode[1] - const commitment = rawNode[2] - const c1 = rawNode[3] - const c2 = rawNode[4] + // 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(): Uint8Array { + commit(): Point { throw new Error('Not implemented') // const commit = TODO // this.commit = commit @@ -127,9 +74,9 @@ return { return [ new Uint8Array([VerkleNodeType.Leaf]), this.stem, - this.commitment, - this.c1, - this.c2, + this.commitment.bytes(), + this.c1.bytes(), + this.c2.bytes(), ...this.values, ] } diff --git a/packages/verkle/src/node/types.ts b/packages/verkle/src/node/types.ts index ab9cecba7f..db975a5e5b 100644 --- a/packages/verkle/src/node/types.ts +++ b/packages/verkle/src/node/types.ts @@ -1,4 +1,4 @@ -import type { CommitmentPoint } from '../types.js' +import type { Point } from '../types.js' import type { InternalNode } from './internalNode.js' import type { LeafNode } from './leafNode.js' @@ -15,14 +15,14 @@ export interface TypedVerkleNode { export type VerkleNode = TypedVerkleNode[VerkleNodeType] export interface VerkleNodeInterface { - commit(): Uint8Array + commit(): Point hash(): any serialize(): Uint8Array } interface BaseVerkleNodeOptions { // Value of the commitment - commitment: CommitmentPoint + commitment: Point depth: number } @@ -32,13 +32,13 @@ interface VerkleInternalNodeOptions extends BaseVerkleNodeOptions { // 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 + copyOnWrite?: Record } interface VerkleLeafNodeOptions extends BaseVerkleNodeOptions { stem: Uint8Array values: Uint8Array[] - c1: CommitmentPoint - c2: CommitmentPoint + c1: Point + c2: Point } export interface VerkleNodeOptions { diff --git a/packages/verkle/src/node/util.ts b/packages/verkle/src/node/util.ts index 1991793d0b..16726076dc 100644 --- a/packages/verkle/src/node/util.ts +++ b/packages/verkle/src/node/util.ts @@ -6,11 +6,12 @@ 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) + return InternalNode.fromRawNode(raw, depth) case VerkleNodeType.Leaf: - return LeafNode.fromRawNode(raw) + return LeafNode.fromRawNode(raw, depth) default: throw new Error('Invalid node type') } diff --git a/packages/verkle/src/types.ts b/packages/verkle/src/types.ts index 3a16ead202..9e17f95bce 100644 --- a/packages/verkle/src/types.ts +++ b/packages/verkle/src/types.ts @@ -109,7 +109,7 @@ export type Checkpoint = { } export type FoundNodeFunction = ( - nodeRef: Point, + nodeRef: Uint8Array, node: VerkleNode | null, key: Uint8Array, walkController: WalkController diff --git a/packages/verkle/src/util/crypto.ts b/packages/verkle/src/util/crypto.ts index b271a3a765..266560f6eb 100644 --- a/packages/verkle/src/util/crypto.ts +++ b/packages/verkle/src/util/crypto.ts @@ -2,6 +2,8 @@ import { type Address, concatBytes, setLengthLeft, toBytes, writeInt32LE } from 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) @@ -35,4 +37,4 @@ export function getTreeKey(address: Address, treeIndex: number, subIndex: number } // TODO: Replace this by the actual value of Point().Identity() from the Go code. -export const POINT_IDENTITY = new Uint8Array(32).fill(0) +export const POINT_IDENTITY = new Uint8Array(32).fill(0) as unknown as Point diff --git a/packages/verkle/src/util/walkController.ts b/packages/verkle/src/util/walkController.ts index c10590bc10..1a8bbdbc4f 100644 --- a/packages/verkle/src/util/walkController.ts +++ b/packages/verkle/src/util/walkController.ts @@ -3,7 +3,7 @@ import { InternalNode, LeafNode } from '../node/index.js' import { PrioritizedTaskExecutor } from './tasks.js' import type { VerkleNode } from '../node/types.js' -import type { CommitmentPoint, FoundNodeFunction } from '../types.js' +import type { FoundNodeFunction } from '../types.js' import type { VerkleTrie } from '../verkleTrie.js' /** @@ -78,8 +78,10 @@ export class WalkController { })) for (const child of children) { - const childKey = new Uint8Array([...key, child.keyExtension]) - this.pushNodeToQueue(child.nodeRef, childKey) + if (child.nodeRef !== null) { + const childKey = new Uint8Array([...key, child.keyExtension]) + this.pushNodeToQueue(child.nodeRef.hash(), childKey) + } } } @@ -126,11 +128,11 @@ export class WalkController { throw new Error('Could not get node at childIndex') } const childKey = new Uint8Array([...key, childIndex]) - this.pushNodeToQueue(childRef, childKey, priority ?? childKey.length) + this.pushNodeToQueue(childRef.hash(), childKey, priority ?? childKey.length) } private processNode( - nodeRef: CommitmentPoint, + nodeRef: Uint8Array, node: VerkleNode | null, key: Uint8Array = new Uint8Array(0) ) { diff --git a/packages/verkle/src/verkleTrie.ts b/packages/verkle/src/verkleTrie.ts index 5e163dd0b5..e5afcf4c0f 100644 --- a/packages/verkle/src/verkleTrie.ts +++ b/packages/verkle/src/verkleTrie.ts @@ -23,7 +23,7 @@ import { WalkController, matchingBytesLength } from './util/index.js' import { Lock } from './util/lock.js' import type { VerkleNode } from './node/types.js' -import type { CommitmentPoint, FoundNodeFunction } from './types.js' +import type { FoundNodeFunction, Point } from './types.js' import type { BatchDBOp, DB, PutBatch } from '@ethereumjs/util' interface Path { @@ -147,6 +147,7 @@ export class VerkleTrie { * @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] @@ -167,30 +168,34 @@ export class VerkleTrie { * @returns A Promise that resolves once value is stored. */ async put(key: Uint8Array, value: Uint8Array): Promise { - // Create the leaf node - // const leafNode = new LeafNode({value:} new Map([[key, value]])) await this._db.put(key, value) - // const leafKey = getLeafKey(key) - // await this.putNode(leafKey, leafNode) + + // 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 = leafNode - let currentKey = leafKey - let currentDepth = this.maxKeyLength + 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: VerkleNode[] | null = InternalNode.create() + const parentNode = InternalNode.create(currentDepth) parentNode.children[parentIndex] = currentNode - await this.putNode(parentKey, parentNode) + await this._db.put(parentKey, parentNode.serialize()) currentNode = parentNode currentKey = parentKey currentDepth-- } - this.root = currentNode + this._root = currentNode.hash() } /** @@ -292,7 +297,7 @@ export class VerkleTrie { /** * Retrieves a node from db by hash. */ - async lookupNode(node: CommitmentPoint | Uint8Array[]): Promise { + async lookupNode(node: Uint8Array | Uint8Array[]): Promise { if (isRawNode(node)) { return decodeRawNode(node) } From c4775f92fe1e185f8e14d4b2cf2d33dccbae8ee9 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 15 Oct 2023 11:57:02 -0400 Subject: [PATCH 29/49] verkle: minor test adjustments --- packages/verkle/package.json | 2 +- packages/verkle/src/node/leafNode.ts | 2 -- .../verkle/test/node/internalNode.spec.ts | 14 ++++++++++--- packages/verkle/test/node/leafNode.spec.ts | 20 +++++++++++++++---- packages/verkle/test/verkle.spec.ts | 3 ++- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/packages/verkle/package.json b/packages/verkle/package.json index 38097f45a0..298566e0ca 100644 --- a/packages/verkle/package.json +++ b/packages/verkle/package.json @@ -1,6 +1,6 @@ { "name": "@ethereumjs/verkle", - "version": "1.0.0", + "version": "0.0.1", "description": "Implementation of verkle tries as used in Ethereum.", "keywords": [ "verkle", diff --git a/packages/verkle/src/node/leafNode.ts b/packages/verkle/src/node/leafNode.ts index 38b75d66c7..dd11308d82 100644 --- a/packages/verkle/src/node/leafNode.ts +++ b/packages/verkle/src/node/leafNode.ts @@ -47,8 +47,6 @@ export class LeafNode extends BaseVerkleNode { } commit(): Point { throw new Error('Not implemented') - // const commit = TODO - // this.commit = commit } getValue(index: number): Uint8Array | null { diff --git a/packages/verkle/test/node/internalNode.spec.ts b/packages/verkle/test/node/internalNode.spec.ts index eda3660b26..f27b375a86 100644 --- a/packages/verkle/test/node/internalNode.spec.ts +++ b/packages/verkle/test/node/internalNode.spec.ts @@ -5,14 +5,19 @@ 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, depth }) + 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, commitment), 'commitment 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. @@ -29,7 +34,10 @@ describe('verkle node - internal', () => { assert.equal(node.type, VerkleNodeType.Internal, 'type should be set') assert.ok( - equalsBytes(node.commitment, POINT_IDENTITY), + 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') diff --git a/packages/verkle/test/node/leafNode.spec.ts b/packages/verkle/test/node/leafNode.spec.ts index edf53ea375..251c4cc6c3 100644 --- a/packages/verkle/test/node/leafNode.spec.ts +++ b/packages/verkle/test/node/leafNode.spec.ts @@ -4,6 +4,8 @@ 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) @@ -12,12 +14,22 @@ describe('verkle node - leaf', () => { const stem = randomBytes(32) const values = [randomBytes(32), randomBytes(32)] const depth = 2 - const node = new LeafNode({ c1, c2, commitment, depth, stem, values }) + 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, commitment), 'commitment should be set') - assert.ok(equalsBytes(node.c1, c1), 'c1 should be set') - assert.ok(equalsBytes(node.c2, c2), 'c2 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])), diff --git a/packages/verkle/test/verkle.spec.ts b/packages/verkle/test/verkle.spec.ts index cbc87add4f..6bccd73918 100644 --- a/packages/verkle/test/verkle.spec.ts +++ b/packages/verkle/test/verkle.spec.ts @@ -37,13 +37,14 @@ const values = [ '0xe703000000000000000000000000000000000000000000000000000000000000', ].map(hexToBytes) +// eslint-disable-next-line @typescript-eslint/no-unused-vars const absentKeys = [ '0x318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d03', '0x318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d04', ].map(hexToBytes) describe('Verkle trie', () => { - it('should insert and retrieve values', async () => { + 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]) From 469196f6c2f197d7bd91ffbb18d3c4c6297407e7 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 15 Oct 2023 12:46:15 -0400 Subject: [PATCH 30/49] verkle: readme and testing related updates --- .../package--ethereumjs-verkle.md | 7 + .github/workflows/verkle-build.yml | 42 ++ README.md | 10 + package-lock.json | 19 +- packages/client/CHANGELOG.md | 2 - packages/verkle/CHANGELOG.md | 603 +----------------- packages/verkle/README.md | 216 +------ packages/verkle/package.json | 19 +- 8 files changed, 88 insertions(+), 830 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/package--ethereumjs-verkle.md create mode 100644 .github/workflows/verkle-build.yml 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..a622cd62b7 --- /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" diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md index 83ed1611c1..0ba2a5dbf4 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -562,5 +562,3 @@ showing the relations between the main components as well as the initialization ## [0.0.1] - 2018-10-26 - Initial development release - -[0.0.1]: https://github.com/ethereumjs/ethereumjs-client/tree/v0.0.1 diff --git a/packages/verkle/CHANGELOG.md b/packages/verkle/CHANGELOG.md index 027f2549af..459edbc1b3 100644 --- a/packages/verkle/CHANGELOG.md +++ b/packages/verkle/CHANGELOG.md @@ -6,605 +6,6 @@ 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). -## 6.0.0-rc.1 - 2023-07-18 +## [0.0.1] - 2023-10-15 -This is the release candidate (RC1) for the upcoming breaking releases on the various EthereumJS libraries. The associated release notes below are the main source of information on the changeset, also for the upcoming final releases, where we'll just provide change addition summaries + references to these RC1 notes. - -At time of the RC1 releases there is/was no plan for a second RC round and breaking releases following relatively shorty (2-3 weeks) after the RC1 round. Things may change though depending on the feedback we'll receive. - -### Introduction - -This round of breaking releases brings the EthereumJS libraries to the browser. Finally! 🤩 - -While you could use our libraries in the browser libraries before, there had been caveats. - -WE HAVE ELIMINATED ALL OF THEM. - -The largest two undertakings: First: we have rewritten all (half) of our API and elimited the usage of Node.js specific `Buffer` all over the place and have rewritten with using `Uint8Array` byte objects. Second: we went throuh our whole stack, rewrote imports and exports, replaced and updated dependencies all over and are now able to provide a hybrid CommonJS/ESM build, for all libraries. Both of these things are huge. - -Together with some few other modifications this now allows to run each (maybe adding an asterisk for client and devp2p) of our libraries directly in the browser - more or less without any modifications - see the `examples/browser.html` file in each package folder for an easy to set up example. - -This is generally a big thing for Ethereum cause this brings the full Ethereum Execution Layer (EL) protocol stack to the browser in an easy accessible way for developers, for the first time ever! 🎉 - -This will allow for easy-to-setup browser applications both around the existing as well as the upcoming Ethereum EL protocol stack in the future. 🏄🏾‍♂️ We are beyond excitement to see what you guys will be building with this for "Browser-Ethereum". 🤓 - -Browser is not the only thing though why this release round is exciting: default Shanghai hardfork, full Cancun support, significantly smaller bundle sizes for various libraries, new database abstractions, a simpler to use EVM, API clean-ups throughout the whole stack. These are just the most prominent additional things here to mention which will make the developer heart beat a bit faster hopefully when you are scanning to the vast release notes for every of the 15 (!) releases! 🧑🏽‍💻 - -So: jump right in and enjoy. We can't wait to hear your feedback and see if you agree that these releases are as good as we think they are. 🙂 ❤️ - -The EthereumJS Team - -### New Trie Node Cache - -There is a new permanent trie node cache which can be leveraged to make Trie operations significantly faster, see PR [#2667](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2667). Since this also increases base-memory usage of a trie instantiation, this new cache is mainly intended to be used in rather long-lived trie scenarios. - -The new cache can be activated by setting a fitting cache size with the new `cacheSize` option (default: `0` (deactivated)). - -### Hybrid CJS/ESM Build - -We now provide both a CommonJS and an ESM build for all our libraries. 🥳 This transition was a huge undertaking and should make the usage of our libraries in the browser a lot more straight-forward, see PR [#2685](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2685), [#2783](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2783), [#2786](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2786), [#2764](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2764), [#2804](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2804) and [#2809](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2809) (and others). We rewrote the whole set of imports and exports within the libraries, updated or completely removed a lot of dependencies along the way and removed the usage of all native Node.js primitives (like `https` or `util`). - -There are now two different build directories in our `dist` folder, being `dist/cjs` for the CommonJS and `dist/esm` for the `ESM` build. That means that direct imports (which you generally should try to avoid, rather open an issue on your import needs), need an update within your code (do a `dist` or the like code search). - -Both builds have respective separate entrypoints in the distributed `package.json` file. - -A CommonJS import of our libraries can then be done like this: - -```typescript -const { Chain, Common } = require('@ethereumjs/common') -const common = new Common({ chain: Chain.Mainnet }) -``` - -And this is how an ESM import looks like: - -```typescript -import { Chain, Common } from '@ethereumjs/common' -const common = new Common({ chain: Chain.Mainnet }) -``` - -Using ESM will give you additional advantages over CJS beyond browser usage like static code analysis / Tree Shaking which CJS can not provide. - -Side note: along this transition we also rewrote our whole test suite (yes!!!) to now work with [Vitest](https://vitest.dev/) instead of `Tape`. - -### Buffer -> Uint8Array - -With these releases we remove all Node.js specific `Buffer` usages from our libraries and replace these with [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) representations, which are available both in Node.js and the browser (`Buffer` is a subclass of `Uint8Array`). While this is a big step towards interoperability and browser compatibility of our libraries, this is also one of the most invasive operations we have ever done, see the huge changeset from PR [#2566](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2566) and [#2607](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2607). 😋 - -We nevertheless think this is very much worth it and we tried to make transition work as easy as possible. - -#### How to upgrade? - -For this library you should check if you use one of the following constructors, methods, constants or types and do a search and update input and/or output values or general usages and add conversion methods if necessary: - -```typescript -Trie.create() / new Trie() // root constructor option -Trie.root(value?: Uint8Array | null): Uint8Array -Trie.checkRoot(root: Uint8Array): Promise -Trie.get(key: Uint8Array, throwIfMissing = false): Promise -Trie.put(key: Uint8Array, value: Uint8Array): Promise -Trie.del(key: Uint8Array): Promise -Trie.findPath(key: Uint8Array, throwIfMissing = false): Promise -Trie.walkTrie(root: Uint8Array, onFound: FoundNodeFunction): Promise -Trie.lookupNode(node: Uint8Array | Uint8Array[]): Promise -Trie.createProof(key: Uint8Array): Promise -Trie.verifyProof() -Trie.createReadStream() -Trie.hash(msg: Uint8Array): Uint8Array -``` - -So basically the whole API. Lol. 😋 - -We have converted existing Buffer conversion methods to Uint8Array conversion methods in the [@ethereumjs/util](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/util) `bytes` module, see the respective README section for guidance. - -#### Prefixed Hex Strings as Default - -The mixed usage of prefixed and unprefixed hex strings is a constant source of errors in byte-handling code bases. - -We have therefore decided to go "prefixed" by default, see PR [#2830](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2830) and [#2845](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2845). - -The `hexToBytes` and `bytesToHex` methods, also similar methods like `intToHex`, now take `0x`-prefixed hex strings as input and output prefixed strings. The corresponding unprefixed methods are marked as `deprecated` and usage should be avoided. - -Please therefore check you code base on updating and ensure that values you are passing to constructors and methods are prefixed with a `0x`. - -### Other Changes - -- Support for `Node.js 16` has been removed (minimal version: `Node.js 18`), PR [#2859](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2859) -- Breaking: `DB` interface and `MapDB` implementation have been moved to [@ethereumjs/util](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/util/src/db.ts) (for re-usage), PR [#2669](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2669) -- Breaking: The `copy()` method has been renamed to `shallowCopy()` (same underlying state DB), PR [#2826](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2826) - -## 5.0.5 - 2023-04-20 - -- Update ethereum-cryptography from 1.2 to 2.0 (switch from noble-secp256k1 to noble-curves), PR [#2641](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2641) -- Bump `@ethereumjs/util` `@chainsafe/ssz` dependency to 0.11.1 (no WASM, native SHA-256 implementation, ES2019 compatible, explicit imports), PRs [#2622](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2622), [#2564](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2564) and [#2656](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2656) - -## 5.0.4 - 2023-02-27 - -- Pinned `@ethereumjs/util` `@chainsafe/ssz` dependency to `v0.9.4` due to ES2021 features used in `v0.10.+` causing compatibility issues, PR [#2555](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2555) - -## 5.0.3 - 2023-02-21 - -**DEPRECATED**: Release is deprecated due to broken dependencies, please update to the subsequent bugfix release version. - -Maintenance release with dependency updates, PR [#2521](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2521) - -## 5.0.2 - 2022-12-09 - -Maintenance release with dependency updates, PR [#2445](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2445) - -## 5.0.1 - 2022-10-18 - -- Fixed a dependency issue when using `TrieReadStream`, PR [#2318](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2318) -- Fixed a pruning verification issue for a case where only the root key is present as a key but not the root value, PR [#2296](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2296) - -## 5.0.0 - 2022-09-06 - -Final release - tada 🎉 - of a wider breaking release round on the [EthereumJS monorepo](https://github.com/ethereumjs/ethereumjs-monorepo) libraries, see the Beta 1 release notes for the main long change set description as well as the Beta 2, Beta 3 and Release Candidate (RC) 1 release notes for notes on some additional changes ([CHANGELOG](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/trie/CHANGELOG.md)). - -### Trie Pruning - -Some great last minute feature (thanks @faustbrian and @jochem-brouwer on this!) allowing to prune a trie on state root updates with a new `useNodePruning` option (default: `false`), see PR [#2203](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2203). This allows for a much smaller DB footprint for the trie for use cases with frequent state root updates. - -### Other Changes - -- New `setCheckpoints(checkpoints: Checkpoint[])` method to support the manual setting of checkpoints for advanced use cases, PR [#2240](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2240) -- Internal refactor: removed ambiguous boolean checks within conditional clauses, PR [#2249](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2249) - -## 5.0.0-rc.1 - 2022-08-29 - -Release candidate 1 for the upcoming breaking release round on the [EthereumJS monorepo](https://github.com/ethereumjs/ethereumjs-monorepo) libraries, see the Beta 1 release notes for the main long change set description as well as the Beta 2 and 3 release notes for notes on some additional changes ([CHANGELOG](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/trie/CHANGELOG.md)). - -From Beta 3 to RC 1 the `Trie` library has seen the most vast set of changes, thanks again to @faustbrian for this various meaningful refactoring contributions! ❤️ - -There are various substantial structural changes and reworkings which will make working with the Trie library more flexible and robust. Note that these changes will need some attention though on an upgrade depending on your existing usage of the Trie library. - -### Single Trie Class - -There is now one single `Trie` class which contains and exposes the functionality previously split into the three separate classes `Trie` -> `CheckpointTrie` and `SecureTrie`, see PRs [#2214](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2214) and [#2215](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2215). Class inheritance has been removed and the existing functionality has been integrated into one class. This should make it easier to extend the Trie class or customize its behavior without having to "dock" into the previous complicated inheritance structure. - -#### Default Checkpointing Behavior - -The `CheckpointTrie` class has been removed in favor of integrating the functionality into the main `Trie` class and make it a default behaviour. Every Trie instance now comes complete with checkpointing behaviour out of the box, without giving any additional weight or performance penalty if the functionality remains unused. - -#### Secure Trie with an Option - -The `SecureTrie` class has been removed as well. Instead there is a new constructor option `useKeyHashing` - defaulting to `false`. This effectively reduces the level of inheritance dependencies (for example, in the old structure, you could not create a secure trie without the checkpoint functionality which, in terms of logic, do not correlate in any way). This also provides more room to accommodate future design modifications and/or additions if required. - -Updating is a straightforward process: - -```ts -// Old -const trie = new SecureTrie() - -// New -const trie = new Trie({ useKeyHashing: true }) -``` - -### Removed Getter and Setter Functions - -Due to the ambiguity of the `get` and `set` functions (also known as getters and setters), usage has been removed from the library. This is because their ambiguity can create the impression of interacting with a property on a trie instance. - -#### Trie `root` Getter/Setter - -For this reason, a single `root(hash?: Buffer): Buffer` function serves as a replacement for the previous `root` getter and setter and can effectively work to get and set properties, see PR [#2219](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2219). This makes it obvious that you intend to modify an internal property of the trie that is neither accessible or mutable via any other means other than this particular function. - -##### Getter Example - -```tsx -// Old -const trie = new Trie() -trie.root - -// New -const trie = new Trie() -trie.root() -``` - -##### Setter Example - -```tsx -// Old -const trie = new Trie() -trie.root = Buffer.alloc(32) - -// New -const trie = new Trie() -trie.root(Buffer.alloc(32)) -``` - -#### Trie `isCheckpoint` Getter - -The `isCheckpoint` getter function has been removed, see PR [#2218](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2218) The `hasCheckpoints()` function serves as its replacement and offers the same behaviour. - -```tsx -// Old -const trie = new Trie() -trie.isCheckpoint - -// New -const trie = new Trie() -trie.hasCheckpoints() -``` - -### Database Abstraction - -Another significant change is that we dropped support for `LevelDB` out of the box. As a result, you will need to have your own implementation available. - -#### Motivation - -The primary reason for this change is increase the flexibility of this package by allowing developers to select any type of storage for their unique purposes. In addition, this change renders the project far less susceptible to [supply chain attacks](https://en.wikipedia.org/wiki/Supply_chain_attack). We trust that users and developers can appreciate the value of reducing this attack surface in exchange for a little more time spent on their part for the duration of this upgrade. - -#### LevelDB Removal - -Prior to v5, this package shipped with a LevelDB integration out of the box. Finalized within this RC release round we have introduced a database abstraction and therefore no longer ship with the aforementioned LevelDB implementation, see previous Beta CHANGELOGs as well as PR [#2167](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2167). However, for your convenience, we provide all of the necessary steps so that you can integrate it accordingly. - -See our [Upgrade Guide](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/trie/UPGRADING.md) for more instructions on how to use the `Trie` library with LevelDB now. - -### Other Changes - -- Store `opts` in private property with defaults, PR [#2224](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2224) -- **Potentially breaking:** Mark `db` as protected and rename to `_db` to avoid leaky properties, PR [#2221](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2221) -- Replace `get/set` for `key/value` for nodes with function, PR [#2220](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2220) -- Rename `persistRoot` to `useRootPersistence`, PR [#2223](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2223) - -### Maintenance Updates - -- Added `engine` field to `package.json` limiting Node versions to v14 or higher, PR [#2164](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2164) -- Replaced `nyc` (code coverage) configurations with `c8` configurations, PR [#2192](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2192) -- Code formats improvements by adding various new linting rules, see Issue [#1935](https://github.com/ethereumjs/ethereumjs-monorepo/issues/1935) -- Use `micro-bmark` for benchmarks, PR [#2128](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2128) -- Move `Trie#_findValueNodes()` function to `TrieReadStream`, PR [#2186](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2186) -- Replaced `semaphore-async-await` with simpler implementation, PR [#2187](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2187) -- Renamed `Semaphore` to `Lock`, PR [#2234](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2234) - -## 5.0.0-beta.3 - 2022-08-10 - -Beta 3 release for the upcoming breaking release round on the [EthereumJS monorepo](https://github.com/ethereumjs/ethereumjs-monorepo) libraries, see the Beta 1 release notes for the main long change set description as well as the Beta 2 release notes for notes on some additional changes ([CHANGELOG](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/devp2p/CHANGELOG.md)). - -### Root Hash Persistance - -The trie library now comes with a new constructor option `useRootPersistence` (note that the option has been called `persistRoot` up to Beta 3) which is disabled by default but allows to persist state root updates along write operations directly in the DB and therefore omits the need to manually set to a new state root, see PR [#2071](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2071) and PR [#2123](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2123), thanks to @faustbrian for the contribution! ❤️ - -To activate root hash persistance you can set the `useRootPersistence` option on instantiation: - -```typescript -import { Trie, LevelDB } from '@ethereumjs/trie' -import { Level } from 'level' - -const trie = new Trie({ - db: new LevelDB(new Level('MY_TRIE_DB_LOCATION')), - useRootPersistence: true, -}) -``` - -### Other Changes - -- Fix: Pass down a custom hash function for hashing on trie copies, PR [#2068](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2068) - -# 5.0.0-beta.2 - 2022-07-15 - -Beta 2 release for the upcoming breaking release round on the [EthereumJS monorepo](https://github.com/ethereumjs/ethereumjs-monorepo) libraries, see the Beta 1 release notes ([CHANGELOG](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/trie/CHANGELOG.md)) for the main change set description. - -### Removed Default Exports - -The change with the biggest effect on UX since the last Beta 1 releases is for sure that we have removed default exports all accross the monorepo, see PR [#2018](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2018), we even now added a new linting rule that completely disallows using. - -Default exports were a common source of error and confusion when using our libraries in a CommonJS context, leading to issues like Issue [#978](https://github.com/ethereumjs/ethereumjs-monorepo/issues/978). - -Now every import is a named import and we think the long term benefits will very much outweigh the one-time hassle of some import adoptions. - -So if you use the Trie library together with other EthereumJS libraries check if the respetive imports need an update. - -## Custom Hash Function - -There is a new constructor option `hash` which allows to customize the hash function used for secure trie key hashing - see PR [#2043](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2043) - thanks to @libotony for the great contribution on this! ❤️ - -This allows to swap out the applied `keccak256` hash functionality from the [@noble/hashes](https://github.com/paulmillr/noble-hashes) library and e.g. use a faster native implementation or an alternative hash function (the PR contribution e.g. was done with the goal to switch to `blake2b256` hashing). - -**Breaking:** Note that this change made it necessary to switch the current proof functionality methods from static to object-bound member functions. - -So the usage of the following methods change and need to be updated (for all types of tries): - -- `Trie.createProof(trie, myKey)` -> `trie.createProof(myKey)` -- `Trie.verifyProof(trie.root(), myKey, proof)` -> `trie.verifyProof(trie.root(), myKey, proof)` -- `Trie.verifyRangeProof(...)` -> `trie.verifyRangeProof(...)` - -## Other Changes - -- Added `ESLint` strict boolean expressions linting rule, PR [#2030](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2030) - -# 5.0.0-beta.1 - 2022-06-30 - -This release is part of a larger breaking release round where all [EthereumJS monorepo](https://github.com/ethereumjs/ethereumjs-monorepo) libraries (VM, Tx, Trie, other) get major version upgrades. This round of releases has been prepared for a long time and we are really pleased with and proud of the result, thanks to all team members and contributors who worked so hard and made this possible! 🙂 ❤️ - -We have gotten rid of a lot of technical debt and inconsistencies and removed unused functionality, renamed methods, improved on the API and on TypeScript typing, to name a few of the more local type of refactoring changes. There are also broader structural changes like a full transition to native JavaScript `BigInt` values as well as various somewhat deep-reaching refactorings, both within a single package as well as some reaching beyond the scope of a single package. Also two completely new packages - `@ethereumjs/evm` (in addition to the existing `@ethereumjs/vm` package) and `@ethereumjs/statemanager` - have been created, leading to a more modular Ethereum JavaScript VM. - -We are very much confident that users of the libraries will greatly benefit from the changes being introduced. However - along the upgrade process - these releases require some extra attention and care since the changeset is both so big and deep reaching. We highly recommend to closely read the release notes, we have done our best to create a full picture on the changes with some special emphasis on delicate code and API parts and give some explicit guidance on how to upgrade and where problems might arise! - -So, enjoy the releases (this is a first round of Beta releases, with final releases following a couple of weeks after if things go well)! 🎉 - -The EthereumJS Team - -### New Package Name - -**Attention!** This library release aligns (and therefore: changes!) the library name with the other EthereumJS libraries and switches to the new scoped package name format, see PR [#1953](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1953). In this case the library is renamed as follows: - -- `merkle-patricia-tree` -> `@ethereumjs/trie` - -Please update your library references accordingly and install with: - -```shell -npm i @ethereumjs/trie -``` - -### BigInt Introduction / ES2020 Build Target - -With this round of breaking releases the whole EthereumJS library stack removes the [BN.js](https://github.com/indutny/bn.js/) library and switches to use native JavaScript [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) values for large-number operations and interactions. - -This makes the libraries more secure and robust (no more BN.js v4 vs v5 incompatibilities) and generally comes with substantial performance gains for the large-number-arithmetic-intense parts of the libraries (particularly the VM). - -While the Trie library currently has no specific BigInt usage we have generally updated our build target to [ES2020](https://262.ecma-international.org/11.0/) to allow for BigInt support now or for future functionality additions. We feel that some still remaining browser compatibility issues on the edges (old Safari versions e.g.) are justified by the substantial gains this step brings along. - -See [#1671](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1671) and [#1771](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1771) for the core `BigInt` transition PRs. - -### Disabled esModuleInterop and allowSyntheticDefaultImports TypeScript Compiler Options - -The above TypeScript options provide some semantic sugar like allowing to write an import like `import React from "react"` instead of `import * as React from "react"`, see [esModuleInterop](https://www.typescriptlang.org/tsconfig#esModuleInterop) and [allowSyntheticDefaultImports](https://www.typescriptlang.org/tsconfig#allowSyntheticDefaultImports) docs for some details. - -While this is convenient it deviates from the ESM specification and forces downstream users into these options which might not be desirable, see [this TypeScript Semver docs section](https://www.semver-ts.org/#module-interop) for some more detailed argumentation. - -Along the breaking releases we have therefore deactivated both of these options and you might therefore need to adopt some import statements accordingly. Note that you still have got the possibility to activate these options in your bundle and/or transpilation pipeline (but now you also have the option to _not_ do which you didn't have before). - -### Database Changes - -#### Generic DB Interface - -In the last round of breaking release preparation @faustbrian came around the corner and came up with some really great DB-related additions to the Trie library, thanks so much for these super valuable contributions! ❤️ ❤️ ❤️ - -Trie usage has now been decoupled from the tight integration with `LevelDB` and it is now possible to replace the datastore with an own implementation respectively a DB wrapper to an alternative key-value-store solution. - -For this there is now a generic `DB` interface defining five methods `get`, `put`, `del`, `batch` and `copy` which a specific `DB` wrapper needs to implement. For `LevelDB` a wrapper with the same name is included and can be directly used. - -The base trie implementation (`Trie`) as well as all subclass implementations (`CheckpointTrie` and `SecureTrie`) have been reworked to now accept any `DB` interface-compatible wrapper implementations as a datastore `db` option input. This allows to easily switch on the underlying backend. - -The new `DB` interface can be used like this for LevelDB: - -```typescript -import { Trie, LevelDB } from '@ethereumjs/trie' -import { Level } from 'level' - -const trie = new Trie({ db: new LevelDB(new Level('MY_TRIE_DB_LOCATION')) }) -``` - -If no `db` option is provided an in-memory [memory-level](https://github.com/Level/memory-level) data storage will be instantiated and used. (Side note: some internal non-persistent trie operations (e.g. proof trie creation for range proofs) will always use the internal `level` based data storage, so there will be some continued `level` DB usage also when you switch to an alternative data store for permanent trie storage). - -#### Level DB Upgrade / Browser Compatibility - -Along with the DB interface extraction the internal Level DB code has been reworked to now be based and work with the latest Level [v8.0.0](https://github.com/Level/level/releases/tag/v8.0.0) major Level DB release. This allows to use ES6-style `import` syntax to import the `Level` instance and allows for better typing when working with Level DB. - -Because of the upgrade, any `level` implementation compliant with the `abstract-level` interface can be use, including `classic-level`, `browser-level` and `memory-level`. This now makes it a lot easier to use the package in browsers without polyfills for `level`. For some context it is worth to mention that the `level` package itself is starting with the v8 release just a proxy for these other packages and has no functionality itself. - -### API Changes - -Options for the Trie constructor are now also taken in as an options dict like in the other EthereumJS libaries. This makes it easier to add additional options in the future, see PR [#1874](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1874). - -Check your Trie instantiations and see if you use constructor options. In this case you need to update to the new format: - -- `constructor(db?: LevelUp | null, root?: Buffer, deleteFromDB: boolean = false)` -> `constructor(opts: TrieOpts = {})` - -The following deprecated or semi-private methods have been removed, see PR [#1874](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1874) and PR [#1834](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1834). - -- `setRoot()` (use `Trie.root` instead) -- Semi-private `_walkTrie()` and `_lookupNode()` methods (should not but might have been used directly) - -### New File Layout - -The trie source files have been reorganized to provide a more consistent and clean file and folder layout, see PR [#1972](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1972). This might or might not affect you, depending if you use direct file references for importing (if you do you might want to generally switch to use root-level exports from the main `index.ts` file). - -All types and Trie options are now bundled in a dedicated `types.ts` file and there are dedicated folders for the different Trie implementations (`trie/`), DB interfaces and classes (`db/`) and proof-related functionality (`proof/`). Additionally some utility functionality has been moved to the `util/` folder. - -# 4.2.4 - 2022-03-15 - -- New `Trie.verifyRangeProof()` function to check whether the given leaf nodes and edge proof can prove the given trie leaves range is matched with the specific root (useful for snapsync, thanks to @samlior for this generous code contribution ❤️), PR [#1731](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1731) - -# 4.2.3 - 2022-02-01 - -- Dependencies: deduplicated RLP import, PR [#1549](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1549) - -# 4.2.2 - 2021-10-06 - -**Bug Fixes** - -- Adds try-catch for "Missing node in DB" in ReadStream, PR [#1515](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1515) - -## 4.2.1 - 2021-08-17 - -**Bug Fixes** - -- Better error checking for invalid proofs with a differentiation on proofs of non-existence (`Trie.verifyProof()` returns `null`) and invalid proofs where `Trie.verifyProof()` will throw, see [README section](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/trie#merkle-proofs) for further details, PR [#1373](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1373) - -**Maintenance** - -- Remove use of deprecated setRoot, PR [#1376](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1376) - -### Included Source Files - -Source files from the `src` folder are now included in the distribution build, see PR [#1301](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1301). This allows for a better debugging experience in debug tools like Chrome DevTools by having working source map references to the original sources available for inspection. - -## 4.2.0 - 2021-05-20 - -### Changed Delete Behavior: NO Default Node Deletes - -This release changes the behavior on trie node deletes after doing a `commit()` on a checkpoint trie (`CheckpointTrie`). This was the scenario where trie nodes were deleted from the database in older versions of the library. After a long discussion we decided to switch to a more conservative approach and keep the trie nodes in the DB in all scenarios. This now allows for setting back the state root to an older root (e.g. with the `StateManager` included in the `VM`) and still be sure to operate on a consistent and complete trie. This had been reported as a problem in some usage scenarios by third-party users of the library. - -So the default behavior on the trie is now: there are no node deletions happening in all type of setups and usage scenarios, so for a `BaseTrie`, `CheckpointTrie` or `SecureTrie` and working checkpointed or non-checkpointed. - -While this change is not directly breaking, it might nevertheless have side effects depending on your usage scenario. If you use a somewhat larger trie and do a lot of change operations, this will significantly increase the disk space used. We have nevertheless decided to make this a non-breaking release since we don't expect this to be the usual way the trie library is used. Instead the former behavior appeared more as some sort of "bug" when reported by developers integrating the library. - -If you want to switch back to a trie where nodes are deleted (so for non-checkpointed tries directly along a trie operation or for checkpointed tries along a `commit()`) there is a new parameter `deleteFromDB` introduced which can be used to switch to a delete behavior on instantiation (the default is `false` here). - -See: PR [#1219](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1219) - -## 4.1.0 - 2021-02-16 - -This release comes with a reworked checkpointing mechanism (PR [#1030](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1030) and subsequently PR [#1035](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1035)). Instead of copying over the whole DB on checkpoints the operations in between checkpoints are now recorded in memory and either applied in batch on a `Trie.checkpoint()` call or discarded along a `Trie.revert()`. This more fine-grained operational mode leads to a substantial performance gain (up to 50x) when working with larger tries. - -Another performance related bug has been fixed along PR [#127](https://github.com/ethereumjs/merkle-patricia-tree/pull/127) removing an unnecessary double-serialization call on nodes. This gives a general performance gain of 10-20% on putting new values in a trie. - -Other changes: - -## New Features - -A new exported `WalkController` class has been added and `trie.walkTrie()` has been made a public method along. This allows for creating own custom ways to traverse a trie. PR [#135](https://github.com/ethereumjs/merkle-patricia-tree/pull/135) - -### Refactoring, Development and Documentation - -- Better `Trie` code documentation, PR [#125](https://github.com/ethereumjs/merkle-patricia-tree/pull/125) -- Internal `Trie` function reordering & partial retwrite, PR [#125](https://github.com/ethereumjs/merkle-patricia-tree/pull/125) -- Added simple integrated profiling, PR [#128](https://github.com/ethereumjs/merkle-patricia-tree/pull/128) -- Reworked benchmarking to be based on `benchmark.js`, basic CI integration, PR [#130](https://github.com/ethereumjs/merkle-patricia-tree/pull/130) -- Upgrade to `ethereumjs-config` `2.0` libs for linting and formatting, PR [#133](https://github.com/ethereumjs/merkle-patricia-tree/pull/133) -- Switched coverage from `coverall` to `codecov`, PR [#137](https://github.com/ethereumjs/merkle-patricia-tree/pull/137) - -## [4.0.0] - 2020-04-17 - -This release introduces a major API upgrade from callbacks to Promises. - -Example using async/await syntax: - -```typescript -import { BaseTrie as Trie } from 'merkle-patricia-tree' -const trie = new Trie() -async function test() { - await trie.put(Buffer.from('test'), Buffer.from('one')) - const value = await trie.get(Buffer.from('test')) - console.log(value.toString()) // 'one' -} -test() -``` - -### Breaking Changes - -#### Trie methods - -See the [docs](https://github.com/ethereumjs/merkle-patricia-tree/tree/master/docs) for the latest Promise-based method signatures. - -#### Trie.prove renamed to Trie.createProof - -To clarify the method's purpose `Trie.prove` has been renamed to `Trie.createProof`. `Trie.prove` has been deprecated but will remain as an alias for `Trie.createProof` until removed. - -#### Trie raw methods - -`getRaw`, `putRaw` and `delRaw` were deprecated in `v3.0.0` and have been removed from this release. Instead, please use `trie.db.get`, `trie.db.put`, and `trie.db.del`. If using a `SecureTrie` or `CheckpointTrie`, use `trie._maindb` to override the checkpointing mechanism and interact directly with the db. - -#### SecureTrie.copy - -`SecureTrie.copy` now includes checkpoint metadata by default. To maintain original behavior of _not_ copying checkpoint state, pass `false` to param `includeCheckpoints`. - -### Changed - -- Convert trieNode to ES6 class ([#71](https://github.com/ethereumjs/merkle-patricia-tree/pull/71)) -- Merge checkpoint and secure interface with their ES6 classes ([#73](https://github.com/ethereumjs/merkle-patricia-tree/pull/73)) -- Extract db-related methods from baseTrie ([#74](https://github.com/ethereumjs/merkle-patricia-tree/pull/74)) -- \_lookupNode callback to use standard error, response pattern ([#83](https://github.com/ethereumjs/merkle-patricia-tree/pull/83)) -- Accept leveldb in constructor, minor fixes ([#92](https://github.com/ethereumjs/merkle-patricia-tree/pull/92)) -- Refactor TrieNode, add levelup types ([#98](https://github.com/ethereumjs/merkle-patricia-tree/pull/98)) -- Promisify rest of library ([#107](https://github.com/ethereumjs/merkle-patricia-tree/pull/107)) -- Use `Nibbles` type for `number[]` ([#115](https://github.com/ethereumjs/merkle-patricia-tree/pull/115)) -- Upgrade ethereumjs-util to 7.0.0 / Upgrade level-mem to 5.0.1 ([#116](https://github.com/ethereumjs/merkle-patricia-tree/pull/116)) -- Create dual ES5 and ES2017 builds ([#117](https://github.com/ethereumjs/merkle-patricia-tree/pull/117)) -- Include checkpoints by default in SecureTrie.copy ([#119](https://github.com/ethereumjs/merkle-patricia-tree/pull/119)) -- Rename Trie.prove to Trie.createProof ([#122](https://github.com/ethereumjs/merkle-patricia-tree/pull/122)) - -### Added - -- Support for proofs of null/absence. Dried up prove/verify. ([#82](https://github.com/ethereumjs/merkle-patricia-tree/pull/82)) -- Add more Ethereum state DB focused example accessing account values ([#89](https://github.com/ethereumjs/merkle-patricia-tree/pull/89)) - -### Fixed - -- Drop ethereumjs-testing dep and fix bug in branch value update ([#69](https://github.com/ethereumjs/merkle-patricia-tree/pull/69)) -- Fix prove and verifyProof in SecureTrie ([#79](https://github.com/ethereumjs/merkle-patricia-tree/pull/79)) -- Fixed src code links in docs ([#93](https://github.com/ethereumjs/merkle-patricia-tree/pull/93)) - -### Dev / Testing / CI - -- Update tape to v4.10.1 ([#81](https://github.com/ethereumjs/merkle-patricia-tree/pull/81)) -- Org links and git hooks ([#87](https://github.com/ethereumjs/merkle-patricia-tree/pull/87)) -- Use module.exports syntax in util files ([#90](https://github.com/ethereumjs/merkle-patricia-tree/pull/90)) -- Rename deprecated sha3 consts and func to keccak256 ([#91](https://github.com/ethereumjs/merkle-patricia-tree/pull/91)) -- Migrate to Typescript ([#96](https://github.com/ethereumjs/merkle-patricia-tree/pull/96)) -- Fix Travis's xvfb service ([#97](https://github.com/ethereumjs/merkle-patricia-tree/pull/97)) -- Fix test cases and docs ([#104](https://github.com/ethereumjs/merkle-patricia-tree/pull/104)) -- Upgrade CI Provider from Travis to GH Actions ([#105](https://github.com/ethereumjs/merkle-patricia-tree/pull/105)) -- Upgrade test suite to TS ([#106](https://github.com/ethereumjs/merkle-patricia-tree/pull/106)) -- Better document `_formatNode` ([#109](https://github.com/ethereumjs/merkle-patricia-tree/pull/109)) -- Move `failingRefactorTests` to `secure.spec.ts` ([#110](https://github.com/ethereumjs/merkle-patricia-tree/pull/110)) -- Fix test suite typos ([#114](https://github.com/ethereumjs/merkle-patricia-tree/pull/110)) - -[4.0.0]: https://github.com/ethereumjs/merkle-patricia-tree/compare/v3.0.0...v4.0.0 - -## [3.0.0] - 2019-01-03 - -This release comes along with some major version bump of the underlying `level` -database storage backend. If you have the library deeper integrated in one of -your projects make sure that the new DB version plays well with the rest of the -code. - -The release also introduces modern `ES6` JavaScript for the library (thanks @alextsg) -switching to `ES6` classes and clean inheritance on all the modules. - -- Replace `levelup` 1.2.1 + `memdown` 1.0.0 with `level-mem` 3.0.1 and upgrade `level-ws` to 1.0.0, PR [#56](https://github.com/ethereumjs/merkle-patricia-tree/pull/56) -- Support for `ES6` classes, PRs [#57](https://github.com/ethereumjs/merkle-patricia-tree/pull/57), [#61](https://github.com/ethereumjs/merkle-patricia-tree/pull/61) -- Updated `async` and `readable-stream` dependencies (resulting in smaller browser builds), PR [#60](https://github.com/ethereumjs/merkle-patricia-tree/pull/60) -- Updated, automated and cleaned up [API documentation](https://github.com/ethereumjs/merkle-patricia-tree/blob/master/docs/index.md) build, PR [#63](https://github.com/ethereumjs/merkle-patricia-tree/pull/63) - -[3.0.0]: https://github.com/ethereumjs/merkle-patricia-tree/compare/v2.3.2...v3.0.0 - -## [2.3.2] - 2018-09-24 - -- Fixed a bug in verify proof if the tree contains an extension node with an embedded branch node, PR [#51](https://github.com/ethereumjs/merkle-patricia-tree/pull/51) -- Fixed `_scratch` 'leak' to global/window, PR [#42](https://github.com/ethereumjs/merkle-patricia-tree/pull/42) -- Fixed coverage report leaving certain tests, PR [#53](https://github.com/ethereumjs/merkle-patricia-tree/pull/53) - -[2.3.2]: https://github.com/ethereumjs/merkle-patricia-tree/compare/v2.3.1...v2.3.2 - -## [2.3.1] - 2018-03-14 - -- Fix OutOfMemory bug when trying to create a read stream on large trie structures - (e.g. a current state DB from a Geth node), PR [#38](https://github.com/ethereumjs/merkle-patricia-tree/pull/38) -- Fix race condition due to mutated `_getDBs`/`_putDBs`, PR [#28](https://github.com/ethereumjs/merkle-patricia-tree/pull/28) - -[2.3.1]: https://github.com/ethereumjs/merkle-patricia-tree/compare/v2.3.0...v2.3.1 - -## [2.3.0] - 2017-11-30 - -- Methods for merkle proof generation `Trie.prove()` and verification `Trie.verifyProof()` (see [./proof.js](./proof.js)) - -[2.3.0]: https://github.com/ethereumjs/merkle-patricia-tree/compare/v2.2.0...v2.3.0 - -## [2.2.0] - 2017-08-03 - -- Renamed `root` functions argument to `nodeRef` for passing a node reference -- Make `findPath()` (path to node for given key) a public method - -[2.2.0]: https://github.com/ethereumjs/merkle-patricia-tree/compare/v2.1.2...v2.2.0 - -## [2.1.2] - 2016-03-01 - -- Added benchmark (see [./benchmarks/](./benchmarks/)) -- Updated dependencies - -[2.1.2]: https://github.com/ethereumjs/merkle-patricia-tree/compare/v2.1.1...v2.1.2 - -## [2.1.1] - 2016-01-06 - -- Added README, API documentation -- Dependency updates - -[2.1.1]: https://github.com/ethereumjs/merkle-patricia-tree/compare/2.0.3...v2.1.1 - -## [2.0.3] - 2015-09-24 - -- Initial, first of the currently released version on npm - -[2.0.3]: https://github.com/ethereumjs/merkle-patricia-tree/compare/1.1.x...2.0.3 +- Initial development release diff --git a/packages/verkle/README.md b/packages/verkle/README.md index 903b198318..e4b15b2663 100644 --- a/packages/verkle/README.md +++ b/packages/verkle/README.md @@ -1,24 +1,24 @@ # @ethereumjs/verkle -[![NPM Package][trie-npm-badge]][trie-npm-link] -[![GitHub Issues][trie-issues-badge]][trie-issues-link] -[![Actions Status][trie-actions-badge]][trie-actions-link] -[![Code Coverage][trie-coverage-badge]][trie-coverage-link] +[![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] -Note: this README has been updated containing the changes from our next breaking release round [UNRELEASED] targeted for Summer 2023. See the README files from the [maintenance-v6](https://github.com/ethereumjs/ethereumjs-monorepo/tree/maintenance-v6/) branch for documentation matching our latest releases. +| Implementation of [Verkle Trees](https://ethereum.org/en/roadmap/verkle-trees/) as specified in [EIP-6800](https://eips.ethereum.org/EIPS/eip-6800) | +| --------------------------------------------------------------------------------------------------------------------------------------------------- | -| Implementation of the [Modified Merkle Patricia Trie](https://ethereum.org/en/developers/docs/data-structures-and-encoding/patricia-merkle-trie/) as specified in the [Ethereum Yellow Paper](http://gavwood.com/Paper.pdf) | -| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +> 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. -> The modified Merkle Patricia tree (trie) provides a persistent data structure to map between arbitrary-length binary data (byte arrays). It is defined in terms of a mutable data structure to map between 256-bit binary fragments and arbitrary-length binary data. The core of the trie, and its sole requirement in terms of the protocol specification, is to provide a single 32-byte value that identifies a given set of key-value pairs. +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/trie +npm install @ethereumjs/verkle ``` ## Usage @@ -32,10 +32,9 @@ It is best to select the variant that is most appropriate for your unique use ca ### Initialization and Basic Usage ```typescript -import { Trie } from '@ethereumjs/trie' -import { bytesToUtf8, MapDB, utf8ToBytes } from '@ethereumjs/util' +import { VerkleTrie } from '@ethereumjs/verkle' -const trie = new Trie({ db: new MapDB() }) +const trie = new VerkleTrie() async function test() { await trie.put(utf8ToBytes('test'), utf8ToBytes('one')) @@ -46,140 +45,11 @@ async function test() { test() ``` -### Use with static constructor - -```typescript -import { Trie } from '@ethereumjs/trie' -import { bytesToUtf8, utf8ToBytes } from '@ethereumjs/util' - -const trie = await Trie.create() - -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() -``` - -When the static `Trie.create` constructor is used without any options, the `trie` object is instantiated with defaults configured to match the Etheruem production spec (i.e. keys are hashed using SHA256). It also persists the state root of the tree on each write operation, ensuring that your trie remains in the state you left it when you start your application the next time. - -### `Trie` Configuration Options - -#### Database Options - -The `DB` opt in the `TrieOpts` allows you to use any database that conforms to the `DB` interface to store the trie data in. We provide several [examples](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/trie/examples) for database implementations. The [level.js](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/trie/examples/level.js) example is used in the `ethereumjs client` while [lmdb.js](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/trie/examples/lmdb.js) is an alternative implementation that uses the popular [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database) as its underlying database. - -If no `db` option is provided, an in-memory database powered by [a Javascript Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) will fulfill this role (imported from `@ethereumjs/util`, see [mapDB](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/util/src/mapDB.ts) module). - -If you want to use an alternative database, you can integrate your own by writing a DB wrapper that conforms to the [`DB` interface](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/util/src/db.ts) (in `@ethereumjs/util`). The `DB` interface defines the methods `get`, `put`, `del`, `batch` and `copy` that a concrete implementation of the `DB` interface will need to implement. - -##### LevelDB - -As an example, to leveage `LevelDB` for all operations then you should create a file with the [following implementation from our recipes](./recipes//level.ts) in your project. Then instantiate your DB and trie as below: - -```typescript -import { Trie } from '@ethereumjs/trie' -import { Level } from 'level' - -import { LevelDB } from './your-level-implementation' - -const trie = new Trie({ db: new LevelDB(new Level('MY_TRIE_DB_LOCATION')) }) -``` - -#### Node Deletion (Pruning) - -By default, the deletion of trie nodes from the underlying database does not occur in order to avoid corrupting older trie states (as of `v4.2.0`). Should you only wish to work with the latest state of a trie, you can switch to a delete behavior (for example, if you wish to save disk space) by using the `useNodePruning` constructor option (see related release notes in the changelog for further details). - -#### Root Persistence - -You can enable persistence by setting the `useRootPersistence` option to `true` when constructing a trie through the `Trie.create` function. As such, this value is preserved when creating copies of the trie and is incapable of being modified once a trie is instantiated. - -```typescript -import { Trie } from '@ethereumjs/trie' - -const trie = await Trie.create({ - useRootPersistence: true, -}) -``` - ## Proofs -### Merkle Proofs - -The `createProof` and `verifyProof` functions allow you to verify that a certain value does or does not exist within a Merkle Patricia Tree with a given root. - -#### Proof-of-Inclusion - -The following code demonstrates how to construct and subsequently verify a proof that confirms the existence of the key `test` (which corresponds with the value `one`) within the given trie. This is also known as inclusion, hence the name 'Proof-of-Inclusion.' - -```typescript -import { Trie } from '@ethereumjs/trie' -import { bytesToUtf8, utf8ToBytes } from '@ethereumjs/util' - -const trie = new Trie() - -async function test() { - await trie.put(utf8ToBytes('test'), utf8ToBytes('one')) - const proof = await trie.createProof(utf8ToBytes('test')) - const value = await trie.verifyProof(trie.root(), utf8ToBytes('test'), proof) - console.log(value ? bytesToUtf8(value) : 'not found') // 'one' -} - -test() -``` - -#### Proof-of-Exclusion - -The following code demonstrates how to construct and subsequently verify a proof that confirms that the key `test3` does not exist within the given trie. This is also known as exclusion, hence the name 'Proof-of-Exclusion.' - -```typescript -import { Trie } from '@ethereumjs/trie' -import { bytesToUtf8, utf8ToBytes } from '@ethereumjs/util' - -const trie = new Trie() - -async function test() { - await trie.put(utf8ToBytes('test'), utf8ToBytes('one')) - await trie.put(utf8ToBytes('test2'), utf8ToBytes('two')) - const proof = await trie.createProof(utf8ToBytes('test3')) - const value = await trie.verifyProof(trie.root(), utf8ToBytes('test3'), proof) - console.log(value ? bytesToUtf8(value) : 'null') // null -} - -test() -``` - -#### Invalid Proofs - -If `verifyProof` detects an invalid proof, it will throw an error. While contrived, the below example illustrates the resulting error condition in the event a prover tampers with the data in a merkle proof. - -```typescript -import { Trie } from '@ethereumjs/trie' -import { bytesToUtf8, utf8ToBytes } from '@ethereumjs/util' - -const trie = new Trie() - -async function test() { - await trie.put(utf8ToBytes('test'), utf8ToBytes('one')) - await trie.put(utf8ToBytes('test2'), utf8ToBytes('two')) - const proof = await trie.createProof(utf8ToBytes('test2')) - proof[1].reverse() - try { - const value = await trie.verifyProof(trie.root(), utf8ToBytes('test2'), proof) - console.log(value ? bytesToUtf8(value) : 'not found') // results in error - } catch (err) { - console.log(err) // Missing node in DB - } -} +### Verkle Proofs -test() -``` - -### Range Proofs - -You may use the `Trie.verifyRangeProof()` function to confirm if the given leaf nodes and edge proof possess the capacity to prove that the given trie leaves' range matches the specific root (which is useful for snap sync, for instance). +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 @@ -215,51 +85,11 @@ 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 can not provide. -### Buffer -> Uint8Array - -With the breaking releases from Summer 2023 we have removed all Node.js specific `Buffer` usages from our libraries and replace these with [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) representations, which are available both in Node.js and the browser (`Buffer` is a subclass of `Uint8Array`). - -We have converted existing Buffer conversion methods to Uint8Array conversion methods in the [@ethereumjs/util](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/util) `bytes` module, see the respective README section for guidance. - -### BigInt Support - -With the 5.0.0 release, [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) takes the place of [BN.js](https://github.com/indutny/bn.js/). - -BigInt is a primitive that is used to represent and manipulate primitive `bigint` values that the number primitive is incapable of representing as a result of their magnitude. `ES2020` saw the introduction of this particular feature. Note that this version update resulted in the altering of number-related API signatures and that the minimal build target is now set to `ES2020`. - -## Benchmarking - -You will find two simple **benchmarks** in the `benchmarks` folder: - -- `random.ts` runs random `PUT` operations on the tree, and -- `checkpointing.ts` runs checkpoints and commits between `PUT` operations - -A third benchmark using mainnet data to simulate real load is also being considered. - -You may run benchmarks using: - -```shell -npm run benchmarks -``` - -To run a **profiler** on the `random.ts` benchmark and generate a flamegraph with [0x](https://github.com/davidmarkclements/0x), you may use: - -```shell -npm run profiling -``` - -0x processes the stacks and generates a profile folder (`.0x`) containing [`flamegraph.html`](https://github.com/davidmarkclements/0x/blob/master/docs/ui.md). - ## References - Wiki - - [Ethereum Trie Specification](https://github.com/ethereum/wiki/wiki/Patricia-Tree) -- Blog posts - - [Ethereum's Merkle Patricia Trees - An Interactive JavaScript Tutorial](https://rockwaterweb.com/ethereum-merkle-patricia-trees-javascript-tutorial/) - - [Merkling in Ethereum](https://blog.ethereum.org/2015/11/15/merkling-in-ethereum/) - - [Understanding the Ethereum Trie](https://easythereentropy.wordpress.com/2014/06/04/understanding-the-ethereum-trie/) (This is worth reading, but mind the outdated Python libraries) -- Videos - - [Trie and Patricia Trie Overview](https://www.youtube.com/watch?v=jXAHLqQthKw&t=26s) + - [Overview of verkle tries](https://ethereum.org/en/roadmap/verkle-trees/) + - [Verkle tries General Resource](https://verkle.info/) ## EthereumJS @@ -271,11 +101,11 @@ See our organizational [documentation](https://ethereumjs.readthedocs.io) for an [discord-badge]: https://img.shields.io/static/v1?logo=discord&label=discord&message=Join&color=blue [discord-link]: https://discord.gg/TNwARpR -[trie-npm-badge]: https://img.shields.io/npm/v/@ethereumjs/trie.svg -[trie-npm-link]: https://www.npmjs.com/package/@ethereumjs/trie -[trie-issues-badge]: https://img.shields.io/github/issues/ethereumjs/ethereumjs-monorepo/package:%20trie?label=issues -[trie-issues-link]: https://github.com/ethereumjs/ethereumjs-monorepo/issues?q=is%3Aopen+is%3Aissue+label%3A"package%3A+trie" -[trie-actions-badge]: https://github.com/ethereumjs/ethereumjs-monorepo/workflows/Trie/badge.svg -[trie-actions-link]: https://github.com/ethereumjs/ethereumjs-monorepo/actions?query=workflow%3A%22Trie%22 -[trie-coverage-badge]: https://codecov.io/gh/ethereumjs/ethereumjs-monorepo/branch/master/graph/badge.svg?flag=trie -[trie-coverage-link]: https://codecov.io/gh/ethereumjs/ethereumjs-monorepo/tree/master/packages/trie +[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 index 298566e0ca..ae2e074494 100644 --- a/packages/verkle/package.json +++ b/packages/verkle/package.json @@ -41,7 +41,7 @@ "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 -- trie", + "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", @@ -52,22 +52,7 @@ "dependencies": { "@ethereumjs/rlp": "5.0.0", "@ethereumjs/util": "9.0.0", - "@types/readable-stream": "^2.3.13", - "lru-cache": "^10.0.0", - "ethereum-cryptography": "^2.1.2", - "readable-stream": "^3.6.0" - }, - "devDependencies": { - "@ethereumjs/genesis": "0.1.0", - "@types/benchmark": "^1.0.33", - "abstract-level": "^1.0.3", - "level": "^8.0.0", - "level-legacy": "npm:level@^7.0.0", - "level-mem": "^6.0.1", - "levelup": "^5.1.1", - "lmdb": "^2.5.3", - "memory-level": "^1.0.0", - "micro-bmark": "0.2.0" + "lru-cache": "^10.0.0" }, "engines": { "node": ">=18" From 8208a86073c7ce02f4ba1ea76af547309d1dc85f Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 15 Oct 2023 12:51:11 -0400 Subject: [PATCH 31/49] verkle: capital V for verkle workflow --- .github/workflows/verkle-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/verkle-build.yml b/.github/workflows/verkle-build.yml index a622cd62b7..5517c34353 100644 --- a/.github/workflows/verkle-build.yml +++ b/.github/workflows/verkle-build.yml @@ -1,4 +1,4 @@ -name: verkle +name: Verkle on: push: branches: [master, develop] From e715a8e9f16860df90294756cc00591901ef068b Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Fri, 20 Oct 2023 05:45:29 -0400 Subject: [PATCH 32/49] Update packages/verkle/README.md Co-authored-by: Scorbajio --- packages/verkle/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/verkle/README.md b/packages/verkle/README.md index e4b15b2663..c60180e4a1 100644 --- a/packages/verkle/README.md +++ b/packages/verkle/README.md @@ -83,7 +83,7 @@ If you use Node.js specific `require`, the CJS build will be used: 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 can not provide. +Using ESM will give you additional advantages over CJS beyond browser usage like static code analysis / Tree Shaking, which CJS cannot provide. ## References From 7ca391faafcb78d41425235affa8383fdcdf2b1b Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Fri, 20 Oct 2023 05:45:38 -0400 Subject: [PATCH 33/49] Update packages/verkle/README.md Co-authored-by: Scorbajio --- packages/verkle/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/verkle/README.md b/packages/verkle/README.md index c60180e4a1..dbd283df77 100644 --- a/packages/verkle/README.md +++ b/packages/verkle/README.md @@ -77,7 +77,7 @@ If you use an ES6-style `import` in your code files from the ESM build will be u import { EthereumJSClass } from '@ethereumjs/[PACKAGE_NAME]' ``` -If you use Node.js specific `require`, the CJS build will be used: +If you use Node.js-specific `require`, the CJS build will be used: ```typescript const { EthereumJSClass } = require('@ethereumjs/[PACKAGE_NAME]') From 85b5665937ef843db39ba11af92524af7ea15abc Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Fri, 20 Oct 2023 05:45:45 -0400 Subject: [PATCH 34/49] Update packages/verkle/README.md Co-authored-by: Scorbajio --- packages/verkle/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/verkle/README.md b/packages/verkle/README.md index dbd283df77..e4ef60be63 100644 --- a/packages/verkle/README.md +++ b/packages/verkle/README.md @@ -71,7 +71,7 @@ Generated TypeDoc API [Documentation](./docs/README.md) 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: +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]' From d85ea87541bad581327a8f8b9761c9b4ae8fbcb4 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Fri, 20 Oct 2023 05:47:11 -0400 Subject: [PATCH 35/49] Update packages/verkle/README.md Co-authored-by: Scorbajio --- packages/verkle/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/verkle/README.md b/packages/verkle/README.md index e4ef60be63..7c9533237e 100644 --- a/packages/verkle/README.md +++ b/packages/verkle/README.md @@ -57,7 +57,7 @@ You can find additional examples complete with detailed explanations [here](./ex ## 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 a frictionless browser usage. +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). From 210faa2ef64b107124280834d933ee1c39aa89e6 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sat, 21 Oct 2023 23:27:03 -0400 Subject: [PATCH 36/49] verkle: use Lock from util package --- packages/verkle/src/util/index.ts | 1 - packages/verkle/src/util/lock.ts | 42 ------------------------------- packages/verkle/src/verkleTrie.ts | 2 +- 3 files changed, 1 insertion(+), 44 deletions(-) delete mode 100644 packages/verkle/src/util/lock.ts diff --git a/packages/verkle/src/util/index.ts b/packages/verkle/src/util/index.ts index 0df1587385..972e26b441 100644 --- a/packages/verkle/src/util/index.ts +++ b/packages/verkle/src/util/index.ts @@ -1,5 +1,4 @@ export * from './bytes.js' export * from './crypto.js' -export * from './lock.js' export * from './tasks.js' export * from './walkController.js' diff --git a/packages/verkle/src/util/lock.ts b/packages/verkle/src/util/lock.ts deleted file mode 100644 index d0f0a9151a..0000000000 --- a/packages/verkle/src/util/lock.ts +++ /dev/null @@ -1,42 +0,0 @@ -// Based on https://github.com/jsoendermann/semaphore-async-await/blob/master/src/Semaphore.ts -export class Lock { - private permits: number = 1 - private promiseResolverQueue: Array<(v: boolean) => void> = [] - - /** - * Returns a promise used to wait for a permit to become available. This method should be awaited on. - * @returns A promise that gets resolved when execution is allowed to proceed. - */ - public async acquire(): Promise { - if (this.permits > 0) { - this.permits -= 1 - return Promise.resolve(true) - } - - // If there is no permit available, we return a promise that resolves once the semaphore gets - // signaled enough times that permits is equal to one. - return new Promise((resolver) => this.promiseResolverQueue.push(resolver)) - } - - /** - * Increases the number of permits by one. If there are other functions waiting, one of them will - * continue to execute in a future iteration of the event loop. - */ - public release(): void { - this.permits += 1 - - if (this.permits > 1 && this.promiseResolverQueue.length > 0) { - // eslint-disable-next-line no-console - console.warn('Lock.permits should never be > 0 when there is someone waiting.') - } else if (this.permits === 1 && this.promiseResolverQueue.length > 0) { - // If there is someone else waiting, immediately consume the permit that was released - // at the beginning of this function and let the waiting function resume. - this.permits -= 1 - - const nextResolver = this.promiseResolverQueue.shift() - if (nextResolver) { - nextResolver(true) - } - } - } -} diff --git a/packages/verkle/src/verkleTrie.ts b/packages/verkle/src/verkleTrie.ts index e5afcf4c0f..e5db5609a8 100644 --- a/packages/verkle/src/verkleTrie.ts +++ b/packages/verkle/src/verkleTrie.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { KeyEncoding, + Lock, MapDB, ValueEncoding, bytesToHex, @@ -20,7 +21,6 @@ import { type VerkleTrieOptsWithDefaults, } from './types.js' import { WalkController, matchingBytesLength } from './util/index.js' -import { Lock } from './util/lock.js' import type { VerkleNode } from './node/types.js' import type { FoundNodeFunction, Point } from './types.js' From 4ba0774825eac9205f0313ffb50485c674d15d16 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 22 Oct 2023 00:15:06 -0400 Subject: [PATCH 37/49] client: undo removal of link --- packages/client/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md index 0ba2a5dbf4..83ed1611c1 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -562,3 +562,5 @@ showing the relations between the main components as well as the initialization ## [0.0.1] - 2018-10-26 - Initial development release + +[0.0.1]: https://github.com/ethereumjs/ethereumjs-client/tree/v0.0.1 From 21e6fc0ae08da5aee8f723a1af3bb23fbee6ef83 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 22 Oct 2023 02:18:34 -0400 Subject: [PATCH 38/49] util: update byte<>int conversion helpers --- packages/util/src/bytes.ts | 81 ++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/packages/util/src/bytes.ts b/packages/util/src/bytes.ts index 34174109fb..b4a5536011 100644 --- a/packages/util/src/bytes.ts +++ b/packages/util/src/bytes.ts @@ -445,64 +445,59 @@ export const concatBytes = (...arrays: Uint8Array[]): Uint8Array => { } /** - * @notice Read a 32-bit little-endian integer from a Uint8Array + * @notice Convert a Uint8Array to a 32-bit integer * @param {Uint8Array} bytes The input Uint8Array from which to read the 32-bit integer. - * @return {number} The 32-bit little-endian integer read from the input Uint8Arrays. + * @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 readInt32LE(bytes: Uint8Array): number { - return (bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24)) >>> 0 +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 Read a 64-bit little-endian bigint from a Uint8Array + * @notice Convert a Uint8Array to a 64-bit bigint * @param {Uint8Array} bytes The input Uint8Array from which to read the 64-bit bigint. - * @return {bigint} The 64-bit little-endian bigint read from the input Uint8Arrays. + * @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 readBigInt64LE(bytes: Uint8Array): bigint { - const lo = BigInt((bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24)) >>> 0) - const hi = BigInt((bytes[4] | (bytes[5] << 8) | (bytes[6] << 16) | (bytes[7] << 24)) >>> 0) - - return (hi << BigInt(32)) | lo +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 Write a 32-bit little-endian number to a Uint8Array. - * @param {number} number The number value to write to the Uint8Array. - * @return {Uint8Array} A Uint8Array of length 32 containing the 32-bit little-endian number. + * @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 writeInt32LE(number: number): Uint8Array { - const bytes = new Uint8Array(32) - - bytes[0] = number & 0xff - bytes[1] = (number >> 8) & 0xff - bytes[2] = (number >> 16) & 0xff - bytes[3] = (number >> 24) & 0xff - - return bytes +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 Write a 64-bit little-endian bigint to a Uint8Array. - * @param {bigint} bigint The bigint value to write to the Uint8Array. - * @return {Uint8Array} A Uint8Array of length 32 containing the 64-bit little-endian bigint. + * @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 writeBigInt64LE(bigint: bigint): Uint8Array { - const bytes = new Uint8Array(32) - - const lo = BigInt.asUintN(32, bigint) - const hi = BigInt.asUintN(32, bigint >> BigInt(32)) - - bytes[0] = Number(lo & BigInt(0xff)) - bytes[1] = Number((lo >> BigInt(8)) & BigInt(0xff)) - bytes[2] = Number((lo >> BigInt(16)) & BigInt(0xff)) - bytes[3] = Number((lo >> BigInt(24)) & BigInt(0xff)) - - bytes[4] = Number(hi & BigInt(0xff)) - bytes[5] = Number((hi >> BigInt(8)) & BigInt(0xff)) - bytes[6] = Number((hi >> BigInt(16)) & BigInt(0xff)) - bytes[7] = Number((hi >> BigInt(24)) & BigInt(0xff)) - - return bytes +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 From 7e10172e5bd0b23e11c4dca4e8041308fd639cb4 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 22 Oct 2023 02:18:58 -0400 Subject: [PATCH 39/49] verkle: use Uint8Array instead of hexstrings for db --- packages/verkle/src/db/checkpoint.ts | 55 ++++++++++++---------------- packages/verkle/src/types.ts | 2 +- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/packages/verkle/src/db/checkpoint.ts b/packages/verkle/src/db/checkpoint.ts index 7027eae1b0..d99c54b54e 100644 --- a/packages/verkle/src/db/checkpoint.ts +++ b/packages/verkle/src/db/checkpoint.ts @@ -1,9 +1,4 @@ -import { - KeyEncoding, - ValueEncoding, - bytesToUnprefixedHex, - unprefixedHexToBytes, -} from '@ethereumjs/util' +import { KeyEncoding, ValueEncoding, bytesToHex, hexToBytes } from '@ethereumjs/util' import { LRUCache } from 'lru-cache' import type { Checkpoint, CheckpointDBOpts } from '../types.js' @@ -15,7 +10,7 @@ import type { BatchDBOp, DB, DelBatch, PutBatch } from '@ethereumjs/util' */ export class CheckpointDB implements DB { public checkpoints: Checkpoint[] - public db: DB + public db: DB public readonly cacheSize: number // Starting with lru-cache v8 undefined and null are not allowed any more @@ -27,7 +22,7 @@ export class CheckpointDB implements DB { // be some not so clean workaround. // // (note that @ts-ignore doesn't work since stripped on declaration (.d.ts) files) - protected _cache?: LRUCache + protected _cache?: LRUCache // protected _cache?: LRUCache _stats = { @@ -103,12 +98,12 @@ export class CheckpointDB implements DB { if (value === undefined) { batchOp.push({ type: 'del', - key: unprefixedHexToBytes(key), + key: hexToBytes(key), }) } else { batchOp.push({ type: 'put', - key: unprefixedHexToBytes(key), + key: hexToBytes(key), value, }) } @@ -135,7 +130,7 @@ export class CheckpointDB implements DB { * @inheritDoc */ async get(key: Uint8Array): Promise { - const keyHex = bytesToUnprefixedHex(key) + const keyHex = bytesToHex(key) if (this._cache !== undefined) { const value = this._cache.get(keyHex) this._stats.cache.reads += 1 @@ -152,16 +147,15 @@ export class CheckpointDB implements DB { } } // Nothing has been found in diff cache, look up from disk - const valueHex = await this.db.get(keyHex, { - keyEncoding: KeyEncoding.String, - valueEncoding: ValueEncoding.String, + const value = await this.db.get(key, { + keyEncoding: KeyEncoding.Bytes, + valueEncoding: ValueEncoding.Bytes, }) this._stats.db.reads += 1 - if (valueHex !== undefined) { + if (value !== undefined) { this._stats.db.hits += 1 } - const value = valueHex !== undefined ? unprefixedHexToBytes(valueHex) : undefined - this._cache?.set(keyHex, value) + this._cache?.set(key, 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. @@ -175,20 +169,18 @@ export class CheckpointDB implements DB { * @inheritDoc */ async put(key: Uint8Array, value: Uint8Array): Promise { - const keyHex = bytesToUnprefixedHex(key) - const valueHex = bytesToUnprefixedHex(value) if (this.hasCheckpoints()) { // put value in diff cache - this.checkpoints[this.checkpoints.length - 1].keyValueMap.set(keyHex, value) + this.checkpoints[this.checkpoints.length - 1].keyValueMap.set(bytesToHex(key), value) } else { - await this.db.put(keyHex, valueHex, { - keyEncoding: KeyEncoding.String, - valueEncoding: ValueEncoding.String, + 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._cache.set(key, value) this._stats.cache.writes += 1 } } @@ -198,19 +190,18 @@ export class CheckpointDB implements DB { * @inheritDoc */ async del(key: Uint8Array): Promise { - const keyHex = bytesToUnprefixedHex(key) if (this.hasCheckpoints()) { // delete the value in the current diff cache - this.checkpoints[this.checkpoints.length - 1].keyValueMap.set(keyHex, undefined) + this.checkpoints[this.checkpoints.length - 1].keyValueMap.set(bytesToHex(key), undefined) } else { // delete the value on disk - await this.db.del(keyHex, { + await this.db.del(key, { keyEncoding: KeyEncoding.String, }) this._stats.db.writes += 1 if (this._cache !== undefined) { - this._cache.set(keyHex, undefined) + this._cache.set(key, undefined) this._stats.cache.writes += 1 } } @@ -231,13 +222,13 @@ export class CheckpointDB implements DB { } else { const convertedOps = opStack.map((op) => { const convertedOp = { - key: bytesToUnprefixedHex(op.key), - value: op.type === 'put' ? bytesToUnprefixedHex(op.value) : undefined, + 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 + if (op.type === 'put') return convertedOp as PutBatch + else return convertedOp as DelBatch }) await this.db.batch(convertedOps) } diff --git a/packages/verkle/src/types.ts b/packages/verkle/src/types.ts index 9e17f95bce..85e53e4f96 100644 --- a/packages/verkle/src/types.ts +++ b/packages/verkle/src/types.ts @@ -93,7 +93,7 @@ export interface CheckpointDBOpts { /** * A database instance. */ - db: DB + db: DB /** * Cache size (default: 0) From 008aed625de8c59ef31857bbed47b69f9bde3666 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 22 Oct 2023 02:20:41 -0400 Subject: [PATCH 40/49] verkle: add imports to example --- packages/verkle/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/verkle/README.md b/packages/verkle/README.md index 7c9533237e..d5440ff799 100644 --- a/packages/verkle/README.md +++ b/packages/verkle/README.md @@ -33,6 +33,7 @@ It is best to select the variant that is most appropriate for your unique use ca ```typescript import { VerkleTrie } from '@ethereumjs/verkle' +import { bytesToUtf8, utf8ToBytes } from 'ethereumjs/util' const trie = new VerkleTrie() From 6a0c702dc5169d83965b2b04c01bfaab188c7b62 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 22 Oct 2023 02:27:32 -0400 Subject: [PATCH 41/49] verkle: update db to Uint8Arrays --- packages/verkle/src/types.ts | 2 +- packages/verkle/src/verkleTrie.ts | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/verkle/src/types.ts b/packages/verkle/src/types.ts index 85e53e4f96..3b946019b6 100644 --- a/packages/verkle/src/types.ts +++ b/packages/verkle/src/types.ts @@ -64,7 +64,7 @@ export interface VerkleTrieOpts { /** * A database instance. */ - db?: DB + db?: DB /** * A `Uint8Array` for the root of a previously stored trie diff --git a/packages/verkle/src/verkleTrie.ts b/packages/verkle/src/verkleTrie.ts index e5db5609a8..583870560d 100644 --- a/packages/verkle/src/verkleTrie.ts +++ b/packages/verkle/src/verkleTrie.ts @@ -61,7 +61,7 @@ export class VerkleTrie { this._opts = { ...this._opts, ...opts } } - this.database(opts?.db ?? new MapDB()) + this.database(opts?.db) this.EMPTY_TRIE_ROOT = zeros(32) this._hashLen = this.EMPTY_TRIE_ROOT.length @@ -77,13 +77,12 @@ export class VerkleTrie { if (opts?.db !== undefined && opts?.useRootPersistence === true) { if (opts?.root === undefined) { - const rootHex = await opts?.db.get(bytesToHex(key), { + opts.root = await opts?.db.get(key, { keyEncoding: KeyEncoding.String, valueEncoding: ValueEncoding.String, }) - opts.root = rootHex !== undefined ? hexToBytes(rootHex) : undefined } else { - await opts?.db.put(bytesToHex(key), bytesToHex(opts.root), { + await opts?.db.put(key, opts.root, { keyEncoding: KeyEncoding.String, valueEncoding: ValueEncoding.String, }) @@ -93,7 +92,7 @@ export class VerkleTrie { return new VerkleTrie(opts) } - database(db?: DB) { + database(db?: DB) { if (db !== undefined) { if (db instanceof CheckpointDB) { throw new Error('Cannot pass in an instance of CheckpointDB') From 68e25409fc6ec1b17896cbc3604f57da247d6bdf Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 22 Oct 2023 13:32:59 -0400 Subject: [PATCH 42/49] verkle: revert to using strings as keys for cache --- packages/verkle/src/db/checkpoint.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/verkle/src/db/checkpoint.ts b/packages/verkle/src/db/checkpoint.ts index d99c54b54e..84195fba97 100644 --- a/packages/verkle/src/db/checkpoint.ts +++ b/packages/verkle/src/db/checkpoint.ts @@ -22,8 +22,7 @@ export class CheckpointDB implements DB { // be some not so clean workaround. // // (note that @ts-ignore doesn't work since stripped on declaration (.d.ts) files) - protected _cache?: LRUCache - // protected _cache?: LRUCache + protected _cache?: LRUCache _stats = { cache: { @@ -48,7 +47,6 @@ export class CheckpointDB implements DB { this.checkpoints = [] if (this.cacheSize > 0) { - // @ts-ignore this._cache = new LRUCache({ max: this.cacheSize, updateAgeOnGet: true, @@ -155,7 +153,7 @@ export class CheckpointDB implements DB { if (value !== undefined) { this._stats.db.hits += 1 } - this._cache?.set(key, value) + 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. @@ -169,9 +167,10 @@ export class CheckpointDB implements DB { * @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(bytesToHex(key), value) + this.checkpoints[this.checkpoints.length - 1].keyValueMap.set(keyHex, value) } else { await this.db.put(key, value, { keyEncoding: KeyEncoding.Bytes, @@ -180,7 +179,7 @@ export class CheckpointDB implements DB { this._stats.db.writes += 1 if (this._cache !== undefined) { - this._cache.set(key, value) + this._cache.set(keyHex, value) this._stats.cache.writes += 1 } } @@ -190,9 +189,10 @@ export class CheckpointDB implements DB { * @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(bytesToHex(key), undefined) + this.checkpoints[this.checkpoints.length - 1].keyValueMap.set(keyHex, undefined) } else { // delete the value on disk await this.db.del(key, { @@ -201,7 +201,7 @@ export class CheckpointDB implements DB { this._stats.db.writes += 1 if (this._cache !== undefined) { - this._cache.set(key, undefined) + this._cache.set(keyHex, undefined) this._stats.cache.writes += 1 } } From 150e99906a89c65668c498e633ee384e9f93060b Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 22 Oct 2023 13:33:10 -0400 Subject: [PATCH 43/49] verkle: update crypto import --- packages/verkle/src/util/crypto.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/verkle/src/util/crypto.ts b/packages/verkle/src/util/crypto.ts index 266560f6eb..8143765b7b 100644 --- a/packages/verkle/src/util/crypto.ts +++ b/packages/verkle/src/util/crypto.ts @@ -1,4 +1,4 @@ -import { type Address, concatBytes, setLengthLeft, toBytes, writeInt32LE } from '@ethereumjs/util' +import { type Address, concatBytes, int32ToBytes, setLengthLeft, toBytes } from '@ethereumjs/util' import * as rustVerkleWasm from '../rust-verkle-wasm/rust_verkle_wasm.js' @@ -27,7 +27,7 @@ export function pedersenHash(input: Uint8Array): Uint8Array { export function getTreeKey(address: Address, treeIndex: number, subIndex: number): Uint8Array { const address32 = setLengthLeft(address.toBytes(), 32) - const treeIndexB = writeInt32LE(treeIndex) + const treeIndexB = int32ToBytes(treeIndex, true) const input = concatBytes(address32, treeIndexB) From 2f013e3a11bbb59daf1c5e200b0405cfdb4e6b8e Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 22 Oct 2023 13:42:00 -0400 Subject: [PATCH 44/49] verkle: adjust Key and Value encoding in create method --- packages/verkle/src/verkleTrie.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/verkle/src/verkleTrie.ts b/packages/verkle/src/verkleTrie.ts index 583870560d..8d9ccb0242 100644 --- a/packages/verkle/src/verkleTrie.ts +++ b/packages/verkle/src/verkleTrie.ts @@ -78,13 +78,13 @@ export class VerkleTrie { if (opts?.db !== undefined && opts?.useRootPersistence === true) { if (opts?.root === undefined) { opts.root = await opts?.db.get(key, { - keyEncoding: KeyEncoding.String, - valueEncoding: ValueEncoding.String, + keyEncoding: KeyEncoding.Bytes, + valueEncoding: ValueEncoding.Bytes, }) } else { await opts?.db.put(key, opts.root, { - keyEncoding: KeyEncoding.String, - valueEncoding: ValueEncoding.String, + keyEncoding: KeyEncoding.Bytes, + valueEncoding: ValueEncoding.Bytes, }) } } From 23bb5dd915a8e08a4c8759cc5297142e683d2e1e Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 22 Oct 2023 13:42:15 -0400 Subject: [PATCH 45/49] verkle: adjust value encoding in db del method --- packages/verkle/src/db/checkpoint.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/verkle/src/db/checkpoint.ts b/packages/verkle/src/db/checkpoint.ts index 84195fba97..701828d90d 100644 --- a/packages/verkle/src/db/checkpoint.ts +++ b/packages/verkle/src/db/checkpoint.ts @@ -196,7 +196,7 @@ export class CheckpointDB implements DB { } else { // delete the value on disk await this.db.del(key, { - keyEncoding: KeyEncoding.String, + keyEncoding: KeyEncoding.Bytes, }) this._stats.db.writes += 1 From 4a98befa450c379f2140a69a8f2a88dba46ed00a Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 22 Oct 2023 13:46:54 -0400 Subject: [PATCH 46/49] verkle: add missing key and value encoding opts to batch options --- packages/verkle/src/db/checkpoint.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/verkle/src/db/checkpoint.ts b/packages/verkle/src/db/checkpoint.ts index 701828d90d..6755796cc9 100644 --- a/packages/verkle/src/db/checkpoint.ts +++ b/packages/verkle/src/db/checkpoint.ts @@ -97,12 +97,16 @@ export class CheckpointDB implements DB { 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 }, }) } } From 3ccfc3f8fc54b7d509c1ecfad817b07ea1baa765 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 22 Oct 2023 15:03:44 -0400 Subject: [PATCH 47/49] verkle: remove extra line --- packages/verkle/src/verkleTrie.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/verkle/src/verkleTrie.ts b/packages/verkle/src/verkleTrie.ts index 8d9ccb0242..51e90ac917 100644 --- a/packages/verkle/src/verkleTrie.ts +++ b/packages/verkle/src/verkleTrie.ts @@ -146,7 +146,6 @@ export class VerkleTrie { * @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] From b6b8ea1519c1e5b65e1417886182caf76cb936c6 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 22 Oct 2023 15:43:27 -0400 Subject: [PATCH 48/49] verkle: add verkle-trie package --- packages/verkle/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/verkle/package.json b/packages/verkle/package.json index ae2e074494..28793eefc7 100644 --- a/packages/verkle/package.json +++ b/packages/verkle/package.json @@ -52,7 +52,8 @@ "dependencies": { "@ethereumjs/rlp": "5.0.0", "@ethereumjs/util": "9.0.0", - "lru-cache": "^10.0.0" + "lru-cache": "^10.0.0", + "verkle-trie": "^0.0.4" }, "engines": { "node": ">=18" From e05168bcc3253cfde986d3e2fe81b977e8482074 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Sun, 22 Oct 2023 15:45:12 -0400 Subject: [PATCH 49/49] verkle: package lock --- package-lock.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index b689a66c70..083f11cfb8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12888,6 +12888,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rust-verkle-wasm": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/rust-verkle-wasm/-/rust-verkle-wasm-0.0.1.tgz", + "integrity": "sha512-BN6fiTsxcd2dCECz/cHtGTt9cdLJR925nh7iAuRcj8ymKw7OOaPmCneQZ7JePOJ/ia27TjEL91VdOi88Yf+mcA==" + }, "node_modules/rustbn-wasm": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/rustbn-wasm/-/rustbn-wasm-0.2.0.tgz", @@ -14627,6 +14632,14 @@ "node": ">= 0.8" } }, + "node_modules/verkle-trie": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/verkle-trie/-/verkle-trie-0.0.4.tgz", + "integrity": "sha512-RIHLSh+G5QviBC+T9xfvt3EWjgUkGNoNryRMcJvTpboybT3mYVb5F/vKdrTp7OAII3VR/6o1kIHRFewFZ/tGwA==", + "dependencies": { + "rust-verkle-wasm": "0.0.1" + } + }, "node_modules/vite": { "version": "4.4.7", "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.7.tgz", @@ -16167,7 +16180,8 @@ "dependencies": { "@ethereumjs/rlp": "5.0.0", "@ethereumjs/util": "9.0.0", - "lru-cache": "^10.0.0" + "lru-cache": "^10.0.0", + "verkle-trie": "^0.0.4" }, "engines": { "node": ">=18"