From 03d1aa9a111122f35ccb258b8a5656a7053a29c4 Mon Sep 17 00:00:00 2001 From: Karim TAAM Date: Fri, 3 Nov 2023 18:04:03 +0100 Subject: [PATCH] prepare for first merge Signed-off-by: Karim TAAM --- .github/issue_template.md | 24 + .github/pull_request_template.md | 7 + .github/workflows/build.yml | 33 +- .github/workflows/dco-merge-group.yml | 10 + .github/workflows/dco.yml | 20 + .github/workflows/pr-checklist-on-open.yml | 20 + .github/workflows/repolinter.yml | 24 + .gitignore | 77 ++- CODE_OF_CONDUCT.md | 166 ++++++ DCO.md | 8 + build.gradle | 162 +++++- .../src/main/groovy/CheckSpdxHeader.groovy | 84 +++ gradle/eclipse-java-google-style.xml | 336 ++++++++++++ gradle/formatter.properties | 54 ++ gradle/spotless.java.license | 15 + gradle/versions.gradle | 51 ++ gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle | 1 + .../besu/ethereum/trie/verkle/BranchNode.java | 267 ---------- .../ethereum/trie/verkle/CommitVisitor.java | 91 ---- .../besu/ethereum/trie/verkle/GetVisitor.java | 63 --- .../ethereum/trie/verkle/HashVisitor.java | 226 --------- .../besu/ethereum/trie/verkle/Hasher.java | 21 - .../besu/ethereum/trie/verkle/IPAHasher.java | 27 - .../besu/ethereum/trie/verkle/LeafNode.java | 222 -------- .../besu/ethereum/trie/verkle/Node.java | 131 ----- .../ethereum/trie/verkle/NodeFactory.java | 23 - .../besu/ethereum/trie/verkle/NodeLoader.java | 23 +- .../ethereum/trie/verkle/NodeUpdater.java | 31 +- .../ethereum/trie/verkle/NodeVisitor.java | 47 -- .../besu/ethereum/trie/verkle/NullNode.java | 125 ----- .../ethereum/trie/verkle/PathNodeVisitor.java | 41 -- .../besu/ethereum/trie/verkle/PutVisitor.java | 133 ----- .../ethereum/trie/verkle/RemoveVisitor.java | 141 ----- .../ethereum/trie/verkle/SHA256Hasher.java | 38 -- .../trie/verkle/SimpleVerkleTrie.java | 181 +++---- .../trie/verkle/StoredNodeFactory.java | 93 ---- .../ethereum/trie/verkle/TrieKeyAdapter.java | 163 ------ .../besu/ethereum/trie/verkle/VerkleTrie.java | 8 +- .../trie/verkle/adapter/TrieKeyAdapter.java | 182 +++++++ .../trie/verkle/factory/NodeFactory.java | 40 ++ .../verkle/factory/StoredNodeFactory.java | 114 +++++ .../ethereum/trie/verkle/hasher/Hasher.java | 36 ++ .../trie/verkle/hasher/IPAHasher.java | 42 ++ .../trie/verkle/hasher/SHA256Hasher.java | 51 ++ .../ethereum/trie/verkle/node/BranchNode.java | 281 ++++++++++ .../ethereum/trie/verkle/node/LeafNode.java | 233 +++++++++ .../besu/ethereum/trie/verkle/node/Node.java | 136 +++++ .../ethereum/trie/verkle/node/NullNode.java | 127 +++++ .../trie/verkle/visitor/CommitVisitor.java | 95 ++++ .../trie/verkle/visitor/GetVisitor.java | 81 +++ .../trie/verkle/visitor/HashVisitor.java | 247 +++++++++ .../trie/verkle/visitor/NodeVisitor.java | 53 ++ .../trie/verkle/visitor/PathNodeVisitor.java | 59 +++ .../trie/verkle/visitor/PutVisitor.java | 132 +++++ .../trie/verkle/visitor/RemoveVisitor.java | 155 ++++++ .../ethereum/trie/verkle/NodeLoaderMock.java | 33 +- .../ethereum/trie/verkle/NodeUpdaterMock.java | 38 +- .../trie/verkle/SimpleVerkleTrieTest.java | 480 +++++++++++------- .../trie/verkle/StoredVerkleTrieTest.java | 335 +++++++----- .../trie/verkle/TrieKeyAdapterTest.java | 136 +++-- 61 files changed, 3820 insertions(+), 2455 deletions(-) create mode 100644 .github/issue_template.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/dco-merge-group.yml create mode 100644 .github/workflows/dco.yml create mode 100644 .github/workflows/pr-checklist-on-open.yml create mode 100644 .github/workflows/repolinter.yml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 DCO.md create mode 100644 buildSrc/src/main/groovy/CheckSpdxHeader.groovy create mode 100644 gradle/eclipse-java-google-style.xml create mode 100644 gradle/formatter.properties create mode 100644 gradle/spotless.java.license create mode 100644 gradle/versions.gradle delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/BranchNode.java delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/CommitVisitor.java delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/GetVisitor.java delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/HashVisitor.java delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/Hasher.java delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/IPAHasher.java delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/LeafNode.java delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/Node.java delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NodeFactory.java delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NodeVisitor.java delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NullNode.java delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/PathNodeVisitor.java delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/PutVisitor.java delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/RemoveVisitor.java delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/SHA256Hasher.java delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/StoredNodeFactory.java delete mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/TrieKeyAdapter.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/adapter/TrieKeyAdapter.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/factory/NodeFactory.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/factory/StoredNodeFactory.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/Hasher.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/IPAHasher.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/SHA256Hasher.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/BranchNode.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/LeafNode.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/Node.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/NullNode.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/CommitVisitor.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/GetVisitor.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/HashVisitor.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/NodeVisitor.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/PathNodeVisitor.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/PutVisitor.java create mode 100644 src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/RemoveVisitor.java diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..cde5abe --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,24 @@ + + +### Description +[Detailed description of the problem and the impact it has] + +### Steps to Reproduce (Bug) +[Please be as specific as possible] + +**Expected behavior:** [What you expect to happen] + +**Actual behavior:** [What actually happens] + +**Frequency:** [How regularly does it occur?] + +### Versions (Add all that apply) +* Java version: [`java -version`] +* OS Name & Version: [`cat /etc/*release`] +* Docker Version: [`docker version`] +* Cloud VM, type, size: [Amazon Web Services I3-large] \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..549afd3 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,7 @@ + + +## PR description + +## Fixed Issue(s) + + \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 757a33a..b6d4716 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,32 +1,33 @@ name: Build and Publish on: - push: - branches: - - main pull_request: + push: branches: - main jobs: build-and-publish: runs-on: ubuntu-latest - + if: ${{ github.actor != 'dependabot[bot]' }} steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Set up JDK 17 - uses: actions/setup-java@v2 + - name: Checkout Repo + uses: actions/checkout@v3 + - name: Set up Java + uses: actions/setup-java@v3 with: - java-version: "17" - distribution: "adopt" - + distribution: adopt + java-version: 17 + cache: gradle + - name: spotless + run: ./gradlew --no-daemon --parallel clean spotlessCheck - name: Build with Gradle run: ./gradlew build - - name: Publish to Artifactory - run: ./gradlew artifactoryPublish + uses: gradle/gradle-build-action@v2 + if: contains('refs/heads/release-', github.ref) || github.ref == 'refs/heads/main' env: - ARTIFACTORY_USER: ${{ secrets.ARTIFACTORY_USER }} - ARTIFACTORY_KEY: ${{ secrets.ARTIFACTORY_KEY }} + ARTIFACTORY_USER: ${{ secrets.ARTIFACTORY_USER}} + ARTIFACTORY_KEY: ${{ secrets.BESU_ARTIFACTORY }} + with: + arguments: --no-daemon --parallel publish artifactoryPublish --scan diff --git a/.github/workflows/dco-merge-group.yml b/.github/workflows/dco-merge-group.yml new file mode 100644 index 0000000..fee29b6 --- /dev/null +++ b/.github/workflows/dco-merge-group.yml @@ -0,0 +1,10 @@ +name: dco +on: + merge_group: + +jobs: + dco: + runs-on: [besu-research-ubuntu-8] + if: ${{ github.actor != 'dependabot[bot]' }} + steps: + - run: echo "This DCO job runs on merge_queue event and doesn't check PR contents" diff --git a/.github/workflows/dco.yml b/.github/workflows/dco.yml new file mode 100644 index 0000000..5fa9931 --- /dev/null +++ b/.github/workflows/dco.yml @@ -0,0 +1,20 @@ +name: dco +on: + pull_request: + workflow_dispatch: + +jobs: + dco: + runs-on: [besu-research-ubuntu-8] + if: ${{ github.actor != 'dependabot[bot]' }} + steps: + - run: echo "This DCO job runs on pull_request event and workflow_dispatch" + - name: Get PR Commits + id: 'get-pr-commits' + uses: tim-actions/get-pr-commits@v1.2.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + - name: DCO Check + uses: tim-actions/dco@v1.1.0 + with: + commits: ${{ steps.get-pr-commits.outputs.commits }} diff --git a/.github/workflows/pr-checklist-on-open.yml b/.github/workflows/pr-checklist-on-open.yml new file mode 100644 index 0000000..e32e4d3 --- /dev/null +++ b/.github/workflows/pr-checklist-on-open.yml @@ -0,0 +1,20 @@ +name: "comment on pr with checklist" +on: + pull_request_target: + types: [ opened ] + branches: [ main ] +jobs: + checklist: + name: "add checklist as a comment on newly opened PRs" + runs-on: [besu-research-ubuntu-8] + steps: + - uses: actions/github-script@v5 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: '- [ ] I thought about the changelog.' + }) diff --git a/.github/workflows/repolinter.yml b/.github/workflows/repolinter.yml new file mode 100644 index 0000000..e1c61ca --- /dev/null +++ b/.github/workflows/repolinter.yml @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: Apache-2.0 +# Hyperledger Repolinter Action +name: Repolinter + +on: + workflow_dispatch: + push: + branches: + - master + - main + pull_request: + branches: + - master + - main + +jobs: + build: + runs-on: [besu-research-ubuntu-16] + container: ghcr.io/todogroup/repolinter:v0.10.1 + steps: + - name: Checkout Code + uses: actions/checkout@v3 + - name: Lint Repo + run: bundle exec /app/bin/repolinter.js --rulesetUrl https://raw.githubusercontent.com/hyperledger-labs/hyperledger-community-management-tools/main/repo_structure/repolint.json --format markdown diff --git a/.gitignore b/.gitignore index 2e9e7aa..e141a48 100644 --- a/.gitignore +++ b/.gitignore @@ -1,46 +1,43 @@ -.gradle -build/ -!gradle/wrapper/gradle-wrapper.jar -!**/src/main/**/build/ -!**/src/test/**/build/ - -### IntelliJ IDEA ### -.idea/modules.xml -.idea/jarRepositories.xml -.idea/compiler.xml -.idea/libraries/ -*.iws +*.bak +*.swp +*.tmp +*~.nib *.iml -*.ipr -### IntelliJ IDEA ### -out/ -!**/src/main/**/out/ -!**/src/test/**/out/ - -### Eclipse ### -.apt_generated +*.launch +*.log +.lh/* +db/ +db.version .classpath -.factorypath +.DS_Store +.gradletasknamecache +.externalToolBuilders/ +.gradle/ +.idea/ +.loadpath +.metadata +.prefs .project +.recommenders/ .settings .springBeans -.sts4-cache +.vertx bin/ -!**/src/main/**/bin/ -!**/src/test/**/bin/ - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ - -### VS Code ### -.vscode/ - -### Mac OS ### -.DS_Store - -### Gradle ### -.gradle/ +local.properties +target/ +tmp/ +**/build/ +out/ +*.vscode/ +gradle/flow/ +*.rlib +*.d +*.a +*.class +scripts/.java-version +**/src/jmh/generated_tests/ +**/src/jmh/generated/* +hs_err_pid* +/shomei.db +node_modules +/**/build/ diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..5d3a57e --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,166 @@ +# [Hyperledger Code of Conduct](https://wiki.hyperledger.org/community/hyperledger-project-code-of-conduct) + +Hyperledger is a collaborative project at The Linux Foundation. It is an open-source and open +community project where participants choose to work together, and in that process experience +differences in language, location, nationality, and experience. In such a diverse environment, +misunderstandings and disagreements happen, which in most cases can be resolved informally. In rare +cases, however, behavior can intimidate, harass, or otherwise disrupt one or more people in the +community, which Hyperledger will not tolerate. + +A **Code of Conduct** is useful to define accepted and acceptable behaviors and to promote high +standards of professional practice. It also provides a benchmark for self evaluation and acts as a +vehicle for better identity of the organization. + +This code (**CoC**) applies to any member of the Hyperledger community – developers, participants in +meetings, teleconferences, mailing lists, conferences or functions, etc. Note that this code +complements rather than replaces legal rights and obligations pertaining to any particular +situation. + +## Statement of Intent + +Hyperledger is committed to maintain a **positive** [work environment](#work-environment). This +commitment calls for a workplace where [participants](#participant) at all levels behave according +to the rules of the following code. A foundational concept of this code is that we all share +responsibility for our work environment. + +## Code + +1. Treat each other with [respect](#respect), professionalism, fairness, and sensitivity to our many + differences and strengths, including in situations of high pressure and urgency. + +2. Never [harass](#harassment) or [bully](#workplace-bullying) anyone verbally, physically or + [sexually](#sexual-harassment). + +3. Never [discriminate](#discrimination) on the basis of personal characteristics or group + membership. + +4. Communicate constructively and avoid [demeaning](#demeaning-behavior) or + [insulting](#insulting-behavior) behavior or language. + +5. Seek, accept, and offer objective work criticism, and [acknowledge](#acknowledgement) properly + the contributions of others. + +6. Be honest about your own qualifications, and about any circumstances that might lead to conflicts + of interest. + +7. Respect the privacy of others and the confidentiality of data you access. + +8. With respect to cultural differences, be conservative in what you do and liberal in what you + accept from others, but not to the point of accepting disrespectful, unprofessional or unfair or + [unwelcome behavior](#unwelcome-behavior) or [advances](#unwelcome-sexual-advance). + +9. Promote the rules of this Code and take action (especially if you are in a + [leadership position](#leadership-position)) to bring the discussion back to a more civil level + whenever inappropriate behaviors are observed. + +10. Stay on topic: Make sure that you are posting to the correct channel and avoid off-topic + discussions. Remember when you update an issue or respond to an email you are potentially + sending to a large number of people. + +11. Step down considerately: Members of every project come and go, and the Hyperledger is no + different. When you leave or disengage from the project, in whole or in part, we ask that you do + so in a way that minimizes disruption to the project. This means you should tell people you are + leaving and take the proper steps to ensure that others can pick up where you left off. + +## Glossary + +### Demeaning Behavior + +is acting in a way that reduces another person's dignity, sense of self-worth or respect within the +community. + +### Discrimination + +is the prejudicial treatment of an individual based on criteria such as: physical appearance, race, +ethnic origin, genetic differences, national or social origin, name, religion, gender, sexual +orientation, family or health situation, pregnancy, disability, age, education, wealth, domicile, +political view, morals, employment, or union activity. + +### Insulting Behavior + +is treating another person with scorn or disrespect. + +### Acknowledgement + +is a record of the origin(s) and author(s) of a contribution. + +### Harassment + +is any conduct, verbal or physical, that has the intent or effect of interfering with an individual, +or that creates an intimidating, hostile, or offensive environment. + +### Leadership Position + +includes group Chairs, project maintainers, staff members, and Board members. + +### Participant + +includes the following persons: + +- Developers +- Member representatives +- Staff members +- Anyone from the Public partaking in the Hyperledger work environment (e.g. contribute code, + comment on our code or specs, email us, attend our conferences, functions, etc) + +### Respect + +is the genuine consideration you have for someone (if only because of their status as participant in +Hyperledger, like yourself), and that you show by treating them in a polite and kind way. + +### Sexual Harassment + +includes visual displays of degrading sexual images, sexually suggestive conduct, offensive remarks +of a sexual nature, requests for sexual favors, unwelcome physical contact, and sexual assault. + +### Unwelcome Behavior + +Hard to define? Some questions to ask yourself are: + +- how would I feel if I were in the position of the recipient? +- would my spouse, parent, child, sibling or friend like to be treated this way? +- would I like an account of my behavior published in the organization's newsletter? +- could my behavior offend or hurt other members of the work group? +- could someone misinterpret my behavior as intentionally harmful or harassing? +- would I treat my boss or a person I admire at work like that ? +- Summary: if you are unsure whether something might be welcome or unwelcome, don't do it. + +### Unwelcome Sexual Advance + +includes requests for sexual favors, and other verbal or physical conduct of a sexual nature, where: + +- submission to such conduct is made either explicitly or implicitly a term or condition of an + individual's employment, +- submission to or rejection of such conduct by an individual is used as a basis for employment + decisions affecting the individual, +- such conduct has the purpose or effect of unreasonably interfering with an individual's work + performance or creating an intimidating hostile or offensive working environment. + +### Workplace Bullying + +is a tendency of individuals or groups to use persistent aggressive or unreasonable behavior (e.g. +verbal or written abuse, offensive conduct or any interference which undermines or impedes work) +against a co-worker or any professional relations. + +### Work Environment + +is the set of all available means of collaboration, including, but not limited to messages to +mailing lists, private correspondence, Web pages, chat channels, phone and video teleconferences, +and any kind of face-to-face meetings or discussions. + +## Incident Procedure + +To report incidents or to appeal reports of incidents, send email to Mike Dolan +(mdolan@linuxfoundation.org) or Angela Brown (angela@linuxfoundation.org). Please include any +available relevant information, including links to any publicly accessible material relating to the +matter. Every effort will be taken to ensure a safe and collegial environment in which to +collaborate on matters relating to the Project. In order to protect the community, the Project +reserves the right to take appropriate action, potentially including the removal of an individual +from any and all participation in the project. The Project will work towards an equitable resolution +in the event of a misunderstanding. + +## Credits + +This code is based on the +[W3C’s Code of Ethics and Professional Conduct](https://www.w3.org/Consortium/cepc) with some +additions from the [Cloud Foundry](https://www.cloudfoundry.org/)‘s Code of Conduct. diff --git a/DCO.md b/DCO.md new file mode 100644 index 0000000..4bf56cb --- /dev/null +++ b/DCO.md @@ -0,0 +1,8 @@ +DCO +=== + +As per section 13.a of the [Hyperledger Charter](https://www.hyperledger.org/about/charter) all code submitted to the Hyperledger Foundation needs to have a [Developer Certificate of Origin](http://developercertificate.org/) (DCO) sign-off. + +The sign off needs to be using your legal name, not a pseudonym. Git has a built-in mechanism to allow this with the `-s` or `--signoff` argument to `git commit` command, providing your `user.name` and `user.email` have been setup correctly. + +If you have any questions, you can reach us on Besu chat; first, [join the Discord server](https://discord.gg/hyperledger/) then [join the Besu channel](https://discord.com/channels/905194001349627914/938504958909747250). diff --git a/build.gradle b/build.gradle index 87b11c9..5620342 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,7 @@ +import net.ltgt.gradle.errorprone.CheckSeverity + +import java.util.regex.Pattern + /* * Copyright Besu Contributors * @@ -13,12 +17,16 @@ * SPDX-License-Identifier: Apache-2.0 */ + buildscript { repositories { maven { url = uri("https://hyperledger.jfrog.io/hyperledger/besu-maven") content { includeGroupByRegex('org\\.hyperledger\\..*') } } + maven { + url "https://plugins.gradle.org/m2/" + } mavenCentral() } } @@ -26,25 +34,39 @@ buildscript { plugins { id 'java-library' id 'maven-publish' + id 'net.ltgt.errorprone' version '2.0.2' + id 'io.spring.dependency-management' version '1.0.11.RELEASE' + id 'com.diffplug.spotless' version '6.12.0' id 'com.jfrog.artifactory' version '4.20.0' } +test { + useJUnitPlatform() +} + dependencies { - implementation "com.google.guava:guava:16.0.1" - implementation 'net.java.dev.jna:jna:5.12.1' - implementation 'org.apache.tuweni:tuweni-bytes:2.3.1' - implementation 'org.apache.tuweni:tuweni-units:2.3.1' - implementation 'org.apache.tuweni:tuweni-rlp:2.3.1' - implementation 'org.hyperledger.besu:ipa-multipoint:0.8.3-SNAPSHOT' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' - testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.2' - testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.12.5' - testImplementation 'org.assertj:assertj-core:3.22.0' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2' + implementation "com.google.guava:guava" + implementation 'net.java.dev.jna:jna' + implementation 'io.tmio:tuweni-bytes' + implementation 'io.tmio:tuweni-units' + implementation 'io.tmio:tuweni-rlp' + implementation 'org.hyperledger.besu:ipa-multipoint' + + testImplementation 'org.junit.jupiter:junit-jupiter-api' + testImplementation 'org.junit.jupiter:junit-jupiter-params' + testImplementation 'com.fasterxml.jackson.core:jackson-databind' + testImplementation 'org.assertj:assertj-core' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' } + + allprojects { apply plugin: 'java-library' apply plugin: 'maven-publish' + apply plugin: 'net.ltgt.errorprone' + apply from: "${rootDir}/gradle/versions.gradle" + + version = rootProject.version sourceCompatibility = 17 targetCompatibility = 17 @@ -96,18 +118,112 @@ allprojects { def artifactoryOrg = System.getenv('ARTIFACTORY_ORG') ?: 'hyperledger' artifactory { - contextUrl = "https://hyperledger.jfrog.io/${artifactoryOrg}" - publish { - repository { - repoKey = artifactoryRepo - username = artifactoryUser - password = artifactoryKey - } - defaults { - publications('mavenJava') - publishArtifacts = true - publishPom = true - } + contextUrl = "https://hyperledger.jfrog.io/${artifactoryOrg}" + publish { + repository { + repoKey = artifactoryRepo + username = artifactoryUser + password = artifactoryKey } + defaults { + publications('mavenJava') + publishArtifacts = true + publishPom = true + } + } + } + + apply plugin: 'com.diffplug.spotless' + spotless { + java { + // This path needs to be relative to each project + target 'src/**/*.java' + targetExclude '**/src/reference-test/**', '**/src/main/generated/**', '**/src/test/generated/**', '**/src/jmh/generated/**' + removeUnusedImports() + googleJavaFormat('1.15.0') + importOrder 'org.hyperledger', 'java', '' + trimTrailingWhitespace() + endWithNewline() + licenseHeaderFile "${rootDir}/gradle/spotless.java.license" + } + groovyGradle { + target '*.gradle' + greclipse('4.21.0').configFile(rootProject.file('gradle/formatter.properties')) + endWithNewline() + } + // Below this line are currently only license header tasks + format 'groovy', { target '**/src/*/grovy/**/*.groovy' } + format 'bash', { target '**/*.sh' } + format 'sol', { target '**/*.sol' } } + + tasks.withType(JavaCompile) { + options.compilerArgs += [ + '-Xlint:unchecked', + '-Xlint:cast', + '-Xlint:rawtypes', + '-Xlint:overloads', + '-Xlint:divzero', + '-Xlint:finally', + '-Xlint:static', + '-Werror', + ] + options.errorprone { + excludedPaths = '.*/(generated/*.*|.*ReferenceTest_.*|build/.*/annotation-output/.*)' + + // Our equals need to be symmetric, this checker doesn't respect that. + check('EqualsGetClass', CheckSeverity.OFF) + // We like to use futures with no return values. + check('FutureReturnValueIgnored', CheckSeverity.OFF) + // We use the JSR-305 annotations instead of the Google annotations. + check('ImmutableEnumChecker', CheckSeverity.OFF) + // This is a style check instead of an error-prone pattern. + check('UnnecessaryParentheses', CheckSeverity.OFF) + + // This check is broken in Java 12. See https://github.com/google/error-prone/issues/1257 + if (JavaVersion.current() == JavaVersion.VERSION_12) { + check('Finally', CheckSeverity.OFF) + } + // This check is broken after Java 12. See https://github.com/google/error-prone/issues/1352 + if (JavaVersion.current() > JavaVersion.VERSION_12) { + check('TypeParameterUnusedInFormals', CheckSeverity.OFF) + } + + check('FieldCanBeFinal', CheckSeverity.WARN) + check('InsecureCryptoUsage', CheckSeverity.WARN) + check('WildcardImport', CheckSeverity.WARN) + } + options.encoding = 'UTF-8' + } + } +def sep = Pattern.quote(File.separator) + +dependencies { + errorprone 'com.google.errorprone:error_prone_core' +} + +task checkSpdxHeader(type: CheckSpdxHeader) { + apply plugin: 'groovy' + + rootPath = "${projectDir}" + spdxHeader = "* SPDX-License-Identifier: Apache-2.0" + filesRegex = "(.*.java)|(.*.groovy)" + excludeRegex = [ + "(.*${sep}generalstate${sep}GeneralStateRegressionReferenceTest.*)", + "(.*${sep}generalstate${sep}GeneralStateReferenceTest.*)", + "(.*${sep}generalstate${sep}LegacyGeneralStateReferenceTest.*)", + "(.*${sep}blockchain${sep}BlockchainReferenceTest.*)", + "(.*${sep}blockchain${sep}LegacyBlockchainReferenceTest.*)", + "(.*${sep}.gradle${sep}.*)", + "(.*${sep}.idea${sep}.*)", + "(.*${sep}out${sep}.*)", + "(.*${sep}build${sep}.*)", + "(.*${sep}src${sep}[^${sep}]+${sep}generated${sep}.*)" + ].join("|") + +} + + +check.dependsOn checkSpdxHeader +build.dependsOn spotlessApply diff --git a/buildSrc/src/main/groovy/CheckSpdxHeader.groovy b/buildSrc/src/main/groovy/CheckSpdxHeader.groovy new file mode 100644 index 0000000..6f8e6cf --- /dev/null +++ b/buildSrc/src/main/groovy/CheckSpdxHeader.groovy @@ -0,0 +1,84 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + + +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction +import org.gradle.tooling.BuildException + +class CheckSpdxHeader extends DefaultTask { + private String rootPath + private String spdxHeader + private String filesRegex + private String excludeRegex + + @Input + String getRootPath() { + return rootPath + } + + void setRootPath(final String rootPath) { + this.rootPath = rootPath + } + + @Input + String getSpdxHeader() { + return spdxHeader + } + + void setSpdxHeader(final String spdxHeader) { + this.spdxHeader = spdxHeader + } + + @Input + String getFilesRegex() { + return filesRegex + } + + void setFilesRegex(final String filesRegex) { + this.filesRegex = filesRegex + } + + @Input + String getExcludeRegex() { + return excludeRegex + } + + void setExcludeRegex(final String excludeRegex) { + this.excludeRegex = excludeRegex + } + + @TaskAction + void checkHeaders() { + def filesWithoutHeader = [] + new File(rootPath).traverse( + type: groovy.io.FileType.FILES, + nameFilter: ~/${filesRegex}/, + excludeFilter: ~/${excludeRegex}/ + ) { + f -> + if (!f.getText().contains(spdxHeader)) { + filesWithoutHeader.add(f) + } + } + + if (!filesWithoutHeader.isEmpty()) { + throw new BuildException("Files without headers: " + filesWithoutHeader.join('\n'), null) + } + } + +} \ No newline at end of file diff --git a/gradle/eclipse-java-google-style.xml b/gradle/eclipse-java-google-style.xml new file mode 100644 index 0000000..34e5ecb --- /dev/null +++ b/gradle/eclipse-java-google-style.xml @@ -0,0 +1,336 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/formatter.properties b/gradle/formatter.properties new file mode 100644 index 0000000..e1be3b0 --- /dev/null +++ b/gradle/formatter.properties @@ -0,0 +1,54 @@ +#Whether to use 'space', 'tab' or 'mixed' (both) characters for indentation. +#The default value is 'tab'. +org.eclipse.jdt.core.formatter.tabulation.char=space + +#Number of spaces used for indentation in case 'space' characters +#have been selected. The default value is 4. +org.eclipse.jdt.core.formatter.tabulation.size=2 + +#Number of spaces used for indentation in case 'mixed' characters +#have been selected. The default value is 4. +org.eclipse.jdt.core.formatter.indentation.size=1 + +#Whether or not indentation characters are inserted into empty lines. +#The default value is 'true'. +org.eclipse.jdt.core.formatter.indent_empty_lines=false + +#Number of spaces used for multiline indentation. +#The default value is 2. +groovy.formatter.multiline.indentation=1 + +#Length after which list are considered too long. These will be wrapped. +#The default value is 30. +groovy.formatter.longListLength=30 + +#Whether opening braces position shall be the next line. +#The default value is 'same'. +groovy.formatter.braces.start=same + +#Whether closing braces position shall be the next line. +#The default value is 'next'. +groovy.formatter.braces.end=next + +#Remove unnecessary semicolons. The default value is 'false'. +groovy.formatter.remove.unnecessary.semicolons=false + +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line + +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert + +# Formatter can be buggy in CI +ignoreFormatterProblems=true diff --git a/gradle/spotless.java.license b/gradle/spotless.java.license new file mode 100644 index 0000000..fbe456f --- /dev/null +++ b/gradle/spotless.java.license @@ -0,0 +1,15 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ \ No newline at end of file diff --git a/gradle/versions.gradle b/gradle/versions.gradle new file mode 100644 index 0000000..265d460 --- /dev/null +++ b/gradle/versions.gradle @@ -0,0 +1,51 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +dependencyManagement { + dependencies { + dependency "com.google.guava:guava:31.1-jre" + + dependency 'net.java.dev.jna:jna:5.13.0' + + dependency 'org.hyperledger.besu:ipa-multipoint:0.8.3-SNAPSHOT' + + dependency 'org.assertj:assertj-core:3.24.2' + + dependencySet(group: 'io.tmio', version: '2.4.2') { + entry 'tuweni-bytes' + entry 'tuweni-rlp' + entry 'tuweni-units' + } + + dependencySet(group: 'com.google.errorprone', version: '2.18.0') { + entry 'error_prone_annotation' + entry 'error_prone_check_api' + entry 'error_prone_core' + entry 'error_prone_test_helpers' + } + + dependencySet(group: 'org.junit.jupiter', version: '5.8.2') { + entry 'junit-jupiter' + entry 'junit-jupiter-api' + entry 'junit-jupiter-engine' + entry 'junit-jupiter-params' + } + + dependencySet(group:'com.fasterxml.jackson.core', version:'2.14.2') { + entry 'jackson-databind' + } + + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3bad3c0..7a0ae29 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Thu Nov 02 14:42:19 CET 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index ecc5d95..2d6069c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ + rootProject.name = 'besu-verkle-trie' diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/BranchNode.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/BranchNode.java deleted file mode 100644 index a16446f..0000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/BranchNode.java +++ /dev/null @@ -1,267 +0,0 @@ -package org.hyperledger.besu.ethereum.trie.verkle; - -import java.util.List; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Objects; -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.rlp.RLP; -import org.apache.tuweni.rlp.RLPWriter; - -/** - * Represents a branch node in the Verkle Trie. - * - * @param The type of the node's value. - */ -public class BranchNode implements Node { - private final Optional location; // Location in the tree - private final Bytes path; // Extension path - private final Optional hash; // Vector commitment of children's commitments - private Optional encodedValue = Optional.empty(); // Encoded value - private List> children; // List of children nodes - - private boolean dirty = true; // not persisted - - /** - * Constructs a new BranchNode with location, hash, path, and children. - * - * @param location The location in the tree. - * @param hash The vector commitment of children's commitments. - * @param path The extension path. - * @param children The list of children nodes. - */ - public BranchNode( - final Bytes location, - final Bytes32 hash, - final Bytes path, - final List> children) { - assert (children.size() == maxChild()); - this.location = Optional.of(location); - this.hash = Optional.of(hash); - this.path = path; - this.children = children; - } - - /** - * Constructs a new BranchNode with optional location, optional hash, path, and - * children. - * - * @param location The optional location in the tree. - * @param hash The optional vector commitment of children's commitments. - * @param path The extension path. - * @param children The list of children nodes. - */ - public BranchNode( - final Optional location, - final Optional hash, - final Bytes path, - final List> children) { - assert (children.size() == maxChild()); - this.location = location; - this.hash = hash; - this.path = path; - this.children = children; - } - - /** - * Constructs a new BranchNode with optional location, path, and children. - * - * @param location The optional location in the tree. - * @param path The extension path. - * @param children The list of children nodes. - */ - public BranchNode( - final Optional location, - final Bytes path, - final List> children) { - assert (children.size() == maxChild()); - this.location = location; - this.path = path; - this.children = children; - hash = Optional.empty(); - } - - /** - * Constructs a new BranchNode with optional location and path, initializing - * children to NullNodes. - * - * @param location The optional location in the tree. - * @param path The extension path. - */ - public BranchNode(final Optional location, final Bytes path) { - this.location = location; - this.path = path; - this.children = new ArrayList<>(); - for (int i = 0; i < maxChild(); i++) { - children.add(NullNode.instance()); - } - hash = Optional.of(EMPTY_HASH); - } - - /** - * Get the maximum number of children nodes (256 for byte indexes). - * - * @return The maximum number of children nodes. - */ - public static int maxChild() { - return 256; - } - - /** - * Accepts a visitor for path-based operations on the node. - * - * @param visitor The path node visitor. - * @param path The path associated with a node. - * @return The result of the visitor's operation. - */ - @Override - public Node accept(PathNodeVisitor visitor, Bytes path) { - return visitor.visit(this, path); - } - - /** - * Accepts a visitor for generic node operations. - * - * @param visitor The node visitor. - * @return The result of the visitor's operation. - */ - @Override - public Node accept(final NodeVisitor visitor) { - return visitor.visit(this); - } - - /** - * Get the child node at a specified index. - * - * @param childIndex The index of the child node. - * @return The child node. - */ - public Node child(final byte childIndex) { - return children.get(Byte.toUnsignedInt(childIndex)); - } - - /** - * Replaces the child node at a specified index with a new node. - * - * @param index The index of the child node to replace. - * @param childNode The new child node. - */ - public void replaceChild(final byte index, final Node childNode) { - children.set(Byte.toUnsignedInt(index), childNode); - } - - /** - * Get the vector commitment of children's commitments. - * - * @return An optional containing the vector commitment. - */ - public Optional getHash() { - return hash; - } - - /** - * Replace the vector commitment with a new one. - * - * @param hash The new vector commitment to set. - * @return A new BranchNode with the updated vector commitment. - */ - public Node replaceHash(Bytes32 hash) { - return new BranchNode(location, Optional.of(hash), path, children); - } - - /** - * Get the location in the tree. - * - * @return An optional containing the location if available. - */ - @Override - public Optional getLocation() { - return location; - } - - /** - * Get the extension path of the node. - * - * @return The extension path. - */ - public Bytes getPath() { - return path; - } - - /** - * Replace the extension path with a new one. - * - * @param path The new extension path to set. - * @return A new BranchNode with the updated extension path. - */ - @Override - public Node replacePath(Bytes path) { - BranchNode updatedNode = new BranchNode(location, path, children); - return updatedNode; - } - - /** - * Get the RLP-encoded value of the node. - * - * @return The RLP-encoded value. - */ - @Override - public Bytes getEncodedValue() { - if (encodedValue.isPresent()) { - return encodedValue.get(); - } - List values = Arrays.asList((Bytes) getHash().get(), getPath()); - Bytes result = RLP.encodeList(values, RLPWriter::writeValue); - this.encodedValue = Optional.of(result); - return result; - } - - /** - * Get the list of children nodes. - * - * @return The list of children nodes. - */ - @Override - public List> getChildren() { - return children; - } - - /** Marks the node as dirty, indicating that it needs to be persisted. */ - @Override - public void markDirty() { - dirty = true; - } - - /** - * Checks if the node is dirty, indicating that it needs to be persisted. - * - * @return `true` if the node is marked as dirty, `false` otherwise. - */ - @Override - public boolean isDirty() { - return dirty; - } - - /** - * Generates a string representation of the branch node and its children. - * - * @return A string representing the branch node and its children. - */ - @Override - public String print() { - final StringBuilder builder = new StringBuilder(); - builder.append("Branch:"); - for (int i = 0; i < maxChild(); i++) { - final Node child = child((byte) i); - if (!Objects.equals(child, NullNode.instance())) { - final String branchLabel = "[" + Integer.toHexString(i) + "] "; - final String childRep = child.print().replaceAll("\n\t", "\n\t\t"); - builder.append("\n\t").append(branchLabel).append(childRep); - } - } - return builder.toString(); - } -} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/CommitVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/CommitVisitor.java deleted file mode 100644 index 566dcbc..0000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/CommitVisitor.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.trie.verkle; - -import org.apache.tuweni.bytes.Bytes; - -/** - * A visitor class responsible for committing changes to nodes in a Trie tree. - * - * It iterates through the nodes and stores the changes in the Trie structure. - * - * @param The type of node values. - */ -public class CommitVisitor implements PathNodeVisitor { - - /** - * The NodeUpdater used to store changes in the Trie structure. - */ - protected final NodeUpdater nodeUpdater; - - /** - * Constructs a CommitVisitor with a provided NodeUpdater. - * - * @param nodeUpdater The NodeUpdater used to store changes in the Trie - * structure. - */ - public CommitVisitor(final NodeUpdater nodeUpdater) { - this.nodeUpdater = nodeUpdater; - } - - /** - * Visits a BranchNode to commit any changes in the node and its children. - * - * @param branchNode The BranchNode being visited. - * @param location The location in the Trie tree. - * @return The visited BranchNode. - */ - @Override - public Node visit(final BranchNode branchNode, final Bytes location) { - if (!branchNode.isDirty()) { - return branchNode; - } - for (int i = 0; i < BranchNode.maxChild(); ++i) { - Bytes index = Bytes.of(i); - final Node child = branchNode.child((byte) i); - child.accept(this, Bytes.concatenate(location, index)); - } - nodeUpdater.store(location, null, branchNode.getEncodedValue()); - return branchNode; - } - - /** - * Visits a LeafNode to commit any changes in the node. - * - * @param leafNode The LeafNode being visited. - * @param location The location in the Trie tree. - * @return The visited LeafNode. - */ - @Override - public Node visit(final LeafNode leafNode, final Bytes location) { - if (!leafNode.isDirty()) { - return leafNode; - } - nodeUpdater.store(location, null, leafNode.getEncodedValue()); - return leafNode; - } - - /** - * Visits a NullNode, indicating no changes to commit. - * - * @param nullNode The NullNode being visited. - * @param location The location in the Trie tree. - * @return The NullNode indicating no changes. - */ - @Override - public Node visit(final NullNode nullNode, final Bytes location) { - return nullNode; - } -} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/GetVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/GetVisitor.java deleted file mode 100644 index 8e8127a..0000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/GetVisitor.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.hyperledger.besu.ethereum.trie.verkle; - -import org.apache.tuweni.bytes.Bytes; - -/** - * Class representing a visitor for traversing nodes in a Trie tree to find a - * node based on a path. - * - * @param The type of node values. - */ -public class GetVisitor implements PathNodeVisitor { - private final Node NULL_NODE_RESULT = NullNode.instance(); - - /** - * Visits a BranchNode to determine the node matching a given path. - * - * @param branchNode The BranchNode being visited. - * @param path The path to search in the tree. - * @return The matching node or NULL_NODE_RESULT if not found. - */ - @Override - public Node visit(final BranchNode branchNode, final Bytes path) { - final Bytes nodePath = branchNode.getPath(); - final Bytes commonPath = nodePath.commonPrefix(path); - if (commonPath.compareTo(nodePath) != 0) { - // path diverges before the end of the extension, so it cannot match - return NULL_NODE_RESULT; - } - final Bytes pathSuffix = path.slice(commonPath.size()); - final byte childIndex = pathSuffix.get(0); - return branchNode.child(childIndex).accept(this, pathSuffix.slice(1)); - } - - /** - * Visits a LeafNode to determine the matching node based on a given path. - * - * @param leafNode The LeafNode being visited. - * @param path The path to search in the tree. - * @return The matching node or NULL_NODE_RESULT if not found. - */ - @Override - public Node visit(LeafNode leafNode, Bytes path) { - final Bytes leafPath = leafNode.getPath(); - final Bytes commonPath = leafPath.commonPrefix(path); - if (commonPath.compareTo(leafPath) != 0) { - return NULL_NODE_RESULT; - } - return leafNode; - } - - /** - * Visits a NullNode to determine the matching node based on a given path. - * - * @param nullNode The NullNode being visited. - * @param path The path to search in the tree. - * @return The NULL_NODE_RESULT since NullNode represents a missing node on the - * path. - */ - @Override - public Node visit(NullNode nullNode, Bytes path) { - return NULL_NODE_RESULT; - } -} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/HashVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/HashVisitor.java deleted file mode 100644 index 95d36f2..0000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/HashVisitor.java +++ /dev/null @@ -1,226 +0,0 @@ -package org.hyperledger.besu.ethereum.trie.verkle; - -import java.util.Arrays; -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; - -/** - * A visitor class for hashing operations on Verkle Trie nodes. - * - * @param The type of the node's value. - */ -public class HashVisitor implements PathNodeVisitor { - Hasher hasher = new IPAHasher(); - - /** - * Visits a branch node, computes its hash, and returns a new branch node with - * the updated hash. - * - * @param branchNode The branch node to visit. - * @param location The location associated with the branch node. - * @return A new branch node with the updated hash. - */ - @Override - public Node visit(BranchNode branchNode, Bytes location) { - if (!branchNode.isDirty() && branchNode.getHash().isPresent()) { - return branchNode; - } - Bytes32 baseHash; - if (location.size() == 31) { // branch with leaf nodes as children - baseHash = hashValues(branchNode, location); - } else { // Regular internal node - int size = BranchNode.maxChild(); - Bytes32[] childCommits = new Bytes32[size]; - for (int i = 0; i < size; i++) { - byte index = (byte) i; - Node child = branchNode.child(index); - Bytes childPath = child.getPath(); - Bytes nextLocation = Bytes.concatenate(location, Bytes.of(index), childPath); - Node updatedChild = child.accept(this, nextLocation); - branchNode.replaceChild(index, updatedChild); - childCommits[i] = updatedChild.getHash().get(); - } - baseHash = hasher.commit(childCommits); - } - return branchNode.replaceHash(hashExtension(branchNode.getPath(), baseHash)); - } - - /** - * Visits a leaf node, computes its hash, and returns a new leaf node with the - * updated hash. - * - * @param leafNode The leaf node to visit. - * @param location The location associated with the leaf node. - * @return A new leaf node with the updated hash. - */ - @Override - public Node visit(LeafNode leafNode, Bytes location) { - Bytes path = leafNode.getPath(); - if (path.size() == 0) { - // LeafNode without extension should not be visited - return leafNode; - } - // Remove last byte from location to get the stem - Bytes stem = location.slice(0, location.size() - 1); - Optional value = leafNode.getValue(); - byte index = path.get(path.size() - 1); - Bytes32 baseHash = hashStemExtensionOne(stem, value, index); - return leafNode.replaceHash(hashExtension(path, baseHash)); - } - - /** - * Visits a null node and returns the same null node. - * - * @param nullNode The null node to visit. - * @param location The location associated with the null node. - * @return The same null node. - */ - @Override - public Node visit(NullNode nullNode, Bytes location) { - return nullNode; - } - - // Should use commit_one. For now, using commit. - /** - * Computes the hash of a single value with a given index. - *

- * Should use commit_one. For now, using commit. - * - * @param value The value to hash. - * @param index The index associated with the value. - * @return The hash of the value. - */ - Bytes32 hashOne(Bytes32 value, byte index) { - int idx = Byte.toUnsignedInt(index); - Bytes32[] values = new Bytes32[idx + 1]; - Arrays.fill(values, Bytes32.ZERO); - values[idx] = value; - return hasher.commit(values); - } - - /** - * Computes the hash of a branch node extension. - * - * @param path The path associated with the extension. - * @param baseHash The base hash. - * @return The hash of the branch node extension. - */ - Bytes32 hashExtension(Bytes path, Bytes32 baseHash) { - Bytes revPath = path.reverse(); - Bytes32 hash = baseHash; - for (int i = 0; i < path.size(); i++) { - hash = hashOne(hash, revPath.get(i)); - } - return hash; - } - - /** - * Computes the hash of a branch node stem extension. - * - * @param stem The stem of the branch node. - * @param leftValues The left values associated with the stem. - * @param rightValues The right values associated with the stem. - * @return The hash of the stem extension. - */ - Bytes32 hashStemExtension(Bytes stem, Bytes32[] leftValues, Bytes32[] rightValues) { - Bytes32[] extensionHashes = new Bytes32[4]; - extensionHashes[0] = Bytes32.rightPad(Bytes.of((byte) 1).reverse()); // extension marker - extensionHashes[1] = Bytes32.rightPad(stem); - extensionHashes[2] = hasher.commit(leftValues); - extensionHashes[3] = hasher.commit(rightValues); - return hasher.commit(extensionHashes); - } - - // Should use commit_sparse to commit low and high values - /** - * Computes the hash of a branch node stem extension with a single value. - *

- * Should use commit_sparse to commit low and high values - * - * @param stem The stem of the branch node. - * @param value The value associated with the stem extension. - * @param index The index associated with the value. - * @return The hash of the stem extension with a single value. - */ - // - Bytes32 hashStemExtensionOne(Bytes stem, Optional value, byte index) { - int idx = Byte.toUnsignedInt(index); - Bytes32 leftHash; - Bytes32 rightHash; - if (idx >= 128) { - int rightIdx = idx - 128; - Bytes32[] values = new Bytes32[rightIdx + 2]; - Arrays.fill(values, Bytes32.ZERO); - values[rightIdx] = getLowValue(value); - values[rightIdx + 1] = getHighValue(value); - leftHash = Bytes32.ZERO; - rightHash = hasher.commit(values); - } else { - Bytes32[] values = new Bytes32[idx + 2]; - Arrays.fill(values, Bytes32.ZERO); - values[idx] = getLowValue(value); - values[idx + 1] = getHighValue(value); - leftHash = hasher.commit(values); - rightHash = Bytes32.ZERO; - } - Bytes32[] extensionHashes = new Bytes32[4]; - extensionHashes[0] = Bytes32.rightPad(Bytes.of((byte) 1).reverse()); // extension marker - extensionHashes[1] = Bytes32.rightPad(stem); - extensionHashes[2] = leftHash; - extensionHashes[3] = rightHash; - return hasher.commit(extensionHashes); - } - - /** - * Retrieves the low value part of a given optional value. - * - * @param value The optional value. - * @return The low value. - */ - Bytes32 getLowValue(Optional value) { - // Low values have a flag at bit 128. - if (!value.isPresent()) { - return Bytes32.ZERO; - } - return Bytes32.rightPad(Bytes.concatenate(value.get().slice(0, 16), Bytes.of((byte) 1).reverse())); - } - - /** - * Retrieves the high value part of a given optional value. - * - * @param value The optional value. - * @return The high value. - */ - Bytes32 getHighValue(Optional value) { - if (!value.isPresent()) { - return Bytes32.ZERO; - } - return Bytes32.rightPad(value.get().slice(16, 16)); - } - - /** - * Computes the hash of values within a branch node. - * - * @param branchNode The branch node containing values. - * @param location The location associated with the branch node. - * @return The hash of the values within the branch node. - */ - Bytes32 hashValues(BranchNode branchNode, Bytes location) { - // Values are little endian - // Values are decomposed into 16 lower bytes and 16 higher bytes - // Lower bytes are appended with a 1 to signify that a value is present - // Each part is hashed separately - int size = BranchNode.maxChild(); - Bytes32[] values = new Bytes32[size * 2]; - for (int i = 0; i < size; i++) { - Optional value = branchNode.child((byte) i).getValue(); - values[2 * i] = getLowValue(value); - values[2 * i + 1] = getHighValue(value); - } - Bytes32[] leftValues = Arrays.copyOfRange(values, 0, size); - Bytes32[] rightValues = Arrays.copyOfRange(values, size, 2 * size); - return hashStemExtension(location, leftValues, rightValues); - } -} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/Hasher.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/Hasher.java deleted file mode 100644 index 77b392f..0000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/Hasher.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.hyperledger.besu.ethereum.trie.verkle; - -import org.apache.tuweni.bytes.Bytes32; - -/** - * Defines an interface for a Verkle Trie node hashing strategy. - * - * @param The type of values to be hashed. - */ -public interface Hasher { - - /** - * Calculates the commitment hash for an array of inputs. - * - * @param inputs An array of values to be hashed. - * @return The commitment hash calculated from the inputs. - */ - public Bytes32 commit(V[] inputs); - - // public Bytes32 commit_sparse(V[] input, int[] index) -} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/IPAHasher.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/IPAHasher.java deleted file mode 100644 index f2e2c80..0000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/IPAHasher.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.hyperledger.besu.ethereum.trie.verkle; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.hyperledger.besu.nativelib.ipamultipoint.LibIpaMultipoint; - -/** - * A class responsible for hashing an array of Bytes32 using the IPA - * (Inter-Participant Agreement) hashing algorithm. - * - *

- * This class implements the Hasher interface and provides a method to commit - * multiple Bytes32 inputs using the IPA hashing algorithm. - */ -public class IPAHasher implements Hasher { - /** - * Commits an array of Bytes32 using the IPA hashing algorithm. - * - * @param inputs An array of Bytes32 inputs to be hashed and committed. - * @return The resulting hash as a Bytes32. - */ - @Override - public Bytes32 commit(Bytes32[] inputs) { - Bytes input_serialized = Bytes.concatenate(inputs); - return Bytes32.wrap(LibIpaMultipoint.commit(input_serialized.toArray())); - } -} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/LeafNode.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/LeafNode.java deleted file mode 100644 index a15d9a0..0000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/LeafNode.java +++ /dev/null @@ -1,222 +0,0 @@ -package org.hyperledger.besu.ethereum.trie.verkle; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.rlp.RLP; -import org.apache.tuweni.rlp.RLPWriter; - -/** - * Represents a leaf node in the Verkle Trie. - * - * @param The type of the node's value. - */ -public class LeafNode implements Node { - private final Optional location; // Location in the tree - protected final V value; // Value associated with the node - private final Bytes path; // Extension path - private final Optional hash; // Hash of the node - private Optional encodedValue = Optional.empty(); // Encoded value - private final Function valueSerializer; // Serializer function for the value - private boolean dirty = true; // not persisted - - /** - * Constructs a new LeafNode with optional location, value, path, and optional - * hash. - * - * @param location The location of the node in the tree (Optional). - * @param value The value associated with the node. - * @param path The path or key of the node. - * @param hash The hash of the node (Optional). - */ - public LeafNode( - final Optional location, - final V value, - final Bytes path, - final Optional hash) { - this.location = location; - this.value = value; - this.path = path; - this.hash = hash; - this.valueSerializer = val -> (Bytes) val; - } - - /** - * Constructs a new LeafNode with a provided optional location, value, and path. - * - * @param location The location of the node in the tree (Optional). - * @param value The value associated with the node. - * @param path The path or key of the node. - */ - public LeafNode( - final Optional location, - final V value, - final Bytes path) { - this.location = location; - this.path = path; - this.value = value; - this.hash = Optional.empty(); - this.valueSerializer = val -> (Bytes) val; - } - - /** - * Constructs a new LeafNode with a value and path. - * - * @param value The value associated with the node. - * @param path The path or key of the node. - */ - public LeafNode(final V value, final Bytes path) { - location = Optional.empty(); - this.value = value; - this.path = path; - hash = Optional.empty(); - this.valueSerializer = val -> (Bytes) val; - } - - /** - * Accepts a visitor for path-based operations on the node. - * - * @param visitor The path node visitor. - * @param path The path associated with a node. - * @return The result of the visitor's operation. - */ - @Override - public Node accept(final PathNodeVisitor visitor, final Bytes path) { - return visitor.visit(this, path); - } - - /** - * Accepts a visitor for generic node operations. - * - * @param visitor The node visitor. - * @return The result of the visitor's operation. - */ - @Override - public Node accept(final NodeVisitor visitor) { - return visitor.visit(this); - } - - /** - * Get the value associated with the node. - * - * @return An optional containing the value of the node if available. - */ - @Override - public Optional getValue() { - return Optional.ofNullable(value); - } - - /** - * Get the location of the node. - * - * @return An optional containing the location of the node if available. - */ - @Override - public Optional getLocation() { - return location; - } - - /** - * Get the path or key of the node. - * - * @return The path of the node. - */ - @Override - public Bytes getPath() { - return path; - } - - /** - * Get the children of the node. A leaf node does not have children, so this - * method - * throws an UnsupportedOperationException. - * - * @return The list of children nodes (unsupported operation). - * @throws UnsupportedOperationException if called on a leaf node. - */ - @Override - public List> getChildren() { - throw new UnsupportedOperationException("LeafNode does not have children."); - } - - /** - * Get the hash of the node if available. - * - * @return An optional containing the hash of the node if available. - */ - @Override - public Optional getHash() { - return hash; - } - - /** - * Replace the hash of the node. - * - * @param hash The new hash to set. - * @return A new node with the updated hash. - */ - public Node replaceHash(Bytes32 hash) { - return new LeafNode(location, value, path, Optional.of(hash)); - } - - /** - * Replace the path of the node. - * - * @param path The new path to set. - * @return A new node with the updated path. - */ - @Override - public Node replacePath(Bytes path) { - return new LeafNode(location, value, path, hash); - } - - /** - * Get the RLP-encoded value of the node. - * - * @return The RLP-encoded value. - */ - @Override - public Bytes getEncodedValue() { - if (encodedValue.isPresent()) { - return encodedValue.get(); - } - Bytes encodedVal = getValue().isPresent() ? valueSerializer.apply(getValue().get()) : Bytes.EMPTY; - List values = Arrays.asList(Bytes.EMPTY, getPath(), encodedVal); - Bytes result = RLP.encodeList(values, RLPWriter::writeValue); - this.encodedValue = Optional.of(result); - return result; - } - - /** - * Marks the node as needing to be persisted. - */ - @Override - public void markDirty() { - dirty = true; - } - - /** - * Checks if the node needs to be persisted. - * - * @return True if the node needs to be persisted. - */ - @Override - public boolean isDirty() { - return dirty; - } - - /** - * Get a string representation of the node. - * - * @return A string representation of the node. - */ - @Override - public String print() { - return "Leaf:" - + getValue().map(Object::toString).orElse("empty"); - } -} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/Node.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/Node.java deleted file mode 100644 index 0ee3d33..0000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/Node.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.trie.verkle; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; - -/** - * An interface representing a node in the Verkle Trie. - * - * @param The type of the node's value. - */ -public interface Node { - - /** - * A constant representing an empty hash value. - */ - Bytes32 EMPTY_HASH = Bytes32.ZERO; - - /** - * Accept a visitor to perform operations on the node based on a provided path. - * - * @param visitor The visitor to accept. - * @param path The path associated with a node. - * @return The result of visitor's operation. - */ - Node accept(PathNodeVisitor visitor, Bytes path); - - /** - * Accept a visitor to perform operations on the node. - * - * @param visitor The visitor to accept. - * @return The result of the visitor's operation. - */ - Node accept(NodeVisitor visitor); - - /** - * Get the path associated with the node. - * - * @return The path of the node. - */ - default Bytes getPath() { - return Bytes.EMPTY; - }; - - /** - * Get the location of the node. - * - * @return An optional containing the location of the node if available. - */ - default Optional getLocation() { - return Optional.empty(); - } - - /** - * Get the value associated with the node. - * - * @return An optional containing the value of the node if available. - */ - default Optional getValue() { - return Optional.empty(); - }; - - /** - * Get the hash associated with the node. - * - * @return An optional containing the hash of the node if available. - */ - default Optional getHash() { - return Optional.empty(); - }; - - /** - * Replace the path of the node. - * - * @param path The new path to set. - * @return A new node with the updated path. - */ - Node replacePath(Bytes path); - - /** - * Get the encoded value of the node. - * - * @return The encoded value of the node. - */ - default Bytes getEncodedValue() { - return Bytes.EMPTY; - } - - /** - * Get the children nodes of this node. - * - * @return A list of children nodes. - */ - default List> getChildren() { - return Collections.emptyList(); - } - - /** Marks the node as needs to be persisted */ - void markDirty(); - - /** - * Is this node not persisted and needs to be? - * - * @return True if the node needs to be persisted. - */ - boolean isDirty(); - - /** - * Get a string representation of the node. - * - * @return A string representation of the node. - */ - String print(); -} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NodeFactory.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NodeFactory.java deleted file mode 100644 index 1fcdaa1..0000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NodeFactory.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.hyperledger.besu.ethereum.trie.verkle; - -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; - -/** - * An interface representing a factory for creating nodes in the Verkle Trie. - * - * @param The type of the nodes to be created. - */ -public interface NodeFactory { - - /** - * Retrieve a node with the given location and hash. - * - * @param location The location of the node. - * @param hash The hash of the node. - * @return An optional containing the retrieved node, or empty if not found. - */ - Optional> retrieve(final Bytes location, final Bytes32 hash); -} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NodeLoader.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NodeLoader.java index dce9b3a..72a973b 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NodeLoader.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NodeLoader.java @@ -1,5 +1,5 @@ /* - * Copyright ConsenSys AG. + * Copyright Besu Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,6 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 + * */ package org.hyperledger.besu.ethereum.trie.verkle; @@ -19,17 +20,15 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -/** - * An interface representing a loader for retrieving nodes in the Verkle Trie. - */ +/** An interface representing a loader for retrieving nodes in the Verkle Trie. */ public interface NodeLoader { - /** - * Retrieve a node with the given location and hash. - * - * @param location The location of the node. - * @param hash The hash of the node. - * @return An optional containing the retrieved node, or empty if not found. - */ - public Optional getNode(Bytes location, Bytes32 hash); + /** + * Retrieve a node with the given location and hash. + * + * @param location The location of the node. + * @param hash The hash of the node. + * @return An optional containing the retrieved node, or empty if not found. + */ + public Optional getNode(Bytes location, Bytes32 hash); } diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NodeUpdater.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NodeUpdater.java index afdc0e0..ccb1f92 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NodeUpdater.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NodeUpdater.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright Besu Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,28 +11,25 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 + * */ package org.hyperledger.besu.ethereum.trie.verkle; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -/** - * An interface representing a node updater for storing nodes in the Verkle - * Trie. - */ +/** An interface representing a node updater for storing nodes in the Verkle Trie. */ public interface NodeUpdater { - // This method is called to ask Besu to save the node in the database (value is - // the node represented as a list of bytes. - // RLP or something else for verkle). + // This method is called to ask Besu to save the node in the database (value is + // the node represented as a list of bytes. + // RLP or something else for verkle). - /** - * Store a node in the database with the specified location and hash. - * - * @param location The location of the node. - * @param hash The hash of the node. - * @param value The node represented as a list of bytes (e.g., RLP-encoded - * data). - */ - void store(Bytes location, Bytes32 hash, Bytes value); + /** + * Store a node in the database with the specified location and hash. + * + * @param location The location of the node. + * @param hash The hash of the node. + * @param value The node represented as a list of bytes (e.g., RLP-encoded data). + */ + void store(Bytes location, Bytes32 hash, Bytes value); } diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NodeVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NodeVisitor.java deleted file mode 100644 index a278e1e..0000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NodeVisitor.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.trie.verkle; - -/** - * Defines a visitor interface for nodes in the Verkle Trie. - * - * @param The type of value associated with nodes. - */ -public interface NodeVisitor { - - /** - * Visits a branch node. - * - * @param branchNode The branch node to visit. - * @return The result of visiting the branch node. - */ - Node visit(BranchNode branchNode); - - /** - * Visits a leaf node. - * - * @param leafNode The leaf node to visit. - * @return The result of visiting the leaf node. - */ - Node visit(LeafNode leafNode); - - /** - * Visits a null node. - * - * @param nullNode The null node to visit. - * @return The result of visiting the null node. - */ - Node visit(NullNode nullNode); -} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NullNode.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NullNode.java deleted file mode 100644 index 184744e..0000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/NullNode.java +++ /dev/null @@ -1,125 +0,0 @@ -/* Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.trie.verkle; - -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; - -/** - * A special node representing a null or empty node in the Verkle Trie. - * - *

- * The `NullNode` class serves as a placeholder for non-existent nodes in the - * Verkle Trie structure. It implements the Node interface and represents a node - * that contains no information or value. - */ -public class NullNode implements Node { - @SuppressWarnings("rawtypes") - private static final NullNode instance = new NullNode(); - - /** - * Constructs a new `NullNode`. - * This constructor is protected to ensure that `NullNode` instances are only - * created as singletons. - */ - protected NullNode() { - } - - /** - * Gets the shared instance of the `NullNode`. - * - * @param The type of the node's value. - * @return The shared `NullNode` instance. - */ - @SuppressWarnings("unchecked") - public static NullNode instance() { - return instance; - } - - /** - * Accepts a visitor for path-based operations on the node. - * - * @param visitor The path node visitor. - * @param path The path associated with a node. - * @return The result of the visitor's operation. - */ - @Override - public Node accept(final PathNodeVisitor visitor, final Bytes path) { - return visitor.visit(this, path); - } - - /** - * Accepts a visitor for generic node operations. - * - * @param visitor The node visitor. - * @return The result of the visitor's operation. - */ - @Override - public Node accept(final NodeVisitor visitor) { - return visitor.visit(this); - } - - /** - * Get the hash associated with the `NullNode`. - * - * @return An optional containing the empty hash. - */ - @Override - public Optional getHash() { - return Optional.of(EMPTY_HASH); - } - - /** - * Replace the path of the `NullNode` with a new one. - * - * @param path The new path to set. - * @return The `NullNode` itself, as it does not have a path to replace. - */ - @Override - public Node replacePath(final Bytes path) { - return this; - } - - /** - * Get a string representation of the `NullNode`. - * - * @return A string representation indicating that it is a "NULL" node. - */ - @Override - public String print() { - return "[NULL]"; - } - - /** - * Check if the `NullNode` is marked as dirty (needing to be persisted). - * - * @return `false` since a `NullNode` does not require persistence. - */ - @Override - public boolean isDirty() { - return false; - } - - /** - * Mark the `NullNode` as dirty (not used, no operation). - *

- * This method intentionally does nothing. - */ - @Override - public void markDirty() { - // do nothing - } -} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/PathNodeVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/PathNodeVisitor.java deleted file mode 100644 index 8a70908..0000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/PathNodeVisitor.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.hyperledger.besu.ethereum.trie.verkle; - -import org.apache.tuweni.bytes.Bytes; - -/** - * Visit the trie with a path parameter. - * - * The path parameter indicates the path visited in the Trie. - * As Nodes are (mostly) immutable, visiting a Node returns a (possibly) new - * Node that should replace the old one. - * - */ -public interface PathNodeVisitor { - - /** - * Visits a branch node with a specified path. - * - * @param branchNode The branch node to visit. - * @param path The path associated with the visit. - * @return The result of visiting the branch node. - */ - Node visit(BranchNode branchNode, Bytes path); - - /** - * Visits a leaf node with a specified path. - * - * @param leafNode The leaf node to visit. - * @param path The path associated with the visit. - * @return The result of visiting the leaf node. - */ - Node visit(LeafNode leafNode, Bytes path); - - /** - * Visits a null node with a specified path. - * - * @param nullNode The null node to visit. - * @param path The path associated with the visit. - * @return The result of visiting the null node. - */ - Node visit(NullNode nullNode, Bytes path); -} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/PutVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/PutVisitor.java deleted file mode 100644 index 2e05189..0000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/PutVisitor.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.trie.verkle; - -import org.apache.tuweni.bytes.Bytes; - -/** - * A visitor for inserting or updating values in a Verkle Trie. - * - *

- * This class implements the PathNodeVisitor interface and is used to visit and - * modify nodes in the Verkle Trie - * while inserting or updating a value associated with a specific path. - * - * @param The type of values to insert or update. - */ -public class PutVisitor implements PathNodeVisitor { - private V value; - - /** - * Constructs a new PutVisitor with the provided value to insert or update. - * - * @param value The value to be inserted or updated in the Verkle Trie. - */ - public PutVisitor(V value) { - this.value = value; - } - - /** - * Inserts or updates a value in a branch node. - * - * @param node The branch node to insert or update the value. - * @param commonPath The common path between the node and the path. - * @param pathSuffix The path suffix associated with the value. - * @param nodeSuffix The remaining node suffix after the common path. - * @return The updated node with the inserted or updated value. - */ - protected Node insertNewBranching( - final Node node, - final Bytes commonPath, - final Bytes pathSuffix, - final Bytes nodeSuffix) { - final Node updatedNode = node.replacePath(nodeSuffix.slice(1)); - // Should also add byte to location - BranchNode newBranchNode = new BranchNode(node.getLocation(), commonPath); - newBranchNode.replaceChild(nodeSuffix.get(0), updatedNode); - final Node insertedNode = newBranchNode.child(pathSuffix.get(0)).accept(this, pathSuffix.slice(1)); - newBranchNode.replaceChild(pathSuffix.get(0), insertedNode); - return newBranchNode; - } - - /** - * Visits a branch node to insert or update a value associated with the provided - * path. - * - * @param branchNode The branch node to visit. - * @param path The path associated with the value to insert or update. - * @return The updated branch node with the inserted or updated value. - */ - @Override - public Node visit(final BranchNode branchNode, final Bytes path) { - final Bytes nodePath = branchNode.getPath(); - final Bytes commonPath = nodePath.commonPrefix(path); - final int commonPathLength = commonPath.size(); - final Bytes pathSuffix = path.slice(commonPathLength); - final Bytes nodeSuffix = nodePath.slice(commonPathLength); - if (commonPath.compareTo(nodePath) == 0) { - final byte childIndex = pathSuffix.get(0); - final Node updatedChild = branchNode.child(childIndex).accept(this, pathSuffix.slice(1)); - branchNode.replaceChild(childIndex, updatedChild); - if (updatedChild.isDirty()) { - branchNode.markDirty(); - } - return branchNode; - } else { - return insertNewBranching(branchNode, commonPath, pathSuffix, nodeSuffix); - } - } - - /** - * Visits a leaf node to insert or update a value associated with the provided - * path. - * - * @param leafNode The leaf node to visit. - * @param path The path associated with the value to insert or update. - * @return The updated leaf node with the inserted or updated value. - */ - @Override - public Node visit(final LeafNode leafNode, final Bytes path) { - /* - * Leaf node is used to store a value. - * However, it is a mixture with ExtensionNode and can have a non-empty path: - * An extension all the way to a LeafNode is stored as a single LeafNode - */ - final Bytes nodePath = leafNode.getPath(); - final Bytes commonPath = nodePath.commonPrefix(path); - final int commonPathLength = commonPath.size(); - final Bytes pathSuffix = path.slice(commonPathLength); - final Bytes nodeSuffix = nodePath.slice(commonPathLength); - if (commonPath.compareTo(nodePath) == 0) { - final LeafNode newNode = new LeafNode(leafNode.getLocation(), value, path); - newNode.markDirty(); - return newNode; - } - - return insertNewBranching(leafNode, commonPath, pathSuffix, nodeSuffix); - } - - /** - * Visits a null node to insert or update a value associated with the provided - * path. - * - * @param nullNode The null node to visit. - * @param path The path associated with the value to insert or update. - * @return A new leaf node containing the inserted or updated value. - */ - @Override - public Node visit(final NullNode nullNode, final Bytes path) { - return new LeafNode(value, path); - } -} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/RemoveVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/RemoveVisitor.java deleted file mode 100644 index 2374bd0..0000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/RemoveVisitor.java +++ /dev/null @@ -1,141 +0,0 @@ -package org.hyperledger.besu.ethereum.trie.verkle; - -import java.util.List; -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; - -/** - * A visitor for removing nodes in a Verkle Trie while preserving its structure. - * - *

- * This class implements the PathNodeVisitor interface and is used to visit and - * remove nodes in the Verkle Trie - * while maintaining the Trie's structural integrity. - * - * @param The type of values associated with the nodes. - */ -public class RemoveVisitor implements PathNodeVisitor { - private final Node NULL_NODE = NullNode.instance(); - - /** - * Merges a single branching node by updating and replacing its structure with - * the new node. - * - * @param node The node to be merged. - * @param commonPath The common path between the node and the path. - * @param pathSuffix The path suffix associated with the node. - * @param nodeSuffix The remaining node suffix after the common path. - * @return The merged node with the updated structure. - */ - protected Node mergeSingleBranching( - final Node node, - final Bytes commonPath, - final Bytes pathSuffix, - final Bytes nodeSuffix) { - final Node updatedNode = node.replacePath(nodeSuffix.slice(1)); - // Should also add byte to location - BranchNode newBranchNode = new BranchNode(node.getLocation(), commonPath); - newBranchNode.replaceChild(nodeSuffix.get(0), updatedNode); - final Node insertedNode = newBranchNode.child(pathSuffix.get(0)).accept(this, pathSuffix.slice(1)); - newBranchNode.replaceChild(pathSuffix.get(0), insertedNode); - return newBranchNode; - } - - /** - * Visits a branch node to remove a node associated with the provided path and - * maintain the Trie's structure. - * - * @param branchNode The branch node to visit. - * @param path The path associated with the node to be removed. - * @return The updated branch node with the removed node and preserved - * structure. - */ - @Override - public Node visit(BranchNode branchNode, Bytes path) { - final Bytes leafPath = branchNode.getPath(); - final Bytes commonPath = leafPath.commonPrefix(path); - if (commonPath.compareTo(leafPath) != 0) { - return branchNode; - } - final Bytes pathSuffix = path.slice(commonPath.size()); - final byte childIndex = pathSuffix.get(0); - final Node childNode = branchNode.child(childIndex).accept(this, pathSuffix.slice(1)); - branchNode.replaceChild(childIndex, childNode); - Node resultNode = maybeFlatten(branchNode); - return resultNode; - } - - /** - * Visits a leaf node to remove a node associated with the provided path and - * maintain the Trie's structure. - * - * @param leafNode The leaf node to visit. - * @param path The path associated with the node to be removed. - * @return A null node, indicating the removal of the node. - */ - @Override - public Node visit(LeafNode leafNode, Bytes path) { - final Bytes nodePath = leafNode.getPath(); - final Bytes commonPath = nodePath.commonPrefix(path); - if (commonPath.compareTo(nodePath) != 0) { - return leafNode; - } - return NULL_NODE; - } - - /** - * Visits a null node and returns a null node, indicating that no removal is - * required. - * - * @param nullNode The null node to visit. - * @param path The path associated with the removal (no operation). - * @return A null node, indicating no removal is needed. - */ - @Override - public Node visit(NullNode nullNode, Bytes path) { - return NULL_NODE; - } - - /** - * Checks if the branch node should be flattened (merged with its only child) - * and performs the flattening operation. - * - * @param branchNode The branch node to consider for flattening. - * @return The updated node after flattening or the original branch node if - * flattening is not applicable. - */ - protected Node maybeFlatten(BranchNode branchNode) { - final Optional onlyChildIndex = findOnlyChild(branchNode.getChildren()); - // Many children => return node as is - if (!onlyChildIndex.isPresent()) { - return branchNode; - } - // One child => merge with child: replace the path of the only child and return - // it - final Node onlyChild = branchNode.child(onlyChildIndex.get()); - final Bytes completePath = Bytes.concatenate(branchNode.getPath(), Bytes.of(onlyChildIndex.get()), - onlyChild.getPath()); - return onlyChild.replacePath(completePath); - } - - /** - * Finds the index of the only non-null child in the list of children nodes. - * - * @param children The list of children nodes. - * @return The index of the only non-null child if it exists, or an empty - * optional if there is no or more than one non-null child. - */ - private Optional findOnlyChild(final List> children) { - Optional onlyChildIndex = Optional.empty(); - for (int i = 0; i < children.size(); ++i) { - if (children.get(i) != NULL_NODE) { - if (onlyChildIndex.isPresent()) { - return Optional.empty(); - } - onlyChildIndex = Optional.of((byte) i); - } - } - return onlyChildIndex; - } -} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/SHA256Hasher.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/SHA256Hasher.java deleted file mode 100644 index 21f78ff..0000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/SHA256Hasher.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.hyperledger.besu.ethereum.trie.verkle; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; - -/** - * A class responsible for hashing an array of Bytes32 using the SHA-256 (Secure - * Hash Algorithm 256-bit) hashing algorithm. - * - *

- * This class implements the Hasher interface and provides a method to commit - * multiple Bytes32 inputs using the SHA-256 hashing algorithm. - * It utilizes the Java built-in MessageDigest for SHA-256. - */ -public class SHA256Hasher implements Hasher { - /** - * Commits an array of Bytes32 using the SHA-256 hashing algorithm provided by - * the MessageDigest. - * - * @param inputs An array of Bytes32 inputs to be hashed and committed. - * @return The resulting hash as a Bytes32. - */ - @Override - public Bytes32 commit(Bytes32[] inputs) { - Bytes32 out; - Bytes inputs_serialized = Bytes.concatenate(inputs); - try { - MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); - out = Bytes32.wrap(sha256.digest(inputs_serialized.toArray())); - } catch (NoSuchAlgorithmException e) { - out = Bytes32.ZERO; - } - return out; - } -} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/SimpleVerkleTrie.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/SimpleVerkleTrie.java index 773b453..876d4a2 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/SimpleVerkleTrie.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/SimpleVerkleTrie.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright Besu Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,11 +11,20 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 + * */ package org.hyperledger.besu.ethereum.trie.verkle; import static com.google.common.base.Preconditions.checkNotNull; +import org.hyperledger.besu.ethereum.trie.verkle.node.Node; +import org.hyperledger.besu.ethereum.trie.verkle.node.NullNode; +import org.hyperledger.besu.ethereum.trie.verkle.visitor.CommitVisitor; +import org.hyperledger.besu.ethereum.trie.verkle.visitor.GetVisitor; +import org.hyperledger.besu.ethereum.trie.verkle.visitor.HashVisitor; +import org.hyperledger.besu.ethereum.trie.verkle.visitor.PutVisitor; +import org.hyperledger.besu.ethereum.trie.verkle.visitor.RemoveVisitor; + import java.util.Optional; import org.apache.tuweni.bytes.Bytes; @@ -28,100 +37,96 @@ * @param The type of values in the Verkle Trie. */ public class SimpleVerkleTrie implements VerkleTrie { - private Node root; + private Node root; - /** - * Creates a new Verkle Trie with a null node as the root. - */ - public SimpleVerkleTrie() { - this.root = NullNode.instance(); - } + /** Creates a new Verkle Trie with a null node as the root. */ + public SimpleVerkleTrie() { + this.root = NullNode.instance(); + } - /** - * Creates a new Verkle Trie with the specified node as the root. - * - * @param root The root node of the Verkle Trie. - */ - public SimpleVerkleTrie(Node root) { - this.root = root; - } + /** + * Creates a new Verkle Trie with the specified node as the root. + * + * @param root The root node of the Verkle Trie. + */ + public SimpleVerkleTrie(Node root) { + this.root = root; + } - /** - * Retrieves the root node of the Verkle Trie. - * - * @return The root node of the Verkle Trie. - */ - public Node getRoot() { - return root; - } + /** + * Retrieves the root node of the Verkle Trie. + * + * @return The root node of the Verkle Trie. + */ + public Node getRoot() { + return root; + } - /** - * Gets the value associated with the specified key from the Verkle Trie. - * - * @param key The key to retrieve the value for. - * @return An optional containing the value if found, or an empty optional if - * not found. - */ - @Override - public Optional get(final K key) { - checkNotNull(key); - return root.accept(new GetVisitor(), key).getValue(); - } + /** + * Gets the value associated with the specified key from the Verkle Trie. + * + * @param key The key to retrieve the value for. + * @return An optional containing the value if found, or an empty optional if not found. + */ + @Override + public Optional get(final K key) { + checkNotNull(key); + return root.accept(new GetVisitor(), key).getValue(); + } - /** - * Inserts a key-value pair into the Verkle Trie. - * - * @param key The key to insert. - * @param value The value to associate with the key. - */ - @Override - public void put(final K key, final V value) { - checkNotNull(key); - checkNotNull(value); - this.root = root.accept(new PutVisitor(value), key); - } + /** + * Inserts a key-value pair into the Verkle Trie. + * + * @param key The key to insert. + * @param value The value to associate with the key. + */ + @Override + public void put(final K key, final V value) { + checkNotNull(key); + checkNotNull(value); + this.root = root.accept(new PutVisitor(value), key); + } - /** - * Removes a key-value pair from the Verkle Trie. - * - * @param key The key to remove. - */ - @Override - public void remove(final K key) { - checkNotNull(key); - this.root = root.accept(new RemoveVisitor(), key); - } + /** + * Removes a key-value pair from the Verkle Trie. + * + * @param key The key to remove. + */ + @Override + public void remove(final K key) { + checkNotNull(key); + this.root = root.accept(new RemoveVisitor(), key); + } - /** - * Computes and returns the root hash of the Verkle Trie. - * - * @return The root hash of the Verkle Trie. - */ - @Override - public Bytes32 getRootHash() { - root = root.accept(new HashVisitor(), root.getPath()); - return root.getHash().get(); - } + /** + * Computes and returns the root hash of the Verkle Trie. + * + * @return The root hash of the Verkle Trie. + */ + @Override + public Bytes32 getRootHash() { + root = root.accept(new HashVisitor(), root.getPath()); + return root.getHash().get(); + } - /** - * Returns a string representation of the Verkle Trie. - * - * @return A string in the format "SimpleVerkleTrie[RootHash]". - */ - @Override - public String toString() { - return getClass().getSimpleName() + "[" + getRootHash() + "]"; - } + /** + * Returns a string representation of the Verkle Trie. + * + * @return A string in the format "SimpleVerkleTrie[RootHash]". + */ + @Override + public String toString() { + return getClass().getSimpleName() + "[" + getRootHash() + "]"; + } - /** - * Commits the Verkle Trie using the provided node updater. - * - * @param nodeUpdater The node updater for storing the changes in the Verkle - * Trie. - */ - @Override - public void commit(final NodeUpdater nodeUpdater) { - root = root.accept(new HashVisitor(), root.getPath()); - root = root.accept(new CommitVisitor(nodeUpdater), Bytes.EMPTY); - } + /** + * Commits the Verkle Trie using the provided node updater. + * + * @param nodeUpdater The node updater for storing the changes in the Verkle Trie. + */ + @Override + public void commit(final NodeUpdater nodeUpdater) { + root = root.accept(new HashVisitor(), root.getPath()); + root = root.accept(new CommitVisitor(nodeUpdater), Bytes.EMPTY); + } } diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/StoredNodeFactory.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/StoredNodeFactory.java deleted file mode 100644 index 72d9ed0..0000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/StoredNodeFactory.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.hyperledger.besu.ethereum.trie.verkle; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.rlp.RLP; - -/** - * A factory for creating Verkle Trie nodes based on stored data. - * - * @param The type of values stored in Verkle Trie nodes. - */ -public class StoredNodeFactory implements NodeFactory { - private NodeLoader nodeLoader; - private final Function valueDeserializer; - - /** - * Creates a new StoredNodeFactory with the given node loader and value - * deserializer. - * - * @param nodeLoader The loader for retrieving stored nodes. - * @param valueDeserializer The function to deserialize values from Bytes. - */ - public StoredNodeFactory(NodeLoader nodeLoader, Function valueDeserializer) { - this.nodeLoader = nodeLoader; - this.valueDeserializer = valueDeserializer; - } - - /** - * Retrieves a Verkle Trie node from stored data based on the location and hash. - * - * @param location The location of the node. - * @param hash The hash of the node. - * @return An optional containing the retrieved node, or an empty optional if - * the node is not found. - */ - public Optional> retrieve(final Bytes location, final Bytes32 hash) { - Optional optionalEncodedValues = nodeLoader.getNode(location, hash); - if (optionalEncodedValues.isEmpty()) { - return Optional.empty(); - } - Bytes encodedValues = optionalEncodedValues.get(); - List values = RLP.decodeToList(encodedValues, reader -> reader.readValue().copy()); - Bytes hashOrEmpty = values.get(0); - if (hashOrEmpty.isEmpty() && values.size() == 1) { // NullNode - return Optional.of(NullNode.instance()); - } - Bytes path = (Bytes) values.get(1); - if (hashOrEmpty.isEmpty() && values.size() > 1) { // LeafNode - V value = valueDeserializer.apply((Bytes) values.get(2)); - return Optional.of(createLeafNode(location, path, value)); - } - if (!hashOrEmpty.isEmpty()) { // BranchNode - Bytes32 savedHash = (Bytes32) hashOrEmpty; - return Optional.of(createBranchNode(location, savedHash, path)); - } - return Optional.empty(); // should not be here. - } - - /** - * Creates a BranchNode using the provided location, hash, and path. - * - * @param location The location of the BranchNode. - * @param hash The hash of the BranchNode. - * @param path The path associated with the BranchNode. - * @return A BranchNode instance. - */ - protected BranchNode createBranchNode(Bytes location, Bytes32 hash, Bytes path) { - int nChild = BranchNode.maxChild(); - ArrayList> children = new ArrayList>(nChild); - for (int i = 0; i < nChild; i++) { - Optional> child = retrieve(Bytes.concatenate(location, Bytes.of(i)), hash); - children.add(child.orElse(NullNode.instance())); - } - return new BranchNode(location, hash, path, children); - } - - /** - * Creates a LeafNode using the provided location, path, and value. - * - * @param location The location of the LeafNode. - * @param path The path associated with the LeafNode. - * @param value The value stored in the LeafNode. - * @return A LeafNode instance. - */ - protected LeafNode createLeafNode(Bytes location, Bytes path, V value) { - return new LeafNode(Optional.of(location), value, path); - } -} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/TrieKeyAdapter.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/TrieKeyAdapter.java deleted file mode 100644 index feb5ac5..0000000 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/TrieKeyAdapter.java +++ /dev/null @@ -1,163 +0,0 @@ -package org.hyperledger.besu.ethereum.trie.verkle; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; - -/** - * Utility class for generating keys used in a Verkle Trie. - * - *

- * The `TrieKeyAdapter` class provides methods for generating various keys, such - * as storage keys, code chunk keys, and header keys, used in a Verkle Trie - * structure. - */ -public class TrieKeyAdapter { - private final UInt256 VERSION_LEAF_KEY = UInt256.valueOf(0); - private final UInt256 BALANCE_LEAF_KEY = UInt256.valueOf(1); - private final UInt256 NONCE_LEAF_KEY = UInt256.valueOf(2); - private final UInt256 CODE_KECCAK_LEAF_KEY = UInt256.valueOf(3); - private final UInt256 CODE_SIZE_LEAF_KEY = UInt256.valueOf(4); - private final UInt256 HEADER_STORAGE_OFFSET = UInt256.valueOf(64); - private final UInt256 CODE_OFFSET = UInt256.valueOf(128); - private final UInt256 VERKLE_NODE_WIDTH = UInt256.valueOf(256); - private final UInt256 MAIN_STORAGE_OFFSET = UInt256.valueOf(256).pow(31); - - private Hasher hasher; - - /** - * Creates a TrieKeyAdapter with the provided hasher. - * - * @param hasher The hasher used for key generation. - */ - public TrieKeyAdapter(Hasher hasher) { - this.hasher = hasher; - } - - /** - * Swaps the last byte of the base key with a given subIndex. - * - * @param base The base key. - * @param subIndex The subIndex. - * @return The modified key. - */ - Bytes32 swapLastByte(Bytes32 base, UInt256 subIndex) { - Bytes32 key = (Bytes32) Bytes.concatenate(base.slice(0, 31), Bytes.of(subIndex.toBytes().get(31))); - return key; - } - - /** - * Generates the base key for a given address and treeIndex. - * - * @param address The address. - * @param treeIndex The tree index. - * @return The generated base key. - */ - Bytes32 baseKey(Bytes32 address, UInt256 treeIndex) { - int type_encoding = 2; - UInt256 encoding = UInt256.valueOf(type_encoding).add(VERKLE_NODE_WIDTH.multiply(UInt256.valueOf(16))); - - Bytes32[] input = new Bytes32[] { - Bytes32.rightPad(encoding.toBytes().slice(16, 16).reverse()), - Bytes32.rightPad(address.slice(0, 16)), - Bytes32.rightPad(address.slice(16, 16)), - Bytes32.rightPad(treeIndex.toBytes().slice(16, 16).reverse()), - Bytes32.rightPad(treeIndex.toBytes().slice(0, 16).reverse()) - }; - Bytes32 key = hasher.commit(input); - return key; - } - - /** - * Generates a storage key for a given address and storage key. - * - * @param address The address. - * @param storageKey The storage key. - * @return The generated storage key. - */ - public Bytes32 storageKey(Bytes32 address, UInt256 storageKey) { - UInt256 headerOffset = CODE_OFFSET.subtract(HEADER_STORAGE_OFFSET); - UInt256 offset = ((storageKey.compareTo(headerOffset) < 0) ? HEADER_STORAGE_OFFSET : MAIN_STORAGE_OFFSET); - UInt256 pos = offset.add(storageKey); - Bytes32 base = baseKey(address, pos.divide(VERKLE_NODE_WIDTH)); - Bytes32 key = swapLastByte(base, pos.mod(VERKLE_NODE_WIDTH)); - return key; - } - - /** - * Generates a code chunk key for a given address and chunkId. - * - * @param address The address. - * @param chunkId The chunk ID. - * @return The generated code chunk key. - */ - public Bytes32 codeChunkKey(Bytes32 address, UInt256 chunkId) { - UInt256 pos = CODE_OFFSET.add(chunkId); - Bytes32 base = baseKey(address, pos.divide(VERKLE_NODE_WIDTH)); - Bytes32 key = swapLastByte(base, pos.mod(VERKLE_NODE_WIDTH)); - return key; - } - - /** - * Generates a header key for a given address and leafKey. - * - * @param address The address. - * @param leafKey The leaf key. - * @return The generated header key. - */ - Bytes32 headerKey(Bytes32 address, UInt256 leafKey) { - Bytes32 base = baseKey(address, UInt256.valueOf(0)); - Bytes32 key = swapLastByte(base, leafKey); - return key; - } - - /** - * Generates a version key for a given address. - * - * @param address The address. - * @return The generated version key. - */ - public Bytes32 versionKey(Bytes32 address) { - return headerKey(address, VERSION_LEAF_KEY); - } - - /** - * Generates a balance key for a given address. - * - * @param address The address. - * @return The generated balance key. - */ - public Bytes32 balanceKey(Bytes32 address) { - return headerKey(address, BALANCE_LEAF_KEY); - } - - /** - * Generates a nonce key for a given address. - * - * @param address The address. - * @return The generated nonce key. - */ - public Bytes32 nonceKey(Bytes32 address) { - return headerKey(address, NONCE_LEAF_KEY); - } - - /** - * Generates a code Keccak key for a given address. - * - * @param address The address. - * @return The generated code Keccak key. - */ - public Bytes32 codeKeccakKey(Bytes32 address) { - return headerKey(address, CODE_KECCAK_LEAF_KEY); - } - - /** - * Generates a code size key for a given address. - * - * @param address The address. - * @return The generated code size key. - */ - public Bytes32 codeSizeKey(Bytes32 address) { - return headerKey(address, CODE_SIZE_LEAF_KEY); - } -} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/VerkleTrie.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/VerkleTrie.java index 03ff47c..c5f3d4e 100644 --- a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/VerkleTrie.java +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/VerkleTrie.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright Besu Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,10 +11,12 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 + * */ package org.hyperledger.besu.ethereum.trie.verkle; import java.util.Optional; + import org.apache.tuweni.bytes.Bytes32; /** Verkle Trie. */ @@ -27,7 +29,7 @@ public interface VerkleTrie { * @return an {@code Optional} of value mapped to the hash if it exists; otherwise empty */ Optional get(K key); - + /** * Updates the value mapped to the specified key, creating the mapping if one does not already * exist. @@ -57,4 +59,4 @@ public interface VerkleTrie { * @param nodeUpdater used to store the node values */ void commit(NodeUpdater nodeUpdater); -} \ No newline at end of file +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/adapter/TrieKeyAdapter.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/adapter/TrieKeyAdapter.java new file mode 100644 index 0000000..38de471 --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/adapter/TrieKeyAdapter.java @@ -0,0 +1,182 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.adapter; + +import org.hyperledger.besu.ethereum.trie.verkle.hasher.Hasher; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +/** + * Utility class for generating keys used in a Verkle Trie. + * + *

The `TrieKeyAdapter` class provides methods for generating various keys, such as storage keys, + * code chunk keys, and header keys, used in a Verkle Trie structure. + */ +public class TrieKeyAdapter { + private final UInt256 VERSION_LEAF_KEY = UInt256.valueOf(0); + private final UInt256 BALANCE_LEAF_KEY = UInt256.valueOf(1); + private final UInt256 NONCE_LEAF_KEY = UInt256.valueOf(2); + private final UInt256 CODE_KECCAK_LEAF_KEY = UInt256.valueOf(3); + private final UInt256 CODE_SIZE_LEAF_KEY = UInt256.valueOf(4); + private final UInt256 HEADER_STORAGE_OFFSET = UInt256.valueOf(64); + private final UInt256 CODE_OFFSET = UInt256.valueOf(128); + private final UInt256 VERKLE_NODE_WIDTH = UInt256.valueOf(256); + private final UInt256 MAIN_STORAGE_OFFSET = UInt256.valueOf(256).pow(31); + + private final Hasher hasher; + + /** + * Creates a TrieKeyAdapter with the provided hasher. + * + * @param hasher The hasher used for key generation. + */ + public TrieKeyAdapter(Hasher hasher) { + this.hasher = hasher; + } + + /** + * Swaps the last byte of the base key with a given subIndex. + * + * @param base The base key. + * @param subIndex The subIndex. + * @return The modified key. + */ + Bytes32 swapLastByte(Bytes32 base, UInt256 subIndex) { + Bytes32 key = + (Bytes32) Bytes.concatenate(base.slice(0, 31), Bytes.of(subIndex.toBytes().get(31))); + return key; + } + + /** + * Generates the base key for a given address and treeIndex. + * + * @param address The address. + * @param treeIndex The tree index. + * @return The generated base key. + */ + Bytes32 baseKey(Bytes32 address, UInt256 treeIndex) { + int type_encoding = 2; + UInt256 encoding = + UInt256.valueOf(type_encoding).add(VERKLE_NODE_WIDTH.multiply(UInt256.valueOf(16))); + + Bytes32[] input = + new Bytes32[] { + Bytes32.rightPad(encoding.toBytes().slice(16, 16).reverse()), + Bytes32.rightPad(address.slice(0, 16)), + Bytes32.rightPad(address.slice(16, 16)), + Bytes32.rightPad(treeIndex.toBytes().slice(16, 16).reverse()), + Bytes32.rightPad(treeIndex.toBytes().slice(0, 16).reverse()) + }; + Bytes32 key = hasher.commit(input); + return key; + } + + /** + * Generates a storage key for a given address and storage key. + * + * @param address The address. + * @param storageKey The storage key. + * @return The generated storage key. + */ + public Bytes32 storageKey(Bytes32 address, UInt256 storageKey) { + UInt256 headerOffset = CODE_OFFSET.subtract(HEADER_STORAGE_OFFSET); + UInt256 offset = + ((storageKey.compareTo(headerOffset) < 0) ? HEADER_STORAGE_OFFSET : MAIN_STORAGE_OFFSET); + UInt256 pos = offset.add(storageKey); + Bytes32 base = baseKey(address, pos.divide(VERKLE_NODE_WIDTH)); + Bytes32 key = swapLastByte(base, pos.mod(VERKLE_NODE_WIDTH)); + return key; + } + + /** + * Generates a code chunk key for a given address and chunkId. + * + * @param address The address. + * @param chunkId The chunk ID. + * @return The generated code chunk key. + */ + public Bytes32 codeChunkKey(Bytes32 address, UInt256 chunkId) { + UInt256 pos = CODE_OFFSET.add(chunkId); + Bytes32 base = baseKey(address, pos.divide(VERKLE_NODE_WIDTH)); + Bytes32 key = swapLastByte(base, pos.mod(VERKLE_NODE_WIDTH)); + return key; + } + + /** + * Generates a header key for a given address and leafKey. + * + * @param address The address. + * @param leafKey The leaf key. + * @return The generated header key. + */ + Bytes32 headerKey(Bytes32 address, UInt256 leafKey) { + Bytes32 base = baseKey(address, UInt256.valueOf(0)); + Bytes32 key = swapLastByte(base, leafKey); + return key; + } + + /** + * Generates a version key for a given address. + * + * @param address The address. + * @return The generated version key. + */ + public Bytes32 versionKey(Bytes32 address) { + return headerKey(address, VERSION_LEAF_KEY); + } + + /** + * Generates a balance key for a given address. + * + * @param address The address. + * @return The generated balance key. + */ + public Bytes32 balanceKey(Bytes32 address) { + return headerKey(address, BALANCE_LEAF_KEY); + } + + /** + * Generates a nonce key for a given address. + * + * @param address The address. + * @return The generated nonce key. + */ + public Bytes32 nonceKey(Bytes32 address) { + return headerKey(address, NONCE_LEAF_KEY); + } + + /** + * Generates a code Keccak key for a given address. + * + * @param address The address. + * @return The generated code Keccak key. + */ + public Bytes32 codeKeccakKey(Bytes32 address) { + return headerKey(address, CODE_KECCAK_LEAF_KEY); + } + + /** + * Generates a code size key for a given address. + * + * @param address The address. + * @return The generated code size key. + */ + public Bytes32 codeSizeKey(Bytes32 address) { + return headerKey(address, CODE_SIZE_LEAF_KEY); + } +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/factory/NodeFactory.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/factory/NodeFactory.java new file mode 100644 index 0000000..3fef9ed --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/factory/NodeFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.factory; + +import org.hyperledger.besu.ethereum.trie.verkle.node.Node; + +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** + * An interface representing a factory for creating nodes in the Verkle Trie. + * + * @param The type of the nodes to be created. + */ +public interface NodeFactory { + + /** + * Retrieve a node with the given location and hash. + * + * @param location The location of the node. + * @param hash The hash of the node. + * @return An optional containing the retrieved node, or empty if not found. + */ + Optional> retrieve(final Bytes location, final Bytes32 hash); +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/factory/StoredNodeFactory.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/factory/StoredNodeFactory.java new file mode 100644 index 0000000..bf7da09 --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/factory/StoredNodeFactory.java @@ -0,0 +1,114 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.factory; + +import org.hyperledger.besu.ethereum.trie.verkle.NodeLoader; +import org.hyperledger.besu.ethereum.trie.verkle.node.BranchNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.LeafNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.Node; +import org.hyperledger.besu.ethereum.trie.verkle.node.NullNode; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.rlp.RLP; + +/** + * A factory for creating Verkle Trie nodes based on stored data. + * + * @param The type of values stored in Verkle Trie nodes. + */ +public class StoredNodeFactory implements NodeFactory { + private final NodeLoader nodeLoader; + private final Function valueDeserializer; + + /** + * Creates a new StoredNodeFactory with the given node loader and value deserializer. + * + * @param nodeLoader The loader for retrieving stored nodes. + * @param valueDeserializer The function to deserialize values from Bytes. + */ + public StoredNodeFactory(NodeLoader nodeLoader, Function valueDeserializer) { + this.nodeLoader = nodeLoader; + this.valueDeserializer = valueDeserializer; + } + + /** + * Retrieves a Verkle Trie node from stored data based on the location and hash. + * + * @param location The location of the node. + * @param hash The hash of the node. + * @return An optional containing the retrieved node, or an empty optional if the node is not + * found. + */ + @Override + public Optional> retrieve(final Bytes location, final Bytes32 hash) { + Optional optionalEncodedValues = nodeLoader.getNode(location, hash); + if (optionalEncodedValues.isEmpty()) { + return Optional.empty(); + } + Bytes encodedValues = optionalEncodedValues.get(); + List values = RLP.decodeToList(encodedValues, reader -> reader.readValue().copy()); + Bytes hashOrEmpty = values.get(0); + if (hashOrEmpty.isEmpty() && values.size() == 1) { // NullNode + return Optional.of(NullNode.instance()); + } + Bytes path = values.get(1); + if (hashOrEmpty.isEmpty() && values.size() > 1) { // LeafNode + V value = valueDeserializer.apply(values.get(2)); + return Optional.of(createLeafNode(location, path, value)); + } + if (!hashOrEmpty.isEmpty()) { // BranchNode + Bytes32 savedHash = (Bytes32) hashOrEmpty; + return Optional.of(createBranchNode(location, savedHash, path)); + } + return Optional.empty(); // should not be here. + } + + /** + * Creates a BranchNode using the provided location, hash, and path. + * + * @param location The location of the BranchNode. + * @param hash The hash of the BranchNode. + * @param path The path associated with the BranchNode. + * @return A BranchNode instance. + */ + protected BranchNode createBranchNode(Bytes location, Bytes32 hash, Bytes path) { + int nChild = BranchNode.maxChild(); + ArrayList> children = new ArrayList>(nChild); + for (int i = 0; i < nChild; i++) { + Optional> child = retrieve(Bytes.concatenate(location, Bytes.of(i)), hash); + children.add(child.orElse(NullNode.instance())); + } + return new BranchNode(location, hash, path, children); + } + + /** + * Creates a LeafNode using the provided location, path, and value. + * + * @param location The location of the LeafNode. + * @param path The path associated with the LeafNode. + * @param value The value stored in the LeafNode. + * @return A LeafNode instance. + */ + protected LeafNode createLeafNode(Bytes location, Bytes path, V value) { + return new LeafNode(Optional.of(location), value, path); + } +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/Hasher.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/Hasher.java new file mode 100644 index 0000000..7f6ef1c --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/Hasher.java @@ -0,0 +1,36 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.hasher; + +import org.apache.tuweni.bytes.Bytes32; + +/** + * Defines an interface for a Verkle Trie node hashing strategy. + * + * @param The type of values to be hashed. + */ +public interface Hasher { + + /** + * Calculates the commitment hash for an array of inputs. + * + * @param inputs An array of values to be hashed. + * @return The commitment hash calculated from the inputs. + */ + public Bytes32 commit(V[] inputs); + + // public Bytes32 commit_sparse(V[] input, int[] index) +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/IPAHasher.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/IPAHasher.java new file mode 100644 index 0000000..ec29f89 --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/IPAHasher.java @@ -0,0 +1,42 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.hasher; + +import org.hyperledger.besu.nativelib.ipamultipoint.LibIpaMultipoint; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** + * A class responsible for hashing an array of Bytes32 using the IPA (Inter-Participant Agreement) + * hashing algorithm. + * + *

This class implements the Hasher interface and provides a method to commit multiple Bytes32 + * inputs using the IPA hashing algorithm. + */ +public class IPAHasher implements Hasher { + /** + * Commits an array of Bytes32 using the IPA hashing algorithm. + * + * @param inputs An array of Bytes32 inputs to be hashed and committed. + * @return The resulting hash as a Bytes32. + */ + @Override + public Bytes32 commit(Bytes32[] inputs) { + Bytes input_serialized = Bytes.concatenate(inputs); + return Bytes32.wrap(LibIpaMultipoint.commit(input_serialized.toArray())); + } +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/SHA256Hasher.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/SHA256Hasher.java new file mode 100644 index 0000000..fd69728 --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/hasher/SHA256Hasher.java @@ -0,0 +1,51 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.hasher; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** + * A class responsible for hashing an array of Bytes32 using the SHA-256 (Secure Hash Algorithm + * 256-bit) hashing algorithm. + * + *

This class implements the Hasher interface and provides a method to commit multiple Bytes32 + * inputs using the SHA-256 hashing algorithm. It utilizes the Java built-in MessageDigest for + * SHA-256. + */ +public class SHA256Hasher implements Hasher { + /** + * Commits an array of Bytes32 using the SHA-256 hashing algorithm provided by the MessageDigest. + * + * @param inputs An array of Bytes32 inputs to be hashed and committed. + * @return The resulting hash as a Bytes32. + */ + @Override + public Bytes32 commit(Bytes32[] inputs) { + Bytes32 out; + Bytes inputs_serialized = Bytes.concatenate(inputs); + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + out = Bytes32.wrap(sha256.digest(inputs_serialized.toArray())); + } catch (NoSuchAlgorithmException e) { + out = Bytes32.ZERO; + } + return out; + } +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/BranchNode.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/BranchNode.java new file mode 100644 index 0000000..e1baf84 --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/BranchNode.java @@ -0,0 +1,281 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.node; + +import org.hyperledger.besu.ethereum.trie.verkle.visitor.NodeVisitor; +import org.hyperledger.besu.ethereum.trie.verkle.visitor.PathNodeVisitor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.rlp.RLP; +import org.apache.tuweni.rlp.RLPWriter; + +/** + * Represents a branch node in the Verkle Trie. + * + * @param The type of the node's value. + */ +public class BranchNode implements Node { + private final Optional location; // Location in the tree + private final Bytes path; // Extension path + private final Optional hash; // Vector commitment of children's commitments + private Optional encodedValue = Optional.empty(); // Encoded value + private final List> children; // List of children nodes + + private boolean dirty = true; // not persisted + + /** + * Constructs a new BranchNode with location, hash, path, and children. + * + * @param location The location in the tree. + * @param hash The vector commitment of children's commitments. + * @param path The extension path. + * @param children The list of children nodes. + */ + public BranchNode( + final Bytes location, final Bytes32 hash, final Bytes path, final List> children) { + assert (children.size() == maxChild()); + this.location = Optional.of(location); + this.hash = Optional.of(hash); + this.path = path; + this.children = children; + } + + /** + * Constructs a new BranchNode with optional location, optional hash, path, and children. + * + * @param location The optional location in the tree. + * @param hash The optional vector commitment of children's commitments. + * @param path The extension path. + * @param children The list of children nodes. + */ + public BranchNode( + final Optional location, + final Optional hash, + final Bytes path, + final List> children) { + assert (children.size() == maxChild()); + this.location = location; + this.hash = hash; + this.path = path; + this.children = children; + } + + /** + * Constructs a new BranchNode with optional location, path, and children. + * + * @param location The optional location in the tree. + * @param path The extension path. + * @param children The list of children nodes. + */ + public BranchNode( + final Optional location, final Bytes path, final List> children) { + assert (children.size() == maxChild()); + this.location = location; + this.path = path; + this.children = children; + hash = Optional.empty(); + } + + /** + * Constructs a new BranchNode with optional location and path, initializing children to + * NullNodes. + * + * @param location The optional location in the tree. + * @param path The extension path. + */ + public BranchNode(final Optional location, final Bytes path) { + this.location = location; + this.path = path; + this.children = new ArrayList<>(); + for (int i = 0; i < maxChild(); i++) { + children.add(NullNode.instance()); + } + hash = Optional.of(EMPTY_HASH); + } + + /** + * Get the maximum number of children nodes (256 for byte indexes). + * + * @return The maximum number of children nodes. + */ + public static int maxChild() { + return 256; + } + + /** + * Accepts a visitor for path-based operations on the node. + * + * @param visitor The path node visitor. + * @param path The path associated with a node. + * @return The result of the visitor's operation. + */ + @Override + public Node accept(PathNodeVisitor visitor, Bytes path) { + return visitor.visit(this, path); + } + + /** + * Accepts a visitor for generic node operations. + * + * @param visitor The node visitor. + * @return The result of the visitor's operation. + */ + @Override + public Node accept(final NodeVisitor visitor) { + return visitor.visit(this); + } + + /** + * Get the child node at a specified index. + * + * @param childIndex The index of the child node. + * @return The child node. + */ + public Node child(final byte childIndex) { + return children.get(Byte.toUnsignedInt(childIndex)); + } + + /** + * Replaces the child node at a specified index with a new node. + * + * @param index The index of the child node to replace. + * @param childNode The new child node. + */ + public void replaceChild(final byte index, final Node childNode) { + children.set(Byte.toUnsignedInt(index), childNode); + } + + /** + * Get the vector commitment of children's commitments. + * + * @return An optional containing the vector commitment. + */ + @Override + public Optional getHash() { + return hash; + } + + /** + * Replace the vector commitment with a new one. + * + * @param hash The new vector commitment to set. + * @return A new BranchNode with the updated vector commitment. + */ + public Node replaceHash(Bytes32 hash) { + return new BranchNode(location, Optional.of(hash), path, children); + } + + /** + * Get the location in the tree. + * + * @return An optional containing the location if available. + */ + @Override + public Optional getLocation() { + return location; + } + + /** + * Get the extension path of the node. + * + * @return The extension path. + */ + @Override + public Bytes getPath() { + return path; + } + + /** + * Replace the extension path with a new one. + * + * @param path The new extension path to set. + * @return A new BranchNode with the updated extension path. + */ + @Override + public Node replacePath(Bytes path) { + BranchNode updatedNode = new BranchNode(location, path, children); + return updatedNode; + } + + /** + * Get the RLP-encoded value of the node. + * + * @return The RLP-encoded value. + */ + @Override + public Bytes getEncodedValue() { + if (encodedValue.isPresent()) { + return encodedValue.get(); + } + List values = Arrays.asList((Bytes) getHash().get(), getPath()); + Bytes result = RLP.encodeList(values, RLPWriter::writeValue); + this.encodedValue = Optional.of(result); + return result; + } + + /** + * Get the list of children nodes. + * + * @return The list of children nodes. + */ + @Override + public List> getChildren() { + return children; + } + + /** Marks the node as dirty, indicating that it needs to be persisted. */ + @Override + public void markDirty() { + dirty = true; + } + + /** + * Checks if the node is dirty, indicating that it needs to be persisted. + * + * @return `true` if the node is marked as dirty, `false` otherwise. + */ + @Override + public boolean isDirty() { + return dirty; + } + + /** + * Generates a string representation of the branch node and its children. + * + * @return A string representing the branch node and its children. + */ + @Override + public String print() { + final StringBuilder builder = new StringBuilder(); + builder.append("Branch:"); + for (int i = 0; i < maxChild(); i++) { + final Node child = child((byte) i); + if (!Objects.equals(child, NullNode.instance())) { + final String branchLabel = "[" + Integer.toHexString(i) + "] "; + final String childRep = child.print().replaceAll("\n\t", "\n\t\t"); + builder.append("\n\t").append(branchLabel).append(childRep); + } + } + return builder.toString(); + } +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/LeafNode.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/LeafNode.java new file mode 100644 index 0000000..ce6fa40 --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/LeafNode.java @@ -0,0 +1,233 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.node; + +import org.hyperledger.besu.ethereum.trie.verkle.visitor.NodeVisitor; +import org.hyperledger.besu.ethereum.trie.verkle.visitor.PathNodeVisitor; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.rlp.RLP; +import org.apache.tuweni.rlp.RLPWriter; + +/** + * Represents a leaf node in the Verkle Trie. + * + * @param The type of the node's value. + */ +public class LeafNode implements Node { + private final Optional location; // Location in the tree + protected final V value; // Value associated with the node + private final Bytes path; // Extension path + private final Optional hash; // Hash of the node + private Optional encodedValue = Optional.empty(); // Encoded value + private final Function valueSerializer; // Serializer function for the value + private boolean dirty = true; // not persisted + + /** + * Constructs a new LeafNode with optional location, value, path, and optional hash. + * + * @param location The location of the node in the tree (Optional). + * @param value The value associated with the node. + * @param path The path or key of the node. + * @param hash The hash of the node (Optional). + */ + public LeafNode( + final Optional location, + final V value, + final Bytes path, + final Optional hash) { + this.location = location; + this.value = value; + this.path = path; + this.hash = hash; + this.valueSerializer = val -> (Bytes) val; + } + + /** + * Constructs a new LeafNode with a provided optional location, value, and path. + * + * @param location The location of the node in the tree (Optional). + * @param value The value associated with the node. + * @param path The path or key of the node. + */ + public LeafNode(final Optional location, final V value, final Bytes path) { + this.location = location; + this.path = path; + this.value = value; + this.hash = Optional.empty(); + this.valueSerializer = val -> (Bytes) val; + } + + /** + * Constructs a new LeafNode with a value and path. + * + * @param value The value associated with the node. + * @param path The path or key of the node. + */ + public LeafNode(final V value, final Bytes path) { + location = Optional.empty(); + this.value = value; + this.path = path; + hash = Optional.empty(); + this.valueSerializer = val -> (Bytes) val; + } + + /** + * Accepts a visitor for path-based operations on the node. + * + * @param visitor The path node visitor. + * @param path The path associated with a node. + * @return The result of the visitor's operation. + */ + @Override + public Node accept(final PathNodeVisitor visitor, final Bytes path) { + return visitor.visit(this, path); + } + + /** + * Accepts a visitor for generic node operations. + * + * @param visitor The node visitor. + * @return The result of the visitor's operation. + */ + @Override + public Node accept(final NodeVisitor visitor) { + return visitor.visit(this); + } + + /** + * Get the value associated with the node. + * + * @return An optional containing the value of the node if available. + */ + @Override + public Optional getValue() { + return Optional.ofNullable(value); + } + + /** + * Get the location of the node. + * + * @return An optional containing the location of the node if available. + */ + @Override + public Optional getLocation() { + return location; + } + + /** + * Get the path or key of the node. + * + * @return The path of the node. + */ + @Override + public Bytes getPath() { + return path; + } + + /** + * Get the children of the node. A leaf node does not have children, so this method throws an + * UnsupportedOperationException. + * + * @return The list of children nodes (unsupported operation). + * @throws UnsupportedOperationException if called on a leaf node. + */ + @Override + public List> getChildren() { + throw new UnsupportedOperationException("LeafNode does not have children."); + } + + /** + * Get the hash of the node if available. + * + * @return An optional containing the hash of the node if available. + */ + @Override + public Optional getHash() { + return hash; + } + + /** + * Replace the hash of the node. + * + * @param hash The new hash to set. + * @return A new node with the updated hash. + */ + public Node replaceHash(Bytes32 hash) { + return new LeafNode(location, value, path, Optional.of(hash)); + } + + /** + * Replace the path of the node. + * + * @param path The new path to set. + * @return A new node with the updated path. + */ + @Override + public Node replacePath(Bytes path) { + return new LeafNode(location, value, path, hash); + } + + /** + * Get the RLP-encoded value of the node. + * + * @return The RLP-encoded value. + */ + @Override + public Bytes getEncodedValue() { + if (encodedValue.isPresent()) { + return encodedValue.get(); + } + Bytes encodedVal = + getValue().isPresent() ? valueSerializer.apply(getValue().get()) : Bytes.EMPTY; + List values = Arrays.asList(Bytes.EMPTY, getPath(), encodedVal); + Bytes result = RLP.encodeList(values, RLPWriter::writeValue); + this.encodedValue = Optional.of(result); + return result; + } + + /** Marks the node as needing to be persisted. */ + @Override + public void markDirty() { + dirty = true; + } + + /** + * Checks if the node needs to be persisted. + * + * @return True if the node needs to be persisted. + */ + @Override + public boolean isDirty() { + return dirty; + } + + /** + * Get a string representation of the node. + * + * @return A string representation of the node. + */ + @Override + public String print() { + return "Leaf:" + getValue().map(Object::toString).orElse("empty"); + } +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/Node.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/Node.java new file mode 100644 index 0000000..7e8d544 --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/Node.java @@ -0,0 +1,136 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.node; + +import org.hyperledger.besu.ethereum.trie.verkle.visitor.NodeVisitor; +import org.hyperledger.besu.ethereum.trie.verkle.visitor.PathNodeVisitor; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** + * An interface representing a node in the Verkle Trie. + * + * @param The type of the node's value. + */ +public interface Node { + + /** A constant representing an empty hash value. */ + Bytes32 EMPTY_HASH = Bytes32.ZERO; + + /** + * Accept a visitor to perform operations on the node based on a provided path. + * + * @param visitor The visitor to accept. + * @param path The path associated with a node. + * @return The result of visitor's operation. + */ + Node accept(PathNodeVisitor visitor, Bytes path); + + /** + * Accept a visitor to perform operations on the node. + * + * @param visitor The visitor to accept. + * @return The result of the visitor's operation. + */ + Node accept(NodeVisitor visitor); + + /** + * Get the path associated with the node. + * + * @return The path of the node. + */ + default Bytes getPath() { + return Bytes.EMPTY; + } + ; + + /** + * Get the location of the node. + * + * @return An optional containing the location of the node if available. + */ + default Optional getLocation() { + return Optional.empty(); + } + + /** + * Get the value associated with the node. + * + * @return An optional containing the value of the node if available. + */ + default Optional getValue() { + return Optional.empty(); + } + ; + + /** + * Get the hash associated with the node. + * + * @return An optional containing the hash of the node if available. + */ + default Optional getHash() { + return Optional.empty(); + } + ; + + /** + * Replace the path of the node. + * + * @param path The new path to set. + * @return A new node with the updated path. + */ + Node replacePath(Bytes path); + + /** + * Get the encoded value of the node. + * + * @return The encoded value of the node. + */ + default Bytes getEncodedValue() { + return Bytes.EMPTY; + } + + /** + * Get the children nodes of this node. + * + * @return A list of children nodes. + */ + default List> getChildren() { + return Collections.emptyList(); + } + + /** Marks the node as needs to be persisted */ + void markDirty(); + + /** + * Is this node not persisted and needs to be? + * + * @return True if the node needs to be persisted. + */ + boolean isDirty(); + + /** + * Get a string representation of the node. + * + * @return A string representation of the node. + */ + String print(); +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/NullNode.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/NullNode.java new file mode 100644 index 0000000..17ee90d --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/node/NullNode.java @@ -0,0 +1,127 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.node; + +import org.hyperledger.besu.ethereum.trie.verkle.visitor.NodeVisitor; +import org.hyperledger.besu.ethereum.trie.verkle.visitor.PathNodeVisitor; + +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** + * A special node representing a null or empty node in the Verkle Trie. + * + *

The `NullNode` class serves as a placeholder for non-existent nodes in the Verkle Trie + * structure. It implements the Node interface and represents a node that contains no information or + * value. + */ +public class NullNode implements Node { + @SuppressWarnings("rawtypes") + private static final NullNode instance = new NullNode(); + + /** + * Constructs a new `NullNode`. This constructor is protected to ensure that `NullNode` instances + * are only created as singletons. + */ + protected NullNode() {} + + /** + * Gets the shared instance of the `NullNode`. + * + * @param The type of the node's value. + * @return The shared `NullNode` instance. + */ + @SuppressWarnings("unchecked") + public static NullNode instance() { + return instance; + } + + /** + * Accepts a visitor for path-based operations on the node. + * + * @param visitor The path node visitor. + * @param path The path associated with a node. + * @return The result of the visitor's operation. + */ + @Override + public Node accept(final PathNodeVisitor visitor, final Bytes path) { + return visitor.visit(this, path); + } + + /** + * Accepts a visitor for generic node operations. + * + * @param visitor The node visitor. + * @return The result of the visitor's operation. + */ + @Override + public Node accept(final NodeVisitor visitor) { + return visitor.visit(this); + } + + /** + * Get the hash associated with the `NullNode`. + * + * @return An optional containing the empty hash. + */ + @Override + public Optional getHash() { + return Optional.of(EMPTY_HASH); + } + + /** + * Replace the path of the `NullNode` with a new one. + * + * @param path The new path to set. + * @return The `NullNode` itself, as it does not have a path to replace. + */ + @Override + public Node replacePath(final Bytes path) { + return this; + } + + /** + * Get a string representation of the `NullNode`. + * + * @return A string representation indicating that it is a "NULL" node. + */ + @Override + public String print() { + return "[NULL]"; + } + + /** + * Check if the `NullNode` is marked as dirty (needing to be persisted). + * + * @return `false` since a `NullNode` does not require persistence. + */ + @Override + public boolean isDirty() { + return false; + } + + /** + * Mark the `NullNode` as dirty (not used, no operation). + * + *

This method intentionally does nothing. + */ + @Override + public void markDirty() { + // do nothing + } +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/CommitVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/CommitVisitor.java new file mode 100644 index 0000000..29127dd --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/CommitVisitor.java @@ -0,0 +1,95 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.visitor; + +import org.hyperledger.besu.ethereum.trie.verkle.NodeUpdater; +import org.hyperledger.besu.ethereum.trie.verkle.node.BranchNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.LeafNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.Node; +import org.hyperledger.besu.ethereum.trie.verkle.node.NullNode; + +import org.apache.tuweni.bytes.Bytes; + +/** + * A visitor class responsible for committing changes to nodes in a Trie tree. + * + *

It iterates through the nodes and stores the changes in the Trie structure. + * + * @param The type of node values. + */ +public class CommitVisitor implements PathNodeVisitor { + + /** The NodeUpdater used to store changes in the Trie structure. */ + protected final NodeUpdater nodeUpdater; + + /** + * Constructs a CommitVisitor with a provided NodeUpdater. + * + * @param nodeUpdater The NodeUpdater used to store changes in the Trie structure. + */ + public CommitVisitor(final NodeUpdater nodeUpdater) { + this.nodeUpdater = nodeUpdater; + } + + /** + * Visits a BranchNode to commit any changes in the node and its children. + * + * @param branchNode The BranchNode being visited. + * @param location The location in the Trie tree. + * @return The visited BranchNode. + */ + @Override + public Node visit(final BranchNode branchNode, final Bytes location) { + if (!branchNode.isDirty()) { + return branchNode; + } + for (int i = 0; i < BranchNode.maxChild(); ++i) { + Bytes index = Bytes.of(i); + final Node child = branchNode.child((byte) i); + child.accept(this, Bytes.concatenate(location, index)); + } + nodeUpdater.store(location, null, branchNode.getEncodedValue()); + return branchNode; + } + + /** + * Visits a LeafNode to commit any changes in the node. + * + * @param leafNode The LeafNode being visited. + * @param location The location in the Trie tree. + * @return The visited LeafNode. + */ + @Override + public Node visit(final LeafNode leafNode, final Bytes location) { + if (!leafNode.isDirty()) { + return leafNode; + } + nodeUpdater.store(location, null, leafNode.getEncodedValue()); + return leafNode; + } + + /** + * Visits a NullNode, indicating no changes to commit. + * + * @param nullNode The NullNode being visited. + * @param location The location in the Trie tree. + * @return The NullNode indicating no changes. + */ + @Override + public Node visit(final NullNode nullNode, final Bytes location) { + return nullNode; + } +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/GetVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/GetVisitor.java new file mode 100644 index 0000000..df95df2 --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/GetVisitor.java @@ -0,0 +1,81 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.visitor; + +import org.hyperledger.besu.ethereum.trie.verkle.node.BranchNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.LeafNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.Node; +import org.hyperledger.besu.ethereum.trie.verkle.node.NullNode; + +import org.apache.tuweni.bytes.Bytes; + +/** + * Class representing a visitor for traversing nodes in a Trie tree to find a node based on a path. + * + * @param The type of node values. + */ +public class GetVisitor implements PathNodeVisitor { + private final Node NULL_NODE_RESULT = NullNode.instance(); + + /** + * Visits a BranchNode to determine the node matching a given path. + * + * @param branchNode The BranchNode being visited. + * @param path The path to search in the tree. + * @return The matching node or NULL_NODE_RESULT if not found. + */ + @Override + public Node visit(final BranchNode branchNode, final Bytes path) { + final Bytes nodePath = branchNode.getPath(); + final Bytes commonPath = nodePath.commonPrefix(path); + if (commonPath.compareTo(nodePath) != 0) { + // path diverges before the end of the extension, so it cannot match + return NULL_NODE_RESULT; + } + final Bytes pathSuffix = path.slice(commonPath.size()); + final byte childIndex = pathSuffix.get(0); + return branchNode.child(childIndex).accept(this, pathSuffix.slice(1)); + } + + /** + * Visits a LeafNode to determine the matching node based on a given path. + * + * @param leafNode The LeafNode being visited. + * @param path The path to search in the tree. + * @return The matching node or NULL_NODE_RESULT if not found. + */ + @Override + public Node visit(LeafNode leafNode, Bytes path) { + final Bytes leafPath = leafNode.getPath(); + final Bytes commonPath = leafPath.commonPrefix(path); + if (commonPath.compareTo(leafPath) != 0) { + return NULL_NODE_RESULT; + } + return leafNode; + } + + /** + * Visits a NullNode to determine the matching node based on a given path. + * + * @param nullNode The NullNode being visited. + * @param path The path to search in the tree. + * @return The NULL_NODE_RESULT since NullNode represents a missing node on the path. + */ + @Override + public Node visit(NullNode nullNode, Bytes path) { + return NULL_NODE_RESULT; + } +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/HashVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/HashVisitor.java new file mode 100644 index 0000000..8d82cb3 --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/HashVisitor.java @@ -0,0 +1,247 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.visitor; + +import org.hyperledger.besu.ethereum.trie.verkle.hasher.Hasher; +import org.hyperledger.besu.ethereum.trie.verkle.hasher.IPAHasher; +import org.hyperledger.besu.ethereum.trie.verkle.node.BranchNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.LeafNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.Node; +import org.hyperledger.besu.ethereum.trie.verkle.node.NullNode; + +import java.util.Arrays; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** + * A visitor class for hashing operations on Verkle Trie nodes. + * + * @param The type of the node's value. + */ +public class HashVisitor implements PathNodeVisitor { + Hasher hasher = new IPAHasher(); + + /** + * Visits a branch node, computes its hash, and returns a new branch node with the updated hash. + * + * @param branchNode The branch node to visit. + * @param location The location associated with the branch node. + * @return A new branch node with the updated hash. + */ + @Override + public Node visit(BranchNode branchNode, Bytes location) { + if (!branchNode.isDirty() && branchNode.getHash().isPresent()) { + return branchNode; + } + Bytes32 baseHash; + if (location.size() == 31) { // branch with leaf nodes as children + baseHash = hashValues(branchNode, location); + } else { // Regular internal node + int size = BranchNode.maxChild(); + Bytes32[] childCommits = new Bytes32[size]; + for (int i = 0; i < size; i++) { + byte index = (byte) i; + Node child = branchNode.child(index); + Bytes childPath = child.getPath(); + Bytes nextLocation = Bytes.concatenate(location, Bytes.of(index), childPath); + Node updatedChild = child.accept(this, nextLocation); + branchNode.replaceChild(index, updatedChild); + childCommits[i] = updatedChild.getHash().get(); + } + baseHash = hasher.commit(childCommits); + } + return branchNode.replaceHash(hashExtension(branchNode.getPath(), baseHash)); + } + + /** + * Visits a leaf node, computes its hash, and returns a new leaf node with the updated hash. + * + * @param leafNode The leaf node to visit. + * @param location The location associated with the leaf node. + * @return A new leaf node with the updated hash. + */ + @Override + public Node visit(LeafNode leafNode, Bytes location) { + Bytes path = leafNode.getPath(); + if (path.size() == 0) { + // LeafNode without extension should not be visited + return leafNode; + } + // Remove last byte from location to get the stem + Bytes stem = location.slice(0, location.size() - 1); + Optional value = leafNode.getValue(); + byte index = path.get(path.size() - 1); + Bytes32 baseHash = hashStemExtensionOne(stem, value, index); + return leafNode.replaceHash(hashExtension(path, baseHash)); + } + + /** + * Visits a null node and returns the same null node. + * + * @param nullNode The null node to visit. + * @param location The location associated with the null node. + * @return The same null node. + */ + @Override + public Node visit(NullNode nullNode, Bytes location) { + return nullNode; + } + + // Should use commit_one. For now, using commit. + /** + * Computes the hash of a single value with a given index. + * + *

Should use commit_one. For now, using commit. + * + * @param value The value to hash. + * @param index The index associated with the value. + * @return The hash of the value. + */ + Bytes32 hashOne(Bytes32 value, byte index) { + int idx = Byte.toUnsignedInt(index); + Bytes32[] values = new Bytes32[idx + 1]; + Arrays.fill(values, Bytes32.ZERO); + values[idx] = value; + return hasher.commit(values); + } + + /** + * Computes the hash of a branch node extension. + * + * @param path The path associated with the extension. + * @param baseHash The base hash. + * @return The hash of the branch node extension. + */ + Bytes32 hashExtension(Bytes path, Bytes32 baseHash) { + Bytes revPath = path.reverse(); + Bytes32 hash = baseHash; + for (int i = 0; i < path.size(); i++) { + hash = hashOne(hash, revPath.get(i)); + } + return hash; + } + + /** + * Computes the hash of a branch node stem extension. + * + * @param stem The stem of the branch node. + * @param leftValues The left values associated with the stem. + * @param rightValues The right values associated with the stem. + * @return The hash of the stem extension. + */ + Bytes32 hashStemExtension(Bytes stem, Bytes32[] leftValues, Bytes32[] rightValues) { + Bytes32[] extensionHashes = new Bytes32[4]; + extensionHashes[0] = Bytes32.rightPad(Bytes.of((byte) 1).reverse()); // extension marker + extensionHashes[1] = Bytes32.rightPad(stem); + extensionHashes[2] = hasher.commit(leftValues); + extensionHashes[3] = hasher.commit(rightValues); + return hasher.commit(extensionHashes); + } + + // Should use commit_sparse to commit low and high values + /** + * Computes the hash of a branch node stem extension with a single value. + * + *

Should use commit_sparse to commit low and high values + * + * @param stem The stem of the branch node. + * @param value The value associated with the stem extension. + * @param index The index associated with the value. + * @return The hash of the stem extension with a single value. + */ + // + Bytes32 hashStemExtensionOne(Bytes stem, Optional value, byte index) { + int idx = Byte.toUnsignedInt(index); + Bytes32 leftHash; + Bytes32 rightHash; + if (idx >= 128) { + int rightIdx = idx - 128; + Bytes32[] values = new Bytes32[rightIdx + 2]; + Arrays.fill(values, Bytes32.ZERO); + values[rightIdx] = getLowValue(value); + values[rightIdx + 1] = getHighValue(value); + leftHash = Bytes32.ZERO; + rightHash = hasher.commit(values); + } else { + Bytes32[] values = new Bytes32[idx + 2]; + Arrays.fill(values, Bytes32.ZERO); + values[idx] = getLowValue(value); + values[idx + 1] = getHighValue(value); + leftHash = hasher.commit(values); + rightHash = Bytes32.ZERO; + } + Bytes32[] extensionHashes = new Bytes32[4]; + extensionHashes[0] = Bytes32.rightPad(Bytes.of((byte) 1).reverse()); // extension marker + extensionHashes[1] = Bytes32.rightPad(stem); + extensionHashes[2] = leftHash; + extensionHashes[3] = rightHash; + return hasher.commit(extensionHashes); + } + + /** + * Retrieves the low value part of a given optional value. + * + * @param value The optional value. + * @return The low value. + */ + Bytes32 getLowValue(Optional value) { + // Low values have a flag at bit 128. + if (!value.isPresent()) { + return Bytes32.ZERO; + } + return Bytes32.rightPad( + Bytes.concatenate(value.get().slice(0, 16), Bytes.of((byte) 1).reverse())); + } + + /** + * Retrieves the high value part of a given optional value. + * + * @param value The optional value. + * @return The high value. + */ + Bytes32 getHighValue(Optional value) { + if (!value.isPresent()) { + return Bytes32.ZERO; + } + return Bytes32.rightPad(value.get().slice(16, 16)); + } + + /** + * Computes the hash of values within a branch node. + * + * @param branchNode The branch node containing values. + * @param location The location associated with the branch node. + * @return The hash of the values within the branch node. + */ + Bytes32 hashValues(BranchNode branchNode, Bytes location) { + // Values are little endian + // Values are decomposed into 16 lower bytes and 16 higher bytes + // Lower bytes are appended with a 1 to signify that a value is present + // Each part is hashed separately + int size = BranchNode.maxChild(); + Bytes32[] values = new Bytes32[size * 2]; + for (int i = 0; i < size; i++) { + Optional value = branchNode.child((byte) i).getValue(); + values[2 * i] = getLowValue(value); + values[2 * i + 1] = getHighValue(value); + } + Bytes32[] leftValues = Arrays.copyOfRange(values, 0, size); + Bytes32[] rightValues = Arrays.copyOfRange(values, size, 2 * size); + return hashStemExtension(location, leftValues, rightValues); + } +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/NodeVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/NodeVisitor.java new file mode 100644 index 0000000..7a0abbb --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/NodeVisitor.java @@ -0,0 +1,53 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.visitor; + +import org.hyperledger.besu.ethereum.trie.verkle.node.BranchNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.LeafNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.Node; +import org.hyperledger.besu.ethereum.trie.verkle.node.NullNode; + +/** + * Defines a visitor interface for nodes in the Verkle Trie. + * + * @param The type of value associated with nodes. + */ +public interface NodeVisitor { + + /** + * Visits a branch node. + * + * @param branchNode The branch node to visit. + * @return The result of visiting the branch node. + */ + Node visit(BranchNode branchNode); + + /** + * Visits a leaf node. + * + * @param leafNode The leaf node to visit. + * @return The result of visiting the leaf node. + */ + Node visit(LeafNode leafNode); + + /** + * Visits a null node. + * + * @param nullNode The null node to visit. + * @return The result of visiting the null node. + */ + Node visit(NullNode nullNode); +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/PathNodeVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/PathNodeVisitor.java new file mode 100644 index 0000000..83ff2bb --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/PathNodeVisitor.java @@ -0,0 +1,59 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.visitor; + +import org.hyperledger.besu.ethereum.trie.verkle.node.BranchNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.LeafNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.Node; +import org.hyperledger.besu.ethereum.trie.verkle.node.NullNode; + +import org.apache.tuweni.bytes.Bytes; + +/** + * Visit the trie with a path parameter. + * + *

The path parameter indicates the path visited in the Trie. As Nodes are (mostly) immutable, + * visiting a Node returns a (possibly) new Node that should replace the old one. + */ +public interface PathNodeVisitor { + + /** + * Visits a branch node with a specified path. + * + * @param branchNode The branch node to visit. + * @param path The path associated with the visit. + * @return The result of visiting the branch node. + */ + Node visit(BranchNode branchNode, Bytes path); + + /** + * Visits a leaf node with a specified path. + * + * @param leafNode The leaf node to visit. + * @param path The path associated with the visit. + * @return The result of visiting the leaf node. + */ + Node visit(LeafNode leafNode, Bytes path); + + /** + * Visits a null node with a specified path. + * + * @param nullNode The null node to visit. + * @param path The path associated with the visit. + * @return The result of visiting the null node. + */ + Node visit(NullNode nullNode, Bytes path); +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/PutVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/PutVisitor.java new file mode 100644 index 0000000..1635479 --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/PutVisitor.java @@ -0,0 +1,132 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.visitor; + +import org.hyperledger.besu.ethereum.trie.verkle.node.BranchNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.LeafNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.Node; +import org.hyperledger.besu.ethereum.trie.verkle.node.NullNode; + +import org.apache.tuweni.bytes.Bytes; + +/** + * A visitor for inserting or updating values in a Verkle Trie. + * + *

This class implements the PathNodeVisitor interface and is used to visit and modify nodes in + * the Verkle Trie while inserting or updating a value associated with a specific path. + * + * @param The type of values to insert or update. + */ +public class PutVisitor implements PathNodeVisitor { + private final V value; + + /** + * Constructs a new PutVisitor with the provided value to insert or update. + * + * @param value The value to be inserted or updated in the Verkle Trie. + */ + public PutVisitor(V value) { + this.value = value; + } + + /** + * Inserts or updates a value in a branch node. + * + * @param node The branch node to insert or update the value. + * @param commonPath The common path between the node and the path. + * @param pathSuffix The path suffix associated with the value. + * @param nodeSuffix The remaining node suffix after the common path. + * @return The updated node with the inserted or updated value. + */ + protected Node insertNewBranching( + final Node node, final Bytes commonPath, final Bytes pathSuffix, final Bytes nodeSuffix) { + final Node updatedNode = node.replacePath(nodeSuffix.slice(1)); + // Should also add byte to location + BranchNode newBranchNode = new BranchNode(node.getLocation(), commonPath); + newBranchNode.replaceChild(nodeSuffix.get(0), updatedNode); + final Node insertedNode = + newBranchNode.child(pathSuffix.get(0)).accept(this, pathSuffix.slice(1)); + newBranchNode.replaceChild(pathSuffix.get(0), insertedNode); + return newBranchNode; + } + + /** + * Visits a branch node to insert or update a value associated with the provided path. + * + * @param branchNode The branch node to visit. + * @param path The path associated with the value to insert or update. + * @return The updated branch node with the inserted or updated value. + */ + @Override + public Node visit(final BranchNode branchNode, final Bytes path) { + final Bytes nodePath = branchNode.getPath(); + final Bytes commonPath = nodePath.commonPrefix(path); + final int commonPathLength = commonPath.size(); + final Bytes pathSuffix = path.slice(commonPathLength); + final Bytes nodeSuffix = nodePath.slice(commonPathLength); + if (commonPath.compareTo(nodePath) == 0) { + final byte childIndex = pathSuffix.get(0); + final Node updatedChild = branchNode.child(childIndex).accept(this, pathSuffix.slice(1)); + branchNode.replaceChild(childIndex, updatedChild); + if (updatedChild.isDirty()) { + branchNode.markDirty(); + } + return branchNode; + } else { + return insertNewBranching(branchNode, commonPath, pathSuffix, nodeSuffix); + } + } + + /** + * Visits a leaf node to insert or update a value associated with the provided path. + * + * @param leafNode The leaf node to visit. + * @param path The path associated with the value to insert or update. + * @return The updated leaf node with the inserted or updated value. + */ + @Override + public Node visit(final LeafNode leafNode, final Bytes path) { + /* + * Leaf node is used to store a value. + * However, it is a mixture with ExtensionNode and can have a non-empty path: + * An extension all the way to a LeafNode is stored as a single LeafNode + */ + final Bytes nodePath = leafNode.getPath(); + final Bytes commonPath = nodePath.commonPrefix(path); + final int commonPathLength = commonPath.size(); + final Bytes pathSuffix = path.slice(commonPathLength); + final Bytes nodeSuffix = nodePath.slice(commonPathLength); + if (commonPath.compareTo(nodePath) == 0) { + final LeafNode newNode = new LeafNode(leafNode.getLocation(), value, path); + newNode.markDirty(); + return newNode; + } + + return insertNewBranching(leafNode, commonPath, pathSuffix, nodeSuffix); + } + + /** + * Visits a null node to insert or update a value associated with the provided path. + * + * @param nullNode The null node to visit. + * @param path The path associated with the value to insert or update. + * @return A new leaf node containing the inserted or updated value. + */ + @Override + public Node visit(final NullNode nullNode, final Bytes path) { + return new LeafNode(value, path); + } +} diff --git a/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/RemoveVisitor.java b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/RemoveVisitor.java new file mode 100644 index 0000000..51b1fc8 --- /dev/null +++ b/src/main/java/org/hyperledger/besu/ethereum/trie/verkle/visitor/RemoveVisitor.java @@ -0,0 +1,155 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ +package org.hyperledger.besu.ethereum.trie.verkle.visitor; + +import org.hyperledger.besu.ethereum.trie.verkle.node.BranchNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.LeafNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.Node; +import org.hyperledger.besu.ethereum.trie.verkle.node.NullNode; + +import java.util.List; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +/** + * A visitor for removing nodes in a Verkle Trie while preserving its structure. + * + *

This class implements the PathNodeVisitor interface and is used to visit and remove nodes in + * the Verkle Trie while maintaining the Trie's structural integrity. + * + * @param The type of values associated with the nodes. + */ +public class RemoveVisitor implements PathNodeVisitor { + private final Node NULL_NODE = NullNode.instance(); + + /** + * Merges a single branching node by updating and replacing its structure with the new node. + * + * @param node The node to be merged. + * @param commonPath The common path between the node and the path. + * @param pathSuffix The path suffix associated with the node. + * @param nodeSuffix The remaining node suffix after the common path. + * @return The merged node with the updated structure. + */ + protected Node mergeSingleBranching( + final Node node, final Bytes commonPath, final Bytes pathSuffix, final Bytes nodeSuffix) { + final Node updatedNode = node.replacePath(nodeSuffix.slice(1)); + // Should also add byte to location + BranchNode newBranchNode = new BranchNode(node.getLocation(), commonPath); + newBranchNode.replaceChild(nodeSuffix.get(0), updatedNode); + final Node insertedNode = + newBranchNode.child(pathSuffix.get(0)).accept(this, pathSuffix.slice(1)); + newBranchNode.replaceChild(pathSuffix.get(0), insertedNode); + return newBranchNode; + } + + /** + * Visits a branch node to remove a node associated with the provided path and maintain the Trie's + * structure. + * + * @param branchNode The branch node to visit. + * @param path The path associated with the node to be removed. + * @return The updated branch node with the removed node and preserved structure. + */ + @Override + public Node visit(BranchNode branchNode, Bytes path) { + final Bytes leafPath = branchNode.getPath(); + final Bytes commonPath = leafPath.commonPrefix(path); + if (commonPath.compareTo(leafPath) != 0) { + return branchNode; + } + final Bytes pathSuffix = path.slice(commonPath.size()); + final byte childIndex = pathSuffix.get(0); + final Node childNode = branchNode.child(childIndex).accept(this, pathSuffix.slice(1)); + branchNode.replaceChild(childIndex, childNode); + Node resultNode = maybeFlatten(branchNode); + return resultNode; + } + + /** + * Visits a leaf node to remove a node associated with the provided path and maintain the Trie's + * structure. + * + * @param leafNode The leaf node to visit. + * @param path The path associated with the node to be removed. + * @return A null node, indicating the removal of the node. + */ + @Override + public Node visit(LeafNode leafNode, Bytes path) { + final Bytes nodePath = leafNode.getPath(); + final Bytes commonPath = nodePath.commonPrefix(path); + if (commonPath.compareTo(nodePath) != 0) { + return leafNode; + } + return NULL_NODE; + } + + /** + * Visits a null node and returns a null node, indicating that no removal is required. + * + * @param nullNode The null node to visit. + * @param path The path associated with the removal (no operation). + * @return A null node, indicating no removal is needed. + */ + @Override + public Node visit(NullNode nullNode, Bytes path) { + return NULL_NODE; + } + + /** + * Checks if the branch node should be flattened (merged with its only child) and performs the + * flattening operation. + * + * @param branchNode The branch node to consider for flattening. + * @return The updated node after flattening or the original branch node if flattening is not + * applicable. + */ + protected Node maybeFlatten(BranchNode branchNode) { + final Optional onlyChildIndex = findOnlyChild(branchNode.getChildren()); + // Many children => return node as is + if (!onlyChildIndex.isPresent()) { + return branchNode; + } + // One child => merge with child: replace the path of the only child and return + // it + final Node onlyChild = branchNode.child(onlyChildIndex.get()); + final Bytes completePath = + Bytes.concatenate( + branchNode.getPath(), Bytes.of(onlyChildIndex.get()), onlyChild.getPath()); + return onlyChild.replacePath(completePath); + } + + /** + * Finds the index of the only non-null child in the list of children nodes. + * + * @param children The list of children nodes. + * @return The index of the only non-null child if it exists, or an empty optional if there is no + * or more than one non-null child. + */ + private Optional findOnlyChild(final List> children) { + Optional onlyChildIndex = Optional.empty(); + for (int i = 0; i < children.size(); ++i) { + if (children.get(i) != NULL_NODE) { + if (onlyChildIndex.isPresent()) { + return Optional.empty(); + } + onlyChildIndex = Optional.of((byte) i); + } + } + return onlyChildIndex; + } +} diff --git a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/NodeLoaderMock.java b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/NodeLoaderMock.java index 2d77b5b..35d321b 100644 --- a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/NodeLoaderMock.java +++ b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/NodeLoaderMock.java @@ -1,3 +1,18 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ package org.hyperledger.besu.ethereum.trie.verkle; import java.util.HashMap; @@ -6,16 +21,16 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; - public class NodeLoaderMock implements NodeLoader { - public HashMap storage; - - public NodeLoaderMock(HashMap storage) { - this.storage = storage; - } + public HashMap storage; + + public NodeLoaderMock(HashMap storage) { + this.storage = storage; + } - public Optional getNode(Bytes location, Bytes32 hash) { - return Optional.ofNullable(storage.get(location)); - } + @Override + public Optional getNode(Bytes location, Bytes32 hash) { + return Optional.ofNullable(storage.get(location)); + } } diff --git a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/NodeUpdaterMock.java b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/NodeUpdaterMock.java index 468953f..6bb1b6d 100644 --- a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/NodeUpdaterMock.java +++ b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/NodeUpdaterMock.java @@ -1,3 +1,18 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ package org.hyperledger.besu.ethereum.trie.verkle; import java.util.HashMap; @@ -7,17 +22,18 @@ public class NodeUpdaterMock implements NodeUpdater { - public HashMap storage; - - public NodeUpdaterMock() { - this.storage = new HashMap(); - } + public HashMap storage; - public NodeUpdaterMock(HashMap storage) { - this.storage = storage; - } + public NodeUpdaterMock() { + this.storage = new HashMap(); + } - public void store(Bytes location, Bytes32 hash, Bytes value) { - storage.put(location, value); - } + public NodeUpdaterMock(HashMap storage) { + this.storage = storage; + } + + @Override + public void store(Bytes location, Bytes32 hash, Bytes value) { + storage.put(location, value); + } } diff --git a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/SimpleVerkleTrieTest.java b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/SimpleVerkleTrieTest.java index 948a9d6..057302f 100644 --- a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/SimpleVerkleTrieTest.java +++ b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/SimpleVerkleTrieTest.java @@ -1,208 +1,308 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ package org.hyperledger.besu.ethereum.trie.verkle; +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.ethereum.trie.verkle.node.BranchNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.LeafNode; +import org.hyperledger.besu.ethereum.trie.verkle.node.Node; +import org.hyperledger.besu.ethereum.trie.verkle.node.NullNode; + import java.util.Optional; -import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.*; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Test; public class SimpleVerkleTrieTest { - - @Test - public void testEmptyTrie() { - SimpleVerkleTrie trie = new SimpleVerkleTrie(); - Node root = trie.getRoot(); - assertThat(root).as("Empty Trie is a NullNode").isInstanceOf(NullNode.class); - assertThat(trie.getRootHash()).as("Retrieve root hash").isEqualByComparingTo(Bytes32.ZERO); - } - @Test - public void testOneValue() { - SimpleVerkleTrie trie = new SimpleVerkleTrie(); - Bytes32 key = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); - Bytes32 value = Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); - trie.put(key, value); - Node root = trie.getRoot(); - assertThat(root).as("One value trie's root is a LeafNode").isInstanceOf(LeafNode.class); - assertThat(trie.get(key)).as("Get one value should be the inserted value").isEqualTo(Optional.of(value)); - Bytes32 expectedRootHash = Bytes32.fromHexString("0x0919cd252910ea715338943554cdf800fe9b951f47182f8d7ae5be9ce4f5ec65"); - assertThat(trie.getRootHash()).as("Retrieve root hash").isEqualByComparingTo(expectedRootHash); - } + @Test + public void testEmptyTrie() { + SimpleVerkleTrie trie = new SimpleVerkleTrie(); + Node root = trie.getRoot(); + assertThat(root).as("Empty Trie is a NullNode").isInstanceOf(NullNode.class); + assertThat(trie.getRootHash()).as("Retrieve root hash").isEqualByComparingTo(Bytes32.ZERO); + } + + @Test + public void testOneValue() { + SimpleVerkleTrie trie = new SimpleVerkleTrie(); + Bytes32 key = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + Bytes32 value = + Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); + trie.put(key, value); + Node root = trie.getRoot(); + assertThat(root).as("One value trie's root is a LeafNode").isInstanceOf(LeafNode.class); + assertThat(trie.get(key)) + .as("Get one value should be the inserted value") + .isEqualTo(Optional.of(value)); + Bytes32 expectedRootHash = + Bytes32.fromHexString("0x0919cd252910ea715338943554cdf800fe9b951f47182f8d7ae5be9ce4f5ec65"); + assertThat(trie.getRootHash()).as("Retrieve root hash").isEqualByComparingTo(expectedRootHash); + } - @Test - public void testTwoValuesAtSameStem() throws Exception { - SimpleVerkleTrie trie = new SimpleVerkleTrie(); - Bytes32 key1 = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); - Bytes32 value1 = Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); - Bytes32 key2 = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddee00"); - Bytes32 value2 = Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000000"); - Bytes path = Bytes.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddee"); - byte index1 = key1.get(31); - byte index2 = key2.get(31); - byte index3 = (byte) 2; - trie.put(key1, value1); - trie.put(key2, value2); - Node root = trie.getRoot(); - assertThat(root).as("Many values trie is a BranchNode").isInstanceOf(BranchNode.class); - BranchNode branchRoot = (BranchNode) root; - assertThat(branchRoot.child(index1)).as("Child at index of first key is a LeafNode").isInstanceOf(LeafNode.class); - assertThat(branchRoot.child(index2)).as("Child at index of second key is a LeafNode").isInstanceOf(LeafNode.class); - assertThat(branchRoot.child(index3)).as("Child at another index is a NullNode").isInstanceOf(NullNode.class); - assertThat(branchRoot.getPath()).as("Path is the common stem").isEqualTo(path); - assertThat(trie.get(key1)).as("Retrieve first value").isEqualTo(Optional.of(value1)); - assertThat(trie.get(key2)).as("Retrieve second value").isEqualTo(Optional.of(value2)); - Bytes32 expectedRootHash = Bytes32.fromHexString("0x0664595728997574720abefebd044352ab20b353f5c8bdb5558d1f17d71d171c"); - assertThat(trie.getRootHash()).as("Retrieve root hash").isEqualByComparingTo(expectedRootHash); - } + @Test + public void testTwoValuesAtSameStem() throws Exception { + SimpleVerkleTrie trie = new SimpleVerkleTrie(); + Bytes32 key1 = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + Bytes32 value1 = + Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); + Bytes32 key2 = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddee00"); + Bytes32 value2 = + Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000000"); + Bytes path = + Bytes.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddee"); + byte index1 = key1.get(31); + byte index2 = key2.get(31); + byte index3 = (byte) 2; + trie.put(key1, value1); + trie.put(key2, value2); + Node root = trie.getRoot(); + assertThat(root).as("Many values trie is a BranchNode").isInstanceOf(BranchNode.class); + BranchNode branchRoot = (BranchNode) root; + assertThat(branchRoot.child(index1)) + .as("Child at index of first key is a LeafNode") + .isInstanceOf(LeafNode.class); + assertThat(branchRoot.child(index2)) + .as("Child at index of second key is a LeafNode") + .isInstanceOf(LeafNode.class); + assertThat(branchRoot.child(index3)) + .as("Child at another index is a NullNode") + .isInstanceOf(NullNode.class); + assertThat(branchRoot.getPath()).as("Path is the common stem").isEqualTo(path); + assertThat(trie.get(key1)).as("Retrieve first value").isEqualTo(Optional.of(value1)); + assertThat(trie.get(key2)).as("Retrieve second value").isEqualTo(Optional.of(value2)); + Bytes32 expectedRootHash = + Bytes32.fromHexString("0x0664595728997574720abefebd044352ab20b353f5c8bdb5558d1f17d71d171c"); + assertThat(trie.getRootHash()).as("Retrieve root hash").isEqualByComparingTo(expectedRootHash); + } - @Test - public void testTwoValuesAtDifferentIndex() throws Exception { - SimpleVerkleTrie trie = new SimpleVerkleTrie(); - Bytes32 key1 = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); - Bytes32 value1 = Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); - Bytes32 key2 = Bytes32.fromHexString("0xff112233445566778899aabbccddeeff00112233445566778899aabbccddee00"); - Bytes32 value2 = Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000000"); - Bytes path = Bytes.fromHexString("0x"); - byte index1 = key1.get(0); - byte index2 = key2.get(0); - byte index3 = (byte) 2; - trie.put(key1, value1); - trie.put(key2, value2); - Node root = trie.getRoot(); - assertThat(root).as("Many values trie's root is a BranchNode").isInstanceOf(BranchNode.class); - BranchNode branchRoot = (BranchNode) root; - assertThat(branchRoot.child(index1)).as("Child at index of first key is a LeafNode").isInstanceOf(LeafNode.class); - assertThat(branchRoot.child(index2)).as("Child at index of second key is a LeafNode").isInstanceOf(LeafNode.class); - assertThat(branchRoot.child(index3)).as("Child at another index is a NullNode").isInstanceOf(NullNode.class); - assertThat(branchRoot.getPath()).as("Path is the common stem").isEqualTo(path); - assertThat(trie.get(key1)).as("Retrieve first value").isEqualTo(Optional.of(value1)); - assertThat(trie.get(key2)).as("Retrieve second value").isEqualTo(Optional.of(value2)); - Bytes32 expectedRootHash = Bytes32.fromHexString("0x18344e51ea6f0699b227f8a00871547430aebba457d4f7d0830315e7b683bba1"); - assertThat(trie.getRootHash()).as("Retrieve root hash").isEqualByComparingTo(expectedRootHash); - } + @Test + public void testTwoValuesAtDifferentIndex() throws Exception { + SimpleVerkleTrie trie = new SimpleVerkleTrie(); + Bytes32 key1 = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + Bytes32 value1 = + Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); + Bytes32 key2 = + Bytes32.fromHexString("0xff112233445566778899aabbccddeeff00112233445566778899aabbccddee00"); + Bytes32 value2 = + Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000000"); + Bytes path = Bytes.fromHexString("0x"); + byte index1 = key1.get(0); + byte index2 = key2.get(0); + byte index3 = (byte) 2; + trie.put(key1, value1); + trie.put(key2, value2); + Node root = trie.getRoot(); + assertThat(root).as("Many values trie's root is a BranchNode").isInstanceOf(BranchNode.class); + BranchNode branchRoot = (BranchNode) root; + assertThat(branchRoot.child(index1)) + .as("Child at index of first key is a LeafNode") + .isInstanceOf(LeafNode.class); + assertThat(branchRoot.child(index2)) + .as("Child at index of second key is a LeafNode") + .isInstanceOf(LeafNode.class); + assertThat(branchRoot.child(index3)) + .as("Child at another index is a NullNode") + .isInstanceOf(NullNode.class); + assertThat(branchRoot.getPath()).as("Path is the common stem").isEqualTo(path); + assertThat(trie.get(key1)).as("Retrieve first value").isEqualTo(Optional.of(value1)); + assertThat(trie.get(key2)).as("Retrieve second value").isEqualTo(Optional.of(value2)); + Bytes32 expectedRootHash = + Bytes32.fromHexString("0x18344e51ea6f0699b227f8a00871547430aebba457d4f7d0830315e7b683bba1"); + assertThat(trie.getRootHash()).as("Retrieve root hash").isEqualByComparingTo(expectedRootHash); + } - @Test - public void testTwoValuesWithDivergentStemsAtDepth2() throws Exception { - SimpleVerkleTrie trie = new SimpleVerkleTrie(); - Bytes32 key1 = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); - Bytes32 value1 = Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); - Bytes32 key2 = Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddee"); - Bytes32 value2 = Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000000"); - Bytes path = Bytes.fromHexString("0x00"); - byte index1 = key1.get(1); - byte index2 = key2.get(1); - byte index3 = (byte) 0; - trie.put(key1, value1); - trie.put(key2, value2); - Node root = trie.getRoot(); - assertThat(root).as("Many values trie's root is a BranchNode").isInstanceOf(BranchNode.class); - BranchNode branchRoot = (BranchNode) root; - assertThat(branchRoot.child(index1)).as("Child at index of first key is a LeafNode").isInstanceOf(LeafNode.class); - assertThat(branchRoot.child(index2)).as("Child at index of second key is a LeafNode").isInstanceOf(LeafNode.class); - assertThat(branchRoot.child(index3)).as("Child at another index is a NullNode").isInstanceOf(NullNode.class); - assertThat(branchRoot.getPath()).as("Path is the common stem").isEqualTo(path); - assertThat(trie.get(key1)).as("Retrieve first value").isEqualTo(Optional.of(value1)); - assertThat(trie.get(key2)).as("Retrieve second value").isEqualTo(Optional.of(value2)); - Bytes32 expectedRootHash = Bytes32.fromHexString("0x0c0f178f291e1113c56903cfcfd7023e64550058127d8de4995461760262a7be"); - assertThat(trie.getRootHash()).as("Retrieve root hash").isEqualByComparingTo(expectedRootHash); - } + @Test + public void testTwoValuesWithDivergentStemsAtDepth2() throws Exception { + SimpleVerkleTrie trie = new SimpleVerkleTrie(); + Bytes32 key1 = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + Bytes32 value1 = + Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); + Bytes32 key2 = + Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddee"); + Bytes32 value2 = + Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000000"); + Bytes path = Bytes.fromHexString("0x00"); + byte index1 = key1.get(1); + byte index2 = key2.get(1); + byte index3 = (byte) 0; + trie.put(key1, value1); + trie.put(key2, value2); + Node root = trie.getRoot(); + assertThat(root).as("Many values trie's root is a BranchNode").isInstanceOf(BranchNode.class); + BranchNode branchRoot = (BranchNode) root; + assertThat(branchRoot.child(index1)) + .as("Child at index of first key is a LeafNode") + .isInstanceOf(LeafNode.class); + assertThat(branchRoot.child(index2)) + .as("Child at index of second key is a LeafNode") + .isInstanceOf(LeafNode.class); + assertThat(branchRoot.child(index3)) + .as("Child at another index is a NullNode") + .isInstanceOf(NullNode.class); + assertThat(branchRoot.getPath()).as("Path is the common stem").isEqualTo(path); + assertThat(trie.get(key1)).as("Retrieve first value").isEqualTo(Optional.of(value1)); + assertThat(trie.get(key2)).as("Retrieve second value").isEqualTo(Optional.of(value2)); + Bytes32 expectedRootHash = + Bytes32.fromHexString("0x0c0f178f291e1113c56903cfcfd7023e64550058127d8de4995461760262a7be"); + assertThat(trie.getRootHash()).as("Retrieve root hash").isEqualByComparingTo(expectedRootHash); + } - @Test - public void testDeleteTwoValuesAtSameStem() throws Exception { - SimpleVerkleTrie trie = new SimpleVerkleTrie(); - Bytes32 key1 = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); - Bytes32 value1 = Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000001"); - Bytes32 key2 = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddee00"); - Bytes32 value2 = Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000002"); - trie.put(key1, value1); - trie.put(key2, value2); - trie.remove(key1); - assertThat(trie.get(key1)).as("Make sure value is deleted").isEqualTo(Optional.empty()); - assertThat(trie.getRoot()).as("One value => flatten to one LeafNode").isInstanceOf(LeafNode.class); - trie.remove(key2); - assertThat(trie.get(key2)).as("Make sure value is deleted").isEqualTo(Optional.empty()); - assertThat(trie.getRoot()).as("No value => back to NullNode root").isInstanceOf(NullNode.class); - } + @Test + public void testDeleteTwoValuesAtSameStem() throws Exception { + SimpleVerkleTrie trie = new SimpleVerkleTrie(); + Bytes32 key1 = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + Bytes32 value1 = + Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000001"); + Bytes32 key2 = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddee00"); + Bytes32 value2 = + Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000002"); + trie.put(key1, value1); + trie.put(key2, value2); + trie.remove(key1); + assertThat(trie.get(key1)).as("Make sure value is deleted").isEqualTo(Optional.empty()); + assertThat(trie.getRoot()) + .as("One value => flatten to one LeafNode") + .isInstanceOf(LeafNode.class); + trie.remove(key2); + assertThat(trie.get(key2)).as("Make sure value is deleted").isEqualTo(Optional.empty()); + assertThat(trie.getRoot()).as("No value => back to NullNode root").isInstanceOf(NullNode.class); + } - @Test - public void testDeleteTwoValuesAtDifferentIndex() throws Exception { - SimpleVerkleTrie trie = new SimpleVerkleTrie(); - Bytes32 key1 = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); - Bytes32 value1 = Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); - Bytes32 key2 = Bytes32.fromHexString("0xff112233445566778899aabbccddeeff00112233445566778899aabbccddee00"); - Bytes32 value2 = Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000000"); - trie.put(key1, value1); - trie.put(key2, value2); - trie.remove(key1); - assertThat(trie.get(key1)).as("Make sure value is deleted").isEqualTo(Optional.empty()); - assertThat(trie.getRoot()).as("One value => flatten to one LeafNode").isInstanceOf(LeafNode.class); - trie.remove(key2); - assertThat(trie.get(key2)).as("Make sure value is deleted").isEqualTo(Optional.empty()); - assertThat(trie.getRoot()).as("No value => back to NullNode root").isInstanceOf(NullNode.class); - } + @Test + public void testDeleteTwoValuesAtDifferentIndex() throws Exception { + SimpleVerkleTrie trie = new SimpleVerkleTrie(); + Bytes32 key1 = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + Bytes32 value1 = + Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); + Bytes32 key2 = + Bytes32.fromHexString("0xff112233445566778899aabbccddeeff00112233445566778899aabbccddee00"); + Bytes32 value2 = + Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000000"); + trie.put(key1, value1); + trie.put(key2, value2); + trie.remove(key1); + assertThat(trie.get(key1)).as("Make sure value is deleted").isEqualTo(Optional.empty()); + assertThat(trie.getRoot()) + .as("One value => flatten to one LeafNode") + .isInstanceOf(LeafNode.class); + trie.remove(key2); + assertThat(trie.get(key2)).as("Make sure value is deleted").isEqualTo(Optional.empty()); + assertThat(trie.getRoot()).as("No value => back to NullNode root").isInstanceOf(NullNode.class); + } - @Test - public void testDeleteTwoValuesWithDivergentStemsAtDepth2() throws Exception { - SimpleVerkleTrie trie = new SimpleVerkleTrie(); - Bytes32 key1 = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); - Bytes32 value1 = Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); - Bytes32 key2 = Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddee"); - Bytes32 value2 = Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000000"); - trie.put(key1, value1); - trie.put(key2, value2); - trie.remove(key1); - assertThat(trie.get(key1)).as("Make sure value is deleted").isEqualTo(Optional.empty()); - assertThat(trie.getRoot()).as("One value => flatten to one LeafNode").isInstanceOf(LeafNode.class); - trie.remove(key2); - assertThat(trie.get(key2)).as("Make sure value is deleted").isEqualTo(Optional.empty()); - assertThat(trie.getRoot()).as("No value => back to NullNode root").isInstanceOf(NullNode.class); - } + @Test + public void testDeleteTwoValuesWithDivergentStemsAtDepth2() throws Exception { + SimpleVerkleTrie trie = new SimpleVerkleTrie(); + Bytes32 key1 = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + Bytes32 value1 = + Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); + Bytes32 key2 = + Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddee"); + Bytes32 value2 = + Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000000"); + trie.put(key1, value1); + trie.put(key2, value2); + trie.remove(key1); + assertThat(trie.get(key1)).as("Make sure value is deleted").isEqualTo(Optional.empty()); + assertThat(trie.getRoot()) + .as("One value => flatten to one LeafNode") + .isInstanceOf(LeafNode.class); + trie.remove(key2); + assertThat(trie.get(key2)).as("Make sure value is deleted").isEqualTo(Optional.empty()); + assertThat(trie.getRoot()).as("No value => back to NullNode root").isInstanceOf(NullNode.class); + } - @Test - public void testDeleteThreeValues() throws Exception { - SimpleVerkleTrie trie = new SimpleVerkleTrie(); - Bytes32 key1 = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); - Bytes32 value1 = Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); - Bytes32 key2 = Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddee"); - Bytes32 value2 = Bytes32.fromHexString("0x0200000000000000000000000000000000000000000000000000000000000000"); - Bytes32 key3 = Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddff"); - Bytes32 value3 = Bytes32.fromHexString("0x0300000000000000000000000000000000000000000000000000000000000000"); - trie.put(key1, value1); - trie.put(key2, value2); - trie.put(key3, value3); - trie.remove(key3); - assertThat(trie.get(key3)).as("Make sure value is deleted").isEqualTo(Optional.empty()); - assertThat(trie.get(key2)).as("Retrieve second value").isEqualTo(Optional.of(value2)); - trie.remove(key2); - assertThat(trie.get(key2)).as("Make sure value is deleted").isEqualTo(Optional.empty()); - assertThat(trie.get(key1)).as("Retrieve first value").isEqualTo(Optional.of(value1)); - trie.remove(key1); - assertThat(trie.get(key1)).as("Make sure value is deleted").isEqualTo(Optional.empty()); - } + @Test + public void testDeleteThreeValues() throws Exception { + SimpleVerkleTrie trie = new SimpleVerkleTrie(); + Bytes32 key1 = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + Bytes32 value1 = + Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); + Bytes32 key2 = + Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddee"); + Bytes32 value2 = + Bytes32.fromHexString("0x0200000000000000000000000000000000000000000000000000000000000000"); + Bytes32 key3 = + Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddff"); + Bytes32 value3 = + Bytes32.fromHexString("0x0300000000000000000000000000000000000000000000000000000000000000"); + trie.put(key1, value1); + trie.put(key2, value2); + trie.put(key3, value3); + trie.remove(key3); + assertThat(trie.get(key3)).as("Make sure value is deleted").isEqualTo(Optional.empty()); + assertThat(trie.get(key2)).as("Retrieve second value").isEqualTo(Optional.of(value2)); + trie.remove(key2); + assertThat(trie.get(key2)).as("Make sure value is deleted").isEqualTo(Optional.empty()); + assertThat(trie.get(key1)).as("Retrieve first value").isEqualTo(Optional.of(value1)); + trie.remove(key1); + assertThat(trie.get(key1)).as("Make sure value is deleted").isEqualTo(Optional.empty()); + } - @Test - public void testDeleteThreeValuesWithFlattening() throws Exception { - SimpleVerkleTrie trie = new SimpleVerkleTrie(); - Bytes32 key1 = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); - Bytes32 value1 = Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); - Bytes32 key2 = Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddee"); - Bytes32 value2 = Bytes32.fromHexString("0x0200000000000000000000000000000000000000000000000000000000000000"); - Bytes32 key3 = Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddff"); - Bytes32 value3 = Bytes32.fromHexString("0x0300000000000000000000000000000000000000000000000000000000000000"); - trie.put(key1, value1); - trie.put(key2, value2); - trie.put(key3, value3); - assertThat(trie.getRoot().getPath()).as("Initial extension path").isEqualTo(Bytes.fromHexString("0x00")); - trie.remove(key1); - assertThat(trie.get(key1)).as("Make sure value is deleted").isEqualTo(Optional.empty()); - assertThat(trie.getRoot().getPath()).as("First flatten: extension path").isEqualTo(Bytes.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccdd")); - assertThat(trie.get(key2)).as("Retrieve second value").isEqualTo(Optional.of(value2)); - trie.remove(key2); - assertThat(trie.get(key2)).as("Make sure value is deleted").isEqualTo(Optional.empty()); - assertThat(trie.getRoot().getPath()).as("Second flatten: extension path").isEqualTo(Bytes.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddff")); - assertThat(trie.get(key3)).as("Retrieve first value").isEqualTo(Optional.of(value3)); - trie.remove(key3); - assertThat(trie.get(key3)).as("Make sure value is deleted").isEqualTo(Optional.empty()); - } + @Test + public void testDeleteThreeValuesWithFlattening() throws Exception { + SimpleVerkleTrie trie = new SimpleVerkleTrie(); + Bytes32 key1 = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + Bytes32 value1 = + Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); + Bytes32 key2 = + Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddee"); + Bytes32 value2 = + Bytes32.fromHexString("0x0200000000000000000000000000000000000000000000000000000000000000"); + Bytes32 key3 = + Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddff"); + Bytes32 value3 = + Bytes32.fromHexString("0x0300000000000000000000000000000000000000000000000000000000000000"); + trie.put(key1, value1); + trie.put(key2, value2); + trie.put(key3, value3); + assertThat(trie.getRoot().getPath()) + .as("Initial extension path") + .isEqualTo(Bytes.fromHexString("0x00")); + trie.remove(key1); + assertThat(trie.get(key1)).as("Make sure value is deleted").isEqualTo(Optional.empty()); + assertThat(trie.getRoot().getPath()) + .as("First flatten: extension path") + .isEqualTo( + Bytes.fromHexString( + "0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccdd")); + assertThat(trie.get(key2)).as("Retrieve second value").isEqualTo(Optional.of(value2)); + trie.remove(key2); + assertThat(trie.get(key2)).as("Make sure value is deleted").isEqualTo(Optional.empty()); + assertThat(trie.getRoot().getPath()) + .as("Second flatten: extension path") + .isEqualTo( + Bytes.fromHexString( + "0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddff")); + assertThat(trie.get(key3)).as("Retrieve first value").isEqualTo(Optional.of(value3)); + trie.remove(key3); + assertThat(trie.get(key3)).as("Make sure value is deleted").isEqualTo(Optional.empty()); + } } diff --git a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/StoredVerkleTrieTest.java b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/StoredVerkleTrieTest.java index b8e851e..cf01dae 100644 --- a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/StoredVerkleTrieTest.java +++ b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/StoredVerkleTrieTest.java @@ -1,146 +1,205 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ package org.hyperledger.besu.ethereum.trie.verkle; -import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.ethereum.trie.verkle.factory.StoredNodeFactory; +import org.hyperledger.besu.ethereum.trie.verkle.node.Node; +import org.hyperledger.besu.ethereum.trie.verkle.node.NullNode; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Test; public class StoredVerkleTrieTest { - - @Test - public void testEmptyTrie() { - NodeUpdaterMock nodeUpdater = new NodeUpdaterMock(); - NodeLoaderMock nodeLoader = new NodeLoaderMock(nodeUpdater.storage); - StoredNodeFactory nodeFactory = new StoredNodeFactory<>(nodeLoader, value -> (Bytes32) value); - SimpleVerkleTrie trie = new SimpleVerkleTrie(); - trie.commit(nodeUpdater); - assertThat(nodeUpdater.storage.isEmpty()); - Node storedRoot = nodeFactory.retrieve(Bytes.EMPTY, Bytes32.ZERO).orElse(NullNode.instance()); - assertThat(storedRoot).isInstanceOf(NullNode.class); - } - - @Test - public void testOneValue() { - NodeUpdaterMock nodeUpdater = new NodeUpdaterMock(); - NodeLoaderMock nodeLoader = new NodeLoaderMock(nodeUpdater.storage); - StoredNodeFactory nodeFactory = new StoredNodeFactory<>(nodeLoader, value -> (Bytes32) value); - SimpleVerkleTrie trie = new SimpleVerkleTrie(); - Bytes32 key = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); - Bytes32 value = Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); - trie.put(key, value); - trie.commit(nodeUpdater); - - Node storedRoot = nodeFactory.retrieve(Bytes.EMPTY, Bytes32.ZERO).get(); - SimpleVerkleTrie storedTrie = new SimpleVerkleTrie(storedRoot); - assertThat(storedTrie.get(key).orElse(null)).as("Retrieved value").isEqualTo(value); - } - - @Test - public void testTwoValuesAtSameStem() throws Exception { - NodeUpdaterMock nodeUpdater = new NodeUpdaterMock(); - NodeLoaderMock nodeLoader = new NodeLoaderMock(nodeUpdater.storage); - StoredNodeFactory nodeFactory = new StoredNodeFactory<>(nodeLoader, value -> (Bytes32) value); - SimpleVerkleTrie trie = new SimpleVerkleTrie(); - Bytes32 key1 = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); - Bytes32 value1 = Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); - Bytes32 key2 = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddee00"); - Bytes32 value2 = Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000000"); - trie.put(key1, value1); - trie.put(key2, value2); - trie.commit(nodeUpdater); - - Node storedRoot = nodeFactory.retrieve(Bytes.EMPTY, Bytes32.ZERO).get(); - SimpleVerkleTrie storedTrie = new SimpleVerkleTrie(storedRoot); - assertThat(storedTrie.get(key1).orElse(null)).isEqualTo(value1); - assertThat(storedTrie.get(key2).orElse(null)).isEqualTo(value2); - } - - @Test - public void testTwoValuesAtDifferentIndex() throws Exception { - NodeUpdaterMock nodeUpdater = new NodeUpdaterMock(); - NodeLoaderMock nodeLoader = new NodeLoaderMock(nodeUpdater.storage); - StoredNodeFactory nodeFactory = new StoredNodeFactory<>(nodeLoader, value -> (Bytes32) value); - SimpleVerkleTrie trie = new SimpleVerkleTrie(); - Bytes32 key1 = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); - Bytes32 value1 = Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); - Bytes32 key2 = Bytes32.fromHexString("0xff112233445566778899aabbccddeeff00112233445566778899aabbccddee00"); - Bytes32 value2 = Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000000"); - trie.put(key1, value1); - trie.put(key2, value2); - trie.commit(nodeUpdater); - - Node storedRoot = nodeFactory.retrieve(Bytes.EMPTY, Bytes32.ZERO).get(); - SimpleVerkleTrie storedTrie = new SimpleVerkleTrie(storedRoot); - assertThat(storedTrie.get(key1).orElse(null)).isEqualTo(value1); - assertThat(storedTrie.get(key2).orElse(null)).isEqualTo(value2); - } - - @Test - public void testTwoValuesWithDivergentStemsAtDepth2() throws Exception { - NodeUpdaterMock nodeUpdater = new NodeUpdaterMock(); - NodeLoaderMock nodeLoader = new NodeLoaderMock(nodeUpdater.storage); - StoredNodeFactory nodeFactory = new StoredNodeFactory<>(nodeLoader, value -> (Bytes32) value); - SimpleVerkleTrie trie = new SimpleVerkleTrie(); - Bytes32 key1 = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); - Bytes32 value1 = Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); - Bytes32 key2 = Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddee"); - Bytes32 value2 = Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000000"); - trie.put(key1, value1); - trie.put(key2, value2); - trie.commit(nodeUpdater); - - Node storedRoot = nodeFactory.retrieve(Bytes.EMPTY, Bytes32.ZERO).get(); - SimpleVerkleTrie storedTrie = new SimpleVerkleTrie(storedRoot); - assertThat(storedTrie.get(key1).orElse(null)).isEqualTo(value1); - assertThat(storedTrie.get(key2).orElse(null)).isEqualTo(value2); - } - - @Test - public void testDeleteThreeValues() throws Exception { - NodeUpdaterMock nodeUpdater = new NodeUpdaterMock(); - NodeLoaderMock nodeLoader = new NodeLoaderMock(nodeUpdater.storage); - StoredNodeFactory nodeFactory = new StoredNodeFactory<>(nodeLoader, value -> (Bytes32) value); - SimpleVerkleTrie trie = new SimpleVerkleTrie(); - Bytes32 key1 = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); - Bytes32 value1 = Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); - Bytes32 key2 = Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddee"); - Bytes32 value2 = Bytes32.fromHexString("0x0200000000000000000000000000000000000000000000000000000000000000"); - Bytes32 key3 = Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddff"); - Bytes32 value3 = Bytes32.fromHexString("0x0300000000000000000000000000000000000000000000000000000000000000"); - trie.put(key1, value1); - trie.put(key2, value2); - trie.put(key3, value3); - trie.commit(nodeUpdater); - - Node storedRoot = nodeFactory.retrieve(Bytes.EMPTY, Bytes32.ZERO).get(); - SimpleVerkleTrie storedTrie = new SimpleVerkleTrie(storedRoot); - assertThat(storedTrie.get(key1).orElse(null)).isEqualTo(value1); - assertThat(storedTrie.get(key2).orElse(null)).isEqualTo(value2); - assertThat(storedTrie.get(key3).orElse(null)).isEqualTo(value3); - } - - @Test - public void testDeleteThreeValuesWithFlattening() throws Exception { - NodeUpdaterMock nodeUpdater = new NodeUpdaterMock(); - NodeLoaderMock nodeLoader = new NodeLoaderMock(nodeUpdater.storage); - StoredNodeFactory nodeFactory = new StoredNodeFactory<>(nodeLoader, value -> (Bytes32) value); - SimpleVerkleTrie trie = new SimpleVerkleTrie(); - Bytes32 key1 = Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); - Bytes32 value1 = Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); - Bytes32 key2 = Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddee"); - Bytes32 value2 = Bytes32.fromHexString("0x0200000000000000000000000000000000000000000000000000000000000000"); - Bytes32 key3 = Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddff"); - Bytes32 value3 = Bytes32.fromHexString("0x0300000000000000000000000000000000000000000000000000000000000000"); - trie.put(key1, value1); - trie.put(key2, value2); - trie.put(key3, value3); - trie.commit(nodeUpdater); - - Node storedRoot = nodeFactory.retrieve(Bytes.EMPTY, Bytes32.ZERO).get(); - SimpleVerkleTrie storedTrie = new SimpleVerkleTrie(storedRoot); - assertThat(storedTrie.get(key1).orElse(null)).isEqualTo(value1); - assertThat(storedTrie.get(key2).orElse(null)).isEqualTo(value2); - assertThat(storedTrie.get(key3).orElse(null)).isEqualTo(value3); - } + + @Test + public void testEmptyTrie() { + NodeUpdaterMock nodeUpdater = new NodeUpdaterMock(); + NodeLoaderMock nodeLoader = new NodeLoaderMock(nodeUpdater.storage); + StoredNodeFactory nodeFactory = + new StoredNodeFactory<>(nodeLoader, value -> (Bytes32) value); + SimpleVerkleTrie trie = new SimpleVerkleTrie(); + trie.commit(nodeUpdater); + assertThat(nodeUpdater.storage).isEmpty(); + Node storedRoot = + nodeFactory.retrieve(Bytes.EMPTY, Bytes32.ZERO).orElse(NullNode.instance()); + assertThat(storedRoot).isInstanceOf(NullNode.class); + } + + @Test + public void testOneValue() { + NodeUpdaterMock nodeUpdater = new NodeUpdaterMock(); + NodeLoaderMock nodeLoader = new NodeLoaderMock(nodeUpdater.storage); + StoredNodeFactory nodeFactory = + new StoredNodeFactory<>(nodeLoader, value -> (Bytes32) value); + SimpleVerkleTrie trie = new SimpleVerkleTrie(); + Bytes32 key = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + Bytes32 value = + Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); + trie.put(key, value); + trie.commit(nodeUpdater); + + Node storedRoot = nodeFactory.retrieve(Bytes.EMPTY, Bytes32.ZERO).get(); + SimpleVerkleTrie storedTrie = + new SimpleVerkleTrie(storedRoot); + assertThat(storedTrie.get(key).orElse(null)).as("Retrieved value").isEqualTo(value); + } + + @Test + public void testTwoValuesAtSameStem() throws Exception { + NodeUpdaterMock nodeUpdater = new NodeUpdaterMock(); + NodeLoaderMock nodeLoader = new NodeLoaderMock(nodeUpdater.storage); + StoredNodeFactory nodeFactory = + new StoredNodeFactory<>(nodeLoader, value -> (Bytes32) value); + SimpleVerkleTrie trie = new SimpleVerkleTrie(); + Bytes32 key1 = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + Bytes32 value1 = + Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); + Bytes32 key2 = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddee00"); + Bytes32 value2 = + Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000000"); + trie.put(key1, value1); + trie.put(key2, value2); + trie.commit(nodeUpdater); + + Node storedRoot = nodeFactory.retrieve(Bytes.EMPTY, Bytes32.ZERO).get(); + SimpleVerkleTrie storedTrie = + new SimpleVerkleTrie(storedRoot); + assertThat(storedTrie.get(key1).orElse(null)).isEqualTo(value1); + assertThat(storedTrie.get(key2).orElse(null)).isEqualTo(value2); + } + + @Test + public void testTwoValuesAtDifferentIndex() throws Exception { + NodeUpdaterMock nodeUpdater = new NodeUpdaterMock(); + NodeLoaderMock nodeLoader = new NodeLoaderMock(nodeUpdater.storage); + StoredNodeFactory nodeFactory = + new StoredNodeFactory<>(nodeLoader, value -> (Bytes32) value); + SimpleVerkleTrie trie = new SimpleVerkleTrie(); + Bytes32 key1 = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + Bytes32 value1 = + Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); + Bytes32 key2 = + Bytes32.fromHexString("0xff112233445566778899aabbccddeeff00112233445566778899aabbccddee00"); + Bytes32 value2 = + Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000000"); + trie.put(key1, value1); + trie.put(key2, value2); + trie.commit(nodeUpdater); + + Node storedRoot = nodeFactory.retrieve(Bytes.EMPTY, Bytes32.ZERO).get(); + SimpleVerkleTrie storedTrie = + new SimpleVerkleTrie(storedRoot); + assertThat(storedTrie.get(key1).orElse(null)).isEqualTo(value1); + assertThat(storedTrie.get(key2).orElse(null)).isEqualTo(value2); + } + + @Test + public void testTwoValuesWithDivergentStemsAtDepth2() throws Exception { + NodeUpdaterMock nodeUpdater = new NodeUpdaterMock(); + NodeLoaderMock nodeLoader = new NodeLoaderMock(nodeUpdater.storage); + StoredNodeFactory nodeFactory = + new StoredNodeFactory<>(nodeLoader, value -> (Bytes32) value); + SimpleVerkleTrie trie = new SimpleVerkleTrie(); + Bytes32 key1 = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + Bytes32 value1 = + Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); + Bytes32 key2 = + Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddee"); + Bytes32 value2 = + Bytes32.fromHexString("0x0100000000000000000000000000000000000000000000000000000000000000"); + trie.put(key1, value1); + trie.put(key2, value2); + trie.commit(nodeUpdater); + + Node storedRoot = nodeFactory.retrieve(Bytes.EMPTY, Bytes32.ZERO).get(); + SimpleVerkleTrie storedTrie = + new SimpleVerkleTrie(storedRoot); + assertThat(storedTrie.get(key1).orElse(null)).isEqualTo(value1); + assertThat(storedTrie.get(key2).orElse(null)).isEqualTo(value2); + } + + @Test + public void testDeleteThreeValues() throws Exception { + NodeUpdaterMock nodeUpdater = new NodeUpdaterMock(); + NodeLoaderMock nodeLoader = new NodeLoaderMock(nodeUpdater.storage); + StoredNodeFactory nodeFactory = + new StoredNodeFactory<>(nodeLoader, value -> (Bytes32) value); + SimpleVerkleTrie trie = new SimpleVerkleTrie(); + Bytes32 key1 = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + Bytes32 value1 = + Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); + Bytes32 key2 = + Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddee"); + Bytes32 value2 = + Bytes32.fromHexString("0x0200000000000000000000000000000000000000000000000000000000000000"); + Bytes32 key3 = + Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddff"); + Bytes32 value3 = + Bytes32.fromHexString("0x0300000000000000000000000000000000000000000000000000000000000000"); + trie.put(key1, value1); + trie.put(key2, value2); + trie.put(key3, value3); + trie.commit(nodeUpdater); + + Node storedRoot = nodeFactory.retrieve(Bytes.EMPTY, Bytes32.ZERO).get(); + SimpleVerkleTrie storedTrie = + new SimpleVerkleTrie(storedRoot); + assertThat(storedTrie.get(key1).orElse(null)).isEqualTo(value1); + assertThat(storedTrie.get(key2).orElse(null)).isEqualTo(value2); + assertThat(storedTrie.get(key3).orElse(null)).isEqualTo(value3); + } + + @Test + public void testDeleteThreeValuesWithFlattening() throws Exception { + NodeUpdaterMock nodeUpdater = new NodeUpdaterMock(); + NodeLoaderMock nodeLoader = new NodeLoaderMock(nodeUpdater.storage); + StoredNodeFactory nodeFactory = + new StoredNodeFactory<>(nodeLoader, value -> (Bytes32) value); + SimpleVerkleTrie trie = new SimpleVerkleTrie(); + Bytes32 key1 = + Bytes32.fromHexString("0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + Bytes32 value1 = + Bytes32.fromHexString("0x1000000000000000000000000000000000000000000000000000000000000000"); + Bytes32 key2 = + Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddee"); + Bytes32 value2 = + Bytes32.fromHexString("0x0200000000000000000000000000000000000000000000000000000000000000"); + Bytes32 key3 = + Bytes32.fromHexString("0x00ff112233445566778899aabbccddeeff00112233445566778899aabbccddff"); + Bytes32 value3 = + Bytes32.fromHexString("0x0300000000000000000000000000000000000000000000000000000000000000"); + trie.put(key1, value1); + trie.put(key2, value2); + trie.put(key3, value3); + trie.commit(nodeUpdater); + + Node storedRoot = nodeFactory.retrieve(Bytes.EMPTY, Bytes32.ZERO).get(); + SimpleVerkleTrie storedTrie = + new SimpleVerkleTrie(storedRoot); + assertThat(storedTrie.get(key1).orElse(null)).isEqualTo(value1); + assertThat(storedTrie.get(key2).orElse(null)).isEqualTo(value2); + assertThat(storedTrie.get(key3).orElse(null)).isEqualTo(value3); + } } diff --git a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/TrieKeyAdapterTest.java b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/TrieKeyAdapterTest.java index 9dafad7..ab08e6a 100644 --- a/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/TrieKeyAdapterTest.java +++ b/src/test/java/org/hyperledger/besu/ethereum/trie/verkle/TrieKeyAdapterTest.java @@ -1,63 +1,89 @@ +/* + * Copyright Besu Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ package org.hyperledger.besu.ethereum.trie.verkle; -import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.ethereum.trie.verkle.adapter.TrieKeyAdapter; +import org.hyperledger.besu.ethereum.trie.verkle.hasher.SHA256Hasher; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.Test; public class TrieKeyAdapterTest { - Bytes32 address = Bytes32.fromHexString("0x000000000000000000000000112233445566778899aabbccddeeff00112233"); - TrieKeyAdapter adapter = new TrieKeyAdapter(new SHA256Hasher()); - - @Test - public void testStorageKey() { - UInt256 storageKey = UInt256.valueOf(32); - // Need to change this once commit is fixed - Bytes32 expected = Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5460"); - assertThat(adapter.storageKey(address, storageKey)).isEqualTo(expected); - } - - @Test - public void testCodeChunkKey() { - UInt256 chunkId = UInt256.valueOf(24); - // Need to change this once commit is fixed - Bytes32 expected = Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5498"); - assertThat(adapter.codeChunkKey(address, chunkId)).isEqualTo(expected); - } - - @Test - public void testVersionKey() { - // Need to change this once commit is fixed - Bytes32 expected = Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5400"); - assertThat(adapter.versionKey(address)).isEqualTo(expected); - } - - @Test - public void testBalanceKey() { - // Need to change this once commit is fixed - Bytes32 expected = Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5401"); - assertThat(adapter.balanceKey(address)).isEqualTo(expected); - } - - @Test - public void testNonceKey() { - // Need to change this once commit is fixed - Bytes32 expected = Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5402"); - assertThat(adapter.nonceKey(address)).isEqualTo(expected); - } - - @Test - public void testCodeKeccakKey() { - // Need to change this once commit is fixed - Bytes32 expected = Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5403"); - assertThat(adapter.codeKeccakKey(address)).isEqualTo(expected); - } - - @Test - public void testCodeSizeKey() { - // Need to change this once commit is fixed - Bytes32 expected = Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5404"); - assertThat(adapter.codeSizeKey(address)).isEqualTo(expected); - } + Bytes32 address = + Bytes32.fromHexString("0x000000000000000000000000112233445566778899aabbccddeeff00112233"); + TrieKeyAdapter adapter = new TrieKeyAdapter(new SHA256Hasher()); + + @Test + public void testStorageKey() { + UInt256 storageKey = UInt256.valueOf(32); + // Need to change this once commit is fixed + Bytes32 expected = + Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5460"); + assertThat(adapter.storageKey(address, storageKey)).isEqualTo(expected); + } + + @Test + public void testCodeChunkKey() { + UInt256 chunkId = UInt256.valueOf(24); + // Need to change this once commit is fixed + Bytes32 expected = + Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5498"); + assertThat(adapter.codeChunkKey(address, chunkId)).isEqualTo(expected); + } + + @Test + public void testVersionKey() { + // Need to change this once commit is fixed + Bytes32 expected = + Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5400"); + assertThat(adapter.versionKey(address)).isEqualTo(expected); + } + + @Test + public void testBalanceKey() { + // Need to change this once commit is fixed + Bytes32 expected = + Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5401"); + assertThat(adapter.balanceKey(address)).isEqualTo(expected); + } + + @Test + public void testNonceKey() { + // Need to change this once commit is fixed + Bytes32 expected = + Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5402"); + assertThat(adapter.nonceKey(address)).isEqualTo(expected); + } + + @Test + public void testCodeKeccakKey() { + // Need to change this once commit is fixed + Bytes32 expected = + Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5403"); + assertThat(adapter.codeKeccakKey(address)).isEqualTo(expected); + } + + @Test + public void testCodeSizeKey() { + // Need to change this once commit is fixed + Bytes32 expected = + Bytes32.fromHexString("0xc3552556138109254d3fb498d2364e85ed427986e389dff3fa5a514e2a3e5404"); + assertThat(adapter.codeSizeKey(address)).isEqualTo(expected); + } }