diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 89a0509..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,647 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "argon2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9" -dependencies = [ - "base64ct", - "blake2", - "cpufeatures", - "password-hash", -] - -[[package]] -name = "arrayref" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "bessie" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30407622e75e71bbc4f53708a3016c75eb960ed417ec11abeae034bd78e1a196" -dependencies = [ - "blake3", - "rand", -] - -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" - -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest", -] - -[[package]] -name = "blake3" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bytes" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" - -[[package]] -name = "cc" -version = "1.0.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "constant_time_eq" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" - -[[package]] -name = "cpufeatures" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "errno" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "fastrand" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "kytz" -version = "0.1.0" -dependencies = [ - "argon2", - "bessie", - "bytes", - "rand", - "thiserror", - "z32", - "zeroize", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.150" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" - -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - -[[package]] -name = "linux-raw-sys" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" - -[[package]] -name = "mast" -version = "0.1.0" -dependencies = [ - "blake3", - "proptest", - "redb", - "tempfile", - "thiserror", -] - -[[package]] -name = "num-traits" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "password-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" -dependencies = [ - "base64ct", - "rand_core", - "subtle", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "proc-macro2" -version = "1.0.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proptest" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" -dependencies = [ - "bit-set", - "bit-vec", - "bitflags 2.4.1", - "lazy_static", - "num-traits", - "rand", - "rand_chacha", - "rand_xorshift", - "regex-syntax", - "rusty-fork", - "tempfile", - "unarray", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - -[[package]] -name = "quote" -version = "1.0.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_xorshift" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" -dependencies = [ - "rand_core", -] - -[[package]] -name = "redb" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08837f9a129bde83c51953b8c96cbb3422b940166b730caa954836106eb1dfd2" -dependencies = [ - "libc", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "regex-syntax" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" - -[[package]] -name = "rustix" -version = "0.38.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" -dependencies = [ - "bitflags 2.4.1", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rusty-fork" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" -dependencies = [ - "fnv", - "quick-error", - "tempfile", - "wait-timeout", -] - -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - -[[package]] -name = "syn" -version = "2.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" -dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall", - "rustix", - "windows-sys 0.48.0", -] - -[[package]] -name = "thiserror" -version = "1.0.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2cd5904763bad08ad5513ddbb12cf2ae273ca53fa9f68e843e236ec6dfccc09" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcf4a824cce0aeacd6f38ae6f24234c8e80d68632338ebaa1443b5df9e29e19" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "unarray" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wait-timeout" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" -dependencies = [ - "libc", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.0", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" -dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" - -[[package]] -name = "z32" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e62ec361f7259399a83b7983270dd0249067d94600c50475e65dca64e24083" - -[[package]] -name = "zeroize" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index d2e2a44..4e61c7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,5 @@ [workspace] -members = [ - "mast", -] +members = [] # See: https://github.com/rust-lang/rust/issues/90148#issuecomment-949194352 resolver = "2" diff --git a/README.md b/README.md index eddb7f3..99e127f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -# Kytz +# Pubky -> Soaring in the Cloud, but you pull the strings. - -Kytz (pronounced Kites) is a set of tools and standards to enable user agency and credible exit. +Public key addressable web. diff --git a/design/seed.md b/design/seed.md deleted file mode 100644 index 1673709..0000000 --- a/design/seed.md +++ /dev/null @@ -1,18 +0,0 @@ -# Seed - -Kytz seed is an encrypted seed encoded as URI as follows: - -``` -kytz:seed: -``` - -The `suffix` is a `z-base32` encoded bytes as follows: - -``` - -``` - -## Version 0 - -For version 0 the encrypted seed is using [`bessie`](https://github.com/oconnor663/bessie/blob/44f9500ebeb0f28efc9689184ff5b1d79e2308e0/design.md) version 0.0.1 - diff --git a/mast/Cargo.toml b/mast/Cargo.toml deleted file mode 100644 index ac282be..0000000 --- a/mast/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "mast" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -blake3 = "1.5.0" -redb = "1.4.0" -thiserror = "1.0.53" - -[dev-dependencies] -proptest = "1.4.0" -tempfile = "3.8.1" diff --git a/mast/proptest-regressions/operations/insert.txt b/mast/proptest-regressions/operations/insert.txt deleted file mode 100644 index 93591fb..0000000 --- a/mast/proptest-regressions/operations/insert.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Seeds for failure cases proptest has generated in the past. It is -# automatically read and these particular cases re-run before any -# novel cases are generated. -# -# It is recommended to check this file in to source control so that -# everyone who runs the test benefits from these saved cases. -cc 7912e0f85e7a1a9892a9c85944f3392f6e1fe1a33d2ea00b3809400d75166743 # shrinks to random_entries = [([135], [150]), ([25], [0]), ([129], [0]), ([135], [150])] diff --git a/mast/proptest-regressions/operations/remove.txt b/mast/proptest-regressions/operations/remove.txt deleted file mode 100644 index 97a1784..0000000 --- a/mast/proptest-regressions/operations/remove.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Seeds for failure cases proptest has generated in the past. It is -# automatically read and these particular cases re-run before any -# novel cases are generated. -# -# It is recommended to check this file in to source control so that -# everyone who runs the test benefits from these saved cases. -cc 491e92aa5f00ba54a3a98d00351fe58ef2b3943a701f80c942349e3b5bb5baca # shrinks to random_entries = [([142], [0], 216), ([142], [0], 29)] -cc 7b8908fd3cf99c905ebf09706be69e33babea514bfc4670b27800e667ed38bbe # shrinks to random_entries = [([223], [0], Insert), ([223], [0], Remove)] -cc 460005103c0d4107d2b993317861e5c237cc6da4d47c44d9e1b6f33bd85f8d51 # shrinks to random_entries = [([120], [0], Insert), ([28], [0], Remove)] -cc 02d5799411cd45dafd4fd3106f577bd0bd3aab86b2edeec47bd4f2a204ec5a86 # shrinks to random_entries = [([23], [0], Insert), ([0], [0], Insert), ([23], [0], Remove)] diff --git a/mast/src/db.rs b/mast/src/db.rs deleted file mode 100644 index 2d882dc..0000000 --- a/mast/src/db.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! Kytz Database - -use crate::node::Node; -use crate::operations::read::{get_node, root_hash, root_node}; -use crate::operations::{insert, remove}; -use crate::{Hash, Result}; - -pub struct Database { - pub(crate) inner: redb::Database, -} - -impl Database { - /// Create a new in-memory database. - pub fn in_memory() -> Self { - let backend = redb::backends::InMemoryBackend::new(); - let inner = redb::Database::builder() - .create_with_backend(backend) - .unwrap(); - - Self { inner } - } - - pub fn begin_write(&self) -> Result { - let txn = self.inner.begin_write().unwrap(); - WriteTransaction::new(txn) - } - - pub fn iter(&self, treap: &str) -> TreapIterator<'_> { - // TODO: save tables instead of opening a new one on every next() call. - TreapIterator::new(self, treap.to_string()) - } - - // === Private Methods === - - pub(crate) fn get_node(&self, hash: &Option) -> Option { - get_node(&self, hash) - } - - pub(crate) fn root_hash(&self, treap: &str) -> Option { - root_hash(&self, treap) - } - - pub(crate) fn root(&self, treap: &str) -> Option { - root_node(self, treap) - } -} - -pub struct TreapIterator<'db> { - db: &'db Database, - treap: String, - stack: Vec, -} - -impl<'db> TreapIterator<'db> { - fn new(db: &'db Database, treap: String) -> Self { - let mut iter = TreapIterator { - db, - treap: treap.clone(), - stack: Vec::new(), - }; - - if let Some(root) = db.root(&treap) { - iter.push_left(root) - }; - - iter - } - - fn push_left(&mut self, mut node: Node) { - while let Some(left) = self.db.get_node(node.left()) { - self.stack.push(node); - node = left; - } - self.stack.push(node); - } -} - -impl<'a> Iterator for TreapIterator<'a> { - type Item = Node; - - fn next(&mut self) -> Option { - match self.stack.pop() { - Some(node) => { - if let Some(right) = self.db.get_node(node.right()) { - self.push_left(right) - } - - Some(node.clone()) - } - _ => None, - } - } -} - -pub struct WriteTransaction<'db> { - inner: redb::WriteTransaction<'db>, -} - -impl<'db> WriteTransaction<'db> { - pub(crate) fn new(inner: redb::WriteTransaction<'db>) -> Result { - Ok(Self { inner }) - } - - pub fn insert( - &mut self, - treap: &str, - key: impl AsRef<[u8]>, - value: impl AsRef<[u8]>, - ) -> Option { - // TODO: validate key and value length. - // key and value mast be less than 2^32 bytes. - - insert(&mut self.inner, treap, key.as_ref(), value.as_ref()) - } - - pub fn remove(&mut self, treap: &str, key: impl AsRef<[u8]>) -> Option { - remove(&mut self.inner, treap, key.as_ref()) - } - - pub fn commit(self) -> Result<()> { - self.inner.commit().map_err(|e| e.into()) - } -} diff --git a/mast/src/error.rs b/mast/src/error.rs deleted file mode 100644 index adc2769..0000000 --- a/mast/src/error.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Main Crate Error - -#[derive(thiserror::Error, Debug)] -/// Mainline crate error enum. -pub enum Error { - /// For starter, to remove as code matures. - #[error("Generic error: {0}")] - Generic(String), - /// For starter, to remove as code matures. - #[error("Static error: {0}")] - Static(&'static str), - - #[error(transparent)] - /// Transparent [std::io::Error] - IO(#[from] std::io::Error), - - #[error(transparent)] - /// Transparent [redb::CommitError] - CommitError(#[from] redb::CommitError), - - #[error(transparent)] - /// Error from `redb::TransactionError`. - TransactionError(#[from] redb::TransactionError), -} diff --git a/mast/src/lib.rs b/mast/src/lib.rs deleted file mode 100644 index 35c9984..0000000 --- a/mast/src/lib.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![allow(unused)] - -pub mod db; -pub mod error; -mod node; -mod operations; - -#[cfg(test)] -mod test; - -pub(crate) use blake3::{Hash, Hasher}; -pub(crate) const HASH_LEN: usize = 32; - -pub use db::Database; -pub use error::Error; - -// Alias Result to be the crate Result. -pub type Result = core::result::Result; diff --git a/mast/src/node.rs b/mast/src/node.rs deleted file mode 100644 index 68334aa..0000000 --- a/mast/src/node.rs +++ /dev/null @@ -1,337 +0,0 @@ -//! In memory representation of a treap node. - -use redb::{ReadableTable, Table}; - -use crate::{Hash, Hasher, HASH_LEN}; - -// TODO: remove unwrap -// TODO: KeyType and ValueType - -#[derive(Debug, Clone, PartialEq)] -/// In memory reprsentation of treap node. -pub struct Node { - // Key value - key: Box<[u8]>, - value: Box<[u8]>, - - // Children - left: Option, - right: Option, - - // Metadata that should not be encoded. - ref_count: u64, - - // Memoized hashes - rank: Hash, - /// The Hash of the node, if None then something changed, and the hash should be recomputed. - hash: Option, -} - -#[derive(Debug)] -pub(crate) enum Branch { - Left, - Right, -} - -#[derive(Debug)] -enum RefCountDiff { - Increment, - Decrement, -} - -impl Node { - pub(crate) fn new(key: &[u8], value: &[u8]) -> Self { - Self { - key: key.into(), - value: value.into(), - left: None, - right: None, - - ref_count: 0, - - rank: hash(key), - hash: None, - } - } - - pub(crate) fn open( - table: &'_ impl ReadableTable<&'static [u8], (u64, &'static [u8])>, - hash: Hash, - ) -> Option { - // TODO: make it Result instead! - let existing = table.get(hash.as_bytes().as_slice()).unwrap(); - - existing.map(|existing| { - let (ref_count, bytes) = { - let (r, v) = existing.value(); - (r, v.to_vec()) - }; - drop(existing); - - decode_node((ref_count, &bytes)) - }) - } - - // === Getters === - - pub fn key(&self) -> &[u8] { - &self.key - } - - pub fn value(&self) -> &[u8] { - &self.value - } - - pub fn left(&self) -> &Option { - &self.left - } - - pub fn right(&self) -> &Option { - &self.right - } - - pub fn rank(&self) -> &Hash { - &self.rank - } - - pub(crate) fn ref_count(&self) -> &u64 { - &self.ref_count - } - - /// Returns the hash of the node. - pub fn hash(&mut self) -> Hash { - self.hash.unwrap_or_else(|| { - let encoded = self.canonical_encode(); - let hash = hash(&encoded); - self.hash = Some(hash); - hash - }) - } - - // === Private Methods === - - /// Set the value. - pub(crate) fn set_value(&mut self, value: &[u8]) -> &mut Self { - self.value = value.into(); - self.hash = None; - - self - } - - /// Set the left child, save the updated node, and return the new hash. - pub(crate) fn set_left_child(&mut self, child: Option) -> &mut Self { - self.set_child(Branch::Left, child) - } - - /// Set the right child, save the updated node, and return the new hash. - pub(crate) fn set_right_child(&mut self, child: Option) -> &mut Self { - self.set_child(Branch::Right, child) - } - - /// Set the child, update its ref_count, save the updated node and return it. - fn set_child(&mut self, branch: Branch, new_child: Option) -> &mut Self { - match branch { - Branch::Left => self.left = new_child, - Branch::Right => self.right = new_child, - }; - self.hash = None; - - self - } - - pub(crate) fn increment_ref_count(&mut self) -> &mut Self { - self.update_ref_count(RefCountDiff::Increment) - } - - pub(crate) fn decrement_ref_count(&mut self) -> &mut Self { - self.update_ref_count(RefCountDiff::Decrement) - } - - fn update_ref_count(&mut self, diff: RefCountDiff) -> &mut Self { - let ref_count = match diff { - RefCountDiff::Increment => self.ref_count + 1, - RefCountDiff::Decrement => { - if self.ref_count > 0 { - self.ref_count - 1 - } else { - self.ref_count - } - } - }; - - // We only updaet the ref count, and handle deletion elsewhere. - self.ref_count = ref_count; - self - } - - /// Saves the node to the nodes table by its hash. - pub(crate) fn save(&mut self, table: &mut Table<&[u8], (u64, &[u8])>) -> &mut Self { - // TODO: keep data in encoded in a bytes field. - let encoded = self.canonical_encode(); - - table - .insert( - hash(&encoded).as_bytes().as_slice(), - (self.ref_count, encoded.as_slice()), - ) - .unwrap(); - - self - } - - /// Encodes the node in a canonical way: - /// - 1 byte header - /// - 0b1100_0000: Two reserved bits - /// - 0b0011_0000: Two bits represents the size of the key length (0, u8, u16, u32) - /// - 0b0000_1100: Two bits represents the size of the value length (0, u8, u16, u32) - /// - 0b0000_0010: left child is present - /// - 0b0000_0001: right child is present - /// - key - /// - value - fn canonical_encode(&self) -> Vec { - let key_length = self.key.len(); - let val_length = self.value.len(); - - let key_length_encoding_length = len_encoding_length(key_length); - let val_length_encoding_length = len_encoding_length(val_length); - - let header = (key_length_encoding_length << 4) - | (val_length_encoding_length << 2) - | ((self.left.is_some() as u8) << 1) - | (self.right.is_some() as u8); - - let mut bytes = vec![header]; - - // Encode key length - match key_length_encoding_length { - 1 => bytes.push(key_length as u8), - 2 => bytes.extend_from_slice(&(key_length as u16).to_be_bytes()), - 3 => bytes.extend_from_slice(&(key_length as u32).to_be_bytes()), - _ => {} // Do nothing for 0 length - } - - // Encode value length - match val_length_encoding_length { - 1 => bytes.push(val_length as u8), - 2 => bytes.extend_from_slice(&(val_length as u16).to_be_bytes()), - 3 => bytes.extend_from_slice(&(val_length as u32).to_be_bytes()), - _ => {} // Do nothing for 0 length - } - - bytes.extend_from_slice(&self.key); - bytes.extend_from_slice(&self.value); - - if let Some(left) = &self.left { - bytes[0] |= 0b0000_0010; - bytes.extend_from_slice(left.as_bytes()); - } - if let Some(right) = &self.right { - bytes[0] |= 0b0000_0001; - bytes.extend_from_slice(right.as_bytes()); - } - - bytes - } -} - -fn hash(bytes: &[u8]) -> Hash { - let mut hasher = Hasher::new(); - hasher.update(bytes); - - hasher.finalize() -} - -fn decode_node(data: (u64, &[u8])) -> Node { - let (ref_count, encoded_node) = data; - - // We can calculate the size of then node from the first few bytes. - let header = encoded_node[0]; - - let mut rest = &encoded_node[1..]; - - let key_length = match (header & 0b0011_0000) >> 4 { - 1 => { - let len = rest[0] as usize; - rest = &rest[1..]; - len - } - 2 => { - let len = u16::from_be_bytes(rest[0..3].try_into().unwrap()) as usize; - rest = &rest[3..]; - len - } - 3 => { - let len = u32::from_be_bytes(rest[0..4].try_into().unwrap()) as usize; - rest = &rest[4..]; - len - } - _ => 0, - }; - - let val_length = match (header & 0b0000_1100) >> 2 { - 1 => { - let len = rest[0] as usize; - rest = &rest[1..]; - len - } - 2 => { - let len = u16::from_be_bytes(rest[0..3].try_into().unwrap()) as usize; - rest = &rest[3..]; - len - } - 3 => { - let len = u32::from_be_bytes(rest[0..4].try_into().unwrap()) as usize; - rest = &rest[4..]; - len - } - _ => 0, - }; - - let key = &rest[..key_length]; - rest = &rest[key_length..]; - - let value = &rest[..val_length]; - rest = &rest[val_length..]; - - let left = match header & 0b0000_0010 == 0 { - true => None, - false => { - let hash_bytes: [u8; HASH_LEN] = rest[0..32].try_into().unwrap(); - rest = &rest[32..]; - - Some(Hash::from_bytes(hash_bytes)) - } - }; - - let right = match header & 0b0000_0001 == 0 { - true => None, - false => { - let hash_bytes: [u8; HASH_LEN] = rest[0..32].try_into().unwrap(); - Some(Hash::from_bytes(hash_bytes)) - } - }; - - Node { - key: key.into(), - value: value.into(), - left, - right, - - ref_count, - - rank: hash(key), - hash: None, - } -} - -fn len_encoding_length(len: usize) -> u8 { - if len == 0 { - 0 - } else if len <= u8::max_value() as usize { - 1 - } else if len <= u16::max_value() as usize { - 2 - } else { - 3 - } -} diff --git a/mast/src/operations/insert.rs b/mast/src/operations/insert.rs deleted file mode 100644 index 4593a67..0000000 --- a/mast/src/operations/insert.rs +++ /dev/null @@ -1,319 +0,0 @@ -use blake3::Hash; -use redb::Table; - -use super::{read::root_node_inner, search::binary_search_path, NODES_TABLE, ROOTS_TABLE}; -use crate::node::{Branch, Node}; - -// Watch this [video](https://youtu.be/NxRXhBur6Xs?si=GNwaUOfuGwr_tBKI&t=1763) for a good explanation of the unzipping algorithm. -// Also see the Iterative insertion algorithm in the page 12 of the [original paper](https://arxiv.org/pdf/1806.06726.pdf). -// The difference here is that in a Hash Treap, we need to update nodes bottom up. - -// Let's say we have the following tree: -// -// F -// / \ -// D P -// / / \ -// C H X -// / / \ \ -// A G M Y -// / -// I -// -// The binary search path for inserting `J` then is: -// -// F -// \ -// P -// / -// H -// \ -// M -// / -// I -// -// Then we define `upper_path` as the path from the root to the insertion point -// marked by the first node with a `rank` that is either: -// -// - less than the `rank` of the inserted key: -// -// F -// \ -// P -// ∧-- / --∧ upper path if rank(J) > rank(H) -// ∨-- H --∨ unzip path -// \ -// M Note that this is an arbitrary example, -// / do not expect the actual ranks of these keys to be the same in implmentation. -// I -// -// Upper path doesn't change much beyond updating the hash of their child in the branch featured in -// this binary search path. -// -// We call the rest of the path `unzipping path` or `lower path` and this is where we create two -// new paths (left and right), each contain the nodes with keys smaller than or larger than the -// inserted key respectively. -// -// We update these unzipped paths from the _bottom up_ to generate the new hashes for their -// parents. -// Once we have the two paths, we use their tips as the new children of the newly inserted node. -// Finally we update the hashes upwards until we reach the new root of the tree. -// -// - equal to the `rank` of the inserted key: -// -// F -// \ -// P -// / -// H --^ upper path if -// rank(H) = rank(H) -// -// This (exact key match) is the only way for the rank to match -// for secure hashes like blake3. -// -// This is a different case since we don't really need to split (unzip) the lower path, we just -// need to update the hash of the node (according to the new value) and update the hash of its -// parents until we reach the root. -// -// After unzipping the lower path, we should get: -// -// F -// \ -// P -// / -// J -// / \ -// H M -// \ -// I -// -// So the end result beocmes: -// -// F -// / \ -// D P -// / / \ -// C J X -// / / \ \ -// A H M Y -// / \ -// G I -// - -pub(crate) fn insert( - write_txn: &mut redb::WriteTransaction, - treap: &str, - key: &[u8], - value: &[u8], -) -> Option { - let mut roots_table = write_txn.open_table(ROOTS_TABLE).unwrap(); - let mut nodes_table = write_txn.open_table(NODES_TABLE).unwrap(); - - let old_root = root_node_inner(&roots_table, &nodes_table, treap); - - let mut path = binary_search_path(&nodes_table, old_root, key); - - let mut left_subtree: Option = None; - let mut right_subtree: Option = None; - - // Unzip the lower path to get left and right children of the inserted node. - for (node, branch) in path.lower.iter_mut().rev() { - // Decrement the old version. - node.decrement_ref_count().save(&mut nodes_table); - - match branch { - Branch::Right => { - node.set_right_child(left_subtree); - left_subtree = Some(node.hash()); - } - Branch::Left => { - node.set_left_child(right_subtree); - right_subtree = Some(node.hash()); - } - } - - node.increment_ref_count().save(&mut nodes_table); - } - - let mut new_root; - - if let Some(mut found) = path.found { - if found.value() == value { - // There is really nothing to update. Skip traversing upwards. - return Some(found); - } - - // Decrement the old version. - found.decrement_ref_count().save(&mut nodes_table); - - // Else, update the value and rehashe the node so that we can update the hashes upwards. - found - .set_value(value) - .increment_ref_count() - .save(&mut nodes_table); - - new_root = found - } else { - // Insert the new node. - let mut node = Node::new(key, value); - - node.set_left_child(left_subtree) - .set_right_child(right_subtree) - .increment_ref_count() - .save(&mut nodes_table); - - new_root = node - }; - - let mut upper_path = path.upper; - - // Propagate the new hashes upwards if there are any nodes in the upper_path. - while let Some((mut node, branch)) = upper_path.pop() { - node.decrement_ref_count().save(&mut nodes_table); - - match branch { - Branch::Left => node.set_left_child(Some(new_root.hash())), - Branch::Right => node.set_right_child(Some(new_root.hash())), - }; - - node.increment_ref_count().save(&mut nodes_table); - - new_root = node; - } - - // Finally set the new root . - roots_table - .insert(treap.as_bytes(), new_root.hash().as_bytes().as_slice()) - .unwrap(); - - // No older value was found. - None -} - -#[cfg(test)] -mod test { - use crate::test::{test_operations, Entry}; - use proptest::prelude::*; - - proptest! { - #[test] - /// Test that upserting an entry with the same key in different tree shapes results in the - /// expected structure - fn test_upsert(random_entries in prop::collection::vec( - (prop::collection::vec(any::(), 1), prop::collection::vec(any::(), 1)), - 1..10, - )) { - let operations = random_entries.into_iter().map(|(key, value)| { - Entry::insert(&key, &value) - }).collect::>(); - - test_operations(&operations, None); - } - - #[test] - fn test_general_insertiong(random_entries in prop::collection::vec( - (prop::collection::vec(any::(), 32), prop::collection::vec(any::(), 32)), - 1..50, - )) { - let operations = random_entries.into_iter().map(|(key, value)| { - Entry::insert(&key, &value) - }).collect::>(); - - test_operations(&operations, None); - } - } - - #[test] - fn insert_single_entry() { - let case = ["A"]; - - test_operations( - &case.map(|key| Entry::insert(key.as_bytes(), &[b"v", key.as_bytes()].concat())), - Some("9fbdb0a2023f8029871b44722b2091a45b8209eaa5ce912740959fc00c611b91"), - ) - } - - #[test] - fn sorted_alphabets() { - let case = [ - "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", - "R", "S", "T", "U", "V", "W", "X", "Y", "Z", - ]; - - test_operations( - &case.map(|key| Entry::insert(key.as_bytes(), &[b"v", key.as_bytes()].concat())), - Some("26820b21fec1451a2478808bb8bc3ade05dcfbcd50d9556cca77d12d6239f4a7"), - ); - } - - #[test] - fn reverse_alphabets() { - let mut case = [ - "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", - "R", "S", "T", "U", "V", "W", "X", "Y", "Z", - ]; - case.reverse(); - - test_operations( - &case.map(|key| Entry::insert(key.as_bytes(), &[b"v", key.as_bytes()].concat())), - Some("26820b21fec1451a2478808bb8bc3ade05dcfbcd50d9556cca77d12d6239f4a7"), - ) - } - - #[test] - fn unsorted() { - let case = ["D", "N", "P", "X", "A", "G", "C", "M", "H", "I", "J"]; - - test_operations( - &case.map(|key| Entry::insert(key.as_bytes(), &[b"v", key.as_bytes()].concat())), - Some("96c3cff677fb331fe2901a6b5297395f089a38af9ab4ad310d362f557d60fca5"), - ) - } - - #[test] - fn upsert_at_root() { - let case = ["X", "X"]; - - let mut i = 0; - - test_operations( - &case.map(|key| { - i += 1; - Entry::insert(key.as_bytes(), i.to_string().as_bytes()) - }), - Some("69e8b408d10174feb9d9befd0a3de95767cc0e342d0dba5f51139f4b49588fb7"), - ) - } - - #[test] - fn upsert_deeper() { - // X has higher rank. - let case = ["X", "F", "F"]; - - let mut i = 0; - - test_operations( - &case.map(|key| { - i += 1; - Entry::insert(key.as_bytes(), i.to_string().as_bytes()) - }), - Some("9e73a80068adf0fb31382eb35d489aa9b50f91a3ad8e55523d5cca6d6247b15b"), - ) - } - - #[test] - fn upsert_root_with_children() { - // X has higher rank. - let case = ["F", "X", "X"]; - - let mut i = 0; - - test_operations( - &case.map(|key| { - i += 1; - Entry::insert(key.as_bytes(), i.to_string().as_bytes()) - }), - Some("8c3cb6bb83df437b73183692e4b1b3809afd6974aec49d67b1ce3266e909cb67"), - ) - } -} diff --git a/mast/src/operations/mod.rs b/mast/src/operations/mod.rs deleted file mode 100644 index d45d9e3..0000000 --- a/mast/src/operations/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -pub mod insert; -pub mod read; -pub mod remove; -mod search; - -pub(crate) use insert::insert; -pub(crate) use remove::remove; - -use redb::{ReadableTable, TableDefinition}; - -// Table: Nodes v0 -// stores all the hash treap nodes from all the treaps in the storage. -// -// Key: `[u8; 32]` # Node hash -// Value: `(u64, [u8])` # (RefCount, EncodedNode) -pub const NODES_TABLE: TableDefinition<&[u8], (u64, &[u8])> = - TableDefinition::new("kytz:hash_treap:nodes:v0"); - -// Table: Roots v0 -// stores all the current roots for all treaps in the storage. -// -// Key: `[u8; 32]` # Treap name -// Value: `[u8; 32]` # Hash -pub const ROOTS_TABLE: TableDefinition<&[u8], &[u8]> = - TableDefinition::new("kytz:hash_treap:roots:v0"); diff --git a/mast/src/operations/read.rs b/mast/src/operations/read.rs deleted file mode 100644 index 24d9434..0000000 --- a/mast/src/operations/read.rs +++ /dev/null @@ -1,47 +0,0 @@ -use super::{ReadableTable, NODES_TABLE, ROOTS_TABLE}; -use crate::node::Node; -use crate::{Database, Hash, HASH_LEN}; - -pub fn root_hash_inner( - table: &'_ impl ReadableTable<&'static [u8], &'static [u8]>, - treap: &str, -) -> Option { - let existing = table.get(treap.as_bytes()).unwrap(); - existing.as_ref()?; - - let hash = existing.unwrap(); - - let hash: [u8; HASH_LEN] = hash.value().try_into().expect("Invalid root hash"); - - Some(Hash::from_bytes(hash)) -} - -pub fn root_node_inner( - roots_table: &'_ impl ReadableTable<&'static [u8], &'static [u8]>, - nodes_table: &'_ impl ReadableTable<&'static [u8], (u64, &'static [u8])>, - treap: &str, -) -> Option { - root_hash_inner(roots_table, treap).and_then(|hash| Node::open(nodes_table, hash)) -} - -pub fn get_node(db: &Database, hash: &Option) -> Option { - let read_txn = db.inner.begin_read().unwrap(); - let table = read_txn.open_table(NODES_TABLE).unwrap(); - - hash.and_then(|h| Node::open(&table, h)) -} - -pub fn root_hash(db: &Database, treap: &str) -> Option { - let read_txn = db.inner.begin_read().unwrap(); - let table = read_txn.open_table(ROOTS_TABLE).unwrap(); - - root_hash_inner(&table, treap) -} - -pub fn root_node(db: &Database, treap: &str) -> Option { - let read_txn = db.inner.begin_read().unwrap(); - let roots_table = read_txn.open_table(ROOTS_TABLE).unwrap(); - let nodes_table = read_txn.open_table(NODES_TABLE).unwrap(); - - root_node_inner(&roots_table, &nodes_table, treap) -} diff --git a/mast/src/operations/remove.rs b/mast/src/operations/remove.rs deleted file mode 100644 index 1ca4fdc..0000000 --- a/mast/src/operations/remove.rs +++ /dev/null @@ -1,244 +0,0 @@ -use blake3::Hash; -use redb::Table; - -use super::{read::root_node_inner, search::binary_search_path, NODES_TABLE, ROOTS_TABLE}; -use crate::node::{Branch, Node}; - -/// Removes the target node if it exists, and returns the new root and the removed node. -pub(crate) fn remove( - write_txn: &mut redb::WriteTransaction, - treap: &str, - key: &[u8], -) -> Option { - let mut roots_table = write_txn.open_table(ROOTS_TABLE).unwrap(); - let mut nodes_table = write_txn.open_table(NODES_TABLE).unwrap(); - - let old_root = root_node_inner(&roots_table, &nodes_table, treap); - - let mut path = binary_search_path(&nodes_table, old_root, key); - - let mut new_root = None; - - if let Some(mut target) = path.found.clone() { - new_root = zip(&mut nodes_table, &mut target) - } else { - // clearly the lower path has the highest node, and it won't be changed. - new_root = path.lower.first().map(|(n, _)| n.clone()); - } - - // If there is an upper path, we propagate the hash updates upwards. - while let Some((mut node, branch)) = path.upper.pop() { - node.decrement_ref_count().save(&mut nodes_table); - - match branch { - Branch::Left => node.set_left_child(new_root.map(|mut n| n.hash())), - Branch::Right => node.set_right_child(new_root.map(|mut n| n.hash())), - }; - - node.increment_ref_count().save(&mut nodes_table); - - new_root = Some(node); - } - - if let Some(mut new_root) = new_root { - roots_table - .insert(treap.as_bytes(), new_root.hash().as_bytes().as_slice()) - .unwrap(); - } else { - roots_table.remove(treap.as_bytes()).unwrap(); - } - - path.found -} - -fn zip( - nodes_table: &'_ mut Table<&'static [u8], (u64, &'static [u8])>, - target: &mut Node, -) -> Option { - target.decrement_ref_count(); - target.save(nodes_table); - - let mut left_subtree = Vec::new(); - let mut right_subtree = Vec::new(); - - if let Some(n) = target.left().and_then(|h| Node::open(nodes_table, h)) { - left_subtree.push(n) - } - - if let Some(n) = target.right().and_then(|h| Node::open(nodes_table, h)) { - right_subtree.push(n) - } - - while let Some(next) = left_subtree - .last() - .and_then(|n| n.right().and_then(|h| Node::open(nodes_table, h))) - { - left_subtree.push(next); - } - - while let Some(next) = right_subtree - .last() - .and_then(|n| n.left().and_then(|h| Node::open(nodes_table, h))) - { - right_subtree.push(next); - } - - let mut i = left_subtree.len().max(right_subtree.len()); - let mut previous: Option = None; - - while i > 0 { - previous = zip_up( - nodes_table, - previous, - left_subtree.get_mut(i - 1), - right_subtree.get_mut(i - 1), - ); - - i -= 1; - } - - previous -} - -fn zip_up( - nodes_table: &'_ mut Table<&'static [u8], (u64, &'static [u8])>, - previous: Option, - left: Option<&mut Node>, - right: Option<&mut Node>, -) -> Option { - match (left, right) { - (Some(left), None) => Some(left.clone()), // Left subtree is deeper - (None, Some(right)) => Some(right.clone()), // Right subtree is deeper - (Some(left), Some(right)) => { - let rank_left = left.rank(); - let rank_right = right.rank(); - - if left.rank().as_bytes() > right.rank().as_bytes() { - right - // decrement old version - .decrement_ref_count() - .save(nodes_table) - // save new version - .set_left_child(previous.map(|mut n| n.hash())) - .increment_ref_count() - .save(nodes_table); - - left - // decrement old version - .decrement_ref_count() - .save(nodes_table) - // save new version - .set_right_child(Some(right.hash())) - .increment_ref_count() - .save(nodes_table); - - Some(left.clone()) - } else { - left - // decrement old version - .decrement_ref_count() - .save(nodes_table) - // save new version - .set_right_child(previous.map(|mut n| n.hash())) - .increment_ref_count() - .save(nodes_table); - - right - // decrement old version - .decrement_ref_count() - .save(nodes_table) - // save new version - .set_left_child(Some(left.hash())) - .increment_ref_count() - .save(nodes_table); - - Some(right.clone()) - } - } - _ => { - // Should never happen! - None - } - } -} - -#[cfg(test)] -mod test { - use crate::test::{test_operations, Entry, Operation}; - use proptest::prelude::*; - - fn operation_strategy() -> impl Strategy { - prop_oneof![ - // For cases without data, `Just` is all you need - Just(Operation::Insert), - Just(Operation::Remove), - ] - } - - proptest! { - #[test] - fn insert_remove( - random_entries in prop::collection::vec( - (prop::collection::vec(any::(), 1), prop::collection::vec(any::(), 1), operation_strategy()), - 1..10, - )) { - let operations = random_entries - .into_iter() - .map(|(key, value, op)| (Entry::new(&key, &value), op)) - .collect::>(); - - test_operations(&operations, None); - } - } - - #[test] - fn becomes_empty() { - let case = [("A", Operation::Insert), ("A", Operation::Remove)] - .map(|(k, op)| (Entry::new(k.as_bytes(), k.as_bytes()), op)); - - test_operations(&case, None) - } - - #[test] - fn lower_path() { - let case = [Entry::insert(&[120], &[0]), Entry::remove(&[28])]; - - test_operations(&case, None) - } - - #[test] - fn remove_with_lower() { - let case = [ - Entry::insert(&[23], &[0]), - Entry::insert(&[0], &[0]), - Entry::remove(&[23]), - ]; - - test_operations(&case, None) - } - - #[test] - fn remove_with_upper() { - let case = [Entry::insert(&[88], &[0]), Entry::remove(&[0])]; - - test_operations(&case, None) - } - - #[test] - fn alphabet_after_remove() { - let mut case = [ - "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", - "R", "S", "T", "U", "V", "W", "X", "Y", "Z", - ] - .map(|key| Entry::insert(key.as_bytes(), &[b"v", key.as_bytes()].concat())) - .to_vec(); - - case.push(Entry::insert(&[0], &[0])); - case.push(Entry::remove(&[0])); - - test_operations( - &case, - Some("26820b21fec1451a2478808bb8bc3ade05dcfbcd50d9556cca77d12d6239f4a7"), - ); - } -} diff --git a/mast/src/operations/search.rs b/mast/src/operations/search.rs deleted file mode 100644 index 460a120..0000000 --- a/mast/src/operations/search.rs +++ /dev/null @@ -1,63 +0,0 @@ -use redb::Table; -use std::cmp::Ordering; - -use crate::node::{Branch, Node}; - -#[derive(Debug)] -pub(crate) struct BinarySearchPath { - pub upper: Vec<(Node, Branch)>, - /// The node with the exact same key (if any). - pub found: Option, - pub lower: Vec<(Node, Branch)>, -} - -/// Returns the binary search path for a given key in the following form: -/// - `upper` is the path with nodes with rank higher than the rank of the key. -/// - `target` is the node with the exact same key (if any). -/// - `lower` is the path with nodes with rank lesss than the rank of the key. -/// -/// If a match was found, the `lower_path` will be empty. -pub(crate) fn binary_search_path( - table: &Table<&'static [u8], (u64, &'static [u8])>, - root: Option, - key: &[u8], -) -> BinarySearchPath { - let target = Node::new(key, &[]); - - let mut path = BinarySearchPath { - upper: Default::default(), - found: None, - lower: Default::default(), - }; - - let mut next = root; - - while let Some(current) = next { - let stack = if current.rank().as_bytes() > target.rank().as_bytes() { - &mut path.upper - } else { - &mut path.lower - }; - - match target.key().cmp(current.key()) { - Ordering::Equal => { - // We found exact match. terminate the search. - - path.found = Some(current); - return path; - } - Ordering::Less => { - next = current.left().and_then(|n| Node::open(table, n)); - - stack.push((current, Branch::Left)); - } - Ordering::Greater => { - next = current.right().and_then(|n| Node::open(table, n)); - - stack.push((current, Branch::Right)); - } - }; - } - - path -} diff --git a/mast/src/test.rs b/mast/src/test.rs deleted file mode 100644 index f1c4f83..0000000 --- a/mast/src/test.rs +++ /dev/null @@ -1,224 +0,0 @@ -//! Test helpers for the merkle treap. - -use redb::ReadableTable; -use std::assert_eq; -use std::collections::BTreeMap; - -use crate::node::Node; -use crate::operations::NODES_TABLE; -use crate::Database; -use crate::Hash; - -#[derive(Clone, Debug)] -pub enum Operation { - Insert, - Remove, -} - -#[derive(Clone, PartialEq)] -pub struct Entry { - pub(crate) key: Vec, - pub(crate) value: Vec, -} - -impl Entry { - pub fn new(key: &[u8], value: &[u8]) -> Self { - Self { - key: key.to_vec(), - value: value.to_vec(), - } - } - pub fn insert(key: &[u8], value: &[u8]) -> (Self, Operation) { - ( - Self { - key: key.to_vec(), - value: value.to_vec(), - }, - Operation::Insert, - ) - } - pub fn remove(key: &[u8]) -> (Self, Operation) { - ( - Self { - key: key.to_vec(), - value: b"".to_vec(), - }, - Operation::Remove, - ) - } -} - -impl std::fmt::Debug for Entry { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "({:?}, {:?})", self.key, self.value) - } -} - -pub fn test_operations(input: &[(Entry, Operation)], root_hash: Option<&str>) { - let db = Database::in_memory(); - let mut txn = db.begin_write().unwrap(); - let treap = "test"; - - for (entry, operation) in input { - match operation { - Operation::Insert => txn.insert(treap, &entry.key, &entry.value), - Operation::Remove => txn.remove(treap, &entry.key), - }; - } - - txn.commit(); - - // Uncomment to see the graph - // println!("{}", into_mermaid_graph(&treap)); - - let collected = db - .iter(treap) - .map(|n| { - assert_eq!( - *n.ref_count(), - 1_u64, - "{}", - format!("Node has wrong ref count {:?}", n) - ); - - Entry { - key: n.key().to_vec(), - value: n.value().to_vec(), - } - }) - .collect::>(); - - verify_ranks(&db, treap); - - let mut btree = BTreeMap::new(); - for (entry, operation) in input { - match operation { - Operation::Insert => { - btree.insert(&entry.key, &entry.value); - } - Operation::Remove => { - btree.remove(&entry.key); - } - } - } - - let expected = btree - .iter() - .map(|(key, value)| Entry { - key: key.to_vec(), - value: value.to_vec(), - }) - .collect::>(); - - assert_eq!(collected, expected, "{}", format!("Entries do not match")); - - if root_hash.is_some() { - assert_root(&db, treap, root_hash.unwrap()); - } - - assert_ref_count(&db, expected.len()) -} - -/// Verify that every node has higher rank than its children. -fn verify_ranks(db: &Database, treap: &str) { - assert!( - verify_children_rank(db, treap, db.root(treap)), - "Ranks are not sorted correctly" - ) -} - -fn verify_children_rank(db: &Database, treap: &str, node: Option) -> bool { - match node { - Some(n) => { - let left_check = db.get_node(n.left()).map_or(true, |left| { - n.rank().as_bytes() > left.rank().as_bytes() - && verify_children_rank(db, treap, Some(left)) - }); - let right_check = db.get_node(n.right()).map_or(true, |right| { - n.rank().as_bytes() > right.rank().as_bytes() - && verify_children_rank(db, treap, Some(right)) - }); - - left_check && right_check - } - None => true, - } -} - -fn assert_root(db: &Database, treap: &str, expected_root_hash: &str) { - let root_hash = db.root_hash(treap).expect("Has root hash after insertion"); - - assert_eq!( - root_hash, - Hash::from_hex(expected_root_hash).expect("Invalid hash hex"), - "Root hash is not correct" - ) -} - -fn assert_ref_count(db: &Database, n: usize) { - // TODO: Assert ref count for blob hashes - let read_txn = db.inner.begin_read().unwrap(); - - let nodes_table = read_txn.open_table(NODES_TABLE).unwrap(); - - let mut count = 0; - - for result in nodes_table.iter().unwrap() { - let (_, value) = result.unwrap(); - let (ref_count, _) = value.value(); - - count += (ref_count > 0) as u64; - } - - assert_eq!(count, n as u64, "Wrong number of nodes with ref count > 0"); -} - -// === Visualize the treap to verify the structure === - -fn into_mermaid_graph(db: &Database, treap: &str) -> String { - let mut graph = String::new(); - - graph.push_str("graph TD;\n"); - - if let Some(mut root) = db.root(treap) { - build_graph_string(db, treap, &mut root, &mut graph); - } - - graph.push_str(&format!( - " classDef null fill:#1111,stroke-width:1px,color:#fff,stroke-dasharray: 5 5;\n" - )); - - graph -} - -fn build_graph_string(db: &Database, treap: &str, node: &mut Node, graph: &mut String) { - let key = format_key(node.key()); - let node_label = format!("{}(({}))", node.hash(), key); - - // graph.push_str(&format!("## START node {}\n", node_label)); - if let Some(mut child) = db.get_node(node.left()) { - let key = format_key(child.key()); - let child_label = format!("{}(({}))", child.hash(), key); - - graph.push_str(&format!(" {} --l--> {};\n", node_label, child_label)); - build_graph_string(db, treap, &mut child, graph); - } else { - graph.push_str(&format!(" {} -.-> {}l((l));\n", node_label, node.hash())); - graph.push_str(&format!(" class {}l null;\n", node.hash())); - } - - if let Some(mut child) = db.get_node(node.right()) { - let key = format_key(child.key()); - let child_label = format!("{}(({}))", child.hash(), key); - - graph.push_str(&format!(" {} --r--> {};\n", node_label, child_label)); - build_graph_string(db, treap, &mut child, graph); - } else { - graph.push_str(&format!(" {} -.-> {}r((r));\n", node_label, node.hash())); - graph.push_str(&format!(" class {}r null;\n", node.hash())); - } -} - -fn format_key(bytes: &[u8]) -> String { - format!("\"{:?}\"", bytes) -}