From be091d43879b3e36d2bcbc0098a261904881f678 Mon Sep 17 00:00:00 2001 From: AL-CT Date: Thu, 6 Nov 2025 16:41:58 +0000 Subject: [PATCH 01/25] initial zktrie commit --- zktrie/LICENSE | 21 ++++ zktrie/go.mod | 44 +++++++ zktrie/go.sum | 167 +++++++++++++++++++++++++ zktrie/zktrie.go | 279 ++++++++++++++++++++++++++++++++++++++++++ zktrie/zktrie_test.go | 99 +++++++++++++++ 5 files changed, 610 insertions(+) create mode 100644 zktrie/LICENSE create mode 100644 zktrie/go.mod create mode 100644 zktrie/go.sum create mode 100644 zktrie/zktrie.go create mode 100644 zktrie/zktrie_test.go diff --git a/zktrie/LICENSE b/zktrie/LICENSE new file mode 100644 index 0000000..9903b1a --- /dev/null +++ b/zktrie/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Hemi Labs, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/zktrie/go.mod b/zktrie/go.mod new file mode 100644 index 0000000..c88ae7f --- /dev/null +++ b/zktrie/go.mod @@ -0,0 +1,44 @@ +module github.com/hemilabs/x/zktrie + +go 1.25.1 + +require ( + github.com/davecgh/go-spew v1.1.1 + github.com/ethereum/go-ethereum v1.16.7 + github.com/hemilabs/x/eth-trie v0.0.0-20251105164544-95cd47e9e738 +) + +require ( + github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect + github.com/VictoriaMetrics/fastcache v1.13.0 // indirect + github.com/bits-and-blooms/bitset v1.20.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/consensys/gnark-crypto v0.18.0 // indirect + github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect + github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/emicklei/dot v1.6.2 // indirect + github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect + github.com/ethereum/go-verkle v0.2.2 // indirect + github.com/ferranbt/fastssz v0.1.4 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/gofrs/flock v0.12.1 // indirect + github.com/golang/snappy v1.0.0 // indirect + github.com/holiman/uint256 v1.3.2 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/minio/sha256-simd v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect + github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.37.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/zktrie/go.sum b/zktrie/go.sum new file mode 100644 index 0000000..6182f45 --- /dev/null +++ b/zktrie/go.sum @@ -0,0 +1,167 @@ +github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 h1:1zYrtlhrZ6/b6SAjLSfKzWtdgqK0U+HtH/VcBWh1BaU= +github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0= +github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= +github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/consensys/gnark-crypto v0.18.0 h1:vIye/FqI50VeAr0B3dx+YjeIvmc3LWz4yEfbWBpTUf0= +github.com/consensys/gnark-crypto v0.18.0/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c= +github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg= +github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= +github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= +github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= +github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= +github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3aXiHh5s= +github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs= +github.com/ethereum/go-ethereum v1.16.7 h1:qeM4TvbrWK0UC0tgkZ7NiRsmBGwsjqc64BHo20U59UQ= +github.com/ethereum/go-ethereum v1.16.7/go.mod h1:Fs6QebQbavneQTYcA39PEKv2+zIjX7rPUZ14DER46wk= +github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= +github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= +github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= +github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hemilabs/x/eth-trie v0.0.0-20251105164544-95cd47e9e738 h1:vfq6tWheHCJnVwpGbdQInMJwLrYUpAWaDe+eSxwAsJg= +github.com/hemilabs/x/eth-trie v0.0.0-20251105164544-95cd47e9e738/go.mod h1:Ww/u6/pC+fwKpLDw2HNxLw0PxsLDukGCMXJRe1VBym4= +github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= +github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= +github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkqOOVnWapUyeWro4= +github.com/prysmaticlabs/gohashtree v0.0.4-beta/go.mod h1:BFdtALS+Ffhg3lGQIHv9HDWuHS8cTvHZzrHWxwOtGOs= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe h1:nbdqkIGOGfUAD54q1s2YBcBz/WcsxCO9HUQ4aGV5hUw= +github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go new file mode 100644 index 0000000..4ffc1d0 --- /dev/null +++ b/zktrie/zktrie.go @@ -0,0 +1,279 @@ +// Copyright (c) 2025 Hemi Labs, Inc. +// Use of this source code is governed by the MIT License, +// which can be found in the LICENSE file. + +package zktrie + +import ( + "context" + "encoding/hex" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethdb/leveldb" + "github.com/hemilabs/x/eth-trie/trie" + "github.com/hemilabs/x/eth-trie/trie/trienode" + "github.com/hemilabs/x/eth-trie/triedb" + "github.com/hemilabs/x/eth-trie/triedb/pathdb" +) + +var metadataAddress common.Address + +func init() { + const reserved string = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + ab, err := hex.DecodeString(reserved) + if err != nil { + panic(fmt.Errorf("error parsing address %v: %w", reserved, err)) + } + metadataAddress.SetBytes(ab) +} + +// ZKBlock holds information on a block used for a ZK Trie state transition. +type ZKBlock struct { + Height uint64 + + // Storage information. Automatically updates the AccountState + // that each storage is associated with. + Storage map[common.Address]map[common.Hash][]byte + + // Accounts information. Overrides any values passed for the + // same address in storage. + Accounts map[common.Address]types.StateAccount +} + +// ZKTrie is used to perform operation on a ZK trie +// and its underlying database. It is not concurrency safe. +type ZKTrie struct { + stateRoots []common.Hash + tdb *triedb.Database +} + +// TODO: set database cache and handles +func NewZKTrie(_ context.Context, home string) (*ZKTrie, error) { + db, err := leveldb.New(home, 0, 0, "", false) + if err != nil { + return nil, fmt.Errorf("open database: %w", err) + } + + // You can now use db as a KeyValueStore + var kv ethdb.KeyValueStore = db + + // high-level database wrapper for the given key-value store + disk, err := rawdb.Open(kv, rawdb.OpenOptions{}) + if err != nil { + return nil, fmt.Errorf("open rawdb: %w", err) + } + tdb := triedb.NewDatabase(disk, &triedb.Config{ + PathDB: &pathdb.Config{ + NoAsyncFlush: true, + }, + }) + + t := &ZKTrie{ + tdb: tdb, + stateRoots: make([]common.Hash, 0), + } + return t, nil +} + +// Close closes the underlying database for ZKTrie. +func (t *ZKTrie) Close() error { + return t.tdb.Close() +} + +// Recover rollbacks the ZKTrie database to a specified historical point. +func (t *ZKTrie) Recover(stateRoot common.Hash) error { + return t.tdb.Recover(stateRoot) +} + +// Put performs an insert into the underlying ZKTrie database. +func (t *ZKTrie) Put(key, value []byte) error { + return t.tdb.Disk().Put(key, value) +} + +// Get performs a lookup on the underlying ZKTrie database. +func (t *ZKTrie) Get(key []byte) ([]byte, error) { + return t.tdb.Disk().Get(key) +} + +func (t *ZKTrie) GetStorage(addr common.Address, key common.Hash) ([]byte, error) { + var ( + stateRoot = t.currentState() + addrHash = crypto.Keccak256Hash(addr.Bytes()) + ) + sa, err := t.GetAccount(addr) + if err != nil { + return nil, fmt.Errorf("get state account: %w", err) + } + if sa == nil { + return nil, nil + } + + storeID := trie.StorageTrieID(stateRoot, addrHash, sa.Root) + storeTrie, err := trie.New(storeID, t.tdb) + if err != nil { + return nil, fmt.Errorf("failed to load trie, err: %w", err) + } + return storeTrie.Get(key[:]) +} + +func (t *ZKTrie) GetAccount(addr common.Address) (*types.StateAccount, error) { + var ( + stateRoot = t.currentState() + stateID = trie.StateTrieID(stateRoot) + addrHash = crypto.Keccak256Hash(addr.Bytes()) + ) + stateTrie, err := trie.New(stateID, t.tdb) + if err != nil { + return nil, fmt.Errorf("failed to load state trie, err: %w", err) + } + stateVal, err := stateTrie.Get(addrHash[:]) + if err != nil { + return nil, fmt.Errorf("get account state: %w", err) + } + if stateVal == nil { + return nil, nil + } + sa, err := types.FullAccount(stateVal) + if err != nil { + return nil, fmt.Errorf("error restoring account: %w", err) + } + return sa, nil +} + +// currentState gets the most recent State Root. +func (t *ZKTrie) currentState() common.Hash { + if len(t.stateRoots) == 0 { + return types.EmptyRootHash + } + return t.stateRoots[len(t.stateRoots)-1] +} + +// TODO: update StateAccount balance +// InsertBlock performs a state transition for a given block. +func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { + var ( + stateRoot = t.currentState() + mergeSet = trienode.NewMergedNodeSet() + stateID = trie.StateTrieID(stateRoot) + mutatedAcc = make(map[common.Hash][]byte, len(block.Storage)+len(block.Accounts)) + originAcc = make(map[common.Address][]byte, len(block.Storage)+len(block.Accounts)) + mutatedStore = make(map[common.Hash]map[common.Hash][]byte, len(block.Storage)) + originStore = make(map[common.Address]map[common.Hash][]byte, len(block.Storage)) + ) + stateTrie, err := trie.New(stateID, t.tdb) + if err != nil { + return types.EmptyRootHash, fmt.Errorf("failed to load state trie, err: %w", err) + } + + for addr, storage := range block.Storage { + if _, ok := block.Accounts[addr]; ok { + continue + } + + addrHash := crypto.Keccak256Hash(addr.Bytes()) + stateVal, err := stateTrie.Get(addrHash[:]) + if err != nil { + return types.EmptyRootHash, fmt.Errorf("get account state: %w", err) + } + + sa := types.NewEmptyStateAccount() + if stateVal != nil { + sa, err = types.FullAccount(stateVal) + if err != nil { + return types.EmptyRootHash, fmt.Errorf("stored value for %v != stateAccount: %w", addr, err) + } + } + na := types.StateAccount{ + Balance: sa.Balance, + CodeHash: sa.CodeHash, + Nonce: sa.Nonce, + } + + storeID := trie.StorageTrieID(stateRoot, addrHash, sa.Root) + storeTrie, err := trie.New(storeID, t.tdb) + if err != nil { + return types.EmptyRootHash, fmt.Errorf("failed to load trie, err: %w", err) + } + + mutatedStore[addrHash] = make(map[common.Hash][]byte, len(block.Storage[addr])) + originStore[addr] = make(map[common.Hash][]byte, len(block.Storage[addr])) + for key, value := range storage { + prev, err := storeTrie.Get(key[:]) + if err != nil { + return types.EmptyRootHash, fmt.Errorf("get storage trie value: %w", err) + } + originStore[addr][key] = prev + mutatedStore[addrHash][key] = value + } + + for k, v := range mutatedStore[addrHash] { + if err := storeTrie.Update(k[:], v); err != nil { + return types.EmptyRootHash, fmt.Errorf("update storage trie: %w", err) + } + } + + // commit the trie, get storage trie root and node set + newStorageRoot, set := storeTrie.Commit(false) + if err := mergeSet.Merge(set); err != nil { + return types.EmptyRootHash, fmt.Errorf("merge storage nodes: %w", err) + } + na.Root = newStorageRoot + + mutatedAcc[addrHash] = types.SlimAccountRLP(na) + originAcc[addr] = stateVal + } + + for addr, sa := range block.Accounts { + addrHash := crypto.Keccak256Hash(addr.Bytes()) + stateVal, err := stateTrie.Get(addrHash[:]) + if err != nil { + return types.EmptyRootHash, fmt.Errorf("get account state: %w", err) + } + originAcc[addr] = stateVal + mutatedAcc[addrHash] = types.SlimAccountRLP(sa) + } + + for key, val := range mutatedAcc { + if err := stateTrie.Update(key.Bytes(), val); err != nil { + return types.EmptyRootHash, fmt.Errorf("update accounts trie: %w", err) + } + } + + // commit the trie, get state trie root and node set + newStateRoot, set := stateTrie.Commit(false) + if err := mergeSet.Merge(set); err != nil { + return types.EmptyRootHash, fmt.Errorf("merge account nodes: %w", err) + } + + // StateSet represents a collection of mutated states during a state transition. + s := triedb.StateSet{ + Accounts: mutatedAcc, + AccountsOrigin: originAcc, + Storages: mutatedStore, + StoragesOrigin: originStore, + RawStorageKey: false, + } + + // performs a state transition + if err := t.tdb.Update(newStateRoot, stateRoot, block.Height, mergeSet, &s); err != nil { + return types.EmptyRootHash, fmt.Errorf("update db: %w", err) + } + + t.stateRoots = append(t.stateRoots, newStateRoot) + return newStateRoot, nil +} + +// Commit commits performed state trasitions from memory to disk. +func (t *ZKTrie) Commit() error { + currState := t.currentState() + if err := t.tdb.Commit(currState, true); err != nil { + return fmt.Errorf("commit db: %w", err) + } + t.stateRoots = []common.Hash{currState} + return nil +} diff --git a/zktrie/zktrie_test.go b/zktrie/zktrie_test.go new file mode 100644 index 0000000..9cf1fca --- /dev/null +++ b/zktrie/zktrie_test.go @@ -0,0 +1,99 @@ +// Copyright (c) 2025 Hemi Labs, Inc. +// Use of this source code is governed by the MIT License, +// which can be found in the LICENSE file. + +package zktrie + +import ( + "crypto/rand" + "io" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +func TestZKTrie(t *testing.T) { + const ( + blockCount uint64 = 10 + storageKeys uint64 = 5 + ) + + home := t.TempDir() + zkt, err := NewZKTrie(t.Context(), home) + if err != nil { + t.Fatal(err) + } + + if err := zkt.Put([]byte("hello"), []byte("world")); err != nil { + t.Fatal(err) + } + + _, err = zkt.Get([]byte("hello")) + if err != nil { + t.Fatal(err) + } + + accounts := make(map[common.Address][]common.Hash, blockCount) + for i := range blockCount { + blk := ZKBlock{ + Height: i, + Storage: make(map[common.Address]map[common.Hash][]byte), + } + + var randAcc common.Address + randAcc.SetBytes(random(20)) + + blk.Storage[randAcc] = make(map[common.Hash][]byte, storageKeys) + accounts[randAcc] = make([]common.Hash, storageKeys) + for i := range storageKeys { + var hash common.Hash + hash.SetBytes(random(32)) + accounts[randAcc][i] = hash + blk.Storage[randAcc][hash] = random(32) + } + + sr, err := zkt.InsertBlock(&blk) + if err != nil { + t.Fatal(err) + } + + t.Logf("inserted block %d, new state root: %v", blk.Height, sr) + } + + if err := zkt.Commit(); err != nil { + t.Fatal(err) + } + + for ac, keys := range accounts { + sa, err := zkt.GetAccount(ac) + if err != nil { + t.Fatal(err) + } + + spew.Dump(sa) + + for _, k := range keys { + v, err := zkt.GetStorage(ac, k) + if err != nil { + t.Fatal(err) + } + t.Logf("address %x, key %x, value %x", ac, k, v) + } + } + + if err := zkt.Recover(types.EmptyRootHash); err != nil { + t.Fatal(err) + } +} + +// Random returns a variable number of random bytes. +func random(n int) []byte { + buffer := make([]byte, n) + _, err := io.ReadFull(rand.Reader, buffer) + if err != nil { + panic(err) + } + return buffer +} From ae013e77b1341d3ebad6444c52527ce1d914df6e Mon Sep 17 00:00:00 2001 From: AL-CT Date: Fri, 7 Nov 2025 13:38:19 +0000 Subject: [PATCH 02/25] add a few more things --- zktrie/zktrie.go | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index 4ffc1d0..554b8f3 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -8,6 +8,7 @@ import ( "context" "encoding/hex" "fmt" + "sync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" @@ -21,7 +22,10 @@ import ( "github.com/hemilabs/x/eth-trie/triedb/pathdb" ) -var metadataAddress common.Address +// XXX probably need a way to be able to differentiate between account +// types so when we insert a block, they update fields in different ways. +// Maybe we can use the CodeHash field of StateAccount? +var MetadataAddress common.Address func init() { const reserved string = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" @@ -29,7 +33,7 @@ func init() { if err != nil { panic(fmt.Errorf("error parsing address %v: %w", reserved, err)) } - metadataAddress.SetBytes(ab) + MetadataAddress.SetBytes(ab) } // ZKBlock holds information on a block used for a ZK Trie state transition. @@ -37,7 +41,8 @@ type ZKBlock struct { Height uint64 // Storage information. Automatically updates the AccountState - // that each storage is associated with. + // that each storage is associated with. The inner map's keys + // are Keccak256 hashes of []byte values. Storage map[common.Address]map[common.Hash][]byte // Accounts information. Overrides any values passed for the @@ -45,9 +50,10 @@ type ZKBlock struct { Accounts map[common.Address]types.StateAccount } -// ZKTrie is used to perform operation on a ZK trie -// and its underlying database. It is not concurrency safe. +// ZKTrie is used to perform operation on +// a ZK trie and its underlying database. type ZKTrie struct { + mtx sync.RWMutex stateRoots []common.Hash tdb *triedb.Database } @@ -82,11 +88,15 @@ func NewZKTrie(_ context.Context, home string) (*ZKTrie, error) { // Close closes the underlying database for ZKTrie. func (t *ZKTrie) Close() error { + t.mtx.Lock() + defer t.mtx.Unlock() return t.tdb.Close() } // Recover rollbacks the ZKTrie database to a specified historical point. func (t *ZKTrie) Recover(stateRoot common.Hash) error { + t.mtx.Lock() + defer t.mtx.Unlock() return t.tdb.Recover(stateRoot) } @@ -100,10 +110,16 @@ func (t *ZKTrie) Get(key []byte) ([]byte, error) { return t.tdb.Disk().Get(key) } -func (t *ZKTrie) GetStorage(addr common.Address, key common.Hash) ([]byte, error) { +// MetadataGet retrieves values from the reserved metadata state account. +func (t *ZKTrie) MetadataGet(key []byte) ([]byte, error) { + return t.GetStorage(MetadataAddress, key) +} + +func (t *ZKTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) { var ( stateRoot = t.currentState() addrHash = crypto.Keccak256Hash(addr.Bytes()) + keyHash = crypto.Keccak256Hash(key) ) sa, err := t.GetAccount(addr) if err != nil { @@ -118,7 +134,7 @@ func (t *ZKTrie) GetStorage(addr common.Address, key common.Hash) ([]byte, error if err != nil { return nil, fmt.Errorf("failed to load trie, err: %w", err) } - return storeTrie.Get(key[:]) + return storeTrie.Get(keyHash[:]) } func (t *ZKTrie) GetAccount(addr common.Address) (*types.StateAccount, error) { @@ -147,6 +163,9 @@ func (t *ZKTrie) GetAccount(addr common.Address) (*types.StateAccount, error) { // currentState gets the most recent State Root. func (t *ZKTrie) currentState() common.Hash { + t.mtx.RLock() + defer t.mtx.RUnlock() + if len(t.stateRoots) == 0 { return types.EmptyRootHash } @@ -259,6 +278,9 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { RawStorageKey: false, } + t.mtx.Lock() + defer t.mtx.Unlock() + // performs a state transition if err := t.tdb.Update(newStateRoot, stateRoot, block.Height, mergeSet, &s); err != nil { return types.EmptyRootHash, fmt.Errorf("update db: %w", err) @@ -271,6 +293,9 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { // Commit commits performed state trasitions from memory to disk. func (t *ZKTrie) Commit() error { currState := t.currentState() + t.mtx.Lock() + defer t.mtx.Unlock() + if err := t.tdb.Commit(currState, true); err != nil { return fmt.Errorf("commit db: %w", err) } From 12d49160ce36c9d3c12dea17de2ef59fcdbf5c83 Mon Sep 17 00:00:00 2001 From: AL-CT Date: Fri, 7 Nov 2025 13:40:23 +0000 Subject: [PATCH 03/25] fix test --- zktrie/zktrie_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/zktrie/zktrie_test.go b/zktrie/zktrie_test.go index 9cf1fca..c61e628 100644 --- a/zktrie/zktrie_test.go +++ b/zktrie/zktrie_test.go @@ -12,6 +12,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" ) func TestZKTrie(t *testing.T) { @@ -35,7 +36,7 @@ func TestZKTrie(t *testing.T) { t.Fatal(err) } - accounts := make(map[common.Address][]common.Hash, blockCount) + accounts := make(map[common.Address][][]byte, blockCount) for i := range blockCount { blk := ZKBlock{ Height: i, @@ -46,11 +47,11 @@ func TestZKTrie(t *testing.T) { randAcc.SetBytes(random(20)) blk.Storage[randAcc] = make(map[common.Hash][]byte, storageKeys) - accounts[randAcc] = make([]common.Hash, storageKeys) + accounts[randAcc] = make([][]byte, storageKeys) for i := range storageKeys { - var hash common.Hash - hash.SetBytes(random(32)) - accounts[randAcc][i] = hash + newKey := random(10) + hash := crypto.Keccak256Hash(newKey) + accounts[randAcc][i] = newKey blk.Storage[randAcc][hash] = random(32) } @@ -82,7 +83,6 @@ func TestZKTrie(t *testing.T) { t.Logf("address %x, key %x, value %x", ac, k, v) } } - if err := zkt.Recover(types.EmptyRootHash); err != nil { t.Fatal(err) } From 30cb7411926ec7c77d6ffe53d461621aa21bcba0 Mon Sep 17 00:00:00 2001 From: AL-CT Date: Fri, 7 Nov 2025 16:25:33 +0000 Subject: [PATCH 04/25] fix RLP encoding bug --- zktrie/zktrie.go | 26 +++++++++++++++++----- zktrie/zktrie_test.go | 50 ++++++++++++++++++++++++++++++++----------- 2 files changed, 59 insertions(+), 17 deletions(-) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index 554b8f3..ecd2f44 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -16,6 +16,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/leveldb" + "github.com/ethereum/go-ethereum/rlp" "github.com/hemilabs/x/eth-trie/trie" "github.com/hemilabs/x/eth-trie/trie/trienode" "github.com/hemilabs/x/eth-trie/triedb" @@ -43,15 +44,22 @@ type ZKBlock struct { // Storage information. Automatically updates the AccountState // that each storage is associated with. The inner map's keys // are Keccak256 hashes of []byte values. + // + // When the value passed is nil, the associated key is deleted + // from the Trie. Nil should only be passed if the key exists + // in the previous state. Storage map[common.Address]map[common.Hash][]byte // Accounts information. Overrides any values passed for the // same address in storage. + // + // When the StateAccount passed is nil, the associated address + // is deleted from the Trie. Nil should only be passed if the + // address exists in the previous state. Accounts map[common.Address]types.StateAccount } -// ZKTrie is used to perform operation on -// a ZK trie and its underlying database. +// ZKTrie is used to perform operation on a ZK trie and its database. type ZKTrie struct { mtx sync.RWMutex stateRoots []common.Hash @@ -204,7 +212,7 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { if stateVal != nil { sa, err = types.FullAccount(stateVal) if err != nil { - return types.EmptyRootHash, fmt.Errorf("stored value for %v != stateAccount: %w", addr, err) + panic(fmt.Errorf("decode stored state value: %w", err)) } } na := types.StateAccount{ @@ -243,7 +251,11 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { } na.Root = newStorageRoot - mutatedAcc[addrHash] = types.SlimAccountRLP(na) + full, err := rlp.EncodeToBytes(&na) + if err != nil { + return types.EmptyRootHash, err + } + mutatedAcc[addrHash] = full originAcc[addr] = stateVal } @@ -253,8 +265,12 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { if err != nil { return types.EmptyRootHash, fmt.Errorf("get account state: %w", err) } + full, err := rlp.EncodeToBytes(&sa) + if err != nil { + return types.EmptyRootHash, err + } + mutatedAcc[addrHash] = full originAcc[addr] = stateVal - mutatedAcc[addrHash] = types.SlimAccountRLP(sa) } for key, val := range mutatedAcc { diff --git a/zktrie/zktrie_test.go b/zktrie/zktrie_test.go index c61e628..f56ac1c 100644 --- a/zktrie/zktrie_test.go +++ b/zktrie/zktrie_test.go @@ -5,7 +5,9 @@ package zktrie import ( + "bytes" "crypto/rand" + "fmt" "io" "testing" @@ -13,39 +15,54 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/holiman/uint256" ) func TestZKTrie(t *testing.T) { const ( - blockCount uint64 = 10 + blockCount uint64 = 11 storageKeys uint64 = 5 ) + var ( + home = t.TempDir() + cacheField = []byte("current block") + accounts = make(map[common.Address][][]byte, blockCount+1) + manualAcc = common.BytesToAddress(random(20)) + ) + accounts[manualAcc] = nil - home := t.TempDir() zkt, err := NewZKTrie(t.Context(), home) if err != nil { t.Fatal(err) } - if err := zkt.Put([]byte("hello"), []byte("world")); err != nil { t.Fatal(err) } - - _, err = zkt.Get([]byte("hello")) + v, err := zkt.Get([]byte("hello")) if err != nil { t.Fatal(err) } + if !bytes.Equal(v, []byte("world")) { + t.Fatalf("got %s, wanted %s", v, []byte("world")) + } - accounts := make(map[common.Address][][]byte, blockCount) for i := range blockCount { blk := ZKBlock{ - Height: i, - Storage: make(map[common.Address]map[common.Hash][]byte), + Height: i, + Storage: make(map[common.Address]map[common.Hash][]byte, 1), + Accounts: make(map[common.Address]types.StateAccount, 1), } - var randAcc common.Address - randAcc.SetBytes(random(20)) + var md []byte + if i%2 == 0 { + // delete and create every other block + md = fmt.Appendf(nil, "%d", blk.Height) + } + blk.Storage[MetadataAddress] = map[common.Hash][]byte{ + crypto.Keccak256Hash(cacheField): md, + } + randAcc := common.BytesToAddress(random(20)) blk.Storage[randAcc] = make(map[common.Hash][]byte, storageKeys) accounts[randAcc] = make([][]byte, storageKeys) for i := range storageKeys { @@ -55,11 +72,14 @@ func TestZKTrie(t *testing.T) { blk.Storage[randAcc][hash] = random(32) } + manualState := types.NewEmptyStateAccount() + manualState.Balance = uint256.NewInt(10 * (i + 1)) + blk.Accounts[manualAcc] = *manualState + sr, err := zkt.InsertBlock(&blk) if err != nil { t.Fatal(err) } - t.Logf("inserted block %d, new state root: %v", blk.Height, sr) } @@ -72,7 +92,6 @@ func TestZKTrie(t *testing.T) { if err != nil { t.Fatal(err) } - spew.Dump(sa) for _, k := range keys { @@ -83,6 +102,13 @@ func TestZKTrie(t *testing.T) { t.Logf("address %x, key %x, value %x", ac, k, v) } } + + md, err := zkt.MetadataGet(cacheField) + if err != nil { + t.Fatal(err) + } + t.Logf("stored metadata: %s", md) + if err := zkt.Recover(types.EmptyRootHash); err != nil { t.Fatal(err) } From c3390a4053794a63526f3e62b61e6863bb194fdd Mon Sep 17 00:00:00 2001 From: AL-CT Date: Mon, 10 Nov 2025 13:52:00 +0000 Subject: [PATCH 05/25] address marco review --- zktrie/zktrie.go | 75 ++++++++++++++++++++++++++++++++++++------- zktrie/zktrie_test.go | 20 ++++-------- 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index ecd2f44..58a28b9 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -5,11 +5,11 @@ package zktrie import ( - "context" - "encoding/hex" + "errors" "fmt" "sync" + "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" @@ -26,18 +26,18 @@ import ( // XXX probably need a way to be able to differentiate between account // types so when we insert a block, they update fields in different ways. // Maybe we can use the CodeHash field of StateAccount? -var MetadataAddress common.Address +var ( + MetadataAddress common.Address + ErrNotFound = errors.New("key not found") +) func init() { - const reserved string = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" - ab, err := hex.DecodeString(reserved) - if err != nil { - panic(fmt.Errorf("error parsing address %v: %w", reserved, err)) - } - MetadataAddress.SetBytes(ab) + const reserved string = "0xffffffffffffffffffffffffffffffffffffffff" + MetadataAddress = common.BytesToAddress([]byte(reserved)) + spew.Dump(MetadataAddress) } -// ZKBlock holds information on a block used for a ZK Trie state transition. +// ZKBlock holds informati∂on on a block used for a ZK Trie state transition. type ZKBlock struct { Height uint64 @@ -59,6 +59,59 @@ type ZKBlock struct { Accounts map[common.Address]types.StateAccount } +func NewZKBlock(height uint64) *ZKBlock { + return &ZKBlock{ + Height: height, + } +} + +func (b *ZKBlock) AddStorage(addr common.Address, key common.Hash, value []byte) { + if b.Storage == nil { + b.Storage = make(map[common.Address]map[common.Hash][]byte) + } + if _, ok := b.Storage[addr]; !ok { + b.Storage[addr] = make(map[common.Hash][]byte) + } + b.Storage[addr][key] = value +} + +func (b *ZKBlock) GetStorage(addr common.Address, key common.Hash) ([]byte, error) { + if b.Storage == nil || b.Storage[addr] == nil { + return nil, ErrNotFound + } + val, ok := b.Storage[addr][key] + if !ok { + return nil, ErrNotFound + } + return val, nil +} + +func (b *ZKBlock) AddAccount(addr common.Address, state types.StateAccount) { + if b.Accounts == nil { + b.Accounts = make(map[common.Address]types.StateAccount) + } + b.Accounts[addr] = state +} + +func (b *ZKBlock) GetAccount(addr common.Address) (types.StateAccount, error) { + if b.Accounts == nil { + return *types.NewEmptyStateAccount(), ErrNotFound + } + val, ok := b.Accounts[addr] + if !ok { + return val, ErrNotFound + } + return val, nil +} + +func (b *ZKBlock) AddMetadata(key common.Hash, value []byte) { + b.AddStorage(MetadataAddress, key, value) +} + +func (b *ZKBlock) GetMetadata(key common.Hash) ([]byte, error) { + return b.GetStorage(MetadataAddress, key) +} + // ZKTrie is used to perform operation on a ZK trie and its database. type ZKTrie struct { mtx sync.RWMutex @@ -67,7 +120,7 @@ type ZKTrie struct { } // TODO: set database cache and handles -func NewZKTrie(_ context.Context, home string) (*ZKTrie, error) { +func NewZKTrie(home string) (*ZKTrie, error) { db, err := leveldb.New(home, 0, 0, "", false) if err != nil { return nil, fmt.Errorf("open database: %w", err) diff --git a/zktrie/zktrie_test.go b/zktrie/zktrie_test.go index f56ac1c..3a36fa6 100644 --- a/zktrie/zktrie_test.go +++ b/zktrie/zktrie_test.go @@ -31,7 +31,7 @@ func TestZKTrie(t *testing.T) { ) accounts[manualAcc] = nil - zkt, err := NewZKTrie(t.Context(), home) + zkt, err := NewZKTrie(home) if err != nil { t.Fatal(err) } @@ -47,36 +47,28 @@ func TestZKTrie(t *testing.T) { } for i := range blockCount { - blk := ZKBlock{ - Height: i, - Storage: make(map[common.Address]map[common.Hash][]byte, 1), - Accounts: make(map[common.Address]types.StateAccount, 1), - } - + blk := NewZKBlock(i) var md []byte if i%2 == 0 { // delete and create every other block md = fmt.Appendf(nil, "%d", blk.Height) } - blk.Storage[MetadataAddress] = map[common.Hash][]byte{ - crypto.Keccak256Hash(cacheField): md, - } + blk.AddMetadata(crypto.Keccak256Hash(cacheField), md) randAcc := common.BytesToAddress(random(20)) - blk.Storage[randAcc] = make(map[common.Hash][]byte, storageKeys) accounts[randAcc] = make([][]byte, storageKeys) for i := range storageKeys { newKey := random(10) hash := crypto.Keccak256Hash(newKey) accounts[randAcc][i] = newKey - blk.Storage[randAcc][hash] = random(32) + blk.AddStorage(randAcc, hash, random(32)) } manualState := types.NewEmptyStateAccount() manualState.Balance = uint256.NewInt(10 * (i + 1)) - blk.Accounts[manualAcc] = *manualState + blk.AddAccount(manualAcc, *manualState) - sr, err := zkt.InsertBlock(&blk) + sr, err := zkt.InsertBlock(blk) if err != nil { t.Fatal(err) } From 35c01612ea83d2f4c52deb6cafa6bfc08458578d Mon Sep 17 00:00:00 2001 From: AL-CT Date: Wed, 12 Nov 2025 13:26:09 +0000 Subject: [PATCH 06/25] temp --- zktrie/go.mod | 3 +- zktrie/go.sum | 2 + zktrie/zktrie.go | 177 ++++++++++++++++++++++++++++------------------- 3 files changed, 110 insertions(+), 72 deletions(-) diff --git a/zktrie/go.mod b/zktrie/go.mod index c88ae7f..01399bb 100644 --- a/zktrie/go.mod +++ b/zktrie/go.mod @@ -3,9 +3,11 @@ module github.com/hemilabs/x/zktrie go 1.25.1 require ( + github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 github.com/davecgh/go-spew v1.1.1 github.com/ethereum/go-ethereum v1.16.7 github.com/hemilabs/x/eth-trie v0.0.0-20251105164544-95cd47e9e738 + github.com/holiman/uint256 v1.3.2 ) require ( @@ -25,7 +27,6 @@ require ( github.com/go-ole/go-ole v1.3.0 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/golang/snappy v1.0.0 // indirect - github.com/holiman/uint256 v1.3.2 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/minio/sha256-simd v1.0.0 // indirect diff --git a/zktrie/go.sum b/zktrie/go.sum index 6182f45..71b0240 100644 --- a/zktrie/go.sum +++ b/zktrie/go.sum @@ -8,6 +8,8 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/consensys/gnark-crypto v0.18.0 h1:vIye/FqI50VeAr0B3dx+YjeIvmc3LWz4yEfbWBpTUf0= diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index 58a28b9..0a2b004 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -5,10 +5,12 @@ package zktrie import ( + "encoding/binary" "errors" "fmt" "sync" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" @@ -37,9 +39,69 @@ func init() { spew.Dump(MetadataAddress) } +// txId + index +type Outpoint [32 + 4]byte + +func NewOutpoint(txid [32]byte, index uint32) (op Outpoint) { + copy(op[:32], txid[:]) + binary.BigEndian.PutUint32(op[32:], index) + return op +} + +// blockhash + txId + txInIdx + value +type SpentOutput [32 + 32 + 4]byte + +func NewSpentOutput(blockHash, txId [32]byte, index uint32) (so SpentOutput) { + copy(so[:32], blockHash[:]) + copy(so[32:64], txId[:]) + binary.BigEndian.PutUint32(so[64:], index) + return so +} + +// blockhash + txId + txOutIdx + value +type SpendableOutput [32 + 32 + 4 + 8]byte + +func (so SpendableOutput) Value() uint64 { + return binary.BigEndian.Uint64(so[68:]) +} + +func NewSpendableOutput(blockHash, txId [32]byte, index uint32, value uint64) (so SpendableOutput) { + copy(so[:32], blockHash[:]) + copy(so[32:64], txId[:]) + binary.BigEndian.PutUint32(so[64:68], index) + binary.BigEndian.PutUint64(so[68:], value) + return so +} + +// prev_stateroot + prev_blockhash + height +type BlockInfo [32 + 32 + 8]byte + +func (bi *BlockInfo) PrevStateRoot() common.Hash { + return common.BytesToHash(bi[:32]) +} + +func (bi *BlockInfo) PrevBlockHash() chainhash.Hash { + ch, err := chainhash.NewHash(bi[32:64]) + if err != nil { + panic("stored value is not blockhash") + } + return *ch +} + +func (bi *BlockInfo) Height() uint64 { + return binary.BigEndian.Uint64(bi[64:]) +} + +func NewBlockInfo(prevStateRoot common.Hash, prevBlockHash chainhash.Hash, height uint64) (bi BlockInfo) { + copy(bi[:32], prevStateRoot[:]) + copy(bi[32:64], prevBlockHash[:]) + binary.BigEndian.PutUint64(bi[64:], height) + return bi +} + // ZKBlock holds informati∂on on a block used for a ZK Trie state transition. type ZKBlock struct { - Height uint64 + blockInfo BlockInfo // Storage information. Automatically updates the AccountState // that each storage is associated with. The inner map's keys @@ -48,69 +110,59 @@ type ZKBlock struct { // When the value passed is nil, the associated key is deleted // from the Trie. Nil should only be passed if the key exists // in the previous state. - Storage map[common.Address]map[common.Hash][]byte - - // Accounts information. Overrides any values passed for the - // same address in storage. - // - // When the StateAccount passed is nil, the associated address - // is deleted from the Trie. Nil should only be passed if the - // address exists in the previous state. - Accounts map[common.Address]types.StateAccount + storage map[common.Address]map[common.Hash][]byte } -func NewZKBlock(height uint64) *ZKBlock { +func NewZKBlock(prevStateRoot common.Hash, prevBlockHash chainhash.Hash, height uint64) *ZKBlock { + bi := NewBlockInfo(prevStateRoot, prevBlockHash, height) return &ZKBlock{ - Height: height, + blockInfo: bi, } } -func (b *ZKBlock) AddStorage(addr common.Address, key common.Hash, value []byte) { - if b.Storage == nil { - b.Storage = make(map[common.Address]map[common.Hash][]byte) +func (b *ZKBlock) NewOut(pkScript []byte, out Outpoint, so SpendableOutput) { + if b.storage == nil { + b.storage = make(map[common.Address]map[common.Hash][]byte) } - if _, ok := b.Storage[addr]; !ok { - b.Storage[addr] = make(map[common.Hash][]byte) + + addr := common.BytesToAddress(pkScript) + if _, ok := b.storage[addr]; !ok { + b.storage[addr] = make(map[common.Hash][]byte) } - b.Storage[addr][key] = value + + key := crypto.Keccak256Hash(out[:]) + b.storage[addr][key] = so[:] } -func (b *ZKBlock) GetStorage(addr common.Address, key common.Hash) ([]byte, error) { - if b.Storage == nil || b.Storage[addr] == nil { - return nil, ErrNotFound +func (b *ZKBlock) NewIn(pkScript []byte, out Outpoint, so SpentOutput) { + if b.storage == nil { + b.storage = make(map[common.Address]map[common.Hash][]byte) } - val, ok := b.Storage[addr][key] - if !ok { - return nil, ErrNotFound - } - return val, nil -} -func (b *ZKBlock) AddAccount(addr common.Address, state types.StateAccount) { - if b.Accounts == nil { - b.Accounts = make(map[common.Address]types.StateAccount) + addr := common.BytesToAddress(pkScript) + if _, ok := b.storage[addr]; !ok { + b.storage[addr] = make(map[common.Hash][]byte) } - b.Accounts[addr] = state + + key := crypto.Keccak256Hash(out[:]) + b.storage[addr][key] = so[:] } -func (b *ZKBlock) GetAccount(addr common.Address) (types.StateAccount, error) { - if b.Accounts == nil { - return *types.NewEmptyStateAccount(), ErrNotFound +func (b *ZKBlock) GetOutpoint(pkScript []byte, out Outpoint) []byte { + addr := common.BytesToAddress(pkScript) + if b.storage == nil || b.storage[addr] == nil { + return nil } - val, ok := b.Accounts[addr] - if !ok { - return val, ErrNotFound - } - return val, nil + key := crypto.Keccak256Hash(out[:]) + return b.storage[addr][key] } -func (b *ZKBlock) AddMetadata(key common.Hash, value []byte) { - b.AddStorage(MetadataAddress, key, value) +func (b *ZKBlock) GetMetadata() BlockInfo { + return b.blockInfo } -func (b *ZKBlock) GetMetadata(key common.Hash) ([]byte, error) { - return b.GetStorage(MetadataAddress, key) -} +// func (b *ZKBlock) GetMetadata(key common.Hash) ([]byte, error) { +// } // ZKTrie is used to perform operation on a ZK trie and its database. type ZKTrie struct { @@ -237,24 +289,20 @@ func (t *ZKTrie) currentState() common.Hash { // InsertBlock performs a state transition for a given block. func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { var ( - stateRoot = t.currentState() + stateRoot = block.blockInfo.PrevStateRoot() mergeSet = trienode.NewMergedNodeSet() stateID = trie.StateTrieID(stateRoot) - mutatedAcc = make(map[common.Hash][]byte, len(block.Storage)+len(block.Accounts)) - originAcc = make(map[common.Address][]byte, len(block.Storage)+len(block.Accounts)) - mutatedStore = make(map[common.Hash]map[common.Hash][]byte, len(block.Storage)) - originStore = make(map[common.Address]map[common.Hash][]byte, len(block.Storage)) + mutatedAcc = make(map[common.Hash][]byte, len(block.storage)) + originAcc = make(map[common.Address][]byte, len(block.storage)) + mutatedStore = make(map[common.Hash]map[common.Hash][]byte, len(block.storage)) + originStore = make(map[common.Address]map[common.Hash][]byte, len(block.storage)) ) stateTrie, err := trie.New(stateID, t.tdb) if err != nil { return types.EmptyRootHash, fmt.Errorf("failed to load state trie, err: %w", err) } - for addr, storage := range block.Storage { - if _, ok := block.Accounts[addr]; ok { - continue - } - + for addr, storage := range block.storage { addrHash := crypto.Keccak256Hash(addr.Bytes()) stateVal, err := stateTrie.Get(addrHash[:]) if err != nil { @@ -280,8 +328,8 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { return types.EmptyRootHash, fmt.Errorf("failed to load trie, err: %w", err) } - mutatedStore[addrHash] = make(map[common.Hash][]byte, len(block.Storage[addr])) - originStore[addr] = make(map[common.Hash][]byte, len(block.Storage[addr])) + mutatedStore[addrHash] = make(map[common.Hash][]byte, len(block.storage[addr])) + originStore[addr] = make(map[common.Hash][]byte, len(block.storage[addr])) for key, value := range storage { prev, err := storeTrie.Get(key[:]) if err != nil { @@ -295,6 +343,7 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { if err := storeTrie.Update(k[:], v); err != nil { return types.EmptyRootHash, fmt.Errorf("update storage trie: %w", err) } + } // commit the trie, get storage trie root and node set @@ -312,20 +361,6 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { originAcc[addr] = stateVal } - for addr, sa := range block.Accounts { - addrHash := crypto.Keccak256Hash(addr.Bytes()) - stateVal, err := stateTrie.Get(addrHash[:]) - if err != nil { - return types.EmptyRootHash, fmt.Errorf("get account state: %w", err) - } - full, err := rlp.EncodeToBytes(&sa) - if err != nil { - return types.EmptyRootHash, err - } - mutatedAcc[addrHash] = full - originAcc[addr] = stateVal - } - for key, val := range mutatedAcc { if err := stateTrie.Update(key.Bytes(), val); err != nil { return types.EmptyRootHash, fmt.Errorf("update accounts trie: %w", err) @@ -351,7 +386,7 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { defer t.mtx.Unlock() // performs a state transition - if err := t.tdb.Update(newStateRoot, stateRoot, block.Height, mergeSet, &s); err != nil { + if err := t.tdb.Update(newStateRoot, stateRoot, block.blockInfo.Height(), mergeSet, &s); err != nil { return types.EmptyRootHash, fmt.Errorf("update db: %w", err) } From c23516e94b0dcaac88b2f4572c8e6f499bc9d479 Mon Sep 17 00:00:00 2001 From: AL-CT Date: Wed, 12 Nov 2025 16:15:33 +0000 Subject: [PATCH 07/25] add proper indexing with helper functions --- zktrie/go.mod | 1 + zktrie/go.sum | 2 + zktrie/zktrie.go | 118 ++++++++++++++++++++++++++++++------------ zktrie/zktrie_test.go | 75 +++++++++++++++------------ 4 files changed, 129 insertions(+), 67 deletions(-) diff --git a/zktrie/go.mod b/zktrie/go.mod index 01399bb..6640472 100644 --- a/zktrie/go.mod +++ b/zktrie/go.mod @@ -3,6 +3,7 @@ module github.com/hemilabs/x/zktrie go 1.25.1 require ( + github.com/btcsuite/btcd v0.25.0 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 github.com/davecgh/go-spew v1.1.1 github.com/ethereum/go-ethereum v1.16.7 diff --git a/zktrie/go.sum b/zktrie/go.sum index 71b0240..0d9e5ed 100644 --- a/zktrie/go.sum +++ b/zktrie/go.sum @@ -8,6 +8,8 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/btcsuite/btcd v0.25.0 h1:JPbjwvHGpSywBRuorFFqTjaVP4y6Qw69XJ1nQ6MyWJM= +github.com/btcsuite/btcd v0.25.0/go.mod h1:qbPE+pEiR9643E1s1xu57awsRhlCIm1ZIi6FfeRA4KE= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index 0a2b004..3156d45 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -23,6 +23,7 @@ import ( "github.com/hemilabs/x/eth-trie/trie/trienode" "github.com/hemilabs/x/eth-trie/triedb" "github.com/hemilabs/x/eth-trie/triedb/pathdb" + "github.com/holiman/uint256" ) // XXX probably need a way to be able to differentiate between account @@ -30,7 +31,10 @@ import ( // Maybe we can use the CodeHash field of StateAccount? var ( MetadataAddress common.Address - ErrNotFound = errors.New("key not found") + + ErrAddressNotFound = errors.New("address not found") + ErrBlockNotFound = errors.New("block information not found") + ErrOutpointNotFound = errors.New("key not found") ) func init() { @@ -48,7 +52,7 @@ func NewOutpoint(txid [32]byte, index uint32) (op Outpoint) { return op } -// blockhash + txId + txInIdx + value +// blockhash + txId + txInIdx type SpentOutput [32 + 32 + 4]byte func NewSpentOutput(blockHash, txId [32]byte, index uint32) (so SpentOutput) { @@ -76,11 +80,11 @@ func NewSpendableOutput(blockHash, txId [32]byte, index uint32, value uint64) (s // prev_stateroot + prev_blockhash + height type BlockInfo [32 + 32 + 8]byte -func (bi *BlockInfo) PrevStateRoot() common.Hash { +func (bi BlockInfo) PrevStateRoot() common.Hash { return common.BytesToHash(bi[:32]) } -func (bi *BlockInfo) PrevBlockHash() chainhash.Hash { +func (bi BlockInfo) PrevBlockHash() chainhash.Hash { ch, err := chainhash.NewHash(bi[32:64]) if err != nil { panic("stored value is not blockhash") @@ -88,7 +92,7 @@ func (bi *BlockInfo) PrevBlockHash() chainhash.Hash { return *ch } -func (bi *BlockInfo) Height() uint64 { +func (bi BlockInfo) Height() uint64 { return binary.BigEndian.Uint64(bi[64:]) } @@ -99,24 +103,24 @@ func NewBlockInfo(prevStateRoot common.Hash, prevBlockHash chainhash.Hash, heigh return bi } -// ZKBlock holds informati∂on on a block used for a ZK Trie state transition. +// ZKBlock holds information on a block used for a ZK Trie state transition. type ZKBlock struct { - blockInfo BlockInfo - - // Storage information. Automatically updates the AccountState - // that each storage is associated with. The inner map's keys - // are Keccak256 hashes of []byte values. - // - // When the value passed is nil, the associated key is deleted - // from the Trie. Nil should only be passed if the key exists - // in the previous state. + blockHash common.Hash + + // Storage information. Automatically managed using the utility methods. storage map[common.Address]map[common.Hash][]byte } -func NewZKBlock(prevStateRoot common.Hash, prevBlockHash chainhash.Hash, height uint64) *ZKBlock { +func NewZKBlock(blockHash, prevBlockHash chainhash.Hash, prevStateRoot common.Hash, height uint64) *ZKBlock { bi := NewBlockInfo(prevStateRoot, prevBlockHash, height) + bh := common.Hash(blockHash) return &ZKBlock{ - blockInfo: bi, + blockHash: bh, + storage: map[common.Address]map[common.Hash][]byte{ + MetadataAddress: { + bh: bi[:], + }, + }, } } @@ -158,12 +162,9 @@ func (b *ZKBlock) GetOutpoint(pkScript []byte, out Outpoint) []byte { } func (b *ZKBlock) GetMetadata() BlockInfo { - return b.blockInfo + return BlockInfo(b.storage[MetadataAddress][b.blockHash]) } -// func (b *ZKBlock) GetMetadata(key common.Hash) ([]byte, error) { -// } - // ZKTrie is used to perform operation on a ZK trie and its database. type ZKTrie struct { mtx sync.RWMutex @@ -223,31 +224,63 @@ func (t *ZKTrie) Get(key []byte) ([]byte, error) { return t.tdb.Disk().Get(key) } -// MetadataGet retrieves values from the reserved metadata state account. -func (t *ZKTrie) MetadataGet(key []byte) ([]byte, error) { - return t.GetStorage(MetadataAddress, key) +// GetBlockInfo retrieves Block information from the reserved +// metadata state account. +func (t *ZKTrie) GetBlockInfo(blockHash chainhash.Hash) (BlockInfo, error) { + var ( + stateRoot = t.currentState() + addrHash = crypto.Keccak256Hash(MetadataAddress.Bytes()) + keyHash = blockHash + ) + sa, err := t.GetAccount(MetadataAddress) + if err != nil { + return BlockInfo{}, fmt.Errorf("get state account: %w", err) + } + + storeID := trie.StorageTrieID(stateRoot, addrHash, sa.Root) + storeTrie, err := trie.New(storeID, t.tdb) + if err != nil { + return BlockInfo{}, fmt.Errorf("failed to load trie, err: %w", err) + } + val, err := storeTrie.Get(keyHash[:]) + if err != nil { + return BlockInfo{}, err + } + if val == nil { + return BlockInfo{}, ErrBlockNotFound + } + + if len(val) != len(BlockInfo{}) { + return BlockInfo{}, fmt.Errorf("unexpected stored value size: %d", len(val)) + } + return BlockInfo(val), nil } -func (t *ZKTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) { +func (t *ZKTrie) GetOutpoint(pkScript []byte, out Outpoint) ([]byte, error) { var ( + addr = common.BytesToAddress(pkScript) stateRoot = t.currentState() addrHash = crypto.Keccak256Hash(addr.Bytes()) - keyHash = crypto.Keccak256Hash(key) + keyHash = crypto.Keccak256Hash(out[:]) ) sa, err := t.GetAccount(addr) if err != nil { return nil, fmt.Errorf("get state account: %w", err) } - if sa == nil { - return nil, nil - } storeID := trie.StorageTrieID(stateRoot, addrHash, sa.Root) storeTrie, err := trie.New(storeID, t.tdb) if err != nil { return nil, fmt.Errorf("failed to load trie, err: %w", err) } - return storeTrie.Get(keyHash[:]) + val, err := storeTrie.Get(keyHash[:]) + if err != nil { + return nil, err + } + if val == nil { + return nil, ErrOutpointNotFound + } + return val, nil } func (t *ZKTrie) GetAccount(addr common.Address) (*types.StateAccount, error) { @@ -265,7 +298,7 @@ func (t *ZKTrie) GetAccount(addr common.Address) (*types.StateAccount, error) { return nil, fmt.Errorf("get account state: %w", err) } if stateVal == nil { - return nil, nil + return nil, ErrAddressNotFound } sa, err := types.FullAccount(stateVal) if err != nil { @@ -289,7 +322,7 @@ func (t *ZKTrie) currentState() common.Hash { // InsertBlock performs a state transition for a given block. func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { var ( - stateRoot = block.blockInfo.PrevStateRoot() + stateRoot = block.GetMetadata().PrevStateRoot() mergeSet = trienode.NewMergedNodeSet() stateID = trie.StateTrieID(stateRoot) mutatedAcc = make(map[common.Hash][]byte, len(block.storage)) @@ -343,7 +376,24 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { if err := storeTrie.Update(k[:], v); err != nil { return types.EmptyRootHash, fmt.Errorf("update storage trie: %w", err) } - + switch len(v) { + case len(SpendableOutput{}): + p := uint256.NewInt(binary.BigEndian.Uint64(v[68:76])) + na.Balance.Add(na.Balance, p) + case len(SpentOutput{}): + val := originStore[addr][k] + if val == nil { + // If out was created and spent in the same block, + // then don't update the balance. + continue + } + pr := originStore[addr][k][68:76] + p := uint256.NewInt(binary.BigEndian.Uint64(pr)) + na.Balance.Sub(na.Balance, p) + case len(BlockInfo{}): + default: + panic("unknown storage value passed") + } } // commit the trie, get storage trie root and node set @@ -386,7 +436,7 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { defer t.mtx.Unlock() // performs a state transition - if err := t.tdb.Update(newStateRoot, stateRoot, block.blockInfo.Height(), mergeSet, &s); err != nil { + if err := t.tdb.Update(newStateRoot, stateRoot, block.GetMetadata().Height(), mergeSet, &s); err != nil { return types.EmptyRootHash, fmt.Errorf("update db: %w", err) } diff --git a/zktrie/zktrie_test.go b/zktrie/zktrie_test.go index 3a36fa6..652ba74 100644 --- a/zktrie/zktrie_test.go +++ b/zktrie/zktrie_test.go @@ -7,15 +7,15 @@ package zktrie import ( "bytes" "crypto/rand" - "fmt" + "encoding/binary" "io" "testing" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/holiman/uint256" ) func TestZKTrie(t *testing.T) { @@ -23,13 +23,7 @@ func TestZKTrie(t *testing.T) { blockCount uint64 = 11 storageKeys uint64 = 5 ) - var ( - home = t.TempDir() - cacheField = []byte("current block") - accounts = make(map[common.Address][][]byte, blockCount+1) - manualAcc = common.BytesToAddress(random(20)) - ) - accounts[manualAcc] = nil + home := t.TempDir() zkt, err := NewZKTrie(home) if err != nil { @@ -46,48 +40,63 @@ func TestZKTrie(t *testing.T) { t.Fatalf("got %s, wanted %s", v, []byte("world")) } + prevBlock := *chaincfg.TestNet3Params.GenesisHash + prevStateRoot := types.EmptyRootHash + outpoints := make(map[uint64][]Outpoint) for i := range blockCount { - blk := NewZKBlock(i) - var md []byte - if i%2 == 0 { - // delete and create every other block - md = fmt.Appendf(nil, "%d", blk.Height) - } - blk.AddMetadata(crypto.Keccak256Hash(cacheField), md) + bh := chainhash.Hash(random(32)) + blk := NewZKBlock(bh, prevBlock, prevStateRoot, i) - randAcc := common.BytesToAddress(random(20)) - accounts[randAcc] = make([][]byte, storageKeys) - for i := range storageKeys { - newKey := random(10) - hash := crypto.Keccak256Hash(newKey) - accounts[randAcc][i] = newKey - blk.AddStorage(randAcc, hash, random(32)) + // simulate outs + var pkScript [8]byte + binary.BigEndian.PutUint64(pkScript[:], i) + outpoints[i] = make([]Outpoint, 0) + for range storageKeys { + o := NewOutpoint([32]byte(random(32)), 1) + so := NewSpendableOutput(blk.blockHash, [32]byte(random(32)), 1, 100) + blk.NewOut(pkScript[:], o, so) + outpoints[i] = append(outpoints[i], o) } - manualState := types.NewEmptyStateAccount() - manualState.Balance = uint256.NewInt(10 * (i + 1)) - blk.AddAccount(manualAcc, *manualState) + // simulate an in + for ac, keys := range outpoints { + if len(keys) <= 1 { + continue + } + var pkScript [8]byte + binary.BigEndian.PutUint64(pkScript[:], ac) + o := keys[0] + outpoints[ac] = keys[1:] + so := NewSpentOutput(blk.blockHash, [32]byte(random(32)), 1) + blk.NewIn(pkScript[:], o, so) + break + } sr, err := zkt.InsertBlock(blk) if err != nil { t.Fatal(err) } - t.Logf("inserted block %d, new state root: %v", blk.Height, sr) + t.Logf("inserted block %d, new state root: %v", blk.GetMetadata().Height(), sr) + prevBlock = bh + prevStateRoot = sr } if err := zkt.Commit(); err != nil { t.Fatal(err) } - for ac, keys := range accounts { - sa, err := zkt.GetAccount(ac) + for ac, keys := range outpoints { + var pkScript [8]byte + binary.BigEndian.PutUint64(pkScript[:], ac) + addr := common.BytesToAddress(pkScript[:]) + sa, err := zkt.GetAccount(addr) if err != nil { t.Fatal(err) } spew.Dump(sa) for _, k := range keys { - v, err := zkt.GetStorage(ac, k) + v, err := zkt.GetOutpoint(pkScript[:], k) if err != nil { t.Fatal(err) } @@ -95,11 +104,11 @@ func TestZKTrie(t *testing.T) { } } - md, err := zkt.MetadataGet(cacheField) + md, err := zkt.GetBlockInfo(prevBlock) if err != nil { t.Fatal(err) } - t.Logf("stored metadata: %s", md) + spew.Dump(md) if err := zkt.Recover(types.EmptyRootHash); err != nil { t.Fatal(err) From 54e3825ed95fd8f2e8bab57aa3c362423c80de93 Mon Sep 17 00:00:00 2001 From: AL-CT Date: Thu, 13 Nov 2025 12:27:10 +0000 Subject: [PATCH 08/25] set state root on recover --- zktrie/zktrie.go | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index 3156d45..e658ed1 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -26,9 +26,6 @@ import ( "github.com/holiman/uint256" ) -// XXX probably need a way to be able to differentiate between account -// types so when we insert a block, they update fields in different ways. -// Maybe we can use the CodeHash field of StateAccount? var ( MetadataAddress common.Address @@ -167,9 +164,9 @@ func (b *ZKBlock) GetMetadata() BlockInfo { // ZKTrie is used to perform operation on a ZK trie and its database. type ZKTrie struct { - mtx sync.RWMutex - stateRoots []common.Hash - tdb *triedb.Database + mtx sync.RWMutex + stateRoot common.Hash + tdb *triedb.Database } // TODO: set database cache and handles @@ -194,8 +191,8 @@ func NewZKTrie(home string) (*ZKTrie, error) { }) t := &ZKTrie{ - tdb: tdb, - stateRoots: make([]common.Hash, 0), + tdb: tdb, + stateRoot: types.EmptyRootHash, } return t, nil } @@ -211,7 +208,11 @@ func (t *ZKTrie) Close() error { func (t *ZKTrie) Recover(stateRoot common.Hash) error { t.mtx.Lock() defer t.mtx.Unlock() - return t.tdb.Recover(stateRoot) + if err := t.tdb.Recover(stateRoot); err != nil { + return err + } + t.stateRoot = stateRoot + return nil } // Put performs an insert into the underlying ZKTrie database. @@ -312,10 +313,7 @@ func (t *ZKTrie) currentState() common.Hash { t.mtx.RLock() defer t.mtx.RUnlock() - if len(t.stateRoots) == 0 { - return types.EmptyRootHash - } - return t.stateRoots[len(t.stateRoots)-1] + return t.stateRoot } // TODO: update StateAccount balance @@ -440,7 +438,7 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { return types.EmptyRootHash, fmt.Errorf("update db: %w", err) } - t.stateRoots = append(t.stateRoots, newStateRoot) + t.stateRoot = newStateRoot return newStateRoot, nil } @@ -453,6 +451,5 @@ func (t *ZKTrie) Commit() error { if err := t.tdb.Commit(currState, true); err != nil { return fmt.Errorf("commit db: %w", err) } - t.stateRoots = []common.Hash{currState} return nil } From 35695fd11f8aea37064576af6c78022c35442d01 Mon Sep 17 00:00:00 2001 From: AL-CT Date: Thu, 13 Nov 2025 12:40:35 +0000 Subject: [PATCH 09/25] add delete --- zktrie/zktrie.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index e658ed1..7a526c2 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -225,6 +225,11 @@ func (t *ZKTrie) Get(key []byte) ([]byte, error) { return t.tdb.Disk().Get(key) } +// Del performs a delete on the underlying ZKTrie database. +func (t *ZKTrie) Del(key []byte) error { + return t.tdb.Disk().Delete(key) +} + // GetBlockInfo retrieves Block information from the reserved // metadata state account. func (t *ZKTrie) GetBlockInfo(blockHash chainhash.Hash) (BlockInfo, error) { From c9ad3c880eabdb2ce57d9e72b0d053912aa64a83 Mon Sep 17 00:00:00 2001 From: AL-CT Date: Thu, 13 Nov 2025 15:17:43 +0000 Subject: [PATCH 10/25] remove logging on init --- zktrie/go.mod | 2 ++ zktrie/zktrie.go | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zktrie/go.mod b/zktrie/go.mod index 6640472..d04b511 100644 --- a/zktrie/go.mod +++ b/zktrie/go.mod @@ -44,3 +44,5 @@ require ( golang.org/x/sys v0.37.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) + +replace github.com/hemilabs/x/eth-trie => /Users/toni/Documents/repos/x/eth-trie \ No newline at end of file diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index 7a526c2..7d957d8 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -11,7 +11,6 @@ import ( "sync" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" @@ -37,7 +36,6 @@ var ( func init() { const reserved string = "0xffffffffffffffffffffffffffffffffffffffff" MetadataAddress = common.BytesToAddress([]byte(reserved)) - spew.Dump(MetadataAddress) } // txId + index From a0ca805f018bdfc76e4c3ec5d25a116c8e7b969d Mon Sep 17 00:00:00 2001 From: AL-CT Date: Fri, 14 Nov 2025 11:24:38 +0000 Subject: [PATCH 11/25] add ancients dir to rawdb --- zktrie/zktrie.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index 7d957d8..2ff138d 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -8,6 +8,7 @@ import ( "encoding/binary" "errors" "fmt" + "path/filepath" "sync" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -178,7 +179,9 @@ func NewZKTrie(home string) (*ZKTrie, error) { var kv ethdb.KeyValueStore = db // high-level database wrapper for the given key-value store - disk, err := rawdb.Open(kv, rawdb.OpenOptions{}) + disk, err := rawdb.Open(kv, rawdb.OpenOptions{ + Ancient: filepath.Join(home, "ancients"), + }) if err != nil { return nil, fmt.Errorf("open rawdb: %w", err) } From 5f2010f402fa2d6067d0bc178fc2984e5d99362c Mon Sep 17 00:00:00 2001 From: AL-CT Date: Mon, 17 Nov 2025 12:18:28 +0000 Subject: [PATCH 12/25] temporarily log some pkscripts --- zktrie/zktrie.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index 2ff138d..6da83d8 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -12,6 +12,7 @@ import ( "sync" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" @@ -346,6 +347,8 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { return types.EmptyRootHash, fmt.Errorf("get account state: %w", err) } + spew.Dump(addr) + sa := types.NewEmptyStateAccount() if stateVal != nil { sa, err = types.FullAccount(stateVal) From 5f294103154ebe275fe82391fb493bde5840ebef Mon Sep 17 00:00:00 2001 From: AL-CT Date: Mon, 17 Nov 2025 12:58:06 +0000 Subject: [PATCH 13/25] add SetState function --- zktrie/zktrie.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index 6da83d8..fa8c118 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -199,6 +199,18 @@ func NewZKTrie(home string) (*ZKTrie, error) { return t, nil } +func (t *ZKTrie) SetCurrentState(state common.Hash) error { + ok, err := t.tdb.Recoverable(state) + if err != nil { + return err + } + if !ok { + return errors.New("state not available") + } + t.stateRoot = state + return nil +} + // Close closes the underlying database for ZKTrie. func (t *ZKTrie) Close() error { t.mtx.Lock() From d795f8ddcdd44f1e24fbd3cc4c595039df04dcaa Mon Sep 17 00:00:00 2001 From: AL-CT Date: Mon, 17 Nov 2025 13:04:38 +0000 Subject: [PATCH 14/25] update set state --- zktrie/zktrie.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index fa8c118..5cdc964 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -200,13 +200,10 @@ func NewZKTrie(home string) (*ZKTrie, error) { } func (t *ZKTrie) SetCurrentState(state common.Hash) error { - ok, err := t.tdb.Recoverable(state) + _, err := t.tdb.HistoricReader(state) if err != nil { return err } - if !ok { - return errors.New("state not available") - } t.stateRoot = state return nil } From bf194ed58911e205f5d6176347e1bc2a99d2451d Mon Sep 17 00:00:00 2001 From: AL-CT Date: Mon, 17 Nov 2025 13:11:27 +0000 Subject: [PATCH 15/25] squash me --- zktrie/zktrie.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index 5cdc964..2caa23d 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -200,10 +200,11 @@ func NewZKTrie(home string) (*ZKTrie, error) { } func (t *ZKTrie) SetCurrentState(state common.Hash) error { - _, err := t.tdb.HistoricReader(state) + start, end, err := t.tdb.HistoryRange() if err != nil { return err } + fmt.Printf("%d, %d\n", start, end) t.stateRoot = state return nil } From 36d9d7c41910fd87f99b9342f49bf74f2fb5c085 Mon Sep 17 00:00:00 2001 From: AL-CT Date: Mon, 17 Nov 2025 18:02:26 +0000 Subject: [PATCH 16/25] add states for queries --- zktrie/zktrie.go | 141 +++++++++++++++++++++--------------------- zktrie/zktrie_test.go | 32 +++++----- 2 files changed, 88 insertions(+), 85 deletions(-) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index 2caa23d..f6f87f9 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -164,9 +164,10 @@ func (b *ZKBlock) GetMetadata() BlockInfo { // ZKTrie is used to perform operation on a ZK trie and its database. type ZKTrie struct { - mtx sync.RWMutex - stateRoot common.Hash - tdb *triedb.Database + mtx sync.RWMutex + stateRoot common.Hash + uncommitedRoots map[common.Hash]struct{} + tdb *triedb.Database } // TODO: set database cache and handles @@ -188,27 +189,19 @@ func NewZKTrie(home string) (*ZKTrie, error) { } tdb := triedb.NewDatabase(disk, &triedb.Config{ PathDB: &pathdb.Config{ - NoAsyncFlush: true, + NoAsyncFlush: true, + EnableStateIndexing: true, }, }) t := &ZKTrie{ - tdb: tdb, - stateRoot: types.EmptyRootHash, + tdb: tdb, + stateRoot: types.EmptyRootHash, + uncommitedRoots: make(map[common.Hash]struct{}), } return t, nil } -func (t *ZKTrie) SetCurrentState(state common.Hash) error { - start, end, err := t.tdb.HistoryRange() - if err != nil { - return err - } - fmt.Printf("%d, %d\n", start, end) - t.stateRoot = state - return nil -} - // Close closes the underlying database for ZKTrie. func (t *ZKTrie) Close() error { t.mtx.Lock() @@ -224,6 +217,7 @@ func (t *ZKTrie) Recover(stateRoot common.Hash) error { return err } t.stateRoot = stateRoot + t.uncommitedRoots = make(map[common.Hash]struct{}) return nil } @@ -242,94 +236,98 @@ func (t *ZKTrie) Del(key []byte) error { return t.tdb.Disk().Delete(key) } +func (t *ZKTrie) inMemory(state common.Hash) bool { + t.mtx.RLock() + defer t.mtx.RUnlock() + _, ok := t.uncommitedRoots[state] + return ok || state.Cmp(t.stateRoot) == 0 +} + // GetBlockInfo retrieves Block information from the reserved // metadata state account. -func (t *ZKTrie) GetBlockInfo(blockHash chainhash.Hash) (BlockInfo, error) { +func (t *ZKTrie) GetBlockInfo(blockHash chainhash.Hash, state *common.Hash) (BlockInfo, error) { var ( + addrHash = crypto.Keccak256Hash(MetadataAddress[:]) stateRoot = t.currentState() - addrHash = crypto.Keccak256Hash(MetadataAddress.Bytes()) - keyHash = blockHash ) - sa, err := t.GetAccount(MetadataAddress) - if err != nil { - return BlockInfo{}, fmt.Errorf("get state account: %w", err) + if state != nil { + stateRoot = *state } - - storeID := trie.StorageTrieID(stateRoot, addrHash, sa.Root) - storeTrie, err := trie.New(storeID, t.tdb) - if err != nil { - return BlockInfo{}, fmt.Errorf("failed to load trie, err: %w", err) - } - val, err := storeTrie.Get(keyHash[:]) - if err != nil { - return BlockInfo{}, err + var b []byte + if t.inMemory(stateRoot) { + r, err := t.tdb.StateReader(stateRoot) + if err != nil { + return BlockInfo{}, err + } + b, err = r.Storage(addrHash, common.BytesToHash(blockHash[:])) + if err != nil { + return BlockInfo{}, err + } + } else { + r, err := t.tdb.HistoricReader(stateRoot) + if err != nil { + return BlockInfo{}, err + } + b, err = r.Storage(MetadataAddress, common.BytesToHash(blockHash[:])) + if err != nil { + return BlockInfo{}, err + } } - if val == nil { + if b == nil { return BlockInfo{}, ErrBlockNotFound } - - if len(val) != len(BlockInfo{}) { - return BlockInfo{}, fmt.Errorf("unexpected stored value size: %d", len(val)) + if len(b) != len(BlockInfo{}) { + return BlockInfo{}, fmt.Errorf("unexpected stored value size: %d", len(b)) } - return BlockInfo(val), nil + return BlockInfo(b), nil } -func (t *ZKTrie) GetOutpoint(pkScript []byte, out Outpoint) ([]byte, error) { +func (t *ZKTrie) GetOutpoint(pkScript []byte, out Outpoint, state *common.Hash) ([]byte, error) { var ( addr = common.BytesToAddress(pkScript) stateRoot = t.currentState() - addrHash = crypto.Keccak256Hash(addr.Bytes()) keyHash = crypto.Keccak256Hash(out[:]) ) - sa, err := t.GetAccount(addr) - if err != nil { - return nil, fmt.Errorf("get state account: %w", err) + if state != nil { + stateRoot = *state } - - storeID := trie.StorageTrieID(stateRoot, addrHash, sa.Root) - storeTrie, err := trie.New(storeID, t.tdb) - if err != nil { - return nil, fmt.Errorf("failed to load trie, err: %w", err) + if t.inMemory(stateRoot) { + r, err := t.tdb.StateReader(stateRoot) + if err != nil { + return nil, err + } + return r.Storage(crypto.Keccak256Hash(addr[:]), keyHash) } - val, err := storeTrie.Get(keyHash[:]) + r, err := t.tdb.HistoricReader(stateRoot) if err != nil { return nil, err } - if val == nil { - return nil, ErrOutpointNotFound - } - return val, nil + return r.Storage(addr, keyHash) } -func (t *ZKTrie) GetAccount(addr common.Address) (*types.StateAccount, error) { - var ( - stateRoot = t.currentState() - stateID = trie.StateTrieID(stateRoot) - addrHash = crypto.Keccak256Hash(addr.Bytes()) - ) - stateTrie, err := trie.New(stateID, t.tdb) - if err != nil { - return nil, fmt.Errorf("failed to load state trie, err: %w", err) - } - stateVal, err := stateTrie.Get(addrHash[:]) - if err != nil { - return nil, fmt.Errorf("get account state: %w", err) +func (t *ZKTrie) GetAccount(addr common.Address, state *common.Hash) (*types.SlimAccount, error) { + var stateRoot = t.currentState() + if state != nil { + stateRoot = *state } - if stateVal == nil { - return nil, ErrAddressNotFound + if t.inMemory(stateRoot) { + r, err := t.tdb.StateReader(stateRoot) + if err != nil { + return nil, err + } + return r.Account(crypto.Keccak256Hash(addr[:])) } - sa, err := types.FullAccount(stateVal) + r, err := t.tdb.HistoricReader(stateRoot) if err != nil { - return nil, fmt.Errorf("error restoring account: %w", err) + return nil, err } - return sa, nil + return r.Account(addr) } // currentState gets the most recent State Root. func (t *ZKTrie) currentState() common.Hash { t.mtx.RLock() defer t.mtx.RUnlock() - return t.stateRoot } @@ -458,6 +456,7 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { } t.stateRoot = newStateRoot + t.uncommitedRoots[newStateRoot] = struct{}{} return newStateRoot, nil } @@ -466,9 +465,9 @@ func (t *ZKTrie) Commit() error { currState := t.currentState() t.mtx.Lock() defer t.mtx.Unlock() - if err := t.tdb.Commit(currState, true); err != nil { return fmt.Errorf("commit db: %w", err) } + t.uncommitedRoots = make(map[common.Hash]struct{}) return nil } diff --git a/zktrie/zktrie_test.go b/zktrie/zktrie_test.go index 652ba74..0244485 100644 --- a/zktrie/zktrie_test.go +++ b/zktrie/zktrie_test.go @@ -43,6 +43,7 @@ func TestZKTrie(t *testing.T) { prevBlock := *chaincfg.TestNet3Params.GenesisHash prevStateRoot := types.EmptyRootHash outpoints := make(map[uint64][]Outpoint) + states := make([]common.Hash, blockCount) for i := range blockCount { bh := chainhash.Hash(random(32)) blk := NewZKBlock(bh, prevBlock, prevStateRoot, i) @@ -79,32 +80,35 @@ func TestZKTrie(t *testing.T) { t.Logf("inserted block %d, new state root: %v", blk.GetMetadata().Height(), sr) prevBlock = bh prevStateRoot = sr + states[i] = sr } if err := zkt.Commit(); err != nil { t.Fatal(err) } - for ac, keys := range outpoints { - var pkScript [8]byte - binary.BigEndian.PutUint64(pkScript[:], ac) - addr := common.BytesToAddress(pkScript[:]) - sa, err := zkt.GetAccount(addr) - if err != nil { - t.Fatal(err) - } - spew.Dump(sa) - - for _, k := range keys { - v, err := zkt.GetOutpoint(pkScript[:], k) + for _, state := range states { + for ac, keys := range outpoints { + var pkScript [8]byte + binary.BigEndian.PutUint64(pkScript[:], ac) + addr := common.BytesToAddress(pkScript[:]) + sa, err := zkt.GetAccount(addr, &state) if err != nil { t.Fatal(err) } - t.Logf("address %x, key %x, value %x", ac, k, v) + spew.Dump(sa) + + for _, k := range keys { + v, err := zkt.GetOutpoint(pkScript[:], k, &state) + if err != nil { + t.Fatal(err) + } + t.Logf("address %x, key %x, value %x", ac, k, v) + } } } - md, err := zkt.GetBlockInfo(prevBlock) + md, err := zkt.GetBlockInfo(prevBlock, nil) if err != nil { t.Fatal(err) } From a8f2a478fc9f88139d00e1018e73e43380c00970 Mon Sep 17 00:00:00 2001 From: AL-CT Date: Mon, 17 Nov 2025 18:36:40 +0000 Subject: [PATCH 17/25] add sync function --- zktrie/zktrie.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index f6f87f9..282f88e 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -282,6 +282,10 @@ func (t *ZKTrie) GetBlockInfo(blockHash chainhash.Hash, state *common.Hash) (Blo return BlockInfo(b), nil } +func (t *ZKTrie) Sync() error { + return t.tdb.Disk().SyncAncient() +} + func (t *ZKTrie) GetOutpoint(pkScript []byte, out Outpoint, state *common.Hash) ([]byte, error) { var ( addr = common.BytesToAddress(pkScript) From b0baf83b368942bb7e99154b67aebe82d8a9b4d0 Mon Sep 17 00:00:00 2001 From: AL-CT Date: Mon, 17 Nov 2025 18:40:10 +0000 Subject: [PATCH 18/25] replace sync with sync progression --- zktrie/zktrie.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index 282f88e..1d288d7 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -189,7 +189,7 @@ func NewZKTrie(home string) (*ZKTrie, error) { } tdb := triedb.NewDatabase(disk, &triedb.Config{ PathDB: &pathdb.Config{ - NoAsyncFlush: true, + // NoAsyncFlush: true, EnableStateIndexing: true, }, }) @@ -282,8 +282,8 @@ func (t *ZKTrie) GetBlockInfo(blockHash chainhash.Hash, state *common.Hash) (Blo return BlockInfo(b), nil } -func (t *ZKTrie) Sync() error { - return t.tdb.Disk().SyncAncient() +func (t *ZKTrie) SyncProgress() (uint64, error) { + return t.tdb.IndexProgress() } func (t *ZKTrie) GetOutpoint(pkScript []byte, out Outpoint, state *common.Hash) ([]byte, error) { From 12f04c52678139d7491708f6f848a768f89b4e68 Mon Sep 17 00:00:00 2001 From: AL-CT Date: Mon, 17 Nov 2025 18:47:19 +0000 Subject: [PATCH 19/25] re-add sync --- zktrie/zktrie.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index 1d288d7..74dc6ff 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -282,6 +282,10 @@ func (t *ZKTrie) GetBlockInfo(blockHash chainhash.Hash, state *common.Hash) (Blo return BlockInfo(b), nil } +func (t *ZKTrie) Sync() error { + return t.tdb.Disk().SyncAncient() +} + func (t *ZKTrie) SyncProgress() (uint64, error) { return t.tdb.IndexProgress() } From 01a996a414e0b02c87703ac8c8291f7cf016f5aa Mon Sep 17 00:00:00 2001 From: AL-CT Date: Tue, 18 Nov 2025 10:42:40 +0000 Subject: [PATCH 20/25] cleanup --- zktrie/go.mod | 2 -- zktrie/zktrie.go | 4 ---- 2 files changed, 6 deletions(-) diff --git a/zktrie/go.mod b/zktrie/go.mod index d04b511..6640472 100644 --- a/zktrie/go.mod +++ b/zktrie/go.mod @@ -44,5 +44,3 @@ require ( golang.org/x/sys v0.37.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) - -replace github.com/hemilabs/x/eth-trie => /Users/toni/Documents/repos/x/eth-trie \ No newline at end of file diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index 74dc6ff..742e012 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -12,7 +12,6 @@ import ( "sync" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" @@ -339,7 +338,6 @@ func (t *ZKTrie) currentState() common.Hash { return t.stateRoot } -// TODO: update StateAccount balance // InsertBlock performs a state transition for a given block. func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { var ( @@ -363,8 +361,6 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { return types.EmptyRootHash, fmt.Errorf("get account state: %w", err) } - spew.Dump(addr) - sa := types.NewEmptyStateAccount() if stateVal != nil { sa, err = types.FullAccount(stateVal) From c20190698b2c354f803c18cafb63b719c8509b38 Mon Sep 17 00:00:00 2001 From: AL-CT Date: Thu, 11 Dec 2025 17:12:17 +0000 Subject: [PATCH 21/25] slight optimizations --- zktrie/zktrie.go | 39 +++++++++-------- zktrie/zktrie_test.go | 99 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 17 deletions(-) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index 742e012..e24b5ef 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -189,6 +189,7 @@ func NewZKTrie(home string) (*ZKTrie, error) { tdb := triedb.NewDatabase(disk, &triedb.Config{ PathDB: &pathdb.Config{ // NoAsyncFlush: true, + StateHistory: 1000, // store last 1000 blocks EnableStateIndexing: true, }, }) @@ -361,12 +362,18 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { return types.EmptyRootHash, fmt.Errorf("get account state: %w", err) } - sa := types.NewEmptyStateAccount() + var ( + sa *types.StateAccount + skipGet bool + ) if stateVal != nil { sa, err = types.FullAccount(stateVal) if err != nil { panic(fmt.Errorf("decode stored state value: %w", err)) } + } else { + sa = types.NewEmptyStateAccount() + skipGet = true } na := types.StateAccount{ Balance: sa.Balance, @@ -382,31 +389,31 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { mutatedStore[addrHash] = make(map[common.Hash][]byte, len(block.storage[addr])) originStore[addr] = make(map[common.Hash][]byte, len(block.storage[addr])) - for key, value := range storage { - prev, err := storeTrie.Get(key[:]) - if err != nil { - return types.EmptyRootHash, fmt.Errorf("get storage trie value: %w", err) + for key, v := range storage { + var prev []byte + if !skipGet { + prev, err = storeTrie.Get(key[:]) + if err != nil { + return types.EmptyRootHash, fmt.Errorf("get storage trie value: %w", err) + } } - originStore[addr][key] = prev - mutatedStore[addrHash][key] = value - } - - for k, v := range mutatedStore[addrHash] { - if err := storeTrie.Update(k[:], v); err != nil { + if err := storeTrie.Update(key[:], v); err != nil { return types.EmptyRootHash, fmt.Errorf("update storage trie: %w", err) } + mutatedStore[addrHash][key] = v + originStore[addr][key] = prev + switch len(v) { case len(SpendableOutput{}): p := uint256.NewInt(binary.BigEndian.Uint64(v[68:76])) na.Balance.Add(na.Balance, p) case len(SpentOutput{}): - val := originStore[addr][k] - if val == nil { + if prev == nil { // If out was created and spent in the same block, // then don't update the balance. continue } - pr := originStore[addr][k][68:76] + pr := prev[68:76] p := uint256.NewInt(binary.BigEndian.Uint64(pr)) na.Balance.Sub(na.Balance, p) case len(BlockInfo{}): @@ -428,10 +435,8 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { } mutatedAcc[addrHash] = full originAcc[addr] = stateVal - } - for key, val := range mutatedAcc { - if err := stateTrie.Update(key.Bytes(), val); err != nil { + if err := stateTrie.Update(addrHash[:], full); err != nil { return types.EmptyRootHash, fmt.Errorf("update accounts trie: %w", err) } } diff --git a/zktrie/zktrie_test.go b/zktrie/zktrie_test.go index 0244485..c4ce27a 100644 --- a/zktrie/zktrie_test.go +++ b/zktrie/zktrie_test.go @@ -119,6 +119,105 @@ func TestZKTrie(t *testing.T) { } } +func BenchmarkZKTrie(b *testing.B) { + const ( + newAddressNum uint64 = 100 + reuseAddressNum uint64 = 100 + outpointPerReusedAddr uint64 = 5 + outpointPerNewAddr uint64 = 5 + ) + home := b.TempDir() + + zkt, err := NewZKTrie(home) + if err != nil { + b.Fatal(err) + } + + // Pre-insert N outs for reuse + prevBlock := *chaincfg.TestNet3Params.GenesisHash + prevStateRoot := types.EmptyRootHash + outpoints := make(map[uint64][]Outpoint) + + bh := chainhash.Hash(random(32)) + blk := NewZKBlock(bh, prevBlock, prevStateRoot, 0) + + // simulate outs + for i := range reuseAddressNum { + var pkScript [8]byte + binary.BigEndian.PutUint64(pkScript[:], i) + outpoints[i] = make([]Outpoint, outpointPerReusedAddr) + for j := range outpointPerReusedAddr { + o := NewOutpoint([32]byte(random(32)), 1) + so := NewSpendableOutput(blk.blockHash, [32]byte(random(32)), 1, 100) + blk.NewOut(pkScript[:], o, so) + outpoints[i][j] = o + } + } + + sr, err := zkt.InsertBlock(blk) + if err != nil { + b.Fatal(err) + } + b.Logf("inserted block %d, new state root: %v", blk.GetMetadata().Height(), sr) + + if err := zkt.Commit(); err != nil { + b.Fatal(err) + } + + bhIn := chainhash.Hash(random(32)) + blkIn := NewZKBlock(bhIn, bh, sr, 1) + + for i := range reuseAddressNum { + var pkScript [8]byte + binary.BigEndian.PutUint64(pkScript[:], i) + for _, o := range outpoints[i] { + so := NewSpentOutput(blkIn.blockHash, [32]byte(random(32)), 100) + blkIn.NewIn(pkScript[:], o, so) + } + } + + for i := range newAddressNum { + var pkScript [8]byte + binary.BigEndian.PutUint64(pkScript[:], i+reuseAddressNum) + for range outpointPerNewAddr { + o := NewOutpoint([32]byte(random(32)), 1) + so := NewSpendableOutput(blkIn.blockHash, [32]byte(random(32)), 1, 100) + blkIn.NewOut(pkScript[:], o, so) + } + } + + b.Run("Block Insert", func(b *testing.B) { + for b.Loop() { + _, err := zkt.InsertBlock(blkIn) + if err != nil { + b.Fatal(err) + } + // if err := zkt.Commit(); err != nil { + // b.Fatal(err) + // } + // if err := zkt.Recover(sr); err != nil { + // b.Fatal(err) + // } + } + }) + + b.Run("Block Commit And Revert", func(b *testing.B) { + for b.Loop() { + _, err := zkt.InsertBlock(blkIn) + if err != nil { + b.Fatal(err) + } + if err := zkt.Commit(); err != nil { + b.Fatal(err) + } + if err := zkt.Recover(sr); err != nil { + b.Fatal(err) + } + } + }) + +} + // Random returns a variable number of random bytes. func random(n int) []byte { buffer := make([]byte, n) From 3d27206e7566dc4ccb931c9860ccd44df345d440 Mon Sep 17 00:00:00 2001 From: AL-CT Date: Wed, 7 Jan 2026 10:37:21 +0000 Subject: [PATCH 22/25] add short circuit for spendable outs --- zktrie/zktrie.go | 4 ++++ zktrie/zktrie_test.go | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index e24b5ef..5479cf6 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -48,6 +48,7 @@ func NewOutpoint(txid [32]byte, index uint32) (op Outpoint) { return op } +// TODO: encode with value and then crop to prevent get for balance update? // blockhash + txId + txInIdx type SpentOutput [32 + 32 + 4]byte @@ -390,6 +391,9 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { mutatedStore[addrHash] = make(map[common.Hash][]byte, len(block.storage[addr])) originStore[addr] = make(map[common.Hash][]byte, len(block.storage[addr])) for key, v := range storage { + if len(v) == len(SpendableOutput{}) { + skipGet = true + } var prev []byte if !skipGet { prev, err = storeTrie.Get(key[:]) diff --git a/zktrie/zktrie_test.go b/zktrie/zktrie_test.go index c4ce27a..b238c29 100644 --- a/zktrie/zktrie_test.go +++ b/zktrie/zktrie_test.go @@ -121,8 +121,8 @@ func TestZKTrie(t *testing.T) { func BenchmarkZKTrie(b *testing.B) { const ( - newAddressNum uint64 = 100 - reuseAddressNum uint64 = 100 + newAddressNum uint64 = 10000 + reuseAddressNum uint64 = 10000 outpointPerReusedAddr uint64 = 5 outpointPerNewAddr uint64 = 5 ) From 4f4a1aaa68f0e7c4517acc24a64779867b4d608e Mon Sep 17 00:00:00 2001 From: AL-CT Date: Wed, 28 Jan 2026 11:24:52 +0000 Subject: [PATCH 23/25] fix skip get bug --- zktrie/zktrie.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index 5479cf6..0ccb0f5 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -364,8 +364,8 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { } var ( - sa *types.StateAccount - skipGet bool + sa *types.StateAccount + newAccount bool ) if stateVal != nil { sa, err = types.FullAccount(stateVal) @@ -374,7 +374,7 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { } } else { sa = types.NewEmptyStateAccount() - skipGet = true + newAccount = true } na := types.StateAccount{ Balance: sa.Balance, @@ -391,6 +391,7 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { mutatedStore[addrHash] = make(map[common.Hash][]byte, len(block.storage[addr])) originStore[addr] = make(map[common.Hash][]byte, len(block.storage[addr])) for key, v := range storage { + skipGet := newAccount if len(v) == len(SpendableOutput{}) { skipGet = true } From 165f72d170d1310ac25a50b1d0d10c28e7285b5f Mon Sep 17 00:00:00 2001 From: AL-CT Date: Wed, 4 Feb 2026 14:32:52 +0000 Subject: [PATCH 24/25] use pathdb v1 with rawkey --- zktrie/go.mod | 2 ++ zktrie/zktrie.go | 60 +++++++++++++++++++++++++++---------------- zktrie/zktrie_test.go | 45 +++++++++++++++++++++++++------- 3 files changed, 76 insertions(+), 31 deletions(-) diff --git a/zktrie/go.mod b/zktrie/go.mod index 6640472..ef0ae5a 100644 --- a/zktrie/go.mod +++ b/zktrie/go.mod @@ -2,6 +2,8 @@ module github.com/hemilabs/x/zktrie go 1.25.1 +replace github.com/hemilabs/x/eth-trie => ../eth-trie + require ( github.com/btcsuite/btcd v0.25.0 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index 0ccb0f5..a43891e 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -105,6 +105,7 @@ type ZKBlock struct { blockHash common.Hash // Storage information. Automatically managed using the utility methods. + // The map key is the hashed storage key (crypto.Keccak256Hash of the raw key). storage map[common.Address]map[common.Hash][]byte } @@ -131,8 +132,8 @@ func (b *ZKBlock) NewOut(pkScript []byte, out Outpoint, so SpendableOutput) { b.storage[addr] = make(map[common.Hash][]byte) } - key := crypto.Keccak256Hash(out[:]) - b.storage[addr][key] = so[:] + rawKey := crypto.Keccak256Hash(out[:]) + b.storage[addr][rawKey] = so[:] } func (b *ZKBlock) NewIn(pkScript []byte, out Outpoint, so SpentOutput) { @@ -145,8 +146,8 @@ func (b *ZKBlock) NewIn(pkScript []byte, out Outpoint, so SpentOutput) { b.storage[addr] = make(map[common.Hash][]byte) } - key := crypto.Keccak256Hash(out[:]) - b.storage[addr][key] = so[:] + rawKey := crypto.Keccak256Hash(out[:]) + b.storage[addr][rawKey] = so[:] } func (b *ZKBlock) GetOutpoint(pkScript []byte, out Outpoint) []byte { @@ -154,12 +155,13 @@ func (b *ZKBlock) GetOutpoint(pkScript []byte, out Outpoint) []byte { if b.storage == nil || b.storage[addr] == nil { return nil } - key := crypto.Keccak256Hash(out[:]) - return b.storage[addr][key] + rawKey := crypto.Keccak256Hash(out[:]) + return b.storage[addr][rawKey] } func (b *ZKBlock) GetMetadata() BlockInfo { - return BlockInfo(b.storage[MetadataAddress][b.blockHash]) + rawKey := b.blockHash + return BlockInfo(b.storage[MetadataAddress][rawKey]) } // ZKTrie is used to perform operation on a ZK trie and its database. @@ -250,6 +252,10 @@ func (t *ZKTrie) GetBlockInfo(blockHash chainhash.Hash, state *common.Hash) (Blo var ( addrHash = crypto.Keccak256Hash(MetadataAddress[:]) stateRoot = t.currentState() + // Use the same key derivation as NewZKBlock: + // rawKey = blockHash, trieKey = keccak256(blockHash) + rawKey = common.BytesToHash(blockHash[:]) + trieKey = crypto.Keccak256Hash(rawKey[:]) ) if state != nil { stateRoot = *state @@ -260,7 +266,8 @@ func (t *ZKTrie) GetBlockInfo(blockHash chainhash.Hash, state *common.Hash) (Blo if err != nil { return BlockInfo{}, err } - b, err = r.Storage(addrHash, common.BytesToHash(blockHash[:])) + // StateReader expects hashed keys + b, err = r.Storage(addrHash, trieKey) if err != nil { return BlockInfo{}, err } @@ -269,7 +276,8 @@ func (t *ZKTrie) GetBlockInfo(blockHash chainhash.Hash, state *common.Hash) (Blo if err != nil { return BlockInfo{}, err } - b, err = r.Storage(MetadataAddress, common.BytesToHash(blockHash[:])) + // HistoricReader expects raw address and raw key (which will be hashed internally) + b, err = r.Storage(MetadataAddress, rawKey) if err != nil { return BlockInfo{}, err } @@ -295,7 +303,8 @@ func (t *ZKTrie) GetOutpoint(pkScript []byte, out Outpoint, state *common.Hash) var ( addr = common.BytesToAddress(pkScript) stateRoot = t.currentState() - keyHash = crypto.Keccak256Hash(out[:]) + rawKey = crypto.Keccak256Hash(out[:]) + trieKey = crypto.Keccak256Hash(rawKey[:]) ) if state != nil { stateRoot = *state @@ -305,13 +314,15 @@ func (t *ZKTrie) GetOutpoint(pkScript []byte, out Outpoint, state *common.Hash) if err != nil { return nil, err } - return r.Storage(crypto.Keccak256Hash(addr[:]), keyHash) + // StateReader expects hashed keys + return r.Storage(crypto.Keccak256Hash(addr[:]), trieKey) } r, err := t.tdb.HistoricReader(stateRoot) if err != nil { return nil, err } - return r.Storage(addr, keyHash) + // HistoricReader expects raw address and raw key (which will be hashed internally) + return r.Storage(addr, rawKey) } func (t *ZKTrie) GetAccount(addr common.Address, state *common.Hash) (*types.SlimAccount, error) { @@ -390,27 +401,32 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { mutatedStore[addrHash] = make(map[common.Hash][]byte, len(block.storage[addr])) originStore[addr] = make(map[common.Hash][]byte, len(block.storage[addr])) - for key, v := range storage { + for rawKey, value := range storage { + trieKey := crypto.Keccak256Hash(rawKey[:]) skipGet := newAccount - if len(v) == len(SpendableOutput{}) { + if len(value) == len(SpendableOutput{}) { skipGet = true } var prev []byte if !skipGet { - prev, err = storeTrie.Get(key[:]) + prev, err = storeTrie.Get(trieKey[:]) if err != nil { return types.EmptyRootHash, fmt.Errorf("get storage trie value: %w", err) } } - if err := storeTrie.Update(key[:], v); err != nil { + if err := storeTrie.Update(trieKey[:], value); err != nil { return types.EmptyRootHash, fmt.Errorf("update storage trie: %w", err) } - mutatedStore[addrHash][key] = v - originStore[addr][key] = prev - - switch len(v) { + // mutatedStore (storageData) uses the hashed key (trieKey) for state lookups. + // originStore uses the raw key. + // With RawStorageKey=true, the raw key is stored in state history and + // will be hashed during rollback to produce the correct trie key. + mutatedStore[addrHash][trieKey] = value + originStore[addr][rawKey] = prev + + switch len(value) { case len(SpendableOutput{}): - p := uint256.NewInt(binary.BigEndian.Uint64(v[68:76])) + p := uint256.NewInt(binary.BigEndian.Uint64(value[68:76])) na.Balance.Add(na.Balance, p) case len(SpentOutput{}): if prev == nil { @@ -458,7 +474,7 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { AccountsOrigin: originAcc, Storages: mutatedStore, StoragesOrigin: originStore, - RawStorageKey: false, + RawStorageKey: true, } t.mtx.Lock() diff --git a/zktrie/zktrie_test.go b/zktrie/zktrie_test.go index b238c29..0a775eb 100644 --- a/zktrie/zktrie_test.go +++ b/zktrie/zktrie_test.go @@ -40,10 +40,21 @@ func TestZKTrie(t *testing.T) { t.Fatalf("got %s, wanted %s", v, []byte("world")) } - prevBlock := *chaincfg.TestNet3Params.GenesisHash - prevStateRoot := types.EmptyRootHash + // Create an initial empty block to establish the empty state in the database + emptyBlock := NewZKBlock(chainhash.Hash([32]byte{}), *chaincfg.TestNet3Params.GenesisHash, types.EmptyRootHash, 0) + initialStateRoot, err := zkt.InsertBlock(emptyBlock) + if err != nil { + t.Fatal(err) + } + if err := zkt.Commit(); err != nil { + t.Fatal(err) + } + + prevBlock := chainhash.Hash([32]byte{}) + prevStateRoot := initialStateRoot outpoints := make(map[uint64][]Outpoint) states := make([]common.Hash, blockCount) + blocks := make([]chainhash.Hash, blockCount) for i := range blockCount { bh := chainhash.Hash(random(32)) blk := NewZKBlock(bh, prevBlock, prevStateRoot, i) @@ -81,6 +92,7 @@ func TestZKTrie(t *testing.T) { prevBlock = bh prevStateRoot = sr states[i] = sr + blocks[i] = bh } if err := zkt.Commit(); err != nil { @@ -88,6 +100,7 @@ func TestZKTrie(t *testing.T) { } for _, state := range states { + var found int for ac, keys := range outpoints { var pkScript [8]byte binary.BigEndian.PutUint64(pkScript[:], ac) @@ -97,24 +110,38 @@ func TestZKTrie(t *testing.T) { t.Fatal(err) } spew.Dump(sa) - for _, k := range keys { v, err := zkt.GetOutpoint(pkScript[:], k, &state) if err != nil { t.Fatal(err) } - t.Logf("address %x, key %x, value %x", ac, k, v) + if v != nil { + found++ + t.Logf("address %x, key %x, value %x", ac, k, v) + } } } + if found < 1 { + t.Fatalf("no outpoints retrieved for state %x", state) + } } - md, err := zkt.GetBlockInfo(prevBlock, nil) - if err != nil { - t.Fatal(err) + for _, blk := range blocks { + md, err := zkt.GetBlockInfo(blk, nil) + if err != nil { + t.Fatal(err) + } + spew.Dump(md) + } + + for i := len(states) - 2; i >= 0; i-- { + if err := zkt.Recover(states[i]); err != nil { + t.Fatal(err) + } + t.Logf("Rolled back to state %v", states[i]) } - spew.Dump(md) - if err := zkt.Recover(types.EmptyRootHash); err != nil { + if err := zkt.Recover(initialStateRoot); err != nil { t.Fatal(err) } } From 1f7e66bb2e72bd56a246f9886c7abc4be0ab3237 Mon Sep 17 00:00:00 2001 From: AL-CT Date: Wed, 4 Feb 2026 17:18:36 +0000 Subject: [PATCH 25/25] Add configs to zktrie --- zktrie/zktrie.go | 58 ++++++++++++++++++++++++++++++++----------- zktrie/zktrie_test.go | 51 ++++++++++++++++++++++++++++--------- 2 files changed, 82 insertions(+), 27 deletions(-) diff --git a/zktrie/zktrie.go b/zktrie/zktrie.go index a43891e..0985c42 100644 --- a/zktrie/zktrie.go +++ b/zktrie/zktrie.go @@ -164,17 +164,41 @@ func (b *ZKBlock) GetMetadata() BlockInfo { return BlockInfo(b.storage[MetadataAddress][rawKey]) } +type Config struct { + Home string + + // Trie + KeepSpentOuts bool // Keep spent outputs in Trie or delete spendable ones + + // LevelDB + OpenFilesCache int // capacity of the open files caching + CacheSize int // Block cache and Write buffer capacity + + // PathDB + StateIndexing uint64 // Blocks to maintain state history for, 0: full chain +} + +func NewDefaultConfig(home string) *Config { + return &Config{ + Home: home, + KeepSpentOuts: false, + StateIndexing: 1000, + } +} + // ZKTrie is used to perform operation on a ZK trie and its database. type ZKTrie struct { - mtx sync.RWMutex + mtx sync.RWMutex + + cfg *Config + stateRoot common.Hash uncommitedRoots map[common.Hash]struct{} tdb *triedb.Database } -// TODO: set database cache and handles -func NewZKTrie(home string) (*ZKTrie, error) { - db, err := leveldb.New(home, 0, 0, "", false) +func NewZKTrie(cfg *Config) (*ZKTrie, error) { + db, err := leveldb.New(cfg.Home, cfg.CacheSize, cfg.OpenFilesCache, "", false) if err != nil { return nil, fmt.Errorf("open database: %w", err) } @@ -184,16 +208,15 @@ func NewZKTrie(home string) (*ZKTrie, error) { // high-level database wrapper for the given key-value store disk, err := rawdb.Open(kv, rawdb.OpenOptions{ - Ancient: filepath.Join(home, "ancients"), + Ancient: filepath.Join(cfg.Home, "ancients"), }) if err != nil { return nil, fmt.Errorf("open rawdb: %w", err) } tdb := triedb.NewDatabase(disk, &triedb.Config{ PathDB: &pathdb.Config{ - // NoAsyncFlush: true, - StateHistory: 1000, // store last 1000 blocks - EnableStateIndexing: true, + StateHistory: cfg.StateIndexing, + EnableStateIndexing: cfg.StateIndexing > 0, }, }) @@ -201,6 +224,7 @@ func NewZKTrie(home string) (*ZKTrie, error) { tdb: tdb, stateRoot: types.EmptyRootHash, uncommitedRoots: make(map[common.Hash]struct{}), + cfg: cfg, } return t, nil } @@ -414,14 +438,18 @@ func (t *ZKTrie) InsertBlock(block *ZKBlock) (common.Hash, error) { return types.EmptyRootHash, fmt.Errorf("get storage trie value: %w", err) } } - if err := storeTrie.Update(trieKey[:], value); err != nil { - return types.EmptyRootHash, fmt.Errorf("update storage trie: %w", err) + + if len(value) == len(SpentOutput{}) && !t.cfg.KeepSpentOuts { + if err := storeTrie.Delete(trieKey[:]); err != nil { + return types.EmptyRootHash, fmt.Errorf("delete storage key: %w", err) + } + mutatedStore[addrHash][trieKey] = nil + } else { + if err := storeTrie.Update(trieKey[:], value); err != nil { + return types.EmptyRootHash, fmt.Errorf("update storage trie: %w", err) + } + mutatedStore[addrHash][trieKey] = value } - // mutatedStore (storageData) uses the hashed key (trieKey) for state lookups. - // originStore uses the raw key. - // With RawStorageKey=true, the raw key is stored in state history and - // will be hashed during rollback to produce the correct trie key. - mutatedStore[addrHash][trieKey] = value originStore[addr][rawKey] = prev switch len(value) { diff --git a/zktrie/zktrie_test.go b/zktrie/zktrie_test.go index 0a775eb..1b9683c 100644 --- a/zktrie/zktrie_test.go +++ b/zktrie/zktrie_test.go @@ -24,8 +24,10 @@ func TestZKTrie(t *testing.T) { storageKeys uint64 = 5 ) home := t.TempDir() + cfg := NewDefaultConfig(home) + cfg.KeepSpentOuts = true - zkt, err := NewZKTrie(home) + zkt, err := NewZKTrie(cfg) if err != nil { t.Fatal(err) } @@ -52,7 +54,8 @@ func TestZKTrie(t *testing.T) { prevBlock := chainhash.Hash([32]byte{}) prevStateRoot := initialStateRoot - outpoints := make(map[uint64][]Outpoint) + newOutpoints := make(map[uint64][]Outpoint) + usedOutpoints := make(map[uint64][]Outpoint, 0) states := make([]common.Hash, blockCount) blocks := make([]chainhash.Hash, blockCount) for i := range blockCount { @@ -62,25 +65,29 @@ func TestZKTrie(t *testing.T) { // simulate outs var pkScript [8]byte binary.BigEndian.PutUint64(pkScript[:], i) - outpoints[i] = make([]Outpoint, 0) + newOutpoints[i] = make([]Outpoint, 0) for range storageKeys { o := NewOutpoint([32]byte(random(32)), 1) so := NewSpendableOutput(blk.blockHash, [32]byte(random(32)), 1, 100) blk.NewOut(pkScript[:], o, so) - outpoints[i] = append(outpoints[i], o) + newOutpoints[i] = append(newOutpoints[i], o) } // simulate an in - for ac, keys := range outpoints { + for ac, keys := range newOutpoints { if len(keys) <= 1 { continue } var pkScript [8]byte binary.BigEndian.PutUint64(pkScript[:], ac) o := keys[0] - outpoints[ac] = keys[1:] + newOutpoints[ac] = keys[1:] so := NewSpentOutput(blk.blockHash, [32]byte(random(32)), 1) blk.NewIn(pkScript[:], o, so) + if _, ok := usedOutpoints[ac]; !ok { + usedOutpoints[ac] = make([]Outpoint, 0) + } + usedOutpoints[ac] = append(usedOutpoints[ac], o) break } @@ -99,9 +106,9 @@ func TestZKTrie(t *testing.T) { t.Fatal(err) } - for _, state := range states { + for n, state := range states { var found int - for ac, keys := range outpoints { + for ac, keys := range newOutpoints { var pkScript [8]byte binary.BigEndian.PutUint64(pkScript[:], ac) addr := common.BytesToAddress(pkScript[:]) @@ -115,14 +122,32 @@ func TestZKTrie(t *testing.T) { if err != nil { t.Fatal(err) } - if v != nil { + if len(v) != 0 { + found++ + t.Logf("address %x, key %x, value %x", ac, k, v) + } + } + } + + for ac, keys := range usedOutpoints { + var pkScript [8]byte + binary.BigEndian.PutUint64(pkScript[:], ac) + for _, k := range keys { + v, err := zkt.GetOutpoint(pkScript[:], k, &state) + if err != nil { + t.Fatal(err) + } + if len(v) != 0 { found++ t.Logf("address %x, key %x, value %x", ac, k, v) } } } - if found < 1 { - t.Fatalf("no outpoints retrieved for state %x", state) + + expected := int(storageKeys) + (n * int(storageKeys)) + if found != expected { + t.Errorf("expected %d outpoints for state %v, got %d", + expected, state, found) } } @@ -154,8 +179,10 @@ func BenchmarkZKTrie(b *testing.B) { outpointPerNewAddr uint64 = 5 ) home := b.TempDir() + cfg := NewDefaultConfig(home) + cfg.KeepSpentOuts = true - zkt, err := NewZKTrie(home) + zkt, err := NewZKTrie(cfg) if err != nil { b.Fatal(err) }