From 6e4c0ad2e93cfb3081f4b1f8e3fbb2956a63a555 Mon Sep 17 00:00:00 2001 From: Ayaan Date: Thu, 7 Mar 2024 12:58:55 +0900 Subject: [PATCH] Initial commit: separating Kroma spec documentation from the Kroma repo - Modify to mdbook usage. - Modify internal relative cc links to external links. - Revised to a consistent structure that includes overview in the table of contents. --- .gitignore | 5 + .markdownlint.json | 13 + Justfile | 41 + LICENSE | 116 ++ README.md | 67 + book.toml | 20 + package.json | 13 + pnpm-lock.yaml | 718 +++++++++ specs/SUMMARY.md | 26 + specs/fault-proof/challenge.md | 444 ++++++ specs/fault-proof/zkevm-prover.md | 72 + specs/glossary.md | 968 +++++++++++++ specs/introduction.md | 194 +++ specs/meta/README.md | 8 + specs/meta/devnet.md | 68 + specs/meta/linting.md | 68 + specs/meta/markdown-style.md | 53 + specs/meta/validator-deposit.md | 86 ++ specs/protocol/batcher.md | 39 + specs/protocol/bridges.md | 122 ++ specs/protocol/contract-upgrades.md | 81 ++ specs/protocol/deposits.md | 361 +++++ specs/protocol/derivation.md | 962 +++++++++++++ specs/protocol/differences-from-optimism.md | 145 ++ specs/protocol/exec-engine.md | 283 ++++ specs/protocol/guaranteed-gas-market.md | 158 ++ specs/protocol/messengers.md | 115 ++ specs/protocol/overview.md | 223 +++ specs/protocol/predeploys.md | 220 +++ specs/protocol/rollup-node-p2p.md | 423 ++++++ specs/protocol/rollup-node.md | 126 ++ specs/protocol/safe-liveness-checking.md | 194 +++ specs/protocol/security-council.md | 63 + specs/protocol/span-batches.md | 385 +++++ specs/protocol/system-config.md | 110 ++ specs/protocol/validator.md | 356 +++++ specs/protocol/withdrawals.md | 225 +++ specs/root.md | 63 + specs/static/assets/architecture.svg | 4 + specs/static/assets/batch-deriv-chain.svg | 4 + .../static/assets/colosseum-state-diagram.svg | 4 + specs/static/assets/components.svg | 4 + specs/static/assets/engine.svg | 4 + .../assets/network-participants-overview.svg | 4 + specs/static/assets/propagation.svg | 4 + specs/static/assets/sequencer-block-gen.svg | 4 + ...cer-handling-deposits-and-transactions.svg | 4 + .../static/assets/user-withdrawing-to-l1.svg | 4 + .../assets/verifier-proving-fault-proof.svg | 4 + specs/static/assets/vscode_debug.png | Bin 0 -> 49881 bytes .../ecotone-gas-price-oracle-deployment.txt | 1 + .../bytecode/ecotone-l1-block-deployment.txt | 1 + specs/static/custom.css | 27 + specs/static/mermaid-init.js | 1 + specs/static/mermaid.min.js | 1282 +++++++++++++++++ specs/static/solidity.min.js | 337 +++++ 56 files changed, 9327 insertions(+) create mode 100644 .gitignore create mode 100644 .markdownlint.json create mode 100644 Justfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 book.toml create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 specs/SUMMARY.md create mode 100644 specs/fault-proof/challenge.md create mode 100644 specs/fault-proof/zkevm-prover.md create mode 100644 specs/glossary.md create mode 100644 specs/introduction.md create mode 100644 specs/meta/README.md create mode 100644 specs/meta/devnet.md create mode 100644 specs/meta/linting.md create mode 100644 specs/meta/markdown-style.md create mode 100644 specs/meta/validator-deposit.md create mode 100644 specs/protocol/batcher.md create mode 100644 specs/protocol/bridges.md create mode 100644 specs/protocol/contract-upgrades.md create mode 100644 specs/protocol/deposits.md create mode 100644 specs/protocol/derivation.md create mode 100644 specs/protocol/differences-from-optimism.md create mode 100644 specs/protocol/exec-engine.md create mode 100644 specs/protocol/guaranteed-gas-market.md create mode 100644 specs/protocol/messengers.md create mode 100644 specs/protocol/overview.md create mode 100644 specs/protocol/predeploys.md create mode 100644 specs/protocol/rollup-node-p2p.md create mode 100644 specs/protocol/rollup-node.md create mode 100644 specs/protocol/safe-liveness-checking.md create mode 100644 specs/protocol/security-council.md create mode 100644 specs/protocol/span-batches.md create mode 100644 specs/protocol/system-config.md create mode 100644 specs/protocol/validator.md create mode 100644 specs/protocol/withdrawals.md create mode 100644 specs/root.md create mode 100644 specs/static/assets/architecture.svg create mode 100644 specs/static/assets/batch-deriv-chain.svg create mode 100644 specs/static/assets/colosseum-state-diagram.svg create mode 100644 specs/static/assets/components.svg create mode 100644 specs/static/assets/engine.svg create mode 100644 specs/static/assets/network-participants-overview.svg create mode 100644 specs/static/assets/propagation.svg create mode 100644 specs/static/assets/sequencer-block-gen.svg create mode 100644 specs/static/assets/sequencer-handling-deposits-and-transactions.svg create mode 100644 specs/static/assets/user-withdrawing-to-l1.svg create mode 100644 specs/static/assets/verifier-proving-fault-proof.svg create mode 100644 specs/static/assets/vscode_debug.png create mode 100644 specs/static/bytecode/ecotone-gas-price-oracle-deployment.txt create mode 100644 specs/static/bytecode/ecotone-l1-block-deployment.txt create mode 100644 specs/static/custom.css create mode 100644 specs/static/mermaid-init.js create mode 100644 specs/static/mermaid.min.js create mode 100644 specs/static/solidity.min.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a15f95 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Rendered book +book/ + +node_modules/** +/.idea diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..2e23f6c --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,13 @@ +{ + "line_length": { + "line_length": 120, + "strict": false, + "stern": true, + "code_blocks": false, + "tables": false + }, + "no-blanks-blockquote": false, + "no-empty-links": false, + "single-title": false, + "no-emphasis-as-heading": false +} diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..927baae --- /dev/null +++ b/Justfile @@ -0,0 +1,41 @@ +set positional-arguments + +# default recipe to display help information +default: + @just --list + +# Install required dependencies +deps: + pnpm i --frozen-lockfile + +# Lint the workspace for all available targets +lint: lint-specs-md-check lint-specs-toc-check lint-links + +# Updates all files to fix linting issues +lint-fix: lint-specs-md-fix lint-specs-toc + +# Validates markdown file formatting +lint-specs-md-check: + npx markdownlint-cli2 "./specs/**/*.md" + +# Updates markdown files formatting to satisfy lints +lint-specs-md-fix: + npx markdownlint-cli2 --fix "./specs/**/*.md" + +# Validates Table of Content Sections with doctoc +lint-specs-toc-check: + npx doctoc '--title=**Table of Contents**' ./specs && git diff --exit-code ./specs + +# Updates Table of Content Sections with doctoc +lint-specs-toc: + npx doctoc '--title=**Table of Contents**' ./specs + +# Validates all hyperlinks respond with status 200 +lint-links: + docker run --init -it -v `pwd`:/input lycheeverse/lychee --verbose --no-progress --exclude-loopback \ + --exclude twitter.com --exclude explorer.optimism.io --exclude linux-mips.org --exclude vitalik.ca \ + --exclude-mail /input/README.md "/input/specs/**/*.md" + +# Serves the mdbook locally +serve *args='': + mdbook serve $@ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..670154e --- /dev/null +++ b/LICENSE @@ -0,0 +1,116 @@ +CC0 1.0 Universal + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later +claims of infringement build upon, modify, incorporate in other works, reuse +and redistribute as freely as possible in any form whatsoever and for any +purposes, including without limitation commercial purposes. These owners may +contribute to the Commons to promote the ideal of a free culture and the +further production of creative, cultural and scientific works, or to gain +reputation or greater distribution for their Work in part through the use and +efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with a +Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not limited +to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + + iii. publicity and privacy rights pertaining to a person's image or likeness + depicted in a Work; + + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + + v. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + + vii. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time +extensions), (iii) in any current or future medium and for any number of +copies, and (iv) for any purpose whatsoever, including without limitation +commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes +the Waiver for the benefit of each member of the public at large and to the +detriment of Affirmer's heirs and successors, fully intending that such Waiver +shall not be subject to revocation, rescission, cancellation, termination, or +any other legal or equitable action to disrupt the quiet enjoyment of the Work +by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account +Affirmer's express Statement of Purpose. In addition, to the extent the Waiver +is so judged Affirmer hereby grants to each affected person a royalty-free, +non transferable, non sublicensable, non exclusive, irrevocable and +unconditional license to exercise Affirmer's Copyright and Related Rights in +the Work (i) in all territories worldwide, (ii) for the maximum duration +provided by applicable law or treaty (including future time extensions), (iii) +in any current or future medium and for any number of copies, and (iv) for any +purpose whatsoever, including without limitation commercial, advertising or +promotional purposes (the "License"). The License shall be deemed effective as +of the date CC0 was applied by Affirmer to the Work. Should any part of the +License for any reason be judged legally invalid or ineffective under +applicable law, such partial invalidity or ineffectiveness shall not +invalidate the remainder of the License, and in such case Affirmer hereby +affirms that he or she will not (i) exercise any of his or her remaining +Copyright and Related Rights in the Work or (ii) assert any associated claims +and causes of action with respect to the Work, in either case contrary to +Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + + b. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or otherwise, + including without limitation warranties of title, merchantability, fitness + for a particular purpose, non infringement, or the absence of latent or + other defects, accuracy, or the present or absence of errors, whether or not + discoverable, all to the greatest extent permissible under applicable law. + + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without limitation + any person's Copyright and Related Rights in the Work. Further, Affirmer + disclaims responsibility for obtaining any necessary consents, permissions + or other rights required for any use of the Work. + + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see + diff --git a/README.md b/README.md new file mode 100644 index 0000000..5ba19e9 --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +
+
+
+ Kroma network +
+

Kroma network aims to be a New Universal ZK Rollup on Ethereum.

+
+
+ +## Kroma network Specification + +This repository contains the [Specs Book](https://kroma-specs.kroma.network). + +## Contributing + +### Dependencies + +**Rust Toolchain** + +```sh +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +**`mdbook` + plugins** + +```sh +cargo install mdbook mdbook-katex mdbook-linkcheck mdbook-mermaid +``` + +### Serving the book locally + +```sh +just serve +``` + +### Linting + +`doctoc` is used to automatically add a table of contents. + +```sh +just lint-specs-toc-check +``` + +To fix markdown linting errors: + +```sh +just lint-specs-md-fix +``` + +See the [markdownlint rule reference](https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md) +and an example [config file](https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.jsonc). + +Justification for linting rules in +[.markdownlint.json](https://github.com/ethereum-optimism/specs/blob/main/.markdownlint.json): + +- _line_length_ (`!strict && stern`): don't trip up on url lines +- _no-blanks-blockquote_: enable multiple consecutive blockquotes separated by white lines +- _single-title_: enable reusing `

` for content +- _no-emphasis-as-heading_: enable emphasized paragraphs + +To lint links: + +```sh +just lint-links +``` + +[lychee][lychee] is used for linting links. diff --git a/book.toml b/book.toml new file mode 100644 index 0000000..87812ec --- /dev/null +++ b/book.toml @@ -0,0 +1,20 @@ +[book] +authors = ["kroma-network"] +language = "en" +multilingual = false +src = "specs" +title = "Kroma-network Specification" + +[preprocessor.katex] +after = ["links"] + +[preprocessor.mermaid] +command = "mdbook-mermaid" + +[output.html] +git-repository-url = "https://github.com/kroma-network/specs" +edit-url-template = "https://github.com/kroma-network/specs/edit/main/{path}" +default-theme = "ayu" +additional-js = ["specs/static/solidity.min.js", "specs/static/mermaid.min.js", "specs/static/mermaid-init.js"] + +[output.linkcheck] diff --git a/package.json b/package.json new file mode 100644 index 0000000..4c60bed --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "specs", + "version": "1.0.0", + "private": true, + "engines": { + "node": ">=16", + "pnpm": ">=8" + }, + "dependencies": { + "doctoc": "^2.2.1", + "markdownlint-cli2": "0.4.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..fd5817e --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,718 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + doctoc: + specifier: ^2.2.1 + version: 2.2.1 + markdownlint-cli2: + specifier: 0.4.0 + version: 0.4.0 + +packages: + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: false + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: false + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.16.0 + dev: false + + /@textlint/ast-node-types@12.6.1: + resolution: {integrity: sha512-uzlJ+ZsCAyJm+lBi7j0UeBbj+Oy6w/VWoGJ3iHRHE5eZ8Z4iK66mq+PG/spupmbllLtz77OJbY89BYqgFyjXmA==} + dev: false + + /@textlint/markdown-to-ast@12.6.1: + resolution: {integrity: sha512-T0HO+VrU9VbLRiEx/kH4+gwGMHNMIGkp0Pok+p0I33saOOLyhfGvwOKQgvt2qkxzQEV2L5MtGB8EnW4r5d3CqQ==} + dependencies: + '@textlint/ast-node-types': 12.6.1 + debug: 4.3.4 + mdast-util-gfm-autolink-literal: 0.1.3 + remark-footnotes: 3.0.0 + remark-frontmatter: 3.0.0 + remark-gfm: 1.0.0 + remark-parse: 9.0.0 + traverse: 0.6.8 + unified: 9.2.2 + transitivePeerDependencies: + - supports-color + dev: false + + /@types/mdast@3.0.15: + resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} + dependencies: + '@types/unist': 2.0.10 + dev: false + + /@types/unist@2.0.10: + resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} + dev: false + + /anchor-markdown-header@0.6.0: + resolution: {integrity: sha512-v7HJMtE1X7wTpNFseRhxsY/pivP4uAJbidVhPT+yhz4i/vV1+qx371IXuV9V7bN6KjFtheLJxqaSm0Y/8neJTA==} + dependencies: + emoji-regex: 10.1.0 + dev: false + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: false + + /array-union@3.0.1: + resolution: {integrity: sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==} + engines: {node: '>=12'} + dev: false + + /bail@1.0.5: + resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==} + dev: false + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: false + + /ccount@1.1.0: + resolution: {integrity: sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==} + dev: false + + /character-entities-legacy@1.1.4: + resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} + dev: false + + /character-entities@1.2.4: + resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} + dev: false + + /character-reference-invalid@1.1.4: + resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} + dev: false + + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: false + + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: false + + /doctoc@2.2.1: + resolution: {integrity: sha512-qNJ1gsuo7hH40vlXTVVrADm6pdg30bns/Mo7Nv1SxuXSM1bwF9b4xQ40a6EFT/L1cI+Yylbyi8MPI4G4y7XJzQ==} + hasBin: true + dependencies: + '@textlint/markdown-to-ast': 12.6.1 + anchor-markdown-header: 0.6.0 + htmlparser2: 7.2.0 + minimist: 1.2.8 + underscore: 1.13.6 + update-section: 0.3.3 + transitivePeerDependencies: + - supports-color + dev: false + + /dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.1.0 + dev: false + + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: false + + /domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: false + + /domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + dev: false + + /emoji-regex@10.1.0: + resolution: {integrity: sha512-xAEnNCT3w2Tg6MA7ly6QqYJvEoY1tm9iIjJ3yMKK9JPlWuRHAMoe5iETwQnx3M9TVbFMfsrBgWKR+IsmswwNjg==} + dev: false + + /entities@2.1.0: + resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==} + dev: false + + /entities@3.0.1: + resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} + engines: {node: '>=0.12'} + dev: false + + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: false + + /extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + dev: false + + /fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.4 + dev: false + + /fastq@1.16.0: + resolution: {integrity: sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==} + dependencies: + reusify: 1.0.4 + dev: false + + /fault@1.0.4: + resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} + dependencies: + format: 0.2.2 + dev: false + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: false + + /format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + dev: false + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: false + + /globby@12.1.0: + resolution: {integrity: sha512-YULDaNwsoUZkRy9TWSY/M7Obh0abamTKoKzTfOI3uU+hfpX2FZqOq8LFDxsjYheF1RH7ITdArgbQnsNBFgcdBA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + array-union: 3.0.1 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.0 + merge2: 1.4.1 + slash: 4.0.0 + dev: false + + /htmlparser2@7.2.0: + resolution: {integrity: sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==} + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + domutils: 2.8.0 + entities: 3.0.1 + dev: false + + /ignore@5.3.0: + resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} + engines: {node: '>= 4'} + dev: false + + /is-alphabetical@1.0.4: + resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} + dev: false + + /is-alphanumerical@1.0.4: + resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + dependencies: + is-alphabetical: 1.0.4 + is-decimal: 1.0.4 + dev: false + + /is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + dev: false + + /is-decimal@1.0.4: + resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} + dev: false + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: false + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: false + + /is-hexadecimal@1.0.4: + resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} + dev: false + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: false + + /is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + dev: false + + /linkify-it@3.0.3: + resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==} + dependencies: + uc.micro: 1.0.6 + dev: false + + /longest-streak@2.0.4: + resolution: {integrity: sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==} + dev: false + + /markdown-it@12.3.2: + resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==} + hasBin: true + dependencies: + argparse: 2.0.1 + entities: 2.1.0 + linkify-it: 3.0.3 + mdurl: 1.0.1 + uc.micro: 1.0.6 + dev: false + + /markdown-table@2.0.0: + resolution: {integrity: sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==} + dependencies: + repeat-string: 1.6.1 + dev: false + + /markdownlint-cli2-formatter-default@0.0.3(markdownlint-cli2@0.4.0): + resolution: {integrity: sha512-QEAJitT5eqX1SNboOD+SO/LNBpu4P4je8JlR02ug2cLQAqmIhh8IJnSK7AcaHBHhNADqdGydnPpQOpsNcEEqCw==} + peerDependencies: + markdownlint-cli2: '>=0.0.4' + dependencies: + markdownlint-cli2: 0.4.0 + dev: false + + /markdownlint-cli2@0.4.0: + resolution: {integrity: sha512-EcwP5tAbyzzL3ACI0L16LqbNctmh8wNX56T+aVvIxWyTAkwbYNx2V7IheRkXS3mE7R/pnaApZ/RSXcXuzRVPjg==} + engines: {node: '>=12'} + hasBin: true + dependencies: + globby: 12.1.0 + markdownlint: 0.25.1 + markdownlint-cli2-formatter-default: 0.0.3(markdownlint-cli2@0.4.0) + markdownlint-rule-helpers: 0.16.0 + micromatch: 4.0.4 + strip-json-comments: 4.0.0 + yaml: 1.10.2 + dev: false + + /markdownlint-rule-helpers@0.16.0: + resolution: {integrity: sha512-oEacRUVeTJ5D5hW1UYd2qExYI0oELdYK72k1TKGvIeYJIbqQWAz476NAc7LNixSySUhcNl++d02DvX0ccDk9/w==} + dev: false + + /markdownlint@0.25.1: + resolution: {integrity: sha512-AG7UkLzNa1fxiOv5B+owPsPhtM4D6DoODhsJgiaNg1xowXovrYgOnLqAgOOFQpWOlHFVQUzjMY5ypNNTeov92g==} + engines: {node: '>=12'} + dependencies: + markdown-it: 12.3.2 + dev: false + + /mdast-util-find-and-replace@1.1.1: + resolution: {integrity: sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==} + dependencies: + escape-string-regexp: 4.0.0 + unist-util-is: 4.1.0 + unist-util-visit-parents: 3.1.1 + dev: false + + /mdast-util-footnote@0.1.7: + resolution: {integrity: sha512-QxNdO8qSxqbO2e3m09KwDKfWiLgqyCurdWTQ198NpbZ2hxntdc+VKS4fDJCmNWbAroUdYnSthu+XbZ8ovh8C3w==} + dependencies: + mdast-util-to-markdown: 0.6.5 + micromark: 2.11.4 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-from-markdown@0.8.5: + resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==} + dependencies: + '@types/mdast': 3.0.15 + mdast-util-to-string: 2.0.0 + micromark: 2.11.4 + parse-entities: 2.0.0 + unist-util-stringify-position: 2.0.3 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-frontmatter@0.2.0: + resolution: {integrity: sha512-FHKL4w4S5fdt1KjJCwB0178WJ0evnyyQr5kXTM3wrOVpytD0hrkvd+AOOjU9Td8onOejCkmZ+HQRT3CZ3coHHQ==} + dependencies: + micromark-extension-frontmatter: 0.2.2 + dev: false + + /mdast-util-gfm-autolink-literal@0.1.3: + resolution: {integrity: sha512-GjmLjWrXg1wqMIO9+ZsRik/s7PLwTaeCHVB7vRxUwLntZc8mzmTsLVr6HW1yLokcnhfURsn5zmSVdi3/xWWu1A==} + dependencies: + ccount: 1.1.0 + mdast-util-find-and-replace: 1.1.1 + micromark: 2.11.4 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-gfm-strikethrough@0.2.3: + resolution: {integrity: sha512-5OQLXpt6qdbttcDG/UxYY7Yjj3e8P7X16LzvpX8pIQPYJ/C2Z1qFGMmcw+1PZMUM3Z8wt8NRfYTvCni93mgsgA==} + dependencies: + mdast-util-to-markdown: 0.6.5 + dev: false + + /mdast-util-gfm-table@0.1.6: + resolution: {integrity: sha512-j4yDxQ66AJSBwGkbpFEp9uG/LS1tZV3P33fN1gkyRB2LoRL+RR3f76m0HPHaby6F4Z5xr9Fv1URmATlRRUIpRQ==} + dependencies: + markdown-table: 2.0.0 + mdast-util-to-markdown: 0.6.5 + dev: false + + /mdast-util-gfm-task-list-item@0.1.6: + resolution: {integrity: sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==} + dependencies: + mdast-util-to-markdown: 0.6.5 + dev: false + + /mdast-util-gfm@0.1.2: + resolution: {integrity: sha512-NNkhDx/qYcuOWB7xHUGWZYVXvjPFFd6afg6/e2g+SV4r9q5XUcCbV4Wfa3DLYIiD+xAEZc6K4MGaE/m0KDcPwQ==} + dependencies: + mdast-util-gfm-autolink-literal: 0.1.3 + mdast-util-gfm-strikethrough: 0.2.3 + mdast-util-gfm-table: 0.1.6 + mdast-util-gfm-task-list-item: 0.1.6 + mdast-util-to-markdown: 0.6.5 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-to-markdown@0.6.5: + resolution: {integrity: sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==} + dependencies: + '@types/unist': 2.0.10 + longest-streak: 2.0.4 + mdast-util-to-string: 2.0.0 + parse-entities: 2.0.0 + repeat-string: 1.6.1 + zwitch: 1.0.5 + dev: false + + /mdast-util-to-string@2.0.0: + resolution: {integrity: sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==} + dev: false + + /mdurl@1.0.1: + resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} + dev: false + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: false + + /micromark-extension-footnote@0.3.2: + resolution: {integrity: sha512-gr/BeIxbIWQoUm02cIfK7mdMZ/fbroRpLsck4kvFtjbzP4yi+OPVbnukTc/zy0i7spC2xYE/dbX1Sur8BEDJsQ==} + dependencies: + micromark: 2.11.4 + transitivePeerDependencies: + - supports-color + dev: false + + /micromark-extension-frontmatter@0.2.2: + resolution: {integrity: sha512-q6nPLFCMTLtfsctAuS0Xh4vaolxSFUWUWR6PZSrXXiRy+SANGllpcqdXFv2z07l0Xz/6Hl40hK0ffNCJPH2n1A==} + dependencies: + fault: 1.0.4 + dev: false + + /micromark-extension-gfm-autolink-literal@0.5.7: + resolution: {integrity: sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==} + dependencies: + micromark: 2.11.4 + transitivePeerDependencies: + - supports-color + dev: false + + /micromark-extension-gfm-strikethrough@0.6.5: + resolution: {integrity: sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==} + dependencies: + micromark: 2.11.4 + transitivePeerDependencies: + - supports-color + dev: false + + /micromark-extension-gfm-table@0.4.3: + resolution: {integrity: sha512-hVGvESPq0fk6ALWtomcwmgLvH8ZSVpcPjzi0AjPclB9FsVRgMtGZkUcpE0zgjOCFAznKepF4z3hX8z6e3HODdA==} + dependencies: + micromark: 2.11.4 + transitivePeerDependencies: + - supports-color + dev: false + + /micromark-extension-gfm-tagfilter@0.3.0: + resolution: {integrity: sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==} + dev: false + + /micromark-extension-gfm-task-list-item@0.3.3: + resolution: {integrity: sha512-0zvM5iSLKrc/NQl84pZSjGo66aTGd57C1idmlWmE87lkMcXrTxg1uXa/nXomxJytoje9trP0NDLvw4bZ/Z/XCQ==} + dependencies: + micromark: 2.11.4 + transitivePeerDependencies: + - supports-color + dev: false + + /micromark-extension-gfm@0.3.3: + resolution: {integrity: sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==} + dependencies: + micromark: 2.11.4 + micromark-extension-gfm-autolink-literal: 0.5.7 + micromark-extension-gfm-strikethrough: 0.6.5 + micromark-extension-gfm-table: 0.4.3 + micromark-extension-gfm-tagfilter: 0.3.0 + micromark-extension-gfm-task-list-item: 0.3.3 + transitivePeerDependencies: + - supports-color + dev: false + + /micromark@2.11.4: + resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==} + dependencies: + debug: 4.3.4 + parse-entities: 2.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /micromatch@4.0.4: + resolution: {integrity: sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: false + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: false + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: false + + /parse-entities@2.0.0: + resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} + dependencies: + character-entities: 1.2.4 + character-entities-legacy: 1.1.4 + character-reference-invalid: 1.1.4 + is-alphanumerical: 1.0.4 + is-decimal: 1.0.4 + is-hexadecimal: 1.0.4 + dev: false + + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: false + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: false + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: false + + /remark-footnotes@3.0.0: + resolution: {integrity: sha512-ZssAvH9FjGYlJ/PBVKdSmfyPc3Cz4rTWgZLI4iE/SX8Nt5l3o3oEjv3wwG5VD7xOjktzdwp5coac+kJV9l4jgg==} + dependencies: + mdast-util-footnote: 0.1.7 + micromark-extension-footnote: 0.3.2 + transitivePeerDependencies: + - supports-color + dev: false + + /remark-frontmatter@3.0.0: + resolution: {integrity: sha512-mSuDd3svCHs+2PyO29h7iijIZx4plX0fheacJcAoYAASfgzgVIcXGYSq9GFyYocFLftQs8IOmmkgtOovs6d4oA==} + dependencies: + mdast-util-frontmatter: 0.2.0 + micromark-extension-frontmatter: 0.2.2 + dev: false + + /remark-gfm@1.0.0: + resolution: {integrity: sha512-KfexHJCiqvrdBZVbQ6RopMZGwaXz6wFJEfByIuEwGf0arvITHjiKKZ1dpXujjH9KZdm1//XJQwgfnJ3lmXaDPA==} + dependencies: + mdast-util-gfm: 0.1.2 + micromark-extension-gfm: 0.3.3 + transitivePeerDependencies: + - supports-color + dev: false + + /remark-parse@9.0.0: + resolution: {integrity: sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==} + dependencies: + mdast-util-from-markdown: 0.8.5 + transitivePeerDependencies: + - supports-color + dev: false + + /repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + dev: false + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: false + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: false + + /slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + dev: false + + /strip-json-comments@4.0.0: + resolution: {integrity: sha512-LzWcbfMbAsEDTRmhjWIioe8GcDRl0fa35YMXFoJKDdiD/quGFmjJjdgPjFJJNwCMaLyQqFIDqCdHD2V4HfLgYA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: false + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: false + + /traverse@0.6.8: + resolution: {integrity: sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==} + engines: {node: '>= 0.4'} + dev: false + + /trough@1.0.5: + resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==} + dev: false + + /uc.micro@1.0.6: + resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} + dev: false + + /underscore@1.13.6: + resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==} + dev: false + + /unified@9.2.2: + resolution: {integrity: sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==} + dependencies: + '@types/unist': 2.0.10 + bail: 1.0.5 + extend: 3.0.2 + is-buffer: 2.0.5 + is-plain-obj: 2.1.0 + trough: 1.0.5 + vfile: 4.2.1 + dev: false + + /unist-util-is@4.1.0: + resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} + dev: false + + /unist-util-stringify-position@2.0.3: + resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} + dependencies: + '@types/unist': 2.0.10 + dev: false + + /unist-util-visit-parents@3.1.1: + resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} + dependencies: + '@types/unist': 2.0.10 + unist-util-is: 4.1.0 + dev: false + + /update-section@0.3.3: + resolution: {integrity: sha512-BpRZMZpgXLuTiKeiu7kK0nIPwGdyrqrs6EDSaXtjD/aQ2T+qVo9a5hRC3HN3iJjCMxNT/VxoLGQ7E/OzE5ucnw==} + dev: false + + /vfile-message@2.0.4: + resolution: {integrity: sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==} + dependencies: + '@types/unist': 2.0.10 + unist-util-stringify-position: 2.0.3 + dev: false + + /vfile@4.2.1: + resolution: {integrity: sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==} + dependencies: + '@types/unist': 2.0.10 + is-buffer: 2.0.5 + unist-util-stringify-position: 2.0.3 + vfile-message: 2.0.4 + dev: false + + /yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: false + + /zwitch@1.0.5: + resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} + dev: false diff --git a/specs/SUMMARY.md b/specs/SUMMARY.md new file mode 100644 index 0000000..8d5a0b4 --- /dev/null +++ b/specs/SUMMARY.md @@ -0,0 +1,26 @@ + + +# Summary + +- [Root](./root.md) +- [Introduction](./introduction.md) +- [Kroma Protocol](./protocol/overview.md) + - [Bridges](./protocol/bridges.md) + - [Messengers](./protocol/messengers.md) + - [Deposits](./protocol/deposits.md) + - [Withdrawals](./protocol/withdrawals.md) + - [Guaranteed Gas Market](./protocol/guaranteed-gas-market.md) + - [L2 Output Root Submissions](./protocol/validator.md) + - [Clients]() + - [Execution Engine](./protocol/exec-engine.md) + - [Rollup Node](./protocol/rollup-node.md) + - [Rollup Node P2P](./protocol/rollup-node-p2p.md) + - [L2 Chain Derivation](./protocol/derivation.md) + - [Span Batches](./protocol/span-batches.md) + - [Batcher](./protocol/batcher.md) + - [Predeploys](./protocol/predeploys.md) + - [SystemConfig](./protocol/system-config.md) +- [Fault Proof]() + - [Challenge](./fault-proof/challenge.md) + - [zkEVM Prover](./fault-proof/zkevm-prover.md) +- [Glossary](./glossary.md) \ No newline at end of file diff --git a/specs/fault-proof/challenge.md b/specs/fault-proof/challenge.md new file mode 100644 index 0000000..602c085 --- /dev/null +++ b/specs/fault-proof/challenge.md @@ -0,0 +1,444 @@ +# Challenge + + + +**Table of Contents** + +- [Overview](#overview) +- [State Diagram](#state-diagram) +- [Challenge Creation](#challenge-creation) +- [Bisection](#bisection) +- [Proving Fault](#proving-fault) +- [Dismiss Challenge](#dismiss-challenge) +- [Force Delete Output](#force-delete-output) +- [Contract Interface](#contract-interface) +- [Upgradeability](#upgradeability) +- [Summary of Definitions](#summary-of-definitions) + - [Constants](#constants) + + + + + +[g-l1]: ../glossary.md#layer-1-l1 +[g-l2]: ../glossary.md#layer-2-l2 +[g-l2-output]: ../glossary.md#l2-output-root +[g-validator]: ../glossary.md#validator +[g-zk-fault-proof]: ../glossary.md#zk-fault-proof +[g-security-council]: ../glossary.md#security-council + +## Overview + +When a [validator][g-validator] detects that a submitted [L2 output root][g-l2-output] contains an invalid state +transition, it can start a dispute challenge process by triggering the [Colosseum contract](#contract-interface). We +refer to a validator who submits a dispute challenge as a "challenger" and a validator who initially submitted +an L2 output as an "asserter." A dispute challenge entails a confrontational interaction between an asserter and a +challenger, which persists until one of them emerges victorious. If the challenger wins, the corresponding L2 output +will be deleted. + +A single output can be subject to multiple challenges. Challengers also need to stake their bonds equivalent to those +staked by asserter when submitting L2 outputs to generate challenges. Should the asserter emerge victorious in a +challenge, they receive the staked bonds of all the challengers as reward. On the other hand, if a challenger prevails, +the one who submitted the first valid ZK fault proof is given the asserter's staked bond. As a preventive measure +against collusion between asserters and challengers, tax is imposed. If there are any ongoing challenges, +the challenges are canceled, and staked bonds are refunded to the respective challengers. + +In the ZK fault-proof challenge process, the following undeniable bug might arise, prompting the intervention of the +[Security Council][g-security-council]: + +- The deletion of a valid output due to two valid and contradictory ZK proofs +- The failure to delete an invalid output due to the bugs in prover/verifier or ZK completeness error + +In the former case, the Security Council validates the legitimacy of the deleted output and, if the aforementioned +error is identified, dismisses the challenge and initiates a rollback of the deleted output. +In the latter scenario, all challengers will fail in proving the fault. In such cases, the Security Council verifies +the output and, if deemed invalid, deletes the output forcibly. All interventions by the Security Council are executed +through multi-sig transactions. + +## State Diagram + +![state-diagram](../static/assets/colosseum-state-diagram.svg) + +1. If the challenge is created, at the same time, the challenger needs to submit the first segments(9 outputs). + The state is set to `ASSERTER_TURN`. +2. Then the asserter picks the first invalid segment and submits the next segments(6 outputs) for the picked segment. + `ASSERTER_TURN` state goes to `CHALLENGER_TURN`. +3. If there's more segments to be interacted with, the challenger picks the first invalid segment and submits the next + segments(10 outputs) for the picked segment. `CHALLENGER_TURN` state goes to `ASSERTER_TURN` and repeat from step 2. + If the output has already been deleted by other challenger, the challenger cancel challenge and refund bond. +4. Both `ASSERTER_TURN` and `CHALLENGER_TURN` states have a timeout called `BISECTION_TIMEOUT` and if it happens, the + state goes to `ASSERTER_TIMEOUT` and `CHALLENGER_TIMEOUT` respectively. This is to mitigate _liveness attack_. + This is because we want to give a penalty to one who doesn't respond timely. +5. When the asserter timed out or bisection is completed, the state of challenge will be `READY_TO_PROVE` automatically. + At this state, the challenger is now able to pick the first invalid output and submit ZK fault proof. + Likewise, the challenge is canceled if the output is already been deleted. +6. If the submitted proof is turned out to be invalid, the state stays at `READY_TO_PROVE` until `PROVING_TIMEOUT` has + occurred. +7. Otherwise, `READY_TO_PROVE` state goes to `PROVEN`, and the L2 output is deleted. +8. The deleted output would be validated by the **[Security Council][g-security-council]** to mitigate ZK soundness + attack. +9. If the deleted output was invalid, so it should have been, the Security Council do nothing. +10. Otherwise, the **Security council** will dismiss the challenge and rollback the valid output. + +## Challenge Creation + +Validators can initiate challenges when they suspect that an invalid output has been submitted. In their role as +challengers, they start the challenge process with initial segments for interactive fault proof. + +> **Note** Challenges can only be initiated within the `CREATION_PERIOD` (< `FINALIZATION_PERIOD`) since the output +> is submitted. This restriction aims to prevent malicious challengers from deleting outputs just before finalization, +> causing a delay attack. + +## Bisection + +At this moment, it takes a long time to generate a proof for state transition of even a single block. +To resolve this problem, we adopt an `interactive fault proof`. +Unlike other implementations bisecting until both parties find a single step of instruction to execute, we bisect +until the challenger finds a single step of block to prove fault-ness. +The basic idea is derived from [Arbitrum Nitro](https://github.com/OffchainLabs/nitro). + +**NOTE:** Someday if the proof generation becomes fast, this process will be removed. + +In conclusion, we have two requirements. One is bisecting to find a single block, the other is the last turn should be +challenger's turn. By making use of the fact that [L2 output root][g-l2-output] is always submitted at every 1800 +[L2][g-l2] blocks, for example, we can decompose 1800 into 45 and 40. We let the challenger submit 46(45 + 1) segments +and the asserter do 41(40 + 1). The reason why the challenger submits more than the asserter is to prevent challenge +abusing. As a result, after one interaction between parties, [ZK fault proof][g-zk-fault-proof] can be ready to be +verified on [L1][g-l1]. In reality, 1800 blocks are segmented using `SEGMENTS_LENGTHS`. + +Here we give a simple example with a small number to show you how it works. Let's say there are 11 blocks and the 3rd +block is the block a challenger wants to argue against and this number is decomposed into 5 and 2. Also, let's assume +that both of them agree the state transitions to the 2nd block. + +| Turn | Action | Segment Start | Segment Length | Segments | Condition | +|------------|-----------------|---------------|----------------|-----------------|--------------------| +| Challenger | createChallenge | 0 | 6 | [0, 2, ..., 10] | No | +| Asserter | bisect(2) | 2 | 3 | [2, 3', 4'] | 2 = 2 && 4 != 4' | +| Challenger | proveFault(2) | 2 | 2 | [2, 3''] | 2 = 2 && 3' != 3'' | + +You can notice that in each turn, the first element of the segments must be same with the element at the same index of +the previous segments. Whereas, the last element of the segments must be different from the element at the same index of +the previous segments. In this way, both parties are able to agree with a single step of block. + +When the challenge process is completed and the corresponding output is deleted by other challenger during bisection, +the challenge will be canceled automatically. + +## Proving Fault + +Since Colosseum verifies public input along with [zkEVM-proof](zkevm-prover.md#zkevm-proof), challengers should +calculate as below and enclose the public input to the `proveFault` transaction. + +```ts +import { DataOptions, hexlify } from '@ethersproject/bytes'; +import { Wallet, constants } from 'ethers'; +import { keccak256 } from 'ethers/lib/utils'; + +function strip0x(str: string): string { + if (str.startsWith('0x')) { + return str.slice(2); + } + return str; +} + +function toFixedBuffer( + value: string | number, + length, + padding = '0', +): Buffer { + const options: DataOptions = { + hexPad: 'left', + }; + return hexToBuffer( + strip0x(hexlify(value, options)).padStart(length * 2, padding), + ); +} + +async function getDummyTxHash(chainId: number): Promise { + const sk = hex.toFixedBuffer(1, 32); + const signer = new Wallet(sk); + const rlp = await signer.signTransaction({ + nonce: 0, + gasLimit: 0, + gasPrice: 0, + to: constants.AddressZero, + value: 0, + data: '0x', + chainId, + }); + return keccak256(rlp); +} + +async function computePublicInput(block: RPCBlock, chainId: number): Promise<[string, string]> { + const maxTxs = 100; + + const buf = Buffer.concat([ + hex.toFixedBuffer(prevStateRoot, 32), + hex.toFixedBuffer(block.stateRoot, 32), + hex.toFixedBuffer(block.withdrawalsRoot ?? 0, 32), + hex.toFixedBuffer(block.hash, 32), + hex.toFixedBuffer(block.parentHash, 32), + hex.toFixedBuffer(block.number, 8), + hex.toFixedBuffer(block.timestamp, 8), + hex.toFixedBuffer(block.baseFeePerGas ?? 0, 32), + hex.toFixedBuffer(block.gasLimit, 8), + hex.toFixedBuffer(block.transactions.length, 2), + Buffer.concat( + block.transactions.map((txHash: string) => { + return toFixedBuffer(txHash, 32); + }), + ), + Buffer.concat( + Array(maxTxs - block.transactions.length).fill( + toFixedBuffer(await getDummyTxHash(chainId), 32), + ), + ), + ]); + const h = hex.toFixedBuffer(keccak256(buf), 32); + return [ + '0x' + h.subarray(0, 16).toString('hex'), + '0x' + h.subarray(16, 32).toString('hex'), + ]; +} +``` + +The following is the verification process of invalid output by +[ZK Verifier Contract](zkevm-prover.md#the-zk-verifier-contract): + +1. Check whether the challenge is ready to prove. The status of challenge should be `READY_TO_PROVE` + or `ASSERTER_TIMEOUT`. +2. Check whether `srcOutputRootProof` is the preimage of the first output root of the segment. +3. Check whether `dstOutputRootProof` is the preimage of the next output root of the segment. +4. Verify that the `nextBlockHash` in `srcOutputRootProof` matches the `blockHash` in `dstOutputRootProof`. +5. Verify that the `stateRoot` in `publicInput` matches the `stateRoot` in `dstOutputRootProof`. +6. Verify that the `nextBlockHash` in `srcOutputRootProof` matches the block hash derived from `publicInput` and `rlps`. +7. Verify that the `withdrawalStorageRoot` in `dstOutputRootProof` is contained in `stateRoot` in `dstOutputRootProof` + using `merkleProof`. +8. If the length of transaction hashes in `publicInput` is less than `MAX_TXS`, fill it with `DUMMY_HASH`. +9. Verify the `_zkproof` using `_pair` and `publicInputHash`. The `publicInputHash` is derived from the `publicInput` + and `stateRoot` of `srcOutputRootProof`, while `_zkproof` and `_pair` are submitted by the challenger directly. +10. Delete the output and request validation of the challenge to [Security Council][g-security-council] if there is any + undeniable bugs such as soundness error. +11. If the deleted output was valid so the challenge has an undeniable bug, Security Council will + [dismiss](#dismiss-challenge) the challenge and roll back the output. + +## Dismiss Challenge + +Upon a successful challenge resulting in output deletion, the Security Council will verify the genuineness of the +deleted output(two valid contradicting ZK proofs). Given that the deletion of output introduces withdrawal delays, +the Security Council conducts a thorough investigation into this issue. Upon validation of the legitimate nature of the +output deletion, the Security Council will dismiss the challenge and initiate the process of output rollback. +This can only be executed through the multi-sig transaction of the Security Council. + +## Force Delete Output + +In the event that an undeniable bug within the ZK fault-proof system, such as a ZK completeness error, is detected, it +becomes necessary to remove outputs deemed invalid. To address this, the Security Council is tasked with inspecting +outputs that have completed the bisect process but have failed the fault-proof verification. If an invalid output is +submitted and is determined to be associated with an undeniable bug, the Security Council holds the authority to delete +the output through a multi-sig transaction. + +## Contract Interface + +The Colosseum contract implements the following interface: + +```solidity +interface Colosseum { + /** + * @notice Emitted when the challenge is created. + * + * @param outputIndex Index of the L2 checkpoint output. + * @param asserter Address of the asserter. + * @param challenger Address of the challenger. + * @param timestamp The timestamp when created. + */ + event ChallengeCreated( + uint256 indexed outputIndex, + address indexed asserter, + address indexed challenger, + uint256 timestamp + ); + + /** + * @notice Emitted when segments are bisected. + * + * @param outputIndex Index of the L2 checkpoint output. + * @param challenger Address of the challenger. + * @param turn The current turn. + * @param timestamp The timestamp when bisected. + */ + event Bisected( + uint256 indexed outputIndex, + address indexed challenger, + uint8 turn, + uint256 timestamp + ); + + /** + * @notice Emitted when it is ready to be proved. + * + * @param outputIndex Index of the L2 checkpoint output. + * @param challenger Address of the challenger. + */ + event ReadyToProve(uint256 indexed outputIndex, address indexed challenger); + + /** + * @notice Emitted when proven fault. + * + * @param outputIndex Index of the L2 checkpoint output. + * @param challenger Address of the challenger. + * @param timestamp The timestamp when proven. + */ + event Proven(uint256 indexed outputIndex, address indexed challenger, uint256 timestamp); + + /** + * @notice Emitted when challenge is dismissed. + * + * @param outputIndex Index of the L2 checkpoint output. + * @param challenger Address of the challenger. + * @param timestamp The timestamp when dismissed. + */ + event ChallengeDismissed( + uint256 indexed outputIndex, + address indexed challenger, + uint256 timestamp + ); + + /** + * @notice Emitted when challenge is canceled. + * + * @param outputIndex Index of the L2 checkpoint output. + * @param challenger Address of the challenger. + * @param timestamp The timestamp when canceled. + */ + event ChallengeCanceled( + uint256 indexed outputIndex, + address indexed challenger, + uint256 timestamp + ); + + /** + * @notice Emitted when challenger timed out. + * + * @param outputIndex Index of the L2 checkpoint output. + * @param challenger Address of the challenger. + * @param timestamp The timestamp when deleted. + */ + event ChallengerTimedOut( + uint256 indexed outputIndex, + address indexed challenger, + uint256 timestamp + ); + + /** + * @notice Creates a challenge against an invalid output. + * + * @param _outputIndex Index of the invalid L2 checkpoint output. + * @param _l1BlockHash The block hash of L1 at the time the output L2 block was created. + * @param _l1BlockNumber The block number of L1 with the specified L1 block hash. + * @param _segments Array of the segment. A segment is the first output root of a specific range. + */ + function createChallenge( + uint256 _outputIndex, + bytes32 _l1BlockHash, + uint256 _l1BlockNumber, + bytes32[] calldata _segments + ) external; + + /** + * @notice Selects an invalid section and submit segments of that section. + * + * @param _outputIndex Index of the L2 checkpoint output. + * @param _challenger Address of the challenger. + * @param _pos Position of the last valid segment. + * @param _segments Array of the segment. A segment is the first output root of a specific range. + */ + function bisect( + uint256 _outputIndex, + address _challenger, + uint256 _pos, + bytes32[] calldata _segments + ) external; + + /** + * @notice Proves that a specific output is invalid using ZKP. + * This function can only be called in the READY_TO_PROVE and ASSERTER_TIMEOUT states. + * + * @param _outputIndex Index of the L2 checkpoint output. + * @param _pos Position of the last valid segment. + * @param _proof Proof for public input validation. + * @param _zkproof Halo2 proofs composed of points and scalars. + * See https://zcash.github.io/halo2/design/implementation/proofs.html. + * @param _pair Aggregated multi-opening proofs and public inputs. (Currently only 2 public inputs) + */ + function proveFault( + uint256 _outputIndex, + uint256 _pos, + Types.PublicInputProof calldata _proof, + uint256[] calldata _zkproof, + uint256[] calldata _pair + ) external; + + /** + * @notice Calls a private function that deletes the challenge because the challenger has timed out. + * Reverts if the challenger hasn't timed out. + * + * @param _outputIndex Index of the L2 checkpoint output. + * @param _challenger Address of the challenger. + */ + function challengerTimeout(uint256 _outputIndex, address _challenger) external; + + /** + * @notice Cancels the challenge. + * Reverts if is not possible to cancel the sender's challenge for the given output index. + * + * @param _outputIndex Index of the L2 checkpoint output. + */ + function cancelChallenge(uint256 _outputIndex) external; + + /** + * @notice Dismisses the challenge and rollback l2 output. + * This function can only be called by Security Council contract. + * + * @param _outputIndex Index of the L2 checkpoint output. + * @param _challenger Address of the challenger. + * @param _asserter Address of the asserter. + * @param _outputRoot The L2 output root to rollback. + * @param _publicInputHash Hash of public input. + */ + function dismissChallenge( + uint256 _outputIndex, + address _challenger, + address _asserter, + bytes32 _outputRoot, + bytes32 _publicInputHash + ) external; + + /** + * @notice Deletes the L2 output root forcefully by the Security Council + * when zk-proving is not possible due to an undeniable bug. + * + * @param _outputIndex Index of the L2 checkpoint output. + */ + function forceDeleteOutput(uint256 _outputIndex) external; +} + +``` + +## Upgradeability + +Colosseum contract should be deployed behind upgradable proxies. + +## Summary of Definitions + +### Constants + +| Name | Value | Unit | +|-------------------------------|--------------------------------------------------------------------|-------------------| +| `REQUIRED_BOND_AMOUNT` | 200000000000000000 (0.2 ETH) | wei | +| `FINALIZATION_PERIOD_SECONDS` | 604800 | seconds | +| `CREATION_PERIOD_SECONDS` | 518400 | seconds | +| `BISECTION_TIMEOUT` | 3600 | seconds | +| `PROVING_TIMEOUT` | 28800 | seconds | +| `SEGMENTS_LENGTHS` | [9, 6, 10, 6] | array of integers | +| `MAX_TXS` | 100 | uint256 | +| `DUMMY_HASH` | 0xedf1ae3da135c124658e215a9bf53477facb442a1dcd5a92388332cb6193237f | bytes32 | diff --git a/specs/fault-proof/zkevm-prover.md b/specs/fault-proof/zkevm-prover.md new file mode 100644 index 0000000..7a831d5 --- /dev/null +++ b/specs/fault-proof/zkevm-prover.md @@ -0,0 +1,72 @@ +# zkEVM Prover + + + +**Table of Contents** + +- [Overview](#overview) +- [zkEVM Proof](#zkevm-proof) +- [The ZK Verifier Contract](#the-zk-verifier-contract) +- [Prover as an RPC Server](#prover-as-an-rpc-server) + + + + + +[g-state]: ../glossary.md#state +[g-zk-fault-proof]: ../glossary.md#zk-fault-proof +[g-mpt]: ../glossary.md#merkle-patricia-trie + +## Overview + +A prover is responsible for generating proof using the [Halo2 proving scheme](https://zcash.github.io/halo2/) +for a target blocks. This proof provides claims regarding the legitimacy of block data and state transitions. +A prover plays a vital role in the challenge process, serving as a key component of the challenger. + +A [ZK fault proof][g-zk-fault-proof] states that a [state][g-state] transition from `S` to `S'` is valid. +It sounds like there are no big differences from validity proof. That's true. But the point is this is used +to prove the state transition `S` to `S''` is wrong by showing a valid state transition `S` to `S'`. + +## zkEVM Proof + +In the context of the EVM, which is regarded as a Turing machine, the generation of proof requires demonstrating the +execution of opcode operations in arbitrary order. This proof serves as an evidence of computation for the EVM. +Additionally, validating the block data's integrity involves proving sub-operations such as +[Merkle Patricia Trie][g-mpt] and ECDSA verification. +To support the proof generation process for a target block, [zkEVM-circuits] offer circuits capable of proving the +required sub-operations. These circuits provide the means to prove the integrity and validity of the block data. +By combining these circuits, a prover completes the overall validity proof for the target block. + +See [zkEVM-specs] for details about the statements claimed by the ZK-proof. + +## The ZK Verifier Contract + +The proof generated by a prover can be verified through the verifier contract that includes the following interface. +The verification in a challenge process is implemented using the `verify` function provided by the verifier contract. + +```solidity +interface ZKVerifier { + function verify( + uint256[] calldata proof, + uint256[] calldata target_circuit_final_pair, + bytes32 publicInputHash + ) public view returns (bool); +} +``` + +## Prover as an RPC Server + +[kroma-prover](https://github.com/kroma-network/kroma-prover) is implemented as a jsonRPC server that generates a zkEVM +proof for the requested height block. It can be utilized as a local component on the challenger's machine +or as a remote jsonRPC server (prover as a service). + +- Step 1: An user (e.g., challenger) requests a zkEVM-proof for a block of specific height to kroma-prover with + the trace of the desired block. +- Step 2: A kroma-prover generates zkEVM-proof for the target block, and return it to the user. + +Operating a kroma-prover, which is launched only when necessary, can alleviate the situation where +regular challengers need to allocate excessive system resources due to the proof generation process +that is occasionally executed (perhaps rarely executed). + +[zkEVM-circuits]: https://github.com/kroma-network/zkevm-circuits +[zkEVM-specs]: https://github.com/kroma-network/zkevm-specs diff --git a/specs/glossary.md b/specs/glossary.md new file mode 100644 index 0000000..90f2171 --- /dev/null +++ b/specs/glossary.md @@ -0,0 +1,968 @@ +# Glossary + + + +**Table of Contents** + +- [General Terms](#general-terms) + - [Layer 1 (L1)](#layer-1-l1) + - [Layer 2 (L2)](#layer-2-l2) + - [Block](#block) + - [Account](#account) + - [EOA](#eoa) + - [State](#state) + - [State Trie](#state-trie) + - [State Root](#state-root) + - [Storage Trie](#storage-trie) + - [Storage Root](#storage-root) + - [Keccak](#keccak) + - [Merkle Patricia Trie](#merkle-patricia-trie) + - [ZK Trie](#zk-trie) + - [Chain Re-Organization](#chain-re-organization) + - [Predeployed Contract ("Predeploy")](#predeployed-contract-predeploy) + - [Receipt](#receipt) + - [Transaction Type](#transaction-type) + - [Fork Choice Rule](#fork-choice-rule) + - [Priority Gas Auction](#priority-gas-auction) +- [Sequencing](#sequencing) + - [Sequencer](#sequencer) + - [Maximal Extractable Value](#maximal-extractable-value) + - [Sequencing Window](#sequencing-window) + - [Sequencing Epoch](#sequencing-epoch) + - [L1 Origin](#l1-origin) +- [Validation](#validation) + - [Checkpoint Output](#checkpoint-output) + - [Validator](#validator) + - [Trusted Validator](#trusted-validator) + - [Validating Epoch](#validating-epoch) + - [SUBMISSION TIMEOUT](#submission-timeout) + - [Priority Round](#priority-round) + - [Public Round](#public-round) + - [Validator Reward](#validator-reward) +- [Deposits](#deposits) + - [Deposited Transaction](#deposited-transaction) + - [L1 Attributes Deposited Transaction](#l1-attributes-deposited-transaction) + - [User-Deposited Transaction](#user-deposited-transaction) + - [Depositing Call](#depositing-call) + - [Depositing Transaction](#depositing-transaction) + - [Depositor](#depositor) + - [Deposited Transaction Type](#deposited-transaction-type) + - [Deposit Contract](#deposit-contract) +- [Withdrawals](#withdrawals) + - [Relayer](#relayer) + - [Finalization Period](#finalization-period) +- [Batch Submission](#batch-submission) + - [Data Availability](#data-availability) + - [Data Availability Provider](#data-availability-provider) + - [Sequencer Batch](#sequencer-batch) + - [Channel](#channel) + - [Channel Frame](#channel-frame) + - [Batcher](#batcher) + - [Batcher Transaction](#batcher-transaction) + - [Channel Timeout](#channel-timeout) +- [L2 Chain Derivation](#l2-chain-derivation) + - [L2 Derivation Inputs](#l2-derivation-inputs) + - [System Configuration](#system-configuration) + - [Payload Attributes](#payload-attributes) + - [L2 Genesis Block](#l2-genesis-block) + - [L2 Chain Inception](#l2-chain-inception) + - [Safe L2 Block](#safe-l2-block) + - [Safe L2 Head](#safe-l2-head) + - [Unsafe L2 Block](#unsafe-l2-block) + - [Unsafe L2 Head](#unsafe-l2-head) + - [Unsafe Block Consolidation](#unsafe-block-consolidation) + - [Finalized L2 Head](#finalized-l2-head) +- [Other L2 Chain Concepts](#other-l2-chain-concepts) + - [Address Aliasing](#address-aliasing) + - [Rollup Node](#rollup-node) + - [Rollup Driver](#rollup-driver) + - [L1 Attributes Predeployed Contract](#l1-attributes-predeployed-contract) + - [L2 Output Root](#l2-output-root) + - [L2 Output Oracle Contract](#l2-output-oracle-contract) + - [Validator Pool Contract](#validator-pool-contract) + - [Colosseum Contract](#colosseum-contract) + - [ZK Fault Proof](#zk-fault-proof) + - [Security Council](#security-council) + - [Time Slot](#time-slot) + - [Block Time](#block-time) + - [Unsafe Sync](#unsafe-sync) +- [Execution Engine Concepts](#execution-engine-concepts) + - [Execution Engine](#execution-engine) + + + +--- + +# General Terms + +## Layer 1 (L1) + +[L1]: glossary.md#layer-1-L1 + +Refers the Ethereum blockchain, used in contrast to [layer 2][L2], which refers to Kroma. + +## Layer 2 (L2) + +[L2]: glossary.md#layer-2-L2 + +Refers to the Kroma blockchain (specified in this repository), used in contrast to [layer 1][L1], which +refers to the Ethereum blockchain. + +## Block + +[block]: glossary.md#block + +Can refer to an [L1] block, or to an [L2] block, which are structured similarly. + +A block is a sequential list of transactions, along with a couple of properties stored in the *header* of the block. A +description of these properties can be found in code comments [here][nano-header], or in the [Ethereum yellow paper +(pdf)][yellow], section 4.3. + +It is useful to distinguish between input block properties, which are known before executing the transactions in the +block, and output block properties, which are derived after executing the block's transactions. These include various +[Merkle Patricia Trie roots][mpt] that notably commit to the L2 state and to the log events emitted during execution. + +## Account + +[account]: glossary.md#account + +Refers to the [Ethereum Account][ethereum-account-details] which comprises `nonce`, `balance`, `codeHash` and +`storageRoot`. + +[ethereum-account-details]: https://ethereum.org/en/developers/docs/accounts/ + +## EOA + +[EOA]: glossary.md#EOA + +"Externally Owned Account", an Ethereum term to designate addresses operated by users, as opposed to contract addresses. + +## State + +[state]: glossary.md#state + +This means all the leaves of a [State Trie][state-trie]. In other words, it means all the [accounts][account]. +A [Block] contains the [State Root][state-root] to represent [State] as a commitment. + +## State Trie + +[state-trie]: glossary.md#state-trie + +A [Merkle Patricia Trie][mpt] that represents [state]. + +## State Root + +[state-root]: glossary.md#state-root + +A merkle root of [State Trie][state-trie]. + +## Storage Trie + +[storage-trie]: glossary.md#storage-trie + +A [Merkle Patricia Trie][mpt] that represents storage slots. + +## Storage Root + +[storage-root]: glossary.md#storage-root + +A merkle root of [Storage Trie][storage-trie]. + +## Keccak + +[keccak]: glossary.md#Keccak + +[Keccak][keccak-wiki] is a hash function used in [L1] for various purposes. Some examples are deriving address of +[EOA] and computing path of [MPT] which is called Secure Trie. + +[keccak-wiki]: https://en.wikipedia.org/wiki/SHA-3 + +## Merkle Patricia Trie + +[mpt]: glossary.md#merkle-patricia-trie + +A [Merkle Patricia Trie (MPT)][mpt-details] is a sparse trie, which is a tree-like structure that maps keys to values. +The root hash of a MPT is a commitment to the contents of the tree, which allows a +proof to be constructed for any key-value mapping encoded in the tree. Such a proof is called a Merkle proof, and can be +verified against the Merkle root. + +## ZK Trie + +[zk trie]: glossary.md#zk-trie + +A [ZK Trie (ZKT)][zkt-details] is a binary sparse trie, which is a tree-like structure that maps keys to values. +The root hash of a ZKT is a commitment to the contents of the tree, which allows a +proof to be constructed for any key-value mapping encoded in the tree. Such a proof is called a Merkle proof, and can be +verified against the Merkle root. + +Whereas [L1] uses [MPT][mpt] to represent state, [L2] uses ZKT. This is because ZKT enables faster proof +generation by avoiding [Keccak][keccak] and [RLP encoding][RLP format]. To accomplish this, ZKT uses poseidon hash to calculate +the path and concatenates leaf values in bytes. + +## Chain Re-Organization + +[reorg]: glossary.md#chain-re-organization + +A re-organization, or re-org for short, is whenever the head of a blockchain (its last block) changes (as dictated by +the [fork choice rule][fork-choice-rule]) to a block that is not a child of the previous head. + +L1 re-orgs can happen because of network conditions or attacks. L2 re-orgs are a consequence of L1 re-orgs, mediated via +[L2 chain derivation][derivation]. + +## Predeployed Contract ("Predeploy") + +[predeploy]: glossary.md#predeployed-contract-predeploy + +A contract placed in the L2 genesis state (i.e. at the start of the chain). + +All predeploy contracts are specified in the [predeploys specification][predeploy]. + +## Receipt + +[receipt]: glossary.md#receipt + +A receipt is an output generated by a transaction, comprising a status code, the amount of gas used, a list of log +entries, and a [bloom filter] indexing these entries. Log entries are most notably used to encode [Solidity events]. + +Receipts are not stored in blocks, but blocks store a [Merkle Patricia Trie root][mpt] for a tree containing the receipt +for every transaction in the block. + +Receipts are specified in the [yellow paper (pdf)][yellow] section 4.3.1. + +## Transaction Type + +[transaction-type]: glossary.md#transaction-type + +Ethereum provides a mechanism (as described in [EIP-2718]) for defining different transaction types. +Different transaction types can contain different payloads, and be handled differently by the protocol. + +[EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 + +## Fork Choice Rule + +[fork-choice-rule]: glossary.md#fork-choice-rule + +The fork choice rule is the rule used to determine which block is to be considered as the head of a blockchain. On L1, +this is determined by the proof of stake rules. + +L2 also has a fork choice rule, although the rules vary depending on whether we want the [safe L2 head][safe-l2-head], +the [unsafe L2 head][unsafe-l2-head] or the [finalized L2 head][finalized-l2-head]. + +## Priority Gas Auction + +Transactions in ethereum are ordered by the price that the transaction pays to the miner. Priority Gas Auctions +(PGAs) occur when multiple parties are competing to be the first transaction in a block. Each party continuously +updates the gas price of their transaction. PGAs occur when there is value in submitting a transaction before other +parties (like being the first deposit or submitting a deposit before there is not more guaranteed gas remaining). +PGAs tend to have negative externalities on the network due to a large amount of transactions being submitted in a +very short amount of time. + +--- + +# Sequencing + +[sequencing]: glossary.md#sequencing + +Transactions in the rollup can be included in two ways: + +- Through a [deposited transaction](#deposited-transaction), enforced by the system +- Through a regular transaction, embedded in a [sequencer batch](#sequencer-batch) + +Submitting transactions for inclusion in a batch saves costs by reducing overhead, and enables the sequencer to +pre-confirm the transactions before the L1 confirms the data. + +## Sequencer + +[sequencer]: glossary.md#sequencer + +A sequencer is either a [rollup node][rollup-node] ran in sequencer mode, or the operator of this rollup node. + +The sequencer is a priviledged actor, which receives L2 transactions from L2 users, creates L2 blocks using them, which +it then submits to [data availability provider][avail-provider] (via a [batcher]). It also submits [output +roots][l2-output] to L1. + +## Sequencing Window + +[sequencing-window]: glossary.md#sequencing-window + +A sequencing window is a range of L1 blocks from which a [sequencing epoch][sequencing-epoch] can be derived. + +A sequencing window whose first L1 block has number `N` contains [batcher transactions][batcher-transaction] for epoch +`N`. The window contains blocks `[N, N + SWS)` where `SWS` is the sequencer window size. + +The current default `sws` is 3600 epochs. + +Additionally, the first block in the window defines the [depositing transactions][depositing-tx] which determine the +[deposits] to be included in the first L2 block of the epoch. + +## Sequencing Epoch + +[sequencing-epoch]: glossary.md#sequencing-epoch + +A sequencing epoch is sequential range of L2 blocks derived from a [sequencing window](#sequencing-window) of L1 blocks. + +Each epoch is identified by an epoch number, which is equal to the block number of the first L1 block in the +sequencing window. + +Epochs can have variable size, subject to some constraints. See the [L2 chain derivation specification][derivation-spec] +for more details. + +## L1 Origin + +[l1-origin]: glossary.md#l1-origin + +The L1 origin of an L2 block is the L1 block corresponding to its [sequencing epoch][sequencing-epoch]. + +--- + +# Validation + +[validation]: glossary.md#validation + +Because transactions are visible to anyone, nodes can derive state. Registered [validators][validator] can submit +[checkpoint output][checkpoint-output] every [validating epoch][validating-epoch]. Validators receive rewards +in return. If the checkpoint output turns out to be invalid, this is challenged by another validator who acts as a +challenger. As a result, validator who submitted invalid checkpoint output will lose their bond. + +## Checkpoint Output + +[checkpoint-output]: glossary.md#checkpoint-output + +Checkpoint output is the l2 output root that denotes state transition during [validating epoch][validating-epoch]. + +## Validator + +[validator]: glossary.md#validator + +A validator is a decentralized actor, who does [validation]. To participate network as a validator, one needs to +deposit to [ValidatorPool contract][validator-pool-contract]. +Then the validator is allowed to be able to submit checkpoint. + +## Trusted Validator + +[trusted-validator]: glossary.md#trusted-validator + +A trusted validator is an actor who is run by Lightscale. If validations are not done until +[submission interval][submission-timeout], trusted validator will submit output for liveness of [L2]. + +## Validating Epoch + +[validating-epoch]: glossary.md#validating-epoch + +c of [L2] [blocks][block] where needs to be checkpointed. + +Each epoch is identified by an epoch number, which is incremented by 1. + +Epochs have fixed size, which can be different in every networks. In Kroma mainnet, it will be fixed to `1800` blocks, +identical to 1 hour. + +## SUBMISSION TIMEOUT + +[submission-timeout]: glossary.md#submission-timeout + +A submission timeout is the maximum duration that the trusted validator waits for decentralized validators to do +validations. + +## Priority Round + +[priority-round]: glossary.md#priority-round + +A priority round is the period during which validator with output submission priority can submit the output. + +## Public Round + +[public-round]: glossary.md#public-round + +A public round is the time period during which any account registered as a Validator can submit the output. + +Any validator can participate in the public round, but only one validator is fully recognized as an output submitter. + +Consider a case where multiple validators competitively submit output in a public round. + +Except for the first-place validator with a valid output submission transaction in the L1 block, + +the remaining validators' transactions will fail and will not be recognized as output submitters. + +However, some of them will have already spent their gas. + +To handle this situation, we provide an option flag to indicate whether or not to participate in the public round. + +Since we're taking a conservative approach, the default value is set to false. + +## Validator Reward + +[validator-reward]: glossary.md#validator-reward + +The validator reward is calculated using the following formula: +`(L2 base fee + L2 priority fee) * validator reward scalar / 10000`. + +--- + +# Deposits + +[deposits]: glossary.md#deposits + +In general, a deposit is an L2 transaction derived from an L1 block (by the [rollup driver]). + +While transaction deposits are notably (but not only) used to "deposit" (bridge) ETH and tokens to L2, the word +*deposit* should be understood as "a transaction *deposited* to L2 from L1". + +This term *deposit* is somewhat ambiguous as these "transactions" exist at multiple levels. This section disambiguates +all deposit-related terms. + +Notably, a *deposit* can refer to: + +- A [deposited transaction][deposited] (on L2) that is part of a deposit block. +- A [depositing call][depositing-call] that causes a [deposited transaction][deposited] to be derived. +- The event/log data generated by the [depositing call][depositing-call], which is what the [rollup driver] reads to + derive the [deposited transaction][deposited]. + +We sometimes also talk about *user deposit* which is a similar term that explicitly excludes [L1 attributes deposited +transactions][l1-attr-deposit]. + +Deposits are specified in the [deposits specification][deposits-spec]. + +## Deposited Transaction + +[deposited]: glossary.md#deposited-transaction + +A *deposited transaction* is a L2 transaction that was derived from L1 and included in a L2 block. + +There are two kinds of deposited transactions: + +- [L1 attributes deposited transaction][l1-attr-deposit], which submits the L1 block's attributes to the [L1 Attributes + Predeployed Contract][l1-attr-predeploy]. +- [User-deposited transactions][user-deposited], which are transactions derived from an L1 call to the [deposit + contract][deposit-contract]. + +## L1 Attributes Deposited Transaction + +[l1-attr-deposit]: glossary.md#l1-attributes-deposited-transaction + +An *L1 attributes deposited transaction* is [deposited transaction][deposited] that is used to register the L1 block +attributes (number, timestamp, ...) on L2 via a call to the [L1 Attributes Predeployed Contract][l1-attr-predeploy]. +That contract can then be used to read the attributes of the L1 block corresponding to the current L2 block. + +L1 attributes deposited transactions are specified in the [L1 Attributes Deposit][l1-attributes-tx-spec] section of the +deposits specification. + +[l1-attributes-tx-spec]: ./protocol/deposits.md#l1-attributes-deposited-transaction + +## User-Deposited Transaction + +[user-deposited]: glossary.md#user-deposited-transaction + +A *user-deposited transaction* is a [deposited transaction][deposited] which is derived from an L1 call to the [deposit + contract][deposit-contract] (a [depositing call][depositing-call]). + +User-deposited transactions are specified in the [Transaction Deposits][tx-deposits-spec] section of the deposits +specification. + +[tx-deposits-spec]: ./protocol/deposits.md#user-deposited-transactions + +## Depositing Call + +[depositing-call]: glossary.md#depositing-call + +A *depositing call* is an L1 call to the [deposit contract][deposit-contract], which will be derived to a +[user-deposited transaction][user-deposited] by the [rollup driver]. + +This call specifies all the data (destination, value, calldata, ...) for the deposited transaction. + +## Depositing Transaction + +[depositing-tx]: glossary.md#depositing-transaction + +A *depositing transaction* is an L1 transaction that makes one or more [depositing calls][depositing-call]. + +## Depositor + +[depositor]: glossary.md#depositor + +The *depositor* is the L1 account (contract or [EOA]) that makes (is the `msg.sender` of) the [depositing +call][depositing-call]. The *depositor* is **NOT** the originator of the depositing transaction (i.e. `tx.origin`). + +## Deposited Transaction Type + +[deposit-tx-type]: glossary.md#deposited-transaction-type + +The *deposited transaction type* is an [EIP-2718] [transaction type][transaction-type], which specifies the input fields +and correct handling of a [deposited transaction][deposited]. + +See the [corresponding section][spec-deposit-tx-type] of the deposits spec for more information. + +[spec-deposit-tx-type]: ./protocol/deposits.md#the-deposited-transaction-type + +## Deposit Contract + +[deposit-contract]: glossary.md#deposit-contract + +The *deposit contract* is an [L1] contract to which [EOAs][EOA] and contracts may send [deposits]. The deposits are +emitted as log records (in Solidity, these are called *events*) for consumption by [rollup nodes][rollup-node]. + +Advanced note: the deposits are not stored in calldata because they can be sent by contracts, in which case the calldata +is part of the *internal* execution between contracts, and this intermediate calldata is not captured in one of the +[Merkle Patricia Trie roots][mpt] included in the L1 block. + +cf. [Deposits Specification][deposits-spec] + +--- + +# Withdrawals + +> **TODO** expand this whole section to be clearer + +[withdrawals]: glossary.md#withdrawals + +In general, a withdrawal is a transaction sent from L2 to L1 that may transfer data and/or value. + +The term *withdrawal* is somewhat ambiguous as these "transactions" exist at multiple levels. In order to differentiate + between the L1 and L2 components of a withdrawal we introduce the following terms: + +- A *withdrawal initiating transaction* refers specifically to a transaction on L2 sent to the Withdrawals predeploy. +- A *withdrawal finalizing transaction* refers specifically to an L1 transaction which finalizes and relays the + withdrawal. + +## Relayer + +[relayer]: glossary.md#withdrawals + +An EOA on L1 which finalizes a withdrawal by submitting the data necessary to verify its inclusion on L2. + +## Finalization Period + +[finalization-period]: glossary.md#finalization-period + +The finalization period — sometimes also called *withdrawal delay* — is the minimum amount of time (in seconds) that +must elapse before a [withdrawal][withdrawals] can be finalized. + +The finalization period is necessary to afford sufficient time for [validators][validator] to make a +[ZK fault proof][zk-fault-proof]. + +> **TODO** specify current value for finalization period + +--- + +# Batch Submission + +[batch-submission]: glossary.md#batch-submission + +## Data Availability + + [data-availability]: glossary.md#data-availability + +Data availability is the guarantee that some data will be "available" (i.e. *retrievable*) during a reasonably long time +window. In Kroma's case, the data in question are [sequencer batches][sequencer-batch] that [validators][validator] +need in order to verify the sequencer's work and validate the L2 chain. + +The [finalization period][finalization-period] should be taken as the lower bound on the availability window, since +that is when data availability is the most crucial, as it is needed to perform a [ZK fault proof][zk-fault-proof]. + +"Availability" **does not** mean guaranteed long-term storage of the data. + +## Data Availability Provider + +[avail-provider]: glossary.md#data-availability-provider + +A data availability provider is a service that can be used to make data available. See the [Data +Availability][data-availability] for more information on what this means. + +Ideally, a good data availability provider provides strong *verifiable* guarantees of data availability + +Currently, the only supported data availability provider is Ethereum call data. [Ethereum data blobs][eip4844] will be +supported when they get deployed on Ethereum. + +## Sequencer Batch + +[sequencer-batch]: glossary.md#sequencer-batch + +A sequencer batch is list of L2 transactions (that were submitted to a sequencer) tagged with an [epoch +number](#sequencing-epoch) and an L2 block timestamp (which can trivially be converted to a block number, given our +block time is constant). + +Sequencer batches are part of the [L2 derivation inputs][deriv-inputs]. Each batch represents the inputs needed to build +**one** L2 block (given the existing L2 chain state) — except for the first block of each epoch, which also needs +information about deposits (cf. the section on [L2 derivation inputs][deriv-inputs]). + +## Channel + +[channel]: glossary.md#channel + +A channel is a sequence of [sequencer batches][sequencer-batch] (for sequential blocks) compressed together. The reason +to group multiple batches together is simply to obtain a better compression rate, hence reducing data availability +costs. + +A channel can be split in [frames][channel-frame] in order to be transmitted via [batcher +transactions][batcher-transaction]. The reason to split a channel into frames is that a channel might be too large to +include in a single batcher transaction. + +A channel is uniquely identified by its timestamp (UNIX time at which the channel was created) and a random value. See +the [Frame Format][frame-format] section of the L2 Chain Derivation specification for more information. + +[frame-format]: ./protocol/derivation.md#frame-format + +On the side of the [rollup node][rollup-node] (which is the consumer of channels), a channel is considered to be +*opened* if its final frame (explicitly marked as such) has not been read, or closed otherwise. + +## Channel Frame + +[channel-frame]: glossary.md#channel-frame + +A channel frame is a chunk of data belonging to a [channel]. [Batcher transactions][batcher-transaction] carry one or +multiple frames. The reason to split a channel into frames is that a channel might too large to include in a single +batcher transaction. + +## Batcher + +[batcher]: glossary.md#batcher + +A batcher is a software component (independant program) that is responsible to make channels available on a data +availability provider. The batcher communicates with the rollup node in order to retrieve the channels. The channels are +then made available using [batcher transactions][batcher-transaction]. + +> **TODO** In the future, we might want to make the batcher responsible for constructing the channels, letting it only +> query the rollup node for L2 block inputs. + +## Batcher Transaction + +[batcher-transaction]: glossary.md#batcher-transaction + +A batcher transaction is a transaction submitted by a [batcher] to a data availability provider, in order to make +channels available. These transactions carry one or more full frames, which may belong to different channels. A +channel's frame may be split between multiple batcher transactions. + +When submitted to Ethereum calldata, the batcher transaction's receiver must be the sequencer inbox address. The +transaction must also be signed by a recognized batch submitter account. The recognized batch submitter account +is stored in the [System Configuration][system-config]. + +## Channel Timeout + +[channel-timeout]: glossary.md#channel-timeout + +The channel timeout is a duration (in L1 blocks) during which [channel frames][channel-frame] may land on L1 within +[batcher transactions][batcher-transaction]. + +The acceptable time range for the frames of a [channel][channel] is `[channel_id.timestamp, channel_id.timestamp + +CHANNEL_TIMEOUT]`. The acceptable L1 block range for these frames are any L1 block whose timestamp falls inside this +time range. (Note that `channel_id.timetamp` must be lower than the L1 block timestamp of any L1 block in which frames +of the channel are seen, or else these frames are ignored.) + +The purpose of channel timeouts is dual: + +- Avoid keeping old unclosed channel data around forever (an unclosed channel is a channel whose final frame was not + sent). +- Bound the number of L1 blocks we have to look back in order to decode [sequencer batches][sequencer-batch] from + channels. This is particularly relevant during L1 re-orgs, see the [Resetting Pipeline][reset-pipeline] + section of the L2 Chain Derivation specifiction for more information. + +[reset-pipeline]: ./protocol/derivation.md#resetting-the-pipeline + +> **TODO** specify `CHANNEL_TIMEOUT` + +--- + +# L2 Chain Derivation + +[derivation]: glossary.md#L2-chain-derivation + +L2 chain derivation is a process that reads [L2 derivation inputs][deriv-inputs] from L1 in order to derive the L2 +chain. + +See the [L2 chain derivation specification][derivation-spec] for more details. + +## L2 Derivation Inputs + +[deriv-inputs]: glossary.md#l2-chain-derivation-inputs + +This term refers to data that is found in L1 blocks and is read by the [rollup node][rollup-node] to construct [payload +attributes][payload-attr]. + +L2 derivation inputs include: + +- L1 block attributes + - block number + - timestamp + - basefee +- [deposits] (as log data) +- [sequencer batches][sequencer-batch] (as transaction data) +- [System configuration][system-config] updates (as log data) + +## System Configuration + +[system-config]: glossary.md#system-configuration + +This term refers to the collection of dynamically configurable rollup parameters maintained +by the [`SystemConfig`](protocol/system-config.md) contract on L1 and read by the L2 [derivation] process. +These parameters enable keys to be rotated regularly and external cost parameters to be adjusted +without the network upgrade overhead of a hardfork. + +## Payload Attributes + +[payload-attr]: glossary.md#payload-attributes + +This term refers to an object that can be derived from [L2 chain derivation inputs][deriv-inputs] found on L1, which are +then passed to the [execution engine][execution-engine] to construct L2 blocks. + +The payload attributes object essentially encodes [a block without output properties][block]. + +Payload attributes are originally specified in the [Ethereum Engine API specification][engine-api], which we expand in +the [Execution Engine Specification][exec-engine]. + +See also the [Building The Payload Attributes][building-payload-attr] section of the rollup node specification. + +[building-payload-attr]: protocol/rollup-node.md#building-the-payload-attributes + +## L2 Genesis Block + +[l2-genesis]: glossary.md#l2-genesis-block + +The L2 genesis [block] is the first block of the [L2] chain in its current version. + +The state of the L2 genesis block contains [Predeployed contracts][predeploy]. + +The timestamp of the L2 genesis block must be a multiple of the [block time][block-time] (i.e. an even number, since the +block time is 2 seconds). + +When updating the rollup protocol to a new version, we may perform a *squash fork*, a process that entails the creation +of a new L2 genesis block. This new L2 genesis block will have block number `X + 1`, where `X` is the block number of +the final L2 block before the update. + +A squash fork is not to be confused with a *re-genesis*, a similar process that we employed in the past, which also +resets L2 block numbers, such that the new L2 genesis block has number 0. We will not employ re-genesis in the future. + +Squash forks are superior to re-geneses because they avoid duplicating L2 block numbers, which breaks a lot of external +tools. + +## L2 Chain Inception + +[l2-chain-inception]: glossary.md#L2-chain-inception + +The L1 block number at which the output roots for the [genesis block][l2-genesis] were proposed on the [output +oracle][output-oracle] contract. + +In the current implementation, this is the L1 block number at which the output oracle contract was deployed or upgraded. + +## Safe L2 Block + +[safe-l2-block]: glossary.md#safe-l2-block + +A safe L2 block is an L2 block that can be derived entirely from L1 by a [rollup node][rollup-node]. This can vary +between different nodes, based on their view of the L1 chain. + +## Safe L2 Head + +[safe-l2-head]: glossary.md#safe-l2-head + +The safe L2 head is the highest [safe L2 block][safe-l2-block] that a [rollup node][rollup-node] knows about. + +## Unsafe L2 Block + +[unsafe-l2-block]: glossary.md#unsafe-l2-block + +An unsafe L2 block is an L2 block that a [rollup node][rollup-node] knows about, but which was not derived from the L1 +chain. In sequencer mode, this will be a block sequenced by the sequencer itself. In syncer mode, this will be a +block acquired from the sequencer via [unsafe sync][unsafe-sync]. + +## Unsafe L2 Head + +[unsafe-l2-head]: glossary.md#unsafe-l2-head + +The unsafe L2 head is the highest [unsafe L2 block][unsafe-l2-block] that a [rollup node][rollup-node] knows about. + +## Unsafe Block Consolidation + +[consolidation]: glossary.md#unsafe-block-consolidation + +Unsafe block consolidation is the process through which the [rollup node][rollup-node] attempts to move the [safe-L2-head] a block forward, so that the oldest [unsafe L2 block][unsafe-l2-block] becomes the new safe L2 head. + +In order to perform consolidation, the node verifies that the [payload attributes][payload-attr] derived from the L1 +chain match the oldest unsafe L2 block exactly. + +See the [Engine Queue section][engine-queue] of the L2 chain derivatiaon spec for more information. + +[engine-queue]: ./protocol/derivation.md#engine-queue + +## Finalized L2 Head + +[finalized-l2-head]: glossary.md#finalized-l2-head + +The finalized L2 head is the highest L2 block that can be derived from _[finalized][finality]_ L1 blocks — i.e. L1 +blocks older than two L1 epochs (64 L1 [time slots][time-slot]). + +[finality]: https://hackmd.io/@prysmaticlabs/finality + +--- + +# Other L2 Chain Concepts + +## Address Aliasing + +[address-aliasing]: glossary.md#address-aliasing + +When a contract submits a [deposit][deposits] from L1 to L2, its address (as returned by `ORIGIN` and `CALLER`) will be +aliased with a modified representation of the address of a contract. + +- cf. [Deposit Specification](./protocol/deposits.md#address-aliasing) + +## Rollup Node + +[rollup-node]: glossary.md#rollup-node + +The rollup node is responsible for [deriving the L2 chain][derivation] from the L1 chain (L1 [blocks][block] and their +associated [receipts][receipt]). + +The rollup node can run either in *syncer* or *sequencer* mode. + +In sequencer mode, the rollup node receives L2 transactions from users, which it uses to create L2 blocks. These are +then submitted to a [data availability provider][avail-provider] via [batch submission][batch-submission]. The L2 chain +derivation then acts as a sanity check and a way to detect L1 chain [re-orgs][reorg]. + +In syncer mode, the rollup node performs derivation as indicated above, but is also able to "run ahead" of the L1 +chain by getting blocks directly from the sequencer, in which case derivation serves to validate the sequencer's +behaviour. + +A rollup node running in syncer mode is sometimes called *a replica*. + +> **TODO** expand this to include output root submission + +See the [rollup node specification][rollup-node-spec] for more information. + +## Rollup Driver + +[rollup driver]: glossary.md#rollup-driver + +The rollup driver is the [rollup node][rollup-node] component responsible for [deriving the L2 chain][derivation] +from the L1 chain (L1 [blocks][block] and their associated [receipts][receipt]). + +> **TODO** delete this entry, alongside its reference — can be replaced by "derivation process" or "derivation logic" +> where needed + +## L1 Attributes Predeployed Contract + +[l1-attr-predeploy]: glossary.md#l1-attributes-predeployed-contract + +A [predeployed contract][predeploy] on L2 that can be used to retrieve the L1 block attributes of L1 blocks with a given +block number or a given block hash. + +cf. [L1 Attributes Predeployed Contract Specification](./protocol/deposits.md#l1-attributes-predeployed-contract) + +## L2 Output Root + +[l2-output]: glossary.md#l2-output-root + +A 32 byte value which serves as a commitment to the current state of the L2 chain. + +cf. [Submitting L2 output commitments](./protocol/validator.md#submitting-l2-output-commitments) + +## L2 Output Oracle Contract + +[output-oracle]: glossary.md#l2-output-oracle-contract + +An L1 contract to which [L2 output roots][l2-output] are posted by the [validator]. + +> **TODO** expand + +## Validator Pool Contract + +[validator-pool-contract]: glossary.md#validator-pool-contract + +An [L1] contract that determines [validator] eligibility, selects the [validator] of next round, and manages bonding for +[L2 output roots][l2-output] submissions. + +## Colosseum Contract + +[colosseum-contract]: glossary.md#colosseum-contract + +An [L1] contract in which the [asserter][validator] and challenger argue with each other to fix +invalid [L2 output roots][l2-output]. + +## ZK Fault Proof + +[zk-fault-proof]: glossary.md#zk-fault-proof + +An on-chain *interactive* proof, performed by [validators][validator], that demonstrates that a [sequencer] provided +erroneous [output roots][l2-output] using zkEVM. + +## Security Council + +[security-council]: glossary.md#security-council + +A group of entities composed of trusted parties responsible for the security of blockchain, such as fault-proof system, +withdrawals, and contract upgrades. + +## Time Slot + +[time-slot]: glossary.md#time-slot + +On L2, there is a block every 2 second (this duration is known as the [block time][block-time]). + +We say that there is a "time slot" every multiple of 2s after the timestamp of the [L2 genesis block][l2-genesis]. + +On L1, post-[merge], the time slots are every 12s. However, an L1 block may not be produced for every time slot, in case +of even benign consensus issues. + +## Block Time + +[block-time]: glossary.md#block-time + +The L2 block time is 2 second, meaning there is an L2 block at every 2s [time slot][time-slot]. + +Post-[merge], it could be said the that L1 block time is 12s as that is the L1 [time slot][time-slot]. However, in +reality the block time is variable as some time slots might be skipped. + +Pre-merge, the L1 block time is variable, though it is on average 13s. + +## Unsafe Sync + +[unsafe-sync]: glossary.md#unsafe-sync + +Unsafe sync is the process through which a [validator][validator] learns about [unsafe L2 blocks][unsafe-l2-block] from +the [sequencer][sequencer]. + +These unsafe blocks will later need to be confirmed by the L1 chain (via [unsafe block consolidation][consolidation]). + +--- + +# Execution Engine Concepts + +## Execution Engine + +[execution-engine]: glossary.md#execution-engine + +The execution engine is responsible for executing transactions in blocks and computing the resulting state roots, +receipts roots and block hash. + +Both L1 (post-[merge]) and L2 have an execution engine. + +On L1, the executed blocks can come from L1 block synchronization; or from a block freshly minted by the execution +engine (using transactions from the L1 [mempool]), at the request of the L1 consensus layer. + +On L2, the executed blocks are freshly minted by the execution engine at the request of the [rollup node][rollup-node], +using transactions [derived from L1 blocks][derivation]. + +In these specifications, "execution engine" always refer to the L2 execution engine, unless otherwise specified. + +- cf. [Execution Engine Specification][exec-engine] + + +[deposits-spec]: ./protocol/deposits.md +[system-config]: ./protocol/system-config.md +[exec-engine]: ./protocol/exec-engine.md +[derivation-spec]: ./protocol/derivation.md +[rollup-node-spec]: ./protocol/rollup-node.md + + +[mpt-details]: https://github.com/norswap/nanoeth/blob/d4c0c89cc774d4225d16970aa44c74114c1cfa63/src/com/norswap/nanoeth/trees/patricia/README.md +[trie]: https://en.wikipedia.org/wiki/Trie +[bloom filter]: https://en.wikipedia.org/wiki/Bloom_filter +[Solidity events]: https://docs.soliditylang.org/en/latest/contracts.html?highlight=events#events +[nano-header]: https://github.com/norswap/nanoeth/blob/cc5d94a349c90627024f3cd629a2d830008fec72/src/com/norswap/nanoeth/blocks/BlockHeader.java#L22-L156 +[yellow]: https://ethereum.github.io/yellowpaper/paper.pdf +[engine-api]: https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#PayloadAttributesV2 +[merge]: https://ethereum.org/en/eth2/merge/ +[mempool]: https://www.quicknode.com/guides/defi/how-to-access-ethereum-mempool +[L1 consensus layer]: https://github.com/ethereum/consensus-specs/#readme +[cannon]: https://github.com/ethereum-optimism/cannon +[eip4844]: https://www.eip4844.com/ +[zkt-details]: https://github.com/kroma-network/zktrie +[RLP format]: https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp diff --git a/specs/introduction.md b/specs/introduction.md new file mode 100644 index 0000000..bebab75 --- /dev/null +++ b/specs/introduction.md @@ -0,0 +1,194 @@ +# Introduction + + + +[g-checkpoint-output]: glossary.md#checkpoint-output +[g-zk-fault-proof]: glossary.md#zk-fault-proof + + + +**Table of Contents** + +- [Foundations](#foundations) + - [Blockchain Trilemma](#blockchain-trilemma) + - [What is Ethereum scalability?](#what-is-ethereum-scalability) + - [What is a Layer2?](#what-is-a-layer2) + - [What is a Rollup?](#what-is-a-rollup) + - [What is EVM Equivalence?](#what-is-evm-equivalence) + - [🎶 All together now 🎶](#-all-together-now-) +- [Protocol Guarantees](#protocol-guarantees) +- [Network Participants](#network-participants) + - [Users](#users) + - [Sequencers](#sequencers) + - [Validators](#validators) +- [Key Interaction Diagrams](#key-interaction-diagrams) + - [Depositing and Sending Transactions](#depositing-and-sending-transactions) + - [Withdrawing](#withdrawing) + + + +Kroma is an _EVM equivalent_, _optimistic rollup_ protocol designed to _scale Ethereum_ while remaining maximally +compatible with existing Ethereum infrastructure. This document provides an overview of the protocol to provide context +for the rest of the specification. + +## Foundations + +### Blockchain Trilemma + +The core value of the blockchain is _decentralization_. It means anyone can take participation in the network without +any permission. Then an issue arises that because participants of the network can't be trusted. Thus, there needs +so-called consensus among the participants. To do consensus, more than 2/3 participants' agreements are required. +In other words, to re-org the chain, you need this much stake. In reality, it is very difficult to perform this attack. +That's why blockchain is _secure_. What matters is an individual participant needs to re-execute transaction(a.k.a state +transition) to do consensus, which requires state synchronization. This is an obstacle for blockchain to be _scalable_. +You can’t simply reduce the block time or increase the block size to achieve scalability. Because the block time gets +reduced or the block size gets increased abruptly, it raises the huddle. As a result, only some of centralized parties +will remain as participants and this will hinder _decentralization_ that is the reason blockchain exists for. + +### What is Ethereum scalability? + +Scaling Ethereum means increasing the number of useful transactions the Ethereum network can process. Ethereum's +limited resources, specifically bandwidth, computation, and storage, constrain the number of transactions which can be +processed on the network. Of the three resources, computation and storage are currently the most significant +bottlenecks. These bottlenecks limit the supply of transactions, leading to extremely high fees. Scaling ethereum and +reducing fees can be achieved by better utilizing bandwidth, computation and storage. + +### What is a Layer2? + +Layer 2 is a solution for Ethereum scalability. The key is to do state transition at not onchain but offchain. +This means the transaction execution is not part of the consensus. Why? because the more participants re-execute +transactions, the more expensive and the slower the computation will be. So small part of participants will do +state transition and they submit the result of state transition to Ethereum(or called Layer 1). Therefore, +Layer 2 inherits the security of Layer 1. To re-org L2 chain, you need to perform attack on Layer 1. + +### What is a Rollup? + +[Rollup](https://vitalik.ca/general/2021/01/05/rollup.html) is one kind of Layer 2 solutions. What differs from others +is to submit transactions to Layer 1. This is called data is available. So anyone can derive state and know how much +balance they own. Depending on how to prove state transition, the solution is divided into +Optimistic Rollup(ORU) and ZK Rollup(ZKR). + +Optimistic rollup is a rollup that needs a so-called fault proof to prove a valid state transition. As the name +suggests, it's based on the assumption that most of the state transitions are valid. Something is needed when +the state transition is wrong. This is where a fault proof kicks in. A fault proof is designed to be able to verify +that submitted state transition is invalid. This can reduce a tremendous fee as a result. But the only weakness of +optimistic rollup is the _dispute period_ that is enough to be long to accept any challenges. That's why this needs +7 days to withdraw. + +Opposite to Optimistic Rollup, ZK rollup is a rollup that needs a so-called validity proof. This proof is mathematically +very difficult to deceive verifier. Therefore, as soon as the proof is verified, the state transition can be immediately +finalized. + +### What is EVM Equivalence? + +[EVM Equivalence](https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306) is complete compliance +with the state transition function described in the Ethereum yellow paper, the formal definition of the protocol. By +conforming to the Ethereum standard across EVM equivalent rollups, smart contract developers can write once and deploy +anywhere. + +**NOTE:** At this moment, `OP_SELFDESTRUCT` is disabled. We are actively trying to fully cover the evm spec. But like +[eip-4758] suggests, we don't recommend users to rely on this opcode. + +[eip-4758]: https://eips.ethereum.org/EIPS/eip-4758 + +### 🎶 All together now 🎶 + +**Kroma is an _EVM equivalent_, _optimistic rollup_ protocol designed to _scale Ethereum_.** + +## Protocol Guarantees + +In order to scale Ethereum without sacrificing security, we must preserve 3 critical properties of Ethereum layer 1: +liveness, availability, and validity. + +1. **Liveness** - Anyone must be able to extend the rollup chain by sending transactions at any time. + - There are two ways transactions can be sent to the rollup chain: 1) via the sequencer, and 2) directly on layer 1. + The sequencer provides low latency & low cost transactions, while sending transactions directly to layer 1 provides + censorship resistance. +2. **Availability** - Anyone must be able to download the rollup chain. + - All information required to derive the chain is embedded into layer 1 blocks. That way as long as the layer 1 chain + is available, so is the rollup. +3. **Validity** - All transactions must be correctly executed and all withdrawals correctly processed. + - The rollup state and withdrawals are managed on an L1 contract called the `L2OutputOracle`. This oracle is + guaranteed to _only_ finalize correct (ie. valid) rollup block hashes given a **single honest validator** + assumption. If there is ever an invalid block hash asserted on layer 1, an honest validator will prove it is + invalid and win a bond. + +## Network Participants + +There are three actors in Kroma: users, sequencers, and validators. + +![Network Overview](./static/assets/network-participants-overview.svg) + +### Users + +At the heart of the network are users (us!). Users can: + +1. Deposit or withdraw arbitrary transactions on L2 by sending data to a contract on Ethereum mainnet. +2. Use EVM smart contracts on layer 2 by sending transactions to the sequencers. +3. View the status of transactions using block explorers provided by network validators. + +### Sequencers + +The sequencer is the primary block producer. +(At this moment, there is a single sequencer that is operated by Lightscale. +We have been actively discussing about how to open the role and how to separate block building role +from sequencer's role.) +There may be one sequencer **or** many using a consensus protocol. +In general, specifications may use "the sequencer" to be a stand-in term +for the consensus protocol operated by multiple sequencers. + +The sequencer: + +1. Accepts user off-chain transactions. +2. Observes on-chain transactions (primarily, deposit events coming from L1). +3. Consolidates both kinds of transactions into L2 blocks with a specific ordering. +4. Propagates consolidated L2 blocks to L1, by submitting two things as calldata to L1: + - The pending off-chain transactions accepted in step 1. + - Sufficient information about the ordering of the on-chain transactions to successfully reconstruct the blocks. +from step 3., purely by watching L1. + +The sequencer also provides access to block data as early as step 3., so that users may access real-time state in +advance of L1 confirmation if they so choose. + +### Validators + +Validators serve two purposes: + +1. Verifying rollup integrity by asserting [checkpoint output][g-checkpoint-output]. +2. Disputing invalid assertions by submitting [ZK fault proof][g-zk-fault-proof]. + +In order for the network to remain secure there must be **at least** one honest validator who is able to verify the +integrity of the rollup chain & serve blockchain data to users. + +## Key Interaction Diagrams + +The following diagrams demonstrate how protocol components are utilized during key user interactions in order to +provide context when diving into any particular component specification. + +### Depositing and Sending Transactions + +Users will often begin their L2 journey by depositing ETH from L1. +Once they have ETH to pay fees, they'll start sending transactions on L2. +The following diagram demonstrates this interaction and all key Kroma components which are or should be utilized: + +![Diagram of Depositing and Sending Transactions](./static/assets/sequencer-handling-deposits-and-transactions.svg) + +Links to components mentioned in this diagram: + +- [Rollup Node](./protocol/rollup-node.md) +- [Execution Engine](./protocol/exec-engine.md) +- [Batcher](./protocol/batcher.md) +- [L2 Output Oracle](./protocol/validator.md#l2-output-oracle-smart-contract) +- [Validator](./protocol/validator.md#submitting-l2-output-commitments) + +### Withdrawing + +Just as important as depositing, it is critical that users can withdraw from the rollup. Withdrawals are initiated by +normal transactions on L2, but then completed using a transaction on L1 after the dispute period has elapsed. + +![Diagram of Withdrawing](./static/assets/user-withdrawing-to-l1.svg) + +Links to components mentioned in this diagram: + +- [Kroma Portal](./protocol/deposits.md#deposit-contract) +- [L2 Output Oracle](./protocol/validator.md#l2-output-oracle-smart-contract) diff --git a/specs/meta/README.md b/specs/meta/README.md new file mode 100644 index 0000000..5039d5a --- /dev/null +++ b/specs/meta/README.md @@ -0,0 +1,8 @@ + +# Meta Processes + +This directory describes processes that we use to lint/test the specification. + +- [Linting](linting.md): how to lint source files. +- [Markdown Style Guide](markdown-style.md): how to format and structure Markdown files. +- [Devnet introduction](devnet.md): how to run a devnet. diff --git a/specs/meta/devnet.md b/specs/meta/devnet.md new file mode 100644 index 0000000..d4e625b --- /dev/null +++ b/specs/meta/devnet.md @@ -0,0 +1,68 @@ + +# Local Devnet Setup + + + +**Table of Contents** + +- [Local Devnet Setup](#local-devnet-setup) + - [Debugging VS Code](#debugging-vs-code) + + + +You can spin up a local devnet via `docker compose`. +For convenience, we have defined `make` targets to start and stop the devnet with a single command. +To run the devnet, you will need `docker` and `docker compose` installed. +Then, as a precondition, make sure that you have compiled the contracts by `cd`ing into `packages/contracts` +and running `pnpm` followed by `pnpm build`. You'll only need to do this if you change the contracts in the future. + +Then, run the following: + +```shell +> make devnet-up # starts the devnet +> make devnet-down # stops the devnet +> make devnet-clean # removes the devnet by deleting images and persistent volumes +``` + +L1 is accessible at `http://localhost:8545`, and L2 is accessible at `http://localhost:9545`. +Any Ethereum tool - Metamask, `seth`, etc. - can use these endpoints. +Note that you will need to specify the L2 chain ID manually if you use Metamask. The devnet's L2 chain ID is 901. + +The devnet comes with a pre-funded account you can use as a faucet: + +- Address: `0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266` +- Private key: `ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80` + +The faucet account exists on both L1 and L2. To deposit onto L2 from L1, you can use the `deposit` hardhat task. +Run the following from the `packages/contracts` directory: + +```shell +> npx hardhat deposit --amount-eth --to
+```` + +You'll need a `.env` with the following contents: + +```bash +L1_PROVIDER_URL=http://localhost:8545 +L2_PROVIDER_URL=http://localhost:9545 +PRIVATE_KEY=bf7604d9d3a1c7748642b1b7b05c2bd219c9faa91458b370f85e5a40f3b03af7 +``` + +The batch submitter uses the account below to submit batches to L1: + +- Address: `0xde3829a23df1479438622a08a116e8eb3f620bb5` +- Private key: `bf7604d9d3a1c7748642b1b7b05c2bd219c9faa91458b370f85e5a40f3b03af7` + +## Debugging VS Code + +We added a debugging configuration called 'Attach to devnet process' to .vscode/launch.json. Therefore, on Linux, you +can use VS Code to attach to the devnet process and perform debugging as shown in the figure below. + +![vscode_debug](../assets/vscode_debug.png) + +You might need to disable linux ptrace protection. + +```shell +> sudo su +> echo 0 > /proc/sys/kernel/yama/ptrace_scope +``` diff --git a/specs/meta/linting.md b/specs/meta/linting.md new file mode 100644 index 0000000..a0256d8 --- /dev/null +++ b/specs/meta/linting.md @@ -0,0 +1,68 @@ +# Linting + + + +**Table of Contents** + +- [Markdown](#markdown) +- [Go](#go) + + + +## Markdown + +See + +- [markdownlint rule reference](https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md) +- [exemple .markdownlint.json file](https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.jsonc) + +Justification for linting rules in [.markdownlint.json](/.markdownlint.json): + +- *line_length* (`!strict && stern`): don't trip up on url lines +- *no-blanks-blockquote*: enable multiple consecutive blockquotes separated by white lines +- *single-title*: enable reusing `

` for content +- *no-emphasis-as-heading*: enable emphasized paragraphs + +```shell +> pnpm # Install dependencies +> pnpm lint:specs:check # Run linter +> pnpm lint:specs:fix # Fix lint issues +> pnpm lint:specs:toc # Update TOC docs + +# Check links +> docker run --init -it -v `pwd`:/input lycheeverse/lychee --verbose --no-progress --exclude-loopback --exclude twitter.com --exclude-mail /input/SUMMARY.md "/input/specs/**/*.md" +``` + +To check links, you'll need to install [lychee]. The [version ran in CI][lychee-ci] is 0.8.1, but +you should install lychee 0.8.2 locally with `cargo install --version 0.8.2 lychee` (there are some +reported build problems with 0.8.1). + +You can install cargo (the Rust package manager) via [rustup]. + +[lychee]: https://github.com/lycheeverse/lychee +[lychee-ci]: https://github.com/lycheeverse/lychee-action/blob/f76b8412c668f78311212d16d33c4784a7d8762c/Dockerfile +[rustup]: https://www.rust-lang.org/tools/install + +To update the TOC, we run [doctoc], installed through the dev-dependencies in `package.json`. + +[doctoc]: https://github.com/thlorenz/doctoc + +## Go + +See + +- [golangci-lint docs](https://golangci-lint.run/usage/install/#local-installation) +- [golangci-lint github](https://github.com/golangci/golangci-lint) +- [github action github](https://github.com/golangci/golangci-lint-action) + +Justification for linting rules: + +- *asciicheck*: no symbol names with invisible unicode and such +- *goimports*: group local and external import + +```shell +# Install linter globally (should not affect go.mod) +> go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.48.0 +# run linter, add --fix option to fix problems (where supported) +> golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint --timeout 2m -e "errors.As" -e "errors.Is" ./... +``` diff --git a/specs/meta/markdown-style.md b/specs/meta/markdown-style.md new file mode 100644 index 0000000..536b99e --- /dev/null +++ b/specs/meta/markdown-style.md @@ -0,0 +1,53 @@ +# Markdown Style Guide + + + +**Table of Contents** + +- [Linting](#linting) +- [Links](#links) + - [Glossary](#glossary) +- [Internal (In-File) Links](#internal-in-file-links) + + + +## Linting + +Respect the [linting rules] (you can run the linter with `pnpm lint`). + +Notably: + +- lines should be < 120 characters long + - in practice, some of our files are justified at 100 characters, some at 120 + +[linting rules]: linting.md#markdown + +## Links + +In general: + +- Use link references preferentially. + - e.g. `[my text][link-ref]` and then on its own line `[link-ref]: https://mylink.com` + - e.g. `[my text]` and then on its own line: `[my text]: https://mylink.com` + - exceptions: where it fits neatly on a single line, in particular in lists of links +- Excepted for internal and glossary links (see below), add the link reference definition directly + after the paragraph where the link first appears. + +### Glossary + +- Use links to the [glossary] liberally. +- Include the references to all the glossary links at the top of the file, under the top-level + title. +- A glossary link reference should start with the `g-` prefix. This enables to see what links to the + glossary at a glance when editing the specification. + - e.g. `[g-block]: glossary.md#block` +- Example: [Rollup Node Specification source][rollup-node] + +[glossary]: ../glossary.md +[rollup-node]: ../rollup-node.md + +## Internal (In-File) Links + +If linking to another heading to the same file, add the link reference directly under that heading. +This makes it easier to keep the heading and the link in-sync, and signals that the heading is being +linked to from elsewhere. diff --git a/specs/meta/validator-deposit.md b/specs/meta/validator-deposit.md new file mode 100644 index 0000000..a02f8ae --- /dev/null +++ b/specs/meta/validator-deposit.md @@ -0,0 +1,86 @@ + + +# Validator Deposit Guide + + + +**Table of Contents** + +- [Deposit into `ValidatorPool`](#deposit-into-validatorpool) +- [Withdraw from `ValidatorPool`](#withdraw-from-validatorpool) +- [Try unbond in `ValidatorPool`](#try-unbond-in-validatorpool) + + + +We want the [validator](../../kroma-validator/) role to be decentralized. Like how the PoS mechanism works, to +achieve this, a validator needs to bond ETH at every [output +submission](../validator.md#submitting-l2-output-commitments). When submitting an output, the amount of bond +specified by the validator is automatically bonded from [the ETH the validator has deposited into the +`ValidatorPool`](#deposit-into-validatorpool). The bonded ETH is automatically unbonded when the submitted output is +finalized. The finalization of the output is checked when the next outputs are submitted, or if the finalization period +of the submitted output has passed, you can directly [trigger unbond by using the `unbond` command]( + #try-unbond-in-validatorpool). For more details about submitting an output as a validator, see +[here](../validator.md). + +This guide teaches you how to deposit, withdraw, or try to unbond in `ValidatorPool` via CLI. You can find the proxy +address of `ValidatorPool` on Sepolia [here](../../packages/contracts/deployments/sepolia/ValidatorPoolProxy.json), +on Mainnet TBD. + +## Deposit into `ValidatorPool` + +```shell +> cd kroma-validator +``` + +```shell +> go run ./cmd/main.go \ + --valpool-address \ # must be set + --l1-eth-rpc \ + --mnemonic \ + --hd-path \ + --rollup-rpc "" \ # empty required flags + --l2oo-address "" \ + --colosseum-address "" \ + --challenger.poll-interval 0s \ + deposit \ + --amount # must be set +``` + +## Withdraw from `ValidatorPool` + +```shell +> cd kroma-validator +``` + +```shell +> go run ./cmd/main.go \ + --valpool-address \ # must be set + --l1-eth-rpc \ + --mnemonic \ + --hd-path \ + --rollup-rpc "" \ # empty required flags + --l2oo-address "" \ + --colosseum-address "" \ + --challenger.poll-interval 0s \ + withdraw \ + --amount # must be set +``` + +## Try unbond in `ValidatorPool` + +```shell +> cd kroma-validator +``` + +```shell +> go run ./cmd/main.go \ + --valpool-address \ # must be set + --l1-eth-rpc \ + --mnemonic \ + --hd-path \ + --rollup-rpc "" \ # empty required flags + --l2oo-address "" \ + --colosseum-address "" \ + --challenger.poll-interval 0s \ + unbond +``` diff --git a/specs/protocol/batcher.md b/specs/protocol/batcher.md new file mode 100644 index 0000000..1d5c455 --- /dev/null +++ b/specs/protocol/batcher.md @@ -0,0 +1,39 @@ +# Batch Submitter + + + +**Table of Contents** + +- [Overview](#overview) + + + + +[g-block]: ../glossary.md#block +[g-l1]: ../glossary.md#layer-1-l1 +[g-l2]: ../glossary.md#layer-2-l2 +[g-validator]: ../glossary.md#validator +[derivation spec]: derivation.md + +## Overview + +The batch submitter, also referred to as the batcher, is the entity submitting the [L2][g-l2] sequencer data to +[L1][g-l1], to make it available for [validators][g-validator]. + +The format of the data transactions is defined in the [derivation spec]: +the data is constructed from L2 blocks in the reverse order as it is derived from data into L2 [blocks][g-block]. + +The timing, operation and transaction signing are implementation-specific: any data can be submitted at any time, +but only the data that matches the [derivation spec] rules will be valid from the validator perspective. + +The most minimal batcher implementation can be defined as a loop of the following operations: + +1. See if the `unsafe` L2 block number is past the `safe` block number: `unsafe` data needs to be submitted. +2. Iterate over all unsafe L2 blocks, skip any that were previously submitted. +3. Open a channel, buffer all the L2 block data to be submitted, + while applying the encoding and compression as defined in the [derivation spec]. +4. Pull frames from the channel to fill data transactions with, until the channel is empty. +5. Submit the data transactions to L1. + +The L2 view of safe/unsafe does not instantly update after data is submitted, nor when it gets confirmed on L1, +so special care may have to be taken to not duplicate data submissions. diff --git a/specs/protocol/bridges.md b/specs/protocol/bridges.md new file mode 100644 index 0000000..de8ec3d --- /dev/null +++ b/specs/protocol/bridges.md @@ -0,0 +1,122 @@ +# Standard Bridges + + + +**Table of Contents** + +- [Overview](#overview) +- [Token Depositing](#token-depositing) +- [Upgradeability](#upgradeability) + + + + + +[g-l1]: ../glossary.md#layer-1-l1 +[g-l2]: ../glossary.md#layer-2-l2 + +## Overview + +The standard bridges are responsible for allowing cross domain +ETH and ERC20 token transfers. They are built on top of the cross domain +messenger contracts and give a standard interface for depositing tokens. + +The `L2StandardBridge` is a predeploy contract located at +`0x4200000000000000000000000000000000000009`. + +```solidity +interface StandardBridge { + event ETHBridgeInitiated( + address indexed from, + address indexed to, + uint256 amount, + bytes extraData + ); + + event ETHBridgeFinalized( + address indexed from, + address indexed to, + uint256 amount, + bytes extraData + ); + + event ERC20BridgeInitiated( + address indexed localToken, + address indexed remoteToken, + address indexed from, + address to, + uint256 amount, + bytes extraData + ); + + event ERC20BridgeFinalized( + address indexed localToken, + address indexed remoteToken, + address indexed from, + address to, + uint256 amount, + bytes extraData + ); + + function bridgeERC20( + address _localToken, + address _remoteToken, + uint256 _amount, + uint32 _minGasLimit, + bytes calldata _extraData + ) external; + + function bridgeERC20To( + address _localToken, + address _remoteToken, + address _to, + uint256 _amount, + uint32 _minGasLimit, + bytes calldata _extraData + ) external; + + function bridgeETH( + uint32 _minGasLimit, + bytes calldata _extraData + ) payable external; + + function bridgeETHTo( + address _to, + uint32 _minGasLimit, + bytes calldata _extraData + ) payable external; + + function deposits(address, address) view external returns (uint256); + + function finalizeBridgeERC20( + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + bytes calldata _extraData + ) external; + + function finalizeBridgeETH( + address _from, + address _to, + uint256 _amount, + bytes calldata _extraData + ) payable external; + + function MESSENGER() view external returns (address); + + function OTHER_BRIDGE() view external returns (address); +} +``` + +## Token Depositing + +The `bridgeERC20` function is used to send a token from one domain to another +domain. A `KromaMintableERC20` token contract must exist on the remote +domain to be able to deposit tokens to that domain. One of these tokens can be +deployed using the `KromaMintableERC20Factory` contract. + +## Upgradeability + +Both the [L1][g-l1] and [L2][g-l2] standard bridges should be behind upgradable proxies. diff --git a/specs/protocol/contract-upgrades.md b/specs/protocol/contract-upgrades.md new file mode 100644 index 0000000..84dd4b9 --- /dev/null +++ b/specs/protocol/contract-upgrades.md @@ -0,0 +1,81 @@ +# Contract Upgrades + + + +**Table of Contents** + +- [Overview](#overview) +- [Upgrade By Governance](#upgrade-by-governance) + - [Interface](#interface) + + + +## Overview + +[Smart contract upgrades](https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies) are executed through the +[governance](https://docs.openzeppelin.com/contracts/4.x/api/governance) of the Security Council. The +authority to perform proxy upgrades lies with the Security Council's governor. +When a proposal for an upgrade is approved, it undergoes a mandatory 7-day timelock delay period before execution. + +## Upgrade By Governance + +When an on-chain proposal for a contract upgrade is submitted by a member of Security Council, the member votes on the +proposal during the voting period. Once a proposal is approved, the Security Council queues the proposal to the batch +to be executed with a 7-day timelock. After the timelock delay, the upgrade can be executed. + +### Interface + +```solidity +/** + * @dev Create a new proposal. Vote start after a delay specified by {IGovernor-votingDelay} and lasts for a + * duration specified by {IGovernor-votingPeriod}. + * + * Emits a {ProposalCreated} event. + */ + function propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) public virtual returns (uint256 proposalId); + +/** + * @dev Cast a vote + * + * Emits a {VoteCast} event. + */ + function castVote(uint256 proposalId, uint8 support) public virtual returns (uint256 balance); + +/** + * @notice Function to queue a proposal to the timelock. + * Added protocol for using custom time-lock zero delay for urgent situations. + * + * @param _targets The destination address that sends the message to. + * @param _values Amount of ether sent with the message. + * @param _calldatas The data portion of the message. + * @param _descriptionHash A hashed form of the description string. + * + * @return Whether the challenge was canceled. + */ + function queue( + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldatas, + bytes32 _descriptionHash + ) public virtual override returns (uint256); + +/** + * @dev Execute a successful proposal. This requires the quorum to be reached, the vote to be successful, and the + * deadline to be reached. + * + * Emits a {ProposalExecuted} event. + * + * Note: some module can modify the requirements for execution, for example by adding an additional timelock. + */ + function execute( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) public payable virtual returns (uint256 proposalId); +``` diff --git a/specs/protocol/deposits.md b/specs/protocol/deposits.md new file mode 100644 index 0000000..dce88a5 --- /dev/null +++ b/specs/protocol/deposits.md @@ -0,0 +1,361 @@ +# Deposits + + + +**Table of Contents** + +- [Overview](#overview) +- [The Deposited Transaction Type](#the-deposited-transaction-type) + - [Source hash computation](#source-hash-computation) + - [Kinds of Deposited Transactions](#kinds-of-deposited-transactions) + - [Validation and Authorization of Deposited Transactions](#validation-and-authorization-of-deposited-transactions) + - [Execution](#execution) + - [Nonce Handling](#nonce-handling) +- [Deposit Receipt](#deposit-receipt) +- [L1 Attributes Deposited Transaction](#l1-attributes-deposited-transaction) +- [Special Accounts on L2](#special-accounts-on-l2) + - [L1 Attributes Depositor Account](#l1-attributes-depositor-account) + - [L1 Attributes Predeployed Contract](#l1-attributes-predeployed-contract) + - [L1 Attributes Predeployed Contract: Reference Implementation](#l1-attributes-predeployed-contract-reference-implementation) +- [User-Deposited Transactions](#user-deposited-transactions) + - [Deposit Contract](#deposit-contract) + - [Address Aliasing](#address-aliasing) + - [Deposit Contract Implementation: Kroma Portal](#deposit-contract-implementation-kroma-portal) + + + + +[g-transaction-type]: ../glossary.md#transaction-type +[g-derivation]: ../glossary.md#L2-chain-derivation +[g-deposited]: ../glossary.md#deposited +[g-deposits]: ../glossary.md#deposits +[g-l1-attr-deposit]: ../glossary.md#l1-attributes-deposited-transaction +[g-user-deposited]: ../glossary.md#user-deposited-transaction +[g-eoa]: ../glossary.md#eoa +[g-exec-engine]: ../glossary.md#execution-engine +[g-validator-reward]: ../glossary.md#validator-reward + +## Overview + +[Deposited transactions][g-deposited], also known as [deposits][g-deposits] are transactions which +are initiated on L1, and executed on L2. This document outlines a new [transaction +type][g-transaction-type] for deposits. It also describes how deposits are initiated on L1, along +with the authorization and validation conditions on L2. + +**Vocabulary note**: *deposited transaction* refers specifically to an L2 transaction, while +*deposit* can refer to the transaction at various stages (for instance when it is deposited on L1). + +## The Deposited Transaction Type + +[deposited-tx-type]: #the-deposited-transaction-type + +[Deposited transactions][g-deposited] have the following notable distinctions from existing +transaction types: + +1. They are derived from Layer 1 blocks, and must be included as part of the protocol. +2. They do not include signature validation (see [User-Deposited Transactions][user-deposited] + for the rationale). +3. They buy their L2 gas on L1 and, as such, the L2 gas is not refundable. + +We define a new [EIP-2718] compatible transaction type with the prefix `0x7E` to represent a deposit transaction. + +A deposit has the following fields +(rlp encoded in the order they appear here): + +[EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 + +- `bytes32 sourceHash`: the source-hash, uniquely identifies the origin of the deposit. +- `address from`: The address of the sender account. +- `address to`: The address of the recipient account, or the null (zero-length) address if the + deposited transaction is a contract creation. +- `uint256 mint`: The ETH value to mint on L2. +- `uint256 value`: The ETH value to send to the recipient account. +- `bytes data`: The input data. +- `uint64 gasLimit`: The gasLimit for the L2 transaction. + +In contrast to [EIP-155] transactions, this transaction type: + +- Does not include a `nonce`, since it is identified by the `sourceHash`. + API responses still include a `nonce` attribute: + - the `nonce` is set to the `depositNonce` attribute of the corresponding transaction receipt. +- Does not include signature information, and makes the `from` address explicit. + API responses contain zeroed signature `v`, `r`, `s` values for backwards compatibility. +- Includes new `sourceHash`, `from`, and `mint` attributes. + API responses contain these as additional fields. + +[EIP-155]:https://eips.ethereum.org/EIPS/eip-155 + +We select `0x7E` because transaction type identifiers are currently allowed to go up to `0x7F`. +Picking a high identifier minimizes the risk that the identifier will be used be claimed by another +transaction type on the L1 chain in the future. We don't pick `0x7F` itself in case it becomes used +for a variable-length encoding scheme. + +### Source hash computation + +The `sourceHash` of a deposit transaction is computed based on the origin: + +- User-deposited: + `keccak256(bytes32(uint256(0)), keccak256(l1BlockHash, bytes32(uint256(l1LogIndex))))`. + Where the `l1BlockHash`, and `l1LogIndex` all refer to the inclusion of the deposit log event on L1. + `l1LogIndex` is the index of the deposit event log in the combined list of log events of the block. +- L1 attributes deposited: + `keccak256(bytes32(uint256(1)), keccak256(l1BlockHash, bytes32(uint256(seqNumber))))`. + Where `l1BlockHash` refers to the L1 block hash of which the info attributes are deposited. + And `seqNumber = l2BlockNum - l2EpochStartBlockNum`, + where `l2BlockNum` is the L2 block number of the inclusion of the deposit tx in L2, + and `l2EpochStartBlockNum` is the L2 block number of the first L2 block in the epoch. + +Without a `sourceHash` in a deposit, two different deposited transactions could have the same exact hash. + +The outer `keccak256` hashes the actual uniquely identifying information with a domain, +to avoid collisions between different types of sources. + +We do not use the sender's nonce to ensure uniqueness because this would require an extra L2 EVM state read from the +[execution engine][g-exec-engine] during block-derivation. + +### Kinds of Deposited Transactions + +Although we define only one new transaction type, we can distinguish between two kinds of deposited +transactions, based on their positioning in the L2 block: + +1. The first transaction MUST be a [L1 attributes deposited transaction][l1-attr-deposit], followed by +2. an array of zero-or-more [user-deposited transactions][user-deposited] + submitted to the deposit feed contract on L1 (called `KromaPortal`). + User-deposited transactions are only present in the first block of a L2 epoch. + +We only define a single new transaction type in order to minimize modifications to L1 client +software, and complexity in general. + +### Validation and Authorization of Deposited Transactions + +[authorization]: #validation-and-authorization-of-deposited-transaction + +As noted above, the deposited transaction type does not include a signature for validation. Rather, +authorization is handled by the [L2 chain derivation][g-derivation] process, which when correctly +applied will only derive transactions with a `from` address attested to by the logs of the [L1 +deposit contract][deposit-contract]. + +### Execution + +In order to execute a deposited transaction: + +First, the balance of the `from` account MUST be increased by the amount of `mint`. +This is unconditional, and does not revert on deposit failure. + +Then, the execution environment for a deposited transaction is initialized based on the +transaction's attributes, in exactly the same manner as it would be for an EIP-155 transaction. + +The deposit transaction is processed exactly like a type-3 (EIP-1559) transaction, with the exception of: + +- No fee fields are verified: the deposit does not have any, as it pays for gas on L1. +- No `nonce` field is verified: the deposit does not have any, it's uniquely identified by its `sourceHash`. +- No access-list is processed: the deposit has no access-list, and it is thus processed as if the access-list is empty. +- No check if `from` is an Externally Owner Account (EOA): the deposit is ensured not to be an EOA through L1 address + masking, this may change in future L1 contract-deployments to e.g. enable an account-abstraction like mechanism. +- No gas is refunded as ETH. (either by not refunding or utilizing the fact the gas-price of the deposit is `0`) +- No transaction priority fee is charged. No payment is made to the block fee-recipient. +- No L1-cost fee is charged, as deposits are derived from L1 and do not have to be submitted as data back to it. +- No base fee is charged. The total base fee accounting does not change. + +Note that this includes contract-deployment behavior like with regular transactions, +and gas metering is the same (with the exception of fee related changes above), including metering of intrinsic gas. + +Any non-EVM state-transition error emitted by the EVM execution is processed in a special way: + +- It is transformed into an EVM-error: + i.e. the deposit will always be included, but its receipt will indicate a failure + if it runs into a non-EVM state-transition error, e.g. failure to transfer the specified + `value` amount of ETH due to insufficient account-balance. +- The world state is rolled back to the start of the EVM processing, after the minting part of the deposit. +- The `nonce` of `from` in the world state is incremented by 1, making the error equivalent to a native EVM failure. + Note that a previous `nonce` increment may have happened during EVM processing, but this would be rolled back first. + +Finally, after the above processing, the execution post-processing runs the same: +i.e. the gas pool and receipt are processed identical to a regular transaction. +The receipt of deposit transactions is extended with an additional `depositNonce` value, storing the `nonce` value +of the `from` sender as registered *before* the EVM processing. + +Note that the gas used as stated by the execution output is subtracted from the gas pool. + +Note for application developers: because `CALLER` and `ORIGIN` are set to `from`, the +semantics of using the `tx.origin == msg.sender` check will not work to determine whether +or not a caller is an EOA during a deposit transaction. Instead, the check could only be useful for +identifying the first call in the L2 deposit transaction. However this check does still satisfy +the common case in which developers are using this check to ensure that the `CALLER` is unable to +execute code before and after the call. + +#### Nonce Handling + +Despite the lack of signature validation, we still increment the nonce of the `from` account when a +deposit transaction is executed. In the context of a deposit-only roll up, this is not necessary +for transaction ordering or replay prevention, however it maintains consistency with the use of +nonces during [contract creation][create-nonce]. It may also simplify integration with downstream +tooling (such as wallets and block explorers). + +[create-nonce]: https://github.com/ethereum/execution-specs/blob/617903a8f8d7b50cf71bf1aa733c37897c8d75c1/src/ethereum/frontier/utils/address.py#L40 + +## Deposit Receipt + +Transaction receipts use standard typing as per [EIP-2718]. +The Deposit transaction receipt type is equal to a regular receipt, +but extended with an optional `depositNonce` field. + +The RLP-encoded consensus-enforced fields are: + +- `postStateOrStatus` (standard): this contains the transaction status, see [EIP-658]. +- `cumulativeGasUsed` (standard): gas used in the block thus far, including this transaction. + - The actual gas used is derived from the difference in `CumulativeGasUsed` with the previous transaction. +- `bloom` (standard): bloom filter of the transaction logs. +- `logs` (standard): log events emitted by the EVM processing. +- `depositNonce` (unique extension): Optional field. The deposit transaction persists the nonce used during execution. +- `depositNonceVersion` (unique extension): Optional field. The value must be 1 if the field is present + - Before Canyon, these `depositNonce` & `depositNonceVersion` fields must always be omitted. + - With Canyon, these `depositNonce` & `depositNonceVersion` fields must always be included. + +The receipt API responses utilize the receipt changes for more accurate response data: + +- The `depositNonce` is included in the receipt JSON data in API responses +- For contract-deployments (when `to == null`), the `depositNonce` helps derive the correct `contractAddress` meta-data, + instead of assuming the nonce was zero. +- The `cumulativeGasUsed` accounts for the actual gas usage, as metered in the EVM processing. + +[EIP-658]: https://eips.ethereum.org/EIPS/eip-658 + +## L1 Attributes Deposited Transaction + +[l1-attr-deposit]: #l1-attributes-deposited-transaction + +An [L1 attributes deposited transaction][g-l1-attr-deposit] is a deposit transaction sent to the [L1 +attributes predeployed contract][predeploy]. + +This transaction MUST have the following values: + +1. `from` is `0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001` (the address of the +[L1 Attributes depositor account][depositor-account]) +2. `to` is `0x4200000000000000000000000000000000000002` (the address of the [L1 attributes predeployed + contract][predeploy]). +3. `mint` is `0`. +4. `value` is `0`. +5. `gasLimit` is set to `1,000,000`. +6. `data` is an [ABI] encoded call to the [L1 attributes predeployed contract][predeploy]'s + `setL1BlockValues()` function with correct values associated with the corresponding L1 block (cf. + [reference implementation][l1-attr-ref-implem]). + +This system-initiated transaction for L1 attributes is not charged any ETH for its allocated `gasLimit`, +as it is effectively part of the state-transition processing. + +## Special Accounts on L2 + +The L1 attributes deposit transaction involves two special purpose accounts: + +1. The L1 attributes depositor account +2. The L1 attributes predeployed contract + +### L1 Attributes Depositor Account + +[depositor-account]: #l1-attributes-depositor-account + +The depositor account is an [EOA][g-eoa] with no known private key. It has the address +`0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001`. Its value is returned by the `CALLER` and `ORIGIN` +opcodes during execution of the L1 attributes deposited transaction. + +### L1 Attributes Predeployed Contract + +[predeploy]: #l1-attributes-predeployed-contract + +A predeployed contract on L2 at address `0x4200000000000000000000000000000000000002`, which holds +certain block variables from the corresponding L1 block in storage, so that they may be accessed +during the execution of the subsequent deposited transactions. + +The predeploy stores the following values: + +- L1 block attributes: + - `number` (`uint64`) + - `timestamp` (`uint64`) + - `basefee` (`uint256`) + - `hash` (`bytes32`) +- `sequenceNumber` (`uint64`): This equals the L2 block number relative to the start of the epoch, + i.e. the L2 block distance to the L2 block height that the L1 attributes last changed, + and reset to 0 at the start of a new epoch. +- System configurables tied to the L1 block, see [System configuration specification](./system-config.md): + - `batcherHash` (`bytes32`): A versioned commitment to the batch-submitter(s) currently operating. + - `overhead` (`uint256`): The L1 fee overhead to apply to L1 cost computation of transactions in this L2 block. + - `scalar` (`uint256`): The L1 fee scalar to apply to L1 cost computation of transactions in this L2 block. +- Parameters to calculate a validator reward. + - `validatorRewardScalar` (`uint256`): A number between 0 and 10000. A [validator reward][g-validator-reward] is + calculated with the expression. + +The contract implements an authorization scheme, such that it only accepts state-changing calls from +the [depositor account][depositor-account]. + +The contract has the following solidity interface, and can be interacted with according to the +[contract ABI specification][ABI]. + +[ABI]: https://docs.soliditylang.org/en/v0.8.10/abi-spec.html + +#### L1 Attributes Predeployed Contract: Reference Implementation + +[l1-attr-ref-implem]: #l1-attributes-predeployed-contract-reference-implementation + +A reference implementation of the L1 Attributes predeploy contract can be found in [L1Block.sol]. + +[L1Block.sol]: https://github.com/kroma-network/kroma/blob/main/packages/contracts/contracts/L2/L1Block.sol + +After running `pnpm build` in the `packages/contracts` directory, the bytecode to add to the genesis +file will be located in the `deployedBytecode` field of the build artifacts file at +`/packages/contracts/artifacts/contracts/L2/L1Block.sol/L1Block.json`. + +## User-Deposited Transactions + +[user-deposited]: #user-deposited-transactions + +[User-deposited transactions][g-user-deposited] are [deposited transactions][deposited-tx-type] +generated by the [L2 Chain Derivation][g-derivation] process. The content of each user-deposited +transaction are determined by the corresponding `TransactionDeposited` event emitted by the +[deposit contract][deposit-contract] on L1. + +1. `from` is unchanged from the emitted value (though it may + have been transformed to an alias in `KromaPortal`, the deposit feed contract). +2. `to` is any 20-byte address (including the zero address) + - In case of a contract creation (cf. `isCreation`), this address is set to `null`. +3. `mint` is set to the emitted value. +4. `value` is set to the emitted value. +5. `gaslimit` is unchanged from the emitted value. It must be at least 21000. +6. `isCreation` is set to `true` if the transaction is a contract creation, `false` otherwise. +7. `data` is unchanged from the emitted value. Depending on the value of `isCreation` it is handled + as either calldata or contract initialization code. + +### Deposit Contract + +[deposit-contract]: #deposit-contract + +The deposit contract is deployed to L1. Deposited transactions are derived from the values in +the `TransactionDeposited` event(s) emitted by the deposit contract. + +The deposit contract is responsible for maintaining the [guaranteed gas market](./guaranteed-gas-market.md), +charging deposits for gas to be used on L2, and ensuring that the total amount of guaranteed +gas in a single L1 block does not exceed the L2 block gas limit. + +The deposit contract handles two special cases: + +1. A contract creation deposit, which is indicated by setting the `isCreation` flag to `true`. + In the event that the `to` address is non-zero, the contract will revert. +2. A call from a contract account, in which case the `from` value is transformed to its L2 + [alias][address-aliasing]. + +#### Address Aliasing + +[address-aliasing]: #address-aliasing + +If the caller is a contract, the address will be transformed by adding +`0x1111000000000000000000000000000000001111` to it. The math is `unchecked` and done on a +Solidity `uint160` so the value will overflow. This prevents attacks in which a +contract on L1 has the same address as a contract on L2 but doesn't have the same code. We can safely ignore this +for EOAs because they're guaranteed to have the same "code" (i.e. no code at all). This also makes +it possible for users to interact with contracts on L2 even when the Sequencer is down. + +#### Deposit Contract Implementation: Kroma Portal + +A reference implementation of the deposit contract can be found in [KromaPortal.sol]. + +[KromaPortal.sol]: https://github.com/kroma-network/kroma/blob/main/packages/contracts/contracts/L1/KromaPortal.sol diff --git a/specs/protocol/derivation.md b/specs/protocol/derivation.md new file mode 100644 index 0000000..f9449c6 --- /dev/null +++ b/specs/protocol/derivation.md @@ -0,0 +1,962 @@ +# L2 Chain Derivation Specification + + + +**Table of Contents** + +- [Overview](#overview) + - [Eager Block Derivation](#eager-block-derivation) +- [Batch Submission](#batch-submission) + - [Sequencing & Batch Submission Overview](#sequencing--batch-submission-overview) + - [Batch Submission Wire Format](#batch-submission-wire-format) + - [Batcher Transaction Format](#batcher-transaction-format) + - [Frame Format](#frame-format) + - [Channel Format](#channel-format) + - [Batch Format](#batch-format) +- [Architecture](#architecture) + - [L2 Chain Derivation Pipeline](#l2-chain-derivation-pipeline) + - [L1 Traversal](#l1-traversal) + - [L1 Retrieval](#l1-retrieval) + - [Frame Queue](#frame-queue) + - [Channel Bank](#channel-bank) + - [Pruning](#pruning) + - [Timeouts](#timeouts) + - [Reading](#reading) + - [Loading frames](#loading-frames) + - [Channel Reader (Batch Decoding)](#channel-reader-batch-decoding) + - [Batch Queue](#batch-queue) + - [Payload Attributes Derivation](#payload-attributes-derivation) + - [Engine Queue](#engine-queue) + - [Engine API usage](#engine-api-usage) + - [Forkchoice synchronization](#forkchoice-synchronization) + - [L1-consolidation: payload attributes matching](#l1-consolidation-payload-attributes-matching) + - [L1-sync: payload attributes processing](#l1-sync-payload-attributes-processing) + - [Processing unsafe payload attributes](#processing-unsafe-payload-attributes) + - [Resetting the Pipeline](#resetting-the-pipeline) + - [Finding the sync starting point](#finding-the-sync-starting-point) + - [Resetting derivation stages](#resetting-derivation-stages) + - [About reorgs Post-Merge](#about-reorgs-post-merge) +- [Deriving Payload Attributes](#deriving-payload-attributes) + - [Deriving the Transaction List](#deriving-the-transaction-list) + - [Building Individual Payload Attributes](#building-individual-payload-attributes) + + + + +[g-derivation]: ../glossary.md#L2-chain-derivation +[g-payload-attr]: ../glossary.md#payload-attributes +[g-block]: ../glossary.md#block +[g-exec-engine]: ../glossary.md#execution-engine +[g-reorg]: ../glossary.md#chain-re-organization +[g-receipts]: ../glossary.md#receipt +[g-inception]: ../glossary.md#L2-chain-inception +[g-deposit-contract]: ../glossary.md#deposit-contract +[g-deposited]: ../glossary.md#deposited-transaction +[g-l1-attr-deposit]: ../glossary.md#l1-attributes-deposited-transaction +[g-user-deposited]: ../glossary.md#user-deposited-transaction +[g-deposits]: ../glossary.md#deposits +[g-deposit-contract]: ../glossary.md#deposit-contract +[g-l1-attr-predeploy]: ../glossary.md#l1-attributes-predeployed-contract +[g-depositing-call]: ../glossary.md#depositing-call +[g-depositing-transaction]: ../glossary.md#depositing-transaction +[g-sequencing]: ../glossary.md#sequencing +[g-sequencer]: ../glossary.md#sequencer +[g-sequencing-epoch]: ../glossary.md#sequencing-epoch +[g-sequencing-window]: ../glossary.md#sequencing-window +[g-sequencer-batch]: ../glossary.md#sequencer-batch +[g-l2-genesis]: ../glossary.md#l2-genesis-block +[g-l2-chain-inception]: ../glossary.md#L2-chain-inception +[g-l2-genesis-block]: ../glossary.md#l2-genesis-block +[g-batcher-transaction]: ../glossary.md#batcher-transaction +[g-avail-provider]: ../glossary.md#data-availability-provider +[g-batcher]: ../glossary.md#batcher +[g-l2-output]: ../glossary.md#l2-output-root +[g-channel]: ../glossary.md#channel +[g-channel-frame]: ../glossary.md#channel-frame +[g-rollup-node]: ../glossary.md#rollup-node +[g-channel-timeout]: ../glossary.md#channel-timeout +[g-block-time]: ../glossary.md#block-time +[g-time-slot]: ../glossary.md#time-slot +[g-consolidation]: ../glossary.md#unsafe-block-consolidation +[g-safe-l2-head]: ../glossary.md#safe-l2-head +[g-safe-l2-block]: ../glossary.md#safe-l2-block +[g-unsafe-l2-head]: ../glossary.md#unsafe-l2-head +[g-unsafe-l2-block]: ../glossary.md#unsafe-l2-block +[g-unsafe-sync]: ../glossary.md#unsafe-sync +[g-l1-origin]: ../glossary.md#l1-origin +[g-deposit-tx-type]: ../glossary.md#deposited-transaction-type +[g-finalized-l2-head]: ../glossary.md#finalized-l2-head +[g-system-config]: ../glossary.md#system-configuration +[g-zk-fault-proof]: ../glossary.md#zk-fault-proof + +# Overview + +> **Note** the following assumes a single sequencer. In the future, the design will be adapted to +> accommodate multiple such entities. + +[L2 chain derivation][g-derivation] — deriving L2 [blocks][g-block] from L1 data — is one of the main responsibilities +of the [rollup node][g-rollup-node], both in syncer mode, and in sequencer mode (where derivation acts as a sanity +check on sequencing, and enables detecting L1 chain [re-organizations][g-reorg]). + +The L2 chain is derived from the L1 chain. In particular, each L1 block following [L2 chain +inception][g-l2-chain-inception] is mapped to a [sequencing epoch][g-sequencing-epoch] comprising +at least one L2 block. Each L2 block belongs to exactly one epoch, and we call the corresponding L1 +block its [L1 origin][l1-origin]. The epoch's number equals that of its L1 origin block. + +To derive the L2 blocks of epoch number `E`, we need the following inputs: + +- L1 blocks in the range `[E, E + SWS)`, called the [sequencing window][g-sequencing-window] of the epoch, and `SWS` + the sequencing window size. (Note that sequencing windows overlap.) +- [Batcher transactions][g-batcher-transaction] from blocks in the sequencing window. + - These transactions allow us to reconstruct the epoch's [sequencer batches][g-sequencer-batch], each of + which will produce one L2 block. Note that: + - The L1 origin will never contain any data needed to construct sequencer batches since + each batch [must contain](#batch-format) the L1 origin hash. + - An epoch may have no sequencer batches. +- [Deposits][g-deposits] made in the L1 origin (in the form of events emitted by the [deposit + contract][g-deposit-contract]). +- L1 block attributes from the L1 origin (to derive the [L1 attributes deposited transaction][g-l1-attr-deposit]). +- The state of the L2 chain after the last L2 block of the previous epoch, or the [L2 genesis state][g-l2-genesis] + if `E` is the first epoch. + +To derive the whole L2 chain from scratch, we start with the [L2 genesis state][g-l2-genesis] and +the [L2 genesis block] as the first L2 block. We then derive L2 blocks from each epoch in order, +starting at the first L1 block following [L2 chain inception][g-l2-chain-inception]. Refer to the +[Architecture section][architecture] for more information on how we implement this in practice. +The L2 chain may contain pre-Bedrock history, but the L2 genesis here refers to the Bedrock L2 +genesis block. + +Each L2 `block` with origin `l1_origin` is subject to the following constraints (whose values are +denominated in seconds): + +- `block.timestamp = prev_l2_timestamp + l2_block_time` + - `prev_l2_timestamp` is the timestamp of the L2 block immediately preceeding this one. If there + is no preceeding block, then this is the genesis block, and its timestamp is explicitly + specified. + - `l2_block_time` is a configurable parameter of the time between L2 blocks (2s on Optimism). + +- `l1_origin.timestamp <= block.timestamp <= max_l2_timestamp`, where + - `max_l2_timestamp = max(l1_origin.timestamp + max_sequencer_drift, prev_l2_timestamp + l2_block_time)` + - `max_sequencer_drift` is a configurable parameter that bounds how far the sequencer can get ahead of + the L1. + +Finally, each epoch must have at least one L2 block. + +The first constraint means there must be an L2 block every `l2_block_time` seconds following L2 +chain inception. + +The second constraint ensures that an L2 block timestamp never precedes its L1 origin timestamp, +and is never more than `max_sequencer_drift` ahead of it, except only in the unusual case where it +might prohibit an L2 block from being produced every l2_block_time seconds. (Such cases might arise +for example under a proof-of-work L1 that sees a period of rapid L1 block production.) In either +case, the sequencer enforces `len(batch.transactions) == 0` while `max_sequencer_drift` is +exceeded. See [Batch Queue](#batch-queue) for more details. + +The final requirement that each epoch must have at least one L2 block ensures that all relevant +information from the L1 (e.g. deposits) is represented in the L2, even if it has no sequencer +batches. + +Post-merge, Ethereum has a fixed 12s [block time][g-block-time], though some slots can be +skipped. Under a 2s L2 block time, we thus expect each epoch to typically contain `12/2 = 6` L2 +blocks. The sequencer will however produce bigger epochs in order to maintain liveness in case of +either a skipped slot on the L1 or a temporary loss of connection to it. For the lost connection +case, smaller epochs might be produced after the connection was restored to keep L2 timestamps from +drifting further and further ahead. + +## Eager Block Derivation + +Deriving an L2 block requires that we have constructed its sequencer batch and derived all L2 +blocks and state updates prior to it. This means we can typically derive the L2 blocks of an epoch +*eagerly* without waiting on the full sequencing window. The full sequencing window is required +before derivation only in the very worst case where some portion of the sequencer batch for the +first block of the epoch appears in the very last L1 block of the window. Note that this only +applies to *block* derivation. Sequencer batches can still be derived and tentatively queued +without deriving blocks from them. + +------------------------------------------------------------------------------------------------------------------------ + +# Batch Submission + +## Sequencing & Batch Submission Overview + +The [sequencer][g-sequencer] accepts L2 transactions from users. It is responsible for building blocks out of these. For +each such block, it also creates a corresponding [sequencer batch][g-sequencer-batch]. It is also responsible for +submitting each batch to a [data availability provider][g-avail-provider] (e.g. Ethereum calldata), which it does via +its [batcher][g-batcher] component. + +The difference between an L2 block and a batch is subtle but important: the block includes an L2 state root, whereas the +batch only commits to transactions at a given L2 timestamp (equivalently: L2 block number). A block also includes a +reference to the previous block (\*). + +(\*) This matters in some edge case where a L1 reorg would occur and a batch would be reposted to the L1 chain but not +the preceding batch, whereas the predecessor of an L2 block cannot possibly change. + +This means that even if the sequencer applies a state transition incorrectly, the transactions in the batch will still +be considered part of the canonical L2 chain. Batches are still subject to validity checks (i.e. they have to be encoded +correctly), and so are individual transactions within the batch (e.g. signatures have to be valid). Invalid batches and +invalid individual transactions within an otherwise valid batch are discarded by correct nodes. + +If the validator applies a state transition incorrectly and posts an [output root][g-l2-output], then this output root +will be incorrect. The incorrect output root will be challenged by a [ZK fault proof][g-zk-fault-proof], and once proven +incorrect, it will be deleted. + +Refer to the [Batch Submission specification][batcher-spec] for more information. + +[batcher-spec]: batcher.md + +## Batch Submission Wire Format + +[wire-format]: #batch-submission-wire-format + +Batch submission is closely tied to L2 chain derivation because the derivation process must decode the batches that have +been encoded for the purpose of batch submission. + +The [batcher][g-batcher] submits [batcher transactions][g-batcher-transaction] to a [data availability +provider][g-avail-provider]. These transactions contain one or multiple [channel frames][g-channel-frame], which are +chunks of data belonging to a [channel][g-channel]. + +A [channel][g-channel] is a sequence of [sequencer batches][g-sequencer-batch] (for any L2 blocks) compressed +together. The reason to group multiple batches together is simply to obtain a better compression rate, hence reducing +data availability costs. + +Channels might be too large to fit in a single [batcher transaction][g-batcher-transaction], hence we need to split it +into chunks known as [channel frames][g-channel-frame]. A single batcher transaction can also carry multiple frames +(belonging to the same or to different channels). + +This design gives use the maximum flexibility in how we aggregate batches into channels, and split channels over batcher +transactions. It notably allows us to maximize data utilisation in a batcher transaction: for instance it allows us to +pack the final (small) frame of a window with large frames from the next window. + +In the future this channel identification feature also allows the [batcher][g-batcher] to employ multiple signers +(private keys) to submit one or multiple channels in parallel (1). + +(1) This helps alleviate issues where, because of transaction nonce values affecting the L2 tx-pool and thus inclusion: +multiple transactions made by the same signer are stuck waiting on the inclusion of a previous transaction. + +Also note that we use a streaming compression scheme, and we do not need to know how many blocks a channel will end up +containing when we start a channel, or even as we send the first frames in the channel. + +And by splitting channels across multiple data transactions, the L2 can have larger block data than the +data-availability layer may support. + +All of this is illustrated in the following diagram. Explanations below. + +![batch derivation chain diagram](../static/assets/batch-deriv-chain.svg) + +The first line represents L1 blocks with their numbers. The boxes under the L1 blocks represent [batcher +transactions][g-batcher-transaction] included within the block. The squiggles under the L1 blocks represent +[deposits][g-deposits] (more specifically, events emitted by the [deposit contract][g-deposit-contract]). + +Each colored chunk within the boxes represents a [channel frame][g-channel-frame]. So `A` and `B` are +[channels][g-channel] whereas `A0`, `A1`, `B0`, `B1`, `B2` are frames. Notice that: + +- multiple channels are interleaved +- frames do not need to be transmitted in order +- a single batcher transaction can carry frames from multiple channels + +In the next line, the rounded boxes represent individual [sequencer batches][g-sequencer-batch] that were extracted from +the channels. The four blue/purple/pink were derived from channel `A` while the other were derived from channel `B`. +These batches are here represented in the order they were decoded from batches (in this case `B` is decoded first). + +> **Note** The caption here says "Channel B was seen first and will be decoded into batches first", but this is not a +> requirement. For instance, it would be equally acceptable for an implementation to peek into the channels and decode +> the one that contains the oldest batches first. + +The rest of the diagram is conceptually distinct from the first part and illustrates L2 chain derivation after the +channels have been reordered. + +The first line shows batcher transactions. Note that in this case, there exists an ordering of the batches that makes +all frames within the channels appear contiguously. This is not true in general. For instance, in the second +transaction, the position of `A1` and `B0` could have been inverted for exactly the same result — no changes needed in +the rest of the diagram. + +The second line shows the reconstructed channels in proper order. The third line shows the batches extracted from the +channel. Because the channels are ordered and the batches within a channel are sequential, this means the batches are +ordered too. The fourth line shows the [L2 block][g-block] derived from each batch. Note that we have a 1-1 batch to +block mapping here but, as we'll see later, empty blocks that do not map to batches can be inserted in cases where there +are "gaps" in the batches posted on L1. + +The fifth line shows the [L1 attributes deposited transaction][g-l1-attr-deposit] which, within each L2 block, records +information about the L1 block that matches the L2 block's epoch. The first number denotes the epoch/L1x number, while +the second number (the "sequence number") denotes the position within the epoch. + +Finally, the sixth line shows [user-deposited transactions][g-user-deposited] derived from the [deposit +contract][g-deposit-contract] event mentioned earlier. + +Note the `101-0` L1 attributes transaction on the bottom right of the diagram. Its presence there is only possible if +frame `B2` indicates that it is the last frame within the channel and (2) no empty blocks must be inserted. + +The diagram does not specify the sequencing window size in use, but from this we can infer that it must be at least 4 +blocks, because the last frame of channel `A` appears in block 102, but belong to epoch 99. + +As for the comment on "security types", it explains the classification of blocks as used on L1 and L2. + +- [Unsafe L2 blocks][g-unsafe-l2-block]: +- [Safe L2 blocks][g-safe-l2-block]: +- Finalized L2 blocks: refer to block that have been derived from [finalized][g-finalized-l2-head] L1 data. + +These security levels map to the `headBlockHash`, `safeBlockHash` and `finalizedBlockHash` values transmitted when +interacting with the [execution-engine API][exec-engine]. + +### Batcher Transaction Format + +Batcher transactions are encoded as `version_byte ++ rollup_payload` (where `++` denotes concatenation). + +| `version_byte` | `rollup_payload` | +|----------------|------------------------------------------------| +| 0 | `frame ...` (one or more frames, concatenated) | + +Unknown versions make the batcher transaction invalid (it must be ignored by the rollup node). +All frames in a batcher transaction must be parseable. If any one frame fails to parse, the all frames in the +transaction are rejected. + +Batch transactions are authenticated by verifying that the `to` address of the transaction matches the batch inbox +address, and the `from` address matches the batch-sender address in the [system configuration][g-system-config] at the +time of the L1 block that the transaction data is read from. + +### Frame Format + +A [channel frame][g-channel-frame] is encoded as: + +```text +frame = channel_id ++ frame_number ++ frame_data_length ++ frame_data ++ is_last + +channel_id = bytes16 +frame_number = uint16 +frame_data_length = uint32 +frame_data = bytes +is_last = bool +``` + +Where `uint32` and `uint16` are all big-endian unsigned integers. Type names should be interpreted to and +encoded according to [the Solidity ABI][solidity-abi]. + +[solidity-abi]: https://docs.soliditylang.org/en/v0.8.16/abi-spec.html + +All data in a frame is fixed-size, except the `frame_data`. The fixed overhead is `16 + 2 + 4 + 1 = 23 bytes`. +Fixed-size frame metadata avoids a circular dependency with the target total data length, +to simplify packing of frames with varying content length. + +where: + +- `channel_id` is an opaque identifier for the channel. It should not be reused and is suggested to be random; however, +outside of timeout rules, it is not checked for validity +- `frame_number` identifies the index of the frame within the channel +- `frame_data_length` is the length of `frame_data` in bytes. It is capped to 1,000,000 bytes. +- `frame_data` is a sequence of bytes belonging to the channel, logically after the bytes from the previous frames +- `is_last` is a single byte with a value of 1 if the frame is the last in the channel, 0 if there are frames in the + channel. Any other value makes the frame invalid (it must be ignored by the rollup node). + +[batcher-spec]: batching.md + +### Channel Format + +A channel is encoded as `channel_encoding`, defined as: + +```text +rlp_batches = [] +for batch in batches: + rlp_batches.append(batch) +channel_encoding = compress(rlp_batches) +``` + +where: + +- `batches` is the input, a sequence of batches byte-encoded as per the next section ("Batch Encoding") +- `rlp_batches` is the concatenation of the RLP-encoded batches +- `compress` is a function performing compression, using the ZLIB algorithm (as specified in [RFC-1950][rfc1950]) with + no dictionary +- `channel_encoding` is the compressed version of `rlp_batches` + +[rfc1950]: https://www.rfc-editor.org/rfc/rfc1950.html + +When decompressing a channel, we limit the amount of decompressed data to `MAX_RLP_BYTES_PER_CHANNEL` (currently +10,000,000 bytes), in order to avoid "zip-bomb" types of attack (where a small compressed input decompresses to a +humongous amount of data). If the decompressed data exceeds the limit, things proceeds as though the channel contained +only the first `MAX_RLP_BYTES_PER_CHANNEL` decompressed bytes. The limit is set on RLP decoding, so all batches that +can be decoded in `MAX_RLP_BYTES_PER_CHANNEL` will be accepted even if the size of the channel is greater than +`MAX_RLP_BYTES_PER_CHANNEL`. The exact requirement is that `length(input) <= MAX_RLP_BYTES_PER_CHANNEL`. + +While the above pseudocode implies that all batches are known in advance, it is possible to perform streaming +compression and decompression of RLP-encoded batches. This means it is possible to start including channel frames in a +[batcher transaction][g-batcher-transaction] before we know how many batches (and how many frames) the channel will +contain. + +### Batch Format + +[batch-format]: #batch-format + +Recall that a batch contains a list of transactions to be included in a specific L2 block. + +A batch is encoded as `batch_version ++ content`, where `content` depends on the `batch_version`: + +| `batch_version` | `content` | +|-----------------|------------------------------------------------------------------------------------| +| 0 | `rlp_encode([parent_hash, epoch_number, epoch_hash, timestamp, transaction_list])` | + +where: + +- `batch_version` is a single byte, prefixed before the RLP contents, alike to transaction typing. +- `rlp_encode` is a function that encodes a batch according to the [RLP format], and `[x, y, z]` denotes a list + containing items `x`, `y` and `z` +- `parent_hash` is the block hash of the previous L2 block +- `epoch_number` and `epoch_hash` are the number and hash of the L1 block corresponding to the [sequencing + epoch][g-sequencing-epoch] of the L2 block +- `timestamp` is the timestamp of the L2 block +- `transaction_list` is an RLP-encoded list of [EIP-2718] encoded transactions. + +[RLP format]: https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ +[EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 + +Unknown versions make the batch invalid (it must be ignored by the rollup node), as do malformed contents. + +The `epoch_number` and the `timestamp` must also respect the constraints listed in the [Batch Queue][batch-queue] +section, otherwise the batch is considered invalid and will be ignored. + +------------------------------------------------------------------------------------------------------------------------ + +# Architecture + +[architecture]: #architecture + +The above primarily describes the general encodings used in L2 chain derivation, +primarily how batches are encoded within [batcher transactions][g-batcher-transaction]. + +This section describes how the L2 chain is produced from the L1 batches using a pipeline architecture. + +A validator may implement this differently, but must be semantically equivalent to not diverge from the L2 chain. + +## L2 Chain Derivation Pipeline + +[pipeline]: #l2-chain-derivation-pipeline + +Our architecture decomposes the derivation process into a pipeline made up of the following stages: + +1. L1 Traversal +2. L1 Retrieval +3. Frame Queue +4. Channel Bank +5. Channel Reader (Batch Decoding) +6. Batch Queue +7. Payload Attributes Derivation +8. Engine Queue + +The data flows from the start (outer) of the pipeline towards the end (inner). +From the innermost stage the data is pulled from the outermost stage. + +However, data is *processed* in reverse order. Meaning that if there is any data to be processed in the last stage, it +will be processed first. Processing proceeds in "steps" that can be taken at each stage. We try to take as many steps as +possible in the last (most inner) stage before taking any steps in its outer stage, etc. + +This ensures that we use the data we already have before pulling more data and minimizes the latency of data traversing +the derivation pipeline. + +Each stage can maintain its own inner state as necessary. In particular, each stage maintains a L1 block reference +(number + hash) to the latest L1 block such that all data originating from previous blocks has been fully processed, and +the data from that block is being or has been processed. This allows the innermost stage to account for finalization of +the L1 data-availability used to produce the L2 chain, to reflect in the L2 chain forkchoice when the L2 chain inputs +become irreversible. + +Let's briefly describe each stage of the pipeline. + +### L1 Traversal + +In the *L1 Traversal* stage, we simply read the header of the next L1 block. In normal operations, these will be new +L1 blocks as they get created, though we can also read old blocks while syncing, or in case of an L1 [re-org][g-reorg]. + +Upon traversal of the L1 block, the [system configuration][g-system-config] copy used by the L1 retrieval stage is +updated, such that the batch-sender authentication is always accurate to the exact L1 block that is read by the stage. + +### L1 Retrieval + +In the *L1 Retrieval* stage, we read the block we get from the outer stage (L1 traversal), and extract data from it. +By default, the rollup operates on calldata retrieved from [batcher transactions][g-batcher-transaction] in the block, +for each transaction: + +- The receiver must be the configured batcher inbox address. +- The sender must match the batcher address loaded from the system config matching the L1 block of the data. + +Each data-transaction is versioned and contains a series of [channel frames][g-channel-frame] to be read by the +Frame Queue, see [Batch Submission Wire Format][wire-format]. + +### Frame Queue + +The Frame Queue buffers one data-transaction at a time, +decoded into [channel frames][g-channel-frame], to be consumed by the next stage. +See [Batcher transaction format](#batcher-transaction-format) and [Frame format](#frame-format) specifications. + +### Channel Bank + +The *Channel Bank* stage is responsible for managing buffering from the channel bank that was written to by the L1 +retrieval stage. A step in the channel bank stage tries to read data from channels that are "ready". + +Channels are currently fully buffered until read or dropped, +streaming channels may be supported in a future version of the ChannelBank. + +To bound resource usage, the Channel Bank prunes based on channel size, and times out old channels. + +Channels are recorded in FIFO order in a structure called the *channel queue*. A channel is added to the channel +queue the first time a frame belonging to the channel is seen. + +#### Pruning + +After successfully inserting a new frame, the ChannelBank is pruned: +channels are dropped in FIFO order, until `total_size <= MAX_CHANNEL_BANK_SIZE`, where: + +- `total_size` is the sum of the sizes of each channel, which is the sum of all buffered frame data of the channel, + with an additional frame-overhead of `200` bytes per frame. +- `MAX_CHANNEL_BANK_SIZE` is a protocol constant of 100,000,000 bytes. + +#### Timeouts + +The L1 origin that the channel was opened in is tracked with the channel as `channel.open_l1_block`, +and determines the maximum span of L1 blocks that the channel data is retained for, before being pruned. + +A channel is timed out if: `current_l1_block.number > channel.open_l1_block.number + CHANNEL_TIMEOUT`, where: + +- `current_l1_block` is the L1 origin that the stage is currently traversing. +- `CHANNEL_TIMEOUT` is a rollup-configurable, expressed in number of L1 blocks. + +New frames for timed-out channels are dropped instead of buffered. + +#### Reading + +Upon reading, while the first opened channel is timed-out, remove it from the channel-bank. + +Prior to the Canyon network upgrade, once the first opened channel, if any, is not timed-out and is ready, +then it is read and removed from the channel-bank. After the Canyon network upgrade, the entire channel bank +is scanned in FIFO order (by open time) & the first ready (i.e. not timed-out) channel will be returned. + +The canyon behavior will activate when frames from a L1 block whose timestamp is greater than or equal to the +canyon time first enter the channel queue. + +A channel is ready if: + +- The channel is closed +- The channel has a contiguous sequence of frames until the closing frame + +If no channel is ready, the next frame is read and ingested into the channel bank. + +#### Loading frames + +When a channel ID referenced by a frame is not already present in the Channel Bank, +a new channel is opened, tagged with the current L1 block, and appended to the channel-queue. + +Frame insertion conditions: + +- New frames matching timed-out channels that have not yet been pruned from the channel-bank are dropped. +- Duplicate frames (by frame number) for frames that have not been pruned from the channel-bank are dropped. +- Duplicate closes (new frame `is_last == 1`, but the channel has already seen a closing frame and has not yet been + pruned from the channel-bank) are dropped. + +If a frame is closing (`is_last == 1`) any existing higher-numbered frames are removed from the channel. + +Note that while this allows channel IDs to be reused once they have been pruned from the channel-bank, it is recommended +that batcher implementations use unique channel IDs. + +### Channel Reader (Batch Decoding) + +In this stage, we decompress the channel we pull from the last stage, and then parse +[batches][g-sequencer-batch] from the decompressed byte stream. + +See [Batch Format][batch-format] for decompression and decoding specification. + +### Batch Queue + +[batch-queue]: #batch-queue + +During the *Batch Buffering* stage, we reorder batches by their timestamps. If batches are missing for some [time +slots][g-time-slot] and a valid batch with a higher timestamp exists, this stage also generates empty batches to fill +the gaps. + +Batches are pushed to the next stage whenever there is one sequential batch directly following the timestamp +of the current [safe L2 head][g-safe-l2-head] (the last block that can be derived from the canonical L1 chain). +The parent hash of the batch must also match the hash of the current safe L2 head. + +Note that the presence of any gaps in the batches derived from L1 means that this stage will need to buffer for a whole +[sequencing window][g-sequencing-window] before it can generate empty batches (because the missing batch(es) could have +data in the last L1 block of the window in the worst case). + +A batch can have 4 different forms of validity: + +- `drop`: the batch is invalid, and will always be in the future, unless we reorg. It can be removed from the buffer. +- `accept`: the batch is valid and should be processed. +- `undecided`: we are lacking L1 information until we can proceed batch filtering. +- `future`: the batch may be valid, but cannot be processed yet and should be checked again later. + +The batches are processed in order of the inclusion on L1: if multiple batches can be `accept`-ed the first is applied. +An implementation can defer `future` batches a later derivation step to reduce validation work. + +The batches validity is derived as follows: + +Definitions: + +- `batch` as defined in the [Batch format section][batch-format]. +- `epoch = safe_l2_head.l1_origin` a [L1 origin][g-l1-origin] coupled to the batch, with properties: + `number` (L1 block number), `hash` (L1 block hash), and `timestamp` (L1 block timestamp). +- `inclusion_block_number` is the L1 block number when `batch` was first *fully* derived, + i.e. decoded and output by the previous stage. +- `next_timestamp = safe_l2_head.timestamp + block_time` is the expected L2 timestamp the next batch should have, + see [block time information][g-block-time]. +- `next_epoch` may not be known yet, but would be the L1 block after `epoch` if available. +- `batch_origin` is either `epoch` or `next_epoch`, depending on validation. + +Note that processing of a batch can be deferred until `batch.timestamp <= next_timestamp`, +since `future` batches will have to be retained anyway. + +Rules, in validation order: + +- `batch.timestamp > next_timestamp` -> `future`: i.e. the batch must be ready to process. +- `batch.timestamp < next_timestamp` -> `drop`: i.e. the batch must not be too old. +- `batch.parent_hash != safe_l2_head.hash` -> `drop`: i.e. the parent hash must be equal to the L2 safe head block hash. +- `batch.epoch_num + sequence_window_size < inclusion_block_number` -> `drop`: i.e. the batch must be included timely. +- `batch.epoch_num < epoch.number` -> `drop`: i.e. the batch origin is not older than that of the L2 safe head. +- `batch.epoch_num == epoch.number`: define `batch_origin` as `epoch`. +- `batch.epoch_num == epoch.number+1`: + - If `next_epoch` is not known -> `undecided`: + i.e. a batch that changes the L1 origin cannot be processed until we have the L1 origin data. + - If known, then define `batch_origin` as `next_epoch` +- `batch.epoch_num > epoch.number+1` -> `drop`: i.e. the L1 origin cannot change by more than one L1 block per L2 block. +- `batch.epoch_hash != batch_origin.hash` -> `drop`: i.e. a batch must reference a canonical L1 origin, + to prevent batches from being replayed onto unexpected L1 chains. +- `batch.timestamp < batch_origin.time` -> `drop`: enforce the min L2 timestamp rule. +- `batch.timestamp > batch_origin.time + max_sequencer_drift`: enforce the L2 timestamp drift rule, + but with exceptions to preserve above min L2 timestamp invariant: + - `len(batch.transactions) == 0`: + - `epoch.number == batch.epoch_num`: + this implies the batch does not already advance the L1 origin, and must thus be checked against `next_epoch`. + - If `next_epoch` is not known -> `undecided`: + without the next L1 origin we cannot yet determine if time invariant could have been kept. + - If `batch.timestamp >= next_epoch.time` -> `drop`: + the batch could have adopted the next L1 origin without breaking the `L2 time >= L1 time` invariant. + - `len(batch.transactions) > 0`: -> `drop`: + when exceeding the sequencer time drift, never allow the sequencer to include transactions. +- `batch.transactions`: `drop` if the `batch.transactions` list contains a transaction + that is invalid or derived by other means exclusively: + - any transaction that is empty (zero length byte string) + - any [deposited transactions][g-deposit-tx-type] (identified by the transaction type prefix byte) + +If no batch can be `accept`-ed, and the stage has completed buffering of all batches that can fully be read from the L1 +block at height `epoch.number + sequence_window_size`, and the `next_epoch` is available, +then an empty batch can be derived with the following properties: + +- `parent_hash = safe_l2_head.hash` +- `timestamp = next_timestamp` +- `transactions` is empty, i.e. no sequencer transactions. Deposited transactions may be added in the next stage. +- If `next_timestamp < next_epoch.time`: the current L1 origin is repeated, to preserve the L2 time invariant. + - `epoch_num = epoch.number` + - `epoch_hash = epoch.hash` +- If the batch is the first batch of the epoch, that epoch is used instead of advancing the epoch to ensure that +there is at least one L2 block per epoch. + - `epoch_num = epoch.number` + - `epoch_hash = epoch.hash` +- Otherwise, + - `epoch_num = next_epoch.number` + - `epoch_hash = next_epoch.hash` + +### Payload Attributes Derivation + +In the *Payload Attributes Derivation* stage, we convert the batches we get from the previous stage into instances of +the [`PayloadAttributes`][g-payload-attr] structure. Such a structure encodes the transactions that need to figure into +a block, as well as other block inputs (timestamp, fee recipient, etc). Payload attributes derivation is detailed in the +section [Deriving Payload Attributes section][deriving-payload-attr] below. + +This stage maintains its own copy of the [system configuration][g-system-config], independent of the L1 retrieval stage. +The system configuration is updated with L1 log events whenever the L1 epoch referenced by the batch input changes. + +### Engine Queue + +In the *Engine Queue* stage, the previously derived `PayloadAttributes` structures are buffered and sent to the +[execution engine][g-exec-engine] to be executed and converted into a proper L2 block. + +The stage maintains references to three L2 blocks: + +- The [finalized L2 head][g-finalized-l2-head]: everything up to and including this block can be fully derived from the + [finalized][l1-finality] (i.e. canonical and forever irreversible) part of the L1 chain. +- The [safe L2 head][g-safe-l2-head]: everything up to and including this block can be fully derived from the + currently canonical L1 chain. +- The [unsafe L2 head][g-unsafe-l2-head]: blocks between the safe and unsafe heads are [unsafe + blocks][g-unsafe-l2-block] that have not been derived from L1. These blocks either come from sequencing (in sequencer + mode) or from [unsafe sync][g-unsafe-sync] to the sequencer (in syncer mode). + This is also known as the "latest" head. + +Additionally, it buffers a short history of references to recently processed safe L2 blocks, along with references +from which L1 blocks each was derived. +This history does not have to be complete, but enables later L1 finality signals to be translated into L2 finality. + +#### Engine API usage + +To interact with the engine, the [execution engine API][exec-engine] is used, with the following JSON-RPC methods: + +[exec-engine]: exec-engine.md + +- [`engine_forkchoiceUpdatedV2`] — updates the forkchoice (i.e. the chain head) to `headBlockHash` if different, and + instructs the engine to start building an execution payload if the payload attributes parameter is not `null`. +- [`engine_getPayloadV2`] — retrieves a previously requested execution payload build. +- [`engine_newPayloadV2`] — executes an execution payload to create a block. + +The current version of `op-node` uses the `v2` RPC methods from the engine API, whereas prior versions used the `v1` +equivalents. The `v2` methods are backwards compatible with `v1` payloads but support Shanghai. + +[`engine_forkchoiceUpdatedV2`]: exec-engine.md#engine_forkchoiceupdatedv2 +[`engine_getPayloadV2`]: exec-engine.md#engine_getpayloadv2 +[`engine_newPayloadV2`]: exec-engine.md#engine_newpayloadv2 + +The execution payload is an object of type [`ExecutionPayloadV2`][eth-payload]. + +[eth-payload]: https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#payloadattributesv2 + +With V2 of the execution payload, before Canyon the withdrawals field is required to be nil. After Canyon the +withdrawals field is required to be non-nil. The op-node should set the withdrawals field to be an empty list. + +#### Forkchoice synchronization + +If there are any forkchoice updates to be applied, before additional inputs are derived or processed, then these are +applied to the engine first. + +This synchronization may happen when: + +- A L1 finality signal finalizes one or more L2 blocks: updating the "finalized" L2 block. +- A successful consolidation of unsafe L2 blocks: updating the "safe" L2 block. +- The first thing after a derivation pipeline reset, to ensure a consistent execution engine forkchoice state. + +The new forkchoice state is applied with `engine_forkchoiceUpdatedV2`. +On forkchoice-state validity errors the derivation pipeline must be reset to recover to consistent state. + +#### L1-consolidation: payload attributes matching + +If the unsafe head is ahead of the safe head, then [consolidation][g-consolidation] is attempted, verifying that +existing unsafe L2 chain matches the derived L2 inputs as derived from the canonical L1 data. + +During consolidation, we consider the oldest unsafe L2 block, i.e. the unsafe L2 block directly after the safe head. If +the payload attributes match this oldest unsafe L2 block, then that block can be considered "safe" and becomes the new +safe head. + +The following fields of the derived L2 payload attributes are checked for equality with the L2 block: + +- `parent_hash` +- `timestamp` +- `randao` +- `fee_recipient` +- `transactions_list` (first length, then equality of each of the encoded transactions, including deposits) + +If consolidation succeeds, the forkchoice change will synchronize as described in the section above. + +If consolidation fails, the L2 payload attributes will be processed immediately as described in the section below. +The payload attributes are chosen in favor of the previous unsafe L2 block, creating an L2 chain reorg on top of the +current safe block. Immediately processing the new alternative attributes enables execution engines like go-ethereum to +enact the change, as linear rewinds of the tip of the chain may not be supported. + +#### L1-sync: payload attributes processing + +[exec-engine-comm]: exec-engine.md#engine-api + +If the safe and unsafe L2 heads are identical (whether because of failed consolidation or not), we send the L2 payload +attributes to the execution engine to be constructed into a proper L2 block. +This L2 block will then become both the new L2 safe and unsafe head. + +If a payload attributes created from a batch cannot be inserted into the chain because of a validation error (i.e. there +was an invalid transaction or state transition in the block) the batch should be dropped & the safe head should not be +advanced. The engine queue will attempt to use the next batch for that timestamp from the batch queue. If no valid batch +is found, the rollup node will create a deposit only batch which should always pass validation because deposits are +always valid. + +Interaction with the execution engine via the execution engine API is detailed in the [Communication with the Execution +Engine][exec-engine-comm] section. + +The payload attributes are then processed with a sequence of: + +- `engine_forkchoiceUpdatedV2` with current forkchoice state of the stage, and the attributes to start block building. + - Non-deterministic sources, like the tx-pool, must be disabled to reconstruct the expected block. +- `engine_getPayload` to retrieve the payload, by the payload-ID in the result of the previous step. +- `engine_newPayload` to import the new payload into the execution engine. +- `engine_forkchoiceUpdatedV2` to make the new payload canonical, + now with a change of both `safe` and `unsafe` fields to refer to the payload, and no payload attributes. + +Engine API Error handling: + +- On RPC-type errors the payload attributes processing should be re-attempted in a future step. +- On payload processing errors the attributes must be dropped, and the forkchoice state must be left unchanged. + - Eventually the derivation pipeline will produce alternative payload attributes, with or without batches. + - If the payload attributes only contained deposits, then it is a critical derivation error if these are invalid. +- On forkchoice-state validity errors the derivation pipeline must be reset to recover to consistent state. + +#### Processing unsafe payload attributes + +If no forkchoice updates or L1 data remain to be processed, and if the next possible L2 block is already available +through an unsafe source such as the sequencer publishing it via the p2p network, then it is optimistically processed as +an "unsafe" block. This reduces later derivation work to just consolidation with L1 in the happy case, and enables the +user to see the head of the L2 chain faster than the L1 may confirm the L2 batches. + +To process unsafe payloads, the payload must: + +- Have a block number higher than the current safe L2 head. + - The safe L2 head may only be reorged out due to L1 reorgs. +- Have a parent blockhash that matches the current unsafe L2 head. + - This prevents the execution engine individually syncing a larger gap in the unsafe L2 chain. + - This prevents unsafe L2 blocks from reorging other previously validated L2 blocks. + - This check may change in the future versions to adopt e.g. the L1 snap-sync protocol. + +The payload is then processed with a sequence of: + +- `engine_newPayloadV2`: process the payload. It does not become canonical yet. +- `engine_forkchoiceUpdatedV2`: make the payload the canonical unsafe L2 head, and keep the safe/finalized L2 heads. + +Engine API Error handling: + +- On RPC-type errors the payload processing should be re-attempted in a future step. +- On payload processing errors the payload must be dropped, and not be marked as canonical. +- On forkchoice-state validity errors the derivation pipeline must be reset to recover to consistent state. + +### Resetting the Pipeline + +It is possible to reset the pipeline, for instance if we detect an L1 [reorg (reorganization)][g-reorg]. +**This enables the rollup node to handle L1 chain reorg events.** + +Resetting will recover the pipeline into a state that produces the same outputs as a full L2 derivation process, +but starting from an existing L2 chain that is traversed back just enough to reconcile with the current L1 chain. + +Note that this algorithm covers several important use-cases: + +- Initialize the pipeline without starting from 0, e.g. when the rollup node restarts with an existing engine instance. +- Recover the pipeline if it becomes inconsistent with the execution engine chain, e.g. when the engine syncs/changes. +- Recover the pipeline when the L1 chain reorganizes, e.g. a late L1 block is orphaned, or a larger attestation failure. +- Initialize the pipeline to derive a disputed L2 block with prior L1 and L2 history inside a fault-proof program. + +Handling these cases also means a node can be configured to eagerly sync L1 data with 0 confirmations, +as it can undo the changes if the L1 later does recognize the data as canonical, enabling safe low-latency usage. + +The Engine Queue is first reset, to determine the L1 and L2 starting points to continue derivation from. +After this, the other stages are reset independent of each other. + +#### Finding the sync starting point + +To find the starting point, there are several steps, relative to the head of the chain traversing back: + +1. Find the current L2 forkchoice state + - If no `finalized` block can be found, start at the L2 genesis block. + - If no `safe` block can be found, fallback to the `finalized` block. + - The `unsafe` block should always be available and consistent with the above + (it may not be in rare engine-corruption recovery cases, this is being reviewed). +2. Find the first L2 block with plausible L1 reference to be the new `unsafe` starting point, + starting from previous `unsafe`, back to `finalized` and no further. + - Plausible iff: the L1 origin of the L2 block is known and canonical, or unknown and has a block-number ahead of L1. +3. Find the first L2 block with an L1 reference older than the sequencing window, to be the new `safe` starting point, + starting at the above plausible `unsafe` head, back to `finalized` and no further. + - If at any point the L1 origin is known but not canonical, the `unsafe` head is revised to parent of the current. + - The highest L2 block with known canonical L1 origin is remembered as `highest`. + - If at any point the L1 origin in the block is corrupt w.r.t. derivation rules, then error. Corruption includes: + - Inconsistent L1 origin block number or parent-hash with parent L1 origin + - Inconsistent L1 sequence number (always changes to `0` for a L1 origin change, or increments by `1` if not) + - If the L1 origin of the L2 block `n` is older than the L1 origin of `highest` by more than a sequence window, + and `n.sequence_number == 0`, then the parent L2 block of `n` will be the `safe` starting point. +4. The `finalized` L2 block persists as the `finalized` starting point. +5. Find the first L2 block with an L1 reference older than the channel-timeout + - The L1 origin referenced by this block which we call `l2base` will be the `base` for the L2 pipeline derivation: + By starting here, the stages can buffer any necessary data, while dropping incomplete derivation outputs until + L1 traversal has caught up with the actual L2 safe head. + +While traversing back the L2 chain, an implementation may sanity-check that the starting point is never set too far +back compared to the existing forkchoice state, to avoid an intensive reorg because of misconfiguration. + +Implementers note: step 1-4 are known as `FindL2Heads`. Step 5 is currently part of the Engine Queue reset. +This may change to isolate the starting-point search from the bare reset logic. + +#### Resetting derivation stages + +1. L1 Traversal: start at L1 `base` as first block to be pulled by next stage. +2. L1 Retrieval: empty previous data, and fetch the `base` L1 data, or defer the fetching work to a later pipeline step. +3. Frame Queue: empty the queue. +4. Channel Bank: empty the channel bank. +5. Channel Reader: reset any batch decoding state. +6. Batch Queue: empty the batch queue, use `base` as initial L1 point of reference. +7. Payload Attributes Derivation: empty any batch/attributes state. +8. Engine Queue: + - Initialize L2 forkchoice state with syncing start point state. (`finalized`/`safe`/`unsafe`) + - Initialize the L1 point of reference of the stage to `base`. + - Require a forkchoice update as first task + - Reset any finality data + +Where necessary, stages starting at `base` can initialize their system-config from data encoded in the `l2base` block. + +#### About reorgs Post-Merge + +Note that post-[merge], the depth of reorgs will be bounded by the [L1 finality delay][l1-finality] +(2 L1 beacon epochs, or approximately 13 minutes, unless more than 1/3 of the network consistently disagrees). +New L1 blocks may be finalized every L1 beacon epoch (approximately 6.4 minutes), and depending on these +finality-signals and batch-inclusion, the derived L2 chain will become irreversible as well. + +Note that this form of finalization only affects inputs, and nodes can then subjectively say the chain is irreversible, +by reproducing the chain from these irreversible inputs and the set protocol rules and parameters. + +This is however completely unrelated to the outputs posted on L1, which require a form of proof like a fault-proof or +zk-proof to finalize. Optimistic-rollup outputs like withdrawals on L1 are only labeled "finalized" after passing a week +without dispute (fault proof challenge window), a name-collision with the proof-of-stake finalization. + +[merge]: https://ethereum.org/en/upgrades/merge/ +[l1-finality]: https://ethereum.org/en/developers/docs/consensus-mechanisms/pos/#finality + +------------------------------------------------------------------------------------------------------------------------ + +# Deriving Payload Attributes + +[deriving-payload-attr]: #deriving-payload-attributes + +For every L2 block derived from L1 data, we need to build [payload attributes][g-payload-attr], +represented by an [expanded version][expanded-payload] of the [`PayloadAttributesV2`][eth-payload] object, +which includes additional `transactions` and `noTxPool` fields. + +This process happens during the payloads-attributes queue ran by a full node or validator node, as well as during +block-production ran by a sequencer node (the sequencer may enable the tx-pool usage if the transactions are +batch-submitted). + +[expanded-payload]: exec-engine.md#extended-payloadattributesv1 +[eth-payload]: https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#payloadattributesv1 + +## Deriving the Transaction List + +For each L2 block to be created by the sequencer, we start from a [sequencer batch][g-sequencer-batch] matching the +target L2 block number. This could potentially be an empty auto-generated batch, if the L1 chain did not include a batch +for the target L2 block number. [Remember][batch-format] that the batch includes a [sequencing +epoch][g-sequencing-epoch] number, an L2 timestamp, and a transaction list. + +This block is part of a [sequencing epoch][g-sequencing-epoch], +whose number matches that of an L1 block (its *[L1 origin][g-l1-origin]*). +This L1 block is used to derive L1 attributes and (for the first L2 block in the epoch) user deposits. + +Therefore, a [`PayloadAttributesV2`][expanded-payload] object must include the following transactions: + +- one or more [deposited transactions][g-deposited], of two kinds: + - a single *[L1 attributes deposited transaction][g-l1-attr-deposit]*, derived from the L1 origin. + - for the first L2 block in the epoch, zero or more *[user-deposited transactions][g-user-deposited]*, derived from + the [receipts][g-receipts] of the L1 origin. +- zero or more *[sequenced transactions][g-sequencing]*: regular transactions signed by L2 users, included in the + sequencer batch. + +Transactions **must** appear in this order in the payload attributes. + +The L1 attributes are read from the L1 block header, while deposits are read from the L1 block's [receipts][g-receipts]. +Refer to the [**deposit contract specification**][deposit-contract-spec] for details on how deposits are encoded as log +entries. + +[deposit-contract-spec]: deposits.md#deposit-contract + +## Building Individual Payload Attributes + +[payload attributes]: #building-individual-payload-attributes + +After deriving the transactions list, the rollup node constructs a [`PayloadAttributesV2`][extended-attributes] as +follows: + +- `timestamp` is set to the batch's timestamp. +- `random` is set to the `prev_randao` L1 block attribute. +- `suggestedFeeRecipient` is set to the zero address because the transaction fee is distributed to the fee vaults. + See [Fee Vaults] specification. +- `transactions` is the array of the derived transactions: deposited transactions and sequenced transactions, all + encoded with [EIP-2718]. +- `noTxPool` is set to `true`, to use the exact above `transactions` list when constructing the block. +- `gasLimit` is set to the current `gasLimit` value in the [system configuration][g-system-config] of this payload. +- `withdrawals` is set to nil prior to Canyon and an empty array after Canyon + +[extended-attributes]: exec-engine.md#extended-payloadattributesv1 +[Fee Vaults]: exec-engine.md#fee-vaults diff --git a/specs/protocol/differences-from-optimism.md b/specs/protocol/differences-from-optimism.md new file mode 100644 index 0000000..4dc9577 --- /dev/null +++ b/specs/protocol/differences-from-optimism.md @@ -0,0 +1,145 @@ +# Differences from Optimism + + + +[g-l2-output-root]: glossary.md#l2-output-root +[g-mpt]: glossary.md#merkle-patricia-trie +[g-zktrie]: glossary.md#zk-trie +[g-zk-fault-proof]: glossary.md#zk-fault-proof +[g-system-config]: glossary.md#system-configuration +[g-validation-rewards]: validator.md#validation-rewards +[g-output-payload-v0]: validator.md#output-payloadversion-0 + + + +**Table of Contents** + +- [Nodes](#nodes) + - [Verifier -> Validator](#verifier---validator) + - [Compositions](#compositions) + - [Adding field to System Configuration](#adding-field-to-system-configuration) + - [Adding field to Output Payload](#adding-field-to-output-payload) +- [Geth](#geth) +- [Validator](#validator) + - [ZK fault proof](#zk-fault-proof) + + + +## Nodes + +There are two types of network participants in the OP Stack: + +- [Sequencers](https://github.com/ethereum-optimism/optimism/blob/develop/specs/introduction.md#sequencers) consolidate + users' on/off chain transactions into blocks. They submit checkpoint outputs as well as batch transactions. +- [Verifiers](https://github.com/ethereum-optimism/optimism/blob/develop/specs/introduction.md#verifiers) verify rollup + integrity and dispute invalid assertions. + +It is crucial to have at least one honest verifier who can verify the integrity of the rollup chain to ensure the +ongoing security of the network. However, there exists a well-known obstacle known as the 'Verifier's Dilemma' that +poses a threat to the security of optimistic rollups by introducing disincentives in such scenarios. + +To resolve the 'Verifier's Dilemma', we have devised an incentive mechanism that motivates node operators to actively +participate in the Kroma network. As part of this redesign, we have separated the responsibility of submitting +checkpoint outputs from `sequencers` and assigned it to `verifiers`. As a result, these participants have been renamed +to reflect these role changes and the future direction of Kroma's decentralization. For more detailed information about +our decentralization scheme, please refer to +[this article](https://medium.com/@kroma-network/the-road-to-kromas-decentralization-38f8e46df442) +on the Kroma blog. + +### Verifier -> Validator + +We utilize the term `validator` to denote a participant who is responsible for submitting the +[L2 output root][g-l2-output-root] and validating its accuracy by either submitting dispute challenges (during the +optimistic rollup phase) or providing ZK validity proofs (during the ZK rollup phase). This concept bears a resemblance +to how L1 validators cast FFG votes at each epoch. + +### Compositions + +Kroma maintains the modular architecture of the OP Stack, with various components communicating through Json RPC calls. +As part of the transition from `verifier` to `validator`, we have renamed the `proposer` (op-proposer) component of the +OP Stack to `validator` (kroma-validator) and made necessary modifications to the code to handle the dispute challenge +processes. + +The followings are components that are used to run different types of nodes: + +| Node | Components | +|-------------|----------------------------------------------------------------------| +| `Sequencer` | `L2 EL client` + `L2 CL client` + `kroma-batcher` | +| `Validator` | `L2 EL client` + `L2 CL client` + `kroma-validator` + `kroma-prover` | +| `Full node` | `L2 EL client` + `L2 CL client` | + +**NOTE:** Here `L2 EL client` means `kroma-geth` and `L2 CL client` means `kroma-node`. `L2 EL client` can +be expanded to other clients for pragmatic decentralization. + +### Adding field to System Configuration + +The `ValidatorRewardScalar` field was added to [system configuration][g-system-config]. + +``` +type L1BlockInfo struct { + Number uint64 + Time uint64 + BaseFee *big.Int + BlockHash common.Hash + // Not strictly a piece of L1 information. Represents the number of L2 blocks since the start of the epoch, + // i.e. when the actual L1 info was first introduced. + SequenceNumber uint64 + // BatcherHash version 0 is just the address with 0 padding to the left. + BatcherAddr common.Address + L1FeeOverhead eth.Bytes32 + L1FeeScalar eth.Bytes32 + // [Kroma: START] + ValidatorRewardScalar eth.Bytes32 + // [Kroma: END] +} +``` + +
+Code link here
+
+This value is set via the `SystemConfig` contract on L1 and passed through the L2 derivation process and used as an +ingredient in the reward calculation. (Detailed calculations : [Validation Rewards][g-validation-rewards]) + +### Adding field to Output Payload + +The `next_block_hash` field was added to [Output Payload][g-output-payload-v0]. + +``` +type OutputV0 struct { + StateRoot Bytes32 + MessagePasserStorageRoot Bytes32 + BlockHash common.Hash + // [Kroma: START] + NextBlockHash common.Hash + // [Kroma: END] +} +``` + +
+Code here
+
+This value is used as an additional material for the [verification process][g-zk-fault-proof] of the fault +proof system. +It is used to validate the relationship between the Source OutputRootProof and Dest OutputRootProof, and the validation +of the public input. + +## Geth + +To prepare for migration to ZK Rollup, we use a [ZK Trie][g-zktrie] to represent state. Currently, this makes +the chain slower than [Merkle Patrica Trie][g-mpt]. As the bottleneck is the time to produce ZK proof right now, +we adopt it from [Scroll]. When we overcome the proof generation time problem, we will smoothly migrate state +without a hard fork or huge changes. Thus, you might face an unexpected result when retrieving JSON-RPC such as +`eth_getProof`. + +Additionally, to produce a zkEVM proof, geth should return so called `execution trace` via JSON-RPC +`kroma_getBlockTraceByNumberOrHash` which provides zkEVM prover with data as a witness. + +[scroll]: https://scroll.io/ + +## Validator + +### ZK fault proof + +Instead of [cannon], Kroma uses zkEVM for [ZK fault proof][g-zk-fault-proof]. + +[cannon]: https://github.com/ethereum-optimism/cannon diff --git a/specs/protocol/exec-engine.md b/specs/protocol/exec-engine.md new file mode 100644 index 0000000..5663480 --- /dev/null +++ b/specs/protocol/exec-engine.md @@ -0,0 +1,283 @@ +# L2 Execution Engine + + + +**Table of Contents** + +- [1559 Parameters](#1559-parameters) +- [Deposited transaction processing](#deposited-transaction-processing) + - [Deposited transaction boundaries](#deposited-transaction-boundaries) +- [Fees](#fees) + - [Fee Vaults](#fee-vaults) + - [Transaction Fees](#transaction-fees) + - [L1-Cost fees (L1 Fee Vault)](#l1-cost-fees-l1-fee-vault) +- [Engine API](#engine-api) + - [`engine_forkchoiceUpdatedV2`](#engine_forkchoiceupdatedv2) + - [Extended PayloadAttributesV2](#extended-payloadattributesv2) + - [`engine_newPayloadV2`](#engine_newpayloadv2) + - [`engine_getPayloadV2`](#engine_getpayloadv2) +- [Networking](#networking) +- [Sync](#sync) + - [Happy-path sync](#happy-path-sync) + - [Worst-case sync](#worst-case-sync) + + + +This document outlines the modifications, configuration and usage of a L1 execution engine for L2. + +## 1559 Parameters + +The execution engine must be able to take a per chain configuration which specifies the EIP-1559 Denominator +and EIP-1559 elasticity. After Canyon it should also take a new value `EIP1559DenominatorCanyon` and use that as +the denominator in the 1559 formula rather than the prior denominator. + +The formula for EIP-1559 is not otherwise modified. + +## Deposited transaction processing + +The Engine interfaces abstract away transaction types with [EIP-2718][eip-2718]. + +To support rollup functionality, processing of a new Deposit [`TransactionType`][eip-2718-transactions] +is implemented by the engine, see the [deposits specification][deposit-spec]. + +This type of transaction can mint L2 ETH, run EVM, +and introduce L1 information to enshrined contracts in the execution state. + +[deposit-spec]: deposits.md + +### Deposited transaction boundaries + +Transactions cannot be blindly trusted, trust is established through authentication. +Unlike other transaction types deposits are not authenticated by a signature: +the rollup node authenticates them, outside of the engine. + +To process deposited transactions safely, the deposits MUST be authenticated first: + +- Ingest directly through trusted Engine API +- Part of sync towards a trusted block hash (trusted through previous Engine API instruction) + +Deposited transactions MUST never be consumed from the transaction pool. +*The transaction pool can be disabled in a deposits-only rollup* + +## Fees + +Sequenced transactions (i.e. not applicable to deposits) are charged with 2 types of fees: +transaction fees(priority fees + base fees), and L1-cost fees. + +### Fee Vaults + +The three types of fees are collected in 3 distinct L2 fee-vault deployments for accounting purposes: +fee payments are not registered as internal EVM calls, and thus distinguished better this way. + +These are hardcoded addresses, pointing at pre-deployed proxy contracts. +The proxies are backed by vault contract deployments, based on `FeeVault`, to route vault funds to L1 securely. + +| Vault Name | Predeploy | +|------------------------|----------------------------------------------------------------| +| Validator Reward Vault | [`ValidatorRewardVault`](./predeploys.md#ValidatorRewardVault) | +| Protocol Vault | [`ProtocolVault`](./predeploys.md#ProtocolVault) | +| L1 Fee Vault | [`L1FeeVault`](./predeploys.md#L1FeeVault) | + +### Transaction Fees + +Transaction fees in Kroma are different from [eip-1559] specification. +The Base Fee is not burned, and there is no distinction between Base Fee and Priority Fee. +The transaction fee is distributed to two vaults, Validator Reward Vault and Protocol Fee. + +- Validator Reward Vault: `(baseFee + priorityFee) * ValidatorRewardScalar / 10000` +- Protocol Vault: `(baseFee + priorityFee) * (10000 - ValidatorRewardScalar) / 10000` + +`ValidatorRewardScalar` value is recorded in the [`L1Block`](./predeploys.md#L1block) contract. + +### L1-Cost fees (L1 Fee Vault) + +The protocol funds batch-submission of sequenced L2 transactions by charging L2 users an additional fee +based on the estimated batch-submission costs. +This fee is charged from the L2 transaction-sender ETH balance, and collected into the L1 Fee Vault. + +The exact L1 cost function to determine the L1-cost fee component of an L2 transaction is calculated as: +`(rollupDataGas + l1FeeOverhead) * l1Basefee * l1FeeScalar / 1000000` +(big-int computation, result in Wei and `uint256` range) +Where: + +- `rollupDataGas` is determined from the *full* encoded transaction + (standard EIP-2718 transaction encoding, including signature fields, which is `zeroes * 4 + ones * 16`). +- `l1FeeOverhead` is the Gas Price Oracle `overhead` value. +- `l1FeeScalar` is the Gas Price Oracle `scalar` value. +- `l1Basefee` is the L1 Base fee of the latest L1 origin registered in the L2 chain. + +Note that the `rollupDataGas` uses the same byte cost accounting as defined in [eip-2028], +except the full L2 transaction now counts towards the bytes charged in the L1 calldata. + +Compression, batching, and intrinsic gas costs of the batch transactions are accounted for by the protocol +with the Gas Price Oracle `overhead` and `scalar` parameters. + +The Gas Price Oracle `l1FeeOverhead` and `l1FeeScalar`, as well as the `l1Basefee` of the L1 origin, +can be accessed in two interchangeable ways: + +- read from the deposited L1 attributes (`l1FeeOverhead`, `l1FeeScalar`, `basefee`) of the current L2 block +- read from the L1 Block Info contract (`0x4200000000000000000000000000000000000002`) + - using the respective solidity `uint256`-getter functions (`l1FeeOverhead`, `l1FeeScalar`, `basefee`) + - using direct storage-reads: + - L1 basefee as big-endian `uint256` in slot `1` + - Overhead as big-endian `uint256` in slot `5` + - Scalar as big-endian `uint256` in slot `6` + +## Engine API + + + +### `engine_forkchoiceUpdatedV2` + +This updates which L2 blocks the engine considers to be canonical (`forkchoiceState` argument), +and optionally initiates block production (`payloadAttributes` argument). + +Within the rollup, the types of forkchoice updates translate as: + +- `headBlockHash`: block hash of the head of the canonical chain. Labeled `"unsafe"` in user JSON-RPC. + Nodes may apply L2 blocks out of band ahead of time, and then reorg when L1 data conflicts. +- `safeBlockHash`: block hash of the canonical chain, derived from L1 data, unlikely to reorg. +- `finalizedBlockHash`: irreversible block hash, matches lower boundary of the dispute period. + +To support rollup functionality, one backwards-compatible change is introduced +to [`engine_forkchoiceUpdatedV2`][engine_forkchoiceUpdatedV2]: the extended `PayloadAttributesV2` + +#### Extended PayloadAttributesV2 + +[`PayloadAttributesV2`][PayloadAttributesV2] is extended to: + +```js +PayloadAttributesV2: { + timestamp: QUANTITY + random: DATA (32 bytes) + suggestedFeeRecipient: DATA (20 bytes) + withdrawals: array of WithdrawalV1 + transactions: array of DATA + noTxPool: bool + gasLimit: QUANTITY or null +} +``` + +The type notation used here refers to the [HEX value encoding] used by the [Ethereum JSON-RPC API +specification][JSON-RPC-API], as this structure will need to be sent over JSON-RPC. `array` refers +to a JSON array. + +Each item of the `transactions` array is a byte list encoding a transaction: `TransactionType || +TransactionPayload` or `LegacyTransaction`, as defined in [EIP-2718][eip-2718]. +This is equivalent to the `transactions` field in [`ExecutionPayloadV2`][ExecutionPayloadV2] + +The `transactions` field is optional: + +- If empty or missing: no changes to engine behavior. The sequencers will (if enabled) build a block + by consuming transactions from the transaction pool. +- If present and non-empty: the payload MUST be produced starting with this exact list of transactions. + The [rollup driver][rollup-driver] determines the transaction list based on deterministic L1 inputs. + +The `noTxPool` is optional as well, and extends the `transactions` meaning: + +- If `false`, the execution engine is free to pack additional transactions from external sources like the tx pool + into the payload, after any of the `transactions`. This is the default behavior a L1 node implements. +- If `true`, the execution engine must not change anything about the given list of `transactions`. + +If the `transactions` field is present, the engine must execute the transactions in order and return `STATUS_INVALID` +if there is an error processing the transactions. It must return `STATUS_VALID` if all of the transactions could +be executed without error. **Note**: The state transition rules have been modified such that deposits will never fail +so if `engine_forkchoiceUpdatedV2` returns `STATUS_INVALID` it is because a batched transaction is invalid. + +The `gasLimit` is optional w.r.t. compatibility with L1, but required when used as rollup. +This field overrides the gas limit used during block-building. +If not specified as rollup, a `STATUS_INVALID` is returned. + +[rollup-driver]: rollup-node.md + +### `engine_newPayloadV2` + +No modifications to [`engine_newPayloadV2`][engine_newPayloadV2]. +Applies a L2 block to the engine state. + +### `engine_getPayloadV2` + +No modifications to [`engine_getPayloadV2`][engine_getPayloadV2]. +Retrieves a payload by ID, prepared by `engine_forkchoiceUpdatedV2` when called with `payloadAttributes`. + +## Networking + +The execution engine can acquire all data through the rollup node, as derived from L1: +*P2P networking is strictly optional.* + +However, to not bottleneck on L1 data retrieval speed, the P2P network functionality SHOULD be enabled, serving: + +- Peer discovery ([Disc v5][discv5]) +- [`eth/66`][eth66]: + - Transaction pool (consumed by sequencer nodes) + - State sync (happy-path for fast trustless db replication) + - Historical block header and body retrieval + - *New blocks are acquired through the consensus layer instead (rollup node)* + +No modifications to L1 network functionality are required, except configuration: + +- [`networkID`][network-id]: Distinguishes the L2 network from L1 and testnets. + Equal to the [`chainID`][chain-id] of the rollup network. +- Activate Merge fork: Enables Engine API and disables propagation of blocks, + as block headers cannot be authenticated without consensus layer. +- Bootnode list: DiscV5 is a shared network, + [bootstrap][discv5-rationale] is faster through connecting with L2 nodes first. + +[discv5]: https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md +[eth66]: https://github.com/ethereum/devp2p/blob/master/caps/eth.md +[network-id]: https://github.com/ethereum/devp2p/blob/master/caps/eth.md#status-0x00 +[chain-id]: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md +[discv5-rationale]: https://github.com/ethereum/devp2p/blob/master/discv5/discv5-rationale.md + +## Sync + +The execution engine can operate sync in different ways: + +- Happy-path: rollup node informs engine of the desired chain head as determined by L1, completes through engine P2P. +- Worst-case: rollup node detects stalled engine, completes sync purely from L1 data, no peers required. + +The happy-path is more suitable to bring new nodes online quickly, +as the engine implementation can sync state faster through methods like [snap-sync][snap-sync]. + +[snap-sync]: https://github.com/ethereum/devp2p/blob/master/caps/snap.md + +### Happy-path sync + +1. The rollup node informs the engine of the L2 chain head, unconditionally (part of regular node operation): + - [`engine_newPayloadV2`][engine_newPayloadV2] is called with latest L2 block received from P2P. + - [`engine_forkchoiceUpdatedV2`][engine_forkchoiceUpdatedV2] is called with the current + `unsafe`/`safe`/`finalized` L2 block hashes. +2. The engine requests headers from peers, in reverse till the parent hash matches the local chain +3. The engine catches up: + a) A form of state sync is activated towards the finalized or head block hash + b) A form of block sync pulls block bodies and processes towards head block hash + +The exact P2P based sync is out of scope for the L2 specification: +the operation within the engine is the exact same as with L1 (although with an EVM that supports deposits). + +### Worst-case sync + +1. Engine is out of sync, not peered and/or stalled due other reasons. +2. The rollup node maintains latest head from engine (poll `eth_getBlockByNumber` and/or maintain a header subscription) +3. The rollup node activates sync if the engine is out of sync but not syncing through P2P (`eth_syncing`) +4. The rollup node inserts blocks, derived from L1, one by one, potentially adapting to L1 reorg(s), + as outlined in the [rollup node spec] (`engine_forkchoiceUpdatedV2`, `engine_newPayloadV2`) + +[rollup node spec]: rollup-node.md + +[eip-1559]: https://eips.ethereum.org/EIPS/eip-1559 +[eip-2028]: https://eips.ethereum.org/EIPS/eip-2028 +[eip-2718]: https://eips.ethereum.org/EIPS/eip-2718 +[eip-2718-transactions]: https://eips.ethereum.org/EIPS/eip-2718#transactions +[exec-api-data]: https://github.com/ethereum/execution-apis/blob/769c53c94c4e487337ad0edea9ee0dce49c79bfa/src/engine/specification.md#structures +[l1-api-spec]: https://github.com/ethereum/execution-apis/blob/769c53c94c4e487337ad0edea9ee0dce49c79bfa/src/engine/specification.md +[PayloadAttributesV2]: https://github.com/ethereum/execution-apis/blob/584905270d8ad665718058060267061ecfd79ca5/src/engine/shanghai.md#PayloadAttributesV2 +[ExecutionPayloadV1]: https://github.com/ethereum/execution-apis/blob/769c53c94c4e487337ad0edea9ee0dce49c79bfa/src/engine/specification.md#ExecutionPayloadV1 +[engine_forkchoiceUpdatedV2]: https://github.com/ethereum/execution-apis/blob/584905270d8ad665718058060267061ecfd79ca5/src/engine/shanghai.md#engine_forkchoiceupdatedv2 +[engine_newPayloadV2]: https://github.com/ethereum/execution-apis/blob/584905270d8ad665718058060267061ecfd79ca5/src/engine/shanghai.md#engine_newpayloadv2 +[engine_getPayloadV2]: https://github.com/ethereum/execution-apis/blob/584905270d8ad665718058060267061ecfd79ca5/src/engine/shanghai.md#engine_getpayloadv2 +[HEX value encoding]: https://eth.wiki/json-rpc/API#hex-value-encoding +[JSON-RPC-API]: https://github.com/ethereum/execution-apis diff --git a/specs/protocol/guaranteed-gas-market.md b/specs/protocol/guaranteed-gas-market.md new file mode 100644 index 0000000..4f640b5 --- /dev/null +++ b/specs/protocol/guaranteed-gas-market.md @@ -0,0 +1,158 @@ +# Guaranteed Gas Fee Market + + + +**Table of Contents** + +- [Overview](#overview) +- [Gas Stipend](#gas-stipend) +- [Default Values](#default-values) +- [Limiting Guaranteed Gas](#limiting-guaranteed-gas) +- [Rationale for burning L1 Gas](#rationale-for-burning-l1-gas) +- [On Preventing Griefing Attacks](#on-preventing-griefing-attacks) + + + +## Overview + +[Deposited transaction](../glossary.md#deposited-transaction) are transactions on L2 that are +initiated on L1. The gas that they use on L2 is bought on L1 via a gas burn (or a direct payment +in the future). We maintain a fee market and hard cap on the amount of gas provided to all deposits +in a single L1 block. + +The gas provided to deposited transactions is sometimes called "guaranteed gas". The gas provided to +deposited transactions is unique in the regard that it is not refundable. It cannot be refunded as +it is sometimes paid for with a gas burn and there may not be any ETH left to refund. + +The **guaranteed gas** is composed of a gas stipend, and of any guaranteed gas the user would like +to purchase (on L1) on top of that. + +Guaranteed gas on L2 is bought in the following manner. An L2 gas price is calculated via an +EIP-1559-style algorithm. The total amount of ETH required to buy that gas is then calculated as +(`guaranteed gas * L2 deposit basefee`). The contract then accepts that amount of ETH (in a future +upgrade) or (only method right now), burns an amount of L1 gas that corresponds to the L2 cost +(`L2 cost / L1 Basefee`). The L2 gas price for guaranteed gas is not synchronized with the basefee +on L2 and will likely be different. + +## Gas Stipend + +To offset the gas spent on the deposit event, we credit `gas spent * L1 basefee` ETH to the cost of +the L2 gas, where `gas spent` is the amount of L1 gas spent processing the deposit. If the ETH value +of this credit is greater than the ETH value of the requested guaranteed gas +(`requested guaranteed gas * L2 gas price`), no L1 gas is burnt. + +## Default Values + +| Variable | Value | +| ------------------------------- | ----------------- | +| Max Resource Limit | 20,000,000 | +| Elasticity Multiplier | 10 | +| Base Fee Max Change Denominator | 8 | +| Minimum Base Fee | 1 gwei | +| Maximum Base Fee | type(uint128).max | +| System Tx Max Gas | 1,000,000 | + +## Limiting Guaranteed Gas + +The total amount of guaranteed gas that can be bought in a single L1 block must be limited to +prevent a denial of service attack against L2 as well as ensure the total amount of guaranteed gas +stays below the L2 block gas limit. + +We set a guaranteed gas limit of 8,000,000 gas per L1 block and a target of 2,000,000 gas per L1 +block. These numbers enabled occasional large transactions while staying within our target and +maximum gas usage on L2. + +Because the amount of guaranteed L2 gas that can be purchased in a single block is now limited, +we implement an EIP-1559-style fee market to reduce congestion on deposits. By setting the limit +at a multiple of the target, we enable deposits to temporarily use more L2 gas at a greater cost. + +```python +# Pseudocode to update the L2 Deposit Basefee and cap the amount of guaranteed gas +# bought in a block. Calling code must handle the gas burn and validity checks on +# the ability of the account to afford this gas. +BASE_FEE_MAX_CHANGE_DENOMINATOR = 8 +ELASTICITY_MULTIPLIER = 4 +MAX_RESOURCE_LIMIT = 8_000_000 +TARGET_RESOURCE_LIMIT = MAX_RESOURCE_LIMIT / ELASTICITY_MULTIPLIER +MINIMUM_BASEFEE = 10000 + +# prev_basefee is a u128, prev_bought_gas and prev_num are u64s +prev_basefee, prev_bought_gas, prev_num = +now_num = block.number + +# Clamp the full basefee to a specific range. The minimum value in the range should be around 100-1000 +# to enable faster responses in the basefee. This replaces the `max` mechanism in the ethereum 1559 +# implementation (it also serves to enable the basefee to increase if it is very small). +def clamp(v: i256, min: u128, max: u128) -> u128: + if v < i256(min): + return min + elif v > i256(max): + return max + else: + return u128(v) + +# If this is a new block, update the basefee and reset the total gas +# If not, just update the total gas +if prev_num == now_num: + now_basefee = prev_basefee + now_bought_gas = prev_bought_gas + requested_gas +elif prev_num != now_num : + # Width extension and conversion to signed integer math + gas_used_delta = int128(prev_bought_gas) - int128(TARGET_RESOURCE_LIMIT) + # Use truncating (round to 0) division - solidity's default. + # Sign extend gas_used_delta & prev_basefee to 256 bits to avoid overflows here. + base_fee_per_gas_delta = prev_basefee * gas_used_delta / TARGET_RESOURCE_LIMIT / BASE_FEE_MAX_CHANGE_DENOMINATOR + now_basefee_wide = prev_basefee + base_fee_per_gas_delta + + now_basefee = clamp(now_basefee_wide, min=MINIMUM_BASEFEE, max=UINT_64_MAX_VALUE) + now_bought_gas = requested_gas + +# If we skipped multiple blocks between the previous block and now update the basefee again. +# This is not exactly the same as iterating the above function, but quite close for reasonable +# gas target values. It is also constant time wrt the number of missed blocks which is important +# for keeping gas usage stable. +if prev_num + 1 < now_num: + n = now_num - prev_num - 1 + # Apply 7/8 reduction to prev_basefee for the n empty blocks in a row. + now_basefee_wide = prev_basefee * pow(1-(1/BASE_FEE_MAX_CHANGE_DENOMINATOR), n) + now_basefee = clamp(now_basefee_wide, min=MINIMUM_BASEFEE, max=UINT_64_MAX_VALUE) + +require(now_bought_gas < MAX_RESOURCE_LIMIT) + +store_values(now_basefee, now_bought_gas, now_num) +``` + +## Rationale for burning L1 Gas + +There must be a sybil resistance mechanism for usage of the network. If it is very cheap to get +guaranteed gas on L2, then it would be possible to spam the network. Burning a dynamic amount +of gas on L1 acts as a sybil resistance mechanism as it becomes more expensive with more demand. + +If we collect ETH directly to pay for L2 gas, every (indirect) caller of the deposit function will need +to be marked with the payable selector. This won't be possible for many existing projects. Unfortunately +this is quite wasteful. As such, we will provide two options to buy L2 gas: + +1. Burn L1 Gas +2. Send ETH to the Kroma Portal (Not yet supported) + +The payable version (Option 2) will likely have discount applied to it (or conversely, #1 has a +premium applied to it). + +For the initial release, only #1 is supported. + +## On Preventing Griefing Attacks + +The cost of purchasing all of the deposit gas in every block must be expensive +enough to prevent attackers from griefing all deposits to the network. +An attacker would observe a deposit in the mempool and frontrun it with a deposit +that purchases enough gas such that the other deposit reverts. +The smaller the max resource limit is, the easier this attack is to pull off. +This attack is mitigated by having a large resource limit as well as a large +elasticity multiplier. This means that the target resource usage is kept small, +giving a lot of room for the deposit base fee to rise when the max resource limit +is being purchased. + +This attack should be too expensive to pull off in practice, but if an extremely +wealthy adversary does decide to grief network deposits for an extended period +of time, efforts will be placed to ensure that deposits are able to be processed +on the network. diff --git a/specs/protocol/messengers.md b/specs/protocol/messengers.md new file mode 100644 index 0000000..0ab3ba2 --- /dev/null +++ b/specs/protocol/messengers.md @@ -0,0 +1,115 @@ +# Cross Domain Messengers + + + +**Table of Contents** + +- [Overview](#overview) +- [Message Passing](#message-passing) +- [Upgradeability](#upgradeability) +- [Message Versioning](#message-versioning) + - [Message Version 0](#message-version-0) + + + +## Overview + +The cross domain messengers are responsible for providing a higher level API for +developers who are interested in sending cross domain messages. They allow for +the ability to replay cross domain messages and sit directly on top of the lower +level system contracts responsible for cross domain messaging on L1 and L2. + +The `CrossDomainMessenger` is extended to create both an +`L1CrossDomainMessenger` and well as a `L2CrossDomainMessenger`. + +The `L2CrossDomainMessenger` is a predeploy contract located at +`0x4200000000000000000000000000000000000004`. + +```solidity +interface CrossDomainMessenger { + event FailedRelayedMessage(bytes32 indexed msgHash); + + event RelayedMessage(bytes32 indexed msgHash); + + event SentMessage( + address indexed target, + address indexed sender, + uint256 value, + bytes message, + uint256 messageNonce, + uint256 gasLimit + ); + + function MESSAGE_VERSION() view external returns (uint16); + + function OTHER_MESSENGER() view external returns (address); + + function messageNonce() view external returns (uint256); + + function failedMessages(bytes32) view external returns (bool); + + function relayMessage( + uint256 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _minGasLimit, + bytes calldata _message + ) payable external; + + function sendMessage( + address _target, + bytes calldata _message, + uint32 _minGasLimit + ) payable external; + + function successfulMessages(bytes32) view external returns (bool); + + function xDomainMessageSender() view external returns (address); +} +``` + +## Message Passing + +The `sendMessage` function is used to send a cross domain message. To trigger +the execution on the other side, the `relayMessage` function is called. +Successful messages have their hash stored in the `successfulMessages` mapping +while unsuccessful messages have their hash stored in the `failedMessages` +mapping. + +The user experience when sending from L1 to L2 is a bit different than when +sending a transaction from L2 to L1. When going into L2 from L1, the user does +not need to call `relayMessage` on L2 themselves. The user pays for L2 gas on L1 +and the transaction is automatically pulled into L2 where it is executed on L2. +When going from L2 into L1, the user proves their withdrawal on KromaPortal, +then waits for the finalization window to pass, and then finalizes the withdrawal +on the KromaPortal, which calls `relayMessage` on the +`L1CrossDomainMessenger` to finalize the withdrawal. + +## Upgradeability + +The L1 and L2 cross domain messengers should be deployed behind upgradable +proxies. This will allow for updating the message version. + +## Message Versioning + +Messages are versioned based on the first 2 bytes of their nonce. Depending on +the version, messages can have a different serialization and hashing scheme. +The first two bytes of the nonce are reserved for version metadata because +a version field was not originally included in the messages themselves, but +a `uint256` nonce is so large that we can very easily pack additional data +into that field. + +### Message Version 0 + +```solidity +abi.encodeWithSignature( + "relayMessage(uint256,address,address,uint256,uint256,bytes)", + _nonce, + _sender, + _target, + _value, + _gasLimit, + _data +); +``` diff --git a/specs/protocol/overview.md b/specs/protocol/overview.md new file mode 100644 index 0000000..3567f56 --- /dev/null +++ b/specs/protocol/overview.md @@ -0,0 +1,223 @@ +# Kroma Overview + + + +[g-l1]: ../glossary.md#layer-1-l1 +[g-l2]: ../glossary.md#layer-2-l2 +[g-l2-output]: ../glossary.md#l2-output-root +[g-sequencer-batches]: ../glossary.md#sequencer-batch +[g-state]: ../glossary.md#state +[g-zk-fault-proof]: ../glossary.md#zk-fault-proof + + + +**Table of Contents** + +- [Architecture Design Goals](#architecture-design-goals) +- [Components](#components) + - [L1 Components](#l1-components) + - [L2 Components](#l2-components) + - [Transaction/Block Propagation](#transactionblock-propagation) +- [Key Interactions In Depth](#key-interactions-in-depth) + - [Deposits](#deposits) + - [Block Derivation](#block-derivation) + - [Overview](#overview) + - [Epochs and the Sequencing Window](#epochs-and-the-sequencing-window) + - [Block Derivation Loop](#block-derivation-loop) + - [Engine API](#engine-api) +- [Architecture](#architecture) + + + +This document is a high-level technical overview of the Kroma protocol. It aims to explain how the protocol works in +an informal manner, and direct readers to other parts of the specification so that they may learn more. + +This document assumes you've read the [introduction](../introduction.md). + +## Architecture Design Goals + +- **Execution-Level EVM Equivalence:** The developer experience should be identical to [L1][g-l1] except where + [L2][g-l2] introduces a fundamental difference. + - No special compiler. + - No unexpected gas costs. + - Transaction traces work out-of-the-box. + - All existing Ethereum tooling works - all you have to do is change the chain ID. +- **Maximal compatibility with ETH1 nodes:** The implementation should minimize any differences with a vanilla Geth + node, and leverage as many existing L1 standards as possible. + - The execution engine/rollup node use the ETH2 Engine API to build the canonical L2 chain. + - The execution engine leverages Geth's existing mempool and sync implementations, including snap sync. +- **Minimize state and complexity:** + - Whenever possible, services contributing to the rollup infrastructure are stateless. + - Stateful services can recover to full operation from a fresh DB using the peer-to-peer network and on-chain sync + mechanisms. + - Running a replica is as simple as running a Geth node. + +## Components + +![Components](../static/assets/components.svg) + +### L1 Components + +- **KromaPortal**: A feed of L2 transactions which originated as smart contract calls in the L1 state. + - The `KromaPortal` contract emits `TransactionDeposited` events, which the rollup driver reads in order to process +deposits. + - Deposits are guaranteed to be reflected in the L2 [state][g-state] within the _sequencing window_. + - Beware that _transactions_ are deposited, not tokens. However deposited transactions are a key part of implementing +token deposits (tokens are locked on L1, then minted on L2 via a deposited transaction). +- **BatchInbox**: An L1 address to which the Batch Submitter submits transaction batches. + - Transaction batches include L2 transaction calldata, timestamps, and ordering information. + - The BatchInbox is a regular EOA address. This lets us pass on gas cost savings by not executing any EVM code. +- **L2OutputOracle**: A smart contract that stores [L2 output roots][g-l2-output] for use with withdrawals + and fault proofs. +- **ValidatorPool**: A smart contract that stores validators and their deposits. +- **Colosseum**: A smart contract where validators can dispute assertions on. +- **ZKVerifier**: A smart contract that verifies [ZK fault proof][g-zk-fault-proof]. + +### L2 Components + +- **Rollup Node**: + - A standalone, stateless binary. + - Receives L2 transactions from users. + - Syncs and verifies rollup data on L1. + - Applies rollup-specific block production rules to synthesize blocks from L1. + - Appends blocks to the L2 chain using the Engine API. + - Handles L1 reorgs. + - Distributes unsubmitted blocks to other rollup nodes. +- **Execution Engine (EE)**: + - A vanilla Geth node with minor modifications to support Kroma. + - Maintains L2 state. + - Sync state to other L2 nodes for fast onboarding. + - Serves the Engine API to the rollup node. +- **Batcher** + - A background process that submits [transaction batches][g-sequencer-batches] to the `BatchInbox` address. +- **Validator** + - A background process that submits L2 output commitments to the + [L2OutputOracle](https://github.com/kroma-network/kroma/blob/main/packages/contracts/contracts/L1/L2OutputOracle.sol). +- **ZK Prover** + - A background process that produces a [ZK Fault proof][g-zk-fault-proof] which proves a certain state transition is + valid on rpc request. + +### Transaction/Block Propagation + +**Spec links:** + +- [Execution Engine](./exec-engine.md) + +Since the EE uses Geth under the hood, Kroma uses Geth's built-in peer-to-peer network and transaction pool to +propagate transactions. The same network can also be used to propagate submitted blocks and support snap-sync. + +Unsubmitted blocks, however, are propagated using a separate peer-to-peer network of Rollup Nodes. This is optional, +however, and is provided as a convenience to lower latency for validators and their JSON-RPC clients. + +The below diagram illustrates how the sequencer and validators fit together: + +![Propagation](../static/assets/propagation.svg) + +## Key Interactions In Depth + +### Deposits + +**Spec links:** + +- [Deposits](./deposits.md) + +Kroma supports two types of deposits: user deposits, and L1 attributes deposits. To perform a user deposit, users +call the `depositTransaction` method on the `KromaPortal` contract. This in turn emits `TransactionDeposited` events, +which the rollup node reads during block derivation. + +L1 attributes deposits are used to register L1 block attributes (number, timestamp, etc.) on L2 via a call to the L1 +Attributes Predeploy. They cannot be initiated by users, and are instead added to L2 blocks automatically by the rollup +node. + +Both deposit types are represented by a single custom [EIP-2718] transaction type on L2. + +[EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 + +### Block Derivation + +#### Overview + +The rollup chain can be deterministically derived given an L1 Ethereum chain. The fact that the entire rollup chain can +be derived based on L1 blocks is _what makes Kroma a rollup_. This process can be represented as: + +```text +derive_rollup_chain(l1_blockchain) -> rollup_blockchain +``` + +Kroma's block derivation function is designed such that it: + +- Requires no state other than what is easily accessible using L1 and L2 execution engine APIs. +- Supports sequencer and sequencer consensus. +- Is resilient to sequencer censorship. + +#### Epochs and the Sequencing Window + +The rollup chain is subdivided into epochs. There is a 1:1 correspondence between L1 block numbers and epoch numbers. + +For L1 block number `n`, there is a corresponding rollup epoch `n` which can only be derived after a _sequencing window_ +worth of blocks has passed, i.e. after L1 block number `n + SEQUENCING_WINDOW_SIZE` is added to the L1 chain. + +Each epoch contains at least one block. Every block in the epoch contains an L1 info transaction which contains +contextual information about L1 such as the block hash and timestamp. The first block in the epoch also contains all +deposits initiated via the `KromaPortal` contract on L1. All L2 blocks can also contain _sequenced transactions_, +i.e. transactions submitted directly to the sequencer. + +Whenever the sequencer creates a new L2 block for a given epoch, it must submit it to L1 as part of a _batch_, within +the epoch's sequencing window (i.e. the batch must land before L1 block `n + SEQUENCING_WINDOW_SIZE`). These batches are +(along with the `TransactionDeposited` L1 events) what allows the derivation of the L2 chain from the L1 chain. + +The sequencer does not need for a L2 block to be batch-submitted to L1 in order to build on top of it. In fact, batches +typically contain multiple L2 blocks worth of sequenced transaction. This is what enables +_fast transaction confirmations_ on the sequencer. + +Since transaction batches for a given epoch can be submitted anywhere within the sequencing window, validators must +search all blocks within the window for transaction batches. This protects against the uncertainty of transaction +inclusion of L1. This uncertainty is also why we need the sequencing window in the first place: otherwise the sequencer +could retroactively add blocks to an old epoch, and validators wouldn't know when they can finalize an epoch. + +The sequencing window also prevents censorship by the sequencer: deposits made on a given L1 block will be included in +the L2 chain at worst after `SEQUENCING_WINDOW_SIZE` L1 blocks have passed. + +The following diagram describes this relationship, and how L2 blocks are derived from L1 blocks (L1 info transactions +have been elided): + +![Epochs and Sequencing Windows](../static/assets/sequencer-block-gen.svg) + +#### Block Derivation Loop + +A sub-component of the rollup node called the _rollup driver_ is actually responsible for performing block derivation. +The rollup driver is essentially an infinite loop that runs the block derivation function. For each epoch, the block +derivation function performs the following steps: + +1. Downloads deposit and transaction batch data for each block in the sequencing window. +2. Converts the deposit and transaction batch data into payload attributes for the Engine API. +3. Submits the payload attributes to the Engine API, where they are converted into blocks and added to the canonical + chain. + +This process is then repeated with incrementing epochs until the tip of L1 is reached. + +### Engine API + +The rollup driver doesn't actually create blocks. Instead, it directs the execution engine to do so via the Engine API. +For each iteration of the block derivation loop described above, the rollup driver will craft a _payload attributes_ +object and send it to the execution engine. The execution engine will then convert the payload attributes object into a +block, and add it to the chain. The basic sequence the rollup driver is as follows: + +1. Call `engine_forkchoiceUpdatedV2` with the payload attributes object. We'll skip over the details of the fork choice +state parameter for now - just know that one of its fields is the L2 chain's `headBlockHash`, and that it is set to the +block hash of the tip of the L2 chain. The Engine API returns a payload ID. +2. Call `engine_getPayloadV2` with the payload ID returned in step 1. The engine API returns a payload object that +includes a block hash as one of its fields. +3. Call `engine_newPayloadV2` with the payload returned in step 2. +4. Call `engine_forkchoiceUpdatedV2` with the fork choice parameter's `headBlockHash` set to the block hash returned in +step 2. The tip of the L2 chain is now the block created in step 1. + +The swimlane diagram below visualizes the process: + +![Engine API](../static/assets/engine.svg) + +## Architecture + +Overall, our architecture can be described like below. + +![Architecture](../static/assets/architecture.svg) diff --git a/specs/protocol/predeploys.md b/specs/protocol/predeploys.md new file mode 100644 index 0000000..7ce54ae --- /dev/null +++ b/specs/protocol/predeploys.md @@ -0,0 +1,220 @@ +# Predeploys + + + +**Table of Contents** + +- [Overview](#overview) +- [ProxyAdmin](#proxyadmin) +- [WETH9](#weth9) +- [L1Block](#l1block) +- [L2ToL1MessagePasser](#l2tol1messagepasser) +- [L2CrossDomainMessenger](#l2crossdomainmessenger) +- [GasPriceOracle](#gaspriceoracle) +- [ProtocolVault](#protocolvault) +- [L1FeeVault](#l1feevault) +- [ValidatorRewardVault](#validatorrewardvault) +- [L2StandardBridge](#l2standardbridge) +- [KromaMintableERC20Factory](#kromamintableerc20factory) +- [KromaMintableERC721Factory](#kromamintableerc721factory) + + + + +[g-predeployed-contract-predeploy]: ../glossary.md#predeployed-contract-predeploy + +## Overview + +[Predeployed smart contracts][g-predeployed-contract-predeploy] exist on Kroma +at predetermined addresses in the genesis state. They are similar to precompiles but instead run +directly in the EVM instead of running native code outside of the EVM. + +Predeploys are used instead of precompiles to make it easier for multiclient +implementations as well as allowing for more integration with hardhat/foundry +network forking. + +Predeploy addresses exist in 1 byte namespace `0x42000000000000000000000000000000000000xx`. +Proxies are set at each possible predeploy address except for the `ProxyAdmin` and the `WETH9`. + +| Name | Address | Proxied | +|----------------------------|--------------------------------------------|---------| +| ProxyAdmin | 0x4200000000000000000000000000000000000000 | No | +| WETH9 | 0x4200000000000000000000000000000000000001 | No | +| L1Block | 0x4200000000000000000000000000000000000002 | Yes | +| L2ToL1MessagePasser | 0x4200000000000000000000000000000000000003 | Yes | +| L2CrossDomainMessenger | 0x4200000000000000000000000000000000000004 | Yes | +| GasPriceOracle | 0x4200000000000000000000000000000000000005 | Yes | +| ProtocolVault | 0x4200000000000000000000000000000000000006 | Yes | +| L1FeeVault | 0x4200000000000000000000000000000000000007 | Yes | +| ValidatorRewardVault | 0x4200000000000000000000000000000000000008 | Yes | +| L2StandardBridge | 0x4200000000000000000000000000000000000009 | Yes | +| L2ERC721Bridge | 0x420000000000000000000000000000000000000A | Yes | +| KromaMintableERC20Factory | 0x420000000000000000000000000000000000000B | Yes | +| KromaMintableERC721Factory | 0x420000000000000000000000000000000000000C | Yes | + +## ProxyAdmin + +[Implementation](https://github.com/kroma-network/kroma/blob/main/packages/contracts/contracts/ProxyAdmin.sol) + +Address: `0x4200000000000000000000000000000000000000` + +The `ProxyAdmin` is the owner of all of the proxy contracts set at the +predeploys. It is itself behind a proxy. The owner of the `ProxyAdmin` will +have the ability to upgrade any of the other predeploy contracts. + +## WETH9 + +[Implementation](https://github.com/kroma-network/kroma/blob/main/packages/contracts/contracts/vendor/WETH9.sol) + +Address: `0x4200000000000000000000000000000000000001` + +`WETH9` is the standard implementation of Wrapped Ether on Kroma. It is a +commonly used contract and is placed as a predeploy so that it is at a +deterministic address across Kroma based networks. + +## L1Block + +[Implementation](https://github.com/kroma-network/kroma/blob/main/packages/contracts/contracts/L2/L1Block.sol) + +Address: `0x4200000000000000000000000000000000000002` + +[l1-block-predeploy]: ../glossary.md#l1-attributes-predeployed-contract + +The [L1Block][l1-block-predeploy] is responsible for maintaining L1 context in L2. +This allows for L1 state to be accessed in L2. + +## L2ToL1MessagePasser + +[Implementation](https://github.com/kroma-network/kroma/blob/main/packages/contracts/contracts/L2/L2ToL1MessagePasser.sol) + +Address: `0x4200000000000000000000000000000000000003` + +The `L2ToL1MessagePasser` stores commitments to withdrawal transactions. +When a user is submitting the withdrawing transaction on L1, they provide a +proof that the transaction that they withdrew on L2 is in the `sentMessages` +mapping of this contract. + +Any withdrawn ETH accumulates into this contract on L2 and can be +permissionlessly removed from the L2 supply by calling the `burn()` function. + +## L2CrossDomainMessenger + +[Implementation](https://github.com/kroma-network/kroma/blob/main/packages/contracts/contracts/L2/L2CrossDomainMessenger.sol) + +Address: `0x4200000000000000000000000000000000000004` + +The `L2CrossDomainMessenger` gives a higher level API for sending cross domain +messages compared to directly calling the `L2ToL1MessagePasser`. +It maintains a mapping of L1 messages that have been relayed to L2 +to prevent replay attacks and also allows for replayability if the L1 to L2 +transaction reverts on L2. + +Any calls to the `L1CrossDomainMessenger` on L1 are serialized such that they +go through the `L2CrossDomainMessenger` on L2. + +The `relayMessage` function executes a transaction from the remote domain while +the `sendMessage` function sends a transaction to be executed on the remote +domain through the remote domain's `relayMessage` function. + +## GasPriceOracle + +[Implementation](https://github.com/kroma-network/kroma/blob/main/packages/contracts/contracts/L2/GasPriceOracle.sol) + +Address: `0x4200000000000000000000000000000000000005` + +The `GasPriceOracle` provides an API for offchain gas estimation. The +function `getL1Fee(bytes)` accepts an unsigned RLP transaction and will return +the L1 portion of the fee. This fee pays for using L1 as a data availability +layer and should be added to the L2 portion of the fee, which pays for +execution, to compute the total transaction fee. + +The values used to compute the L2 portion of the fee are: + +- scalar +- overhead +- decimals + +These values are managed by the `SystemConfig` contract on L2. The `scalar` and +`overhead` values are sent to the `L1Block` contract each block and the `decimals` +value is hardcoded to 6. + +## ProtocolVault + +[Implementation](https://github.com/kroma-network/kroma/blob/main/packages/contracts/contracts/L2/ProtocolVault.sol) + +Address: `0x4200000000000000000000000000000000000006` + +The `ProtocolVault` predeploy accumulates transaction fees to fund network operation. +Once the contract has received a certain amount of fees, the ETH can be +withdrawn to an immutable address on L1. + +## L1FeeVault + +[Implementation](https://github.com/kroma-network/kroma/blob/main/packages/contracts/contracts/L2/L1FeeVault.sol) + +Address: `0x4200000000000000000000000000000000000007` + +The `L1FeeVault` predeploy receives the L1 portion of the transaction fees. +Once the contract has received a certain amount of fees, the ETH can be +withdrawn to an immutable address on L1. + +## ValidatorRewardVault + +[Implementation](https://github.com/kroma-network/kroma/blob/main/packages/contracts/contracts/L2/ValidatorRewardVault.sol) + +Address: `0x4200000000000000000000000000000000000008` + +The `ValidatorRewardVault` accumulates transaction fees and pays rewards to validators. +When enough fees accumulate in this account, they can be withdrawn to an immutable L1 address. + +To change the L1 address that fees are withdrawn to, the contract must be +upgraded by changing its proxy's implementation key. + +## L2StandardBridge + +[Implementation](https://github.com/kroma-network/kroma/blob/main/packages/contracts/contracts/L2/L2StandardBridge.sol) + +Address: `0x4200000000000000000000000000000000000009` + +The `L2StandardBridge` is a higher level API built on top of the +`L2CrossDomainMessenger` that gives a standard interface for sending ETH or +ERC20 tokens across domains. + +To deposit a token from L1 to L2, the `L1StandardBridge` locks the token and +sends a cross domain message to the `L2StandardBridge` which then mints the +token to the specified account. + +To withdraw a token from L2 to L1, the user will burn the token on L2 and the +`L2StandardBridge` will send a message to the `L1StandardBridge` which will +unlock the underlying token and transfer it to the specified account. + +The `KromaMintableERC20Factory` can be used to create an ERC20 token contract +on a remote domain that maps to an ERC20 token contract on the local domain +where tokens can be deposited to the remote domain. It deploys an +`KromaMintableERC20` which has the interface that works with the +`StandardBridge`. + +This contract can also be deployed on L1 to allow for L2 native tokens to be +withdrawn to L1. + +## KromaMintableERC20Factory + +[Implementation](https://github.com/kroma-network/kroma/blob/main/packages/contracts/contracts/universal/KromaMintableERC20Factory.sol) + +Address: `0x420000000000000000000000000000000000000B` + +The `KromaMintableERC20Factory` is responsible for creating ERC20 contracts on L2 that can be +used for depositing native L1 tokens into. These ERC20 contracts can be created permisionlessly +and implement the interface required by the `StandardBridge` to just work with deposits and withdrawals. + +Each ERC20 contract that is created by the `KromaMintableERC20Factory` allows for the `L2StandardBridge` to mint +and burn tokens, depending on if the user is depositing from L1 to L2 or withdrawing from L2 to L1. + +## KromaMintableERC721Factory + +[Implementation](https://github.com/kroma-network/kroma/blob/main/packages/contracts/contracts/universal/KromaMintableERC721Factory.sol) + +Address: `0x4200000000000000000000000000000000000017` + +The `KromaMintableERC721Factory` is responsible for creating ERC721 contracts on L2 that can be used for +depositing native L1 NFTs into. diff --git a/specs/protocol/rollup-node-p2p.md b/specs/protocol/rollup-node-p2p.md new file mode 100644 index 0000000..0312250 --- /dev/null +++ b/specs/protocol/rollup-node-p2p.md @@ -0,0 +1,423 @@ +# Rollup-node P2P interface + + + +**Table of Contents** + +- [Overview](#overview) +- [P2P configuration](#p2p-configuration) + - [Identification](#identification) + - [Discv5](#discv5) + - [Structure](#structure) + - [LibP2P](#libp2p) + - [Transport](#transport) + - [Dialing](#dialing) + - [NAT](#nat) + - [Peer management](#peer-management) + - [Transport security](#transport-security) + - [Protocol negotiation](#protocol-negotiation) + - [Identify](#identify) + - [Ping](#ping) + - [Multiplexing](#multiplexing) + - [GossipSub](#gossipsub) + - [Content-based message identification](#content-based-message-identification) + - [Message compression and limits](#message-compression-and-limits) + - [Message ID computation](#message-id-computation) + - [Heartbeat and parameters](#heartbeat-and-parameters) + - [Topic configuration](#topic-configuration) + - [Topic validation](#topic-validation) +- [Gossip Topics](#gossip-topics) + - [`blocksv1`](#blocksv1) + - [`blocksv2`](#blocksv2) + - [Block encoding](#block-encoding) + - [Block signatures](#block-signatures) + - [Block validation](#block-validation) + - [Block processing](#block-processing) + - [Block topic scoring parameters](#block-topic-scoring-parameters) +- [Req-Resp](#req-resp) + - [`payload_by_number`](#payload_by_number) + + + +## Overview + +The [rollup node](rollup-node.md) has an optional peer-to-peer (P2P) network service to improve the latency between +the view of sequencers and the rest of the network by bypassing the L1 in the happy case, +without relying on a single centralized endpoint. + +This also enables faster historical sync to be bootstrapped by providing block headers to sync towards, +and only having to compare the L2 chain inputs to the L1 data as compared to processing everything one block at a time. + +The rollup node will *always* prioritize L1 and reorganize to match the canonical chain. +The L2 data retrieved via the P2P interface is strictly a speculative extension, also known as the "unsafe" chain, +to improve the happy case performance. + +This also means that P2P behavior is a soft-rule: nodes keep each other in check with scoring and eventual banning +of malicious peers by identity or IP. Any behavior on the P2P layer does not affect the rollup security, at worst nodes +rely on higher-latency data from L1 to serve. + +In summary, the P2P stack looks like: + +- Discovery to find peers: [Discv5][discv5] +- Connections, peering, transport security, multiplexing, gossip: [LibP2P][libp2p] +- Application-layer publishing and validation of gossiped messages like L2 blocks. + +This document only specifies the composition and configuration of these network libraries. +These components have their own standards, implementations in Go/Rust/Java/Nim/JS/more, +and are adopted by several other blockchains, most notably the [L1 consensus layer (Eth2)][eth2-p2p]. + +## P2P configuration + +### Identification + +Nodes have a **separate** network- and consensus-identity. +The network identity is a `secp256k1` key, used for both discovery and active LibP2P connections. + +Common representations of network identity: + +- `PeerID`: a LibP2P specific ID derived from the pubkey (through protobuf encoding, typing and hashing) +- `NodeID`: a Discv5 specific ID derived from the pubkey (through hashing, used in the DHT) +- `Multi-address`: an unsigned address, containing: IP, TCP port, PeerID +- `ENR`: a signed record used for discovery, containing: IP, TCP port, UDP port, signature (pubkey can be derived) + and L2 network identification. Generally encoded in base64. + +### Discv5 + +#### Structure + +The Ethereum Node Record (ENR) for an Kroma rollup node must contain the following values, identified by unique keys: + +- An IPv4 address (`ip` field) and/or IPv6 address (`ip6` field). +- A TCP port (`tcp` field) representing the local libp2p listening port. +- A UDP port (`udp` field) representing the local discv5 listening port. +- An OpStack (`opstack` field) L2 network identifier + +The `opstack` value is encoded as a single RLP `bytes` value, the concatenation of: + +- chain ID (`unsigned varint`) +- fork ID (`unsigned varint`) + +Note that DiscV5 is a shared DHT (Distributed Hash Table): the L1 consensus and execution nodes, +as well as testnet nodes, and even external IOT nodes, all communicate records in this large common DHT. +This makes it more difficult to censor the discovery of node records. + +The discovery process in Kroma is a pipeline of node records: + +1. Fill the table with `FINDNODES` if necessary (Performed by Discv5 library) +2. Pull additional records with searches to random Node IDs if necessary + (e.g. iterate [`RandomNodes()`][discv5-random-nodes] in Go implementation) +3. Pull records from the DiscV5 module when looking for peers +4. Check if the record contains the `opstack` entry, verify it matches the chain ID and current or future fork number +5. If not already connected, and not recently disconnected or put on deny-list, attempt to dial. + +### LibP2P + +#### Transport + +TCP transport. Additional transports are supported by LibP2P, but not required. + +#### Dialing + +Nodes should be publicly dialable, not rely on relay extensions, and able to dial both IPv4 and IPv6. + +#### NAT + +The listening endpoint must be publicly facing, but may be configured behind a NAT. +LibP2P will use PMP / UPNP based techniques to track the external IP of the node. +It is recommended to disable the above if the external IP is static and configured manually. + +#### Peer management + +The default is to maintain a peer count with a tide-system based on active peer count: + +- At "low tide" the node starts to actively search for additional peer connections. +- At "high tide" the node starts to prune active connections, + except those that are marked as trusted or have a grace period. + +Peers will have a grace period for a configurable amount of time after joining. +In emergency, when memory runs low, the node should start pruning more aggressively. + +Peer records can be persisted to disk to quickly reconnect with known peers after restarting the rollup node. + +The discovery process feeds the peerstore with peer records to connect to, tagged with a time-to-live (TTL). +The current P2P processes do not require selective topic-specific peer connections, +other than filtering for the basic network participation requirement. + +Peers may be banned if their performance score is too low, or if an objectively malicious action was detected. + +Banned peers will be persisted to the same data-store as the peerstore records. + +TODO: the connection gater does currently not gate by IP address on the dial Accept-callback. + +#### Transport security + +[Libp2p-noise][libp2p-noise], `XX` handshake, with the the `secp256k1` P2P identity, as popularized in Eth2. +The TLS option is available as well, but `noise` should be prioritized in negotiation. + +#### Protocol negotiation + +[Multistream-select 1.0][multistream-select] (`/multistream/1.0.0`) is an interactive protocol +used to negotiate sub-protocols supported in LibP2P peers. Multistream-select 2.0 may be used in the future. + +#### Identify + +LibP2P offers a minimal identification module to share client version and programming language. +This is optional and can be disabled for enhanced privacy. +It also includes the same protocol negotiation information, which can speed up initial connections. + +#### Ping + +LibP2P includes a simple ping protocol to track latency between connections. +This should be enabled to help provide insight into the network health. + +#### Multiplexing + +For async communication over different channels over the same connection, multiplexing is used. +[mplex][mplex] (`/mplex/6.7.0`) is required, and [yamux][yamux] (`/yamux/1.0.0`) is recommended but optional + +#### GossipSub + +[GossipSub 1.1][gossipsub] (`/meshsub/1.1.0`, i.e. with peer-scoring extension) is a pubsub protocol for mesh-networks, +deployed on L1 consensus (Eth2) and other protocols such as Filecoin, offering lots of customization options. + +##### Content-based message identification + +Messages are deduplicated, and filtered through application-layer signature verification. +Thus origin-stamping is disabled and published messages must only contain application data, +enforced through a [`StrictNoSign` Signature Policy][signature-policy] + +This provides greater privacy, and allows sequencers (consensus identity) to maintain +multiple network identities for redundancy. + +##### Message compression and limits + +The application contents are compressed with [snappy][snappy] single-block-compression +(as opposed to frame-compression), and constrained to 10 MiB. + +##### Message ID computation + +[Same as L1][l1-message-id], with recognition of compression: + +- If `message.data` has a valid snappy decompression, set `message-id` to the first 20 bytes of the `SHA256` hash of + the concatenation of `MESSAGE_DOMAIN_VALID_SNAPPY` with the snappy decompressed message data, + i.e. `SHA256(MESSAGE_DOMAIN_VALID_SNAPPY + snappy_decompress(message.data))[:20]`. +- Otherwise, set `message-id` to the first 20 bytes of the `SHA256` hash of + the concatenation of `MESSAGE_DOMAIN_INVALID_SNAPPY` with the raw message data, + i.e. `SHA256(MESSAGE_DOMAIN_INVALID_SNAPPY + message.data)[:20]`. + +#### Heartbeat and parameters + +GossipSub [parameters][gossip-parameters]: + +- `D` (topic stable mesh target count): 8 +- `D_low` (topic stable mesh low watermark): 6 +- `D_high` (topic stable mesh high watermark): 12 +- `D_lazy` (gossip target): 6 +- `heartbeat_interval` (interval of heartbeat, in seconds): 0.5 +- `fanout_ttl` (ttl for fanout maps for topics we are not subscribed to but have published to, in seconds): 24 +- `mcache_len` (number of windows to retain full messages in cache for `IWANT` responses): 12 +- `mcache_gossip` (number of windows to gossip about): 3 +- `seen_ttl` (number of heartbeat intervals to retain message IDs): 130 (= 65 seconds) + +Notable differences from L1 consensus (Eth2): + +- `seen_ttl` does not need to cover a full L1 epoch (6.4 minutes), but rather just a small window covering latest blocks +- `fanout_ttl`: adjusted to lower than `seen_ttl` +- `mcache_len`: a larger number of heartbeats can be retained since the gossip is much less noisy. +- `heartbeat_interval`: faster interval to reduce latency, bandwidth should still be reasonable since + there are far fewer messages to gossip about each interval than on L1 which uses an interval of 0.7 seconds. + +#### Topic configuration + +Topics have string identifiers and are communicated with messages and subscriptions. +`/kroma/chain_id/hardfork_version/Name` + +- `chain_id`: replace with decimal representation of chain ID +- `hardfork_version`: replace with decimal representation of hardfork, starting at `0` +- `Name`: topic application-name + +Note that the topic encoding depends on the topic, unlike L1, +since there are less topics, and all are snappy-compressed. + +#### Topic validation + +To ensure only valid messages are relayed, and malicious peers get scored based on application behavior, +an [extended validator][extended-validator] checks the message before it is relayed or processed. +The extended validator emits one of the following validation signals: + +- `ACCEPT` valid, relayed to other peers and passed to local topic subscriber +- `IGNORE` scored like inactivity, message is dropped and not processed +- `REJECT` score penalties, message is dropped + +## Gossip Topics + +There are two topics for distributing blocks to other nodes faster than proxying through L1 would. These are: + +### `blocksv1` + +Pre-Canyon/Shanghai blocks are broadcast on `/optimism//0/blocks`. + +### `blocksv2` + +Post-Canyon/Shanghai blocks are broadcast on `/optimism//1/blocks`. + +#### Block encoding + +A block is structured as the concatenation of: + +- `signature`: A `secp256k1` signature, always 65 bytes, `r (uint256), s (uint256), y_parity (uint8)` +- `payload`: A SSZ-encoded `ExecutionPayload`, always the remaining bytes. + +The topic uses Snappy block-compression (i.e. no snappy frames): +the above needs to be compressed after encoding, and decompressed before decoding. + +#### Block signatures + +The `signature` is a `secp256k1` signature, and signs over a message: +`keccak256(domain ++ chain_id ++ payload_hash)`, where: + +- `domain` is 32 bytes, reserved for message types and versioning info. All zero for this signature. +- `chain_id` is a big-endian encoded `uint256`. +- `payload_hash` is `keccak256(payload)`, where `payload` is the SSZ-encoded `ExecutionPayload` + +The `secp256k1` signature must have `y_parity = 1 or 0`, the `chain_id` is already signed over. + +#### Block validation + +An [extended-validator] checks the incoming messages as follows, in order of operation: + +- `[REJECT]` if the compression is not valid +- `[REJECT]` if the block encoding is not valid +- `[REJECT]` if the `payload.timestamp` is older than 60 seconds in the past + (graceful boundary for worst-case propagation and clock skew) +- `[REJECT]` if the `payload.timestamp` is more than 5 seconds into the future +- `[REJECT]` if the `block_hash` in the `payload` is not valid +- `[REJECT]` if the block is on the V1 topic and has withdrawals +- `[REJECT]` if the block is on the V2 topic and does not have withdrawals +- `[REJECT]` if the block is on the V2 topic and has a non-zero amount of withdrawals +- `[REJECT]` if more than 5 different blocks have been seen with the same block height +- `[IGNORE]` if the block has already been seen +- `[REJECT]` if the signature by the sequencer is not valid +- Mark the block as seen for the given block height + +The block is signed by the corresponding sequencer, to filter malicious messages. +The sequencer model is singular but may change to multiple sequencers in the future. +A default sequencer pubkey is distributed with rollup nodes and should be configurable. + +Note that blocks that a block may still be propagated even if the L1 already confirmed a different block. +The local L1 view of the node may be wrong, and the time and signature validation will prevent spam. +Hence, calling into the execution engine with a block lookup every propagation step is not worth the added delay. + +##### Block processing + +A node may apply the block to their local engine ahead of L1 availability, if it ensures that: + +- The application of the block is reversible, in case of a conflict with delayed L1 information +- The subsequent forkchoice-update ensures this block is recognized as "unsafe" + (see [fork choice updated](derivation.md#engine-api-usage)) + +##### Block topic scoring parameters + +TODO: GossipSub per-topic scoring to fine-tune incentives for ideal propagation delay and bandwidth usage. + +## Req-Resp + +The kroma-node implements a similar request-response encoding for its sync protocols as the L1 ethereum Beacon-Chain. +See [L1 P2P-interface req-resp specification][eth2-p2p-reqresp] and [Altair P2P update][eth2-p2p-altair-reqresp]. + +However, the protocol is simplified, to avoid several issues seen in L1: + +- Error strings in responses, if there is any alternative response, + should not need to be compressed or have an artificial global length limit. +- Payload lengths should be fixed-length: byte-by-byte uvarint reading from the underlying stream is undesired. +- `` are relaxed to encode a `uint32`, rather than a beacon-chain `ForkDigest`. +- Payload-encoding may change per hardfork, so is not part of the protocol-ID. +- Usage of response-chunks is specific to the req-resp method: most basic req-resp does not need chunked responses. +- Compression is encouraged to be part of the payload-encoding, specific to the req-resp method, where necessary: + pings and such do not need streaming frame compression etc. + +And the protocol ID format follows the same scheme as L1, +except the trailing encoding schema part, which is now message-specific: + +```text +/ProtocolPrefix/MessageName/SchemaVersion/ +``` + +The req-resp protocols served by the kroma-node all have `/ProtocolPrefix` set to `/opstack/req`. + +Individual methods may include the chain ID as part of the `/MessageName` segment, +so it's immediately clear which chain the method applies to, if the communication is chain-specific. +Other methods may include chain-information in the request and/or response data, +such as the `ForkDigest` `` in L1 beacon chain req-resp protocols. + +Each segment starts with a `/`, and may contain multiple `/`, and the final protocol ID is suffixed with a `/`. + +### `payload_by_number` + +This is an optional chain syncing method, to request/serve execution payloads by number. +This serves as a method to fill gaps upon missed gossip, and sync short to medium ranges of unsafe L2 blocks. + +Protocol ID: `/opstack/req/payload_by_number//0/` + +- `/MessageName` is `/block_by_number/` where `` is set to the kroma-node L2 chain ID. +- `/SchemaVersion` is `/0` + +Request format: ``: a little-endian `uint64` - the block number to request. + +Response format: ` = ` + +- `` is a byte code describing the result. + - `0` on success, `` should follow. + - `1` if valid request, but unavailable payload. + - `2` if invalid request + - `3+` if other error + - The `>= 128` range is reserved for future use. +- `` is a little-endian `uint32`, identifying the type of `ExecutionPayload` (fork-specific) +- `` is an encoded block, read till stream EOF. + +The input of `` should be limited, as well as any generated decompressed output, +to avoid unexpected resource usage or zip-bomb type attacks. +A 10 MB limit is recommended, to ensure all blocks may be synced. +Implementations may opt for a different limit, since this sync method is optional. + +`` list: + +- `0`: SSZ-encoded `ExecutionPayload`, with Snappy framing compression, + matching the `ExecutionPayload` SSZ definition of the L1 Merge, L2 Kroma versions. +- Other versions may be listed here with future network upgrades, such as the L1 Shanghai upgrade. + +The request is by block-number, enabling parallel fetching of a chain across many peers. + +A `res = 0` response should be verified to: + +- Have a block-number matching the requested block number. +- Have a consistent `blockhash` w.r.t. the other block contents. +- Build towards a known canonical block. + - This can be verified by checking if the parent-hash of a previous trusted canonical block matches + that of the verified hash of the retrieved block. + - For unsafe blocks this may be relaxed to verification against the parent-hash of any previously trusted block: + - The gossip validation process limits the amount of blocks that may be trusted to sync towards. + - The unsafe blocks should be queued for processing, the latest received L2 unsafe blocks should always + override any previous chain, until the final L2 chain can be reproduced from L1 data. + +A `res > 0` response code should not be accepted. The result code is helpful for debugging, +but the client should regard any error like any any other unanswered request, as the responding peer cannot be trusted. + +---- + +[libp2p]: https://libp2p.io/ +[discv5]: https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md +[discv5-random-nodes]: https://pkg.go.dev/github.com/ethereum/go-ethereum@v1.10.12/p2p/discover#UDPv5.RandomNodes +[eth2-p2p]: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/p2p-interface.md +[eth2-p2p-reqresp]: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/p2p-interface.md#the-reqresp-domain +[eth2-p2p-altair-reqresp]: https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/p2p-interface.md#the-reqresp-domain +[libp2p-noise]: https://github.com/libp2p/specs/tree/master/noise +[multistream-select]: https://github.com/multiformats/multistream-select/ +[mplex]: https://github.com/libp2p/specs/tree/master/mplex +[yamux]: https://github.com/hashicorp/yamux/blob/master/spec.md +[gossipsub]: https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md +[signature-policy]: https://github.com/libp2p/specs/blob/master/pubsub/README.md#signature-policy-options +[snappy]: https://github.com/google/snappy +[l1-message-id]: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/p2p-interface.md#topics-and-messages +[gossip-parameters]: https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.0.md#parameters +[extended-validator]: https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#extended-validators diff --git a/specs/protocol/rollup-node.md b/specs/protocol/rollup-node.md new file mode 100644 index 0000000..9d0f833 --- /dev/null +++ b/specs/protocol/rollup-node.md @@ -0,0 +1,126 @@ +# Rollup Node Specification + + + +**Table of Contents** + +- [Overview](#overview) +- [Driver](#driver) + - [Derivation](#derivation) +- [L2 Output RPC method](#l2-output-rpc-method) + - [Structures](#structures) + - [BlockID](#blockid) + - [L1BlockRef](#l1blockref) + - [L2BlockRef](#l2blockref) + - [SyncStatus](#syncstatus) + - [Output Method API](#output-method-api) + + + + + +[g-block]: ../glossary.md#block +[g-derivation]: ../glossary.md#L2-chain-derivation +[g-exec-engine]: ../glossary.md#execution-engine +[g-l1]: ../glossary.md#layer-1-l1 +[g-l2]: ../glossary.md#layer-2-l2 +[g-payload-attr]: ../glossary.md#payload-attributes +[g-sequencer-batch]: ../glossary.md#sequencer-batch +[g-receipts]: ../glossary.md#receipt +[g-reorg]: ../glossary.md#re-organization +[g-rollup-driver]: ../glossary.md#rollup-driver +[g-rollup-node]: ../glossary.md#rollup-node + +## Overview + +The [rollup node][g-rollup-node] is the component responsible for [deriving the L2 chain][g-derivation] from [L1][g-l1] +blocks (and their associated [receipts][g-receipts]). + +The part of the rollup node that derives the [L2][g-l2] chain is called the [rollup driver][g-rollup-driver]. This +document is currently only concerned with the specification of the rollup driver. + +## Driver + +The task of the [driver][g-rollup-driver] in the [rollup node][g-rollup-node] +is to manage the [derivation][g-derivation] process: + +- Keep track of L1 head block +- Keep track of the L2 chain sync progress +- Iterate over the derivation steps as new inputs become available + +### Derivation + +This process happens in three steps: + +1. Select inputs from the L1 chain, on top of the last L2 block: + a list of blocks, with transactions and associated data and receipts. +2. Read L1 information, deposits, and [sequencing batches][g-sequencer-batch] in order to generate + [payload attributes][g-payload-attr] (essentially [a block without output properties][g-block]). +3. Pass the payload attributes to the [execution engine][g-exec-engine], so that the L2 block (including [output block + properties][g-block]) may be computed. + +While this process is conceptually a pure function from the L1 chain to the L2 chain, it is in practice incremental. The +L2 chain is extended whenever new L1 blocks are added to the L1 chain. Similarly, the L2 chain re-organizes whenever the +L1 chain [re-organizes][g-reorg]. + +For a complete specification of the L2 block derivation, refer to the [L2 block derivation document](./derivation.md). + +## L2 Output RPC method + +The Rollup node has its own RPC method, `optimism_outputAtBlock` which returns a 32 +byte hash corresponding to the [L2 output root](validator.md#l2-output-commitment-construction). + +### Structures + +These define the types used by rollup node API methods. +The types defined here are extended from the [engine API specs][engine-structures]. + +#### BlockID + +- `hash`: `DATA`, 32 Bytes +- `number`: `QUANTITY`, 64 Bits + +#### L1BlockRef + +- `hash`: `DATA`, 32 Bytes +- `number`: `QUANTITY`, 64 Bits +- `parentHash`: `DATA`, 32 Bytes +- `timestamp`: `QUANTITY`, 64 Bits + +#### L2BlockRef + +- `hash`: `DATA`, 32 Bytes +- `number`: `QUANTITY`, 64 Bits +- `parentHash`: `DATA`, 32 Bytes +- `timestamp`: `QUANTITY`, 64 Bits +- `l1origin`: `BlockID` +- `sequenceNumber`: `QUANTITY`, 64 Bits - distance to first block of epoch + +#### SyncStatus + +Represents a snapshot of the rollup driver. + +- `current_l1`: `Object` - instance of [`L1BlockRef`](#l1blockref). +- `current_l1_finalized`: `Object` - instance of [`L1BlockRef`](#l1blockref). +- `head_l1`: `Object` - instance of [`L1BlockRef`](#l1blockref). +- `safe_l1`: `Object` - instance of [`L1BlockRef`](#l1blockref). +- `finalized_l1`: `Object` - instance of [`L1BlockRef`](#l1blockref). +- `unsafe_l2`: `Object` - instance of [`L2BlockRef`](#l2blockref). +- `safe_l2`: `Object` - instance of [`L2BlockRef`](#l2blockref). +- `finalized_l2`: `Object` - instance of [`L2BlockRef`](#l2blockref). +- `pending_safe_l2`: `Object` - instance of [`L2BlockRef`](#l2blockref). +- `queued_unsafe_l2`: `Object` - instance of [`L2BlockRef`](#l2blockref). + +### Output Method API + +The input and return types here are as defined by the [engine API specs][engine-structures]. + +[engine-structures]: https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#structures + +- method: `optimism_outputAtBlock` +- params: + 1. `blockNumber`: `QUANTITY`, 64 bits - L2 integer block number
+ OR `String` - one of `"safe"`, `"latest"`, or `"pending"`. +- returns: + 1. `version`: `DATA`, 32 Bytes - the output root version number, beginning with 0. + 2. `l2OutputRoot`: `DATA`, 32 Bytes - the output root. diff --git a/specs/protocol/safe-liveness-checking.md b/specs/protocol/safe-liveness-checking.md new file mode 100644 index 0000000..654516b --- /dev/null +++ b/specs/protocol/safe-liveness-checking.md @@ -0,0 +1,194 @@ +# Safe Liveness Checking + + + +**Table of Contents** + +- [Liveness checking Mechanism](#liveness-checking-mechanism) +- [Liveness checking methodology](#liveness-checking-methodology) + - [The liveness guard](#the-liveness-guard) + - [The liveness module](#the-liveness-module) + - [Owner removal call flow](#owner-removal-call-flow) + - [Shutdown](#shutdown) + - [Security Properties](#security-properties) + - [In the guard](#in-the-guard) + - [In the module](#in-the-module) + - [Interdependency between the guard and module](#interdependency-between-the-guard-and-module) +- [Operational considerations](#operational-considerations) + - [Manual validation of new owner liveness](#manual-validation-of-new-owner-liveness) + - [Deploying the liveness checking system](#deploying-the-liveness-checking-system) + - [Modify the liveness checking system](#modify-the-liveness-checking-system) + - [Replacing the module](#replacing-the-module) + - [Replacing the guard](#replacing-the-guard) + + + +## Liveness checking Mechanism + +The Security Security Council uses a specially extended Safe multisig contract to ensure that +any loss of access to a signer's keys is identified and addressed within a predictable period of +time. + +This mechanism is intended only to be used to remove signers who have lost access to their keys, or +are otherwise inactive. It is not intended to be used to remove signers who are acting in bad faith, +or any other subjective criteria, such cases should be addressed by governance, and the removal +handled via the standard Safe ownership management functionality. + +## Liveness checking methodology + +This is achieved using two types of contracts which the Safe contract has built-in support for: + +1. **Guard contracts:** can execute pre- and post- transaction checks. +1. **Module contracts:** a contract which is added to the Safe by the signers, and thenceforth is + authorized to execute transactions via the Safe. This means the module must properly implement + auth conditions internally. + +### The liveness guard + +For implementing liveness checks a `LivenessGuard` is created which receives the signatures from +each executed transaction, and tracks the latest time at which a transaction was signed by each +signer. This time is made publicly available by calling a `lastLive(address)(Timestamp)` method. + +Owners are recorded in this mapping in one of 4 ways: + +1. Upon deployment, the guard reads the current set of owners from the Safe contract. +1. When a new owner is added to the safe. Similarly, when an owner is removed from the Safe, it's + entry is deleted from the mapping. +1. When a transaction is executed, the signatures on that transaction are passed to the guard and + used to identify the signers. If more than the required number of signatures is provided, they + are ignored. +1. An owner may call the contract's `showLiveness()()` method directly in order to prove liveness. + +Note that the first two methods do not require the owner to actually sign anything. However these mechanisms +are necessary to prevent new owners from being removed before they have had a chance to show liveness. + +### The liveness module + +A `LivenessModule` is also created which does the following: + +1. Has a function `removeOwners()` that anyone may call to specify one or more owners to be removed from the + Safe. +1. The Module would then check the `LivenessGuard.lastLive()` to determine if the signer is + eligible for removal. +1. If so, it will call the Safe's `removeSigner()` to remove the non-live signer, and if necessary + reduce the threshold. +1. When a member is removed, the signing parameters are modified such that `M/N` is the lowest ratio + which remains greater than or equal to 75%. Using integer math, this can be expressed as `M = (N * 75 + 99) / 100`. + +### Owner removal call flow + +The following diagram illustrates the flow for removing a single owner. The `verifyFinalState` +box indicates calls to the Safe which ensure the final state is valid. + +```mermaid +sequenceDiagram + participant User + participant LivenessModule + participant LivenessGuard + participant Safe + User->>LivenessModule: removeOwners([previousOwner], [owner]) + LivenessModule->>LivenessGuard: lastLive(owner) + LivenessModule->>Safe: getOwners() + LivenessModule->>Safe: removeOwner(previousOwner, owner) + + alt verifyFinalState + LivenessModule->>Safe: getOwners() + LivenessModule->>Safe: getThreshold() + LivenessModule->>Safe: getGuard() + end +``` + +### Shutdown + +In the unlikely event that the signer set (`N`) is reduced below the allowed minimum number of + owners, then (and only then) is a shutdown mechanism activated which removes the existing + signers, and hands control of the multisig over to a predetermined entity. + +### Security Properties + +The following security properties must be upheld: + +#### In the guard + +1. Signatures are assigned to the correct signer. +1. Non-signers are unable to create a record of having signed. +1. An owner cannot be censored or griefed such that their signing is not recorded. +1. Owners may demonstrate liveness either by signing a transaction or by calling directly to the + guard. +1. It must be impossible for the guard's `checkTransaction` or `checkAfterExecution` method to + permanently revert given any calldata and the current state. +1. The guard correctly handles updates to the owners list, such that new owners are recorded, and + removed owners are deleted. + 1. An `ownersBefore` enumerable set variable is used to accomplish this, it must be emptied at + the end of the `checkAfterExecution` call. + +#### In the module + +1. During a shutdown the module correctly removes all signers, and converts the safe to a 1 of 1. +1. The module only removes an owner if they have not demonstrated liveness during the interval, or + if enough other owners have been removed to activate the shutdown mechanism. +1. The module correctly sets the Safe's threshold upon removing a signer. + +Note: neither the module nor guard attempt to prevent a quorum of owners from removing either the liveness +module or guard. There are legitimate reasons they might wish to do so. Moreover, if such a quorum +of owners exists, there is no benefit to removing them, as they are defacto 'sufficiently live'. + +### Interdependency between the guard and module + +The guard has no dependency on the module, and can be used independently to track liveness of +Safe owners. + +This means that the module can be removed or replaced without any affect on the guard. + +The module however does have a dependency on the guard; if the guard is removed from the Safe, then +the module will no longer be functional and calls to its `removeOwners` function will revert. + +## Operational considerations + +### Manual validation of new owner liveness + +As [noted above](#the-liveness-guard) newly added owners are recorded in the guard without +necessarily having signed a transaction. Off-chain validation of the liveness of an address must +therefore be done prior to adding a new owner. + +### Deploying the liveness checking system + +[deploying]: #deploying-the-liveness-checking-system + +The module and guard are intended to be deployed and installed on the safe in the following +sequence: + +1. Deploy the guard contract + 2. The guard's constructor will read the Safe's owners and set a timestamp +1. Deploy the module. +1. Set the guard on the safe. +1. Enable the module on the safe. + +This order of operations is necessary to satisfy the constructor checks in the module, and is +intended to prevent owners from being immediately removable. + +Note that changes to the owners set should not be made between the time the module is deployed, and +when it is enabled on the Safe, otherwise the checks made in the module's constructor may be +invalidated. If such changes are made, a new module should be deployed. + +### Modify the liveness checking system + +Changes to the liveness checking system should be done in the following manner: + +#### Replacing the module + +The module can safely be removed without affecting the operation of the guard. A new module can then +be added. + +Note: none of the module's parameters are modifiable. In order to update the security properties +enforced by the module, it must be replaced. + +#### Replacing the guard + +The safe can only have one guard contract at a time, and if the guard is removed the module will +cease to function. This does not affect the ability of the Safe to operate normally, however the +module should be removed as a best practice. + +If a new guard is added, eg. as a means of upgrading it, then a new module will also need to be +deployed and enabled. Once both the guard and module have been removed, they can be replaced +according to the steps in the [Deployment][deploying] section above. diff --git a/specs/protocol/security-council.md b/specs/protocol/security-council.md new file mode 100644 index 0000000..5ea49a5 --- /dev/null +++ b/specs/protocol/security-council.md @@ -0,0 +1,63 @@ +# Security Council + +[g-l1]: ../glossary.md#layer-1-l1 + + + +**Table of Contents** + +- [Overview](#overview) +- [Guardian of Fault Proof System](#guardian-of-fault-proof-system) +- [Guardian of Bridge](#guardian-of-bridge) +- [Contract Upgrades](#contract-upgrades) + + + +## Overview + +The Security Council comprises a group of trusted parties with the responsibility of safeguarding the blockchain's +security. They are minimally eligible for ensuring the blockchain's safety concerning the Fault Proof System, bridge +operations, and contract upgrades. No single member of the Security Council can unilaterally execute actions. Instead, +all actions are carried out through multi-sig transaction or governance processes. + +## Guardian of Fault Proof System + +When an undeniable bug occurs within the [ZK Fault Proof System](../glossary.md#zk-fault-proof), assets locked in +Layer 2 may be exposed to potential risks. To prevent this, the Security Council has the authority to rectify such +issues. The Security Council intervenes in cases where two valid and contradictory ZK proofs exist +([ZK soundness error](../fault-proof/challenge.mdhallenge.md#dismiss-challenge)) or fail to prove with a valid proof +([ZK completeness error](../fault-proof/challenge.mdhallenge.md#force-delete-output)). Their intervention aims to prevent invalid outputs from +being finalized, thereby safeguarding the assets locked in Layer 2. + +## Guardian of Bridge + +If potential threats exposing Layer 2 assets within the Bridge, the Security Council possesses the authority to promptly +pause/unpause the bridge through a multi-sig transaction. The `GUARDIAN` in the +[`KromaPortal.sol`](../packages/contracts/contracts/L1/KromaPortal.sol) is configured to be Security Council. + +```solidity + /** + * @notice Pause deposits and withdrawals. + */ + function pause() external { + require(msg.sender == GUARDIAN, "KromaPortal: only guardian can pause"); + paused = true; + emit Paused(msg.sender); + } + + /** + * @notice Unpause deposits and withdrawals. + */ + function unpause() external { + require(msg.sender == GUARDIAN, "KromaPortal: only guardian can unpause"); + paused = false; + emit Unpaused(msg.sender); + } +``` + +## Contract Upgrades + +All contract upgrades deployed on [Layer 1][g-l1] are conducted by the +[governance of the Security Council](contract-upgrades.md#upgrade-by-governance). These upgrades are proposed by a +member of Security Council and are determined through the voting of these members. If a proposal passes, it typically +has a 7-day timelock delay for execution. diff --git a/specs/protocol/span-batches.md b/specs/protocol/span-batches.md new file mode 100644 index 0000000..10d1133 --- /dev/null +++ b/specs/protocol/span-batches.md @@ -0,0 +1,385 @@ +# Span-batches + + +[g-deposit-tx-type]: ../glossary.md#deposited-transaction-type + + + +**Table of Contents** + +- [Introduction](#introduction) +- [Span batch format](#span-batch-format) +- [Span batch Activation Rule](#span-batch-activation-rule) +- [Optimization Strategies](#optimization-strategies) + - [Truncating information and storing only necessary data](#truncating-information-and-storing-only-necessary-data) + - [`tx_data_headers` removal from initial specs](#tx_data_headers-removal-from-initial-specs) + - [`Chain ID` removal from initial specs](#chain-id-removal-from-initial-specs) + - [Reorganization of constant length transaction fields](#reorganization-of-constant-length-transaction-fields) + - [RLP encoding for only variable length fields](#rlp-encoding-for-only-variable-length-fields) + - [Store `y_parity` and `protected_bit` instead of `v`](#store-y_parity-and-protected_bit-instead-of-v) + - [Adjust `txs` Data Layout for Better Compression](#adjust-txs-data-layout-for-better-compression) + - [`fee_recipients` Encoding Scheme](#fee_recipients-encoding-scheme) +- [How derivation works with Span Batch?](#how-derivation-works-with-span-batch) +- [Integration](#integration) + - [Channel Reader (Batch Decoding)](#channel-reader-batch-decoding) + - [Batch Queue](#batch-queue) + - [Batcher](#batcher) + + + +> The span-batches spec is experimental :shipit: +> +> *this feature is in active R&D and not yet part of any hard fork + +## Introduction + +Span-batches reduce overhead of OP-stack chains. +This enables sparse and low-throughput OP-stack chains. + +The overhead is reduced by representing a span of +consecutive L2 blocks in a more efficient manner, +while preserving the same consistency checks as regular batch data. + +Note that the [channel](./derivation.md#channel-format) and +[frame](./derivation.md#frame-format) formats stay the same: +data slicing, packing and multi-transaction transport is already optimized. + +The overhead in the [V0 batch format](./derivation.md) comes from: + +- The meta-data attributes are repeated for every L2 block, while these are mostly implied already: + - parent hash (32 bytes) + - L1 epoch: blockhash (32 bytes) and block number (~4 bytes) + - timestamp (~4 bytes) +- The organization of block data is inefficient: + - Similar attributes are far apart, diminishing any chances of effective compression. + - Random data like hashes are positioned in-between the more compressible application data. +- The RLP encoding of the data adds unnecessary overhead + - The outer list does not have to be length encoded, the attributes are known + - Fixed-length attributes do not need any encoding + - The batch-format is static and can be optimized further +- Remaining meta-data for consistency checks can be optimized further: + - The metadata only needs to be secure for consistency checks. E.g. 20 bytes of a hash may be enough. + +Span-batches address these inefficiencies, with a new batch format version. + +## Span batch format + +[span-batch-format]: #span-batch-format + +Note that span-batches, unlike previous singular batches, +encode *a range of consecutive* L2 blocks at the same time. + +Introduce version `1` to the [batch-format](./derivation.md#batch-format) table: + +| `batch_version` | `content` | +|-----------------|---------------------| +| 1 | `prefix ++ payload` | + +Notation: + +- `++`: concatenation of byte-strings +- `span_start`: first L2 block in the span +- `span_end`: last L2 block in the span +- `uvarint`: unsigned Base128 varint, as defined in [protobuf spec] +- `rlp_encode`: a function that encodes a batch according to the RLP format, + and `[x, y, z]` denotes a list containing items `x`, `y` and `z` + +[protobuf spec]: https://protobuf.dev/programming-guides/encoding/#varints + +Standard bitlists, in the context of span-batches, are encoded as big-endian integers, +left-padded with zeroes to the next multiple of 8 bits. + +Where: + +- `prefix = rel_timestamp ++ l1_origin_num ++ parent_check ++ l1_origin_check` + - `rel_timestamp`: `uvarint` relative timestamp since L2 genesis, + i.e. `span_start.timestamp - config.genesis.timestamp`. + - `l1_origin_num`: `uvarint` number of last l1 origin number. i.e. `span_end.l1_origin.number` + - `parent_check`: first 20 bytes of parent hash, the hash is truncated to 20 bytes for efficiency, + i.e. `span_start.parent_hash[:20]`. + - `l1_origin_check`: the block hash of the last L1 origin is referenced. + The hash is truncated to 20 bytes for efficiency, i.e. `span_end.l1_origin.hash[:20]`. +- `payload = block_count ++ origin_bits ++ block_tx_counts ++ txs`: + - `block_count`: `uvarint` number of L2 blocks. This is at least 1, empty span batches are invalid. + - `origin_bits`: standard bitlist of `block_count` bits: + 1 bit per L2 block, indicating if the L1 origin changed this L2 block. + - `block_tx_counts`: for each block, a `uvarint` of `len(block.transactions)`. + - `txs`: L2 transactions which is reorganized and encoded as below. +- `txs = contract_creation_bits ++ y_parity_bits ++ + tx_sigs ++ tx_tos ++ tx_datas ++ tx_nonces ++ tx_gases ++ protected_bits` + - `contract_creation_bits`: standard bitlist of `sum(block_tx_counts)` bits: + 1 bit per L2 transactions, indicating if transaction is a contract creation transaction. + - `y_parity_bits`: standard bitlist of `sum(block_tx_counts)` bits: + 1 bit per L2 transactions, indicating the y parity value when recovering transaction sender address. + - `tx_sigs`: concatenated list of transaction signatures + - `r` is encoded as big-endian `uint256` + - `s` is encoded as big-endian `uint256` + - `tx_tos`: concatenated list of `to` field. `to` field in contract creation transaction will be `nil` and ignored. + - `tx_datas`: concatenated list of variable length rlp encoded data, + matching the encoding of the fields as in the [EIP-2718] format of the `TransactionType`. + - `legacy`: `rlp_encode(value, gasPrice, data)` + - `1`: ([EIP-2930]): `0x01 ++ rlp_encode(value, gasPrice, data, accessList)` + - `2`: ([EIP-1559]): `0x02 ++ rlp_encode(value, max_priority_fee_per_gas, max_fee_per_gas, data, access_list)` + - `tx_nonces`: concatenated list of `uvarint` of `nonce` field. + - `tx_gases`: concatenated list of `uvarint` of gas limits. + - `legacy`: `gasLimit` + - `1`: ([EIP-2930]): `gasLimit` + - `2`: ([EIP-1559]): `gas_limit` + - `protected_bits`: standard bitlist of length of number of legacy transactions: + 1 bit per L2 legacy transactions, indicating if transaction is protected([EIP-155]) or not. + +Introduce version `2` to the [batch-format](./derivation.md#batch-format) table: + +| `batch_version` | `content` | +|-----------------|---------------------| +| 2 | `prefix ++ payload` | + +Where: + +- `prefix = rel_timestamp ++ l1_origin_num ++ parent_check ++ l1_origin_check`: + - Identical to `batch_version` 1 +- `payload = block_count ++ origin_bits ++ block_tx_counts ++ txs ++ fee_recipients`: + - An empty span-batch, i.e. with `block_count == 0`, is invalid and must not be processed. + - Every field definition identical to `batch_version` 1 except that `fee_recipients` is + added to support more decentralized sequencing. + - `fee_recipients = fee_recipients_idxs + fee_recipients_set` + - `fee_recipients_set`: concatenated list of unique L2 fee recipient address. + - `fee_recipients_idxs`: for each block, + `uvarint` number of index to decode fee recipients from `fee_recipients_set`. + +[EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 + +[EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + +[EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 + +[EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + +Total size of encoded span batch is limited to `MAX_SPAN_BATCH_SIZE` (currently 10,000,000 bytes, +equal to `MAX_RLP_BYTES_PER_CHANNEL`). Therefore every field size of span batch will be implicitly limited to +`MAX_SPAN_BATCH_SIZE` . There can be at least single span batch per channel, and channel size is limited +to `MAX_RLP_BYTES_PER_CHANNEL` and you may think that there is already an implicit limit. However, having an explicit +limit for span batch is helpful for several reasons. We may save computation costs by avoiding malicious input while +decoding. For example, lets say bad batcher wrote span batch which `block_count = max.Uint64`. We may early return using +the explicit limit, not trying to consume data until EOF is reached. We can also safely preallocate memory for decoding +because we know the upper limit of memory usage. + +## Span batch Activation Rule + +The span batch upgrade is activated based on timestamp. + +Activation Rule: `upgradeTime != null && span_start.l1_origin.timestamp >= upgradeTime` + +`span_start.l1_origin.timestamp` is the L1 origin block timestamp of the first block in the span batch. +This rule ensures that every chain activity regarding this span batch is done after the hard fork. +i.e. Every block in the span is created, submitted to the L1, and derived from the L1 after the hard fork. + +## Optimization Strategies + +### Truncating information and storing only necessary data + +The following fields stores truncated data: + +- `rel_timestamp`: We can save two bytes by storing `rel_timestamp` instead of the full `span_start.timestamp`. +- `parent_check` and `l1_origin_check`: We can save twelve bytes by truncating twelve bytes from the full hash, + while having enough safety. + +### `tx_data_headers` removal from initial specs + +We do not need to store length per each `tx_datas` elements even if those are variable length, +because the elements itself is RLP encoded, containing their length in RLP prefix. + +### `Chain ID` removal from initial specs + +Every transaction has chain id. We do not need to include chain id in span batch because L2 already knows its chain id, +and use its own value for processing span batches while derivation. + +### Reorganization of constant length transaction fields + +`signature`, `nonce`, `gaslimit`, `to` field are constant size, so these were split up completely and +are grouped into individual arrays. +This adds more complexity, but organizes data for improved compression by grouping data with similar data pattern. + +### RLP encoding for only variable length fields + +Further size optimization can be done by packing variable length fields, such as `access_list`. +However, doing this will introduce much more code complexity, comparing to benefiting by size reduction. + +Our goal is to find the sweet spot on code complexity - span batch size tradeoff. +I decided that using RLP for all variable length fields will be the best option, +not risking codebase with gnarly custom encoding/decoding implementations. + +### Store `y_parity` and `protected_bit` instead of `v` + +Only legacy type transactions can be optionally protected. If protected([EIP-155]), `v = 2 * ChainID + 35 + y_parity`. +Else, `v = 27 + y_parity`. For other types of transactions, `v = y_parity`. +We store `y_parity`, which is single bit per L2 transaction. +We store `protected_bit`, which is single bit per L2 legacy type transactions to indicate that tx is protected. + +This optimization will benefit more when ratio between number of legacy type transactions over number of transactions +excluding deposit tx is higher. +Deposit transactions are excluded in batches and are never written at L1 so excluded while analyzing. + +### Adjust `txs` Data Layout for Better Compression + +There are (7 choose 2) * 5! = 2520 permutations of ordering fields of `txs`. +It is not 7! because `contract_creation_bits` must be first decoded in order to decode `tx_tos`. +We experimented to find out the best layout for compression. +It turned out placing random data together(`TxSigs`, `TxTos`, `TxDatas`), +then placing leftovers helped gzip to gain more size reduction. + +### `fee_recipients` Encoding Scheme + +Let `K` := number of unique fee recipients(cardinality) per span batch. Let `N` := number of L2 blocks. +If we naively encode each fee recipients by concatenating every fee recipients, it will need `20 * N` bytes. +If we manage `fee_recipients_idxs` and `fee_recipients_set`, It will need at most `max uvarint size * N = 8 * N`, +`20 * K` bytes each. If `20 * N > 8 * N + 20 * K` then maintaining an index of fee recipients is reduces the size. + +we thought sequencer rotation happens not much often, so assumed that `K` will be much lesser than `N`. +The assumption makes upper inequality to hold. Therefore, we decided to manage `fee_recipients_idxs` and +`fee_recipients_set` separately. This adds complexity but reduces data. + +## How derivation works with Span Batch? + +- Block Timestamp + - The first L2 block's block timestamp is `rel_timestamp + L2Genesis.Timestamp`. + - Then we can derive other blocks timestamp by adding L2 block time for each. +- L1 Origin Number + - The parent of the first L2 block's L1 origin number is `l1_origin_num - sum(origin_bits)` + - Then we can derive other blocks' L1 origin number with `origin_bits` + - `ith block's L1 origin number = (i-1)th block's L1 origin number + (origin_bits[i] ? 1 : 0)` +- L1 Origin Hash + - We only need the `l1_origin_check`, the truncated L1 origin hash of the last L2 block of Span Batch. + - If the last block references canonical L1 chain as its origin, + we can ensure the all other blocks' origins are consistent with the canonical L1 chain. +- Parent hash + - In V0 Batch spec, we need batch's parent hash to validate if batch's parent is consistent with current L2 safe head. + - But in the case of Span Batch, because it contains consecutive L2 blocks in the span, + we do not need to validate all blocks' parent hash except the first block. +- Transactions + - Deposit transactions can be derived from its L1 origin, identical with V0 batch. + - User transactions can be derived by following way: + - Recover `V` value of TX signature from `y_parity_bits` and L2 chainId, as described in optimization strategies. + - When parsing `tx_tos`, `contract_creation_bits` is used to determine if the TX has `to` value or not. + +## Integration + +### Channel Reader (Batch Decoding) + +The Channel Reader decodes the span-batch, as described in the [span-batch format](#span-batch-format). + +A set of derived attributes is computed as described above. Then cached with the decoded result: + +### Batch Queue + +A span-batch is buffered as a singular large batch, +by its starting timestamp (transformed `rel_timestamp`). + +Span-batches share the same queue with v0 batches: batches are processed in L1 inclusion order. + +A set of modified validation rules apply to the span-batches. + +Rules are enforced with the [contextual definitions](./derivation.md#batch-queue) as v0-batch validation: +`epoch`, `inclusion_block_number`, `next_timestamp` + +Definitions: + +- `batch` as defined in the [Span batch format section][span-batch-format]. +- `prev_l2_block` is the L2 block from the current safe chain, + whose timestamp is at `span_start.timestamp - l2_block_time` + +Span-batch rules, in validation order: + +- `batch_origin` is determined like with singular batches: + - `batch.epoch_num == epoch.number+1`: + - If `next_epoch` is not known -> `undecided`: + i.e. a batch that changes the L1 origin cannot be processed until we have the L1 origin data. + - If known, then define `batch_origin` as `next_epoch` +- `batch_origin.timestamp < span_batch_upgrade_timestamp` -> `drop`: + i.e. enforce the [span batch upgrade activation rule](#span-batch-activation-rule). +- `span_start.timestamp > next_timestamp` -> `future`: i.e. the batch must be ready to process, + but does not have to start exactly at the `next_timestamp`, since it can overlap with previously processed blocks, +- `span_end.timestamp < next_timestamp` -> `drop`: i.e. the batch must have at least one new block to process. +- If there's no `prev_l2_block` in the current safe chain -> `drop`: i.e. the timestamp must be aligned. +- `batch.parent_check != prev_l2_block.hash[:20]` -> `drop`: + i.e. the checked part of the parent hash must be equal to the same part of the corresponding L2 block hash. +- Sequencing-window checks: + - Note: The sequencing window is enforced for the *batch as a whole*: + if the batch was partially invalid instead, it would drop the oldest L2 blocks, + which makes the later L2 blocks invalid. + - Variables: + - `origin_changed_bit = origin_bits[0]`: `true` if the first L2 block changed its L1 origin, `false` otherwise. + - `start_epoch_num = batch.l1_origin_num - sum(origin_bits) + (origin_changed_bit ? 1 : 0)` + - `end_epoch_num = batch.l1_origin_num` + - Rules: + - `start_epoch_num + sequence_window_size < inclusion_block_number` -> `drop`: + i.e. the batch must be included timely. + - `start_epoch_num > prev_l2_block.l1_origin.number + 1` -> `drop`: + i.e. the L1 origin cannot change by more than one L1 block per L2 block. + - If `batch.l1_origin_check` does not match the canonical L1 chain at `end_epoch_num` -> `drop`: + verify the batch is intended for this L1 chain. + - After upper `l1_origin_check` check is passed, we don't need to check if the origin + is past `inclusion_block_number` because of the following invariant. + - Invariant: the epoch-num in the batch is always less than the inclusion block number, + if and only if the L1 epoch hash is correct. + - `start_epoch_num < prev_l2_block.l1_origin.number` -> `drop`: + epoch number cannot be older than the origin of parent block +- Max Sequencer time-drift checks: + - Note: The max time-drift is enforced for the *batch as a whole*, to keep the possible output variants small. + - Variables: + - `block_input`: an L2 block from the span-batch, + with L1 origin as derived from the `origin_bits` and now established canonical L1 chain. + - `next_epoch`: `block_input.origin`'s next L1 block. + It may reach to the next origin outside the L1 origins of the span. + - Rules: + - For each `block_input` whose timestamp is greater than `safe_head.timestamp`: + - `block_input.timestamp < block_input.origin.time` -> `drop`: enforce the min L2 timestamp rule. + - `block_input.timestamp > block_input.origin.time + max_sequencer_drift`: enforce the L2 timestamp drift rule, + but with exceptions to preserve above min L2 timestamp invariant: + - `len(block_input.transactions) == 0`: + - `origin_bits[i] == 0`: `i` is the index of `block_input` in the span batch. + So this implies the block_input did not advance the L1 origin, + and must thus be checked against `next_epoch`. + - If `next_epoch` is not known -> `undecided`: + without the next L1 origin we cannot yet determine if time invariant could have been kept. + - If `block_input.timestamp >= next_epoch.time` -> `drop`: + the batch could have adopted the next L1 origin without breaking the `L2 time >= L1 time` invariant. + - `len(block_input.transactions) > 0`: -> `drop`: + when exceeding the sequencer time drift, never allow the sequencer to include transactions. +- And for all transactions: + - `drop` if the `batch.tx_datas` list contains a transaction + that is invalid or derived by other means exclusively: + - any transaction that is empty (zero length `tx_data`) + - any [deposited transactions][g-deposit-tx-type] (identified by the transaction type prefix byte in `tx_data`) +- Overlapped blocks checks: + - Note: If the span batch overlaps the current L2 safe chain, we must validate all overlapped blocks. + - Variables: + - `block_input`: an L2 block derived from the span-batch. + - `safe_block`: an L2 block from the current L2 safe chain, at same timestamp as `block_input` + - Rules: + - For each `block_input`, whose timestamp is less than `next_timestamp`: + - `block_input.l1_origin.number != safe_block.l1_origin.number` -> `drop` + - `block_input.transactions != safe_block.transactions` -> `drop` + - compare excluding deposit transactions + +Once validated, the batch-queue then emits a block-input for each of the blocks included in the span-batch. +The next derivation stage is thus only aware of individual block inputs, similar to the previous V0 batch, +although not strictly a "v0 batch" anymore. + +### Batcher + +Instead of transforming L2 blocks into batches, +the blocks should be buffered to form a span-batch. + +Ideally the L2 blocks are buffered as block-inputs, to maximize the span of blocks covered by the span-batch: +span-batches of single L2 blocks do not increase efficiency as much as with larger spans. + +This means that the `(c *channelBuilder) AddBlock` function is changed to +not directly call `(co *ChannelOut) AddBatch` but defer that until a minimum number of blocks have been buffered. + +Output-size estimation of the queued up blocks is not possible until the span-batch is written to the channel. +Past a given number of blocks, the channel may be written for estimation, and then re-written if more blocks arrive. + +The [batcher functionality](./batcher.md) stays the same otherwise: unsafe blocks are transformed into batches, +encoded in compressed channels, and then split into frames for submission to L1. +Batcher implementations can implement different heuristics and re-attempts to build the most gas-efficient data-txs. diff --git a/specs/protocol/system-config.md b/specs/protocol/system-config.md new file mode 100644 index 0000000..bf68825 --- /dev/null +++ b/specs/protocol/system-config.md @@ -0,0 +1,110 @@ +# System Config + + + +**Table of Contents** + +- [Overview](#overview) +- [System config contents (version 0)](#system-config-contents-version-0) + - [`batcherHash` (`bytes32`)](#batcherhash-bytes32) + - [`overhead` and `scalar` (`uint256,uint256`)](#overhead-and-scalar-uint256uint256) + - [`gasLimit` (`uint64`)](#gaslimit-uint64) + - [`unsafeBlockSigner` (`address`)](#unsafeblocksigner-address) + - [`validatorRewardScalar` (`uint256`)](#validatorrewardscalar-uint256) +- [Writing the system config](#writing-the-system-config) +- [Reading the system config](#reading-the-system-config) + + + + + +[g-l1]: ../glossary.md#layer-1-l1 +[g-l1-origin]: ../glossary.md#l1-origin +[g-l2]: ../glossary.md#layer-2-l2 + +## Overview + +The `SystemConfig` is a contract on L1 that can emit rollup configuration changes as log events. +The rollup [block derivation process](derivation.md) picks up on these log events and applies the changes. + +## System config contents (version 0) + +Version 0 of the system configuration contract defines the following parameters: + +### `batcherHash` (`bytes32`) + +A versioned hash of the current authorized [batcher](batcher.md) sender(s), to rotate keys as batch-submitter. +The first byte identifies the version. + +Version `0` embeds the current batch submitter ethereum address (`bytes20`) in the last 20 bytes of the versioned hash. + +In the future, this versioned hash may become a commitment to a more extensive configuration, +to enable more extensive redundancy and/or rotation configurations. + +### `overhead` and `scalar` (`uint256,uint256`) + +The L1 fee parameters, also known as [Gas Price Oracle (GPO)](predeploys.md#gaspriceoracle) parameters, +are updated in conjunction and apply new [L1][g-l1] costs to the [L2][g-l2] transactions. + +### `gasLimit` (`uint64`) + +The gas limit of the L2 blocks is configured through the system config. +Changes to the L2 gas limit are fully applied in the first L2 block with the [L1 origin][g-l1-origin] that introduced +the change, as opposed to the 1/1024 adjustments towards a target as seen in limit updates of L1 blocks. + +### `unsafeBlockSigner` (`address`) + +Blocks are gossiped around the p2p network before they are made available on L1. +To prevent denial of service on the p2p layer, these unsafe blocks must be +signed with a particular key to be accepted as "canonical" unsafe blocks. +The address corresponding to this key is the `unsafeBlockSigner`. To ensure +that its value can be fetched with a storage proof in a storage layout independent +manner, it is stored at a special storage slot corresponding to +`keccak256("systemconfig.unsafeblocksigner")`. + +Unlike the other values, the `unsafeBlockSigner` only operates on blockchain +policy. It is not a consensus level parameter. + +### `validatorRewardScalar` (`uint256`) + +The scalar value to distribute [transaction fees](./exec-engine.md#transaction-fees) on L2 to validators as a reward of +checkpoint output submission. The denominator is 10000. + +## Writing the system config + +The `SystemConfig` contract applies authentication to all writing contract functions, +the configuration management can be configured to be any type of ethereum account or contract. + +On a write, an event is emitted for the change to be picked up by the L2 system, +and a copy of the new written configuration variable is retained in L1 state to read with L1 contracts. + +## Reading the system config + +A rollup node initializes its derivation process by finding a starting point based on its past L2 chain: + +- When started from L2 genesis, the initial system configuration is retrieved from the rollup chain configuration. +- When started from an existing L2 chain, a previously included L1 block is determined as derivation starting point, + and the system config can thus be retrieved from the last L2 block that referenced the L1 block as L1 origin: + - `batcherHash`, `overhead` and `scalar` are retrieved from the L1 block info transaction. + - `gasLimit` is retrieved from the L2 block header. + - other future variables may also be retrieved from other contents of the L2 block, such as the header. + +After preparing the initial system configuration for the given L1 starting input, +the system configuration is updated by processing all receipts from each new L1 block. + +The contained log events are filtered and processed as follows: + +- the log event contract address must match the rollup `SystemConfig` deployment +- the first log event topic must match the ABI hash of `ConfigUpdate(uint256,uint8,bytes)` +- the second topic determines the version. Unknown versions are critical derivation errors. +- the third topic determines the type of update. Unknown types are critical derivation errors. +- the remaining event data is opaque, encoded as ABI bytes (i.e. includes offset and length data), + and encodes the configuration update. In version `0` the following types are supported: + - type `0`: `batcherHash` overwrite, as `bytes32` payload. + - type `1`: `overhead` and `scalar` overwrite, as two packed `uint256` entries. + - type `2`: `gasLimit` overwrite, as `uint64` payload. + - type `3`: `unsafeBlockSigner` overwrite, as `address` payload. + +Note that individual derivation stages may be processing different L1 blocks, +and should thus maintain individual system configuration copies, +and apply the event-based changes as the stage traverses to the next L1 block. diff --git a/specs/protocol/validator.md b/specs/protocol/validator.md new file mode 100644 index 0000000..bdde3a0 --- /dev/null +++ b/specs/protocol/validator.md @@ -0,0 +1,356 @@ +# Validations + + + +**Table of Contents** + +- [Submitting L2 Output Commitments](#submitting-l2-output-commitments) +- [L2 Output Commitment Construction](#l2-output-commitment-construction) + - [Output Payload(Version 0)](#output-payloadversion-0) +- [L2 Output Oracle Smart Contract](#l2-output-oracle-smart-contract) + - [Configuration of L2OutputOracle](#configuration-of-l2outputoracle) +- [Validator Pool Smart Contract](#validator-pool-smart-contract) + - [Validation Rewards](#validation-rewards) + - [Configuration of ValidatorPool](#configuration-of-validatorpool) +- [Security Considerations](#security-considerations) + - [L1 Reorgs](#l1-reorgs) +- [Summary of Definitions](#summary-of-definitions) + - [Constants](#constants) + + + + + +[g-l1]: ../glossary.md#layer-1-l1 +[g-l2]: ../glossary.md#layer-2-l2 +[g-zk-fault-proof]: ../glossary.md#zk-fault-proof +[g-zktrie]: ../glossary.md#zk-trie +[g-l2-output]: ../glossary.md#l2-output-root +[g-validator]: ../glossary.md#validator +[g-priority-round]: ../glossary.md#priority-round +[g-public-round]: ../glossary.md#public-round + +## Overview + +![Validation Overview](../static/assets/verifier-proving-fault-proof.svg) + +After processing one or more blocks, the outputs will need to be synchronized with [L1][g-l1] for trustless execution of +L2-to-L1 messaging, such as withdrawals. +These output proposals act as the bridge's view into the L2 state. +Actors called "Validators" submit the output roots to L1 and can be contested with a [ZK fault proof][g-zk-fault-proof], +with a bond at stake if the proof is wrong. + +## Submitting L2 Output Commitments + +The validator's role is to construct and submit output roots, which are commitments to the L2's state, +to the `L2OutputOracle` contract on L1. To do this, the validator periodically +queries the [rollup node](rollup-node.md) for the latest output root derived from the latest +[finalized][finality] L1 block. It then takes the output root and +submits it to the `L2OutputOracle` contract on L1. + +[finality]: https://hackmd.io/@prysmaticlabs/finality + +The validator that submits the output root is determined by the `ValidatorPool` contract on L1. +The output submission rounds are divided into [Priority Round][g-priority-round] and [Public Round][g-public-round], +and the time limit of each round is configured as `ROUND_DURATION` in the ValidatorPool contract. +A prioritized validator is selected by a random function in the `ValidatorPool` contract, and the prioritized validator +must submit output within the `Priority Round` time. +If the prioritized validator fails to submit within the `Priority Round`, the round moves to the `Public Round`, where +all validators can submit output regardless of priority. + +The validator is expected to submit output roots on a deterministic +interval based on the configured `SUBMISSION_INTERVAL` in the `L2OutputOracle`. The larger +the `SUBMISSION_INTERVAL`, the less often L1 transactions need to be sent to the `L2OutputOracle` +contract, but L2 users will need to wait a bit longer for an output root to be included in L1 +that includes their intention to withdrawal from the system. + +The honest `kroma-validator` algorithm assumes a connection to the `L2OutputOracle` contract to know +the L2 block number that corresponds to the next output root that must be submitted. It also +assumes a connection to an `op-node` to be able to query the `kroma_syncStatus` RPC endpoint. + +Once your submitted output is [finalized][finality], the submitter becomes eligible for a reward. +For more information on this, see [Validation Rewards](#validation-rewards). + +```python +import time + +while True: + next_checkpoint_block = L2OutputOracle.nextBlockNumber() + rollup_status = kroma_node_client.sync_status() + if rollup_status.finalized_l2.number >= next_checkpoint_block: + output = kroma_node_client.output_at_block(next_checkpoint_block) + tx = send_transaction(output) + time.sleep(poll_interval) +``` + +## L2 Output Commitment Construction + +The `output_root` is a 32bytes string, which is derived based on a versioned scheme: + +```pseudocode +output_root = keccak256(version_byte || payload) +``` + +where: + +1. `version_byte` (`bytes32`) a simple version string which increments anytime the construction of the output root + is changed. + +2. `payload` (`bytes`) is a byte string of arbitrary length. + +### Output Payload(Version 0) + +The version 0 payload is defined as: + +```pseudocode +payload = state_root || withdrawal_storage_root || block_hash || next_block_hash +``` + +where: + +1. The `block_hash` (`bytes32`) is the block hash for the [L2][g-l2] block that the output is generated from. + +2. The `state_root` (`bytes32`) is the [ZK-Trie][g-zktrie] root of all execution-layer accounts. + This value is frequently used and thus elevated closer to the L2 output root, which removes the need to prove its + inclusion in the pre-image of the `block_hash`. This reduces the merkle proof depth and cost of accessing the + L2 state root on L1. + +3. The `withdrawal_storage_root` (`bytes32`) elevates the ZK Trie root of the + [L2ToL1MessagePasser contract](withdrawals.md#the-l2tol1messagepasser-contract) storage. Instead of making a + [ZKT][g-zktrie] proof for a withdrawal against the state root (proving first the storage root of the + L2toL1MessagePasser against the state root, then the withdrawal against that storage root), we can prove against the + L2toL1MessagePasser's storage root directly, thus reducing the verification cost of withdrawals on L1. + +4. The `next_block_hash` (`bytes32`) is the next block hash for the block that is next to the `block_hash`. + +The height of the block where the output is submitted has been delayed by one. + +## L2 Output Oracle Smart Contract + +L2 blocks are produced at a constant rate of `L2_BLOCK_TIME`. +A new L2 output MUST be appended to the chain once per `SUBMISSION_INTERVAL` which is based on the number of L2 blocks. + +L2 Output Oracle Smart Contract implements the following interface: + +```solidity +interface L2OutputOracle { + event OutputSubmitted( + bytes32 indexed outputRoot, + uint256 indexed l2OutputIndex, + uint256 indexed l2BlockNumber, + uint256 l1Timestamp + ); + + event OutputReplaced(uint256 indexed outputIndex, bytes32 newOutputRoot); + + function replaceL2Output( + uint256 _l2OutputIndex, + bytes32 _newOutputRoot, + address _submitter + ) external; + + function submitL2Output( + bytes32 _outputRoot, + uint256 _l2BlockNumber, + bytes32 _l1BlockHash, + uint256 _l1BlockNumber + ) external payable; +} +``` + +### Configuration of L2OutputOracle + +The `startingBlockNumber` must be at least the number of the first recorded L2 block. +The `startingTimestamp` MUST be the same as the timestamp of the first recorded L2 block. + +Thus, the first `outputRoot` submitted will be at height `startingBlockNumber`, and each subsequent one will be at +height incremented by `SUBMISSION_INTERVAL`. + +## Validator Pool Smart Contract + +Only accounts registered as [Validator][g-validator] can submit [output][g-l2-output] to +the [L2 Output Oracle](#l2-output-oracle-smart-contract). +To register as a [Validator][g-validator], you must deposit at least `REQUIRED_BOND_AMOUNT` of ETH into +the `ValidatorPool` contract. +When submitting the output, the validator must bond Ethereum for `REQUIRED_BOND_AMOUNT`, which will be unbonded and +rewarded to the L2 `ValidatorRewardVault` contract when the output is finalized. + +Also, validators should stake their bond for disputing challenge. This bond will be given to the winner of the challenge +as a reward. When this reward distributed, a [tax](../fault-proof/challenge.md) is imposed to prevent collusive attacks of asserter +and challenger. + +Validator Pool Smart Contract implements the following interface: + +```solidity +interface ValidatorPool { + /** + * @notice Emitted when a validator bonds. + * + * @param submitter Address of submitter. + * @param outputIndex Index of the L2 checkpoint output index. + * @param amount Amount of bonded. + * @param expiresAt The expiration timestamp of bond. + */ + event Bonded( + address indexed submitter, + uint256 indexed outputIndex, + uint128 amount, + uint128 expiresAt + ); + + /** + * @notice Emitted when the pending bond is added. + * + * @param outputIndex Index of the L2 checkpoint output. + * @param challenger Address of the challenger. + * @param amount Amount of bond added. + */ + event PendingBondAdded(uint256 indexed outputIndex, address indexed challenger, uint128 amount); + + /** + * @notice Emitted when the bond is increased. + * + * @param outputIndex Index of the L2 checkpoint output. + * @param challenger Address of the challenger. + * @param amount Amount of bond increased. + */ + event BondIncreased(uint256 indexed outputIndex, address indexed challenger, uint128 amount); + + /** + * @notice Emitted when the pending bond is released(refunded). + * + * @param outputIndex Index of the L2 checkpoint output. + * @param challenger Address of the challenger. + * @param recipient Address to receive amount from a pending bond. + * @param amount Amount of bond released. + */ + event PendingBondReleased( + uint256 indexed outputIndex, + address indexed challenger, + address indexed recipient, + uint128 amount + ); + + /** + * @notice Emitted when a validator unbonds. + * + * @param outputIndex Index of the L2 checkpoint output. + * @param recipient Address of the recipient. + * @param amount Amount of unbonded. + */ + event Unbonded(uint256 indexed outputIndex, address indexed recipient, uint128 amount); + + /** + * @notice Deposit ETH to be used as bond. + */ + function deposit() external payable; + + /** + * @notice Withdraw a given amount. + * + * @param _amount Amount to withdraw. + */ + function withdraw(uint256 _amount) external; + + /** + * @notice Bond asset corresponding to the given output index. + * This function is called when submitting output. + * + * @param _outputIndex Index of the L2 checkpoint output. + * @param _expiresAt The expiration timestamp of bond. + */ + function createBond( + uint256 _outputIndex, + uint128 _expiresAt + ) external; + + /** + * @notice Adds a pending bond to the challenge corresponding to the given output index and challenger address. + * The pending bond is added to the bond when the challenge is proven or challenger is timed out, + * or refunded when the challenge is canceled. + * + * @param _outputIndex Index of the L2 checkpoint output. + * @param _challenger Address of the challenger. + */ + function addPendingBond(uint256 _outputIndex, address _challenger) external; + + /** + * @notice Releases the corresponding pending bond to the given output index and challenger address + * if a challenge is canceled. + * + * @param _outputIndex Index of the L2 checkpoint output. + * @param _challenger Address of the challenger. + * @param _recipient Address to receive amount from a pending bond. + */ + function releasePendingBond( + uint256 _outputIndex, + address _challenger, + address _recipient + ) external; + + /** + * @notice Increases the bond amount corresponding to the given output index by the pending bond amount. + * This is when taxes are charged, and note that taxes are a means of preventing collusive attacks by + * the asserter and challenger. + * + * @param _outputIndex Index of the L2 checkpoint output. + * @param _challenger Address of the challenger. + */ + function increaseBond(uint256 _outputIndex, address _challenger) external; + + /** + * @notice Attempt to unbond. Reverts if unbond is not possible. + */ + function unbond() external; + + /** + * @notice Returns the balance of given address. + * + * @param _addr Address of validator. + * + * @return Balance of given address. + */ + function balanceOf(address _addr) external view returns (uint256); + + /** + * @notice Determines who can submit the L2 output next. + * + * @return The address of the validator. + */ + function nextValidator() public view returns (address); +} +``` + +### Validation Rewards + +A validator who submits an output can receive a reward from L2 `ValidatorRewardVault` contract when the output is +finalized, it is called validation reward. When the output is finalized, the `ValidatorPool` contract sends a message +to pay the reward in the L2 `ValidatorRewardVault` via the `KromaPortal` contract. +The reward is calculated by `ValidatorRewardVault` divided by `REWARD_DIVIDER`, which is the number of outputs +in a week. +Rewards received by the validator can be withdrawn to L1 via `withdraw()` in the ValidatorRewardVault. + +### Configuration of ValidatorPool + +`ROUND_DURATION` is equal to `(L2_BLOCK_TIME * SUBMISSION_INTERVAL) / 2`. + +## Security Considerations + +### L1 Reorgs + +If the L1 has a reorg after an output has been generated and submitted, the L2 state and correct output may change +leading to a misbehavior. This is mitigated against by allowing the validator to submit an +L1 block number and hash to the [L2 Output Oracle](#l2-output-oracle-smart-contract) when appending a new output; +in the event of a reorg, the block hash will not match that of the block with that number and the call will revert. + +## Summary of Definitions + +### Constants + +| Name | Value | Unit | +|------------------------|--------|----------------| +| `SUBMISSION_INTERVAL` | `1800` | blocks | +| `L2_BLOCK_TIME` | `2` | seconds | +| `REQUIRED_BOND_AMOUNT` | TBD | wei | +| `REWARD_DIVIDER` | `168` | num of outputs | +| `ROUND_DURATION` | `30` | minutes | diff --git a/specs/protocol/withdrawals.md b/specs/protocol/withdrawals.md new file mode 100644 index 0000000..be004d1 --- /dev/null +++ b/specs/protocol/withdrawals.md @@ -0,0 +1,225 @@ +# Withdrawals + + + +**Table of Contents** + +- [Overview](#overview) +- [Withdrawal Flow](#withdrawal-flow) + - [On L2](#on-l2) + - [On L1](#on-l1) +- [The L2ToL1MessagePasser Contract](#the-l2tol1messagepasser-contract) + - [Addresses are not Aliased on Withdrawals](#addresses-are-not-aliased-on-withdrawals) +- [The Kroma Portal Contract](#the-kroma-portal-contract) +- [Withdrawal Verification and Finalization](#withdrawal-verification-and-finalization) +- [Security Considerations](#security-considerations) + - [Key Properties of Withdrawal Verification](#key-properties-of-withdrawal-verification) + - [Handling Successfully Verified Messages That Fail When Relayed](#handling-successfully-verified-messages-that-fail-when-relayed) +- [Summary of Definitions](#summary-of-definitions) + - [Constants](#constants) + + + + + + +[g-deposits]: ../glossary.md#deposits +[g-execution-engine]: ../glossary.md#execution-engine +[g-l1]: ../glossary.md#layer-1-l1 +[g-l2]: ../glossary.md#layer-2-l2 +[g-relayer]: ../glossary.md#relayer +[g-withdrawal]: ../glossary.md#withdrawal + +## Overview + +[Withdrawals][g-withdrawal] are cross domain transactions which are initiated on [L2][g-l2], and finalized by +a transaction executed on [L1][g-l1]. Notably, withdrawals may be used by an L2 account to call an L1 contract, or to +transfer ETH from an L2 account to an L1 account. + +**Vocabulary note**: _withdrawal_ can refer to the transaction at various stages of the process, but we introduce +more specific terms to differentiate: + +- A _withdrawal initiating transaction_ refers specifically to a transaction on L2 sent to the Withdrawals predeploy. +- A _withdrawal finalizing transaction_ refers specifically to an L1 transaction which finalizes and relays the + withdrawal. + +Withdrawals are initiated on L2 via a call to the Message Passer predeploy contract, which records the important +properties of the message in its storage. Withdrawals are finalized on L1 via a call to the `KromaPortal` +contract, which proves the inclusion of this withdrawal message. + +In this way, withdrawals are different from [deposits][g-deposits] which make use of a special transaction type in the +[execution engine][g-execution-engine] client. Rather, withdrawals transaction must use smart contracts on L1 for +finalization. + +## Withdrawal Flow + +We first describe the end to end flow of initiating and finalizing a withdrawal. + +### On L2 + +An L2 account sends a withdrawal message (and possibly also ETH) to the `L2ToL1MessagePasser` predeploy contract. +This is a very simple contract that stores the hash of the withdrawal data. + +### On L1 + +1. A [relayer][g-relayer] submits the required inputs to the `KromaPortal` contract. The relayer need + not be the same entity which initiated the withdrawal on L2. + These inputs include the withdrawal transaction data, inclusion proofs, and a block number. The block number + must be one for which an L2 output root exists, which commits to the withdrawal as registered on L2. +2. The `KromaPortal` contract retrieves the output root for the given block number from the `L2OutputOracle`'s + `getL2OutputAfter()` function, and performs the remainder of the verification process internally. +3. If proof verification fails, the call reverts. Otherwise the hash is recorded to prevent it from being re-proven. + Note that the withdrawal can be proven more than once if the corresponding output root changes. +4. After the withdrawal is proven, it enters a 7 day challenge period, allowing time for other network participants + to challenge the integrity of the corresponding output root. +5. Once the challenge period has passed, a relayer submits the withdrawal transaction once again to the + `KromaPortal` contract. Again, the relayer need not be the same entity which initiated the withdrawal on L2. +6. The `KromaPortal` contract receives the withdrawal transaction data and verifies that the withdrawal has + both been proven and passed the challenge period. +7. If the requirements are not met, the call reverts. Otherwise the call is forwarded, and the hash is recorded to + prevent it from being replayed. + +## The L2ToL1MessagePasser Contract + +A withdrawal is initiated by calling the L2ToL1MessagePasser contract's `initiateWithdrawal` function. +The L2ToL1MessagePasser is a simple predeploy contract at `0x4200000000000000000000000000000000000003` +which stores messages to be withdrawn. + +The L2ToL1MessagePasser contract implements the following interface: + +```solidity +interface L2ToL1MessagePasser { + event MessagePassed( + uint256 indexed nonce, // this is a global nonce value for all withdrawal messages + address indexed sender, + address indexed target, + uint256 value, + uint256 gasLimit, + bytes data, + bytes32 withdrawalHash + ); + + event WithdrawerBalanceBurnt(uint256 indexed amount); + + function burn() external; + + function initiateWithdrawal( + address _target, + uint256 _gasLimit, + bytes memory _data + ) payable external; + + function messageNonce() external view returns (uint256); + + function sentMessages(bytes32) external view returns (bool); +} +``` + +The `MessagePassed` event includes all of the data that is hashed and +stored in the `sentMessages` mapping, as well as the hash itself. + +### Addresses are not Aliased on Withdrawals + +When a contract makes a deposit, the sender's address is [aliased](./deposits.md#address-aliasing). The same is not true +of withdrawals, which do not modify the sender's address. The difference is that: + +- on L2, the deposit sender's address is returned by the `CALLER` opcode, meaning a contract cannot easily tell if the + call originated on L1 or L2, whereas +- on L1, the withdrawal sender's address is accessed by calling the `l2Sender()` function on the `KromaPortal` + contract. + +Calling `l2Sender()` removes any ambiguity about which domain the call originated from. Still, developers will need to +recognize that having the same address does not imply that a contract on L2 will behave the same as a contract on L1. + +## The Kroma Portal Contract + +The Kroma Portal serves as both the entry and exit point to the Kroma L2. It is a contract which inherits from +the [KromaPortal](./deposits.md#deposit-contract) contract, and in addition provides the following interface for +withdrawals: + +- [`WithdrawalTransaction` type] +- [`OutputRootProof` type] + +```solidity +interface KromaPortal { + event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success); + + function l2Sender() external returns (address); + + function proveWithdrawalTransaction( + Types.WithdrawalTransaction memory _tx, + uint256 _l2OutputIndex, + Types.OutputRootProof calldata _outputRootProof, + bytes[] calldata _withdrawalProof + ) external; + + function finalizeWithdrawalTransaction( + Types.WithdrawalTransaction memory _tx + ) external; +} +``` + +## Withdrawal Verification and Finalization + +The following inputs are required to prove and finalize a withdrawal: + +- Withdrawal transaction data: + - `nonce`: Nonce for the provided message. + - `sender`: Message sender address on L2. + - `target`: Target address on L1. + - `value`: ETH to send to the target. + - `data`: Data to send to the target. + - `gasLimit`: Gas to be forwarded to the target. +- Proof and verification data: + - `l2BlockNumber`: The L2 block number that corresponds to the output root. + - `outputRootProof`: Four `bytes32` values which are used to derive the output root. + - `withdrawalProof`: An inclusion proof for the given withdrawal in the L2ToL1MessagePasser contract. + +These inputs must satisfy the following conditions: + +1. The `l2BlockNumber` must be the block number that corresponds to the `CheckpointOutput` being proven. +2. `L2OutputOracle.getL2OutputAfter(l2BlockNumber)` returns a non-zero `CheckpointOutput`. +3. The keccak256 hash of the `outputRootProof` values is equal to the `outputRoot`. +4. The `withdrawalProof` is a valid inclusion proof demonstrating that a hash of the Withdrawal transaction data + is contained in the storage of the [L2ToL1MessagePasser contract](#the-l2tol1messagepasser-contract) on L2. + +## Security Considerations + +### Key Properties of Withdrawal Verification + +1. It should not be possible to 'double spend' a withdrawal, ie. to relay a withdrawal on L1 which does not + correspond to a message initiated on L2. For reference, see [this writeup][polygon-dbl-spend] of a vulnerability + of this type found on Polygon. + + [polygon-dbl-spend]: https://gerhard-wagner.medium.com/double-spending-bug-in-polygons-plasma-bridge-2e0954ccadf1 + +2. For each withdrawal initiated on L2 (ie. with a unique `nonce`), the following properties must hold: + 1. It should only be possible to prove the withdrawal once, unless the outputRoot for the withdrawal + has changed. + 2. It should only be possible to finalize the withdrawal once. + 3. It should not be possible to relay the message with any of its fields modified, ie. + 1. Modifying the `sender` field would enable a 'spoofing' attack. + 2. Modifying the `target`, `message`, or `value` fields would enable an attacker to dangerously change the + intended outcome of the withdrawal. + 3. Modifying the `gasLimit` could make the cost of relaying too high, or allow the relayer to cause execution + to fail (out of gas) in the `target`. + +### Handling Successfully Verified Messages That Fail When Relayed + +If the execution of the relayed call fails in the `target` contract, it is unfortunately not possible to determine +whether or not it was 'supposed' to fail, and whether or not it should be 'replayable'. For this reason, and to +minimize complexity, we have not provided any replay functionality, this may be implemented in external utility +contracts if desired. + +## Summary of Definitions + +### Constants + +| Name | Value | Unit | +| --------------------- | --------- | ------- | +| `FINALIZATION_PERIOD` | `604_800` | seconds | + +This `FINALIZATION_PERIOD` value is equivalent to 7 days. + +[`WithdrawalTransaction` type]: https://github.com/kroma-network/kroma/blob/7a4126dcc30d10f4ebf881f681296d3fbe308eaf/packages/contracts/contracts/libraries/Types.sol#L131-L138 +[`OutputRootProof` type]: https://github.com/kroma-network/kroma/blob/7a4126dcc30d10f4ebf881f681296d3fbe308eaf/packages/contracts/contracts/libraries/Types.sol#L34-L40 diff --git a/specs/root.md b/specs/root.md new file mode 100644 index 0000000..dce74e7 --- /dev/null +++ b/specs/root.md @@ -0,0 +1,63 @@ + +# Kroma specs + +This directory contains the plain english specs for Kroma, a minimal optimistic rollup protocol +that maintains 1:1 compatibility with Ethereum. + + + + +**Table of Contents** + +- [Specification Contents](#specification-contents) +- [Design Goals](#design-goals) + + + +## Specification Contents + +- [Introduction](./introduction.md) +- [Overview](./protocol/overview.md) +- [Deposits](./protocol/deposits.md) +- [Withdrawals](./protocol/withdrawals.md) +- [Execution Engine](./protocol/exec-engine.md) +- [Batcher](./protocol/batcher.md) +- [L2 Output Root Submissions](./protocol/validator.md) +- [Rollup Node](./protocol/rollup-node.md) +- [Rollup Node P2P](./protocol/rollup-node-p2p.md) +- [L2 Chain Derivation](./protocol/derivation.md) +- [Guaranteed Gas Market](./protocol/guaranteed-gas-market.md) +- [Messengers](./protocol/messengers.md) +- [Bridges](./protocol/bridges.md) +- [Predeploys](./protocol/predeploys.md) +- [Challenge](./fault-proof/challenge.md) +- [zkEVM Prover](./fault-proof/zkevm-prover.md) +- [SystemConfig](./protocol/system-config.md) +- [Glossary](./glossary.md) + +## Design Goals + +Our aim is to design a protocol specification that is: + +- **Fast:** When users send transactions, they get reliable confirmations with low-latency. + For example when swapping on Uniswap you should see that your transaction succeeds in less than 2 + seconds. +- **Scalable:** It should be possible to handle an enormous number of transactions + per second which will enable the system to charge low fees. +- **Modular:** Our designs will use modularity to reduce complexity and enable parallel + contributions. Coming up with good conceptual frameworks & composable atoms of software enables us + to build extremely complex software even when any one person cannot hold that much in their brain. +- **Minimal:** Rollups should be minimal to best take advantage of the battle-tested infrastructure + (like Geth) that already runs Ethereum. An ideal optimistic rollup design should be representable + as a *diff* against Ethereum client software. +- **Developer Driven:** Our designs will be developer driven to ensure we are actually building + something that people want to use. We must constantly engage with the developers who will be using + our software to avoid creating a system no one wants to use. +- **Clear and Readable:** The specs we write are written to be read. So tight feedback loop with the + systems team consuming the spec is also key! +- **Secure:** This is self-evident. + User’s assets are at stake. Every component of the system must be incredibly secure. +- **Decentralizable:** Kroma must be designed to avail itself of the security and + censorship-resistant guarantees achieved by a decentralized system. + Currently centralized components of the system should have a clear path towards decentralization. + Already decentralized components of the system should be protected and preserved. diff --git a/specs/static/assets/architecture.svg b/specs/static/assets/architecture.svg new file mode 100644 index 0000000..ec7fb40 --- /dev/null +++ b/specs/static/assets/architecture.svg @@ -0,0 +1,4 @@ + + + +

L2

L2

L1

L1
KromaPortal
KromaPortal

Execution
Engine

Execution...

Rollup Node
(sequencer mode)

Rollup Node...

Batcher

Batcher
Sequencer
Sequencer
Validator
Validator

State Sync

State Sync

TX Sync

TX Sync

Unsafe Block Sync

Unsafe Block Sync

get
sync status

get...

get block

get block

derivation

derivation

Execution
Engine

Execution...

Rollup Node
(syncer mode)

Rollup Node...

derivation

derivation

Validator
(proposer or
challenger)

Validator...

ZK Prover

ZK Prover

get proof

get proof

get
output

get...
BatchInbox
BatchInbox

submit
batch

submit...
ZKVerifier
ZKVerifier
Colosseum
Colosseum
L2OutputOracle
L2OutputOracle

verify ZK proof

verify ZK proof

create challenge &
bisect &
submit ZK fault proof

create challenge &...

read output 

read output 

submit output

submit output

read
batches

read...

submit transactions

submit transactions

submit deposits

submit deposits

get execution
trace

get execution...
read
deposit
events
read...
read
deposit
events
read...

delete
output

delete...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/specs/static/assets/batch-deriv-chain.svg b/specs/static/assets/batch-deriv-chain.svg new file mode 100644 index 0000000..fbf40c5 --- /dev/null +++ b/specs/static/assets/batch-deriv-chain.svg @@ -0,0 +1,4 @@ + + + +
99
99
100
100
101
101
102
102
engine_forkChoiceUpdatedV1
engine_forkChoiceUpdatedV1
B1
B1
A0
A0
deposit
deposit
deposit
deposit
Deposits are L1 log events,
parsed from EVM receipts
Deposits are L1 log events,...
A1
A1
B0
B0
B2
B2
Batches can be buffered
for up to a full sequencing window
worth of L1 blocks
to get the L2 ordering back.
Batches can be buffered...
3
3
4
4
5
5
6
6
7
7
8
8
9
9
10
10
11
11
Batches,
1 batch = 1 L2 block tx list
Batches,...
L2 Blocks,
(= execution payloads)
L2 Blocks,...
99-2
99-2
99-3
99-3
Deposits get included
the first L2 block that
adopts the L1 origin the
deposits were made in.
Deposits get included...
99-4
99-4
99-5
99-5
100-0
100-0
100-1
100-1
100-2
100-2
100-3
100-3
101-0
101-0
deposit
deposit
deposit
deposit
100-4
100-4
L1 Blocks
These may not be as
frequent/consistent
as L2 blocks.
L1 Blocks...
Actual inclusion on L1:
channels are valid within a timeout
Actual inclusion on L1:...
Compressed & encoded batch data
Compressed & encoded batch data
A0
A0
Channels,
with timeout

Channels,...
L1 Transactions,
~ 128 KB each

L1 Transactions,...
A1
A1
B0
B0
B2
B2
Channel A, Frame 0
Channel A, Frame 0
Channel A,
Frame 1

Channel A,...
Channel B,
Frame 0

Channel B,...
Channel B, Frame 1
Channel B, Frame 1
Channel B, Frame 2
Channel B, Frame 2
...
...
...
...
...
...
...
...
Channel B was seen first,
and will be decoded into batches first.
Channel B was seen first,...
B1
B1
Text is not SVG - cannot display
\ No newline at end of file diff --git a/specs/static/assets/colosseum-state-diagram.svg b/specs/static/assets/colosseum-state-diagram.svg new file mode 100644 index 0000000..6c4baa7 --- /dev/null +++ b/specs/static/assets/colosseum-state-diagram.svg @@ -0,0 +1,4 @@ + + + +

READY_TO_PROVE

READY_TO_PROVE

PROVEN

PROVEN

CHALLENGER_TIMEOUT

CHALLENGER_TIMEOUT

CHALLENGER_TURN

CHALLENGER_TURN
when isAbleToBisect() returns false
when isAbleToBisect() returns false
proveFault()=true
proveFault()=true
proving timeout
proving timeout
proveFault()=false
proveFault()=false
createChallenge()
createChallenge()

OUTPUT_DELETED

OUTPUT_DELETED

ASSERTER_TIMEOUT

ASSERTER_TIMEOUT
challengerTimeout()
challengerTimeout()
bisection timeout
bisection timeout

ASSERTER_TURN

ASSERTER_TURN

CHALLENGE_FAIL

CHALLENGE_FAIL
bisection timeout
bisection timeout
bisect()
bisect()

OUTPUT_DELETED

OUTPUT_DELETED
output already deleted
output already deleted
output already deleted
output already deleted
cancelChallenge()
cancelChallenge()

OUTPUT_ROLLBACKED

OUTPUT_ROLLBACKED
deleted output was invalid
deleted output was invalid
when deleted output was valid
dismissChallenge()
when deleted output was valid...
Text is not SVG - cannot display
diff --git a/specs/static/assets/components.svg b/specs/static/assets/components.svg new file mode 100644 index 0000000..128950b --- /dev/null +++ b/specs/static/assets/components.svg @@ -0,0 +1,4 @@ + + + +
ZKVerifier
ZKVerifier
Colosseum
Colosseum
L2OutputOracle
L2OutputOracle
KromaPortal
KromaPortal
BatchInbox
BatchInbox

L2

L2

L1

L1

Validator

Validator

Execution Engine

Execution Engin...

Rollup Node

Rollup Node

Batcher

Batcher
Sequencer
Sequencer
Validator
Validator

Rollup Node

Rollup Node

Execution Engine

Execution Engine

ZK Prover

ZK Prover
Text is not SVG - cannot display
\ No newline at end of file diff --git a/specs/static/assets/engine.svg b/specs/static/assets/engine.svg new file mode 100644 index 0000000..ff96fd1 --- /dev/null +++ b/specs/static/assets/engine.svg @@ -0,0 +1,4 @@ + + + +
Rollup Driver
Rollup Driver
Engine API
Engine API
PayloadAttributes

- timestamp
- random
- suggestedFeeRecipient
- transactions
PayloadAttributes...
ForkChoiceState

- headBlockHash
- safeBlockHash
- finalizedBlockHash
ForkChoiceState...
FCS
FCS
PA
PA
payloadID
payloadID
Initiate block
production
Initiate block...
engine_forkchoiceUpdatedV2
engine_forkchoiceUpdatedV2
payload
payload
engine_getPayloadV2
(payloadID)
engine_getPayloadV2...
engine_newPayloadV2
(payload)
engine_newPayloadV2...
engine_forkchoiceUpdatedV2
engine_forkchoiceUpdatedV2
FCS
FCS
Current L2
block hash
Current L2...
payload.blockHash
payload.blockHash
Text is not SVG - cannot display
diff --git a/specs/static/assets/network-participants-overview.svg b/specs/static/assets/network-participants-overview.svg new file mode 100644 index 0000000..46c745b --- /dev/null +++ b/specs/static/assets/network-participants-overview.svg @@ -0,0 +1,4 @@ + + + +
Kroma L2 Network
Kroma L2 Network
Ethereum L1 Chain
Ethereum L1 Chain
Sequencer
Sequencer
Full node
Full node

Submit deposits

Submit deposits
Submit batches
Submit batches
Assert checkpoint outputs
and submit ZK fault proofs when challenging invalid outputs
Assert checkpoint outputs...
Submit
transactions
Submit...
Validators
Validato...

P2P Realtime Updates

P2P Realtime Updates
Users
Users

P2P Realtime Updates

P2P Realtime Updates

P2P Realtime Updates

P2P Realtime Updates
Query data
(e.g, block explorer)
Query data...
Read blocks and outputs
Read blocks and outp...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/specs/static/assets/propagation.svg b/specs/static/assets/propagation.svg new file mode 100644 index 0000000..5f4cb29 --- /dev/null +++ b/specs/static/assets/propagation.svg @@ -0,0 +1,4 @@ + + + +

L2

L2

L1

L1
KromaPortal
KromaPortal
BatchInbox
BatchInbox

Validator

Validator

Execution
Engine

Execution...

Rollup Node

Rollup Node

Batcher

Batcher
Sequencer
Sequencer
Validator
Validator

Rollup Node

Rollup Node

Execution
Engine

Execution...

Unsafe Block Sync

Unsafe Block Sync

State Sync

State Sync

TX Sync

TX Sync
Text is not SVG - cannot display
\ No newline at end of file diff --git a/specs/static/assets/sequencer-block-gen.svg b/specs/static/assets/sequencer-block-gen.svg new file mode 100644 index 0000000..dd50818 --- /dev/null +++ b/specs/static/assets/sequencer-block-gen.svg @@ -0,0 +1,4 @@ + + + +
Epoch 1
Epoch 1
Epoch 2
Epoch 2
Epoch 3
Epoch 3
D1
D1
B1
B1
D1
D1
B1
B1
B2
B2
B2
B2
D2
D2
B3
B3
D2
D2
B4
B4
B5
B5
B3
B3
D3
D3
B4
B4
B5
B5
D3
D3
B6
B6
B7
B7
B7
B7
B6
B6
Text is not SVG - cannot display
\ No newline at end of file diff --git a/specs/static/assets/sequencer-handling-deposits-and-transactions.svg b/specs/static/assets/sequencer-handling-deposits-and-transactions.svg new file mode 100644 index 0000000..7a90e77 --- /dev/null +++ b/specs/static/assets/sequencer-handling-deposits-and-transactions.svg @@ -0,0 +1,4 @@ + + + +
Ethereum L1 Chain
Ethereum L1 Chain

L2OutputOracle

L2OutputOracle
12. Submit
L2 checkpoint 
output
12. Submit...
1. Submit   Deposit
1. Submit   Depos...
4. Send Transaction
4. Send Transacti...

Execution Engine

Accept transactions,
build blocks,
store the chain,
serve RPC requests

Execution Engine...
Sequencer
Sequencer
Validator
Validator

Rollup Node

Create payload attributes with deposit transactions

Rollup Node...

Batcher

Queries for unsafe blocks and submits them to the batch inbox

Batcher...

BatchInbox

BatchInbox
7. Submit batches
to the batch in box
7. Submit batches...
2. Watch
deposit events
2. Watch...
5. Get unsafe
block hash
5. Get unsafe...
3. create block
with payload attributes
3. create block...

Validator

Queries for the next checkpoint output & submit it to the L2 Output Oracle

Validator...

Rollup Node

Transform lastest L1 blocks
into L2 block inputs

Rollup Node...

Execution Engine

Build blocks,
store the chain,
serve RPC requests

Execution Engine...
10. create block
with payload attributes
10. create block...
11. Get checkpoint output to submit
11. Get checkpoint o...
9. Watch
deposit events
9. Watch...
8. Get batches
8. Get batches
6. Get
unsafe blocks
6. Get...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/specs/static/assets/user-withdrawing-to-l1.svg b/specs/static/assets/user-withdrawing-to-l1.svg new file mode 100644 index 0000000..be9c450 --- /dev/null +++ b/specs/static/assets/user-withdrawing-to-l1.svg @@ -0,0 +1,4 @@ + + + +
Ethereum L1 Chain
Ethereum L1 Chain
Users
Users
Validator
Validator
2. Post batch with
withdrawal
transaction data
2. Post batch with...

BatchInbox

BatchInbox

L2OutputOracle

L2OutputOracle
3. Assert output containing
withdrawal commitment
3. Assert output containing...

KromaPortal

KromaPortal
4. Send withdrawal proving
 transaction
4. Send withdrawal proving...

ETH

ETH
5. Verify withdrawal 
proof
5. Verify withdrawal...
Sequencer
Sequencer
1. Send withdrawal initiating
 transaction
1. Send withdrawal initiating...
5. Wait for
block to finalize
5. Wait for...
6. Send withdrawal finalizing
 transaction
6. Send withdrawal finalizing...
7. Verify withdrawal 
transaction
7. Verify withdrawal...
8. Transfer ETH
8. Transfer ETH
Text is not SVG - cannot display
\ No newline at end of file diff --git a/specs/static/assets/verifier-proving-fault-proof.svg b/specs/static/assets/verifier-proving-fault-proof.svg new file mode 100644 index 0000000..890afda --- /dev/null +++ b/specs/static/assets/verifier-proving-fault-proof.svg @@ -0,0 +1,4 @@ + + + +
Ethereum L1 Chain
Ethereum L1 Chain

L2OutputOracle

L2OutputOracle

Validator

Watches L1 chain for fault & performs challenges.
local_output != assertion_output

Validator...

Colosseum

Colosseum

Rollup Node

Transform latestL1 blocks into
L2 block inputs

Rollup Node...

Execution Engine

Execute blocks,
store the chain,
serve RPC calls 

Execution Engine...

ZK Prover

Generate ZK proof

ZK Prover...
4. Read output
4. Read output
3. Create block
with payload attributes
3. Create block...
5. Get
corresponding output
5. Get...
7. Get zkEVM proof
 for invalid assertion
7. Get zkEVM proof...
6. Isolate disputed
state transition
6. Isolate disputed...
8. Get execution trace
8. Get execution trace
Validator
Validator

BatchInBox

BatchInBox

KromaPortal

KromaPortal
1. Get batches
1. Get batches
2. Get event
2. Get event

ZKVerifier

ZKVerifier
9. Verify disputed
state transition
9. Verify disputed...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/specs/static/assets/vscode_debug.png b/specs/static/assets/vscode_debug.png new file mode 100644 index 0000000000000000000000000000000000000000..5fea4afe616162174780878753068cd2df362d7f GIT binary patch literal 49881 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 in jr?Ost(jr,wn,{enumerable:!0,configurable:!0,writable:!0,value:fn}):jr[wn]=fn;var vl=(jr,wn,fn)=>(Fst(jr,typeof wn!="symbol"?wn+"":wn,fn),fn);var jr=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function wn(t){var e=t.default;if(typeof e=="function"){var r=function(){return e.apply(this,arguments)};r.prototype=e.prototype}else r={};return Object.defineProperty(r,"__esModule",{value:!0}),Object.keys(t).forEach(function(n){var i=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(r,n,i.get?i:{enumerable:!0,get:function(){return t[n]}})}),r}function fn(t){throw new Error('Could not dynamically require "'+t+'". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.')}var y_={exports:{}};(function(t,e){(function(r,n){t.exports=n()})(jr,function(){var r;function n(){return r.apply(null,arguments)}function i(g){return g instanceof Array||Object.prototype.toString.call(g)==="[object Array]"}function a(g){return g!=null&&Object.prototype.toString.call(g)==="[object Object]"}function s(g,E){return Object.prototype.hasOwnProperty.call(g,E)}function o(g){if(Object.getOwnPropertyNames)return Object.getOwnPropertyNames(g).length===0;for(var E in g)if(s(g,E))return;return 1}function l(g){return g===void 0}function u(g){return typeof g=="number"||Object.prototype.toString.call(g)==="[object Number]"}function h(g){return g instanceof Date||Object.prototype.toString.call(g)==="[object Date]"}function d(g,E){for(var I=[],O=g.length,G=0;G>>0,O=0;Oue(g)?(ht=g+1,xt-ue(g)):(ht=g,xt);return{year:ht,dayOfYear:Mt}}function Ke(g,E,I){var O,G,ht=Ie(g.year(),E,I),ht=Math.floor((g.dayOfYear()-ht-1)/7)+1;return ht<1?O=ht+wr(G=g.year()-1,E,I):ht>wr(g.year(),E,I)?(O=ht-wr(g.year(),E,I),G=g.year()+1):(G=g.year(),O=ht),{week:O,year:G}}function wr(g,G,I){var O=Ie(g,G,I),G=Ie(g+1,G,I);return(ue(g)-O+G)/7}Y("w",["ww",2],"wo","week"),Y("W",["WW",2],"Wo","isoWeek"),W("week","w"),W("isoWeek","W"),Z("week",5),Z("isoWeek",5),ft("w",at),ft("ww",at,fe),ft("W",at),ft("WW",at,fe),we(["w","ww","W","WW"],function(g,E,I,O){E[O.substr(0,1)]=q(g)});function Ge(g,E){return g.slice(E,7).concat(g.slice(0,E))}Y("d",0,"do","day"),Y("dd",0,0,function(g){return this.localeData().weekdaysMin(this,g)}),Y("ddd",0,0,function(g){return this.localeData().weekdaysShort(this,g)}),Y("dddd",0,0,function(g){return this.localeData().weekdays(this,g)}),Y("e",0,0,"weekday"),Y("E",0,0,"isoWeekday"),W("day","d"),W("weekday","e"),W("isoWeekday","E"),Z("day",11),Z("weekday",11),Z("isoWeekday",11),ft("d",at),ft("e",at),ft("E",at),ft("dd",function(g,E){return E.weekdaysMinRegex(g)}),ft("ddd",function(g,E){return E.weekdaysShortRegex(g)}),ft("dddd",function(g,E){return E.weekdaysRegex(g)}),we(["dd","ddd","dddd"],function(g,E,I,O){O=I._locale.weekdaysParse(g,O,I._strict),O!=null?E.d=O:m(I).invalidWeekday=g}),we(["d","e","E"],function(g,E,I,O){E[O]=q(g)});var Ze="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),qt="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),st="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),At=Tt,Nt=Tt,Jt=Tt;function ze(){function g(Ot,de){return de.length-Ot.length}for(var E,I,O,G=[],ht=[],xt=[],Mt=[],Vt=0;Vt<7;Vt++)O=p([2e3,1]).day(Vt),E=Dt(this.weekdaysMin(O,"")),I=Dt(this.weekdaysShort(O,"")),O=Dt(this.weekdays(O,"")),G.push(E),ht.push(I),xt.push(O),Mt.push(E),Mt.push(I),Mt.push(O);G.sort(g),ht.sort(g),xt.sort(g),Mt.sort(g),this._weekdaysRegex=new RegExp("^("+Mt.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+xt.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+ht.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+G.join("|")+")","i")}function Pe(){return this.hours()%12||12}function qe(g,E){Y(g,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),E)})}function Tr(g,E){return E._meridiemParse}Y("H",["HH",2],0,"hour"),Y("h",["hh",2],0,Pe),Y("k",["kk",2],0,function(){return this.hours()||24}),Y("hmm",0,0,function(){return""+Pe.apply(this)+N(this.minutes(),2)}),Y("hmmss",0,0,function(){return""+Pe.apply(this)+N(this.minutes(),2)+N(this.seconds(),2)}),Y("Hmm",0,0,function(){return""+this.hours()+N(this.minutes(),2)}),Y("Hmmss",0,0,function(){return""+this.hours()+N(this.minutes(),2)+N(this.seconds(),2)}),qe("a",!0),qe("A",!1),W("hour","h"),Z("hour",13),ft("a",Tr),ft("A",Tr),ft("H",at),ft("h",at),ft("k",at),ft("HH",at,fe),ft("hh",at,fe),ft("kk",at,fe),ft("hmm",It),ft("hmmss",Lt),ft("Hmm",It),ft("Hmmss",Lt),Qt(["H","HH"],bt),Qt(["k","kk"],function(g,E,I){g=q(g),E[bt]=g===24?0:g}),Qt(["a","A"],function(g,E,I){I._isPm=I._locale.isPM(g),I._meridiem=g}),Qt(["h","hh"],function(g,E,I){E[bt]=q(g),m(I).bigHour=!0}),Qt("hmm",function(g,E,I){var O=g.length-2;E[bt]=q(g.substr(0,O)),E[Et]=q(g.substr(O)),m(I).bigHour=!0}),Qt("hmmss",function(g,E,I){var O=g.length-4,G=g.length-2;E[bt]=q(g.substr(0,O)),E[Et]=q(g.substr(O,2)),E[kt]=q(g.substr(G)),m(I).bigHour=!0}),Qt("Hmm",function(g,E,I){var O=g.length-2;E[bt]=q(g.substr(0,O)),E[Et]=q(g.substr(O))}),Qt("Hmmss",function(g,E,I){var O=g.length-4,G=g.length-2;E[bt]=q(g.substr(0,O)),E[Et]=q(g.substr(O,2)),E[kt]=q(g.substr(G))}),Tt=U("Hours",!0);var Ve,va={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:ne,monthsShort:ve,week:{dow:0,doy:6},weekdays:Ze,weekdaysMin:st,weekdaysShort:qt,meridiemParse:/[ap]\.?m?\.?/i},Ce={},Wi={};function E0(g){return g&&g.toLowerCase().replace("_","-")}function _u(g){for(var E,I,O,G,ht=0;ht=E&&function(xt,Mt){for(var Vt=Math.min(xt.length,Mt.length),Ot=0;Ot=E-1)break;E--}ht++}return Ve}function Ln(g){var E;if(Ce[g]===void 0&&!0&&t&&t.exports&&g.match("^[^/\\\\]*$")!=null)try{E=Ve._abbr,fn("./locale/"+g),Xt(E)}catch{Ce[g]=null}return Ce[g]}function Xt(g,E){return g&&((E=l(E)?ce(g):ee(g,E))?Ve=E:typeof console<"u"&&console.warn&&console.warn("Locale "+g+" not found. Did you forget to load it?")),Ve._abbr}function ee(g,E){if(E===null)return delete Ce[g],null;var I,O=va;if(E.abbr=g,Ce[g]!=null)L("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),O=Ce[g]._config;else if(E.parentLocale!=null)if(Ce[E.parentLocale]!=null)O=Ce[E.parentLocale]._config;else{if((I=Ln(E.parentLocale))==null)return Wi[E.parentLocale]||(Wi[E.parentLocale]=[]),Wi[E.parentLocale].push({name:g,config:E}),null;O=I._config}return Ce[g]=new w(B(O,E)),Wi[g]&&Wi[g].forEach(function(G){ee(G.name,G.config)}),Xt(g),Ce[g]}function ce(g){var E;if(!(g=g&&g._locale&&g._locale._abbr?g._locale._abbr:g))return Ve;if(!i(g)){if(E=Ln(g))return E;g=[g]}return _u(g)}function Pt(g){var E=g._a;return E&&m(g).overflow===-2&&(E=E[zt]<0||11yt(E[Ft],E[zt])?wt:E[bt]<0||24wr(ht,Vt,Ot)?m(O)._overflowWeeks=!0:de!=null?m(O)._overflowWeekday=!0:(ie=oe(ht,xt,Mt,Vt,Ot),O._a[Ft]=ie.year,O._dayOfYear=ie.dayOfYear)),g._dayOfYear!=null&&(G=Gi(g._a[Ft],I[Ft]),(g._dayOfYear>ue(G)||g._dayOfYear===0)&&(m(g)._overflowDayOfYear=!0),de=Hr(G,0,g._dayOfYear),g._a[zt]=de.getUTCMonth(),g._a[wt]=de.getUTCDate()),E=0;E<3&&g._a[E]==null;++E)g._a[E]=er[E]=I[E];for(;E<7;E++)g._a[E]=er[E]=g._a[E]==null?E===2?1:0:g._a[E];g._a[bt]===24&&g._a[Et]===0&&g._a[kt]===0&&g._a[Ut]===0&&(g._nextDay=!0,g._a[bt]=0),g._d=(g._useUTC?Hr:_a).apply(null,er),ht=g._useUTC?g._d.getUTCDay():g._d.getDay(),g._tzm!=null&&g._d.setUTCMinutes(g._d.getUTCMinutes()-g._tzm),g._nextDay&&(g._a[bt]=24),g._w&&g._w.d!==void 0&&g._w.d!==ht&&(m(g).weekdayMismatch=!0)}}function vu(g){if(g._f===n.ISO_8601)A0(g);else if(g._f===n.RFC_2822)Hi(g);else{g._a=[],m(g).empty=!0;for(var E,I,O,G,ht,xt=""+g._i,Mt=xt.length,Vt=0,Ot=lt(g._f,g._locale).match(z)||[],de=Ot.length,ie=0;ieg.valueOf():g.valueOf()"}),P.toJSON=function(){return this.isValid()?this.toISOString():null},P.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},P.unix=function(){return Math.floor(this.valueOf()/1e3)},P.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},P.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},P.eraName=function(){for(var g,E=this.localeData().eras(),I=0,O=E.length;Ithis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},P.isLocal=function(){return!!this.isValid()&&!this._isUTC},P.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},P.isUtc=rR,P.isUTC=rR,P.zoneAbbr=function(){return this._isUTC?"UTC":""},P.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},P.dates=R("dates accessor is deprecated. Use date instead.",ls),P.months=R("months accessor is deprecated. Use month instead",se),P.years=R("years accessor is deprecated. Use year instead",N0),P.zone=R("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(g,E){return g!=null?(this.utcOffset(g=typeof g!="string"?-g:g,E),this):-this.utcOffset()}),P.isDSTShifted=R("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!l(this._isDSTShifted))return this._isDSTShifted;var g,E={};return T(E,this),(E=M0(E))._a?(g=(E._isUTC?p:De)(E._a),this._isDSTShifted=this.isValid()&&0{},debug:(...t)=>{},info:(...t)=>{},warn:(...t)=>{},error:(...t)=>{},fatal:(...t)=>{}},D0=function(t="fatal"){let e=ji.fatal;typeof t=="string"?(t=t.toLowerCase(),t in ji&&(e=ji[t])):typeof t=="number"&&(e=t),H.trace=()=>{},H.debug=()=>{},H.info=()=>{},H.warn=()=>{},H.error=()=>{},H.fatal=()=>{},e<=ji.fatal&&(H.fatal=console.error?console.error.bind(console,Nn("FATAL"),"color: orange"):console.log.bind(console,"\x1B[35m",Nn("FATAL"))),e<=ji.error&&(H.error=console.error?console.error.bind(console,Nn("ERROR"),"color: orange"):console.log.bind(console,"\x1B[31m",Nn("ERROR"))),e<=ji.warn&&(H.warn=console.warn?console.warn.bind(console,Nn("WARN"),"color: orange"):console.log.bind(console,"\x1B[33m",Nn("WARN"))),e<=ji.info&&(H.info=console.info?console.info.bind(console,Nn("INFO"),"color: lightblue"):console.log.bind(console,"\x1B[34m",Nn("INFO"))),e<=ji.debug&&(H.debug=console.debug?console.debug.bind(console,Nn("DEBUG"),"color: lightgreen"):console.log.bind(console,"\x1B[32m",Nn("DEBUG"))),e<=ji.trace&&(H.trace=console.debug?console.debug.bind(console,Nn("TRACE"),"color: lightgreen"):console.log.bind(console,"\x1B[32m",Nn("TRACE")))},Nn=t=>`%c${Xn().format("ss.SSS")} : ${t} : `;var O0={};Object.defineProperty(O0,"__esModule",{value:!0});var ki=O0.sanitizeUrl=void 0,bR=/^([^\w]*)(javascript|data|vbscript)/im,_R=/&#(\w+)(^\w|;)?/g,vR=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,xR=/^([^:]+):/gm,kR=[".","/"];function wR(t){return kR.indexOf(t[0])>-1}function TR(t){return t.replace(_R,function(e,r){return String.fromCharCode(r)})}function ER(t){var e=TR(t||"").replace(vR,"").trim();if(!e)return"about:blank";if(wR(e))return e;var r=e.match(xR);if(!r)return e;var n=r[0];return bR.test(n)?"about:blank":e}ki=O0.sanitizeUrl=ER;function Qe(t,e){return t==null||e==null?NaN:te?1:t>=e?0:NaN}function m_(t,e){return t==null||e==null?NaN:et?1:e>=t?0:NaN}function ku(t){let e,r,n;t.length!==2?(e=Qe,r=(o,l)=>Qe(t(o),l),n=(o,l)=>t(o)-l):(e=t===Qe||t===m_?t:CR,r=t,n=t);function i(o,l,u=0,h=o.length){if(u>>1;r(o[d],l)<0?u=d+1:h=d}while(u>>1;r(o[d],l)<=0?u=d+1:h=d}while(uu&&n(o[d-1],l)>-n(o[d],l)?d-1:d}return{left:i,center:s,right:a}}function CR(){return 0}function b_(t){return t===null?NaN:+t}function*__(t,e){if(e===void 0)for(let r of t)r!=null&&(r=+r)>=r&&(yield r);else{let r=-1;for(let n of t)(n=e(n,++r,t))!=null&&(n=+n)>=n&&(yield n)}}const v_=ku(Qe),x_=v_.right,SR=v_.left,AR=ku(b_).center,cs=x_;function MR(t,e){if(!((e=+e)>=0))throw new RangeError("invalid r");let r=t.length;if(!((r=Math.floor(r))>=0))throw new RangeError("invalid length");if(!r||!e)return t;const n=F0(e),i=t.slice();return n(t,i,0,r,1),n(i,t,0,r,1),n(t,i,0,r,1),t}const k_=w_(F0),LR=w_(RR);function w_(t){return function(e,r,n=r){if(!((r=+r)>=0))throw new RangeError("invalid rx");if(!((n=+n)>=0))throw new RangeError("invalid ry");let{data:i,width:a,height:s}=e;if(!((a=Math.floor(a))>=0))throw new RangeError("invalid width");if(!((s=Math.floor(s!==void 0?s:i.length/a))>=0))throw new RangeError("invalid height");if(!a||!s||!r&&!n)return e;const o=r&&t(r),l=n&&t(n),u=i.slice();return o&&l?(ro(o,u,i,a,s),ro(o,i,u,a,s),ro(o,u,i,a,s),no(l,i,u,a,s),no(l,u,i,a,s),no(l,i,u,a,s)):o?(ro(o,i,u,a,s),ro(o,u,i,a,s),ro(o,i,u,a,s)):l&&(no(l,i,u,a,s),no(l,u,i,a,s),no(l,i,u,a,s)),e}}function ro(t,e,r,n,i){for(let a=0,s=n*i;a{i<<=2,a<<=2,s<<=2,e(r,n,i+0,a+0,s),e(r,n,i+1,a+1,s),e(r,n,i+2,a+2,s),e(r,n,i+3,a+3,s)}}function F0(t){const e=Math.floor(t);if(e===t)return IR(t);const r=t-e,n=2*t+1;return(i,a,s,o,l)=>{if(!((o-=l)>=s))return;let u=e*a[s];const h=l*e,d=h+l;for(let f=s,p=s+h;f{if(!((a-=s)>=i))return;let o=t*n[i];const l=s*t;for(let u=i,h=i+l;u=n&&++r;else{let n=-1;for(let i of t)(i=e(i,++n,t))!=null&&(i=+i)>=i&&++r}return r}function NR(t){return t.length|0}function BR(t){return!(t>0)}function DR(t){return typeof t!="object"||"length"in t?t:Array.from(t)}function OR(t){return e=>t(...e)}function FR(...t){const e=typeof t[t.length-1]=="function"&&OR(t.pop());t=t.map(DR);const r=t.map(NR),n=t.length-1,i=new Array(n+1).fill(0),a=[];if(n<0||r.some(BR))return a;for(;;){a.push(i.map((o,l)=>t[l][o]));let s=n;for(;++i[s]===r[s];){if(s===0)return e?a.map(e):a;i[s--]=0}}}function PR(t,e){var r=0,n=0;return Float64Array.from(t,e===void 0?i=>r+=+i||0:i=>r+=+e(i,n++,t)||0)}function T_(t,e){let r=0,n,i=0,a=0;if(e===void 0)for(let s of t)s!=null&&(s=+s)>=s&&(n=s-i,i+=n/++r,a+=n*(s-i));else{let s=-1;for(let o of t)(o=e(o,++s,t))!=null&&(o=+o)>=o&&(n=o-i,i+=n/++r,a+=n*(o-i))}if(r>1)return a/(r-1)}function E_(t,e){const r=T_(t,e);return r&&Math.sqrt(r)}function xl(t,e){let r,n;if(e===void 0)for(const i of t)i!=null&&(r===void 0?i>=i&&(r=n=i):(r>i&&(r=i),n=a&&(r=n=a):(r>a&&(r=a),n0){for(s=e[--r];r>0&&(n=s,i=e[--r],s=n+i,a=i-(s-n),!a););r>0&&(a<0&&e[r-1]<0||a>0&&e[r-1]>0)&&(i=a*2,n=s+i,i==n-s&&(s=n))}return s}}function qR(t,e){const r=new _r;if(e===void 0)for(let n of t)(n=+n)&&r.add(n);else{let n=-1;for(let i of t)(i=+e(i,++n,t))&&r.add(i)}return+r}function VR(t,e){const r=new _r;let n=-1;return Float64Array.from(t,e===void 0?i=>r.add(+i||0):i=>r.add(+e(i,++n,t)||0))}class kl extends Map{constructor(e,r=A_){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:r}}),e!=null)for(const[n,i]of e)this.set(n,i)}get(e){return super.get(P0(this,e))}has(e){return super.has(P0(this,e))}set(e,r){return super.set(C_(this,e),r)}delete(e){return super.delete(S_(this,e))}}class us extends Set{constructor(e,r=A_){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:r}}),e!=null)for(const n of e)this.add(n)}has(e){return super.has(P0(this,e))}add(e){return super.add(C_(this,e))}delete(e){return super.delete(S_(this,e))}}function P0({_intern:t,_key:e},r){const n=e(r);return t.has(n)?t.get(n):r}function C_({_intern:t,_key:e},r){const n=e(r);return t.has(n)?t.get(n):(t.set(n,r),r)}function S_({_intern:t,_key:e},r){const n=e(r);return t.has(n)&&(r=t.get(n),t.delete(n)),r}function A_(t){return t!==null&&typeof t=="object"?t.valueOf():t}function io(t){return t}function M_(t,...e){return ao(t,io,io,e)}function L_(t,...e){return ao(t,Array.from,io,e)}function R_(t,e){for(let r=1,n=e.length;ri.pop().map(([a,s])=>[...i,a,s]));return t}function zR(t,...e){return R_(L_(t,...e),e)}function YR(t,e,...r){return R_(N_(t,e,...r),r)}function I_(t,e,...r){return ao(t,io,e,r)}function N_(t,e,...r){return ao(t,Array.from,e,r)}function UR(t,...e){return ao(t,io,B_,e)}function WR(t,...e){return ao(t,Array.from,B_,e)}function B_(t){if(t.length!==1)throw new Error("duplicate key");return t[0]}function ao(t,e,r,n){return function i(a,s){if(s>=n.length)return r(a);const o=new kl,l=n[s++];let u=-1;for(const h of a){const d=l(h,++u,a),f=o.get(d);f?f.push(h):o.set(d,[h])}for(const[h,d]of o)o.set(h,i(d,s));return e(o)}(t,0)}function D_(t,e){return Array.from(e,r=>t[r])}function q0(t,...e){if(typeof t[Symbol.iterator]!="function")throw new TypeError("values is not iterable");t=Array.from(t);let[r]=e;if(r&&r.length!==2||e.length>1){const n=Uint32Array.from(t,(i,a)=>a);return e.length>1?(e=e.map(i=>t.map(i)),n.sort((i,a)=>{for(const s of e){const o=so(s[i],s[a]);if(o)return o}})):(r=t.map(r),n.sort((i,a)=>so(r[i],r[a]))),D_(t,n)}return t.sort(V0(r))}function V0(t=Qe){if(t===Qe)return so;if(typeof t!="function")throw new TypeError("compare is not a function");return(e,r)=>{const n=t(e,r);return n||n===0?n:(t(r,r)===0)-(t(e,e)===0)}}function so(t,e){return(t==null||!(t>=t))-(e==null||!(e>=e))||(te?1:0)}function HR(t,e,r){return(e.length!==2?q0(I_(t,e,r),([n,i],[a,s])=>Qe(i,s)||Qe(n,a)):q0(M_(t,r),([n,i],[a,s])=>e(i,s)||Qe(n,a))).map(([n])=>n)}var GR=Array.prototype,jR=GR.slice;function Tu(t){return()=>t}var z0=Math.sqrt(50),Y0=Math.sqrt(10),U0=Math.sqrt(2);function hs(t,e,r){var n,i=-1,a,s,o;if(e=+e,t=+t,r=+r,t===e&&r>0)return[t];if((n=e0){let l=Math.round(t/o),u=Math.round(e/o);for(l*oe&&--u,s=new Array(a=u-l+1);++ie&&--u,s=new Array(a=u-l+1);++i=0?(a>=z0?10:a>=Y0?5:a>=U0?2:1)*Math.pow(10,i):-Math.pow(10,-i)/(a>=z0?10:a>=Y0?5:a>=U0?2:1)}function wl(t,e,r){var n=Math.abs(e-t)/Math.max(0,r),i=Math.pow(10,Math.floor(Math.log(n)/Math.LN10)),a=n/i;return a>=z0?i*=10:a>=Y0?i*=5:a>=U0&&(i*=2),e0?(t=Math.floor(t/i)*i,e=Math.ceil(e/i)*i):i<0&&(t=Math.ceil(t*i)/i,e=Math.floor(e*i)/i),n=i}}function W0(t){return Math.ceil(Math.log(wu(t))/Math.LN2)+1}function F_(){var t=io,e=xl,r=W0;function n(i){Array.isArray(i)||(i=Array.from(i));var a,s=i.length,o,l,u=new Array(s);for(a=0;a=f)if(b>=f&&e===xl){const k=oo(d,f,x);isFinite(k)&&(k>0?f=(Math.floor(f/k)+1)*k:k<0&&(f=(Math.ceil(f*-k)+1)/-k))}else p.pop()}for(var m=p.length;p[0]<=d;)p.shift(),--m;for(;p[m-1]>f;)p.pop(),--m;var _=new Array(m+1),y;for(a=0;a<=m;++a)y=_[a]=[],y.x0=a>0?p[a-1]:d,y.x1=a0)for(a=0;a=n)&&(r=n);else{let n=-1;for(let i of t)(i=e(i,++n,t))!=null&&(r=i)&&(r=i)}return r}function H0(t,e){let r,n=-1,i=-1;if(e===void 0)for(const a of t)++i,a!=null&&(r=a)&&(r=a,n=i);else for(let a of t)(a=e(a,++i,t))!=null&&(r=a)&&(r=a,n=i);return n}function Tl(t,e){let r;if(e===void 0)for(const n of t)n!=null&&(r>n||r===void 0&&n>=n)&&(r=n);else{let n=-1;for(let i of t)(i=e(i,++n,t))!=null&&(r>i||r===void 0&&i>=i)&&(r=i)}return r}function G0(t,e){let r,n=-1,i=-1;if(e===void 0)for(const a of t)++i,a!=null&&(r>a||r===void 0&&a>=a)&&(r=a,n=i);else for(let a of t)(a=e(a,++i,t))!=null&&(r>a||r===void 0&&a>=a)&&(r=a,n=i);return n}function Eu(t,e,r=0,n=t.length-1,i){for(i=i===void 0?so:V0(i);n>r;){if(n-r>600){const l=n-r+1,u=e-r+1,h=Math.log(l),d=.5*Math.exp(2*h/3),f=.5*Math.sqrt(h*d*(l-d)/l)*(u-l/2<0?-1:1),p=Math.max(r,Math.floor(e-u*d/l+f)),m=Math.min(n,Math.floor(e+(l-u)*d/l+f));Eu(t,e,p,m,i)}const a=t[e];let s=r,o=n;for(El(t,r,e),i(t[n],a)>0&&El(t,r,n);s0;)--o}i(t[r],a)===0?El(t,r,o):(++o,El(t,o,n)),o<=e&&(r=o+1),e<=o&&(n=o-1)}return t}function El(t,e,r){const n=t[e];t[e]=t[r],t[r]=n}function P_(t,e=Qe){let r,n=!1;if(e.length===1){let i;for(const a of t){const s=e(a);(n?Qe(s,i)>0:Qe(s,s)===0)&&(r=a,i=s,n=!0)}}else for(const i of t)(n?e(i,r)>0:e(i,i)===0)&&(r=i,n=!0);return r}function Cl(t,e,r){if(t=Float64Array.from(__(t,r)),!!(n=t.length)){if((e=+e)<=0||n<2)return Tl(t);if(e>=1)return lo(t);var n,i=(n-1)*e,a=Math.floor(i),s=lo(Eu(t,a).subarray(0,a+1)),o=Tl(t.subarray(a+1));return s+(o-s)*(i-a)}}function q_(t,e,r=b_){if(!!(n=t.length)){if((e=+e)<=0||n<2)return+r(t[0],0,t);if(e>=1)return+r(t[n-1],n-1,t);var n,i=(n-1)*e,a=Math.floor(i),s=+r(t[a],a,t),o=+r(t[a+1],a+1,t);return s+(o-s)*(i-a)}}function V_(t,e,r){if(t=Float64Array.from(__(t,r)),!!(n=t.length)){if((e=+e)<=0||n<2)return G0(t);if(e>=1)return H0(t);var n,i=Math.floor((n-1)*e),a=(o,l)=>so(t[o],t[l]),s=Eu(Uint32Array.from(t,(o,l)=>l),i,0,n-1,a);return P_(s.subarray(0,i+1),o=>t[o])}}function $R(t,e,r){return Math.ceil((r-e)/(2*(Cl(t,.75)-Cl(t,.25))*Math.pow(wu(t),-1/3)))}function XR(t,e,r){return Math.ceil((r-e)*Math.cbrt(wu(t))/(3.49*E_(t)))}function KR(t,e){let r=0,n=0;if(e===void 0)for(let i of t)i!=null&&(i=+i)>=i&&(++r,n+=i);else{let i=-1;for(let a of t)(a=e(a,++i,t))!=null&&(a=+a)>=a&&(++r,n+=a)}if(r)return n/r}function ZR(t,e){return Cl(t,.5,e)}function QR(t,e){return V_(t,.5,e)}function*JR(t){for(const e of t)yield*e}function j0(t){return Array.from(JR(t))}function tI(t,e){const r=new kl;if(e===void 0)for(let a of t)a!=null&&a>=a&&r.set(a,(r.get(a)||0)+1);else{let a=-1;for(let s of t)(s=e(s,++a,t))!=null&&s>=s&&r.set(s,(r.get(s)||0)+1)}let n,i=0;for(const[a,s]of r)s>i&&(i=s,n=a);return n}function eI(t,e=rI){const r=[];let n,i=!1;for(const a of t)i&&r.push(e(n,a)),n=a,i=!0;return r}function rI(t,e){return[t,e]}function Ca(t,e,r){t=+t,e=+e,r=(i=arguments.length)<2?(e=t,t=0,1):i<3?1:+r;for(var n=-1,i=Math.max(0,Math.ceil((e-t)/r))|0,a=new Array(i);++ne(r[o],r[l]);let a,s;return Uint32Array.from(r,(o,l)=>l).sort(e===Qe?(o,l)=>so(r[o],r[l]):V0(i)).forEach((o,l)=>{const u=i(o,a===void 0?o:a);u>=0?((a===void 0||u>0)&&(a=o,s=l),n[o]=s):n[o]=NaN}),n}function iI(t,e=Qe){let r,n=!1;if(e.length===1){let i;for(const a of t){const s=e(a);(n?Qe(s,i)<0:Qe(s,s)===0)&&(r=a,i=s,n=!0)}}else for(const i of t)(n?e(i,r)<0:e(i,i)===0)&&(r=i,n=!0);return r}function z_(t,e=Qe){if(e.length===1)return G0(t,e);let r,n=-1,i=-1;for(const a of t)++i,(n<0?e(a,a)===0:e(a,r)<0)&&(r=a,n=i);return n}function aI(t,e=Qe){if(e.length===1)return H0(t,e);let r,n=-1,i=-1;for(const a of t)++i,(n<0?e(a,a)===0:e(a,r)>0)&&(r=a,n=i);return n}function sI(t,e){const r=z_(t,e);return r<0?void 0:r}const oI=Y_(Math.random);function Y_(t){return function(r,n=0,i=r.length){let a=i-(n=+n);for(;a;){const s=t()*a--|0,o=r[a+n];r[a+n]=r[s+n],r[s+n]=o}return r}}function lI(t,e){let r=0;if(e===void 0)for(let n of t)(n=+n)&&(r+=n);else{let n=-1;for(let i of t)(i=+e(i,++n,t))&&(r+=i)}return r}function U_(t){if(!(a=t.length))return[];for(var e=-1,r=Tl(t,cI),n=new Array(r);++ee(r,n,t))}function gI(t,e,r){if(typeof e!="function")throw new TypeError("reducer is not a function");const n=t[Symbol.iterator]();let i,a,s=-1;if(arguments.length<3){if({done:i,value:r}=n.next(),i)return;++s}for(;{done:i,value:a}=n.next(),!i;)r=e(r,a,++s,t);return r}function yI(t){if(typeof t[Symbol.iterator]!="function")throw new TypeError("values is not iterable");return Array.from(t).reverse()}function mI(t,...e){t=new us(t);for(const r of e)for(const n of r)t.delete(n);return t}function bI(t,e){const r=e[Symbol.iterator](),n=new us;for(const i of t){if(n.has(i))return!1;let a,s;for(;({value:a,done:s}=r.next())&&!s;){if(Object.is(i,a))return!1;n.add(a)}}return!0}function _I(t,...e){t=new us(t),e=e.map(vI);t:for(const r of t)for(const n of e)if(!n.has(r)){t.delete(r);continue t}return t}function vI(t){return t instanceof us?t:new us(t)}function W_(t,e){const r=t[Symbol.iterator](),n=new Set;for(const i of e){const a=H_(i);if(n.has(a))continue;let s,o;for(;{value:s,done:o}=r.next();){if(o)return!1;const l=H_(s);if(n.add(l),Object.is(a,l))break}}return!0}function H_(t){return t!==null&&typeof t=="object"?t.valueOf():t}function xI(t,e){return W_(e,t)}function kI(...t){const e=new us;for(const r of t)for(const n of r)e.add(n);return e}function wI(t){return t}var Cu=1,Su=2,$0=3,Sl=4,G_=1e-6;function TI(t){return"translate("+t+",0)"}function EI(t){return"translate(0,"+t+")"}function CI(t){return e=>+t(e)}function SI(t,e){return e=Math.max(0,t.bandwidth()-e*2)/2,t.round()&&(e=Math.round(e)),r=>+t(r)+e}function AI(){return!this.__axis}function Au(t,e){var r=[],n=null,i=null,a=6,s=6,o=3,l=typeof window<"u"&&window.devicePixelRatio>1?0:.5,u=t===Cu||t===Sl?-1:1,h=t===Sl||t===Su?"x":"y",d=t===Cu||t===$0?TI:EI;function f(p){var m=n==null?e.ticks?e.ticks.apply(e,r):e.domain():n,_=i==null?e.tickFormat?e.tickFormat.apply(e,r):wI:i,y=Math.max(a,0)+o,b=e.range(),x=+b[0]+l,k=+b[b.length-1]+l,T=(e.bandwidth?SI:CI)(e.copy(),l),C=p.selection?p.selection():p,M=C.selectAll(".domain").data([null]),S=C.selectAll(".tick").data(m,e).order(),R=S.exit(),A=S.enter().append("g").attr("class","tick"),L=S.select("line"),v=S.select("text");M=M.merge(M.enter().insert("path",".tick").attr("class","domain").attr("stroke","currentColor")),S=S.merge(A),L=L.merge(A.append("line").attr("stroke","currentColor").attr(h+"2",u*a)),v=v.merge(A.append("text").attr("fill","currentColor").attr(h,u*y).attr("dy",t===Cu?"0em":t===$0?"0.71em":"0.32em")),p!==C&&(M=M.transition(p),S=S.transition(p),L=L.transition(p),v=v.transition(p),R=R.transition(p).attr("opacity",G_).attr("transform",function(B){return isFinite(B=T(B))?d(B+l):this.getAttribute("transform")}),A.attr("opacity",G_).attr("transform",function(B){var w=this.parentNode.__axis;return d((w&&isFinite(w=w(B))?w:T(B))+l)})),R.remove(),M.attr("d",t===Sl||t===Su?s?"M"+u*s+","+x+"H"+l+"V"+k+"H"+u*s:"M"+l+","+x+"V"+k:s?"M"+x+","+u*s+"V"+l+"H"+k+"V"+u*s:"M"+x+","+l+"H"+k),S.attr("opacity",1).attr("transform",function(B){return d(T(B)+l)}),L.attr(h+"2",u*a),v.attr(h,u*y).text(_),C.filter(AI).attr("fill","none").attr("font-size",10).attr("font-family","sans-serif").attr("text-anchor",t===Su?"start":t===Sl?"end":"middle"),C.each(function(){this.__axis=T})}return f.scale=function(p){return arguments.length?(e=p,f):e},f.ticks=function(){return r=Array.from(arguments),f},f.tickArguments=function(p){return arguments.length?(r=p==null?[]:Array.from(p),f):r.slice()},f.tickValues=function(p){return arguments.length?(n=p==null?null:Array.from(p),f):n&&n.slice()},f.tickFormat=function(p){return arguments.length?(i=p,f):i},f.tickSize=function(p){return arguments.length?(a=s=+p,f):a},f.tickSizeInner=function(p){return arguments.length?(a=+p,f):a},f.tickSizeOuter=function(p){return arguments.length?(s=+p,f):s},f.tickPadding=function(p){return arguments.length?(o=+p,f):o},f.offset=function(p){return arguments.length?(l=+p,f):l},f}function j_(t){return Au(Cu,t)}function MI(t){return Au(Su,t)}function $_(t){return Au($0,t)}function LI(t){return Au(Sl,t)}var RI={value:()=>{}};function fs(){for(var t=0,e=arguments.length,r={},n;t=0&&(n=r.slice(i+1),r=r.slice(0,i)),r&&!e.hasOwnProperty(r))throw new Error("unknown type: "+r);return{type:r,name:n}})}Mu.prototype=fs.prototype={constructor:Mu,on:function(t,e){var r=this._,n=II(t+"",r),i,a=-1,s=n.length;if(arguments.length<2){for(;++a0)for(var r=new Array(i),n=0,i,a;n=0&&(e=t.slice(0,r))!=="xmlns"&&(t=t.slice(r+1)),K0.hasOwnProperty(e)?{space:K0[e],local:t}:t}function BI(t){return function(){var e=this.ownerDocument,r=this.namespaceURI;return r===X0&&e.documentElement.namespaceURI===X0?e.createElement(t):e.createElementNS(r,t)}}function DI(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function Lu(t){var e=Al(t);return(e.local?DI:BI)(e)}function OI(){}function Ru(t){return t==null?OI:function(){return this.querySelector(t)}}function FI(t){typeof t!="function"&&(t=Ru(t));for(var e=this._groups,r=e.length,n=new Array(r),i=0;i=k&&(k=x+1);!(C=y[k])&&++k=0;)(s=n[i])&&(a&&s.compareDocumentPosition(a)^4&&a.parentNode.insertBefore(s,a),a=s);return this}function oN(t){t||(t=lN);function e(d,f){return d&&f?t(d.__data__,f.__data__):!d-!f}for(var r=this._groups,n=r.length,i=new Array(n),a=0;ae?1:t>=e?0:NaN}function cN(){var t=arguments[0];return arguments[0]=this,t.apply(null,arguments),this}function uN(){return Array.from(this)}function hN(){for(var t=this._groups,e=0,r=t.length;e1?this.each((e==null?kN:typeof e=="function"?TN:wN)(t,e,r==null?"":r)):ds(this.node(),t)}function ds(t,e){return t.style.getPropertyValue(e)||J0(t).getComputedStyle(t,null).getPropertyValue(e)}function CN(t){return function(){delete this[t]}}function SN(t,e){return function(){this[t]=e}}function AN(t,e){return function(){var r=e.apply(this,arguments);r==null?delete this[t]:this[t]=r}}function MN(t,e){return arguments.length>1?this.each((e==null?CN:typeof e=="function"?AN:SN)(t,e)):this.node()[t]}function J_(t){return t.trim().split(/^|\s+/)}function td(t){return t.classList||new t5(t)}function t5(t){this._node=t,this._names=J_(t.getAttribute("class")||"")}t5.prototype={add:function(t){var e=this._names.indexOf(t);e<0&&(this._names.push(t),this._node.setAttribute("class",this._names.join(" ")))},remove:function(t){var e=this._names.indexOf(t);e>=0&&(this._names.splice(e,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};function e5(t,e){for(var r=td(t),n=-1,i=e.length;++n=0&&(r=e.slice(n+1),e=e.slice(0,n)),{type:e,name:r}})}function nB(t){return function(){var e=this.__on;if(!!e){for(var r=0,n=-1,i=e.length,a;rTn(r,e))}function Nu(t){return typeof t=="string"?new $r([document.querySelectorAll(t)],[document.documentElement]):new $r([K_(t)],ed)}const pB={passive:!1},Ml={capture:!0,passive:!1};function nd(t){t.stopImmediatePropagation()}function co(t){t.preventDefault(),t.stopImmediatePropagation()}function Bu(t){var e=t.document.documentElement,r=St(t).on("dragstart.drag",co,Ml);"onselectstart"in e?r.on("selectstart.drag",co,Ml):(e.__noselect=e.style.MozUserSelect,e.style.MozUserSelect="none")}function Du(t,e){var r=t.document.documentElement,n=St(t).on("dragstart.drag",null);e&&(n.on("click.drag",co,Ml),setTimeout(function(){n.on("click.drag",null)},0)),"onselectstart"in r?n.on("selectstart.drag",null):(r.style.MozUserSelect=r.__noselect,delete r.__noselect)}const Ou=t=>()=>t;function id(t,{sourceEvent:e,subject:r,target:n,identifier:i,active:a,x:s,y:o,dx:l,dy:u,dispatch:h}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:e,enumerable:!0,configurable:!0},subject:{value:r,enumerable:!0,configurable:!0},target:{value:n,enumerable:!0,configurable:!0},identifier:{value:i,enumerable:!0,configurable:!0},active:{value:a,enumerable:!0,configurable:!0},x:{value:s,enumerable:!0,configurable:!0},y:{value:o,enumerable:!0,configurable:!0},dx:{value:l,enumerable:!0,configurable:!0},dy:{value:u,enumerable:!0,configurable:!0},_:{value:h}})}id.prototype.on=function(){var t=this._.on.apply(this._,arguments);return t===this._?this:t};function gB(t){return!t.ctrlKey&&!t.button}function yB(){return this.parentNode}function mB(t,e){return e==null?{x:t.x,y:t.y}:e}function bB(){return navigator.maxTouchPoints||"ontouchstart"in this}function _B(){var t=gB,e=yB,r=mB,n=bB,i={},a=fs("start","drag","end"),s=0,o,l,u,h,d=0;function f(T){T.on("mousedown.drag",p).filter(n).on("touchstart.drag",y).on("touchmove.drag",b,pB).on("touchend.drag touchcancel.drag",x).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function p(T,C){if(!(h||!t.call(this,T,C))){var M=k(this,e.call(this,T,C),T,C,"mouse");!M||(St(T.view).on("mousemove.drag",m,Ml).on("mouseup.drag",_,Ml),Bu(T.view),nd(T),u=!1,o=T.clientX,l=T.clientY,M("start",T))}}function m(T){if(co(T),!u){var C=T.clientX-o,M=T.clientY-l;u=C*C+M*M>d}i.mouse("drag",T)}function _(T){St(T.view).on("mousemove.drag mouseup.drag",null),Du(T.view,u),co(T),i.mouse("end",T)}function y(T,C){if(!!t.call(this,T,C)){var M=T.changedTouches,S=e.call(this,T,C),R=M.length,A,L;for(A=0;A>8&15|e>>4&240,e>>4&15|e&240,(e&15)<<4|e&15,1):r===8?Fu(e>>24&255,e>>16&255,e>>8&255,(e&255)/255):r===4?Fu(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|e&240,((e&15)<<4|e&15)/255):null):(e=xB.exec(t))?new Er(e[1],e[2],e[3],1):(e=kB.exec(t))?new Er(e[1]*255/100,e[2]*255/100,e[3]*255/100,1):(e=wB.exec(t))?Fu(e[1],e[2],e[3],e[4]):(e=TB.exec(t))?Fu(e[1]*255/100,e[2]*255/100,e[3]*255/100,e[4]):(e=EB.exec(t))?f5(e[1],e[2]/100,e[3]/100,1):(e=CB.exec(t))?f5(e[1],e[2]/100,e[3]/100,e[4]):s5.hasOwnProperty(t)?c5(s5[t]):t==="transparent"?new Er(NaN,NaN,NaN,0):null}function c5(t){return new Er(t>>16&255,t>>8&255,t&255,1)}function Fu(t,e,r,n){return n<=0&&(t=e=r=NaN),new Er(t,e,r,n)}function ad(t){return t instanceof Sa||(t=Aa(t)),t?(t=t.rgb(),new Er(t.r,t.g,t.b,t.opacity)):new Er}function po(t,e,r,n){return arguments.length===1?ad(t):new Er(t,e,r,n==null?1:n)}function Er(t,e,r,n){this.r=+t,this.g=+e,this.b=+r,this.opacity=+n}uo(Er,po,Ll(Sa,{brighter(t){return t=t==null?ho:Math.pow(ho,t),new Er(this.r*t,this.g*t,this.b*t,this.opacity)},darker(t){return t=t==null?gs:Math.pow(gs,t),new Er(this.r*t,this.g*t,this.b*t,this.opacity)},rgb(){return this},clamp(){return new Er(ys(this.r),ys(this.g),ys(this.b),Pu(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:u5,formatHex:u5,formatHex8:MB,formatRgb:h5,toString:h5}));function u5(){return`#${ms(this.r)}${ms(this.g)}${ms(this.b)}`}function MB(){return`#${ms(this.r)}${ms(this.g)}${ms(this.b)}${ms((isNaN(this.opacity)?1:this.opacity)*255)}`}function h5(){const t=Pu(this.opacity);return`${t===1?"rgb(":"rgba("}${ys(this.r)}, ${ys(this.g)}, ${ys(this.b)}${t===1?")":`, ${t})`}`}function Pu(t){return isNaN(t)?1:Math.max(0,Math.min(1,t))}function ys(t){return Math.max(0,Math.min(255,Math.round(t)||0))}function ms(t){return t=ys(t),(t<16?"0":"")+t.toString(16)}function f5(t,e,r,n){return n<=0?t=e=r=NaN:r<=0||r>=1?t=e=NaN:e<=0&&(t=NaN),new Kn(t,e,r,n)}function d5(t){if(t instanceof Kn)return new Kn(t.h,t.s,t.l,t.opacity);if(t instanceof Sa||(t=Aa(t)),!t)return new Kn;if(t instanceof Kn)return t;t=t.rgb();var e=t.r/255,r=t.g/255,n=t.b/255,i=Math.min(e,r,n),a=Math.max(e,r,n),s=NaN,o=a-i,l=(a+i)/2;return o?(e===a?s=(r-n)/o+(r0&&l<1?0:s,new Kn(s,o,l,t.opacity)}function qu(t,e,r,n){return arguments.length===1?d5(t):new Kn(t,e,r,n==null?1:n)}function Kn(t,e,r,n){this.h=+t,this.s=+e,this.l=+r,this.opacity=+n}uo(Kn,qu,Ll(Sa,{brighter(t){return t=t==null?ho:Math.pow(ho,t),new Kn(this.h,this.s,this.l*t,this.opacity)},darker(t){return t=t==null?gs:Math.pow(gs,t),new Kn(this.h,this.s,this.l*t,this.opacity)},rgb(){var t=this.h%360+(this.h<0)*360,e=isNaN(t)||isNaN(this.s)?0:this.s,r=this.l,n=r+(r<.5?r:1-r)*e,i=2*r-n;return new Er(sd(t>=240?t-240:t+120,i,n),sd(t,i,n),sd(t<120?t+240:t-120,i,n),this.opacity)},clamp(){return new Kn(p5(this.h),Vu(this.s),Vu(this.l),Pu(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){const t=Pu(this.opacity);return`${t===1?"hsl(":"hsla("}${p5(this.h)}, ${Vu(this.s)*100}%, ${Vu(this.l)*100}%${t===1?")":`, ${t})`}`}}));function p5(t){return t=(t||0)%360,t<0?t+360:t}function Vu(t){return Math.max(0,Math.min(1,t||0))}function sd(t,e,r){return(t<60?e+(r-e)*t/60:t<180?r:t<240?e+(r-e)*(240-t)/60:e)*255}const g5=Math.PI/180,y5=180/Math.PI,zu=18,m5=.96422,b5=1,_5=.82521,v5=4/29,go=6/29,x5=3*go*go,LB=go*go*go;function k5(t){if(t instanceof Zn)return new Zn(t.l,t.a,t.b,t.opacity);if(t instanceof Ti)return T5(t);t instanceof Er||(t=ad(t));var e=ud(t.r),r=ud(t.g),n=ud(t.b),i=od((.2225045*e+.7168786*r+.0606169*n)/b5),a,s;return e===r&&r===n?a=s=i:(a=od((.4360747*e+.3850649*r+.1430804*n)/m5),s=od((.0139322*e+.0971045*r+.7141733*n)/_5)),new Zn(116*i-16,500*(a-i),200*(i-s),t.opacity)}function RB(t,e){return new Zn(t,0,0,e==null?1:e)}function Yu(t,e,r,n){return arguments.length===1?k5(t):new Zn(t,e,r,n==null?1:n)}function Zn(t,e,r,n){this.l=+t,this.a=+e,this.b=+r,this.opacity=+n}uo(Zn,Yu,Ll(Sa,{brighter(t){return new Zn(this.l+zu*(t==null?1:t),this.a,this.b,this.opacity)},darker(t){return new Zn(this.l-zu*(t==null?1:t),this.a,this.b,this.opacity)},rgb(){var t=(this.l+16)/116,e=isNaN(this.a)?t:t+this.a/500,r=isNaN(this.b)?t:t-this.b/200;return e=m5*ld(e),t=b5*ld(t),r=_5*ld(r),new Er(cd(3.1338561*e-1.6168667*t-.4906146*r),cd(-.9787684*e+1.9161415*t+.033454*r),cd(.0719453*e-.2289914*t+1.4052427*r),this.opacity)}}));function od(t){return t>LB?Math.pow(t,1/3):t/x5+v5}function ld(t){return t>go?t*t*t:x5*(t-v5)}function cd(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function ud(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function w5(t){if(t instanceof Ti)return new Ti(t.h,t.c,t.l,t.opacity);if(t instanceof Zn||(t=k5(t)),t.a===0&&t.b===0)return new Ti(NaN,0=1?(r=1,e-1):Math.floor(r*e),i=t[n],a=t[n+1],s=n>0?t[n-1]:2*i-a,o=n()=>t;function I5(t,e){return function(r){return t+r*e}}function BB(t,e,r){return t=Math.pow(t,r),e=Math.pow(e,r)-t,r=1/r,function(n){return Math.pow(t+n*e,r)}}function Gu(t,e){var r=e-t;return r?I5(t,r>180||r<-180?r-360*Math.round(r/360):r):Hu(isNaN(t)?e:t)}function DB(t){return(t=+t)==1?Cr:function(e,r){return r-e?BB(e,r,t):Hu(isNaN(e)?r:e)}}function Cr(t,e){var r=e-t;return r?I5(t,r):Hu(isNaN(t)?e:t)}const Nl=function t(e){var r=DB(e);function n(i,a){var s=r((i=po(i)).r,(a=po(a)).r),o=r(i.g,a.g),l=r(i.b,a.b),u=Cr(i.opacity,a.opacity);return function(h){return i.r=s(h),i.g=o(h),i.b=l(h),i.opacity=u(h),i+""}}return n.gamma=t,n}(1);function N5(t){return function(e){var r=e.length,n=new Array(r),i=new Array(r),a=new Array(r),s,o;for(s=0;sr&&(a=e.slice(r,a),o[s]?o[s]+=a:o[++s]=a),(n=n[0])===(i=i[0])?o[s]?o[s]+=i:o[++s]=i:(o[++s]=null,l.push({i:s,x:Bn(n,i)})),r=gd.lastIndex;return r180?h+=360:h-u>180&&(u+=360),f.push({i:d.push(i(d)+"rotate(",null,n)-2,x:Bn(u,h)})):h&&d.push(i(d)+"rotate("+h+n)}function o(u,h,d,f){u!==h?f.push({i:d.push(i(d)+"skewX(",null,n)-2,x:Bn(u,h)}):h&&d.push(i(d)+"skewX("+h+n)}function l(u,h,d,f,p,m){if(u!==d||h!==f){var _=p.push(i(p)+"scale(",null,",",null,")");m.push({i:_-4,x:Bn(u,d)},{i:_-2,x:Bn(h,f)})}else(d!==1||f!==1)&&p.push(i(p)+"scale("+d+","+f+")")}return function(u,h){var d=[],f=[];return u=t(u),h=t(h),a(u.translateX,u.translateY,h.translateX,h.translateY,d,f),s(u.rotate,h.rotate,d,f),o(u.skewX,h.skewX,d,f),l(u.scaleX,u.scaleY,h.scaleX,h.scaleY,d,f),u=h=null,function(p){for(var m=-1,_=f.length,y;++m<_;)d[(y=f[m]).i]=y.x(p);return d.join("")}}}var Y5=z5(YB,"px, ","px)","deg)"),U5=z5(UB,", ",")",")"),WB=1e-12;function W5(t){return((t=Math.exp(t))+1/t)/2}function HB(t){return((t=Math.exp(t))-1/t)/2}function GB(t){return((t=Math.exp(2*t))-1)/(t+1)}const H5=function t(e,r,n){function i(a,s){var o=a[0],l=a[1],u=a[2],h=s[0],d=s[1],f=s[2],p=h-o,m=d-l,_=p*p+m*m,y,b;if(_=0&&t._call.call(void 0,e),t=t._next;--yo}function tv(){_s=(Zu=Fl.now())+Qu,yo=Bl=0;try{J5()}finally{yo=0,eD(),_s=0}}function tD(){var t=Fl.now(),e=t-Zu;e>Z5&&(Qu-=e,Zu=t)}function eD(){for(var t,e=Ku,r,n=1/0;e;)e._call?(n>e._time&&(n=e._time),t=e,e=e._next):(r=e._next,e._next=null,e=t?t._next=r:Ku=r);Ol=t,bd(n)}function bd(t){if(!yo){Bl&&(Bl=clearTimeout(Bl));var e=t-_s;e>24?(t<1/0&&(Bl=setTimeout(tv,t-Fl.now()-Qu)),Dl&&(Dl=clearInterval(Dl))):(Dl||(Zu=Fl.now(),Dl=setInterval(tD,Z5)),yo=1,Q5(tv))}}function _d(t,e,r){var n=new ql;return e=e==null?0:+e,n.restart(i=>{n.stop(),t(i+e)},e,r),n}function rD(t,e,r){var n=new ql,i=e;return e==null?(n.restart(t,e,r),n):(n._restart=n.restart,n.restart=function(a,s,o){s=+s,o=o==null?Pl():+o,n._restart(function l(u){u+=i,n._restart(l,i+=s,o),a(u)},s,o)},n.restart(t,e,r),n)}var nD=fs("start","end","cancel","interrupt"),iD=[],ev=0,vd=1,xd=2,th=3,rv=4,kd=5,eh=6;function rh(t,e,r,n,i,a){var s=t.__transition;if(!s)t.__transition={};else if(r in s)return;aD(t,r,{name:e,index:n,group:i,on:nD,tween:iD,time:a.time,delay:a.delay,duration:a.duration,ease:a.ease,timer:null,state:ev})}function wd(t,e){var r=Jn(t,e);if(r.state>ev)throw new Error("too late; already scheduled");return r}function Ei(t,e){var r=Jn(t,e);if(r.state>th)throw new Error("too late; already running");return r}function Jn(t,e){var r=t.__transition;if(!r||!(r=r[e]))throw new Error("transition not found");return r}function aD(t,e,r){var n=t.__transition,i;n[e]=r,r.timer=Ju(a,0,r.time);function a(u){r.state=vd,r.timer.restart(s,r.delay,r.time),r.delay<=u&&s(u-r.delay)}function s(u){var h,d,f,p;if(r.state!==vd)return l();for(h in n)if(p=n[h],p.name===r.name){if(p.state===th)return _d(s);p.state===rv?(p.state=eh,p.timer.stop(),p.on.call("interrupt",t,t.__data__,p.index,p.group),delete n[h]):+hxd&&n.state=0&&(e=e.slice(0,r)),!e||e==="start"})}function DD(t,e,r){var n,i,a=BD(e)?wd:Ei;return function(){var s=a(this,t),o=s.on;o!==n&&(i=(n=o).copy()).on(e,r),s.on=i}}function OD(t,e){var r=this._id;return arguments.length<2?Jn(this.node(),r).on.on(t):this.each(DD(r,t,e))}function FD(t){return function(){var e=this.parentNode;for(var r in this.__transition)if(+r!==t)return;e&&e.removeChild(this)}}function PD(){return this.on("end.remove",FD(this._id))}function qD(t){var e=this._name,r=this._id;typeof t!="function"&&(t=Ru(t));for(var n=this._groups,i=n.length,a=new Array(i),s=0;s+t;function oO(t){return t*t}function lO(t){return t*(2-t)}function ov(t){return((t*=2)<=1?t*t:--t*(2-t)+1)/2}function cO(t){return t*t*t}function uO(t){return--t*t*t+1}function Ed(t){return((t*=2)<=1?t*t*t:(t-=2)*t*t+2)/2}var Cd=3,hO=function t(e){e=+e;function r(n){return Math.pow(n,e)}return r.exponent=t,r}(Cd),fO=function t(e){e=+e;function r(n){return 1-Math.pow(1-n,e)}return r.exponent=t,r}(Cd),lv=function t(e){e=+e;function r(n){return((n*=2)<=1?Math.pow(n,e):2-Math.pow(2-n,e))/2}return r.exponent=t,r}(Cd),cv=Math.PI,uv=cv/2;function dO(t){return+t==1?1:1-Math.cos(t*uv)}function pO(t){return Math.sin(t*uv)}function hv(t){return(1-Math.cos(cv*t))/2}function La(t){return(Math.pow(2,-10*t)-.0009765625)*1.0009775171065494}function gO(t){return La(1-+t)}function yO(t){return 1-La(t)}function fv(t){return((t*=2)<=1?La(1-t):2-La(t-1))/2}function mO(t){return 1-Math.sqrt(1-t*t)}function bO(t){return Math.sqrt(1- --t*t)}function dv(t){return((t*=2)<=1?1-Math.sqrt(1-t*t):Math.sqrt(1-(t-=2)*t)+1)/2}var Sd=4/11,_O=6/11,vO=8/11,xO=3/4,kO=9/11,wO=10/11,TO=15/16,EO=21/22,CO=63/64,nh=1/Sd/Sd;function SO(t){return 1-Vl(1-t)}function Vl(t){return(t=+t)vd&&n.name===e)return new Ci([[t]],OO,e,+i)}return null}const Rd=t=>()=>t;function PO(t,{sourceEvent:e,target:r,selection:n,mode:i,dispatch:a}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:e,enumerable:!0,configurable:!0},target:{value:r,enumerable:!0,configurable:!0},selection:{value:n,enumerable:!0,configurable:!0},mode:{value:i,enumerable:!0,configurable:!0},_:{value:a}})}function qO(t){t.stopImmediatePropagation()}function Id(t){t.preventDefault(),t.stopImmediatePropagation()}var yv={name:"drag"},Nd={name:"space"},bo={name:"handle"},_o={name:"center"};const{abs:mv,max:Or,min:Fr}=Math;function bv(t){return[+t[0],+t[1]]}function Bd(t){return[bv(t[0]),bv(t[1])]}var ih={name:"x",handles:["w","e"].map(zl),input:function(t,e){return t==null?null:[[+t[0],e[0][1]],[+t[1],e[1][1]]]},output:function(t){return t&&[t[0][0],t[1][0]]}},ah={name:"y",handles:["n","s"].map(zl),input:function(t,e){return t==null?null:[[e[0][0],+t[0]],[e[1][0],+t[1]]]},output:function(t){return t&&[t[0][1],t[1][1]]}},VO={name:"xy",handles:["n","w","e","s","nw","ne","sw","se"].map(zl),input:function(t){return t==null?null:Bd(t)},output:function(t){return t}},Xi={overlay:"crosshair",selection:"move",n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},_v={e:"w",w:"e",nw:"ne",ne:"nw",se:"sw",sw:"se"},vv={n:"s",s:"n",nw:"sw",ne:"se",se:"ne",sw:"nw"},zO={overlay:1,selection:1,n:null,e:1,s:null,w:-1,nw:-1,ne:1,se:1,sw:-1},YO={overlay:1,selection:1,n:-1,e:null,s:1,w:null,nw:-1,ne:-1,se:1,sw:1};function zl(t){return{type:t}}function UO(t){return!t.ctrlKey&&!t.button}function WO(){var t=this.ownerSVGElement||this;return t.hasAttribute("viewBox")?(t=t.viewBox.baseVal,[[t.x,t.y],[t.x+t.width,t.y+t.height]]):[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]}function HO(){return navigator.maxTouchPoints||"ontouchstart"in this}function Dd(t){for(;!t.__brush;)if(!(t=t.parentNode))return;return t.__brush}function GO(t){return t[0][0]===t[1][0]||t[0][1]===t[1][1]}function jO(t){var e=t.__brush;return e?e.dim.output(e.selection):null}function $O(){return Od(ih)}function XO(){return Od(ah)}function KO(){return Od(VO)}function Od(t){var e=WO,r=UO,n=HO,i=!0,a=fs("start","brush","end"),s=6,o;function l(y){var b=y.property("__brush",_).selectAll(".overlay").data([zl("overlay")]);b.enter().append("rect").attr("class","overlay").attr("pointer-events","all").attr("cursor",Xi.overlay).merge(b).each(function(){var k=Dd(this).extent;St(this).attr("x",k[0][0]).attr("y",k[0][1]).attr("width",k[1][0]-k[0][0]).attr("height",k[1][1]-k[0][1])}),y.selectAll(".selection").data([zl("selection")]).enter().append("rect").attr("class","selection").attr("cursor",Xi.selection).attr("fill","#777").attr("fill-opacity",.3).attr("stroke","#fff").attr("shape-rendering","crispEdges");var x=y.selectAll(".handle").data(t.handles,function(k){return k.type});x.exit().remove(),x.enter().append("rect").attr("class",function(k){return"handle handle--"+k.type}).attr("cursor",function(k){return Xi[k.type]}),y.each(u).attr("fill","none").attr("pointer-events","all").on("mousedown.brush",f).filter(n).on("touchstart.brush",f).on("touchmove.brush",p).on("touchend.brush touchcancel.brush",m).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}l.move=function(y,b,x){y.tween?y.on("start.brush",function(k){h(this,arguments).beforestart().start(k)}).on("interrupt.brush end.brush",function(k){h(this,arguments).end(k)}).tween("brush",function(){var k=this,T=k.__brush,C=h(k,arguments),M=T.selection,S=t.input(typeof b=="function"?b.apply(this,arguments):b,T.extent),R=Ma(M,S);function A(L){T.selection=L===1&&S===null?null:R(L),u.call(k),C.brush()}return M!==null&&S!==null?A:A(1)}):y.each(function(){var k=this,T=arguments,C=k.__brush,M=t.input(typeof b=="function"?b.apply(k,T):b,C.extent),S=h(k,T).beforestart();vs(k),C.selection=M===null?null:M,u.call(k),S.start(x).brush(x).end(x)})},l.clear=function(y,b){l.move(y,null,b)};function u(){var y=St(this),b=Dd(this).selection;b?(y.selectAll(".selection").style("display",null).attr("x",b[0][0]).attr("y",b[0][1]).attr("width",b[1][0]-b[0][0]).attr("height",b[1][1]-b[0][1]),y.selectAll(".handle").style("display",null).attr("x",function(x){return x.type[x.type.length-1]==="e"?b[1][0]-s/2:b[0][0]-s/2}).attr("y",function(x){return x.type[0]==="s"?b[1][1]-s/2:b[0][1]-s/2}).attr("width",function(x){return x.type==="n"||x.type==="s"?b[1][0]-b[0][0]+s:s}).attr("height",function(x){return x.type==="e"||x.type==="w"?b[1][1]-b[0][1]+s:s})):y.selectAll(".selection,.handle").style("display","none").attr("x",null).attr("y",null).attr("width",null).attr("height",null)}function h(y,b,x){var k=y.__brush.emitter;return k&&(!x||!k.clean)?k:new d(y,b,x)}function d(y,b,x){this.that=y,this.args=b,this.state=y.__brush,this.active=0,this.clean=x}d.prototype={beforestart:function(){return++this.active===1&&(this.state.emitter=this,this.starting=!0),this},start:function(y,b){return this.starting?(this.starting=!1,this.emit("start",y,b)):this.emit("brush",y),this},brush:function(y,b){return this.emit("brush",y,b),this},end:function(y,b){return--this.active===0&&(delete this.state.emitter,this.emit("end",y,b)),this},emit:function(y,b,x){var k=St(this.that).datum();a.call(y,this.that,new PO(y,{sourceEvent:b,target:l,selection:t.output(this.state.selection),mode:x,dispatch:a}),k)}};function f(y){if(o&&!y.touches||!r.apply(this,arguments))return;var b=this,x=y.target.__data__.type,k=(i&&y.metaKey?x="overlay":x)==="selection"?yv:i&&y.altKey?_o:bo,T=t===ah?null:zO[x],C=t===ih?null:YO[x],M=Dd(b),S=M.extent,R=M.selection,A=S[0][0],L,v,B=S[0][1],w,D,N=S[1][0],z,X,ct=S[1][1],J,Y,$=0,lt=0,ut,W=T&&C&&i&&y.shiftKey,tt,K,it=Array.from(y.touches||[y],at=>{const It=at.identifier;return at=Tn(at,b),at.point0=at.slice(),at.identifier=It,at});vs(b);var Z=h(b,arguments,!0).beforestart();if(x==="overlay"){R&&(ut=!0);const at=[it[0],it[1]||it[0]];M.selection=R=[[L=t===ah?A:Fr(at[0][0],at[1][0]),w=t===ih?B:Fr(at[0][1],at[1][1])],[z=t===ah?N:Or(at[0][0],at[1][0]),J=t===ih?ct:Or(at[0][1],at[1][1])]],it.length>1&&F(y)}else L=R[0][0],w=R[0][1],z=R[1][0],J=R[1][1];v=L,D=w,X=z,Y=J;var V=St(b).attr("pointer-events","none"),Q=V.selectAll(".overlay").attr("cursor",Xi[x]);if(y.touches)Z.moved=U,Z.ended=j;else{var q=St(y.view).on("mousemove.brush",U,!0).on("mouseup.brush",j,!0);i&&q.on("keydown.brush",P,!0).on("keyup.brush",et,!0),Bu(y.view)}u.call(b),Z.start(y,k.name);function U(at){for(const It of at.changedTouches||[at])for(const Lt of it)Lt.identifier===It.identifier&&(Lt.cur=Tn(It,b));if(W&&!tt&&!K&&it.length===1){const It=it[0];mv(It.cur[0]-It[0])>mv(It.cur[1]-It[1])?K=!0:tt=!0}for(const It of it)It.cur&&(It[0]=It.cur[0],It[1]=It.cur[1]);ut=!0,Id(at),F(at)}function F(at){const It=it[0],Lt=It.point0;var Rt;switch($=It[0]-Lt[0],lt=It[1]-Lt[1],k){case Nd:case yv:{T&&($=Or(A-L,Fr(N-z,$)),v=L+$,X=z+$),C&&(lt=Or(B-w,Fr(ct-J,lt)),D=w+lt,Y=J+lt);break}case bo:{it[1]?(T&&(v=Or(A,Fr(N,it[0][0])),X=Or(A,Fr(N,it[1][0])),T=1),C&&(D=Or(B,Fr(ct,it[0][1])),Y=Or(B,Fr(ct,it[1][1])),C=1)):(T<0?($=Or(A-L,Fr(N-L,$)),v=L+$,X=z):T>0&&($=Or(A-z,Fr(N-z,$)),v=L,X=z+$),C<0?(lt=Or(B-w,Fr(ct-w,lt)),D=w+lt,Y=J):C>0&&(lt=Or(B-J,Fr(ct-J,lt)),D=w,Y=J+lt));break}case _o:{T&&(v=Or(A,Fr(N,L-$*T)),X=Or(A,Fr(N,z+$*T))),C&&(D=Or(B,Fr(ct,w-lt*C)),Y=Or(B,Fr(ct,J+lt*C)));break}}X0&&(L=v-$),C<0?J=Y-lt:C>0&&(w=D-lt),k=Nd,Q.attr("cursor",Xi.selection),F(at));break}default:return}Id(at)}function et(at){switch(at.keyCode){case 16:{W&&(tt=K=W=!1,F(at));break}case 18:{k===_o&&(T<0?z=X:T>0&&(L=v),C<0?J=Y:C>0&&(w=D),k=bo,F(at));break}case 32:{k===Nd&&(at.altKey?(T&&(z=X-$*T,L=v+$*T),C&&(J=Y-lt*C,w=D+lt*C),k=_o):(T<0?z=X:T>0&&(L=v),C<0?J=Y:C>0&&(w=D),k=bo),Q.attr("cursor",Xi[x]),F(at));break}default:return}Id(at)}}function p(y){h(this,arguments).moved(y)}function m(y){h(this,arguments).ended(y)}function _(){var y=this.__brush||{selection:null};return y.extent=Bd(e.apply(this,arguments)),y.dim=t,y}return l.extent=function(y){return arguments.length?(e=typeof y=="function"?y:Rd(Bd(y)),l):e},l.filter=function(y){return arguments.length?(r=typeof y=="function"?y:Rd(!!y),l):r},l.touchable=function(y){return arguments.length?(n=typeof y=="function"?y:Rd(!!y),l):n},l.handleSize=function(y){return arguments.length?(s=+y,l):s},l.keyModifiers=function(y){return arguments.length?(i=!!y,l):i},l.on=function(){var y=a.on.apply(a,arguments);return y===a?l:y},l}var xv=Math.abs,vo=Math.cos,xo=Math.sin,kv=Math.PI,sh=kv/2,wv=kv*2,Tv=Math.max,Fd=1e-12;function Pd(t,e){return Array.from({length:e-t},(r,n)=>t+n)}function ZO(t){return function(e,r){return t(e.source.value+e.target.value,r.source.value+r.target.value)}}function QO(){return qd(!1,!1)}function JO(){return qd(!1,!0)}function tF(){return qd(!0,!1)}function qd(t,e){var r=0,n=null,i=null,a=null;function s(o){var l=o.length,u=new Array(l),h=Pd(0,l),d=new Array(l*l),f=new Array(l),p=0,m;o=Float64Array.from({length:l*l},e?(_,y)=>o[y%l][y/l|0]:(_,y)=>o[y/l|0][y%l]);for(let _=0;_n(u[y],u[b]));for(const y of h){const b=_;if(t){const x=Pd(~l+1,l).filter(k=>k<0?o[~k*l+y]:o[y*l+k]);i&&x.sort((k,T)=>i(k<0?-o[~k*l+y]:o[y*l+k],T<0?-o[~T*l+y]:o[y*l+T]));for(const k of x)if(k<0){const T=d[~k*l+y]||(d[~k*l+y]={source:null,target:null});T.target={index:y,startAngle:_,endAngle:_+=o[~k*l+y]*p,value:o[~k*l+y]}}else{const T=d[y*l+k]||(d[y*l+k]={source:null,target:null});T.source={index:y,startAngle:_,endAngle:_+=o[y*l+k]*p,value:o[y*l+k]}}f[y]={index:y,startAngle:b,endAngle:_,value:u[y]}}else{const x=Pd(0,l).filter(k=>o[y*l+k]||o[k*l+y]);i&&x.sort((k,T)=>i(o[y*l+k],o[y*l+T]));for(const k of x){let T;if(yxs)if(!(Math.abs(h*o-l*u)>xs)||!i)this._+="L"+(this._x1=t)+","+(this._y1=e);else{var f=r-a,p=n-s,m=o*o+l*l,_=f*f+p*p,y=Math.sqrt(m),b=Math.sqrt(d),x=i*Math.tan((Vd-Math.acos((m+d-_)/(2*y*b)))/2),k=x/b,T=x/y;Math.abs(k-1)>xs&&(this._+="L"+(t+k*u)+","+(e+k*h)),this._+="A"+i+","+i+",0,0,"+ +(h*f>u*p)+","+(this._x1=t+T*o)+","+(this._y1=e+T*l)}},arc:function(t,e,r,n,i,a){t=+t,e=+e,r=+r,a=!!a;var s=r*Math.cos(n),o=r*Math.sin(n),l=t+s,u=e+o,h=1^a,d=a?n-i:i-n;if(r<0)throw new Error("negative radius: "+r);this._x1===null?this._+="M"+l+","+u:(Math.abs(this._x1-l)>xs||Math.abs(this._y1-u)>xs)&&(this._+="L"+l+","+u),r&&(d<0&&(d=d%zd+zd),d>eF?this._+="A"+r+","+r+",0,1,"+h+","+(t-s)+","+(e-o)+"A"+r+","+r+",0,1,"+h+","+(this._x1=l)+","+(this._y1=u):d>xs&&(this._+="A"+r+","+r+",0,"+ +(d>=Vd)+","+h+","+(this._x1=t+r*Math.cos(i))+","+(this._y1=e+r*Math.sin(i))))},rect:function(t,e,r,n){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+e)+"h"+ +r+"v"+ +n+"h"+-r+"Z"},toString:function(){return this._}};var rF=Array.prototype.slice;function ks(t){return function(){return t}}function nF(t){return t.source}function iF(t){return t.target}function Ev(t){return t.radius}function aF(t){return t.startAngle}function sF(t){return t.endAngle}function oF(){return 0}function lF(){return 10}function Cv(t){var e=nF,r=iF,n=Ev,i=Ev,a=aF,s=sF,o=oF,l=null;function u(){var h,d=e.apply(this,arguments),f=r.apply(this,arguments),p=o.apply(this,arguments)/2,m=rF.call(arguments),_=+n.apply(this,(m[0]=d,m)),y=a.apply(this,m)-sh,b=s.apply(this,m)-sh,x=+i.apply(this,(m[0]=f,m)),k=a.apply(this,m)-sh,T=s.apply(this,m)-sh;if(l||(l=h=Ra()),p>Fd&&(xv(b-y)>p*2+Fd?b>y?(y+=p,b-=p):(y-=p,b+=p):y=b=(y+b)/2,xv(T-k)>p*2+Fd?T>k?(k+=p,T-=p):(k-=p,T+=p):k=T=(k+T)/2),l.moveTo(_*vo(y),_*xo(y)),l.arc(0,0,_,y,b),y!==k||b!==T)if(t){var C=+t.apply(this,arguments),M=x-C,S=(k+T)/2;l.quadraticCurveTo(0,0,M*vo(k),M*xo(k)),l.lineTo(x*vo(S),x*xo(S)),l.lineTo(M*vo(T),M*xo(T))}else l.quadraticCurveTo(0,0,x*vo(k),x*xo(k)),l.arc(0,0,x,k,T);if(l.quadraticCurveTo(0,0,_*vo(y),_*xo(y)),l.closePath(),h)return l=null,h+""||null}return t&&(u.headRadius=function(h){return arguments.length?(t=typeof h=="function"?h:ks(+h),u):t}),u.radius=function(h){return arguments.length?(n=i=typeof h=="function"?h:ks(+h),u):n},u.sourceRadius=function(h){return arguments.length?(n=typeof h=="function"?h:ks(+h),u):n},u.targetRadius=function(h){return arguments.length?(i=typeof h=="function"?h:ks(+h),u):i},u.startAngle=function(h){return arguments.length?(a=typeof h=="function"?h:ks(+h),u):a},u.endAngle=function(h){return arguments.length?(s=typeof h=="function"?h:ks(+h),u):s},u.padAngle=function(h){return arguments.length?(o=typeof h=="function"?h:ks(+h),u):o},u.source=function(h){return arguments.length?(e=h,u):e},u.target=function(h){return arguments.length?(r=h,u):r},u.context=function(h){return arguments.length?(l=h==null?null:h,u):l},u}function cF(){return Cv()}function uF(){return Cv(lF)}var hF=Array.prototype,Sv=hF.slice;function fF(t,e){return t-e}function dF(t){for(var e=0,r=t.length,n=t[r-1][1]*t[0][0]-t[r-1][0]*t[0][1];++e()=>t;function pF(t,e){for(var r=-1,n=e.length,i;++rn!=p>n&&r<(f-u)*(n-h)/(p-h)+u&&(i=-i)}return i}function yF(t,e,r){var n;return mF(t,e,r)&&bF(t[n=+(t[0]===e[0])],r[n],e[n])}function mF(t,e,r){return(e[0]-t[0])*(r[1]-t[1])===(r[0]-t[0])*(e[1]-t[1])}function bF(t,e,r){return t<=e&&e<=r||r<=e&&e<=t}function _F(){}var Ki=[[],[[[1,1.5],[.5,1]]],[[[1.5,1],[1,1.5]]],[[[1.5,1],[.5,1]]],[[[1,.5],[1.5,1]]],[[[1,1.5],[.5,1]],[[1,.5],[1.5,1]]],[[[1,.5],[1,1.5]]],[[[1,.5],[.5,1]]],[[[.5,1],[1,.5]]],[[[1,1.5],[1,.5]]],[[[.5,1],[1,.5]],[[1.5,1],[1,1.5]]],[[[1.5,1],[1,.5]]],[[[.5,1],[1.5,1]]],[[[1,1.5],[1.5,1]]],[[[.5,1],[1,1.5]]],[]];function Ud(){var t=1,e=1,r=W0,n=l;function i(u){var h=r(u);if(Array.isArray(h))h=h.slice().sort(fF);else{const d=xl(u),f=wl(d[0],d[1],h);h=hs(Math.floor(d[0]/f)*f,Math.floor(d[1]/f-1)*f,h)}return h.map(d=>a(u,d))}function a(u,h){var d=[],f=[];return s(u,h,function(p){n(p,u,h),dF(p)>0?d.push([p]):f.push(p)}),f.forEach(function(p){for(var m=0,_=d.length,y;m<_;++m)if(pF((y=d[m])[0],p)!==-1){y.push(p);return}}),{type:"MultiPolygon",value:h,coordinates:d}}function s(u,h,d){var f=new Array,p=new Array,m,_,y,b,x,k;for(m=_=-1,b=u[0]>=h,Ki[b<<1].forEach(T);++m=h,Ki[y|b<<1].forEach(T);for(Ki[b<<0].forEach(T);++_=h,x=u[_*t]>=h,Ki[b<<1|x<<2].forEach(T);++m=h,k=x,x=u[_*t+m+1]>=h,Ki[y|b<<1|x<<2|k<<3].forEach(T);Ki[b|x<<3].forEach(T)}for(m=-1,x=u[_*t]>=h,Ki[x<<2].forEach(T);++m=h,Ki[x<<2|k<<3].forEach(T);Ki[x<<3].forEach(T);function T(C){var M=[C[0][0]+m,C[0][1]+_],S=[C[1][0]+m,C[1][1]+_],R=o(M),A=o(S),L,v;(L=p[R])?(v=f[A])?(delete p[L.end],delete f[v.start],L===v?(L.ring.push(S),d(L.ring)):f[L.start]=p[v.end]={start:L.start,end:v.end,ring:L.ring.concat(v.ring)}):(delete p[L.end],L.ring.push(S),p[L.end=A]=L):(L=f[A])?(v=p[R])?(delete f[L.start],delete p[v.end],L===v?(L.ring.push(S),d(L.ring)):f[v.start]=p[L.end]={start:v.start,end:L.end,ring:v.ring.concat(L.ring)}):(delete f[L.start],L.ring.unshift(M),f[L.start=R]=L):f[R]=p[A]={start:R,end:A,ring:[M,S]}}}function o(u){return u[0]*2+u[1]*(t+1)*4}function l(u,h,d){u.forEach(function(f){var p=f[0],m=f[1],_=p|0,y=m|0,b,x=h[y*t+_];p>0&&p0&&m=0&&d>=0))throw new Error("invalid size");return t=h,e=d,i},i.thresholds=function(u){return arguments.length?(r=typeof u=="function"?u:Array.isArray(u)?Ia(Sv.call(u)):Ia(u),i):r},i.smooth=function(u){return arguments.length?(n=u?l:_F,i):n===l},i}function vF(t){return t[0]}function xF(t){return t[1]}function kF(){return 1}function wF(){var t=vF,e=xF,r=kF,n=960,i=500,a=20,s=2,o=a*3,l=n+o*2>>s,u=i+o*2>>s,h=Ia(20);function d(x){var k=new Float32Array(l*u),T=Math.pow(2,-s),C=-1;for(const w of x){var M=(t(w,++C,x)+o)*T,S=(e(w,C,x)+o)*T,R=+r(w,C,x);if(M>=0&&M=0&&SM*C))(k).map((M,S)=>(M.value=+T[S],p(M)))}f.contours=function(x){var k=d(x),T=Ud().size([l,u]),C=Math.pow(2,2*s),M=S=>{S=+S;var R=p(T.contour(k,S*C));return R.value=S,R};return Object.defineProperty(M,"max",{get:()=>lo(k)/C}),M};function p(x){return x.coordinates.forEach(m),x}function m(x){x.forEach(_)}function _(x){x.forEach(y)}function y(x){x[0]=x[0]*Math.pow(2,s)-o,x[1]=x[1]*Math.pow(2,s)-o}function b(){return o=a*3,l=n+o*2>>s,u=i+o*2>>s,f}return f.x=function(x){return arguments.length?(t=typeof x=="function"?x:Ia(+x),f):t},f.y=function(x){return arguments.length?(e=typeof x=="function"?x:Ia(+x),f):e},f.weight=function(x){return arguments.length?(r=typeof x=="function"?x:Ia(+x),f):r},f.size=function(x){if(!arguments.length)return[n,i];var k=+x[0],T=+x[1];if(!(k>=0&&T>=0))throw new Error("invalid size");return n=k,i=T,b()},f.cellSize=function(x){if(!arguments.length)return 1<=1))throw new Error("invalid cell size");return s=Math.floor(Math.log(x)/Math.LN2),b()},f.thresholds=function(x){return arguments.length?(h=typeof x=="function"?x:Array.isArray(x)?Ia(Sv.call(x)):Ia(x),f):h},f.bandwidth=function(x){if(!arguments.length)return Math.sqrt(a*(a+1));if(!((x=+x)>=0))throw new Error("invalid bandwidth");return a=(Math.sqrt(4*x*x+1)-1)/2,b()},f}const Zi=11102230246251565e-32,Pr=134217729,TF=(3+8*Zi)*Zi;function Wd(t,e,r,n,i){let a,s,o,l,u=e[0],h=n[0],d=0,f=0;h>u==h>-u?(a=u,u=e[++d]):(a=h,h=n[++f]);let p=0;if(du==h>-u?(s=u+a,o=a-(s-u),u=e[++d]):(s=h+a,o=a-(s-h),h=n[++f]),a=s,o!==0&&(i[p++]=o);du==h>-u?(s=a+u,l=s-a,o=a-(s-l)+(u-l),u=e[++d]):(s=a+h,l=s-a,o=a-(s-l)+(h-l),h=n[++f]),a=s,o!==0&&(i[p++]=o);for(;d=D||-w>=D||(d=t-A,o=t-(A+d)+(d-i),d=r-L,u=r-(L+d)+(d-i),d=e-v,l=e-(v+d)+(d-a),d=n-B,h=n-(B+d)+(d-a),o===0&&l===0&&u===0&&h===0)||(D=AF*s+TF*Math.abs(w),w+=A*h+B*o-(v*u+L*l),w>=D||-w>=D))return w;T=o*B,f=Pr*o,p=f-(f-o),m=o-p,f=Pr*B,_=f-(f-B),y=B-_,C=m*y-(T-p*_-m*_-p*y),M=l*L,f=Pr*l,p=f-(f-l),m=l-p,f=Pr*L,_=f-(f-L),y=L-_,S=m*y-(M-p*_-m*_-p*y),b=C-S,d=C-b,Xr[0]=C-(b+d)+(d-S),x=T+b,d=x-T,k=T-(x-d)+(b-d),b=k-M,d=k-b,Xr[1]=k-(b+d)+(d-M),R=x+b,d=R-x,Xr[2]=x-(R-d)+(b-d),Xr[3]=R;const N=Wd(4,ko,4,Xr,Av);T=A*h,f=Pr*A,p=f-(f-A),m=A-p,f=Pr*h,_=f-(f-h),y=h-_,C=m*y-(T-p*_-m*_-p*y),M=v*u,f=Pr*v,p=f-(f-v),m=v-p,f=Pr*u,_=f-(f-u),y=u-_,S=m*y-(M-p*_-m*_-p*y),b=C-S,d=C-b,Xr[0]=C-(b+d)+(d-S),x=T+b,d=x-T,k=T-(x-d)+(b-d),b=k-M,d=k-b,Xr[1]=k-(b+d)+(d-M),R=x+b,d=R-x,Xr[2]=x-(R-d)+(b-d),Xr[3]=R;const z=Wd(N,Av,4,Xr,Mv);T=o*h,f=Pr*o,p=f-(f-o),m=o-p,f=Pr*h,_=f-(f-h),y=h-_,C=m*y-(T-p*_-m*_-p*y),M=l*u,f=Pr*l,p=f-(f-l),m=l-p,f=Pr*u,_=f-(f-u),y=u-_,S=m*y-(M-p*_-m*_-p*y),b=C-S,d=C-b,Xr[0]=C-(b+d)+(d-S),x=T+b,d=x-T,k=T-(x-d)+(b-d),b=k-M,d=k-b,Xr[1]=k-(b+d)+(d-M),R=x+b,d=R-x,Xr[2]=x-(R-d)+(b-d),Xr[3]=R;const X=Wd(z,Mv,4,Xr,Lv);return Lv[X-1]}function oh(t,e,r,n,i,a){const s=(e-a)*(r-i),o=(t-i)*(n-a),l=s-o;if(s===0||o===0||s>0!=o>0)return l;const u=Math.abs(s+o);return Math.abs(l)>=CF*u?l:-MF(t,e,r,n,i,a,u)}const Rv=Math.pow(2,-52),lh=new Uint32Array(512);class ch{static from(e,r=BF,n=DF){const i=e.length,a=new Float64Array(i*2);for(let s=0;s>1;if(r>0&&typeof e[0]!="number")throw new Error("Expected coords to contain numbers.");this.coords=e;const n=Math.max(2*r-5,0);this._triangles=new Uint32Array(n*3),this._halfedges=new Int32Array(n*3),this._hashSize=Math.ceil(Math.sqrt(r)),this._hullPrev=new Uint32Array(r),this._hullNext=new Uint32Array(r),this._hullTri=new Uint32Array(r),this._hullHash=new Int32Array(this._hashSize).fill(-1),this._ids=new Uint32Array(r),this._dists=new Float64Array(r),this.update()}update(){const{coords:e,_hullPrev:r,_hullNext:n,_hullTri:i,_hullHash:a}=this,s=e.length>>1;let o=1/0,l=1/0,u=-1/0,h=-1/0;for(let L=0;Lu&&(u=v),B>h&&(h=B),this._ids[L]=L}const d=(o+u)/2,f=(l+h)/2;let p=1/0,m,_,y;for(let L=0;L0&&(_=L,p=v)}let k=e[2*_],T=e[2*_+1],C=1/0;for(let L=0;Lw&&(L[v++]=D,w=this._dists[D])}this.hull=L.subarray(0,v),this.triangles=new Uint32Array(0),this.halfedges=new Uint32Array(0);return}if(oh(b,x,k,T,M,S)<0){const L=_,v=k,B=T;_=y,k=M,T=S,y=L,M=v,S=B}const R=NF(b,x,k,T,M,S);this._cx=R.x,this._cy=R.y;for(let L=0;L0&&Math.abs(D-v)<=Rv&&Math.abs(N-B)<=Rv||(v=D,B=N,w===m||w===_||w===y))continue;let z=0;for(let $=0,lt=this._hashKey(D,N);$=0;)if(X=ct,X===z){X=-1;break}if(X===-1)continue;let J=this._addTriangle(X,w,n[X],-1,-1,i[X]);i[w]=this._legalize(J+2),i[X]=J,A++;let Y=n[X];for(;ct=n[Y],oh(D,N,e[2*Y],e[2*Y+1],e[2*ct],e[2*ct+1])<0;)J=this._addTriangle(Y,w,ct,i[w],-1,i[Y]),i[w]=this._legalize(J+2),n[Y]=Y,A--,Y=ct;if(X===z)for(;ct=r[X],oh(D,N,e[2*ct],e[2*ct+1],e[2*X],e[2*X+1])<0;)J=this._addTriangle(ct,w,X,-1,i[X],i[ct]),this._legalize(J+2),i[ct]=J,n[X]=X,A--,X=ct;this._hullStart=r[w]=X,n[X]=r[Y]=w,n[w]=Y,a[this._hashKey(D,N)]=w,a[this._hashKey(e[2*X],e[2*X+1])]=X}this.hull=new Uint32Array(A);for(let L=0,v=this._hullStart;L0?3-r:1+r)/4}function Hd(t,e,r,n){const i=t-r,a=e-n;return i*i+a*a}function RF(t,e,r,n,i,a,s,o){const l=t-s,u=e-o,h=r-s,d=n-o,f=i-s,p=a-o,m=l*l+u*u,_=h*h+d*d,y=f*f+p*p;return l*(d*y-_*p)-u*(h*y-_*f)+m*(h*p-d*f)<0}function IF(t,e,r,n,i,a){const s=r-t,o=n-e,l=i-t,u=a-e,h=s*s+o*o,d=l*l+u*u,f=.5/(s*u-o*l),p=(u*h-o*d)*f,m=(s*d-l*h)*f;return p*p+m*m}function NF(t,e,r,n,i,a){const s=r-t,o=n-e,l=i-t,u=a-e,h=s*s+o*o,d=l*l+u*u,f=.5/(s*u-o*l),p=t+(u*h-o*d)*f,m=e+(s*d-l*h)*f;return{x:p,y:m}}function wo(t,e,r,n){if(n-r<=20)for(let i=r+1;i<=n;i++){const a=t[i],s=e[a];let o=i-1;for(;o>=r&&e[t[o]]>s;)t[o+1]=t[o--];t[o+1]=a}else{const i=r+n>>1;let a=r+1,s=n;Ul(t,i,a),e[t[r]]>e[t[n]]&&Ul(t,r,n),e[t[a]]>e[t[n]]&&Ul(t,a,n),e[t[r]]>e[t[a]]&&Ul(t,r,a);const o=t[a],l=e[o];for(;;){do a++;while(e[t[a]]l);if(s=s-r?(wo(t,e,a,n),wo(t,e,r,s-1)):(wo(t,e,r,s-1),wo(t,e,a,n))}}function Ul(t,e,r){const n=t[e];t[e]=t[r],t[r]=n}function BF(t){return t[0]}function DF(t){return t[1]}const Iv=1e-6;class ws{constructor(){this._x0=this._y0=this._x1=this._y1=null,this._=""}moveTo(e,r){this._+=`M${this._x0=this._x1=+e},${this._y0=this._y1=+r}`}closePath(){this._x1!==null&&(this._x1=this._x0,this._y1=this._y0,this._+="Z")}lineTo(e,r){this._+=`L${this._x1=+e},${this._y1=+r}`}arc(e,r,n){e=+e,r=+r,n=+n;const i=e+n,a=r;if(n<0)throw new Error("negative radius");this._x1===null?this._+=`M${i},${a}`:(Math.abs(this._x1-i)>Iv||Math.abs(this._y1-a)>Iv)&&(this._+="L"+i+","+a),n&&(this._+=`A${n},${n},0,1,1,${e-n},${r}A${n},${n},0,1,1,${this._x1=i},${this._y1=a}`)}rect(e,r,n,i){this._+=`M${this._x0=this._x1=+e},${this._y0=this._y1=+r}h${+n}v${+i}h${-n}Z`}value(){return this._||null}}class Gd{constructor(){this._=[]}moveTo(e,r){this._.push([e,r])}closePath(){this._.push(this._[0].slice())}lineTo(e,r){this._.push([e,r])}value(){return this._.length?this._:null}}class Nv{constructor(e,[r,n,i,a]=[0,0,960,500]){if(!((i=+i)>=(r=+r))||!((a=+a)>=(n=+n)))throw new Error("invalid bounds");this.delaunay=e,this._circumcenters=new Float64Array(e.points.length*2),this.vectors=new Float64Array(e.points.length*2),this.xmax=i,this.xmin=r,this.ymax=a,this.ymin=n,this._init()}update(){return this.delaunay.update(),this._init(),this}_init(){const{delaunay:{points:e,hull:r,triangles:n},vectors:i}=this,a=this.circumcenters=this._circumcenters.subarray(0,n.length/3*2);for(let p=0,m=0,_=n.length,y,b;p<_;p+=3,m+=2){const x=n[p]*2,k=n[p+1]*2,T=n[p+2]*2,C=e[x],M=e[x+1],S=e[k],R=e[k+1],A=e[T],L=e[T+1],v=S-C,B=R-M,w=A-C,D=L-M,N=(v*D-B*w)*2;if(Math.abs(N)<1e-9){let z=1e9;const X=n[0]*2;z*=Math.sign((e[X]-C)*D-(e[X+1]-M)*w),y=(C+A)/2-z*D,b=(M+L)/2+z*w}else{const z=1/N,X=v*v+B*B,ct=w*w+D*D;y=C+(D*X-B*ct)*z,b=M+(v*ct-w*X)*z}a[m]=y,a[m+1]=b}let s=r[r.length-1],o,l=s*4,u,h=e[2*s],d,f=e[2*s+1];i.fill(0);for(let p=0;p1;)a-=2;for(let s=2;s4)for(let s=0;s0){if(r>=this.ymax)return null;(s=(this.ymax-r)/i)0){if(e>=this.xmax)return null;(s=(this.xmax-e)/n)this.xmax?2:0)|(rthis.ymax?8:0)}}const OF=2*Math.PI,To=Math.pow;function FF(t){return t[0]}function PF(t){return t[1]}function qF(t){const{triangles:e,coords:r}=t;for(let n=0;n1e-10)return!1}return!0}function VF(t,e,r){return[t+Math.sin(t+e)*r,e+Math.cos(t-e)*r]}class jd{static from(e,r=FF,n=PF,i){return new jd("length"in e?zF(e,r,n,i):Float64Array.from(YF(e,r,n,i)))}constructor(e){this._delaunator=new ch(e),this.inedges=new Int32Array(e.length/2),this._hullIndex=new Int32Array(e.length/2),this.points=this._delaunator.coords,this._init()}update(){return this._delaunator.update(),this._init(),this}_init(){const e=this._delaunator,r=this.points;if(e.hull&&e.hull.length>2&&qF(e)){this.collinear=Int32Array.from({length:r.length/2},(f,p)=>p).sort((f,p)=>r[2*f]-r[2*p]||r[2*f+1]-r[2*p+1]);const l=this.collinear[0],u=this.collinear[this.collinear.length-1],h=[r[2*l],r[2*l+1],r[2*u],r[2*u+1]],d=1e-8*Math.hypot(h[3]-h[1],h[2]-h[0]);for(let f=0,p=r.length/2;f0&&(this.triangles=new Int32Array(3).fill(-1),this.halfedges=new Int32Array(3).fill(-1),this.triangles[0]=i[0],s[i[0]]=1,i.length===2&&(s[i[1]]=0,this.triangles[1]=i[1],this.triangles[2]=i[1]))}voronoi(e){return new Nv(this,e)}*neighbors(e){const{inedges:r,hull:n,_hullIndex:i,halfedges:a,triangles:s,collinear:o}=this;if(o){const d=o.indexOf(e);d>0&&(yield o[d-1]),d=0&&a!==n&&a!==i;)n=a;return a}_step(e,r,n){const{inedges:i,hull:a,_hullIndex:s,halfedges:o,triangles:l,points:u}=this;if(i[e]===-1||!u.length)return(e+1)%(u.length>>1);let h=e,d=To(r-u[e*2],2)+To(n-u[e*2+1],2);const f=i[e];let p=f;do{let m=l[p];const _=To(r-u[m*2],2)+To(n-u[m*2+1],2);if(_9999?"+"+dn(t,6):dn(t,4)}function HF(t){var e=t.getUTCHours(),r=t.getUTCMinutes(),n=t.getUTCSeconds(),i=t.getUTCMilliseconds();return isNaN(t)?"Invalid Date":WF(t.getUTCFullYear())+"-"+dn(t.getUTCMonth()+1,2)+"-"+dn(t.getUTCDate(),2)+(i?"T"+dn(e,2)+":"+dn(r,2)+":"+dn(n,2)+"."+dn(i,3)+"Z":n?"T"+dn(e,2)+":"+dn(r,2)+":"+dn(n,2)+"Z":r||e?"T"+dn(e,2)+":"+dn(r,2)+"Z":"")}function uh(t){var e=new RegExp('["'+t+` +\r]`),r=t.charCodeAt(0);function n(d,f){var p,m,_=i(d,function(y,b){if(p)return p(y,b-1);m=y,p=f?UF(y,f):Dv(y)});return _.columns=m||[],_}function i(d,f){var p=[],m=d.length,_=0,y=0,b,x=m<=0,k=!1;d.charCodeAt(m-1)===Wl&&--m,d.charCodeAt(m-1)===Kd&&--m;function T(){if(x)return $d;if(k)return k=!1,Bv;var M,S=_,R;if(d.charCodeAt(S)===Xd){for(;_++=m?x=!0:(R=d.charCodeAt(_++))===Wl?k=!0:R===Kd&&(k=!0,d.charCodeAt(_)===Wl&&++_),d.slice(S+1,M-1).replace(/""/g,'"')}for(;_hh(e,r).then(n=>new DOMParser().parseFromString(n,t))}const mP=Zd("application/xml");var bP=Zd("text/html"),_P=Zd("image/svg+xml");function vP(t,e){var r,n=1;t==null&&(t=0),e==null&&(e=0);function i(){var a,s=r.length,o,l=0,u=0;for(a=0;a=(d=(o+u)/2))?o=d:u=d,(y=r>=(f=(l+h)/2))?l=f:h=f,i=a,!(a=a[b=y<<1|_]))return i[b]=s,t;if(p=+t._x.call(null,a.data),m=+t._y.call(null,a.data),e===p&&r===m)return s.next=a,i?i[b]=s:t._root=s,t;do i=i?i[b]=new Array(4):t._root=new Array(4),(_=e>=(d=(o+u)/2))?o=d:u=d,(y=r>=(f=(l+h)/2))?l=f:h=f;while((b=y<<1|_)===(x=(m>=f)<<1|p>=d));return i[x]=a,i[b]=s,t}function kP(t){var e,r,n=t.length,i,a,s=new Array(n),o=new Array(n),l=1/0,u=1/0,h=-1/0,d=-1/0;for(r=0;rh&&(h=i),ad&&(d=a));if(l>h||u>d)return this;for(this.cover(l,u).cover(h,d),r=0;rt||t>=i||n>e||e>=a;)switch(u=(eh||(o=m.y0)>d||(l=m.x1)=b)<<1|t>=y)&&(m=f[f.length-1],f[f.length-1]=f[f.length-1-_],f[f.length-1-_]=m)}else{var x=t-+this._x.call(null,p.data),k=e-+this._y.call(null,p.data),T=x*x+k*k;if(T=(f=(s+l)/2))?s=f:l=f,(_=d>=(p=(o+u)/2))?o=p:u=p,e=r,!(r=r[y=_<<1|m]))return this;if(!r.length)break;(e[y+1&3]||e[y+2&3]||e[y+3&3])&&(n=e,b=y)}for(;r.data!==t;)if(i=r,!(r=r.next))return this;return(a=r.next)&&delete r.next,i?(a?i.next=a:delete i.next,this):e?(a?e[y]=a:delete e[y],(r=e[0]||e[1]||e[2]||e[3])&&r===(e[3]||e[2]||e[1]||e[0])&&!r.length&&(n?n[b]=r:this._root=r),this):(this._root=a,this)}function AP(t){for(var e=0,r=t.length;ef.index){var v=p-R.x-R.vx,B=m-R.y-R.vy,w=v*v+B*B;wp+L||Mm+L||Su.r&&(u.r=u[h].r)}function l(){if(!!e){var u,h=e.length,d;for(r=new Array(h),u=0;u[e(C,M,s),C])),T;for(y=0,o=new Array(b);y(t=(YP*t+UP)%Uv)/Uv}function HP(t){return t.x}function GP(t){return t.y}var jP=10,$P=Math.PI*(3-Math.sqrt(5));function XP(t){var e,r=1,n=.001,i=1-Math.pow(n,1/300),a=0,s=.6,o=new Map,l=Ju(d),u=fs("tick","end"),h=WP();t==null&&(t=[]);function d(){f(),u.call("tick",e),r1?(y==null?o.delete(_):o.set(_,m(y)),e):o.get(_)},find:function(_,y,b){var x=0,k=t.length,T,C,M,S,R;for(b==null?b=1/0:b*=b,x=0;x1?(u.on(_,y),e):u.on(_)}}}function KP(){var t,e,r,n,i=vr(-30),a,s=1,o=1/0,l=.81;function u(p){var m,_=t.length,y=fh(t,HP,GP).visitAfter(d);for(n=p,m=0;m<_;++m)e=t[m],y.visit(f)}function h(){if(!!t){var p,m=t.length,_;for(a=new Array(m),p=0;p=o)return;(p.data!==e||p.next)&&(b===0&&(b=Na(r),T+=b*b),x===0&&(x=Na(r),T+=x*x),T=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)}function dh(t,e){if((r=(t=e?t.toExponential(e-1):t.toExponential()).indexOf("e"))<0)return null;var r,n=t.slice(0,r);return[n.length>1?n[0]+n.slice(2):n,+t.slice(r+1)]}function Eo(t){return t=dh(Math.abs(t)),t?t[1]:NaN}function eq(t,e){return function(r,n){for(var i=r.length,a=[],s=0,o=t[0],l=0;i>0&&o>0&&(l+o+1>n&&(o=Math.max(1,n-l)),a.push(r.substring(i-=o,i+o)),!((l+=o+1)>n));)o=t[s=(s+1)%t.length];return a.reverse().join(e)}}function rq(t){return function(e){return e.replace(/[0-9]/g,function(r){return t[+r]})}}var nq=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Co(t){if(!(e=nq.exec(t)))throw new Error("invalid format: "+t);var e;return new ph({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}Co.prototype=ph.prototype;function ph(t){this.fill=t.fill===void 0?" ":t.fill+"",this.align=t.align===void 0?">":t.align+"",this.sign=t.sign===void 0?"-":t.sign+"",this.symbol=t.symbol===void 0?"":t.symbol+"",this.zero=!!t.zero,this.width=t.width===void 0?void 0:+t.width,this.comma=!!t.comma,this.precision=t.precision===void 0?void 0:+t.precision,this.trim=!!t.trim,this.type=t.type===void 0?"":t.type+""}ph.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(this.width===void 0?"":Math.max(1,this.width|0))+(this.comma?",":"")+(this.precision===void 0?"":"."+Math.max(0,this.precision|0))+(this.trim?"~":"")+this.type};function iq(t){t:for(var e=t.length,r=1,n=-1,i;r0&&(n=0);break}return n>0?t.slice(0,n)+t.slice(i+1):t}var Wv;function aq(t,e){var r=dh(t,e);if(!r)return t+"";var n=r[0],i=r[1],a=i-(Wv=Math.max(-8,Math.min(8,Math.floor(i/3)))*3)+1,s=n.length;return a===s?n:a>s?n+new Array(a-s+1).join("0"):a>0?n.slice(0,a)+"."+n.slice(a):"0."+new Array(1-a).join("0")+dh(t,Math.max(0,e+a-1))[0]}function Hv(t,e){var r=dh(t,e);if(!r)return t+"";var n=r[0],i=r[1];return i<0?"0."+new Array(-i).join("0")+n:n.length>i+1?n.slice(0,i+1)+"."+n.slice(i+1):n+new Array(i-n.length+2).join("0")}const Gv={"%":(t,e)=>(t*100).toFixed(e),b:t=>Math.round(t).toString(2),c:t=>t+"",d:tq,e:(t,e)=>t.toExponential(e),f:(t,e)=>t.toFixed(e),g:(t,e)=>t.toPrecision(e),o:t=>Math.round(t).toString(8),p:(t,e)=>Hv(t*100,e),r:Hv,s:aq,X:t=>Math.round(t).toString(16).toUpperCase(),x:t=>Math.round(t).toString(16)};function jv(t){return t}var $v=Array.prototype.map,Xv=["y","z","a","f","p","n","\xB5","m","","k","M","G","T","P","E","Z","Y"];function Kv(t){var e=t.grouping===void 0||t.thousands===void 0?jv:eq($v.call(t.grouping,Number),t.thousands+""),r=t.currency===void 0?"":t.currency[0]+"",n=t.currency===void 0?"":t.currency[1]+"",i=t.decimal===void 0?".":t.decimal+"",a=t.numerals===void 0?jv:rq($v.call(t.numerals,String)),s=t.percent===void 0?"%":t.percent+"",o=t.minus===void 0?"\u2212":t.minus+"",l=t.nan===void 0?"NaN":t.nan+"";function u(d){d=Co(d);var f=d.fill,p=d.align,m=d.sign,_=d.symbol,y=d.zero,b=d.width,x=d.comma,k=d.precision,T=d.trim,C=d.type;C==="n"?(x=!0,C="g"):Gv[C]||(k===void 0&&(k=12),T=!0,C="g"),(y||f==="0"&&p==="=")&&(y=!0,f="0",p="=");var M=_==="$"?r:_==="#"&&/[boxX]/.test(C)?"0"+C.toLowerCase():"",S=_==="$"?n:/[%p]/.test(C)?s:"",R=Gv[C],A=/[defgprs%]/.test(C);k=k===void 0?6:/[gprs]/.test(C)?Math.max(1,Math.min(21,k)):Math.max(0,Math.min(20,k));function L(v){var B=M,w=S,D,N,z;if(C==="c")w=R(v)+w,v="";else{v=+v;var X=v<0||1/v<0;if(v=isNaN(v)?l:R(Math.abs(v),k),T&&(v=iq(v)),X&&+v==0&&m!=="+"&&(X=!1),B=(X?m==="("?m:o:m==="-"||m==="("?"":m)+B,w=(C==="s"?Xv[8+Wv/3]:"")+w+(X&&m==="("?")":""),A){for(D=-1,N=v.length;++Dz||z>57){w=(z===46?i+v.slice(D+1):v.slice(D))+w,v=v.slice(0,D);break}}}x&&!y&&(v=e(v,1/0));var ct=B.length+v.length+w.length,J=ct>1)+B+v+w+J.slice(ct);break;default:v=J+B+v+w;break}return a(v)}return L.toString=function(){return d+""},L}function h(d,f){var p=u((d=Co(d),d.type="f",d)),m=Math.max(-8,Math.min(8,Math.floor(Eo(f)/3)))*3,_=Math.pow(10,-m),y=Xv[8+m/3];return function(b){return p(_*b)+y}}return{format:u,formatPrefix:h}}var gh,yh,Jd;Zv({thousands:",",grouping:[3],currency:["$",""]});function Zv(t){return gh=Kv(t),yh=gh.format,Jd=gh.formatPrefix,gh}function Qv(t){return Math.max(0,-Eo(Math.abs(t)))}function Jv(t,e){return Math.max(0,Math.max(-8,Math.min(8,Math.floor(Eo(e)/3)))*3-Eo(Math.abs(t)))}function t6(t,e){return t=Math.abs(t),e=Math.abs(e)-t,Math.max(0,Eo(e)-Eo(t))+1}var te=1e-6,Hl=1e-12,Ae=Math.PI,rr=Ae/2,mh=Ae/4,Qr=Ae*2,Ue=180/Ae,re=Ae/180,Ne=Math.abs,So=Math.atan,Jr=Math.atan2,Kt=Math.cos,bh=Math.ceil,e6=Math.exp,t2=Math.hypot,_h=Math.log,e2=Math.pow,Ht=Math.sin,Dn=Math.sign||function(t){return t>0?1:t<0?-1:0},Sr=Math.sqrt,r2=Math.tan;function r6(t){return t>1?0:t<-1?Ae:Math.acos(t)}function tn(t){return t>1?rr:t<-1?-rr:Math.asin(t)}function n6(t){return(t=Ht(t/2))*t}function Je(){}function vh(t,e){t&&a6.hasOwnProperty(t.type)&&a6[t.type](t,e)}var i6={Feature:function(t,e){vh(t.geometry,e)},FeatureCollection:function(t,e){for(var r=t.features,n=-1,i=r.length;++n=0?1:-1,i=n*r,a=Kt(e),s=Ht(e),o=s2*s,l=a2*a+o*Kt(i),u=o*n*Ht(i);xh.add(Jr(u,l)),i2=t,a2=a,s2=s}function cq(t){return kh=new _r,ti(t,Si),kh*2}function wh(t){return[Jr(t[1],t[0]),tn(t[2])]}function Cs(t){var e=t[0],r=t[1],n=Kt(r);return[n*Kt(e),n*Ht(e),Ht(r)]}function Th(t,e){return t[0]*e[0]+t[1]*e[1]+t[2]*e[2]}function Ao(t,e){return[t[1]*e[2]-t[2]*e[1],t[2]*e[0]-t[0]*e[2],t[0]*e[1]-t[1]*e[0]]}function o2(t,e){t[0]+=e[0],t[1]+=e[1],t[2]+=e[2]}function Eh(t,e){return[t[0]*e,t[1]*e,t[2]*e]}function Ch(t){var e=Sr(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=e,t[1]/=e,t[2]/=e}var tr,pn,nr,En,Ss,u6,h6,Mo,Gl,Ba,Qi,Ji={point:l2,lineStart:d6,lineEnd:p6,polygonStart:function(){Ji.point=g6,Ji.lineStart=uq,Ji.lineEnd=hq,Gl=new _r,Si.polygonStart()},polygonEnd:function(){Si.polygonEnd(),Ji.point=l2,Ji.lineStart=d6,Ji.lineEnd=p6,xh<0?(tr=-(nr=180),pn=-(En=90)):Gl>te?En=90:Gl<-te&&(pn=-90),Qi[0]=tr,Qi[1]=nr},sphere:function(){tr=-(nr=180),pn=-(En=90)}};function l2(t,e){Ba.push(Qi=[tr=t,nr=t]),eEn&&(En=e)}function f6(t,e){var r=Cs([t*re,e*re]);if(Mo){var n=Ao(Mo,r),i=[n[1],-n[0],0],a=Ao(i,n);Ch(a),a=wh(a);var s=t-Ss,o=s>0?1:-1,l=a[0]*Ue*o,u,h=Ne(s)>180;h^(o*SsEn&&(En=u)):(l=(l+360)%360-180,h^(o*SsEn&&(En=e))),h?tCn(tr,nr)&&(nr=t):Cn(t,nr)>Cn(tr,nr)&&(tr=t):nr>=tr?(tnr&&(nr=t)):t>Ss?Cn(tr,t)>Cn(tr,nr)&&(nr=t):Cn(t,nr)>Cn(tr,nr)&&(tr=t)}else Ba.push(Qi=[tr=t,nr=t]);eEn&&(En=e),Mo=r,Ss=t}function d6(){Ji.point=f6}function p6(){Qi[0]=tr,Qi[1]=nr,Ji.point=l2,Mo=null}function g6(t,e){if(Mo){var r=t-Ss;Gl.add(Ne(r)>180?r+(r>0?360:-360):r)}else u6=t,h6=e;Si.point(t,e),f6(t,e)}function uq(){Si.lineStart()}function hq(){g6(u6,h6),Si.lineEnd(),Ne(Gl)>te&&(tr=-(nr=180)),Qi[0]=tr,Qi[1]=nr,Mo=null}function Cn(t,e){return(e-=t)<0?e+360:e}function fq(t,e){return t[0]-e[0]}function y6(t,e){return t[0]<=t[1]?t[0]<=e&&e<=t[1]:eCn(n[0],n[1])&&(n[1]=i[1]),Cn(i[0],n[1])>Cn(n[0],n[1])&&(n[0]=i[0])):a.push(n=i);for(s=-1/0,r=a.length-1,e=0,n=a[r];e<=r;n=i,++e)i=a[e],(o=Cn(n[1],i[0]))>s&&(s=o,tr=i[0],nr=n[1])}return Ba=Qi=null,tr===1/0||pn===1/0?[[NaN,NaN],[NaN,NaN]]:[[tr,pn],[nr,En]]}var jl,Sh,Ah,Mh,Lh,Rh,Ih,Nh,c2,u2,h2,m6,b6,en,rn,nn,ei={sphere:Je,point:f2,lineStart:_6,lineEnd:v6,polygonStart:function(){ei.lineStart=yq,ei.lineEnd=mq},polygonEnd:function(){ei.lineStart=_6,ei.lineEnd=v6}};function f2(t,e){t*=re,e*=re;var r=Kt(e);$l(r*Kt(t),r*Ht(t),Ht(e))}function $l(t,e,r){++jl,Ah+=(t-Ah)/jl,Mh+=(e-Mh)/jl,Lh+=(r-Lh)/jl}function _6(){ei.point=pq}function pq(t,e){t*=re,e*=re;var r=Kt(e);en=r*Kt(t),rn=r*Ht(t),nn=Ht(e),ei.point=gq,$l(en,rn,nn)}function gq(t,e){t*=re,e*=re;var r=Kt(e),n=r*Kt(t),i=r*Ht(t),a=Ht(e),s=Jr(Sr((s=rn*a-nn*i)*s+(s=nn*n-en*a)*s+(s=en*i-rn*n)*s),en*n+rn*i+nn*a);Sh+=s,Rh+=s*(en+(en=n)),Ih+=s*(rn+(rn=i)),Nh+=s*(nn+(nn=a)),$l(en,rn,nn)}function v6(){ei.point=f2}function yq(){ei.point=bq}function mq(){x6(m6,b6),ei.point=f2}function bq(t,e){m6=t,b6=e,t*=re,e*=re,ei.point=x6;var r=Kt(e);en=r*Kt(t),rn=r*Ht(t),nn=Ht(e),$l(en,rn,nn)}function x6(t,e){t*=re,e*=re;var r=Kt(e),n=r*Kt(t),i=r*Ht(t),a=Ht(e),s=rn*a-nn*i,o=nn*n-en*a,l=en*i-rn*n,u=t2(s,o,l),h=tn(u),d=u&&-h/u;c2.add(d*s),u2.add(d*o),h2.add(d*l),Sh+=h,Rh+=h*(en+(en=n)),Ih+=h*(rn+(rn=i)),Nh+=h*(nn+(nn=a)),$l(en,rn,nn)}function _q(t){jl=Sh=Ah=Mh=Lh=Rh=Ih=Nh=0,c2=new _r,u2=new _r,h2=new _r,ti(t,ei);var e=+c2,r=+u2,n=+h2,i=t2(e,r,n);return iAe?t+Math.round(-t/Qr)*Qr:t,e]}p2.invert=p2;function g2(t,e,r){return(t%=Qr)?e||r?d2(w6(t),T6(e,r)):w6(t):e||r?T6(e,r):p2}function k6(t){return function(e,r){return e+=t,[e>Ae?e-Qr:e<-Ae?e+Qr:e,r]}}function w6(t){var e=k6(t);return e.invert=k6(-t),e}function T6(t,e){var r=Kt(t),n=Ht(t),i=Kt(e),a=Ht(e);function s(o,l){var u=Kt(l),h=Kt(o)*u,d=Ht(o)*u,f=Ht(l),p=f*r+h*n;return[Jr(d*i-p*a,h*r-f*n),tn(p*i+d*a)]}return s.invert=function(o,l){var u=Kt(l),h=Kt(o)*u,d=Ht(o)*u,f=Ht(l),p=f*i-d*a;return[Jr(d*i+f*a,h*r+p*n),tn(p*r-h*n)]},s}function E6(t){t=g2(t[0]*re,t[1]*re,t.length>2?t[2]*re:0);function e(r){return r=t(r[0]*re,r[1]*re),r[0]*=Ue,r[1]*=Ue,r}return e.invert=function(r){return r=t.invert(r[0]*re,r[1]*re),r[0]*=Ue,r[1]*=Ue,r},e}function C6(t,e,r,n,i,a){if(!!r){var s=Kt(e),o=Ht(e),l=n*r;i==null?(i=e+n*Qr,a=e-l/2):(i=S6(s,i),a=S6(s,a),(n>0?ia)&&(i+=n*Qr));for(var u,h=i;n>0?h>a:h1&&t.push(t.pop().concat(t.shift()))},result:function(){var r=t;return t=[],e=null,r}}}function Bh(t,e){return Ne(t[0]-e[0])=0;--o)i.point((d=h[o])[0],d[1]);else n(f.x,f.p.x,-1,i);f=f.p}f=f.o,h=f.z,p=!p}while(!f.v);i.lineEnd()}}}function L6(t){if(!!(e=t.length)){for(var e,r=0,n=t[0],i;++r=0?1:-1,L=A*R,v=L>Ae,B=y*M;if(l.add(Jr(B*A*Ht(L),b*S+B*Kt(L))),s+=v?R+A*Qr:R,v^m>=r^T>=r){var w=Ao(Cs(p),Cs(k));Ch(w);var D=Ao(a,w);Ch(D);var N=(v^R>=0?-1:1)*tn(D[2]);(n>N||n===N&&(w[0]||w[1]))&&(o+=v^R>=0?1:-1)}}return(s<-te||s0){for(l||(i.polygonStart(),l=!0),i.lineStart(),M=0;M1&&T&2&&C.push(C.pop().concat(C.shift())),h.push(C.filter(xq))}}return f}}function xq(t){return t.length>1}function kq(t,e){return((t=t.x)[0]<0?t[1]-rr-te:rr-t[1])-((e=e.x)[0]<0?e[1]-rr-te:rr-e[1])}const m2=I6(function(){return!0},wq,Eq,[-Ae,-rr]);function wq(t){var e=NaN,r=NaN,n=NaN,i;return{lineStart:function(){t.lineStart(),i=1},point:function(a,s){var o=a>0?Ae:-Ae,l=Ne(a-e);Ne(l-Ae)0?rr:-rr),t.point(n,r),t.lineEnd(),t.lineStart(),t.point(o,r),t.point(a,r),i=0):n!==o&&l>=Ae&&(Ne(e-n)te?So((Ht(e)*(a=Kt(n))*Ht(r)-Ht(n)*(i=Kt(e))*Ht(t))/(i*a*s)):(e+n)/2}function Eq(t,e,r,n){var i;if(t==null)i=r*rr,n.point(-Ae,i),n.point(0,i),n.point(Ae,i),n.point(Ae,0),n.point(Ae,-i),n.point(0,-i),n.point(-Ae,-i),n.point(-Ae,0),n.point(-Ae,i);else if(Ne(t[0]-e[0])>te){var a=t[0]0,i=Ne(e)>te;function a(h,d,f,p){C6(p,t,r,f,h,d)}function s(h,d){return Kt(h)*Kt(d)>e}function o(h){var d,f,p,m,_;return{lineStart:function(){m=p=!1,_=1},point:function(y,b){var x=[y,b],k,T=s(y,b),C=n?T?0:u(y,b):T?u(y+(y<0?Ae:-Ae),b):0;if(!d&&(m=p=T)&&h.lineStart(),T!==p&&(k=l(d,x),(!k||Bh(d,k)||Bh(x,k))&&(x[2]=1)),T!==p)_=0,T?(h.lineStart(),k=l(x,d),h.point(k[0],k[1])):(k=l(d,x),h.point(k[0],k[1],2),h.lineEnd()),d=k;else if(i&&d&&n^T){var M;!(C&f)&&(M=l(x,d,!0))&&(_=0,n?(h.lineStart(),h.point(M[0][0],M[0][1]),h.point(M[1][0],M[1][1]),h.lineEnd()):(h.point(M[1][0],M[1][1]),h.lineEnd(),h.lineStart(),h.point(M[0][0],M[0][1],3)))}T&&(!d||!Bh(d,x))&&h.point(x[0],x[1]),d=x,p=T,f=C},lineEnd:function(){p&&h.lineEnd(),d=null},clean:function(){return _|(m&&p)<<1}}}function l(h,d,f){var p=Cs(h),m=Cs(d),_=[1,0,0],y=Ao(p,m),b=Th(y,y),x=y[0],k=b-x*x;if(!k)return!f&&h;var T=e*b/k,C=-e*x/k,M=Ao(_,y),S=Eh(_,T),R=Eh(y,C);o2(S,R);var A=M,L=Th(S,A),v=Th(A,A),B=L*L-v*(Th(S,S)-1);if(!(B<0)){var w=Sr(B),D=Eh(A,(-L-w)/v);if(o2(D,S),D=wh(D),!f)return D;var N=h[0],z=d[0],X=h[1],ct=d[1],J;z0^D[1]<(Ne(D[0]-N)Ae^(N<=D[0]&&D[0]<=z)){var ut=Eh(A,(-L+w)/v);return o2(ut,S),[D,wh(ut)]}}}function u(h,d){var f=n?t:Ae-t,p=0;return h<-f?p|=1:h>f&&(p|=2),d<-f?p|=4:d>f&&(p|=8),p}return I6(s,o,a,n?[0,-t]:[-Ae,t-Ae])}function Cq(t,e,r,n,i,a){var s=t[0],o=t[1],l=e[0],u=e[1],h=0,d=1,f=l-s,p=u-o,m;if(m=r-s,!(!f&&m>0)){if(m/=f,f<0){if(m0){if(m>d)return;m>h&&(h=m)}if(m=i-s,!(!f&&m<0)){if(m/=f,f<0){if(m>d)return;m>h&&(h=m)}else if(f>0){if(m0)){if(m/=p,p<0){if(m0){if(m>d)return;m>h&&(h=m)}if(m=a-o,!(!p&&m<0)){if(m/=p,p<0){if(m>d)return;m>h&&(h=m)}else if(p>0){if(m0&&(t[0]=s+h*f,t[1]=o+h*p),d<1&&(e[0]=s+d*f,e[1]=o+d*p),!0}}}}}var Xl=1e9,Oh=-Xl;function Fh(t,e,r,n){function i(u,h){return t<=u&&u<=r&&e<=h&&h<=n}function a(u,h,d,f){var p=0,m=0;if(u==null||(p=s(u,d))!==(m=s(h,d))||l(u,h)<0^d>0)do f.point(p===0||p===3?t:r,p>1?n:e);while((p=(p+d+4)%4)!==m);else f.point(h[0],h[1])}function s(u,h){return Ne(u[0]-t)0?0:3:Ne(u[0]-r)0?2:1:Ne(u[1]-e)0?1:0:h>0?3:2}function o(u,h){return l(u.x,h.x)}function l(u,h){var d=s(u,1),f=s(h,1);return d!==f?d-f:d===0?h[1]-u[1]:d===1?u[0]-h[0]:d===2?u[1]-h[1]:h[0]-u[0]}return function(u){var h=u,d=A6(),f,p,m,_,y,b,x,k,T,C,M,S={point:R,lineStart:B,lineEnd:w,polygonStart:L,polygonEnd:v};function R(N,z){i(N,z)&&h.point(N,z)}function A(){for(var N=0,z=0,X=p.length;zn&&(W-lt)*(n-ut)>(tt-ut)*(t-lt)&&++N:tt<=n&&(W-lt)*(n-ut)<(tt-ut)*(t-lt)&&--N;return N}function L(){h=d,f=[],p=[],M=!0}function v(){var N=A(),z=M&&N,X=(f=j0(f)).length;(z||X)&&(u.polygonStart(),z&&(u.lineStart(),a(null,null,1,u),u.lineEnd()),X&&M6(f,o,N,a,u),u.polygonEnd()),h=u,f=p=m=null}function B(){S.point=D,p&&p.push(m=[]),C=!0,T=!1,x=k=NaN}function w(){f&&(D(_,y),b&&T&&d.rejoin(),f.push(d.result())),S.point=R,T&&h.lineEnd()}function D(N,z){var X=i(N,z);if(p&&m.push([N,z]),C)_=N,y=z,b=X,C=!1,X&&(h.lineStart(),h.point(N,z));else if(X&&T)h.point(N,z);else{var ct=[x=Math.max(Oh,Math.min(Xl,x)),k=Math.max(Oh,Math.min(Xl,k))],J=[N=Math.max(Oh,Math.min(Xl,N)),z=Math.max(Oh,Math.min(Xl,z))];Cq(ct,J,t,e,r,n)?(T||(h.lineStart(),h.point(ct[0],ct[1])),h.point(J[0],J[1]),X||h.lineEnd(),M=!1):X&&(h.lineStart(),h.point(N,z),M=!1)}x=N,k=z,T=X}return S}}function Sq(){var t=0,e=0,r=960,n=500,i,a,s;return s={stream:function(o){return i&&a===o?i:i=Fh(t,e,r,n)(a=o)},extent:function(o){return arguments.length?(t=+o[0][0],e=+o[0][1],r=+o[1][0],n=+o[1][1],i=a=null,s):[[t,e],[r,n]]}}}var b2,_2,Ph,qh,Ro={sphere:Je,point:Je,lineStart:Aq,lineEnd:Je,polygonStart:Je,polygonEnd:Je};function Aq(){Ro.point=Lq,Ro.lineEnd=Mq}function Mq(){Ro.point=Ro.lineEnd=Je}function Lq(t,e){t*=re,e*=re,_2=t,Ph=Ht(e),qh=Kt(e),Ro.point=Rq}function Rq(t,e){t*=re,e*=re;var r=Ht(e),n=Kt(e),i=Ne(t-_2),a=Kt(i),s=Ht(i),o=n*s,l=qh*r-Ph*n*a,u=Ph*r+qh*n*a;b2.add(Jr(Sr(o*o+l*l),u)),_2=t,Ph=r,qh=n}function B6(t){return b2=new _r,ti(t,Ro),+b2}var v2=[null,null],Iq={type:"LineString",coordinates:v2};function Vh(t,e){return v2[0]=t,v2[1]=e,B6(Iq)}var D6={Feature:function(t,e){return zh(t.geometry,e)},FeatureCollection:function(t,e){for(var r=t.features,n=-1,i=r.length;++n0&&(i=Vh(t[a],t[a-1]),i>0&&r<=i&&n<=i&&(r+n-i)*(1-Math.pow((r-n)/i,2))te}).map(f)).concat(Ca(bh(a/u)*u,i,u).filter(function(k){return Ne(k%d)>te}).map(p))}return b.lines=function(){return x().map(function(k){return{type:"LineString",coordinates:k}})},b.outline=function(){return{type:"Polygon",coordinates:[m(n).concat(_(s).slice(1),m(r).reverse().slice(1),_(o).reverse().slice(1))]}},b.extent=function(k){return arguments.length?b.extentMajor(k).extentMinor(k):b.extentMinor()},b.extentMajor=function(k){return arguments.length?(n=+k[0][0],r=+k[1][0],o=+k[0][1],s=+k[1][1],n>r&&(k=n,n=r,r=k),o>s&&(k=o,o=s,s=k),b.precision(y)):[[n,o],[r,s]]},b.extentMinor=function(k){return arguments.length?(e=+k[0][0],t=+k[1][0],a=+k[0][1],i=+k[1][1],e>t&&(k=e,e=t,t=k),a>i&&(k=a,a=i,i=k),b.precision(y)):[[e,a],[t,i]]},b.step=function(k){return arguments.length?b.stepMajor(k).stepMinor(k):b.stepMinor()},b.stepMajor=function(k){return arguments.length?(h=+k[0],d=+k[1],b):[h,d]},b.stepMinor=function(k){return arguments.length?(l=+k[0],u=+k[1],b):[l,u]},b.precision=function(k){return arguments.length?(y=+k,f=z6(a,i,90),p=Y6(e,t,y),m=z6(o,s,90),_=Y6(n,r,y),b):y},b.extentMajor([[-180,-90+te],[180,90-te]]).extentMinor([[-180,-80-te],[180,80+te]])}function Dq(){return U6()()}function Oq(t,e){var r=t[0]*re,n=t[1]*re,i=e[0]*re,a=e[1]*re,s=Kt(n),o=Ht(n),l=Kt(a),u=Ht(a),h=s*Kt(r),d=s*Ht(r),f=l*Kt(i),p=l*Ht(i),m=2*tn(Sr(n6(a-n)+s*l*n6(i-r))),_=Ht(m),y=m?function(b){var x=Ht(b*=m)/_,k=Ht(m-b)/_,T=k*h+x*f,C=k*d+x*p,M=k*o+x*u;return[Jr(C,T)*Ue,Jr(M,Sr(T*T+C*C))*Ue]}:function(){return[r*Ue,n*Ue]};return y.distance=m,y}const Kl=t=>t;var x2=new _r,k2=new _r,W6,H6,w2,T2,Da={point:Je,lineStart:Je,lineEnd:Je,polygonStart:function(){Da.lineStart=Fq,Da.lineEnd=qq},polygonEnd:function(){Da.lineStart=Da.lineEnd=Da.point=Je,x2.add(Ne(k2)),k2=new _r},result:function(){var t=x2/2;return x2=new _r,t}};function Fq(){Da.point=Pq}function Pq(t,e){Da.point=G6,W6=w2=t,H6=T2=e}function G6(t,e){k2.add(T2*t-w2*e),w2=t,T2=e}function qq(){G6(W6,H6)}const j6=Da;var Io=1/0,Yh=Io,Zl=-Io,Uh=Zl,Vq={point:zq,lineStart:Je,lineEnd:Je,polygonStart:Je,polygonEnd:Je,result:function(){var t=[[Io,Yh],[Zl,Uh]];return Zl=Uh=-(Yh=Io=1/0),t}};function zq(t,e){tZl&&(Zl=t),eUh&&(Uh=e)}const Wh=Vq;var E2=0,C2=0,Ql=0,Hh=0,Gh=0,No=0,S2=0,A2=0,Jl=0,$6,X6,Ai,Mi,ri={point:As,lineStart:K6,lineEnd:Z6,polygonStart:function(){ri.lineStart=Wq,ri.lineEnd=Hq},polygonEnd:function(){ri.point=As,ri.lineStart=K6,ri.lineEnd=Z6},result:function(){var t=Jl?[S2/Jl,A2/Jl]:No?[Hh/No,Gh/No]:Ql?[E2/Ql,C2/Ql]:[NaN,NaN];return E2=C2=Ql=Hh=Gh=No=S2=A2=Jl=0,t}};function As(t,e){E2+=t,C2+=e,++Ql}function K6(){ri.point=Yq}function Yq(t,e){ri.point=Uq,As(Ai=t,Mi=e)}function Uq(t,e){var r=t-Ai,n=e-Mi,i=Sr(r*r+n*n);Hh+=i*(Ai+t)/2,Gh+=i*(Mi+e)/2,No+=i,As(Ai=t,Mi=e)}function Z6(){ri.point=As}function Wq(){ri.point=Gq}function Hq(){Q6($6,X6)}function Gq(t,e){ri.point=Q6,As($6=Ai=t,X6=Mi=e)}function Q6(t,e){var r=t-Ai,n=e-Mi,i=Sr(r*r+n*n);Hh+=i*(Ai+t)/2,Gh+=i*(Mi+e)/2,No+=i,i=Mi*t-Ai*e,S2+=i*(Ai+t),A2+=i*(Mi+e),Jl+=i*3,As(Ai=t,Mi=e)}const J6=ri;function tx(t){this._context=t}tx.prototype={_radius:4.5,pointRadius:function(t){return this._radius=t,this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){this._line===0&&this._context.closePath(),this._point=NaN},point:function(t,e){switch(this._point){case 0:{this._context.moveTo(t,e),this._point=1;break}case 1:{this._context.lineTo(t,e);break}default:{this._context.moveTo(t+this._radius,e),this._context.arc(t,e,this._radius,0,Qr);break}}},result:Je};var M2=new _r,L2,ex,rx,tc,ec,jh={point:Je,lineStart:function(){jh.point=jq},lineEnd:function(){L2&&nx(ex,rx),jh.point=Je},polygonStart:function(){L2=!0},polygonEnd:function(){L2=null},result:function(){var t=+M2;return M2=new _r,t}};function jq(t,e){jh.point=nx,ex=tc=t,rx=ec=e}function nx(t,e){tc-=t,ec-=e,M2.add(Sr(tc*tc+ec*ec)),tc=t,ec=e}const ix=jh;function ax(){this._string=[]}ax.prototype={_radius:4.5,_circle:sx(4.5),pointRadius:function(t){return(t=+t)!==this._radius&&(this._radius=t,this._circle=null),this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){this._line===0&&this._string.push("Z"),this._point=NaN},point:function(t,e){switch(this._point){case 0:{this._string.push("M",t,",",e),this._point=1;break}case 1:{this._string.push("L",t,",",e);break}default:{this._circle==null&&(this._circle=sx(this._radius)),this._string.push("M",t,",",e,this._circle);break}}},result:function(){if(this._string.length){var t=this._string.join("");return this._string=[],t}else return null}};function sx(t){return"m0,"+t+"a"+t+","+t+" 0 1,1 0,"+-2*t+"a"+t+","+t+" 0 1,1 0,"+2*t+"z"}function $q(t,e){var r=4.5,n,i;function a(s){return s&&(typeof r=="function"&&i.pointRadius(+r.apply(this,arguments)),ti(s,n(i))),i.result()}return a.area=function(s){return ti(s,n(j6)),j6.result()},a.measure=function(s){return ti(s,n(ix)),ix.result()},a.bounds=function(s){return ti(s,n(Wh)),Wh.result()},a.centroid=function(s){return ti(s,n(J6)),J6.result()},a.projection=function(s){return arguments.length?(n=s==null?(t=null,Kl):(t=s).stream,a):t},a.context=function(s){return arguments.length?(i=s==null?(e=null,new ax):new tx(e=s),typeof r!="function"&&i.pointRadius(r),a):e},a.pointRadius=function(s){return arguments.length?(r=typeof s=="function"?s:(i.pointRadius(+s),+s),a):r},a.projection(t).context(e)}function Xq(t){return{stream:rc(t)}}function rc(t){return function(e){var r=new R2;for(var n in t)r[n]=t[n];return r.stream=e,r}}function R2(){}R2.prototype={constructor:R2,point:function(t,e){this.stream.point(t,e)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}};function I2(t,e,r){var n=t.clipExtent&&t.clipExtent();return t.scale(150).translate([0,0]),n!=null&&t.clipExtent(null),ti(r,t.stream(Wh)),e(Wh.result()),n!=null&&t.clipExtent(n),t}function $h(t,e,r){return I2(t,function(n){var i=e[1][0]-e[0][0],a=e[1][1]-e[0][1],s=Math.min(i/(n[1][0]-n[0][0]),a/(n[1][1]-n[0][1])),o=+e[0][0]+(i-s*(n[1][0]+n[0][0]))/2,l=+e[0][1]+(a-s*(n[1][1]+n[0][1]))/2;t.scale(150*s).translate([o,l])},r)}function N2(t,e,r){return $h(t,[[0,0],e],r)}function B2(t,e,r){return I2(t,function(n){var i=+e,a=i/(n[1][0]-n[0][0]),s=(i-a*(n[1][0]+n[0][0]))/2,o=-a*n[0][1];t.scale(150*a).translate([s,o])},r)}function D2(t,e,r){return I2(t,function(n){var i=+e,a=i/(n[1][1]-n[0][1]),s=-a*n[0][0],o=(i-a*(n[1][1]+n[0][1]))/2;t.scale(150*a).translate([s,o])},r)}var ox=16,Kq=Kt(30*re);function lx(t,e){return+e?Qq(t,e):Zq(t)}function Zq(t){return rc({point:function(e,r){e=t(e,r),this.stream.point(e[0],e[1])}})}function Qq(t,e){function r(n,i,a,s,o,l,u,h,d,f,p,m,_,y){var b=u-n,x=h-i,k=b*b+x*x;if(k>4*e&&_--){var T=s+f,C=o+p,M=l+m,S=Sr(T*T+C*C+M*M),R=tn(M/=S),A=Ne(Ne(M)-1)e||Ne((b*w+x*D)/k-.5)>.3||s*f+o*p+l*m2?N[2]%360*re:0,w()):[o*Ue,l*Ue,u*Ue]},v.angle=function(N){return arguments.length?(d=N%360*re,w()):d*Ue},v.reflectX=function(N){return arguments.length?(f=N?-1:1,w()):f<0},v.reflectY=function(N){return arguments.length?(p=N?-1:1,w()):p<0},v.precision=function(N){return arguments.length?(M=lx(S,C=N*N),D()):Sr(C)},v.fitExtent=function(N,z){return $h(v,N,z)},v.fitSize=function(N,z){return N2(v,N,z)},v.fitWidth=function(N,z){return B2(v,N,z)},v.fitHeight=function(N,z){return D2(v,N,z)};function w(){var N=cx(r,0,0,f,p,d).apply(null,e(a,s)),z=cx(r,n-N[0],i-N[1],f,p,d);return h=g2(o,l,u),S=d2(e,z),R=d2(h,S),M=lx(S,C),D()}function D(){return A=L=null,v}return function(){return e=t.apply(this,arguments),v.invert=e.invert&&B,w()}}function F2(t){var e=0,r=Ae/3,n=O2(t),i=n(e,r);return i.parallels=function(a){return arguments.length?n(e=a[0]*re,r=a[1]*re):[e*Ue,r*Ue]},i}function rV(t){var e=Kt(t);function r(n,i){return[n*e,Ht(i)/e]}return r.invert=function(n,i){return[n/e,tn(i*e)]},r}function ux(t,e){var r=Ht(t),n=(r+Ht(e))/2;if(Ne(n)=.12&&y<.234&&_>=-.425&&_<-.214?i:y>=.166&&y<.234&&_>=-.214&&_<-.115?s:r).invert(f)},h.stream=function(f){return t&&e===f?t:t=nV([r.stream(e=f),i.stream(f),s.stream(f)])},h.precision=function(f){return arguments.length?(r.precision(f),i.precision(f),s.precision(f),d()):r.precision()},h.scale=function(f){return arguments.length?(r.scale(f),i.scale(f*.35),s.scale(f),h.translate(r.translate())):r.scale()},h.translate=function(f){if(!arguments.length)return r.translate();var p=r.scale(),m=+f[0],_=+f[1];return n=r.translate(f).clipExtent([[m-.455*p,_-.238*p],[m+.455*p,_+.238*p]]).stream(u),a=i.translate([m-.307*p,_+.201*p]).clipExtent([[m-.425*p+te,_+.12*p+te],[m-.214*p-te,_+.234*p-te]]).stream(u),o=s.translate([m-.205*p,_+.212*p]).clipExtent([[m-.214*p+te,_+.166*p+te],[m-.115*p-te,_+.234*p-te]]).stream(u),d()},h.fitExtent=function(f,p){return $h(h,f,p)},h.fitSize=function(f,p){return N2(h,f,p)},h.fitWidth=function(f,p){return B2(h,f,p)},h.fitHeight=function(f,p){return D2(h,f,p)};function d(){return t=e=null,h}return h.scale(1070)}function fx(t){return function(e,r){var n=Kt(e),i=Kt(r),a=t(n*i);return a===1/0?[2,0]:[a*i*Ht(e),a*Ht(r)]}}function nc(t){return function(e,r){var n=Sr(e*e+r*r),i=t(n),a=Ht(i),s=Kt(i);return[Jr(e*a,n*s),tn(n&&r*a/n)]}}var P2=fx(function(t){return Sr(2/(1+t))});P2.invert=nc(function(t){return 2*tn(t/2)});function aV(){return Li(P2).scale(124.75).clipAngle(180-.001)}var q2=fx(function(t){return(t=r6(t))&&t/Ht(t)});q2.invert=nc(function(t){return t});function sV(){return Li(q2).scale(79.4188).clipAngle(180-.001)}function ic(t,e){return[t,_h(r2((rr+e)/2))]}ic.invert=function(t,e){return[t,2*So(e6(e))-rr]};function oV(){return dx(ic).scale(961/Qr)}function dx(t){var e=Li(t),r=e.center,n=e.scale,i=e.translate,a=e.clipExtent,s=null,o,l,u;e.scale=function(d){return arguments.length?(n(d),h()):n()},e.translate=function(d){return arguments.length?(i(d),h()):i()},e.center=function(d){return arguments.length?(r(d),h()):r()},e.clipExtent=function(d){return arguments.length?(d==null?s=o=l=u=null:(s=+d[0][0],o=+d[0][1],l=+d[1][0],u=+d[1][1]),h()):s==null?null:[[s,o],[l,u]]};function h(){var d=Ae*n(),f=e(E6(e.rotate()).invert([0,0]));return a(s==null?[[f[0]-d,f[1]-d],[f[0]+d,f[1]+d]]:t===ic?[[Math.max(f[0]-d,s),o],[Math.min(f[0]+d,l),u]]:[[s,Math.max(f[1]-d,o)],[l,Math.min(f[1]+d,u)]])}return h()}function Kh(t){return r2((rr+t)/2)}function px(t,e){var r=Kt(t),n=t===e?Ht(t):_h(r/Kt(e))/_h(Kh(e)/Kh(t)),i=r*e2(Kh(t),n)/n;if(!n)return ic;function a(s,o){i>0?o<-rr+te&&(o=-rr+te):o>rr-te&&(o=rr-te);var l=i/e2(Kh(o),n);return[l*Ht(n*s),i-l*Kt(n*s)]}return a.invert=function(s,o){var l=i-o,u=Dn(n)*Sr(s*s+l*l),h=Jr(s,Ne(l))*Dn(l);return l*n<0&&(h-=Ae*Dn(s)*Dn(l)),[h/n,2*So(e2(i/u,1/n))-rr]},a}function lV(){return F2(px).scale(109.5).parallels([30,30])}function ac(t,e){return[t,e]}ac.invert=ac;function cV(){return Li(ac).scale(152.63)}function gx(t,e){var r=Kt(t),n=t===e?Ht(t):(r-Kt(e))/(e-t),i=r/n+t;if(Ne(n)te&&--n>0);return[t/(.8707+(a=r*r)*(-.131979+a*(-.013791+a*a*a*(.003971-.001529*a)))),r]};function gV(){return Li(Y2).scale(175.295)}function U2(t,e){return[Kt(e)*Ht(t),Ht(e)]}U2.invert=nc(tn);function yV(){return Li(U2).scale(249.5).clipAngle(90+te)}function W2(t,e){var r=Kt(e),n=1+Kt(t)*r;return[r*Ht(t)/n,Ht(e)/n]}W2.invert=nc(function(t){return 2*So(t)});function mV(){return Li(W2).scale(250).clipAngle(142)}function H2(t,e){return[_h(r2((rr+e)/2)),-t]}H2.invert=function(t,e){return[-e,2*So(e6(t))-rr]};function bV(){var t=dx(H2),e=t.center,r=t.rotate;return t.center=function(n){return arguments.length?e([-n[1],n[0]]):(n=e(),[n[1],-n[0]])},t.rotate=function(n){return arguments.length?r([n[0],n[1],n.length>2?n[2]+90:90]):(n=r(),[n[0],n[1],n[2]-90])},r([0,0,90]).scale(159.155)}function _V(t,e){return t.parent===e.parent?1:2}function vV(t){return t.reduce(xV,0)/t.length}function xV(t,e){return t+e.x}function kV(t){return 1+t.reduce(wV,0)}function wV(t,e){return Math.max(t,e.y)}function TV(t){for(var e;e=t.children;)t=e[0];return t}function EV(t){for(var e;e=t.children;)t=e[e.length-1];return t}function CV(){var t=_V,e=1,r=1,n=!1;function i(a){var s,o=0;a.eachAfter(function(f){var p=f.children;p?(f.x=vV(p),f.y=kV(p)):(f.x=s?o+=t(f,s):0,f.y=0,s=f)});var l=TV(a),u=EV(a),h=l.x-t(l,u)/2,d=u.x+t(u,l)/2;return a.eachAfter(n?function(f){f.x=(f.x-a.x)*e,f.y=(a.y-f.y)*r}:function(f){f.x=(f.x-h)/(d-h)*e,f.y=(1-(a.y?f.y/a.y:1))*r})}return i.separation=function(a){return arguments.length?(t=a,i):t},i.size=function(a){return arguments.length?(n=!1,e=+a[0],r=+a[1],i):n?null:[e,r]},i.nodeSize=function(a){return arguments.length?(n=!0,e=+a[0],r=+a[1],i):n?[e,r]:null},i}function SV(t){var e=0,r=t.children,n=r&&r.length;if(!n)e=1;else for(;--n>=0;)e+=r[n].value;t.value=e}function AV(){return this.eachAfter(SV)}function MV(t,e){let r=-1;for(const n of this)t.call(e,n,++r,this);return this}function LV(t,e){for(var r=this,n=[r],i,a,s=-1;r=n.pop();)if(t.call(e,r,++s,this),i=r.children)for(a=i.length-1;a>=0;--a)n.push(i[a]);return this}function RV(t,e){for(var r=this,n=[r],i=[],a,s,o,l=-1;r=n.pop();)if(i.push(r),a=r.children)for(s=0,o=a.length;s=0;)r+=n[i].value;e.value=r})}function BV(t){return this.eachBefore(function(e){e.children&&e.children.sort(t)})}function DV(t){for(var e=this,r=OV(e,t),n=[e];e!==r;)e=e.parent,n.push(e);for(var i=n.length;t!==r;)n.splice(i,0,t),t=t.parent;return n}function OV(t,e){if(t===e)return t;var r=t.ancestors(),n=e.ancestors(),i=null;for(t=r.pop(),e=n.pop();t===e;)i=t,t=r.pop(),e=n.pop();return i}function FV(){for(var t=this,e=[t];t=t.parent;)e.push(t);return e}function PV(){return Array.from(this)}function qV(){var t=[];return this.eachBefore(function(e){e.children||t.push(e)}),t}function VV(){var t=this,e=[];return t.each(function(r){r!==t&&e.push({source:r.parent,target:r})}),e}function*zV(){var t=this,e,r=[t],n,i,a;do for(e=r.reverse(),r=[];t=e.pop();)if(yield t,n=t.children)for(i=0,a=n.length;i=0;--o)i.push(a=s[o]=new Ms(s[o])),a.parent=n,a.depth=n.depth+1;return r.eachBefore(yx)}function YV(){return G2(this).eachBefore(HV)}function UV(t){return t.children}function WV(t){return Array.isArray(t)?t[1]:null}function HV(t){t.data.value!==void 0&&(t.value=t.data.value),t.data=t.data.data}function yx(t){var e=0;do t.height=e;while((t=t.parent)&&t.height<++e)}function Ms(t){this.data=t,this.depth=this.height=0,this.parent=null}Ms.prototype=G2.prototype={constructor:Ms,count:AV,each:MV,eachAfter:RV,eachBefore:LV,find:IV,sum:NV,sort:BV,path:DV,ancestors:FV,descendants:PV,leaves:qV,links:VV,copy:YV,[Symbol.iterator]:zV};function Qh(t){return t==null?null:mx(t)}function mx(t){if(typeof t!="function")throw new Error;return t}function Ls(){return 0}function Bo(t){return function(){return t}}const GV=1664525,jV=1013904223,bx=4294967296;function j2(){let t=1;return()=>(t=(GV*t+jV)%bx)/bx}function $V(t){return typeof t=="object"&&"length"in t?t:Array.from(t)}function XV(t,e){let r=t.length,n,i;for(;r;)i=e()*r--|0,n=t[r],t[r]=t[i],t[i]=n;return t}function KV(t){return _x(t,j2())}function _x(t,e){for(var r=0,n=(t=XV(Array.from(t),e)).length,i=[],a,s;r0&&r*r>n*n+i*i}function $2(t,e){for(var r=0;r1e-6?(v+Math.sqrt(v*v-4*L*B))/(2*L):B/v);return{x:n+M+S*w,y:i+R+A*w,r:w}}function kx(t,e,r){var n=t.x-e.x,i,a,s=t.y-e.y,o,l,u=n*n+s*s;u?(a=e.r+r.r,a*=a,l=t.r+r.r,l*=l,a>l?(i=(u+l-a)/(2*u),o=Math.sqrt(Math.max(0,l/u-i*i)),r.x=t.x-i*n-o*s,r.y=t.y-i*s+o*n):(i=(u+a-l)/(2*u),o=Math.sqrt(Math.max(0,a/u-i*i)),r.x=e.x+i*n-o*s,r.y=e.y+i*s+o*n)):(r.x=e.x+r.r,r.y=e.y)}function wx(t,e){var r=t.r+e.r-1e-6,n=e.x-t.x,i=e.y-t.y;return r>0&&r*r>n*n+i*i}function Tx(t){var e=t._,r=t.next._,n=e.r+r.r,i=(e.x*r.r+r.x*e.r)/n,a=(e.y*r.r+r.y*e.r)/n;return i*i+a*a}function tf(t){this._=t,this.next=null,this.previous=null}function Ex(t,e){if(!(a=(t=$V(t)).length))return 0;var r,n,i,a,s,o,l,u,h,d,f;if(r=t[0],r.x=0,r.y=0,!(a>1))return r.r;if(n=t[1],r.x=-n.r,n.x=r.r,n.y=0,!(a>2))return r.r+n.r;kx(n,r,i=t[2]),r=new tf(r),n=new tf(n),i=new tf(i),r.next=i.previous=n,n.next=r.previous=i,i.next=n.previous=r;t:for(l=3;llz(r(T,C,i))),x=b.map(Lx),k=new Set(b).add("");for(const T of x)k.has(T)||(k.add(T),b.push(T),x.push(Lx(T)),a.push(K2));s=(T,C)=>b[C],o=(T,C)=>x[C]}for(h=0,l=a.length;h=0&&(p=a[b],p.data===K2);--b)p.data=null}if(d.parent=iz,d.eachBefore(function(b){b.depth=b.parent.depth+1,--l}).eachBefore(yx),d.parent=null,l>0)throw new Error("cycle");return d}return n.id=function(i){return arguments.length?(t=Qh(i),n):t},n.parentId=function(i){return arguments.length?(e=Qh(i),n):e},n.path=function(i){return arguments.length?(r=Qh(i),n):r},n}function lz(t){t=`${t}`;let e=t.length;return Z2(t,e-1)&&!Z2(t,e-2)&&(t=t.slice(0,-1)),t[0]==="/"?t:`/${t}`}function Lx(t){let e=t.length;if(e<2)return"";for(;--e>1&&!Z2(t,e););return t.slice(0,e)}function Z2(t,e){if(t[e]==="/"){let r=0;for(;e>0&&t[--e]==="\\";)++r;if((r&1)===0)return!0}return!1}function cz(t,e){return t.parent===e.parent?1:2}function Q2(t){var e=t.children;return e?e[0]:t.t}function J2(t){var e=t.children;return e?e[e.length-1]:t.t}function uz(t,e,r){var n=r/(e.i-t.i);e.c-=n,e.s+=r,t.c+=n,e.z+=r,e.m+=r}function hz(t){for(var e=0,r=0,n=t.children,i=n.length,a;--i>=0;)a=n[i],a.z+=e,a.m+=e,e+=a.s+(r+=a.c)}function fz(t,e,r){return t.a.parent===e.parent?t.a:r}function ef(t,e){this._=t,this.parent=null,this.children=null,this.A=null,this.a=this,this.z=0,this.m=0,this.c=0,this.s=0,this.t=null,this.i=e}ef.prototype=Object.create(Ms.prototype);function dz(t){for(var e=new ef(t,0),r,n=[e],i,a,s,o;r=n.pop();)if(a=r._.children)for(r.children=new Array(o=a.length),s=o-1;s>=0;--s)n.push(i=r.children[s]=new ef(a[s],s)),i.parent=r;return(e.parent=new ef(null,0)).children=[e],e}function pz(){var t=cz,e=1,r=1,n=null;function i(u){var h=dz(u);if(h.eachAfter(a),h.parent.m=-h.z,h.eachBefore(s),n)u.eachBefore(l);else{var d=u,f=u,p=u;u.eachBefore(function(x){x.xf.x&&(f=x),x.depth>p.depth&&(p=x)});var m=d===f?1:t(d,f)/2,_=m-d.x,y=e/(f.x+m+_),b=r/(p.depth||1);u.eachBefore(function(x){x.x=(x.x+_)*y,x.y=x.depth*b})}return u}function a(u){var h=u.children,d=u.parent.children,f=u.i?d[u.i-1]:null;if(h){hz(u);var p=(h[0].z+h[h.length-1].z)/2;f?(u.z=f.z+t(u._,f._),u.m=u.z-p):u.z=p}else f&&(u.z=f.z+t(u._,f._));u.parent.A=o(u,f,u.parent.A||d[0])}function s(u){u._.x=u.z+u.parent.m,u.m+=u.parent.m}function o(u,h,d){if(h){for(var f=u,p=u,m=h,_=f.parent.children[0],y=f.m,b=p.m,x=m.m,k=_.m,T;m=J2(m),f=Q2(f),m&&f;)_=Q2(_),p=J2(p),p.a=u,T=m.z+x-f.z-y+t(m._,f._),T>0&&(uz(fz(m,u,d),u,T),y+=T,b+=T),x+=m.m,y+=f.m,k+=_.m,b+=p.m;m&&!J2(p)&&(p.t=m,p.m+=x-b),f&&!Q2(_)&&(_.t=f,_.m+=y-k,d=u)}return d}function l(u){u.x*=e,u.y=u.depth*r}return i.separation=function(u){return arguments.length?(t=u,i):t},i.size=function(u){return arguments.length?(n=!1,e=+u[0],r=+u[1],i):n?null:[e,r]},i.nodeSize=function(u){return arguments.length?(n=!0,e=+u[0],r=+u[1],i):n?[e,r]:null},i}function rf(t,e,r,n,i){for(var a=t.children,s,o=-1,l=a.length,u=t.value&&(i-r)/t.value;++ox&&(x=u),M=y*y*C,k=Math.max(x/M,M/b),k>T){y-=u;break}T=k}s.push(l={value:y,dice:p1?n:1)},r}(Rx);function gz(){var t=Nx,e=!1,r=1,n=1,i=[0],a=Ls,s=Ls,o=Ls,l=Ls,u=Ls;function h(f){return f.x0=f.y0=0,f.x1=r,f.y1=n,f.eachBefore(d),i=[0],e&&f.eachBefore(Ax),f}function d(f){var p=i[f.depth],m=f.x0+p,_=f.y0+p,y=f.x1-p,b=f.y1-p;y=f-1){var x=a[d];x.x0=m,x.y0=_,x.x1=y,x.y1=b;return}for(var k=u[d],T=p/2+k,C=d+1,M=f-1;C>>1;u[S]b-_){var L=p?(m*A+y*R)/p:y;h(d,C,R,m,_,L,b),h(C,f,A,L,_,y,b)}else{var v=p?(_*A+b*R)/p:b;h(d,C,R,m,_,y,v),h(C,f,A,m,v,y,b)}}}function mz(t,e,r,n,i){(t.depth&1?rf:hc)(t,e,r,n,i)}const bz=function t(e){function r(n,i,a,s,o){if((l=n._squarify)&&l.ratio===e)for(var l,u,h,d,f=-1,p,m=l.length,_=n.value;++f1?n:1)},r}(Rx);function _z(t){for(var e=-1,r=t.length,n,i=t[r-1],a=0;++e1&&xz(t[r[n-2]],t[r[n-1]],t[i])<=0;)--n;r[n++]=i}return r.slice(0,n)}function wz(t){if((r=t.length)<3)return null;var e,r,n=new Array(r),i=new Array(r);for(e=0;e=0;--e)u.push(t[n[a[e]][2]]);for(e=+o;ea!=o>a&&i<(s-l)*(a-u)/(o-u)+l&&(h=!h),s=l,o=u;return h}function Ez(t){for(var e=-1,r=t.length,n=t[r-1],i,a,s=n[0],o=n[1],l=0;++e1);return n+i*o*Math.sqrt(-2*Math.log(s)/s)}}return r.source=t,r}(Ir),Az=function t(e){var r=tp.source(e);function n(){var i=r.apply(this,arguments);return function(){return Math.exp(i())}}return n.source=t,n}(Ir),Dx=function t(e){function r(n){return(n=+n)<=0?()=>0:function(){for(var i=0,a=n;a>1;--a)i+=e();return i+a*e()}}return r.source=t,r}(Ir),Mz=function t(e){var r=Dx.source(e);function n(i){if((i=+i)==0)return e;var a=r(i);return function(){return a()/i}}return n.source=t,n}(Ir),Lz=function t(e){function r(n){return function(){return-Math.log1p(-e())/n}}return r.source=t,r}(Ir),Rz=function t(e){function r(n){if((n=+n)<0)throw new RangeError("invalid alpha");return n=1/-n,function(){return Math.pow(1-e(),n)}}return r.source=t,r}(Ir),Iz=function t(e){function r(n){if((n=+n)<0||n>1)throw new RangeError("invalid p");return function(){return Math.floor(e()+n)}}return r.source=t,r}(Ir),Ox=function t(e){function r(n){if((n=+n)<0||n>1)throw new RangeError("invalid p");return n===0?()=>1/0:n===1?()=>1:(n=Math.log1p(-n),function(){return 1+Math.floor(Math.log1p(-e())/n)})}return r.source=t,r}(Ir),ep=function t(e){var r=tp.source(e)();function n(i,a){if((i=+i)<0)throw new RangeError("invalid k");if(i===0)return()=>0;if(a=a==null?1:+a,i===1)return()=>-Math.log1p(-e())*a;var s=(i<1?i+1:i)-1/3,o=1/(3*Math.sqrt(s)),l=i<1?()=>Math.pow(e(),1/i):()=>1;return function(){do{do var u=r(),h=1+o*u;while(h<=0);h*=h*h;var d=1-e()}while(d>=1-.0331*u*u*u*u&&Math.log(d)>=.5*u*u+s*(1-h+Math.log(h)));return s*h*l()*a}}return n.source=t,n}(Ir),Fx=function t(e){var r=ep.source(e);function n(i,a){var s=r(i),o=r(a);return function(){var l=s();return l===0?0:l/(l+o())}}return n.source=t,n}(Ir),Px=function t(e){var r=Ox.source(e),n=Fx.source(e);function i(a,s){return a=+a,(s=+s)>=1?()=>a:s<=0?()=>0:function(){for(var o=0,l=a,u=s;l*u>16&&l*(1-u)>16;){var h=Math.floor((l+1)*u),d=n(h,l-h+1)();d<=u?(o+=h,l-=h,u=(u-d)/(1-d)):(l=h-1,u/=d)}for(var f=u<.5,p=f?u:1-u,m=r(p),_=m(),y=0;_<=l;++y)_+=m();return o+(f?y:l-y)}}return i.source=t,i}(Ir),Nz=function t(e){function r(n,i,a){var s;return(n=+n)==0?s=o=>-Math.log(o):(n=1/n,s=o=>Math.pow(o,n)),i=i==null?0:+i,a=a==null?1:+a,function(){return i+a*s(-Math.log1p(-e()))}}return r.source=t,r}(Ir),Bz=function t(e){function r(n,i){return n=n==null?0:+n,i=i==null?1:+i,function(){return n+i*Math.tan(Math.PI*e())}}return r.source=t,r}(Ir),Dz=function t(e){function r(n,i){return n=n==null?0:+n,i=i==null?1:+i,function(){var a=e();return n+i*Math.log(a/(1-a))}}return r.source=t,r}(Ir),Oz=function t(e){var r=ep.source(e),n=Px.source(e);function i(a){return function(){for(var s=0,o=a;o>16;){var l=Math.floor(.875*o),u=r(l)();if(u>o)return s+n(l-1,o/u)();s+=l,o-=u}for(var h=-Math.log1p(-e()),d=0;h<=o;++d)h-=Math.log1p(-e());return s+d}}return i.source=t,i}(Ir),Fz=1664525,Pz=1013904223,qx=1/4294967296;function qz(t=Math.random()){let e=(0<=t&&t<1?t/qx:Math.abs(t))|0;return()=>(e=Fz*e+Pz|0,qx*(e>>>0))}function On(t,e){switch(arguments.length){case 0:break;case 1:this.range(t);break;default:this.range(e).domain(t);break}return this}function ta(t,e){switch(arguments.length){case 0:break;case 1:{typeof t=="function"?this.interpolator(t):this.range(t);break}default:{this.domain(t),typeof e=="function"?this.interpolator(e):this.range(e);break}}return this}const rp=Symbol("implicit");function nf(){var t=new kl,e=[],r=[],n=rp;function i(a){let s=t.get(a);if(s===void 0){if(n!==rp)return n;t.set(a,s=e.push(a)-1)}return r[s%r.length]}return i.domain=function(a){if(!arguments.length)return e.slice();e=[],t=new kl;for(const s of a)t.has(s)||t.set(s,e.push(s)-1);return i},i.range=function(a){return arguments.length?(r=Array.from(a),i):r.slice()},i.unknown=function(a){return arguments.length?(n=a,i):n},i.copy=function(){return nf(e,r).unknown(n)},On.apply(i,arguments),i}function np(){var t=nf().unknown(void 0),e=t.domain,r=t.range,n=0,i=1,a,s,o=!1,l=0,u=0,h=.5;delete t.unknown;function d(){var f=e().length,p=ie&&(r=t,t=e,e=r),function(n){return Math.max(t,Math.min(e,n))}}function Uz(t,e,r){var n=t[0],i=t[1],a=e[0],s=e[1];return i2?Wz:Uz,l=u=null,d}function d(f){return f==null||isNaN(f=+f)?a:(l||(l=o(t.map(n),e,r)))(n(s(f)))}return d.invert=function(f){return s(i((u||(u=o(e,t.map(n),Bn)))(f)))},d.domain=function(f){return arguments.length?(t=Array.from(f,af),h()):t.slice()},d.range=function(f){return arguments.length?(e=Array.from(f),h()):e.slice()},d.rangeRound=function(f){return e=Array.from(f),r=ju,h()},d.clamp=function(f){return arguments.length?(s=f?!0:an,h()):s!==an},d.interpolate=function(f){return arguments.length?(r=f,h()):r},d.unknown=function(f){return arguments.length?(a=f,d):a},function(f,p){return n=f,i=p,h()}}function ap(){return sf()(an,an)}function Yx(t,e,r,n){var i=wl(t,e,r),a;switch(n=Co(n==null?",f":n),n.type){case"s":{var s=Math.max(Math.abs(t),Math.abs(e));return n.precision==null&&!isNaN(a=Jv(i,s))&&(n.precision=a),Jd(n,s)}case"":case"e":case"g":case"p":case"r":{n.precision==null&&!isNaN(a=t6(i,Math.max(Math.abs(t),Math.abs(e))))&&(n.precision=a-(n.type==="e"));break}case"f":case"%":{n.precision==null&&!isNaN(a=Qv(i))&&(n.precision=a-(n.type==="%")*2);break}}return yh(n)}function Oa(t){var e=t.domain;return t.ticks=function(r){var n=e();return hs(n[0],n[n.length-1],r==null?10:r)},t.tickFormat=function(r,n){var i=e();return Yx(i[0],i[i.length-1],r==null?10:r,n)},t.nice=function(r){r==null&&(r=10);var n=e(),i=0,a=n.length-1,s=n[i],o=n[a],l,u,h=10;for(o0;){if(u=oo(s,o,r),u===l)return n[i]=s,n[a]=o,e(n);if(u>0)s=Math.floor(s/u)*u,o=Math.ceil(o/u)*u;else if(u<0)s=Math.ceil(s*u)/u,o=Math.floor(o*u)/u;else break;l=u}return t},t}function sp(){var t=ap();return t.copy=function(){return fc(t,sp())},On.apply(t,arguments),Oa(t)}function Ux(t){var e;function r(n){return n==null||isNaN(n=+n)?e:n}return r.invert=r,r.domain=r.range=function(n){return arguments.length?(t=Array.from(n,af),r):t.slice()},r.unknown=function(n){return arguments.length?(e=n,r):e},r.copy=function(){return Ux(t).unknown(e)},t=arguments.length?Array.from(t,af):[0,1],Oa(r)}function Wx(t,e){t=t.slice();var r=0,n=t.length-1,i=t[r],a=t[n],s;return aMath.pow(t,e)}function Xz(t){return t===Math.E?Math.log:t===10&&Math.log10||t===2&&Math.log2||(t=Math.log(t),e=>Math.log(e)/t)}function jx(t){return(e,r)=>-t(-e,r)}function op(t){const e=t(Hx,Gx),r=e.domain;let n=10,i,a;function s(){return i=Xz(n),a=$z(n),r()[0]<0?(i=jx(i),a=jx(a),t(Hz,Gz)):t(Hx,Gx),e}return e.base=function(o){return arguments.length?(n=+o,s()):n},e.domain=function(o){return arguments.length?(r(o),s()):r()},e.ticks=o=>{const l=r();let u=l[0],h=l[l.length-1];const d=h0){for(;f<=p;++f)for(m=1;mh)break;b.push(_)}}else for(;f<=p;++f)for(m=n-1;m>=1;--m)if(_=f>0?m/a(-f):m*a(f),!(_h)break;b.push(_)}b.length*2{if(o==null&&(o=10),l==null&&(l=n===10?"s":","),typeof l!="function"&&(!(n%1)&&(l=Co(l)).precision==null&&(l.trim=!0),l=yh(l)),o===1/0)return l;const u=Math.max(1,n*o/e.ticks().length);return h=>{let d=h/a(Math.round(i(h)));return d*nr(Wx(r(),{floor:o=>a(Math.floor(i(o))),ceil:o=>a(Math.ceil(i(o)))})),e}function $x(){const t=op(sf()).domain([1,10]);return t.copy=()=>fc(t,$x()).base(t.base()),On.apply(t,arguments),t}function Xx(t){return function(e){return Math.sign(e)*Math.log1p(Math.abs(e/t))}}function Kx(t){return function(e){return Math.sign(e)*Math.expm1(Math.abs(e))*t}}function lp(t){var e=1,r=t(Xx(e),Kx(e));return r.constant=function(n){return arguments.length?t(Xx(e=+n),Kx(e)):e},Oa(r)}function Zx(){var t=lp(sf());return t.copy=function(){return fc(t,Zx()).constant(t.constant())},On.apply(t,arguments)}function Qx(t){return function(e){return e<0?-Math.pow(-e,t):Math.pow(e,t)}}function Kz(t){return t<0?-Math.sqrt(-t):Math.sqrt(t)}function Zz(t){return t<0?-t*t:t*t}function cp(t){var e=t(an,an),r=1;function n(){return r===1?t(an,an):r===.5?t(Kz,Zz):t(Qx(r),Qx(1/r))}return e.exponent=function(i){return arguments.length?(r=+i,n()):r},Oa(e)}function up(){var t=cp(sf());return t.copy=function(){return fc(t,up()).exponent(t.exponent())},On.apply(t,arguments),t}function Qz(){return up.apply(null,arguments).exponent(.5)}function Jx(t){return Math.sign(t)*t*t}function Jz(t){return Math.sign(t)*Math.sqrt(Math.abs(t))}function t8(){var t=ap(),e=[0,1],r=!1,n;function i(a){var s=Jz(t(a));return isNaN(s)?n:r?Math.round(s):s}return i.invert=function(a){return t.invert(Jx(a))},i.domain=function(a){return arguments.length?(t.domain(a),i):t.domain()},i.range=function(a){return arguments.length?(t.range((e=Array.from(a,af)).map(Jx)),i):e.slice()},i.rangeRound=function(a){return i.range(a).round(!0)},i.round=function(a){return arguments.length?(r=!!a,i):r},i.clamp=function(a){return arguments.length?(t.clamp(a),i):t.clamp()},i.unknown=function(a){return arguments.length?(n=a,i):n},i.copy=function(){return t8(t.domain(),e).round(r).clamp(t.clamp()).unknown(n)},On.apply(i,arguments),Oa(i)}function e8(){var t=[],e=[],r=[],n;function i(){var s=0,o=Math.max(1,e.length);for(r=new Array(o-1);++s0?r[o-1]:t[0],o=r?[n[r-1],e]:[n[u-1],n[u]]},s.unknown=function(l){return arguments.length&&(a=l),s},s.thresholds=function(){return n.slice()},s.copy=function(){return r8().domain([t,e]).range(i).unknown(a)},On.apply(Oa(s),arguments)}function n8(){var t=[.5],e=[0,1],r,n=1;function i(a){return a!=null&&a<=a?e[cs(t,a,0,n)]:r}return i.domain=function(a){return arguments.length?(t=Array.from(a),n=Math.min(t.length,e.length-1),i):t.slice()},i.range=function(a){return arguments.length?(e=Array.from(a),n=Math.min(t.length,e.length-1),i):e.slice()},i.invertExtent=function(a){var s=e.indexOf(a);return[t[s-1],t[s]]},i.unknown=function(a){return arguments.length?(r=a,i):r},i.copy=function(){return n8().domain(t).range(e).unknown(r)},On.apply(i,arguments)}var hp=new Date,fp=new Date;function xr(t,e,r,n){function i(a){return t(a=arguments.length===0?new Date:new Date(+a)),a}return i.floor=function(a){return t(a=new Date(+a)),a},i.ceil=function(a){return t(a=new Date(a-1)),e(a,1),t(a),a},i.round=function(a){var s=i(a),o=i.ceil(a);return a-s0))return l;do l.push(u=new Date(+a)),e(a,o),t(a);while(u=s)for(;t(s),!a(s);)s.setTime(s-1)},function(s,o){if(s>=s)if(o<0)for(;++o<=0;)for(;e(s,-1),!a(s););else for(;--o>=0;)for(;e(s,1),!a(s););})},r&&(i.count=function(a,s){return hp.setTime(+a),fp.setTime(+s),t(hp),t(fp),Math.floor(r(hp,fp))},i.every=function(a){return a=Math.floor(a),!isFinite(a)||!(a>0)?null:a>1?i.filter(n?function(s){return n(s)%a===0}:function(s){return i.count(0,s)%a===0}):i}),i}var of=xr(function(){},function(t,e){t.setTime(+t+e)},function(t,e){return e-t});of.every=function(t){return t=Math.floor(t),!isFinite(t)||!(t>0)?null:t>1?xr(function(e){e.setTime(Math.floor(e/t)*t)},function(e,r){e.setTime(+e+r*t)},function(e,r){return(r-e)/t}):of};const dp=of;var i8=of.range;const ea=1e3,Fn=ea*60,ra=Fn*60,Rs=ra*24,pp=Rs*7,a8=Rs*30,gp=Rs*365;var s8=xr(function(t){t.setTime(t-t.getMilliseconds())},function(t,e){t.setTime(+t+e*ea)},function(t,e){return(e-t)/ea},function(t){return t.getUTCSeconds()});const Fa=s8;var o8=s8.range,l8=xr(function(t){t.setTime(t-t.getMilliseconds()-t.getSeconds()*ea)},function(t,e){t.setTime(+t+e*Fn)},function(t,e){return(e-t)/Fn},function(t){return t.getMinutes()});const yp=l8;var tY=l8.range,c8=xr(function(t){t.setTime(t-t.getMilliseconds()-t.getSeconds()*ea-t.getMinutes()*Fn)},function(t,e){t.setTime(+t+e*ra)},function(t,e){return(e-t)/ra},function(t){return t.getHours()});const mp=c8;var eY=c8.range,u8=xr(t=>t.setHours(0,0,0,0),(t,e)=>t.setDate(t.getDate()+e),(t,e)=>(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*Fn)/Rs,t=>t.getDate()-1);const dc=u8;var rY=u8.range;function Is(t){return xr(function(e){e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)},function(e,r){e.setDate(e.getDate()+r*7)},function(e,r){return(r-e-(r.getTimezoneOffset()-e.getTimezoneOffset())*Fn)/pp})}var Do=Is(0),pc=Is(1),h8=Is(2),f8=Is(3),Ns=Is(4),d8=Is(5),p8=Is(6),g8=Do.range,nY=pc.range,iY=h8.range,aY=f8.range,sY=Ns.range,oY=d8.range,lY=p8.range,y8=xr(function(t){t.setDate(1),t.setHours(0,0,0,0)},function(t,e){t.setMonth(t.getMonth()+e)},function(t,e){return e.getMonth()-t.getMonth()+(e.getFullYear()-t.getFullYear())*12},function(t){return t.getMonth()});const bp=y8;var cY=y8.range,_p=xr(function(t){t.setMonth(0,1),t.setHours(0,0,0,0)},function(t,e){t.setFullYear(t.getFullYear()+e)},function(t,e){return e.getFullYear()-t.getFullYear()},function(t){return t.getFullYear()});_p.every=function(t){return!isFinite(t=Math.floor(t))||!(t>0)?null:xr(function(e){e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)},function(e,r){e.setFullYear(e.getFullYear()+r*t)})};const Pa=_p;var uY=_p.range,m8=xr(function(t){t.setUTCSeconds(0,0)},function(t,e){t.setTime(+t+e*Fn)},function(t,e){return(e-t)/Fn},function(t){return t.getUTCMinutes()});const vp=m8;var hY=m8.range,b8=xr(function(t){t.setUTCMinutes(0,0,0)},function(t,e){t.setTime(+t+e*ra)},function(t,e){return(e-t)/ra},function(t){return t.getUTCHours()});const xp=b8;var fY=b8.range,_8=xr(function(t){t.setUTCHours(0,0,0,0)},function(t,e){t.setUTCDate(t.getUTCDate()+e)},function(t,e){return(e-t)/Rs},function(t){return t.getUTCDate()-1});const gc=_8;var dY=_8.range;function Bs(t){return xr(function(e){e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)},function(e,r){e.setUTCDate(e.getUTCDate()+r*7)},function(e,r){return(r-e)/pp})}var Oo=Bs(0),yc=Bs(1),v8=Bs(2),x8=Bs(3),Ds=Bs(4),k8=Bs(5),w8=Bs(6),T8=Oo.range,pY=yc.range,gY=v8.range,yY=x8.range,mY=Ds.range,bY=k8.range,_Y=w8.range,E8=xr(function(t){t.setUTCDate(1),t.setUTCHours(0,0,0,0)},function(t,e){t.setUTCMonth(t.getUTCMonth()+e)},function(t,e){return e.getUTCMonth()-t.getUTCMonth()+(e.getUTCFullYear()-t.getUTCFullYear())*12},function(t){return t.getUTCMonth()});const kp=E8;var vY=E8.range,wp=xr(function(t){t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},function(t,e){t.setUTCFullYear(t.getUTCFullYear()+e)},function(t,e){return e.getUTCFullYear()-t.getUTCFullYear()},function(t){return t.getUTCFullYear()});wp.every=function(t){return!isFinite(t=Math.floor(t))||!(t>0)?null:xr(function(e){e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)},function(e,r){e.setUTCFullYear(e.getUTCFullYear()+r*t)})};const qa=wp;var xY=wp.range;function C8(t,e,r,n,i,a){const s=[[Fa,1,ea],[Fa,5,5*ea],[Fa,15,15*ea],[Fa,30,30*ea],[a,1,Fn],[a,5,5*Fn],[a,15,15*Fn],[a,30,30*Fn],[i,1,ra],[i,3,3*ra],[i,6,6*ra],[i,12,12*ra],[n,1,Rs],[n,2,2*Rs],[r,1,pp],[e,1,a8],[e,3,3*a8],[t,1,gp]];function o(u,h,d){const f=hy).right(s,f);if(p===s.length)return t.every(wl(u/gp,h/gp,d));if(p===0)return dp.every(Math.max(wl(u,h,d),1));const[m,_]=s[f/s[p-1][2]53)return null;"w"in U||(U.w=1),"Z"in U?(j=Ep(mc(U.y,0,1)),P=j.getUTCDay(),j=P>4||P===0?yc.ceil(j):yc(j),j=gc.offset(j,(U.V-1)*7),U.y=j.getUTCFullYear(),U.m=j.getUTCMonth(),U.d=j.getUTCDate()+(U.w+6)%7):(j=Tp(mc(U.y,0,1)),P=j.getDay(),j=P>4||P===0?pc.ceil(j):pc(j),j=dc.offset(j,(U.V-1)*7),U.y=j.getFullYear(),U.m=j.getMonth(),U.d=j.getDate()+(U.w+6)%7)}else("W"in U||"U"in U)&&("w"in U||(U.w="u"in U?U.u%7:"W"in U?1:0),P="Z"in U?Ep(mc(U.y,0,1)).getUTCDay():Tp(mc(U.y,0,1)).getDay(),U.m=0,U.d="W"in U?(U.w+6)%7+U.W*7-(P+5)%7:U.w+U.U*7-(P+6)%7);return"Z"in U?(U.H+=U.Z/100|0,U.M+=U.Z%100,Ep(U)):Tp(U)}}function R(V,Q,q,U){for(var F=0,j=Q.length,P=q.length,et,at;F=P)return-1;if(et=Q.charCodeAt(F++),et===37){if(et=Q.charAt(F++),at=C[et in I8?Q.charAt(F++):et],!at||(U=at(V,q,U))<0)return-1}else if(et!=q.charCodeAt(U++))return-1}return U}function A(V,Q,q){var U=u.exec(Q.slice(q));return U?(V.p=h.get(U[0].toLowerCase()),q+U[0].length):-1}function L(V,Q,q){var U=p.exec(Q.slice(q));return U?(V.w=m.get(U[0].toLowerCase()),q+U[0].length):-1}function v(V,Q,q){var U=d.exec(Q.slice(q));return U?(V.w=f.get(U[0].toLowerCase()),q+U[0].length):-1}function B(V,Q,q){var U=b.exec(Q.slice(q));return U?(V.m=x.get(U[0].toLowerCase()),q+U[0].length):-1}function w(V,Q,q){var U=_.exec(Q.slice(q));return U?(V.m=y.get(U[0].toLowerCase()),q+U[0].length):-1}function D(V,Q,q){return R(V,e,Q,q)}function N(V,Q,q){return R(V,r,Q,q)}function z(V,Q,q){return R(V,n,Q,q)}function X(V){return s[V.getDay()]}function ct(V){return a[V.getDay()]}function J(V){return l[V.getMonth()]}function Y(V){return o[V.getMonth()]}function $(V){return i[+(V.getHours()>=12)]}function lt(V){return 1+~~(V.getMonth()/3)}function ut(V){return s[V.getUTCDay()]}function W(V){return a[V.getUTCDay()]}function tt(V){return l[V.getUTCMonth()]}function K(V){return o[V.getUTCMonth()]}function it(V){return i[+(V.getUTCHours()>=12)]}function Z(V){return 1+~~(V.getUTCMonth()/3)}return{format:function(V){var Q=M(V+="",k);return Q.toString=function(){return V},Q},parse:function(V){var Q=S(V+="",!1);return Q.toString=function(){return V},Q},utcFormat:function(V){var Q=M(V+="",T);return Q.toString=function(){return V},Q},utcParse:function(V){var Q=S(V+="",!0);return Q.toString=function(){return V},Q}}}var I8={"-":"",_:" ",0:"0"},Ar=/^\s*\d+/,kY=/^%/,wY=/[\\^$*+?|[\]().{}]/g;function Oe(t,e,r){var n=t<0?"-":"",i=(n?-t:t)+"",a=i.length;return n+(a[e.toLowerCase(),r]))}function EY(t,e,r){var n=Ar.exec(e.slice(r,r+1));return n?(t.w=+n[0],r+n[0].length):-1}function CY(t,e,r){var n=Ar.exec(e.slice(r,r+1));return n?(t.u=+n[0],r+n[0].length):-1}function SY(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.U=+n[0],r+n[0].length):-1}function AY(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.V=+n[0],r+n[0].length):-1}function MY(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.W=+n[0],r+n[0].length):-1}function N8(t,e,r){var n=Ar.exec(e.slice(r,r+4));return n?(t.y=+n[0],r+n[0].length):-1}function B8(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.y=+n[0]+(+n[0]>68?1900:2e3),r+n[0].length):-1}function LY(t,e,r){var n=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(r,r+6));return n?(t.Z=n[1]?0:-(n[2]+(n[3]||"00")),r+n[0].length):-1}function RY(t,e,r){var n=Ar.exec(e.slice(r,r+1));return n?(t.q=n[0]*3-3,r+n[0].length):-1}function IY(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.m=n[0]-1,r+n[0].length):-1}function D8(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.d=+n[0],r+n[0].length):-1}function NY(t,e,r){var n=Ar.exec(e.slice(r,r+3));return n?(t.m=0,t.d=+n[0],r+n[0].length):-1}function O8(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.H=+n[0],r+n[0].length):-1}function BY(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.M=+n[0],r+n[0].length):-1}function DY(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.S=+n[0],r+n[0].length):-1}function OY(t,e,r){var n=Ar.exec(e.slice(r,r+3));return n?(t.L=+n[0],r+n[0].length):-1}function FY(t,e,r){var n=Ar.exec(e.slice(r,r+6));return n?(t.L=Math.floor(n[0]/1e3),r+n[0].length):-1}function PY(t,e,r){var n=kY.exec(e.slice(r,r+1));return n?r+n[0].length:-1}function qY(t,e,r){var n=Ar.exec(e.slice(r));return n?(t.Q=+n[0],r+n[0].length):-1}function VY(t,e,r){var n=Ar.exec(e.slice(r));return n?(t.s=+n[0],r+n[0].length):-1}function F8(t,e){return Oe(t.getDate(),e,2)}function zY(t,e){return Oe(t.getHours(),e,2)}function YY(t,e){return Oe(t.getHours()%12||12,e,2)}function UY(t,e){return Oe(1+dc.count(Pa(t),t),e,3)}function P8(t,e){return Oe(t.getMilliseconds(),e,3)}function WY(t,e){return P8(t,e)+"000"}function HY(t,e){return Oe(t.getMonth()+1,e,2)}function GY(t,e){return Oe(t.getMinutes(),e,2)}function jY(t,e){return Oe(t.getSeconds(),e,2)}function $Y(t){var e=t.getDay();return e===0?7:e}function XY(t,e){return Oe(Do.count(Pa(t)-1,t),e,2)}function q8(t){var e=t.getDay();return e>=4||e===0?Ns(t):Ns.ceil(t)}function KY(t,e){return t=q8(t),Oe(Ns.count(Pa(t),t)+(Pa(t).getDay()===4),e,2)}function ZY(t){return t.getDay()}function QY(t,e){return Oe(pc.count(Pa(t)-1,t),e,2)}function JY(t,e){return Oe(t.getFullYear()%100,e,2)}function tU(t,e){return t=q8(t),Oe(t.getFullYear()%100,e,2)}function eU(t,e){return Oe(t.getFullYear()%1e4,e,4)}function rU(t,e){var r=t.getDay();return t=r>=4||r===0?Ns(t):Ns.ceil(t),Oe(t.getFullYear()%1e4,e,4)}function nU(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+Oe(e/60|0,"0",2)+Oe(e%60,"0",2)}function V8(t,e){return Oe(t.getUTCDate(),e,2)}function iU(t,e){return Oe(t.getUTCHours(),e,2)}function aU(t,e){return Oe(t.getUTCHours()%12||12,e,2)}function sU(t,e){return Oe(1+gc.count(qa(t),t),e,3)}function z8(t,e){return Oe(t.getUTCMilliseconds(),e,3)}function oU(t,e){return z8(t,e)+"000"}function lU(t,e){return Oe(t.getUTCMonth()+1,e,2)}function cU(t,e){return Oe(t.getUTCMinutes(),e,2)}function uU(t,e){return Oe(t.getUTCSeconds(),e,2)}function hU(t){var e=t.getUTCDay();return e===0?7:e}function fU(t,e){return Oe(Oo.count(qa(t)-1,t),e,2)}function Y8(t){var e=t.getUTCDay();return e>=4||e===0?Ds(t):Ds.ceil(t)}function dU(t,e){return t=Y8(t),Oe(Ds.count(qa(t),t)+(qa(t).getUTCDay()===4),e,2)}function pU(t){return t.getUTCDay()}function gU(t,e){return Oe(yc.count(qa(t)-1,t),e,2)}function yU(t,e){return Oe(t.getUTCFullYear()%100,e,2)}function mU(t,e){return t=Y8(t),Oe(t.getUTCFullYear()%100,e,2)}function bU(t,e){return Oe(t.getUTCFullYear()%1e4,e,4)}function _U(t,e){var r=t.getUTCDay();return t=r>=4||r===0?Ds(t):Ds.ceil(t),Oe(t.getUTCFullYear()%1e4,e,4)}function vU(){return"+0000"}function U8(){return"%"}function W8(t){return+t}function H8(t){return Math.floor(+t/1e3)}var Fo,vc,G8,lf,Cp;j8({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});function j8(t){return Fo=R8(t),vc=Fo.format,G8=Fo.parse,lf=Fo.utcFormat,Cp=Fo.utcParse,Fo}var $8="%Y-%m-%dT%H:%M:%S.%LZ";function xU(t){return t.toISOString()}var kU=Date.prototype.toISOString?xU:lf($8);const wU=kU;function TU(t){var e=new Date(t);return isNaN(e)?null:e}var EU=+new Date("2000-01-01T00:00:00.000Z")?TU:Cp($8);const CU=EU;function SU(t){return new Date(t)}function AU(t){return t instanceof Date?+t:+new Date(+t)}function Sp(t,e,r,n,i,a,s,o,l,u){var h=ap(),d=h.invert,f=h.domain,p=u(".%L"),m=u(":%S"),_=u("%I:%M"),y=u("%I %p"),b=u("%a %d"),x=u("%b %d"),k=u("%B"),T=u("%Y");function C(M){return(l(M)e(i/(t.length-1)))},r.quantiles=function(n){return Array.from({length:n+1},(i,a)=>Cl(t,a/n))},r.copy=function(){return J8(e).domain(t)},ta.apply(r,arguments)}function uf(){var t=0,e=.5,r=1,n=1,i,a,s,o,l,u=an,h,d=!1,f;function p(_){return isNaN(_=+_)?f:(_=.5+((_=+h(_))-a)*(n*_B5(t[t.length-1]);var n7=new Array(3).concat("d8b365f5f5f55ab4ac","a6611adfc27d80cdc1018571","a6611adfc27df5f5f580cdc1018571","8c510ad8b365f6e8c3c7eae55ab4ac01665e","8c510ad8b365f6e8c3f5f5f5c7eae55ab4ac01665e","8c510abf812ddfc27df6e8c3c7eae580cdc135978f01665e","8c510abf812ddfc27df6e8c3f5f5f5c7eae580cdc135978f01665e","5430058c510abf812ddfc27df6e8c3c7eae580cdc135978f01665e003c30","5430058c510abf812ddfc27df6e8c3f5f5f5c7eae580cdc135978f01665e003c30").map(Ee);const YU=We(n7);var i7=new Array(3).concat("af8dc3f7f7f77fbf7b","7b3294c2a5cfa6dba0008837","7b3294c2a5cff7f7f7a6dba0008837","762a83af8dc3e7d4e8d9f0d37fbf7b1b7837","762a83af8dc3e7d4e8f7f7f7d9f0d37fbf7b1b7837","762a839970abc2a5cfe7d4e8d9f0d3a6dba05aae611b7837","762a839970abc2a5cfe7d4e8f7f7f7d9f0d3a6dba05aae611b7837","40004b762a839970abc2a5cfe7d4e8d9f0d3a6dba05aae611b783700441b","40004b762a839970abc2a5cfe7d4e8f7f7f7d9f0d3a6dba05aae611b783700441b").map(Ee);const UU=We(i7);var a7=new Array(3).concat("e9a3c9f7f7f7a1d76a","d01c8bf1b6dab8e1864dac26","d01c8bf1b6daf7f7f7b8e1864dac26","c51b7de9a3c9fde0efe6f5d0a1d76a4d9221","c51b7de9a3c9fde0eff7f7f7e6f5d0a1d76a4d9221","c51b7dde77aef1b6dafde0efe6f5d0b8e1867fbc414d9221","c51b7dde77aef1b6dafde0eff7f7f7e6f5d0b8e1867fbc414d9221","8e0152c51b7dde77aef1b6dafde0efe6f5d0b8e1867fbc414d9221276419","8e0152c51b7dde77aef1b6dafde0eff7f7f7e6f5d0b8e1867fbc414d9221276419").map(Ee);const WU=We(a7);var s7=new Array(3).concat("998ec3f7f7f7f1a340","5e3c99b2abd2fdb863e66101","5e3c99b2abd2f7f7f7fdb863e66101","542788998ec3d8daebfee0b6f1a340b35806","542788998ec3d8daebf7f7f7fee0b6f1a340b35806","5427888073acb2abd2d8daebfee0b6fdb863e08214b35806","5427888073acb2abd2d8daebf7f7f7fee0b6fdb863e08214b35806","2d004b5427888073acb2abd2d8daebfee0b6fdb863e08214b358067f3b08","2d004b5427888073acb2abd2d8daebf7f7f7fee0b6fdb863e08214b358067f3b08").map(Ee);const HU=We(s7);var o7=new Array(3).concat("ef8a62f7f7f767a9cf","ca0020f4a58292c5de0571b0","ca0020f4a582f7f7f792c5de0571b0","b2182bef8a62fddbc7d1e5f067a9cf2166ac","b2182bef8a62fddbc7f7f7f7d1e5f067a9cf2166ac","b2182bd6604df4a582fddbc7d1e5f092c5de4393c32166ac","b2182bd6604df4a582fddbc7f7f7f7d1e5f092c5de4393c32166ac","67001fb2182bd6604df4a582fddbc7d1e5f092c5de4393c32166ac053061","67001fb2182bd6604df4a582fddbc7f7f7f7d1e5f092c5de4393c32166ac053061").map(Ee);const GU=We(o7);var l7=new Array(3).concat("ef8a62ffffff999999","ca0020f4a582bababa404040","ca0020f4a582ffffffbababa404040","b2182bef8a62fddbc7e0e0e09999994d4d4d","b2182bef8a62fddbc7ffffffe0e0e09999994d4d4d","b2182bd6604df4a582fddbc7e0e0e0bababa8787874d4d4d","b2182bd6604df4a582fddbc7ffffffe0e0e0bababa8787874d4d4d","67001fb2182bd6604df4a582fddbc7e0e0e0bababa8787874d4d4d1a1a1a","67001fb2182bd6604df4a582fddbc7ffffffe0e0e0bababa8787874d4d4d1a1a1a").map(Ee);const jU=We(l7);var c7=new Array(3).concat("fc8d59ffffbf91bfdb","d7191cfdae61abd9e92c7bb6","d7191cfdae61ffffbfabd9e92c7bb6","d73027fc8d59fee090e0f3f891bfdb4575b4","d73027fc8d59fee090ffffbfe0f3f891bfdb4575b4","d73027f46d43fdae61fee090e0f3f8abd9e974add14575b4","d73027f46d43fdae61fee090ffffbfe0f3f8abd9e974add14575b4","a50026d73027f46d43fdae61fee090e0f3f8abd9e974add14575b4313695","a50026d73027f46d43fdae61fee090ffffbfe0f3f8abd9e974add14575b4313695").map(Ee);const $U=We(c7);var u7=new Array(3).concat("fc8d59ffffbf91cf60","d7191cfdae61a6d96a1a9641","d7191cfdae61ffffbfa6d96a1a9641","d73027fc8d59fee08bd9ef8b91cf601a9850","d73027fc8d59fee08bffffbfd9ef8b91cf601a9850","d73027f46d43fdae61fee08bd9ef8ba6d96a66bd631a9850","d73027f46d43fdae61fee08bffffbfd9ef8ba6d96a66bd631a9850","a50026d73027f46d43fdae61fee08bd9ef8ba6d96a66bd631a9850006837","a50026d73027f46d43fdae61fee08bffffbfd9ef8ba6d96a66bd631a9850006837").map(Ee);const XU=We(u7);var h7=new Array(3).concat("fc8d59ffffbf99d594","d7191cfdae61abdda42b83ba","d7191cfdae61ffffbfabdda42b83ba","d53e4ffc8d59fee08be6f59899d5943288bd","d53e4ffc8d59fee08bffffbfe6f59899d5943288bd","d53e4ff46d43fdae61fee08be6f598abdda466c2a53288bd","d53e4ff46d43fdae61fee08bffffbfe6f598abdda466c2a53288bd","9e0142d53e4ff46d43fdae61fee08be6f598abdda466c2a53288bd5e4fa2","9e0142d53e4ff46d43fdae61fee08bffffbfe6f598abdda466c2a53288bd5e4fa2").map(Ee);const KU=We(h7);var f7=new Array(3).concat("e5f5f999d8c92ca25f","edf8fbb2e2e266c2a4238b45","edf8fbb2e2e266c2a42ca25f006d2c","edf8fbccece699d8c966c2a42ca25f006d2c","edf8fbccece699d8c966c2a441ae76238b45005824","f7fcfde5f5f9ccece699d8c966c2a441ae76238b45005824","f7fcfde5f5f9ccece699d8c966c2a441ae76238b45006d2c00441b").map(Ee);const ZU=We(f7);var d7=new Array(3).concat("e0ecf49ebcda8856a7","edf8fbb3cde38c96c688419d","edf8fbb3cde38c96c68856a7810f7c","edf8fbbfd3e69ebcda8c96c68856a7810f7c","edf8fbbfd3e69ebcda8c96c68c6bb188419d6e016b","f7fcfde0ecf4bfd3e69ebcda8c96c68c6bb188419d6e016b","f7fcfde0ecf4bfd3e69ebcda8c96c68c6bb188419d810f7c4d004b").map(Ee);const QU=We(d7);var p7=new Array(3).concat("e0f3dba8ddb543a2ca","f0f9e8bae4bc7bccc42b8cbe","f0f9e8bae4bc7bccc443a2ca0868ac","f0f9e8ccebc5a8ddb57bccc443a2ca0868ac","f0f9e8ccebc5a8ddb57bccc44eb3d32b8cbe08589e","f7fcf0e0f3dbccebc5a8ddb57bccc44eb3d32b8cbe08589e","f7fcf0e0f3dbccebc5a8ddb57bccc44eb3d32b8cbe0868ac084081").map(Ee);const JU=We(p7);var g7=new Array(3).concat("fee8c8fdbb84e34a33","fef0d9fdcc8afc8d59d7301f","fef0d9fdcc8afc8d59e34a33b30000","fef0d9fdd49efdbb84fc8d59e34a33b30000","fef0d9fdd49efdbb84fc8d59ef6548d7301f990000","fff7ecfee8c8fdd49efdbb84fc8d59ef6548d7301f990000","fff7ecfee8c8fdd49efdbb84fc8d59ef6548d7301fb300007f0000").map(Ee);const tW=We(g7);var y7=new Array(3).concat("ece2f0a6bddb1c9099","f6eff7bdc9e167a9cf02818a","f6eff7bdc9e167a9cf1c9099016c59","f6eff7d0d1e6a6bddb67a9cf1c9099016c59","f6eff7d0d1e6a6bddb67a9cf3690c002818a016450","fff7fbece2f0d0d1e6a6bddb67a9cf3690c002818a016450","fff7fbece2f0d0d1e6a6bddb67a9cf3690c002818a016c59014636").map(Ee);const eW=We(y7);var m7=new Array(3).concat("ece7f2a6bddb2b8cbe","f1eef6bdc9e174a9cf0570b0","f1eef6bdc9e174a9cf2b8cbe045a8d","f1eef6d0d1e6a6bddb74a9cf2b8cbe045a8d","f1eef6d0d1e6a6bddb74a9cf3690c00570b0034e7b","fff7fbece7f2d0d1e6a6bddb74a9cf3690c00570b0034e7b","fff7fbece7f2d0d1e6a6bddb74a9cf3690c00570b0045a8d023858").map(Ee);const rW=We(m7);var b7=new Array(3).concat("e7e1efc994c7dd1c77","f1eef6d7b5d8df65b0ce1256","f1eef6d7b5d8df65b0dd1c77980043","f1eef6d4b9dac994c7df65b0dd1c77980043","f1eef6d4b9dac994c7df65b0e7298ace125691003f","f7f4f9e7e1efd4b9dac994c7df65b0e7298ace125691003f","f7f4f9e7e1efd4b9dac994c7df65b0e7298ace125698004367001f").map(Ee);const nW=We(b7);var _7=new Array(3).concat("fde0ddfa9fb5c51b8a","feebe2fbb4b9f768a1ae017e","feebe2fbb4b9f768a1c51b8a7a0177","feebe2fcc5c0fa9fb5f768a1c51b8a7a0177","feebe2fcc5c0fa9fb5f768a1dd3497ae017e7a0177","fff7f3fde0ddfcc5c0fa9fb5f768a1dd3497ae017e7a0177","fff7f3fde0ddfcc5c0fa9fb5f768a1dd3497ae017e7a017749006a").map(Ee);const iW=We(_7);var v7=new Array(3).concat("edf8b17fcdbb2c7fb8","ffffcca1dab441b6c4225ea8","ffffcca1dab441b6c42c7fb8253494","ffffccc7e9b47fcdbb41b6c42c7fb8253494","ffffccc7e9b47fcdbb41b6c41d91c0225ea80c2c84","ffffd9edf8b1c7e9b47fcdbb41b6c41d91c0225ea80c2c84","ffffd9edf8b1c7e9b47fcdbb41b6c41d91c0225ea8253494081d58").map(Ee);const aW=We(v7);var x7=new Array(3).concat("f7fcb9addd8e31a354","ffffccc2e69978c679238443","ffffccc2e69978c67931a354006837","ffffccd9f0a3addd8e78c67931a354006837","ffffccd9f0a3addd8e78c67941ab5d238443005a32","ffffe5f7fcb9d9f0a3addd8e78c67941ab5d238443005a32","ffffe5f7fcb9d9f0a3addd8e78c67941ab5d238443006837004529").map(Ee);const sW=We(x7);var k7=new Array(3).concat("fff7bcfec44fd95f0e","ffffd4fed98efe9929cc4c02","ffffd4fed98efe9929d95f0e993404","ffffd4fee391fec44ffe9929d95f0e993404","ffffd4fee391fec44ffe9929ec7014cc4c028c2d04","ffffe5fff7bcfee391fec44ffe9929ec7014cc4c028c2d04","ffffe5fff7bcfee391fec44ffe9929ec7014cc4c02993404662506").map(Ee);const oW=We(k7);var w7=new Array(3).concat("ffeda0feb24cf03b20","ffffb2fecc5cfd8d3ce31a1c","ffffb2fecc5cfd8d3cf03b20bd0026","ffffb2fed976feb24cfd8d3cf03b20bd0026","ffffb2fed976feb24cfd8d3cfc4e2ae31a1cb10026","ffffccffeda0fed976feb24cfd8d3cfc4e2ae31a1cb10026","ffffccffeda0fed976feb24cfd8d3cfc4e2ae31a1cbd0026800026").map(Ee);const lW=We(w7);var T7=new Array(3).concat("deebf79ecae13182bd","eff3ffbdd7e76baed62171b5","eff3ffbdd7e76baed63182bd08519c","eff3ffc6dbef9ecae16baed63182bd08519c","eff3ffc6dbef9ecae16baed64292c62171b5084594","f7fbffdeebf7c6dbef9ecae16baed64292c62171b5084594","f7fbffdeebf7c6dbef9ecae16baed64292c62171b508519c08306b").map(Ee);const cW=We(T7);var E7=new Array(3).concat("e5f5e0a1d99b31a354","edf8e9bae4b374c476238b45","edf8e9bae4b374c47631a354006d2c","edf8e9c7e9c0a1d99b74c47631a354006d2c","edf8e9c7e9c0a1d99b74c47641ab5d238b45005a32","f7fcf5e5f5e0c7e9c0a1d99b74c47641ab5d238b45005a32","f7fcf5e5f5e0c7e9c0a1d99b74c47641ab5d238b45006d2c00441b").map(Ee);const uW=We(E7);var C7=new Array(3).concat("f0f0f0bdbdbd636363","f7f7f7cccccc969696525252","f7f7f7cccccc969696636363252525","f7f7f7d9d9d9bdbdbd969696636363252525","f7f7f7d9d9d9bdbdbd969696737373525252252525","fffffff0f0f0d9d9d9bdbdbd969696737373525252252525","fffffff0f0f0d9d9d9bdbdbd969696737373525252252525000000").map(Ee);const hW=We(C7);var S7=new Array(3).concat("efedf5bcbddc756bb1","f2f0f7cbc9e29e9ac86a51a3","f2f0f7cbc9e29e9ac8756bb154278f","f2f0f7dadaebbcbddc9e9ac8756bb154278f","f2f0f7dadaebbcbddc9e9ac8807dba6a51a34a1486","fcfbfdefedf5dadaebbcbddc9e9ac8807dba6a51a34a1486","fcfbfdefedf5dadaebbcbddc9e9ac8807dba6a51a354278f3f007d").map(Ee);const fW=We(S7);var A7=new Array(3).concat("fee0d2fc9272de2d26","fee5d9fcae91fb6a4acb181d","fee5d9fcae91fb6a4ade2d26a50f15","fee5d9fcbba1fc9272fb6a4ade2d26a50f15","fee5d9fcbba1fc9272fb6a4aef3b2ccb181d99000d","fff5f0fee0d2fcbba1fc9272fb6a4aef3b2ccb181d99000d","fff5f0fee0d2fcbba1fc9272fb6a4aef3b2ccb181da50f1567000d").map(Ee);const dW=We(A7);var M7=new Array(3).concat("fee6cefdae6be6550d","feeddefdbe85fd8d3cd94701","feeddefdbe85fd8d3ce6550da63603","feeddefdd0a2fdae6bfd8d3ce6550da63603","feeddefdd0a2fdae6bfd8d3cf16913d948018c2d04","fff5ebfee6cefdd0a2fdae6bfd8d3cf16913d948018c2d04","fff5ebfee6cefdd0a2fdae6bfd8d3cf16913d94801a636037f2704").map(Ee);const pW=We(M7);function gW(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(-4.54-t*(35.34-t*(2381.73-t*(6402.7-t*(7024.72-t*2710.57)))))))+", "+Math.max(0,Math.min(255,Math.round(32.49+t*(170.73+t*(52.82-t*(131.46-t*(176.58-t*67.37)))))))+", "+Math.max(0,Math.min(255,Math.round(81.24+t*(442.36-t*(2482.43-t*(6167.24-t*(6614.94-t*2475.67)))))))+")"}const yW=Xu(Qn(300,.5,0),Qn(-240,.5,1));var mW=Xu(Qn(-100,.75,.35),Qn(80,1.5,.8)),bW=Xu(Qn(260,.75,.35),Qn(80,1.5,.8)),hf=Qn();function _W(t){(t<0||t>1)&&(t-=Math.floor(t));var e=Math.abs(t-.5);return hf.h=360*t-100,hf.s=1.5-1.5*e,hf.l=.8-.9*e,hf+""}var ff=po(),vW=Math.PI/3,xW=Math.PI*2/3;function kW(t){var e;return t=(.5-t)*Math.PI,ff.r=255*(e=Math.sin(t))*e,ff.g=255*(e=Math.sin(t+vW))*e,ff.b=255*(e=Math.sin(t+xW))*e,ff+""}function wW(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(34.61+t*(1172.33-t*(10793.56-t*(33300.12-t*(38394.49-t*14825.05)))))))+", "+Math.max(0,Math.min(255,Math.round(23.31+t*(557.33+t*(1225.33-t*(3574.96-t*(1073.77+t*707.56)))))))+", "+Math.max(0,Math.min(255,Math.round(27.2+t*(3211.1-t*(15327.97-t*(27814-t*(22569.18-t*6838.66)))))))+")"}function df(t){var e=t.length;return function(r){return t[Math.max(0,Math.min(e-1,Math.floor(r*e)))]}}const TW=df(Ee("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725"));var EW=df(Ee("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf")),CW=df(Ee("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4")),SW=df(Ee("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921"));function xe(t){return function(){return t}}const L7=Math.abs,qr=Math.atan2,na=Math.cos,AW=Math.max,Po=Math.min,gn=Math.sin,He=Math.sqrt,Vr=1e-12,za=Math.PI,pf=za/2,Ya=2*za;function MW(t){return t>1?0:t<-1?za:Math.acos(t)}function R7(t){return t>=1?pf:t<=-1?-pf:Math.asin(t)}function LW(t){return t.innerRadius}function RW(t){return t.outerRadius}function IW(t){return t.startAngle}function NW(t){return t.endAngle}function BW(t){return t&&t.padAngle}function DW(t,e,r,n,i,a,s,o){var l=r-t,u=n-e,h=s-i,d=o-a,f=d*l-h*u;if(!(f*fD*D+N*N&&(R=L,A=v),{cx:R,cy:A,x01:-h,y01:-d,x11:R*(i/C-1),y11:A*(i/C-1)}}function yf(){var t=LW,e=RW,r=xe(0),n=null,i=IW,a=NW,s=BW,o=null;function l(){var u,h,d=+t.apply(this,arguments),f=+e.apply(this,arguments),p=i.apply(this,arguments)-pf,m=a.apply(this,arguments)-pf,_=L7(m-p),y=m>p;if(o||(o=u=Ra()),fVr))o.moveTo(0,0);else if(_>Ya-Vr)o.moveTo(f*na(p),f*gn(p)),o.arc(0,0,f,p,m,!y),d>Vr&&(o.moveTo(d*na(m),d*gn(m)),o.arc(0,0,d,m,p,y));else{var b=p,x=m,k=p,T=m,C=_,M=_,S=s.apply(this,arguments)/2,R=S>Vr&&(n?+n.apply(this,arguments):He(d*d+f*f)),A=Po(L7(f-d)/2,+r.apply(this,arguments)),L=A,v=A,B,w;if(R>Vr){var D=R7(R/d*gn(S)),N=R7(R/f*gn(S));(C-=D*2)>Vr?(D*=y?1:-1,k+=D,T-=D):(C=0,k=T=(p+m)/2),(M-=N*2)>Vr?(N*=y?1:-1,b+=N,x-=N):(M=0,b=x=(p+m)/2)}var z=f*na(b),X=f*gn(b),ct=d*na(T),J=d*gn(T);if(A>Vr){var Y=f*na(x),$=f*gn(x),lt=d*na(k),ut=d*gn(k),W;if(_Vr?v>Vr?(B=gf(lt,ut,z,X,f,v,y),w=gf(Y,$,ct,J,f,v,y),o.moveTo(B.cx+B.x01,B.cy+B.y01),vVr)||!(C>Vr)?o.lineTo(ct,J):L>Vr?(B=gf(ct,J,Y,$,d,-L,y),w=gf(z,X,lt,ut,d,-L,y),o.lineTo(B.cx+B.x01,B.cy+B.y01),L=f;--p)o.point(x[p],k[p]);o.lineEnd(),o.areaEnd()}y&&(x[d]=+t(_,d,h),k[d]=+e(_,d,h),o.point(n?+n(_,d,h):x[d],r?+r(_,d,h):k[d]))}if(b)return o=null,b+""||null}function u(){return Ua().defined(i).curve(s).context(a)}return l.x=function(h){return arguments.length?(t=typeof h=="function"?h:xe(+h),n=null,l):t},l.x0=function(h){return arguments.length?(t=typeof h=="function"?h:xe(+h),l):t},l.x1=function(h){return arguments.length?(n=h==null?null:typeof h=="function"?h:xe(+h),l):n},l.y=function(h){return arguments.length?(e=typeof h=="function"?h:xe(+h),r=null,l):e},l.y0=function(h){return arguments.length?(e=typeof h=="function"?h:xe(+h),l):e},l.y1=function(h){return arguments.length?(r=h==null?null:typeof h=="function"?h:xe(+h),l):r},l.lineX0=l.lineY0=function(){return u().x(t).y(e)},l.lineY1=function(){return u().x(t).y(r)},l.lineX1=function(){return u().x(n).y(e)},l.defined=function(h){return arguments.length?(i=typeof h=="function"?h:xe(!!h),l):i},l.curve=function(h){return arguments.length?(s=h,a!=null&&(o=s(a)),l):s},l.context=function(h){return arguments.length?(h==null?a=o=null:o=s(a=h),l):a},l}function FW(t,e){return et?1:e>=t?0:NaN}function PW(t){return t}function B7(){var t=PW,e=FW,r=null,n=xe(0),i=xe(Ya),a=xe(0);function s(o){var l,u=(o=mf(o)).length,h,d,f=0,p=new Array(u),m=new Array(u),_=+n.apply(this,arguments),y=Math.min(Ya,Math.max(-Ya,i.apply(this,arguments)-_)),b,x=Math.min(Math.abs(y)/u,a.apply(this,arguments)),k=x*(y<0?-1:1),T;for(l=0;l0&&(f+=T);for(e!=null?p.sort(function(C,M){return e(m[C],m[M])}):r!=null&&p.sort(function(C,M){return r(o[C],o[M])}),l=0,d=f?(y-u*k)/f:0;l0?T*d:0)+k,m[h]={data:o[h],index:l,value:T,startAngle:_,endAngle:b,padAngle:x};return m}return s.value=function(o){return arguments.length?(t=typeof o=="function"?o:xe(+o),s):t},s.sortValues=function(o){return arguments.length?(e=o,r=null,s):e},s.sort=function(o){return arguments.length?(r=o,e=null,s):r},s.startAngle=function(o){return arguments.length?(n=typeof o=="function"?o:xe(+o),s):n},s.endAngle=function(o){return arguments.length?(i=typeof o=="function"?o:xe(+o),s):i},s.padAngle=function(o){return arguments.length?(a=typeof o=="function"?o:xe(+o),s):a},s}var D7=Ip(yn);function O7(t){this._curve=t}O7.prototype={areaStart:function(){this._curve.areaStart()},areaEnd:function(){this._curve.areaEnd()},lineStart:function(){this._curve.lineStart()},lineEnd:function(){this._curve.lineEnd()},point:function(t,e){this._curve.point(e*Math.sin(t),e*-Math.cos(t))}};function Ip(t){function e(r){return new O7(t(r))}return e._curve=t,e}function xc(t){var e=t.curve;return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t.curve=function(r){return arguments.length?e(Ip(r)):e()._curve},t}function F7(){return xc(Ua().curve(D7))}function P7(){var t=N7().curve(D7),e=t.curve,r=t.lineX0,n=t.lineX1,i=t.lineY0,a=t.lineY1;return t.angle=t.x,delete t.x,t.startAngle=t.x0,delete t.x0,t.endAngle=t.x1,delete t.x1,t.radius=t.y,delete t.y,t.innerRadius=t.y0,delete t.y0,t.outerRadius=t.y1,delete t.y1,t.lineStartAngle=function(){return xc(r())},delete t.lineX0,t.lineEndAngle=function(){return xc(n())},delete t.lineX1,t.lineInnerRadius=function(){return xc(i())},delete t.lineY0,t.lineOuterRadius=function(){return xc(a())},delete t.lineY1,t.curve=function(s){return arguments.length?e(Ip(s)):e()._curve},t}function kc(t,e){return[(e=+e)*Math.cos(t-=Math.PI/2),e*Math.sin(t)]}class q7{constructor(e,r){this._context=e,this._x=r}areaStart(){this._line=0}areaEnd(){this._line=NaN}lineStart(){this._point=0}lineEnd(){(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line}point(e,r){switch(e=+e,r=+r,this._point){case 0:{this._point=1,this._line?this._context.lineTo(e,r):this._context.moveTo(e,r);break}case 1:this._point=2;default:{this._x?this._context.bezierCurveTo(this._x0=(this._x0+e)/2,this._y0,this._x0,r,e,r):this._context.bezierCurveTo(this._x0,this._y0=(this._y0+r)/2,e,this._y0,e,r);break}}this._x0=e,this._y0=r}}class qW{constructor(e){this._context=e}lineStart(){this._point=0}lineEnd(){}point(e,r){if(e=+e,r=+r,this._point++===0)this._x0=e,this._y0=r;else{const n=kc(this._x0,this._y0),i=kc(this._x0,this._y0=(this._y0+r)/2),a=kc(e,this._y0),s=kc(e,r);this._context.moveTo(...n),this._context.bezierCurveTo(...i,...a,...s)}}}function V7(t){return new q7(t,!0)}function z7(t){return new q7(t,!1)}function VW(t){return new qW(t)}function zW(t){return t.source}function YW(t){return t.target}function bf(t){let e=zW,r=YW,n=Lp,i=Rp,a=null,s=null;function o(){let l;const u=OW.call(arguments),h=e.apply(this,u),d=r.apply(this,u);if(a==null&&(s=t(l=Ra())),s.lineStart(),u[0]=h,s.point(+n.apply(this,u),+i.apply(this,u)),u[0]=d,s.point(+n.apply(this,u),+i.apply(this,u)),s.lineEnd(),l)return s=null,l+""||null}return o.source=function(l){return arguments.length?(e=l,o):e},o.target=function(l){return arguments.length?(r=l,o):r},o.x=function(l){return arguments.length?(n=typeof l=="function"?l:xe(+l),o):n},o.y=function(l){return arguments.length?(i=typeof l=="function"?l:xe(+l),o):i},o.context=function(l){return arguments.length?(l==null?a=s=null:s=t(a=l),o):a},o}function UW(){return bf(V7)}function WW(){return bf(z7)}function HW(){const t=bf(VW);return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t}const GW=He(3),Y7={draw(t,e){const r=He(e+Po(e/28,.75))*.59436,n=r/2,i=n*GW;t.moveTo(0,r),t.lineTo(0,-r),t.moveTo(-i,-n),t.lineTo(i,n),t.moveTo(-i,n),t.lineTo(i,-n)}},_f={draw(t,e){const r=He(e/za);t.moveTo(r,0),t.arc(0,0,r,0,Ya)}},U7={draw(t,e){const r=He(e/5)/2;t.moveTo(-3*r,-r),t.lineTo(-r,-r),t.lineTo(-r,-3*r),t.lineTo(r,-3*r),t.lineTo(r,-r),t.lineTo(3*r,-r),t.lineTo(3*r,r),t.lineTo(r,r),t.lineTo(r,3*r),t.lineTo(-r,3*r),t.lineTo(-r,r),t.lineTo(-3*r,r),t.closePath()}},W7=He(1/3),jW=W7*2,H7={draw(t,e){const r=He(e/jW),n=r*W7;t.moveTo(0,-r),t.lineTo(n,0),t.lineTo(0,r),t.lineTo(-n,0),t.closePath()}},G7={draw(t,e){const r=He(e)*.62625;t.moveTo(0,-r),t.lineTo(r,0),t.lineTo(0,r),t.lineTo(-r,0),t.closePath()}},j7={draw(t,e){const r=He(e-Po(e/7,2))*.87559;t.moveTo(-r,0),t.lineTo(r,0),t.moveTo(0,r),t.lineTo(0,-r)}},$7={draw(t,e){const r=He(e),n=-r/2;t.rect(n,n,r,r)}},X7={draw(t,e){const r=He(e)*.4431;t.moveTo(r,r),t.lineTo(r,-r),t.lineTo(-r,-r),t.lineTo(-r,r),t.closePath()}},$W=.8908130915292852,K7=gn(za/10)/gn(7*za/10),XW=gn(Ya/10)*K7,KW=-na(Ya/10)*K7,Z7={draw(t,e){const r=He(e*$W),n=XW*r,i=KW*r;t.moveTo(0,-r),t.lineTo(n,i);for(let a=1;a<5;++a){const s=Ya*a/5,o=na(s),l=gn(s);t.lineTo(l*r,-o*r),t.lineTo(o*n-l*i,l*n+o*i)}t.closePath()}},Np=He(3),Q7={draw(t,e){const r=-He(e/(Np*3));t.moveTo(0,r*2),t.lineTo(-Np*r,-r),t.lineTo(Np*r,-r),t.closePath()}},ZW=He(3),J7={draw(t,e){const r=He(e)*.6824,n=r/2,i=r*ZW/2;t.moveTo(0,-r),t.lineTo(i,n),t.lineTo(-i,n),t.closePath()}},Pn=-.5,qn=He(3)/2,Bp=1/He(12),QW=(Bp/2+1)*3,tk={draw(t,e){const r=He(e/QW),n=r/2,i=r*Bp,a=n,s=r*Bp+r,o=-a,l=s;t.moveTo(n,i),t.lineTo(a,s),t.lineTo(o,l),t.lineTo(Pn*n-qn*i,qn*n+Pn*i),t.lineTo(Pn*a-qn*s,qn*a+Pn*s),t.lineTo(Pn*o-qn*l,qn*o+Pn*l),t.lineTo(Pn*n+qn*i,Pn*i-qn*n),t.lineTo(Pn*a+qn*s,Pn*s-qn*a),t.lineTo(Pn*o+qn*l,Pn*l-qn*o),t.closePath()}},ek={draw(t,e){const r=He(e-Po(e/6,1.7))*.6189;t.moveTo(-r,-r),t.lineTo(r,r),t.moveTo(-r,r),t.lineTo(r,-r)}},rk=[_f,U7,H7,$7,Z7,Q7,tk],JW=[_f,j7,ek,J7,Y7,X7,G7];function tH(t,e){let r=null;t=typeof t=="function"?t:xe(t||_f),e=typeof e=="function"?e:xe(e===void 0?64:+e);function n(){let i;if(r||(r=i=Ra()),t.apply(this,arguments).draw(r,+e.apply(this,arguments)),i)return r=null,i+""||null}return n.type=function(i){return arguments.length?(t=typeof i=="function"?i:xe(i),n):t},n.size=function(i){return arguments.length?(e=typeof i=="function"?i:xe(+i),n):e},n.context=function(i){return arguments.length?(r=i==null?null:i,n):r},n}function Wa(){}function vf(t,e,r){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+e)/6,(t._y0+4*t._y1+r)/6)}function xf(t){this._context=t}xf.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:vf(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:vf(this,t,e);break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};function Os(t){return new xf(t)}function nk(t){this._context=t}nk.prototype={areaStart:Wa,areaEnd:Wa,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:{this._context.moveTo(this._x2,this._y2),this._context.closePath();break}case 2:{this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break}case 3:{this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4);break}}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x2=t,this._y2=e;break;case 1:this._point=2,this._x3=t,this._y3=e;break;case 2:this._point=3,this._x4=t,this._y4=e,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+e)/6);break;default:vf(this,t,e);break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};function ik(t){return new nk(t)}function ak(t){this._context=t}ak.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var r=(this._x0+4*this._x1+t)/6,n=(this._y0+4*this._y1+e)/6;this._line?this._context.lineTo(r,n):this._context.moveTo(r,n);break;case 3:this._point=4;default:vf(this,t,e);break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};function sk(t){return new ak(t)}function ok(t,e){this._basis=new xf(t),this._beta=e}ok.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var t=this._x,e=this._y,r=t.length-1;if(r>0)for(var n=t[0],i=e[0],a=t[r]-n,s=e[r]-i,o=-1,l;++o<=r;)l=o/r,this._basis.point(this._beta*t[o]+(1-this._beta)*(n+l*a),this._beta*e[o]+(1-this._beta)*(i+l*s));this._x=this._y=null,this._basis.lineEnd()},point:function(t,e){this._x.push(+t),this._y.push(+e)}};const eH=function t(e){function r(n){return e===1?new xf(n):new ok(n,e)}return r.beta=function(n){return t(+n)},r}(.85);function kf(t,e,r){t._context.bezierCurveTo(t._x1+t._k*(t._x2-t._x0),t._y1+t._k*(t._y2-t._y0),t._x2+t._k*(t._x1-e),t._y2+t._k*(t._y1-r),t._x2,t._y2)}function Dp(t,e){this._context=t,this._k=(1-e)/6}Dp.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:kf(this,this._x1,this._y1);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2,this._x1=t,this._y1=e;break;case 2:this._point=3;default:kf(this,t,e);break}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const rH=function t(e){function r(n){return new Dp(n,e)}return r.tension=function(n){return t(+n)},r}(0);function Op(t,e){this._context=t,this._k=(1-e)/6}Op.prototype={areaStart:Wa,areaEnd:Wa,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:{this._context.moveTo(this._x3,this._y3),this._context.closePath();break}case 2:{this._context.lineTo(this._x3,this._y3),this._context.closePath();break}case 3:{this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5);break}}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:kf(this,t,e);break}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const nH=function t(e){function r(n){return new Op(n,e)}return r.tension=function(n){return t(+n)},r}(0);function Fp(t,e){this._context=t,this._k=(1-e)/6}Fp.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:kf(this,t,e);break}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const iH=function t(e){function r(n){return new Fp(n,e)}return r.tension=function(n){return t(+n)},r}(0);function Pp(t,e,r){var n=t._x1,i=t._y1,a=t._x2,s=t._y2;if(t._l01_a>Vr){var o=2*t._l01_2a+3*t._l01_a*t._l12_a+t._l12_2a,l=3*t._l01_a*(t._l01_a+t._l12_a);n=(n*o-t._x0*t._l12_2a+t._x2*t._l01_2a)/l,i=(i*o-t._y0*t._l12_2a+t._y2*t._l01_2a)/l}if(t._l23_a>Vr){var u=2*t._l23_2a+3*t._l23_a*t._l12_a+t._l12_2a,h=3*t._l23_a*(t._l23_a+t._l12_a);a=(a*u+t._x1*t._l23_2a-e*t._l12_2a)/h,s=(s*u+t._y1*t._l23_2a-r*t._l12_2a)/h}t._context.bezierCurveTo(n,i,a,s,t._x2,t._y2)}function lk(t,e){this._context=t,this._alpha=e}lk.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var r=this._x2-t,n=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(r*r+n*n,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3;default:Pp(this,t,e);break}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const aH=function t(e){function r(n){return e?new lk(n,e):new Dp(n,0)}return r.alpha=function(n){return t(+n)},r}(.5);function ck(t,e){this._context=t,this._alpha=e}ck.prototype={areaStart:Wa,areaEnd:Wa,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:{this._context.moveTo(this._x3,this._y3),this._context.closePath();break}case 2:{this._context.lineTo(this._x3,this._y3),this._context.closePath();break}case 3:{this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5);break}}},point:function(t,e){if(t=+t,e=+e,this._point){var r=this._x2-t,n=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(r*r+n*n,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:Pp(this,t,e);break}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const sH=function t(e){function r(n){return e?new ck(n,e):new Op(n,0)}return r.alpha=function(n){return t(+n)},r}(.5);function uk(t,e){this._context=t,this._alpha=e}uk.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var r=this._x2-t,n=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(r*r+n*n,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:Pp(this,t,e);break}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const oH=function t(e){function r(n){return e?new uk(n,e):new Fp(n,0)}return r.alpha=function(n){return t(+n)},r}(.5);function hk(t){this._context=t}hk.prototype={areaStart:Wa,areaEnd:Wa,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(t,e){t=+t,e=+e,this._point?this._context.lineTo(t,e):(this._point=1,this._context.moveTo(t,e))}};function fk(t){return new hk(t)}function dk(t){return t<0?-1:1}function pk(t,e,r){var n=t._x1-t._x0,i=e-t._x1,a=(t._y1-t._y0)/(n||i<0&&-0),s=(r-t._y1)/(i||n<0&&-0),o=(a*i+s*n)/(n+i);return(dk(a)+dk(s))*Math.min(Math.abs(a),Math.abs(s),.5*Math.abs(o))||0}function gk(t,e){var r=t._x1-t._x0;return r?(3*(t._y1-t._y0)/r-e)/2:e}function qp(t,e,r){var n=t._x0,i=t._y0,a=t._x1,s=t._y1,o=(a-n)/3;t._context.bezierCurveTo(n+o,i+o*e,a-o,s-o*r,a,s)}function wf(t){this._context=t}wf.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=this._t0=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x1,this._y1);break;case 3:qp(this,this._t0,gk(this,this._t0));break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){var r=NaN;if(t=+t,e=+e,!(t===this._x1&&e===this._y1)){switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,qp(this,gk(this,r=pk(this,t,e)),r);break;default:qp(this,this._t0,r=pk(this,t,e));break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e,this._t0=r}}};function yk(t){this._context=new mk(t)}(yk.prototype=Object.create(wf.prototype)).point=function(t,e){wf.prototype.point.call(this,e,t)};function mk(t){this._context=t}mk.prototype={moveTo:function(t,e){this._context.moveTo(e,t)},closePath:function(){this._context.closePath()},lineTo:function(t,e){this._context.lineTo(e,t)},bezierCurveTo:function(t,e,r,n,i,a){this._context.bezierCurveTo(e,t,n,r,a,i)}};function bk(t){return new wf(t)}function _k(t){return new yk(t)}function vk(t){this._context=t}vk.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x=[],this._y=[]},lineEnd:function(){var t=this._x,e=this._y,r=t.length;if(r)if(this._line?this._context.lineTo(t[0],e[0]):this._context.moveTo(t[0],e[0]),r===2)this._context.lineTo(t[1],e[1]);else for(var n=xk(t),i=xk(e),a=0,s=1;s=0;--e)i[e]=(s[e]-i[e+1])/a[e];for(a[r-1]=(t[r]+i[r-1])/2,e=0;e=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:{if(this._t<=0)this._context.lineTo(this._x,e),this._context.lineTo(t,e);else{var r=this._x*(1-this._t)+t*this._t;this._context.lineTo(r,this._y),this._context.lineTo(r,e)}break}}this._x=t,this._y=e}};function wk(t){return new Tf(t,.5)}function Tk(t){return new Tf(t,0)}function Ek(t){return new Tf(t,1)}function qo(t,e){if((s=t.length)>1)for(var r=1,n,i,a=t[e[0]],s,o=a.length;r=0;)r[e]=e;return r}function lH(t,e){return t[e]}function cH(t){const e=[];return e.key=t,e}function uH(){var t=xe([]),e=Vo,r=qo,n=lH;function i(a){var s=Array.from(t.apply(this,arguments),cH),o,l=s.length,u=-1,h;for(const d of a)for(o=0,++u;o0){for(var r,n,i=0,a=t[0].length,s;i0)for(var r,n=0,i,a,s,o,l,u=t[e[0]].length;n0?(i[0]=s,i[1]=s+=a):a<0?(i[1]=o,i[0]=o+=a):(i[0]=0,i[1]=a)}function dH(t,e){if((i=t.length)>0){for(var r=0,n=t[e[0]],i,a=n.length;r0)||!((a=(i=t[e[0]]).length)>0))){for(var r=0,n=1,i,a,s;na&&(a=i,r=e);return r}function Sk(t){var e=t.map(Ak);return Vo(t).sort(function(r,n){return e[r]-e[n]})}function Ak(t){for(var e=0,r=-1,n=t.length,i;++r()=>t;function _H(t,{sourceEvent:e,target:r,transform:n,dispatch:i}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:e,enumerable:!0,configurable:!0},target:{value:r,enumerable:!0,configurable:!0},transform:{value:n,enumerable:!0,configurable:!0},_:{value:i}})}function Ri(t,e,r){this.k=t,this.x=e,this.y=r}Ri.prototype={constructor:Ri,scale:function(t){return t===1?this:new Ri(this.k*t,this.x,this.y)},translate:function(t,e){return t===0&e===0?this:new Ri(this.k,this.x+this.k*t,this.y+this.k*e)},apply:function(t){return[t[0]*this.k+this.x,t[1]*this.k+this.y]},applyX:function(t){return t*this.k+this.x},applyY:function(t){return t*this.k+this.y},invert:function(t){return[(t[0]-this.x)/this.k,(t[1]-this.y)/this.k]},invertX:function(t){return(t-this.x)/this.k},invertY:function(t){return(t-this.y)/this.k},rescaleX:function(t){return t.copy().domain(t.range().map(this.invertX,this).map(t.invert,t))},rescaleY:function(t){return t.copy().domain(t.range().map(this.invertY,this).map(t.invert,t))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var Cf=new Ri(1,0,0);Mk.prototype=Ri.prototype;function Mk(t){for(;!t.__zoom;)if(!(t=t.parentNode))return Cf;return t.__zoom}function Vp(t){t.stopImmediatePropagation()}function wc(t){t.preventDefault(),t.stopImmediatePropagation()}function vH(t){return(!t.ctrlKey||t.type==="wheel")&&!t.button}function xH(){var t=this;return t instanceof SVGElement?(t=t.ownerSVGElement||t,t.hasAttribute("viewBox")?(t=t.viewBox.baseVal,[[t.x,t.y],[t.x+t.width,t.y+t.height]]):[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]):[[0,0],[t.clientWidth,t.clientHeight]]}function Lk(){return this.__zoom||Cf}function kH(t){return-t.deltaY*(t.deltaMode===1?.05:t.deltaMode?1:.002)*(t.ctrlKey?10:1)}function wH(){return navigator.maxTouchPoints||"ontouchstart"in this}function TH(t,e,r){var n=t.invertX(e[0][0])-r[0][0],i=t.invertX(e[1][0])-r[1][0],a=t.invertY(e[0][1])-r[0][1],s=t.invertY(e[1][1])-r[1][1];return t.translate(i>n?(n+i)/2:Math.min(0,n)||Math.max(0,i),s>a?(a+s)/2:Math.min(0,a)||Math.max(0,s))}function EH(){var t=vH,e=xH,r=TH,n=kH,i=wH,a=[0,1/0],s=[[-1/0,-1/0],[1/0,1/0]],o=250,l=H5,u=fs("start","zoom","end"),h,d,f,p=500,m=150,_=0,y=10;function b(D){D.property("__zoom",Lk).on("wheel.zoom",R,{passive:!1}).on("mousedown.zoom",A).on("dblclick.zoom",L).filter(i).on("touchstart.zoom",v).on("touchmove.zoom",B).on("touchend.zoom touchcancel.zoom",w).style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}b.transform=function(D,N,z,X){var ct=D.selection?D.selection():D;ct.property("__zoom",Lk),D!==ct?C(D,N,z,X):ct.interrupt().each(function(){M(this,arguments).event(X).start().zoom(null,typeof N=="function"?N.apply(this,arguments):N).end()})},b.scaleBy=function(D,N,z,X){b.scaleTo(D,function(){var ct=this.__zoom.k,J=typeof N=="function"?N.apply(this,arguments):N;return ct*J},z,X)},b.scaleTo=function(D,N,z,X){b.transform(D,function(){var ct=e.apply(this,arguments),J=this.__zoom,Y=z==null?T(ct):typeof z=="function"?z.apply(this,arguments):z,$=J.invert(Y),lt=typeof N=="function"?N.apply(this,arguments):N;return r(k(x(J,lt),Y,$),ct,s)},z,X)},b.translateBy=function(D,N,z,X){b.transform(D,function(){return r(this.__zoom.translate(typeof N=="function"?N.apply(this,arguments):N,typeof z=="function"?z.apply(this,arguments):z),e.apply(this,arguments),s)},null,X)},b.translateTo=function(D,N,z,X,ct){b.transform(D,function(){var J=e.apply(this,arguments),Y=this.__zoom,$=X==null?T(J):typeof X=="function"?X.apply(this,arguments):X;return r(Cf.translate($[0],$[1]).scale(Y.k).translate(typeof N=="function"?-N.apply(this,arguments):-N,typeof z=="function"?-z.apply(this,arguments):-z),J,s)},X,ct)};function x(D,N){return N=Math.max(a[0],Math.min(a[1],N)),N===D.k?D:new Ri(N,D.x,D.y)}function k(D,N,z){var X=N[0]-z[0]*D.k,ct=N[1]-z[1]*D.k;return X===D.x&&ct===D.y?D:new Ri(D.k,X,ct)}function T(D){return[(+D[0][0]+ +D[1][0])/2,(+D[0][1]+ +D[1][1])/2]}function C(D,N,z,X){D.on("start.zoom",function(){M(this,arguments).event(X).start()}).on("interrupt.zoom end.zoom",function(){M(this,arguments).event(X).end()}).tween("zoom",function(){var ct=this,J=arguments,Y=M(ct,J).event(X),$=e.apply(ct,J),lt=z==null?T($):typeof z=="function"?z.apply(ct,J):z,ut=Math.max($[1][0]-$[0][0],$[1][1]-$[0][1]),W=ct.__zoom,tt=typeof N=="function"?N.apply(ct,J):N,K=l(W.invert(lt).concat(ut/W.k),tt.invert(lt).concat(ut/tt.k));return function(it){if(it===1)it=tt;else{var Z=K(it),V=ut/Z[2];it=new Ri(V,lt[0]-Z[0]*V,lt[1]-Z[1]*V)}Y.zoom(null,it)}})}function M(D,N,z){return!z&&D.__zooming||new S(D,N)}function S(D,N){this.that=D,this.args=N,this.active=0,this.sourceEvent=null,this.extent=e.apply(D,N),this.taps=0}S.prototype={event:function(D){return D&&(this.sourceEvent=D),this},start:function(){return++this.active===1&&(this.that.__zooming=this,this.emit("start")),this},zoom:function(D,N){return this.mouse&&D!=="mouse"&&(this.mouse[1]=N.invert(this.mouse[0])),this.touch0&&D!=="touch"&&(this.touch0[1]=N.invert(this.touch0[0])),this.touch1&&D!=="touch"&&(this.touch1[1]=N.invert(this.touch1[0])),this.that.__zoom=N,this.emit("zoom"),this},end:function(){return--this.active===0&&(delete this.that.__zooming,this.emit("end")),this},emit:function(D){var N=St(this.that).datum();u.call(D,this.that,new _H(D,{sourceEvent:this.sourceEvent,target:b,type:D,transform:this.that.__zoom,dispatch:u}),N)}};function R(D,...N){if(!t.apply(this,arguments))return;var z=M(this,N).event(D),X=this.__zoom,ct=Math.max(a[0],Math.min(a[1],X.k*Math.pow(2,n.apply(this,arguments)))),J=Tn(D);if(z.wheel)(z.mouse[0][0]!==J[0]||z.mouse[0][1]!==J[1])&&(z.mouse[1]=X.invert(z.mouse[0]=J)),clearTimeout(z.wheel);else{if(X.k===ct)return;z.mouse=[J,X.invert(J)],vs(this),z.start()}wc(D),z.wheel=setTimeout(Y,m),z.zoom("mouse",r(k(x(X,ct),z.mouse[0],z.mouse[1]),z.extent,s));function Y(){z.wheel=null,z.end()}}function A(D,...N){if(f||!t.apply(this,arguments))return;var z=D.currentTarget,X=M(this,N,!0).event(D),ct=St(D.view).on("mousemove.zoom",lt,!0).on("mouseup.zoom",ut,!0),J=Tn(D,z),Y=D.clientX,$=D.clientY;Bu(D.view),Vp(D),X.mouse=[J,this.__zoom.invert(J)],vs(this),X.start();function lt(W){if(wc(W),!X.moved){var tt=W.clientX-Y,K=W.clientY-$;X.moved=tt*tt+K*K>_}X.event(W).zoom("mouse",r(k(X.that.__zoom,X.mouse[0]=Tn(W,z),X.mouse[1]),X.extent,s))}function ut(W){ct.on("mousemove.zoom mouseup.zoom",null),Du(W.view,X.moved),wc(W),X.event(W).end()}}function L(D,...N){if(!!t.apply(this,arguments)){var z=this.__zoom,X=Tn(D.changedTouches?D.changedTouches[0]:D,this),ct=z.invert(X),J=z.k*(D.shiftKey?.5:2),Y=r(k(x(z,J),X,ct),e.apply(this,N),s);wc(D),o>0?St(this).transition().duration(o).call(C,Y,X,D):St(this).call(b.transform,Y,X,D)}}function v(D,...N){if(!!t.apply(this,arguments)){var z=D.touches,X=z.length,ct=M(this,N,D.changedTouches.length===X).event(D),J,Y,$,lt;for(Vp(D),Y=0;Y"u"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch{return!1}}function Sf(t,e,r){return SH()?Sf=Reflect.construct:Sf=function(i,a,s){var o=[null];o.push.apply(o,a);var l=Function.bind.apply(i,o),u=new l;return s&&zp(u,s.prototype),u},Sf.apply(null,arguments)}function ni(t){return AH(t)||MH(t)||LH(t)||RH()}function AH(t){if(Array.isArray(t))return Yp(t)}function MH(t){if(typeof Symbol<"u"&&t[Symbol.iterator]!=null||t["@@iterator"]!=null)return Array.from(t)}function LH(t,e){if(!!t){if(typeof t=="string")return Yp(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);if(r==="Object"&&t.constructor&&(r=t.constructor.name),r==="Map"||r==="Set")return Array.from(t);if(r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return Yp(t,e)}}function Yp(t,e){(e==null||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r1?r-1:0),i=1;i/gm),GH=Ii(/^data-[\-\w.\u00B7-\uFFFF]/),jH=Ii(/^aria-[\-\w]+$/),$H=Ii(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),XH=Ii(/^(?:\w+script|data):/i),KH=Ii(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),ZH=Ii(/^html$/i),QH=function(){return typeof window>"u"?null:window},JH=function(e,r){if(Ha(e)!=="object"||typeof e.createPolicy!="function")return null;var n=null,i="data-tt-policy-suffix";r.currentScript&&r.currentScript.hasAttribute(i)&&(n=r.currentScript.getAttribute(i));var a="dompurify"+(n?"#"+n:"");try{return e.createPolicy(a,{createHTML:function(o){return o},createScriptURL:function(o){return o}})}catch{return console.warn("TrustedTypes policy "+a+" could not be created."),null}};function Pk(){var t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:QH(),e=function(st){return Pk(st)};if(e.version="2.4.0",e.removed=[],!t||!t.document||t.document.nodeType!==9)return e.isSupported=!1,e;var r=t.document,n=t.document,i=t.DocumentFragment,a=t.HTMLTemplateElement,s=t.Node,o=t.Element,l=t.NodeFilter,u=t.NamedNodeMap,h=u===void 0?t.NamedNodeMap||t.MozNamedAttrMap:u,d=t.HTMLFormElement,f=t.DOMParser,p=t.trustedTypes,m=o.prototype,_=Lf(m,"cloneNode"),y=Lf(m,"nextSibling"),b=Lf(m,"childNodes"),x=Lf(m,"parentNode");if(typeof a=="function"){var k=n.createElement("template");k.content&&k.content.ownerDocument&&(n=k.content.ownerDocument)}var T=JH(p,r),C=T?T.createHTML(""):"",M=n,S=M.implementation,R=M.createNodeIterator,A=M.createDocumentFragment,L=M.getElementsByTagName,v=r.importNode,B={};try{B=Fs(n).documentMode?n.documentMode:{}}catch{}var w={};e.isSupported=typeof x=="function"&&S&&typeof S.createHTMLDocument<"u"&&B!==9;var D=WH,N=HH,z=GH,X=jH,ct=XH,J=KH,Y=$H,$=null,lt=Me({},[].concat(ni(Bk),ni(Hp),ni(Gp),ni(jp),ni(Dk))),ut=null,W=Me({},[].concat(ni(Ok),ni($p),ni(Fk),ni(Rf))),tt=Object.seal(Object.create(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),K=null,it=null,Z=!0,V=!0,Q=!1,q=!1,U=!1,F=!1,j=!1,P=!1,et=!1,at=!1,It=!0,Lt=!1,Rt="user-content-",Ct=!0,pt=!1,mt={},vt=null,Tt=Me({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),ft=null,le=Me({},["audio","video","img","source","image","track"]),Dt=null,Gt=Me({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),$t="http://www.w3.org/1998/Math/MathML",Qt="http://www.w3.org/2000/svg",we="http://www.w3.org/1999/xhtml",jt=we,Ft=!1,zt,wt=["application/xhtml+xml","text/html"],bt="text/html",Et,kt=null,Ut=n.createElement("form"),gt=function(st){return st instanceof RegExp||st instanceof Function},he=function(st){kt&&kt===st||((!st||Ha(st)!=="object")&&(st={}),st=Fs(st),zt=wt.indexOf(st.PARSER_MEDIA_TYPE)===-1?zt=bt:zt=st.PARSER_MEDIA_TYPE,Et=zt==="application/xhtml+xml"?function(At){return At}:Mf,$="ALLOWED_TAGS"in st?Me({},st.ALLOWED_TAGS,Et):lt,ut="ALLOWED_ATTR"in st?Me({},st.ALLOWED_ATTR,Et):W,Dt="ADD_URI_SAFE_ATTR"in st?Me(Fs(Gt),st.ADD_URI_SAFE_ATTR,Et):Gt,ft="ADD_DATA_URI_TAGS"in st?Me(Fs(le),st.ADD_DATA_URI_TAGS,Et):le,vt="FORBID_CONTENTS"in st?Me({},st.FORBID_CONTENTS,Et):Tt,K="FORBID_TAGS"in st?Me({},st.FORBID_TAGS,Et):{},it="FORBID_ATTR"in st?Me({},st.FORBID_ATTR,Et):{},mt="USE_PROFILES"in st?st.USE_PROFILES:!1,Z=st.ALLOW_ARIA_ATTR!==!1,V=st.ALLOW_DATA_ATTR!==!1,Q=st.ALLOW_UNKNOWN_PROTOCOLS||!1,q=st.SAFE_FOR_TEMPLATES||!1,U=st.WHOLE_DOCUMENT||!1,P=st.RETURN_DOM||!1,et=st.RETURN_DOM_FRAGMENT||!1,at=st.RETURN_TRUSTED_TYPE||!1,j=st.FORCE_BODY||!1,It=st.SANITIZE_DOM!==!1,Lt=st.SANITIZE_NAMED_PROPS||!1,Ct=st.KEEP_CONTENT!==!1,pt=st.IN_PLACE||!1,Y=st.ALLOWED_URI_REGEXP||Y,jt=st.NAMESPACE||we,st.CUSTOM_ELEMENT_HANDLING&>(st.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(tt.tagNameCheck=st.CUSTOM_ELEMENT_HANDLING.tagNameCheck),st.CUSTOM_ELEMENT_HANDLING&>(st.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(tt.attributeNameCheck=st.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),st.CUSTOM_ELEMENT_HANDLING&&typeof st.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements=="boolean"&&(tt.allowCustomizedBuiltInElements=st.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),q&&(V=!1),et&&(P=!0),mt&&($=Me({},ni(Dk)),ut=[],mt.html===!0&&(Me($,Bk),Me(ut,Ok)),mt.svg===!0&&(Me($,Hp),Me(ut,$p),Me(ut,Rf)),mt.svgFilters===!0&&(Me($,Gp),Me(ut,$p),Me(ut,Rf)),mt.mathMl===!0&&(Me($,jp),Me(ut,Fk),Me(ut,Rf))),st.ADD_TAGS&&($===lt&&($=Fs($)),Me($,st.ADD_TAGS,Et)),st.ADD_ATTR&&(ut===W&&(ut=Fs(ut)),Me(ut,st.ADD_ATTR,Et)),st.ADD_URI_SAFE_ATTR&&Me(Dt,st.ADD_URI_SAFE_ATTR,Et),st.FORBID_CONTENTS&&(vt===Tt&&(vt=Fs(vt)),Me(vt,st.FORBID_CONTENTS,Et)),Ct&&($["#text"]=!0),U&&Me($,["html","head","body"]),$.table&&(Me($,["tbody"]),delete K.tbody),sn&&sn(st),kt=st)},yt=Me({},["mi","mo","mn","ms","mtext"]),ne=Me({},["foreignobject","desc","title","annotation-xml"]),ve=Me({},["title","style","font","a","script"]),ye=Me({},Hp);Me(ye,Gp),Me(ye,YH);var be=Me({},jp);Me(be,UH);var Te=function(st){var At=x(st);(!At||!At.tagName)&&(At={namespaceURI:we,tagName:"template"});var Nt=Mf(st.tagName),Jt=Mf(At.tagName);return st.namespaceURI===Qt?At.namespaceURI===we?Nt==="svg":At.namespaceURI===$t?Nt==="svg"&&(Jt==="annotation-xml"||yt[Jt]):Boolean(ye[Nt]):st.namespaceURI===$t?At.namespaceURI===we?Nt==="math":At.namespaceURI===Qt?Nt==="math"&&ne[Jt]:Boolean(be[Nt]):st.namespaceURI===we?At.namespaceURI===Qt&&!ne[Jt]||At.namespaceURI===$t&&!yt[Jt]?!1:!be[Nt]&&(ve[Nt]||!ye[Nt]):!1},Wt=function(st){Tc(e.removed,{element:st});try{st.parentNode.removeChild(st)}catch{try{st.outerHTML=C}catch{st.remove()}}},se=function(st,At){try{Tc(e.removed,{attribute:At.getAttributeNode(st),from:At})}catch{Tc(e.removed,{attribute:null,from:At})}if(At.removeAttribute(st),st==="is"&&!ut[st])if(P||et)try{Wt(At)}catch{}else try{At.setAttribute(st,"")}catch{}},me=function(st){var At,Nt;if(j)st=""+st;else{var Jt=PH(st,/^[\r\n\t ]+/);Nt=Jt&&Jt[0]}zt==="application/xhtml+xml"&&(st=''+st+"");var ze=T?T.createHTML(st):st;if(jt===we)try{At=new f().parseFromString(ze,zt)}catch{}if(!At||!At.documentElement){At=S.createDocument(jt,"template",null);try{At.documentElement.innerHTML=Ft?"":ze}catch{}}var Pe=At.body||At.documentElement;return st&&Nt&&Pe.insertBefore(n.createTextNode(Nt),Pe.childNodes[0]||null),jt===we?L.call(At,U?"html":"body")[0]:U?At.documentElement:Pe},ue=function(st){return R.call(st.ownerDocument||st,st,l.SHOW_ELEMENT|l.SHOW_COMMENT|l.SHOW_TEXT,null,!1)},_a=function(st){return st instanceof d&&(typeof st.nodeName!="string"||typeof st.textContent!="string"||typeof st.removeChild!="function"||!(st.attributes instanceof h)||typeof st.removeAttribute!="function"||typeof st.setAttribute!="function"||typeof st.namespaceURI!="string"||typeof st.insertBefore!="function")},Hr=function(st){return Ha(s)==="object"?st instanceof s:st&&Ha(st)==="object"&&typeof st.nodeType=="number"&&typeof st.nodeName=="string"},Ie=function(st,At,Nt){!w[st]||FH(w[st],function(Jt){Jt.call(e,At,Nt,kt)})},oe=function(st){var At;if(Ie("beforeSanitizeElements",st,null),_a(st)||on(/[\u0080-\uFFFF]/,st.nodeName))return Wt(st),!0;var Nt=Et(st.nodeName);if(Ie("uponSanitizeElement",st,{tagName:Nt,allowedTags:$}),st.hasChildNodes()&&!Hr(st.firstElementChild)&&(!Hr(st.content)||!Hr(st.content.firstElementChild))&&on(/<[/\w]/g,st.innerHTML)&&on(/<[/\w]/g,st.textContent)||Nt==="select"&&on(/