mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-08 22:28:12 -05:00
drk is back in the menu boys
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,6 +16,9 @@
|
||||
/bin/darkfi-mmproxy/darkfi-mmproxy
|
||||
/darkfi-mmproxy
|
||||
|
||||
/bin/drk/drk
|
||||
/drk
|
||||
|
||||
/bin/darkirc/darkirc
|
||||
/darkirc
|
||||
|
||||
|
||||
397
Cargo.lock
generated
397
Cargo.lock
generated
@@ -131,6 +131,28 @@ version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
|
||||
|
||||
[[package]]
|
||||
name = "alsa"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2562ad8dcf0f789f65c6fdaad8a8a9708ed6b488e649da28c01656ad66b8b47"
|
||||
dependencies = [
|
||||
"alsa-sys",
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
"nix 0.24.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alsa-sys"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "amplify"
|
||||
version = "4.5.0"
|
||||
@@ -823,6 +845,26 @@ dependencies = [
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.69.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4c69fae65a523209d34240b60abe0c42d33d1045d445c0839d8a4894a736e2d"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"peeking_take_while",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
@@ -1082,6 +1124,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cesu8"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
@@ -1261,6 +1309,16 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
|
||||
dependencies = [
|
||||
"bytes 1.5.0",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.4.0"
|
||||
@@ -1389,6 +1447,26 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coreaudio-rs"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation-sys",
|
||||
"coreaudio-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coreaudio-sys"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f01585027057ff5f0a5bf276174ae4c1594a2c5bde93d5f46a016d76270f5a9"
|
||||
dependencies = [
|
||||
"bindgen 0.69.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "corosensei"
|
||||
version = "0.1.4"
|
||||
@@ -1402,6 +1480,31 @@ dependencies = [
|
||||
"windows-sys 0.33.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpal"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d959d90e938c5493000514b446987c07aed46c668faaa7d34d6c7a67b1a578c"
|
||||
dependencies = [
|
||||
"alsa",
|
||||
"core-foundation-sys",
|
||||
"coreaudio-rs",
|
||||
"dasp_sample",
|
||||
"jni 0.19.0",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"mach2",
|
||||
"ndk",
|
||||
"ndk-context",
|
||||
"oboe",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.1",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.12"
|
||||
@@ -1699,7 +1802,7 @@ version = "3.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"nix 0.27.1",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@@ -2260,6 +2363,12 @@ dependencies = [
|
||||
"num-order",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dasp_sample"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.5.0"
|
||||
@@ -2558,6 +2667,33 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
|
||||
|
||||
[[package]]
|
||||
name = "drk"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"blake3 1.5.0",
|
||||
"bs58",
|
||||
"darkfi",
|
||||
"darkfi-sdk",
|
||||
"darkfi-serial",
|
||||
"darkfi_dao_contract",
|
||||
"darkfi_money_contract",
|
||||
"easy-parallel",
|
||||
"log",
|
||||
"prettytable-rs",
|
||||
"rand 0.8.5",
|
||||
"rodio",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"signal-hook",
|
||||
"signal-hook-async-std",
|
||||
"simplelog",
|
||||
"smol",
|
||||
"structopt",
|
||||
"structopt-toml",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dwrote"
|
||||
version = "0.11.0"
|
||||
@@ -3811,6 +3947,40 @@ version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec"
|
||||
dependencies = [
|
||||
"cesu8",
|
||||
"combine",
|
||||
"jni-sys",
|
||||
"log",
|
||||
"thiserror",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c"
|
||||
dependencies = [
|
||||
"cesu8",
|
||||
"combine",
|
||||
"jni-sys",
|
||||
"log",
|
||||
"thiserror",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.27"
|
||||
@@ -4023,6 +4193,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mach2"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
@@ -4118,6 +4297,26 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "minimp3-sys"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e21c73734c69dc95696c9ed8926a2b393171d98b3f5f5935686a26a487ab9b90"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimp3_fixed"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42b0f14e7e75da97ae396c2656b10262a3d4afa2ec98f35795630eff0c8b951b"
|
||||
dependencies = [
|
||||
"minimp3-sys",
|
||||
"slice-ring-buffer",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
@@ -4149,6 +4348,46 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389"
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"jni-sys",
|
||||
"ndk-sys",
|
||||
"num_enum 0.5.11",
|
||||
"raw-window-handle",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk-context"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
|
||||
|
||||
[[package]]
|
||||
name = "ndk-sys"
|
||||
version = "0.4.1+23.1.7779620"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3"
|
||||
dependencies = [
|
||||
"jni-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.24.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.27.1"
|
||||
@@ -4208,6 +4447,17 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
@@ -4265,13 +4515,34 @@ dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
|
||||
dependencies = [
|
||||
"num_enum_derive 0.5.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
"num_enum_derive 0.7.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
|
||||
dependencies = [
|
||||
"proc-macro-crate 1.3.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4304,6 +4575,29 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oboe"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8868cc237ee02e2d9618539a23a8d228b9bb3fc2e7a5b11eed3831de77c395d0"
|
||||
dependencies = [
|
||||
"jni 0.20.0",
|
||||
"ndk",
|
||||
"ndk-context",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"oboe-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oboe-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f44155e7fb718d3cfddcf70690b2b51ac4412f347cd9e4fbe511abe9cd7b5f2"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oid-registry"
|
||||
version = "0.6.1"
|
||||
@@ -5161,10 +5455,16 @@ name = "randomx"
|
||||
version = "1.1.11"
|
||||
source = "git+https://github.com/darkrenaissance/RandomX#65c0b322edf5ab66fad53bd0d163b7a2e9c63125"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"bindgen 0.66.1",
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.8.0"
|
||||
@@ -5378,6 +5678,16 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rodio"
|
||||
version = "0.17.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b1bb7b48ee48471f55da122c0044fcc7600cfcc85db88240b89cb832935e611"
|
||||
dependencies = [
|
||||
"cpal",
|
||||
"minimp3_fixed",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "routefinder"
|
||||
version = "0.4.0"
|
||||
@@ -6098,6 +6408,17 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7"
|
||||
|
||||
[[package]]
|
||||
name = "slice-ring-buffer"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7081c7e452cb62f5f0c32edd4e265391bdbb23e90905df8bb88a23d3b5166b77"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mach2",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slotmap"
|
||||
version = "1.0.7"
|
||||
@@ -7223,7 +7544,7 @@ dependencies = [
|
||||
"humantime",
|
||||
"humantime-serde",
|
||||
"itertools 0.12.0",
|
||||
"num_enum",
|
||||
"num_enum 0.7.2",
|
||||
"pin-project",
|
||||
"postage",
|
||||
"rand 0.8.5",
|
||||
@@ -7473,7 +7794,7 @@ dependencies = [
|
||||
"hex",
|
||||
"humantime",
|
||||
"itertools 0.12.0",
|
||||
"num_enum",
|
||||
"num_enum 0.7.2",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"signature 1.6.4",
|
||||
@@ -8393,6 +8714,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25"
|
||||
dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
@@ -8433,6 +8763,21 @@ dependencies = [
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
@@ -8463,6 +8808,12 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
@@ -8481,6 +8832,12 @@ version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
@@ -8499,6 +8856,12 @@ version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
@@ -8517,6 +8880,12 @@ version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
@@ -8535,6 +8904,12 @@ version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
@@ -8547,6 +8922,12 @@ version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
@@ -8565,6 +8946,12 @@ version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
|
||||
@@ -23,8 +23,7 @@ members = [
|
||||
#"bin/darkfid",
|
||||
"bin/darkfid2",
|
||||
"bin/darkfi-mmproxy",
|
||||
#"bin/drk",
|
||||
#"bin/drk2",
|
||||
"bin/drk",
|
||||
#"bin/faucetd",
|
||||
#"bin/fud/fu",
|
||||
#"bin/fud/fud",
|
||||
|
||||
7
Makefile
7
Makefile
@@ -60,6 +60,13 @@ darkfi-mmproxy:
|
||||
RUST_TARGET="$(RUST_TARGET)" \
|
||||
RUSTFLAGS="$(RUSTFLAGS)"
|
||||
|
||||
drk: contracts
|
||||
$(MAKE) -C bin/$@ \
|
||||
PREFIX="$(PREFIX)" \
|
||||
CARGO="$(CARGO)" \
|
||||
RUST_TARGET="$(RUST_TARGET)" \
|
||||
RUSTFLAGS="$(RUSTFLAGS)"
|
||||
|
||||
darkirc: zkas
|
||||
$(MAKE) -C bin/$@ \
|
||||
PREFIX="$(PREFIX)" \
|
||||
|
||||
@@ -9,23 +9,31 @@ license = "AGPL-3.0-only"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.79"
|
||||
async-std = {version = "1.12.0", features = ["attributes"]}
|
||||
# Darkfi
|
||||
darkfi = {path = "../../", features = ["async-daemonize", "bs58", "rpc"]}
|
||||
darkfi_money_contract = {path = "../../src/contract/money", features = ["no-entrypoint", "client"]}
|
||||
darkfi_dao_contract = {path = "../../src/contract/dao", features = ["no-entrypoint", "client"]}
|
||||
darkfi-sdk = {path = "../../src/sdk", features = ["async"]}
|
||||
darkfi-serial = {path = "../../src/serial"}
|
||||
|
||||
# Misc
|
||||
blake3 = "1.5.0"
|
||||
bs58 = "0.5.0"
|
||||
clap = {version = "4.4.14", features = ["derive"]}
|
||||
clap_complete = "4.4.6"
|
||||
darkfi = {path = "../../", features = ["blockchain", "rpc", "util", "wallet"]}
|
||||
darkfi-sdk = {path = "../../src/sdk"}
|
||||
darkfi-serial = {path = "../../src/serial", features = ["derive", "crypto"]}
|
||||
darkfi-money-contract = {path = "../../src/contract/money", features = ["no-entrypoint", "client"]}
|
||||
darkfi-dao-contract = {path = "../../src/contract/dao", features = ["no-entrypoint", "client"]}
|
||||
log = "0.4.20"
|
||||
prettytable-rs = "0.10.0"
|
||||
rand = "0.8.5"
|
||||
serde_json = "1.0.111"
|
||||
smol = "1.3.0"
|
||||
simplelog = "0.12.1"
|
||||
rodio = {version = "0.17.3", default-features = false, features = ["minimp3"]}
|
||||
rusqlite = {version = "0.30.0", features = ["sqlcipher"]}
|
||||
url = "2.5.0"
|
||||
|
||||
# Daemon
|
||||
easy-parallel = "3.3.1"
|
||||
signal-hook-async-std = "0.2.2"
|
||||
signal-hook = "0.3.17"
|
||||
url = "2.5.0"
|
||||
rodio = {version = "0.17.3", default-features = false, features = ["minimp3"]}
|
||||
simplelog = "0.12.1"
|
||||
smol = "1.3.0"
|
||||
|
||||
# Argument parsing
|
||||
serde = {version = "1.0.195", features = ["derive"]}
|
||||
structopt = "0.3.26"
|
||||
structopt-toml = "0.5.1"
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
* Spent coins can be tracked through nullifiers
|
||||
* So in the wallet, once we broadcast a tx, we can spend a coin
|
||||
* Also extend the wallet.sql schema to support both confirmed and
|
||||
unconfirmed coins (use tx hash for that like DAO does)
|
||||
@@ -15,17 +15,17 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use std::{io::Cursor, process::exit};
|
||||
use std::{io::Cursor, process::exit, str::FromStr};
|
||||
|
||||
use darkfi::{
|
||||
util::{async_util::sleep, parse::decode_base10},
|
||||
Result,
|
||||
};
|
||||
use darkfi_sdk::crypto::TokenId;
|
||||
use rodio::{source::Source, Decoder, OutputStream};
|
||||
use structopt_toml::clap::{App, Arg, Shell, SubCommand};
|
||||
|
||||
use super::Drk;
|
||||
use darkfi::{cli_desc, system::sleep, util::parse::decode_base10, Error, Result};
|
||||
use darkfi_sdk::crypto::TokenId;
|
||||
|
||||
use crate::{money::BALANCE_BASE10_DECIMALS, Drk};
|
||||
|
||||
/// Auxiliary function to parse provided string into a values pair.
|
||||
pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
|
||||
let v: Vec<&str> = s.split(':').collect();
|
||||
if v.len() != 2 {
|
||||
@@ -33,9 +33,8 @@ pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// TODO: We shouldn't be hardcoding everything to 8 decimals.
|
||||
let val0 = decode_base10(v[0], 8, true);
|
||||
let val1 = decode_base10(v[1], 8, true);
|
||||
let val0 = decode_base10(v[0], BALANCE_BASE10_DECIMALS, true);
|
||||
let val1 = decode_base10(v[1], BALANCE_BASE10_DECIMALS, true);
|
||||
|
||||
if val0.is_err() || val1.is_err() {
|
||||
eprintln!("Invalid value pair. Use a pair such as 13.37:11.0");
|
||||
@@ -45,6 +44,7 @@ pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
|
||||
Ok((val0.unwrap(), val1.unwrap()))
|
||||
}
|
||||
|
||||
/// Auxiliary function to parse provided string into a tokens pair.
|
||||
pub async fn parse_token_pair(drk: &Drk, s: &str) -> Result<(TokenId, TokenId)> {
|
||||
let v: Vec<&str> = s.split(':').collect();
|
||||
if v.len() != 2 {
|
||||
@@ -85,3 +85,385 @@ pub async fn kaching() {
|
||||
|
||||
sleep(2).await;
|
||||
}
|
||||
|
||||
/// Auxiliary function to generate provided shell completions.
|
||||
pub fn generate_completions(shell: &str) -> Result<()> {
|
||||
// Sub-commands
|
||||
|
||||
// Kaching
|
||||
let kaching = SubCommand::with_name("kaching").about("Fun");
|
||||
|
||||
// Ping
|
||||
let ping =
|
||||
SubCommand::with_name("ping").about("Send a ping request to the darkfid RPC endpoint");
|
||||
|
||||
// Completions
|
||||
let shell_arg = Arg::with_name("shell").help("The Shell you want to generate script for");
|
||||
|
||||
let completions = SubCommand::with_name("completions")
|
||||
.about("Generate a SHELL completion script and print to stdout")
|
||||
.arg(shell_arg);
|
||||
|
||||
// Wallet
|
||||
let initialize =
|
||||
Arg::with_name("initialize").long("initialize").help("Initialize wallet database");
|
||||
|
||||
let keygen =
|
||||
Arg::with_name("keygen").long("keygen").help("Generate a new keypair in the wallet");
|
||||
|
||||
let balance =
|
||||
Arg::with_name("balance").long("balance").help("Query the wallet for known balances");
|
||||
|
||||
let address =
|
||||
Arg::with_name("address").long("address").help("Get the default address in the wallet");
|
||||
|
||||
let addresses =
|
||||
Arg::with_name("addresses").long("addresses").help("Print all the addresses in the wallet");
|
||||
|
||||
let default_address = Arg::with_name("default-address")
|
||||
.long("default-address")
|
||||
.takes_value(true)
|
||||
.help("Set the default address in the wallet");
|
||||
|
||||
let secrets =
|
||||
Arg::with_name("secrets").long("secrets").help("Print all the secret keys from the wallet");
|
||||
|
||||
let import_secrets = Arg::with_name("import-secrets")
|
||||
.long("import-secrets")
|
||||
.help("Import secret keys from stdin into the wallet, separated by newlines");
|
||||
|
||||
let tree = Arg::with_name("tree").long("tree").help("Print the Merkle tree in the wallet");
|
||||
|
||||
let coins = Arg::with_name("coins").long("coins").help("Print all the coins in the wallet");
|
||||
|
||||
let wallet = SubCommand::with_name("wallet").about("Wallet operations").args(&vec![
|
||||
initialize,
|
||||
keygen,
|
||||
balance,
|
||||
address,
|
||||
addresses,
|
||||
default_address,
|
||||
secrets,
|
||||
import_secrets,
|
||||
tree,
|
||||
coins,
|
||||
]);
|
||||
|
||||
// Unspend
|
||||
let coin = Arg::with_name("coin").help("base58-encoded coin to mark as unspent");
|
||||
|
||||
let unspend = SubCommand::with_name("unspend").about("Unspend a coin").arg(coin);
|
||||
|
||||
// Transfer
|
||||
let amount = Arg::with_name("amount").help("Amount to send");
|
||||
|
||||
let token = Arg::with_name("token").help("Token ID to send");
|
||||
|
||||
let recipient = Arg::with_name("recipient").help("Recipient address");
|
||||
|
||||
let transfer = SubCommand::with_name("transfer")
|
||||
.about("Create a payment transaction")
|
||||
.args(&vec![amount, token, recipient]);
|
||||
|
||||
// Otc
|
||||
let value_pair = Arg::with_name("value-pair")
|
||||
.short("v")
|
||||
.long("value-pair")
|
||||
.takes_value(true)
|
||||
.help("Value pair to send:recv (11.55:99.42)");
|
||||
|
||||
let token_pair = Arg::with_name("token-pair")
|
||||
.short("t")
|
||||
.long("token-pair")
|
||||
.takes_value(true)
|
||||
.help("Token pair to send:recv (f00:b4r)");
|
||||
|
||||
let init = SubCommand::with_name("init")
|
||||
.about("Initialize the first half of the atomic swap")
|
||||
.args(&vec![value_pair, token_pair]);
|
||||
|
||||
let join =
|
||||
SubCommand::with_name("join").about("Build entire swap tx given the first half from stdin");
|
||||
|
||||
let inspect = SubCommand::with_name("inspect")
|
||||
.about("Inspect a swap half or the full swap tx from stdin");
|
||||
|
||||
let sign = SubCommand::with_name("sign")
|
||||
.about("Sign a transaction given from stdin as the first-half");
|
||||
|
||||
let otc = SubCommand::with_name("otc")
|
||||
.about("OTC atomic swap")
|
||||
.subcommands(vec![init, join, inspect, sign]);
|
||||
|
||||
// Inspect
|
||||
let inspect = SubCommand::with_name("inspect").about("Inspect a transaction from stdin");
|
||||
|
||||
// Broadcast
|
||||
let broadcast =
|
||||
SubCommand::with_name("broadcast").about("Read a transaction from stdin and broadcast it");
|
||||
|
||||
// Subscribe
|
||||
let subscribe = SubCommand::with_name("subscribe").about(
|
||||
"This subscription will listen for incoming blocks from darkfid and look \
|
||||
through their transactions to see if there's any that interest us. \
|
||||
With `drk` we look at transactions calling the money contract so we can \
|
||||
find coins sent to us and fill our wallet with the necessary metadata.",
|
||||
);
|
||||
|
||||
// DAO
|
||||
let proposer_limit = Arg::with_name("proposer-limit")
|
||||
.help("The minimum amount of governance tokens needed to open a proposal for this DAO");
|
||||
|
||||
let quorum = Arg::with_name("quorum")
|
||||
.help("Minimal threshold of participating total tokens needed for a proposal to pass");
|
||||
|
||||
let approval_ratio = Arg::with_name("approval-ratio")
|
||||
.help("The ratio of winning votes/total votes needed for a proposal to pass (2 decimals)");
|
||||
|
||||
let gov_token_id = Arg::with_name("gov-token-id").help("DAO's governance token ID");
|
||||
|
||||
let create = SubCommand::with_name("create").about("Create DAO parameters").args(&vec![
|
||||
proposer_limit,
|
||||
quorum,
|
||||
approval_ratio,
|
||||
gov_token_id,
|
||||
]);
|
||||
|
||||
let view = SubCommand::with_name("view").about("View DAO data from stdin");
|
||||
|
||||
let dao_name = Arg::with_name("dao-name").help("Named identifier for the DAO");
|
||||
|
||||
let import =
|
||||
SubCommand::with_name("import").about("Import DAO data from stdin").args(&vec![dao_name]);
|
||||
|
||||
let dao_alias = Arg::with_name("dao-alias").help("Numeric identifier for the DAO (optional)");
|
||||
|
||||
let list = SubCommand::with_name("list")
|
||||
.about("List imported DAOs (or info about a specific one)")
|
||||
.args(&vec![dao_alias]);
|
||||
|
||||
let dao_alias = Arg::with_name("dao-alias").help("Name or numeric identifier for the DAO");
|
||||
|
||||
let balance = SubCommand::with_name("balance")
|
||||
.about("Show the balance of a DAO")
|
||||
.args(&vec![dao_alias.clone()]);
|
||||
|
||||
let mint = SubCommand::with_name("mint")
|
||||
.about("Mint an imported DAO on-chain")
|
||||
.args(&vec![dao_alias.clone()]);
|
||||
|
||||
let recipient =
|
||||
Arg::with_name("recipient").help("Pubkey to send tokens to with proposal success");
|
||||
|
||||
let amount = Arg::with_name("amount").help("Amount to send from DAO with proposal success");
|
||||
|
||||
let token = Arg::with_name("token").help("Token ID to send from DAO with proposal success");
|
||||
|
||||
let propose = SubCommand::with_name("propose")
|
||||
.about("Create a proposal for a DAO")
|
||||
.args(&vec![dao_alias.clone(), recipient, amount, token]);
|
||||
|
||||
let proposals = SubCommand::with_name("proposals")
|
||||
.about("List DAO proposals")
|
||||
.args(&vec![dao_alias.clone()]);
|
||||
|
||||
let proposal_id = Arg::with_name("proposal-id").help("Numeric identifier for the proposal");
|
||||
|
||||
let proposal = SubCommand::with_name("proposal")
|
||||
.about("View a DAO proposal data")
|
||||
.args(&vec![dao_alias.clone(), proposal_id.clone()]);
|
||||
|
||||
let vote = Arg::with_name("vote").help("Vote (0 for NO, 1 for YES)");
|
||||
|
||||
let vote_weight =
|
||||
Arg::with_name("vote-weight").help("Vote weight (amount of governance tokens)");
|
||||
|
||||
let vote = SubCommand::with_name("vote").about("Vote on a given proposal").args(&vec![
|
||||
dao_alias.clone(),
|
||||
proposal_id.clone(),
|
||||
vote,
|
||||
vote_weight,
|
||||
]);
|
||||
|
||||
let exec = SubCommand::with_name("exec")
|
||||
.about("Execute a DAO proposal")
|
||||
.args(&vec![dao_alias, proposal_id]);
|
||||
|
||||
let dao = SubCommand::with_name("dao").about("DAO functionalities").subcommands(vec![
|
||||
create, view, import, list, balance, mint, propose, proposals, proposal, vote, exec,
|
||||
]);
|
||||
|
||||
// Scan
|
||||
let reset = Arg::with_name("reset")
|
||||
.long("reset")
|
||||
.help("Reset Merkle tree and start scanning from first block");
|
||||
|
||||
let list = Arg::with_name("list").long("list").help("List all available checkpoints");
|
||||
|
||||
let checkpoint = Arg::with_name("checkpoint")
|
||||
.long("checkpoint")
|
||||
.takes_value(true)
|
||||
.help("Reset Merkle tree to checkpoint index and start scanning");
|
||||
|
||||
let scan = SubCommand::with_name("scan")
|
||||
.about("Scan the blockchain and parse relevant transactions")
|
||||
.args(&vec![reset, list, checkpoint]);
|
||||
|
||||
// Explorer
|
||||
let tx_hash = Arg::with_name("tx-hash").help("Transaction hash");
|
||||
|
||||
let full = Arg::with_name("full").long("full").help("Print the full transaction information");
|
||||
|
||||
let encode = Arg::with_name("encode").long("encode").help("Encode transaction to base58");
|
||||
|
||||
let fetch_tx = SubCommand::with_name("fetch-tx")
|
||||
.about("Fetch a blockchain transaction by hash")
|
||||
.args(&vec![tx_hash, full, encode]);
|
||||
|
||||
let simulate_tx =
|
||||
SubCommand::with_name("simulate-tx").about("Read a transaction from stdin and simulate it");
|
||||
|
||||
let tx_hash = Arg::with_name("tx-hash").help("Transaction hash");
|
||||
|
||||
let encode = Arg::with_name("encode")
|
||||
.long("encode")
|
||||
.help("Encode specific history record transaction to base58");
|
||||
|
||||
let txs_history = SubCommand::with_name("txs-history")
|
||||
.about("Fetch broadcasted transactions history")
|
||||
.args(&vec![tx_hash, encode]);
|
||||
|
||||
let explorer = SubCommand::with_name("explorer")
|
||||
.about("Explorer related subcommands")
|
||||
.subcommands(vec![fetch_tx, simulate_tx, txs_history]);
|
||||
|
||||
// Alias
|
||||
let alias = Arg::with_name("alias").help("Token alias");
|
||||
|
||||
let token = Arg::with_name("token").help("Token to create alias for");
|
||||
|
||||
let add = SubCommand::with_name("add").about("Create a Token alias").args(&vec![alias, token]);
|
||||
|
||||
let alias = Arg::with_name("alias")
|
||||
.short("a")
|
||||
.long("alias")
|
||||
.takes_value(true)
|
||||
.help("Token alias to search for");
|
||||
|
||||
let token = Arg::with_name("token")
|
||||
.short("t")
|
||||
.long("token")
|
||||
.takes_value(true)
|
||||
.help("Token to search alias for");
|
||||
|
||||
let show = SubCommand::with_name("show")
|
||||
.about(
|
||||
"Print alias info of optional arguments. \
|
||||
If no argument is provided, list all the aliases in the wallet.",
|
||||
)
|
||||
.args(&vec![alias, token]);
|
||||
|
||||
let alias = Arg::with_name("alias").help("Token alias to remove");
|
||||
|
||||
let remove = SubCommand::with_name("remove").about("Remove a Token alias").arg(alias);
|
||||
|
||||
let alias = SubCommand::with_name("alias")
|
||||
.about("Manage Token aliases")
|
||||
.subcommands(vec![add, show, remove]);
|
||||
|
||||
// Token
|
||||
let import = SubCommand::with_name("import").about("Import a mint authority secret from stdin");
|
||||
|
||||
let generate_mint =
|
||||
SubCommand::with_name("generate-mint").about("Generate a new mint authority");
|
||||
|
||||
let list =
|
||||
SubCommand::with_name("list").about("List token IDs with available mint authorities");
|
||||
|
||||
let token = Arg::with_name("token").help("Token ID to mint");
|
||||
|
||||
let amount = Arg::with_name("amount").help("Amount to mint");
|
||||
|
||||
let recipient = Arg::with_name("recipient").help("Recipient of the minted tokens");
|
||||
|
||||
let mint =
|
||||
SubCommand::with_name("mint").about("Mint tokens").args(&vec![token, amount, recipient]);
|
||||
|
||||
let token = Arg::with_name("token").help("Token ID to freeze");
|
||||
|
||||
let freeze = SubCommand::with_name("freeze").about("Freeze a token mint").arg(token);
|
||||
|
||||
let token = SubCommand::with_name("token").about("Token functionalities").subcommands(vec![
|
||||
import,
|
||||
generate_mint,
|
||||
list,
|
||||
mint,
|
||||
freeze,
|
||||
]);
|
||||
|
||||
// Main arguments
|
||||
let config = Arg::with_name("config")
|
||||
.short("c")
|
||||
.long("config")
|
||||
.takes_value(true)
|
||||
.help("Configuration file to use");
|
||||
|
||||
let wallet_path = Arg::with_name("wallet_path")
|
||||
.long("wallet-path")
|
||||
.takes_value(true)
|
||||
.help("Path to wallet database");
|
||||
|
||||
let wallet_pass = Arg::with_name("wallet_pass")
|
||||
.long("wallet-pass")
|
||||
.takes_value(true)
|
||||
.help("Password for the wallet database");
|
||||
|
||||
let endpoint = Arg::with_name("endpoint")
|
||||
.short("e")
|
||||
.long("endpoint")
|
||||
.takes_value(true)
|
||||
.help("darkfid JSON-RPC endpoint");
|
||||
|
||||
let command = vec![
|
||||
kaching,
|
||||
ping,
|
||||
completions,
|
||||
wallet,
|
||||
unspend,
|
||||
transfer,
|
||||
otc,
|
||||
inspect,
|
||||
broadcast,
|
||||
subscribe,
|
||||
dao,
|
||||
scan,
|
||||
explorer,
|
||||
alias,
|
||||
token,
|
||||
];
|
||||
|
||||
let log = Arg::with_name("log")
|
||||
.short("l")
|
||||
.long("log")
|
||||
.takes_value(true)
|
||||
.help("Set log file to ouput into");
|
||||
|
||||
let verbose = Arg::with_name("verbose")
|
||||
.short("v")
|
||||
.multiple(true)
|
||||
.help("Increase verbosity (-vvv supported)");
|
||||
|
||||
let mut app = App::new("drk")
|
||||
.about(cli_desc!())
|
||||
.args(&vec![config, wallet_path, wallet_pass, endpoint, log, verbose])
|
||||
.subcommands(command);
|
||||
|
||||
let shell = match Shell::from_str(shell) {
|
||||
Ok(s) => s,
|
||||
Err(e) => return Err(Error::Custom(e)),
|
||||
};
|
||||
|
||||
app.gen_completions_to("./drk", shell, &mut std::io::stdout());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::{
|
||||
env::set_current_dir,
|
||||
fs::{read, read_dir, read_to_string, File},
|
||||
io::{ErrorKind, Write},
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
|
||||
use darkfi::{
|
||||
crypto::keypair::SecretKey,
|
||||
node::{MemoryState, State},
|
||||
runtime::vm_runtime::Runtime,
|
||||
util::cli::{fg_green, fg_red},
|
||||
zkas::ZkBinary,
|
||||
Error, Result,
|
||||
};
|
||||
|
||||
const CIRCUIT_DIR_NAME: &str = "proof";
|
||||
const CONTRACT_FILE_NAME: &str = "contract.wasm";
|
||||
const DEPLOY_KEY_NAME: &str = "deploy.key";
|
||||
|
||||
/// Creates a new deploy key used for deploying private smart contracts.
|
||||
/// This key allows to update the wasm code and the zk circuits on chain
|
||||
/// by creating a signature. When deployed, the contract can be accessed
|
||||
/// by requesting the public counterpart of this secret key.
|
||||
pub fn create_deploy_key(mut rng: impl RngCore, path: &Path) -> Result<SecretKey> {
|
||||
let secret = SecretKey::random(&mut rng);
|
||||
let mut file = File::create(path)?;
|
||||
file.write_all(bs58::encode(&secret.to_bytes()).into_string().as_bytes())?;
|
||||
Ok(secret)
|
||||
}
|
||||
|
||||
/// Reads a deploy key from a file on the filesystem and returns it.
|
||||
fn read_deploy_key(s: &Path) -> core::result::Result<SecretKey, std::io::Error> {
|
||||
eprintln!("Trying to read deploy key from file: {:?}", s);
|
||||
let contents = read_to_string(s)?;
|
||||
let secret = SecretKey::from_str(&contents).unwrap();
|
||||
Ok(secret)
|
||||
}
|
||||
|
||||
/// Creates necessary data to deploy a given smart contract on the network.
|
||||
/// For consistency, we point this function to a directory where our smart
|
||||
/// contract and the compiled circuits are contained. This is going to give
|
||||
/// us a uniform approach to scm and gives a generic layout of the source:
|
||||
/// ```text
|
||||
/// smart-contract
|
||||
/// ├── Cargo.toml
|
||||
/// ├── deploy.key
|
||||
/// ├── Makefile
|
||||
/// ├── proof
|
||||
/// │ ├── circuit0.zk
|
||||
/// │ ├── circuit0.zk.bin
|
||||
/// │ ├── circuit1.zk
|
||||
/// │ └── circuit1.zk.bin
|
||||
/// ├── contract.wasm
|
||||
/// ├── src
|
||||
/// │ └── lib.rs
|
||||
/// └── tests
|
||||
/// ```
|
||||
//pub fn create_deploy_data(path: &Path) -> Result<ContractDeploy> {
|
||||
pub fn create_deploy_data(path: &Path) -> Result<()> {
|
||||
// Try to chdir into the contract directory
|
||||
if let Err(e) = set_current_dir(path) {
|
||||
eprintln!("Failed to chdir into {:?}", path);
|
||||
return Err(e.into())
|
||||
}
|
||||
|
||||
let deploy_key: SecretKey;
|
||||
|
||||
let deploy_key = match read_deploy_key(&PathBuf::from(DEPLOY_KEY_NAME)) {
|
||||
Ok(v) => deploy_key = v,
|
||||
Err(e) => {
|
||||
if e.kind() == ErrorKind::NotFound {
|
||||
// We didn't find a deploy key, generate a new one.
|
||||
eprintln!("Did not find an existing key, creating a new one.");
|
||||
match create_deploy_key(&mut OsRng, &PathBuf::from(DEPLOY_KEY_NAME)) {
|
||||
Ok(v) => {
|
||||
eprintln!("Created new deploy key in \"{}\".", DEPLOY_KEY_NAME);
|
||||
deploy_key = v;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to create new deploy key");
|
||||
return Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
eprintln!("Failed to read deploy key");
|
||||
return Err(e.into())
|
||||
}
|
||||
};
|
||||
|
||||
// Search for ZK circuits in the directory. If none are found, we'll bail.
|
||||
// The logic searches for `.zk.bin` files created by zkas.
|
||||
eprintln!("Searching for compiled ZK circuits in \"{}\" ...", CIRCUIT_DIR_NAME);
|
||||
let mut circuits = vec![];
|
||||
for i in read_dir(CIRCUIT_DIR_NAME)? {
|
||||
if let Err(e) = i {
|
||||
eprintln!("Error iterating over \"{}\" directory", CIRCUIT_DIR_NAME);
|
||||
return Err(e.into())
|
||||
}
|
||||
|
||||
let f = i.unwrap();
|
||||
let fname = f.file_name();
|
||||
let fname = fname.to_str().unwrap();
|
||||
|
||||
if fname.ends_with(".zk.bin") {
|
||||
// Validate that the files can be properly decoded
|
||||
eprintln!("{} {}", fg_green("Found:"), f.path().display());
|
||||
let buf = read(f.path())?;
|
||||
if let Err(e) = ZkBinary::decode(&buf) {
|
||||
eprintln!("{} Failed to decode zkas bincode in {:?}", fg_red("Error:"), f.path());
|
||||
return Err(e)
|
||||
}
|
||||
|
||||
circuits.push(buf.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if circuits.is_empty() {
|
||||
return Err(Error::Custom("Found no valid ZK circuits".to_string()))
|
||||
}
|
||||
|
||||
/* FIXME
|
||||
// Validate wasm binary. We inspect the bincode and try to load it into
|
||||
// the wasm runtime. If loaded, we then look for the `ENTRYPOINT` function
|
||||
// which we hardcode into our sdk and runtime and is the canonical way to
|
||||
// run wasm binaries on chain.
|
||||
eprintln!("Inspecting wasm binary in \"{}\"", CONTRACT_FILE_NAME);
|
||||
let wasm_bytes = read(CONTRACT_FILE_NAME)?;
|
||||
eprintln!("Initializing moch wasm runtime to check validity");
|
||||
let runtime = match Runtime::new(&wasm_bytes, MemoryState::new(State::dummy()?)) {
|
||||
Ok(v) => {
|
||||
eprintln!("Found {} wasm binary", fg_green("valid"));
|
||||
v
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to initialize wasm runtime");
|
||||
return Err(e)
|
||||
}
|
||||
};
|
||||
|
||||
eprintln!("Looking for entrypoint function inside the wasm");
|
||||
let cs = ContractSection::Exec;
|
||||
if let Err(e) = runtime.instance.exports.get_function(cs.name()) {
|
||||
eprintln!("{} Could not find entrypoint function", fg_red("Error:"));
|
||||
return Err(e.into())
|
||||
}
|
||||
|
||||
// TODO: Create a ZK proof enforcing the deploy key relations with their public
|
||||
// counterparts (public key and contract address)
|
||||
let mut total_bytes = 0;
|
||||
total_bytes += wasm_bytes.len();
|
||||
for circuit in circuits {
|
||||
total_bytes += circuit.len();
|
||||
}
|
||||
*/
|
||||
|
||||
// TODO: Return the data back to the main function, and work further in creating
|
||||
// a transaction and broadcasting it.
|
||||
Ok(())
|
||||
}
|
||||
1151
bin/drk/src/main.rs
1151
bin/drk/src/main.rs
File diff suppressed because it is too large
Load Diff
@@ -1,72 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use darkfi::rpc::{client::RpcClient, jsonrpc::JsonRequest};
|
||||
use darkfi_sdk::{
|
||||
crypto::{mimc_vdf, PublicKey},
|
||||
num_bigint::BigUint,
|
||||
num_traits::Num,
|
||||
};
|
||||
use serde_json::json;
|
||||
use url::Url;
|
||||
|
||||
use super::Drk;
|
||||
|
||||
impl Drk {
|
||||
/// Request an airdrop of `amount` `token_id` tokens from a faucet.
|
||||
/// Returns a transaction ID on success.
|
||||
pub async fn request_airdrop(
|
||||
&self,
|
||||
faucet_endpoint: Url,
|
||||
amount: f64,
|
||||
address: PublicKey,
|
||||
) -> Result<String> {
|
||||
let rpc_client = RpcClient::new(faucet_endpoint, None).await?;
|
||||
|
||||
// First we request a VDF challenge from the faucet
|
||||
let params = json!([format!("{}", address)]);
|
||||
let req = JsonRequest::new("challenge", params);
|
||||
let rep = rpc_client.request(req).await?;
|
||||
|
||||
let Some(rep) = rep.as_array() else {
|
||||
return Err(anyhow!("Invalid challenge response from faucet: {:?}", rep))
|
||||
};
|
||||
if rep.len() != 2 || !rep[0].is_string() || !rep[1].is_u64() {
|
||||
return Err(anyhow!("Invalid challenge response from faucet: {:?}", rep))
|
||||
}
|
||||
|
||||
// Retrieve VDF challenge
|
||||
let challenge = BigUint::from_str_radix(rep[0].as_str().unwrap(), 16)?;
|
||||
let n_steps = rep[1].as_u64().unwrap();
|
||||
|
||||
// Then evaluate the VDF
|
||||
eprintln!("Evaluating VDF with n_steps={} ... (this could take about a minute)", n_steps);
|
||||
let witness = mimc_vdf::eval(&challenge, n_steps);
|
||||
eprintln!("Done! Sending airdrop request...");
|
||||
|
||||
// And finally request airdrop with the VDF evaluation witness
|
||||
let params = json!([format!("{}", address), amount, witness.to_str_radix(16)]);
|
||||
let req = JsonRequest::new("airdrop", params);
|
||||
let rep = rpc_client.oneshot_request(req).await?;
|
||||
|
||||
let txid = serde_json::from_value(rep)?;
|
||||
|
||||
Ok(txid)
|
||||
}
|
||||
}
|
||||
@@ -1,355 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_std::{stream::StreamExt, task};
|
||||
use darkfi::{
|
||||
consensus::BlockInfo,
|
||||
rpc::{
|
||||
client::RpcClient,
|
||||
jsonrpc::{JsonRequest, JsonResult},
|
||||
},
|
||||
system::Subscriber,
|
||||
tx::Transaction,
|
||||
wallet::walletdb::QueryType,
|
||||
};
|
||||
use darkfi_money_contract::client::{MONEY_INFO_COL_LAST_SCANNED_SLOT, MONEY_INFO_TABLE};
|
||||
use darkfi_sdk::crypto::ContractId;
|
||||
use darkfi_serial::{deserialize, serialize};
|
||||
use serde_json::json;
|
||||
use signal_hook::consts::{SIGINT, SIGQUIT, SIGTERM};
|
||||
use signal_hook_async_std::Signals;
|
||||
use url::Url;
|
||||
|
||||
use super::Drk;
|
||||
|
||||
impl Drk {
|
||||
/// Subscribes to darkfid's JSON-RPC notification endpoint that serves
|
||||
/// new finalized blocks. Upon receiving them, all the transactions are
|
||||
/// scanned and we check if any of them call the money contract, and if
|
||||
/// the payments are intended for us. If so, we decrypt them and append
|
||||
/// the metadata to our wallet.
|
||||
pub async fn subscribe_blocks(&self, endpoint: Url) -> Result<()> {
|
||||
let req = JsonRequest::new("blockchain.last_known_slot", json!([]));
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
let last_known: u64 = serde_json::from_value(rep)?;
|
||||
let last_scanned = self.last_scanned_slot().await?;
|
||||
|
||||
if last_known != last_scanned {
|
||||
eprintln!("Warning: Last scanned slot is not the last known slot.");
|
||||
eprintln!("You should first fully scan the blockchain, and then subscribe");
|
||||
return Err(anyhow!("Blockchain not fully scanned"))
|
||||
}
|
||||
|
||||
eprintln!("Subscribing to receive notifications of incoming blocks");
|
||||
let subscriber = Subscriber::new();
|
||||
let subscription = subscriber.clone().subscribe().await;
|
||||
|
||||
let rpc_client = RpcClient::new(endpoint, None).await?;
|
||||
|
||||
let req = JsonRequest::new("blockchain.subscribe_blocks", json!([]));
|
||||
task::spawn(async move { rpc_client.subscribe(req, subscriber).await.unwrap() });
|
||||
eprintln!("Detached subscription to background");
|
||||
eprintln!("All is good. Waiting for block notifications...");
|
||||
|
||||
let e = loop {
|
||||
match subscription.receive().await {
|
||||
JsonResult::Notification(n) => {
|
||||
eprintln!("Got Block notification from darkfid subscription");
|
||||
if n.method != "blockchain.subscribe_blocks" {
|
||||
break anyhow!("Got foreign notification from darkfid: {}", n.method)
|
||||
}
|
||||
|
||||
let Some(params) = n.params.as_array() else {
|
||||
break anyhow!("Received notification params are not an array")
|
||||
};
|
||||
|
||||
if params.len() != 1 {
|
||||
break anyhow!("Notification parameters are not len 1")
|
||||
}
|
||||
|
||||
let params = n.params.as_array().unwrap()[0].as_str().unwrap();
|
||||
let bytes = bs58::decode(params).into_vec()?;
|
||||
|
||||
let block_data: BlockInfo = deserialize(&bytes)?;
|
||||
eprintln!("=======================================");
|
||||
eprintln!("Block header:\n{:#?}", block_data.header);
|
||||
eprintln!("=======================================");
|
||||
|
||||
eprintln!("Deserialized successfully. Scanning block...");
|
||||
self.scan_block_money(&block_data).await?;
|
||||
self.scan_block_dao(&block_data).await?;
|
||||
self.update_tx_history_records_status(&block_data.txs, "Finalized").await?;
|
||||
}
|
||||
|
||||
JsonResult::Error(e) => {
|
||||
// Some error happened in the transmission
|
||||
break anyhow!("Got error from JSON-RPC: {:?}", e)
|
||||
}
|
||||
|
||||
x => {
|
||||
// And this is weird
|
||||
break anyhow!("Got unexpected data from JSON-RPC: {:?}", x)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Err(e)
|
||||
}
|
||||
|
||||
/// `scan_block_dao` will go over transactions in a block and fetch the ones dealing
|
||||
/// with the dao contract. Then over all of them, try to see if any are related
|
||||
/// to us. If any are found, the metadata is extracted and placed into the wallet
|
||||
/// for future use.
|
||||
async fn scan_block_dao(&self, block: &BlockInfo) -> Result<()> {
|
||||
eprintln!("[DAO] Iterating over {} transactions", block.txs.len());
|
||||
for tx in block.txs.iter() {
|
||||
self.apply_tx_dao_data(tx, true).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `scan_block_money` will go over transactions in a block and fetch the ones dealing
|
||||
/// with the money contract. Then over all of them, try to see if any are related
|
||||
/// to us. If any are found, the metadata is extracted and placed into the wallet
|
||||
/// for future use.
|
||||
async fn scan_block_money(&self, block: &BlockInfo) -> Result<()> {
|
||||
eprintln!("[Money] Iterating over {} transactions", block.txs.len());
|
||||
|
||||
for tx in block.txs.iter() {
|
||||
self.apply_tx_money_data(tx, true).await?;
|
||||
}
|
||||
|
||||
// Write this slot into `last_scanned_slot`
|
||||
let query =
|
||||
format!("UPDATE {} SET {} = ?1;", MONEY_INFO_TABLE, MONEY_INFO_COL_LAST_SCANNED_SLOT);
|
||||
let params = json!([query, QueryType::Integer as u8, block.header.slot]);
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let _ = self.rpc_client.request(req).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Try to fetch zkas bincodes for the given `ContractId`.
|
||||
pub async fn lookup_zkas(&self, contract_id: &ContractId) -> Result<Vec<(String, Vec<u8>)>> {
|
||||
eprintln!("Querying zkas bincode for {}", contract_id);
|
||||
|
||||
let params = json!([format!("{}", contract_id)]);
|
||||
let req = JsonRequest::new("blockchain.lookup_zkas", params);
|
||||
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
let ret = serde_json::from_value(rep)?;
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Broadcast a given transaction to darkfid and forward onto the network.
|
||||
/// Returns the transaction ID upon success
|
||||
pub async fn broadcast_tx(&self, tx: &Transaction) -> Result<String> {
|
||||
eprintln!("Broadcasting transaction...");
|
||||
|
||||
let params = json!([bs58::encode(&serialize(tx)).into_string()]);
|
||||
let req = JsonRequest::new("tx.broadcast", params);
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
let txid = serde_json::from_value(rep)?;
|
||||
|
||||
// Store transactions history record
|
||||
self.insert_tx_history_record(tx).await?;
|
||||
|
||||
Ok(txid)
|
||||
}
|
||||
|
||||
/// Simulate the transaction with the state machine
|
||||
pub async fn simulate_tx(&self, tx: &Transaction) -> Result<bool> {
|
||||
let params = json!([bs58::encode(&serialize(tx)).into_string()]);
|
||||
let req = JsonRequest::new("tx.simulate", params);
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
let is_valid = serde_json::from_value(rep)?;
|
||||
Ok(is_valid)
|
||||
}
|
||||
|
||||
/// Queries darkfid for a block with given slot
|
||||
async fn get_block_by_slot(&self, slot: u64) -> Result<Option<BlockInfo>> {
|
||||
let req = JsonRequest::new("blockchain.get_slot", json!([slot]));
|
||||
|
||||
// This API is weird, we need some way of telling it's an empty slot and
|
||||
// not an error
|
||||
match self.rpc_client.request(req).await {
|
||||
Ok(v) => {
|
||||
let block_bytes: Vec<u8> = serde_json::from_value(v)?;
|
||||
let block = deserialize(&block_bytes)?;
|
||||
Ok(Some(block))
|
||||
}
|
||||
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Queries darkfid for a tx with given hash
|
||||
pub async fn get_tx(&self, tx_hash: &blake3::Hash) -> Result<Option<Transaction>> {
|
||||
let tx_hash_str: &str = &tx_hash.to_hex();
|
||||
let req = JsonRequest::new("blockchain.get_tx", json!([tx_hash_str]));
|
||||
|
||||
match self.rpc_client.request(req).await {
|
||||
Ok(v) => {
|
||||
let tx_bytes: Vec<u8> = serde_json::from_value(v)?;
|
||||
let tx = deserialize(&tx_bytes)?;
|
||||
Ok(Some(tx))
|
||||
}
|
||||
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Scans the blockchain starting from the last scanned slot, for relevant
|
||||
/// money transfer transactions. If reset flag is provided, Merkle tree state
|
||||
/// and coins are reset, and start scanning from beginning. Alternatively,
|
||||
/// it looks for a checkpoint in the wallet to reset and start scanning from.
|
||||
pub async fn scan_blocks(&self, reset: bool) -> Result<()> {
|
||||
let mut sl = if reset {
|
||||
self.reset_money_tree().await?;
|
||||
self.reset_money_coins().await?;
|
||||
self.reset_dao_trees().await?;
|
||||
self.reset_daos().await?;
|
||||
self.reset_dao_proposals().await?;
|
||||
self.reset_dao_votes().await?;
|
||||
self.update_all_tx_history_records_status("Rejected").await?;
|
||||
0
|
||||
} else {
|
||||
self.last_scanned_slot().await?
|
||||
};
|
||||
|
||||
let req = JsonRequest::new("blockchain.last_known_slot", json!([]));
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
let last: u64 = serde_json::from_value(rep)?;
|
||||
|
||||
eprintln!("Requested to scan from slot number: {}", sl);
|
||||
eprintln!("Last known slot number reported by darkfid: {}", last);
|
||||
|
||||
// Already scanned last known slot
|
||||
if sl == last {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
// We set this up to handle an interrupt
|
||||
let mut signals = Signals::new([SIGTERM, SIGINT, SIGQUIT])?;
|
||||
let handle = signals.handle();
|
||||
let (term_tx, _term_rx) = smol::channel::bounded::<()>(1);
|
||||
|
||||
let term_tx_ = term_tx.clone();
|
||||
let signals_task = task::spawn(async move {
|
||||
while let Some(signal) = signals.next().await {
|
||||
match signal {
|
||||
SIGTERM | SIGINT | SIGQUIT => term_tx_.close(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
while !term_tx.is_closed() {
|
||||
sl += 1;
|
||||
|
||||
if sl > last {
|
||||
term_tx.close();
|
||||
break
|
||||
}
|
||||
|
||||
eprint!("Requesting slot {}... ", sl);
|
||||
if let Some(block) = self.get_block_by_slot(sl).await? {
|
||||
eprintln!("Found");
|
||||
self.scan_block_money(&block).await?;
|
||||
self.scan_block_dao(&block).await?;
|
||||
self.update_tx_history_records_status(&block.txs, "Finalized").await?;
|
||||
} else {
|
||||
eprintln!("Not found");
|
||||
// Write down the slot number into back to the wallet
|
||||
// This might be a bit intense, but we accept it for now.
|
||||
let query = format!(
|
||||
"UPDATE {} SET {} = ?1;",
|
||||
MONEY_INFO_TABLE, MONEY_INFO_COL_LAST_SCANNED_SLOT
|
||||
);
|
||||
let params = json!([query, QueryType::Integer as u8, sl]);
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let _ = self.rpc_client.request(req).await?;
|
||||
}
|
||||
}
|
||||
|
||||
handle.close();
|
||||
signals_task.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Subscribes to darkfid's JSON-RPC notification endpoint that serves
|
||||
/// erroneous transactions rejections.
|
||||
pub async fn subscribe_err_txs(&self, endpoint: Url) -> Result<()> {
|
||||
eprintln!("Subscribing to receive notifications of erroneous transactions");
|
||||
let subscriber = Subscriber::new();
|
||||
let subscription = subscriber.clone().subscribe().await;
|
||||
|
||||
let rpc_client = RpcClient::new(endpoint, None).await?;
|
||||
|
||||
let req = JsonRequest::new("blockchain.subscribe_err_txs", json!([]));
|
||||
task::spawn(async move { rpc_client.subscribe(req, subscriber).await.unwrap() });
|
||||
eprintln!("Detached subscription to background");
|
||||
eprintln!("All is good. Waiting for erroneous transactions notifications...");
|
||||
|
||||
let e = loop {
|
||||
match subscription.receive().await {
|
||||
JsonResult::Notification(n) => {
|
||||
eprintln!("Got erroneous transaction notification from darkfid subscription");
|
||||
if n.method != "blockchain.subscribe_err_txs" {
|
||||
break anyhow!("Got foreign notification from darkfid: {}", n.method)
|
||||
}
|
||||
|
||||
let Some(params) = n.params.as_array() else {
|
||||
break anyhow!("Received notification params are not an array")
|
||||
};
|
||||
|
||||
if params.len() != 1 {
|
||||
break anyhow!("Notification parameters are not len 1")
|
||||
}
|
||||
|
||||
let params = n.params.as_array().unwrap()[0].as_str().unwrap();
|
||||
let bytes = bs58::decode(params).into_vec()?;
|
||||
|
||||
let tx_hash: String = deserialize(&bytes)?;
|
||||
eprintln!("===================================");
|
||||
eprintln!("Erroneous transaction: {}", tx_hash);
|
||||
eprintln!("===================================");
|
||||
self.update_tx_history_record_status(&tx_hash, "Rejected").await?;
|
||||
}
|
||||
|
||||
JsonResult::Error(e) => {
|
||||
// Some error happened in the transmission
|
||||
break anyhow!("Got error from JSON-RPC: {:?}", e)
|
||||
}
|
||||
|
||||
x => {
|
||||
// And this is weird
|
||||
break anyhow!("Got unexpected data from JSON-RPC: {:?}", x)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
@@ -1,553 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use darkfi::{
|
||||
tx::Transaction,
|
||||
zk::{empty_witnesses, halo2::Field, ProvingKey, ZkCircuit},
|
||||
zkas::ZkBinary,
|
||||
};
|
||||
use darkfi_dao_contract::{
|
||||
client as dao_client,
|
||||
client::{DaoInfo, DaoProposalInfo, DaoVoteCall, DaoVoteInput},
|
||||
model::DaoBlindAggregateVote,
|
||||
DaoFunction, DAO_CONTRACT_ZKAS_DAO_EXEC_NS, DAO_CONTRACT_ZKAS_DAO_MINT_NS,
|
||||
DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS,
|
||||
DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
|
||||
};
|
||||
use darkfi_money_contract::{
|
||||
client::{transfer_v1::TransferCallBuilder, OwnCoin},
|
||||
MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
|
||||
};
|
||||
use darkfi_sdk::{
|
||||
crypto::{
|
||||
pedersen_commitment_u64, Keypair, PublicKey, SecretKey, TokenId, DAO_CONTRACT_ID,
|
||||
MONEY_CONTRACT_ID,
|
||||
},
|
||||
pasta::pallas,
|
||||
ContractCall,
|
||||
};
|
||||
use darkfi_serial::Encodable;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use super::Drk;
|
||||
use crate::wallet_dao::{Dao, DaoProposal};
|
||||
|
||||
impl Drk {
|
||||
/// Mint a DAO on-chain
|
||||
pub async fn dao_mint(&self, dao_id: u64) -> Result<Transaction> {
|
||||
let dao = self.get_dao_by_id(dao_id).await?;
|
||||
|
||||
if dao.tx_hash.is_some() {
|
||||
return Err(anyhow!("This DAO seems to have already been minted on-chain"))
|
||||
}
|
||||
|
||||
let dao_info = DaoInfo {
|
||||
proposer_limit: dao.proposer_limit,
|
||||
quorum: dao.quorum,
|
||||
approval_ratio_base: dao.approval_ratio_base,
|
||||
approval_ratio_quot: dao.approval_ratio_quot,
|
||||
gov_token_id: dao.gov_token_id,
|
||||
public_key: PublicKey::from_secret(dao.secret_key),
|
||||
bulla_blind: dao.bulla_blind,
|
||||
};
|
||||
|
||||
let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
|
||||
let Some(dao_mint_zkbin) = zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_MINT_NS)
|
||||
else {
|
||||
return Err(anyhow!("DAO Mint circuit not found"))
|
||||
};
|
||||
|
||||
let dao_mint_zkbin = ZkBinary::decode(&dao_mint_zkbin.1)?;
|
||||
let dao_mint_circuit = ZkCircuit::new(empty_witnesses(&dao_mint_zkbin)?, &dao_mint_zkbin);
|
||||
eprintln!("Creating DAO Mint proving key");
|
||||
let dao_mint_pk = ProvingKey::build(dao_mint_zkbin.k, &dao_mint_circuit);
|
||||
|
||||
let (params, proofs) =
|
||||
dao_client::make_mint_call(&dao_info, &dao.secret_key, &dao_mint_zkbin, &dao_mint_pk)?;
|
||||
|
||||
let mut data = vec![DaoFunction::Mint as u8];
|
||||
params.encode(&mut data)?;
|
||||
let calls = vec![ContractCall { contract_id: *DAO_CONTRACT_ID, data }];
|
||||
let proofs = vec![proofs];
|
||||
let mut tx = Transaction { calls, proofs, signatures: vec![] };
|
||||
let sigs = tx.create_sigs(&mut OsRng, &[dao.secret_key])?;
|
||||
tx.signatures = vec![sigs];
|
||||
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
/// Create a DAO proposal
|
||||
pub async fn dao_propose(
|
||||
&self,
|
||||
dao_id: u64,
|
||||
recipient: PublicKey,
|
||||
amount: u64,
|
||||
token_id: TokenId,
|
||||
) -> Result<Transaction> {
|
||||
let Ok(dao) = self.get_dao_by_id(dao_id).await else {
|
||||
return Err(anyhow!("DAO not found in wallet"))
|
||||
};
|
||||
|
||||
if dao.leaf_position.is_none() || dao.tx_hash.is_none() {
|
||||
return Err(anyhow!("DAO seems to not have been deployed yet"))
|
||||
}
|
||||
|
||||
let bulla = dao.bulla();
|
||||
let owncoins = self.get_coins(false).await?;
|
||||
|
||||
let mut dao_owncoins: Vec<OwnCoin> = owncoins.iter().map(|x| x.0.clone()).collect();
|
||||
dao_owncoins.retain(|x| {
|
||||
x.note.token_id == token_id &&
|
||||
x.note.spend_hook == DAO_CONTRACT_ID.inner() &&
|
||||
x.note.user_data == bulla.inner()
|
||||
});
|
||||
|
||||
let mut gov_owncoins: Vec<OwnCoin> = owncoins.iter().map(|x| x.0.clone()).collect();
|
||||
gov_owncoins.retain(|x| x.note.token_id == dao.gov_token_id);
|
||||
|
||||
if dao_owncoins.is_empty() {
|
||||
return Err(anyhow!("Did not find any {} coins owned by this DAO", token_id))
|
||||
}
|
||||
|
||||
if gov_owncoins.is_empty() {
|
||||
return Err(anyhow!("Did not find any governance {} coins in wallet", dao.gov_token_id))
|
||||
}
|
||||
|
||||
if dao_owncoins.iter().map(|x| x.note.value).sum::<u64>() < amount {
|
||||
return Err(anyhow!("Not enough DAO balance for token ID: {}", token_id))
|
||||
}
|
||||
|
||||
if gov_owncoins.iter().map(|x| x.note.value).sum::<u64>() < dao.proposer_limit {
|
||||
return Err(anyhow!("Not enough gov token {} balance to propose", dao.gov_token_id))
|
||||
}
|
||||
|
||||
// FIXME: Here we're looking for a coin == proposer_limit but this shouldn't have to
|
||||
// be the case {
|
||||
let Some(gov_coin) = gov_owncoins.iter().find(|x| x.note.value == dao.proposer_limit)
|
||||
else {
|
||||
return Err(anyhow!("Did not find a single gov coin of value {}", dao.proposer_limit))
|
||||
};
|
||||
// }
|
||||
|
||||
// Lookup the zkas bins
|
||||
let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
|
||||
let Some(propose_burn_zkbin) =
|
||||
zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS)
|
||||
else {
|
||||
return Err(anyhow!("Propose Burn circuit not found"))
|
||||
};
|
||||
|
||||
let Some(propose_main_zkbin) =
|
||||
zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS)
|
||||
else {
|
||||
return Err(anyhow!("Propose Main circuit not found"))
|
||||
};
|
||||
|
||||
let propose_burn_zkbin = ZkBinary::decode(&propose_burn_zkbin.1)?;
|
||||
let propose_main_zkbin = ZkBinary::decode(&propose_main_zkbin.1)?;
|
||||
|
||||
let propose_burn_circuit =
|
||||
ZkCircuit::new(empty_witnesses(&propose_burn_zkbin)?, &propose_burn_zkbin);
|
||||
let propose_main_circuit =
|
||||
ZkCircuit::new(empty_witnesses(&propose_main_zkbin)?, &propose_main_zkbin);
|
||||
|
||||
eprintln!("Creating Propose Burn circuit proving key");
|
||||
let propose_burn_pk = ProvingKey::build(propose_burn_zkbin.k, &propose_burn_circuit);
|
||||
eprintln!("Creating Propose Main circuit proving key");
|
||||
let propose_main_pk = ProvingKey::build(propose_main_zkbin.k, &propose_main_circuit);
|
||||
|
||||
// Now create the parameters for the proposal tx
|
||||
let signature_secret = SecretKey::random(&mut OsRng);
|
||||
|
||||
// Get the Merkle path for the gov coin in the money tree
|
||||
let money_merkle_tree = self.get_money_tree().await?;
|
||||
let gov_coin_merkle_path = money_merkle_tree.witness(gov_coin.leaf_position, 0).unwrap();
|
||||
|
||||
// Fetch the daos Merkle tree
|
||||
let (daos_tree, _) = self.get_dao_trees().await?;
|
||||
|
||||
let input = dao_client::DaoProposeStakeInput {
|
||||
secret: gov_coin.secret, // <-- TODO: Is this correct?
|
||||
note: gov_coin.note.clone(),
|
||||
leaf_position: gov_coin.leaf_position,
|
||||
merkle_path: gov_coin_merkle_path,
|
||||
signature_secret,
|
||||
};
|
||||
|
||||
let (dao_merkle_path, dao_merkle_root) = {
|
||||
let root = daos_tree.root(0).unwrap();
|
||||
let leaf_pos = dao.leaf_position.unwrap();
|
||||
let dao_merkle_path = daos_tree.witness(leaf_pos, 0).unwrap();
|
||||
(dao_merkle_path, root)
|
||||
};
|
||||
|
||||
let proposal_blind = pallas::Base::random(&mut OsRng);
|
||||
let proposal = dao_client::DaoProposalInfo {
|
||||
dest: recipient,
|
||||
amount,
|
||||
token_id,
|
||||
blind: proposal_blind,
|
||||
};
|
||||
|
||||
let daoinfo = DaoInfo {
|
||||
proposer_limit: dao.proposer_limit,
|
||||
quorum: dao.quorum,
|
||||
approval_ratio_quot: dao.approval_ratio_quot,
|
||||
approval_ratio_base: dao.approval_ratio_base,
|
||||
gov_token_id: dao.gov_token_id,
|
||||
public_key: PublicKey::from_secret(dao.secret_key),
|
||||
bulla_blind: dao.bulla_blind,
|
||||
};
|
||||
|
||||
let call = dao_client::DaoProposeCall {
|
||||
inputs: vec![input],
|
||||
proposal,
|
||||
dao: daoinfo,
|
||||
dao_leaf_position: dao.leaf_position.unwrap(),
|
||||
dao_merkle_path,
|
||||
dao_merkle_root,
|
||||
};
|
||||
|
||||
eprintln!("Creating ZK proofs...");
|
||||
let (params, proofs) = call.make(
|
||||
&propose_burn_zkbin,
|
||||
&propose_burn_pk,
|
||||
&propose_main_zkbin,
|
||||
&propose_main_pk,
|
||||
)?;
|
||||
|
||||
let mut data = vec![DaoFunction::Propose as u8];
|
||||
params.encode(&mut data)?;
|
||||
let calls = vec![ContractCall { contract_id: *DAO_CONTRACT_ID, data }];
|
||||
let proofs = vec![proofs];
|
||||
let mut tx = Transaction { calls, proofs, signatures: vec![] };
|
||||
let sigs = tx.create_sigs(&mut OsRng, &[signature_secret])?;
|
||||
tx.signatures = vec![sigs];
|
||||
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
/// Vote on a DAO proposal
|
||||
pub async fn dao_vote(
|
||||
&self,
|
||||
dao_id: u64,
|
||||
proposal_id: u64,
|
||||
vote_option: bool,
|
||||
weight: u64,
|
||||
) -> Result<Transaction> {
|
||||
let dao = self.get_dao_by_id(dao_id).await?;
|
||||
let proposals = self.get_dao_proposals(dao_id).await?;
|
||||
let Some(proposal) = proposals.iter().find(|x| x.id == proposal_id) else {
|
||||
return Err(anyhow!("Proposal ID not found"))
|
||||
};
|
||||
|
||||
let money_tree = proposal.money_snapshot_tree.clone().unwrap();
|
||||
|
||||
let mut coins: Vec<OwnCoin> =
|
||||
self.get_coins(false).await?.iter().map(|x| x.0.clone()).collect();
|
||||
|
||||
coins.retain(|x| x.note.token_id == dao.gov_token_id);
|
||||
coins.retain(|x| x.note.spend_hook == pallas::Base::zero());
|
||||
|
||||
if coins.iter().map(|x| x.note.value).sum::<u64>() < weight {
|
||||
return Err(anyhow!("Not enough balance for vote weight"))
|
||||
}
|
||||
|
||||
// TODO: The spent coins need to either be marked as spent here, and/or on scan
|
||||
let mut spent_value = 0;
|
||||
let mut spent_coins = vec![];
|
||||
let mut inputs = vec![];
|
||||
let mut input_secrets = vec![];
|
||||
|
||||
// FIXME: We don't take back any change so it's possible to vote with > requested weight.
|
||||
for coin in coins {
|
||||
if spent_value >= weight {
|
||||
break
|
||||
}
|
||||
|
||||
spent_value += coin.note.value;
|
||||
spent_coins.push(coin.clone());
|
||||
|
||||
let signature_secret = SecretKey::random(&mut OsRng);
|
||||
input_secrets.push(signature_secret);
|
||||
|
||||
let leaf_position = coin.leaf_position;
|
||||
let merkle_path = money_tree.witness(coin.leaf_position, 0).unwrap();
|
||||
|
||||
let input = DaoVoteInput {
|
||||
secret: coin.secret,
|
||||
note: coin.note.clone(),
|
||||
leaf_position,
|
||||
merkle_path,
|
||||
signature_secret,
|
||||
};
|
||||
|
||||
inputs.push(input);
|
||||
}
|
||||
|
||||
// We use the DAO secret to encrypt the vote.
|
||||
let vote_keypair = Keypair::new(dao.secret_key);
|
||||
|
||||
let proposal_info = DaoProposalInfo {
|
||||
dest: proposal.recipient,
|
||||
amount: proposal.amount,
|
||||
token_id: proposal.token_id,
|
||||
blind: proposal.bulla_blind,
|
||||
};
|
||||
|
||||
let dao_info = DaoInfo {
|
||||
proposer_limit: dao.proposer_limit,
|
||||
quorum: dao.quorum,
|
||||
approval_ratio_quot: dao.approval_ratio_quot,
|
||||
approval_ratio_base: dao.approval_ratio_base,
|
||||
gov_token_id: dao.gov_token_id,
|
||||
public_key: PublicKey::from_secret(dao.secret_key),
|
||||
bulla_blind: dao.bulla_blind,
|
||||
};
|
||||
|
||||
let call = DaoVoteCall {
|
||||
inputs,
|
||||
vote_option,
|
||||
yes_vote_blind: pallas::Scalar::random(&mut OsRng),
|
||||
vote_keypair,
|
||||
proposal: proposal_info,
|
||||
dao: dao_info,
|
||||
};
|
||||
|
||||
let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
|
||||
let Some(dao_vote_burn_zkbin) =
|
||||
zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_VOTE_BURN_NS)
|
||||
else {
|
||||
return Err(anyhow!("DAO Vote Burn circuit not found"))
|
||||
};
|
||||
|
||||
let Some(dao_vote_main_zkbin) =
|
||||
zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS)
|
||||
else {
|
||||
return Err(anyhow!("DAO Vote Main circuit not found"))
|
||||
};
|
||||
|
||||
let dao_vote_burn_zkbin = ZkBinary::decode(&dao_vote_burn_zkbin.1)?;
|
||||
let dao_vote_main_zkbin = ZkBinary::decode(&dao_vote_main_zkbin.1)?;
|
||||
|
||||
let dao_vote_burn_circuit =
|
||||
ZkCircuit::new(empty_witnesses(&dao_vote_burn_zkbin)?, &dao_vote_burn_zkbin);
|
||||
let dao_vote_main_circuit =
|
||||
ZkCircuit::new(empty_witnesses(&dao_vote_main_zkbin)?, &dao_vote_main_zkbin);
|
||||
|
||||
eprintln!("Creating DAO Vote Burn proving key");
|
||||
let dao_vote_burn_pk = ProvingKey::build(dao_vote_burn_zkbin.k, &dao_vote_burn_circuit);
|
||||
eprintln!("Creating DAO Vote Main proving key");
|
||||
let dao_vote_main_pk = ProvingKey::build(dao_vote_main_zkbin.k, &dao_vote_main_circuit);
|
||||
|
||||
let (params, proofs) = call.make(
|
||||
&dao_vote_burn_zkbin,
|
||||
&dao_vote_burn_pk,
|
||||
&dao_vote_main_zkbin,
|
||||
&dao_vote_main_pk,
|
||||
)?;
|
||||
|
||||
let mut data = vec![DaoFunction::Vote as u8];
|
||||
params.encode(&mut data)?;
|
||||
let calls = vec![ContractCall { contract_id: *DAO_CONTRACT_ID, data }];
|
||||
let proofs = vec![proofs];
|
||||
let mut tx = Transaction { calls, proofs, signatures: vec![] };
|
||||
let sigs = tx.create_sigs(&mut OsRng, &input_secrets)?;
|
||||
tx.signatures = vec![sigs];
|
||||
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
/// Import given DAO votes into the wallet
|
||||
/// This function is really bad but I'm also really tired and annoyed.
|
||||
pub async fn dao_exec(&self, dao: Dao, proposal: DaoProposal) -> Result<Transaction> {
|
||||
let dao_bulla = dao.bulla();
|
||||
eprintln!("Fetching proposal's votes");
|
||||
let votes = self.get_dao_proposal_votes(proposal.id).await?;
|
||||
|
||||
// Find the treasury coins that can be used for this proposal
|
||||
let mut coins: Vec<OwnCoin> =
|
||||
self.get_coins(false).await?.iter().map(|x| x.0.clone()).collect();
|
||||
coins.retain(|x| x.note.spend_hook == DAO_CONTRACT_ID.inner());
|
||||
coins.retain(|x| x.note.user_data == dao_bulla.inner());
|
||||
coins.retain(|x| x.note.token_id == proposal.token_id);
|
||||
|
||||
if coins.iter().map(|x| x.note.value).sum::<u64>() < proposal.amount {
|
||||
return Err(anyhow!("Not enough balance in DAO treasury to execute proposal"))
|
||||
}
|
||||
|
||||
// FIXME: This assumes we aren't sending to a protocol.
|
||||
let rcpt_spend_hook = pallas::Base::ZERO;
|
||||
let rcpt_user_data = pallas::Base::ZERO;
|
||||
let rcpt_user_data_blind = pallas::Base::random(&mut OsRng);
|
||||
|
||||
let change_spend_hook = DAO_CONTRACT_ID.inner();
|
||||
let change_user_data = dao_bulla.inner();
|
||||
let change_user_data_blind = pallas::Base::random(&mut OsRng);
|
||||
|
||||
let money_merkle_tree = self.get_money_tree().await?;
|
||||
|
||||
let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
|
||||
let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_MINT_NS_V1)
|
||||
else {
|
||||
return Err(anyhow!("Money Mint circuit not found"))
|
||||
};
|
||||
let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_BURN_NS_V1)
|
||||
else {
|
||||
return Err(anyhow!("Money Burn circuit not found"))
|
||||
};
|
||||
let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
|
||||
let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?;
|
||||
let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
|
||||
let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin);
|
||||
eprintln!("Creating Money Mint circuit proving key");
|
||||
let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
|
||||
eprintln!("Creating Money Burn circuit proving key");
|
||||
let burn_pk = ProvingKey::build(burn_zkbin.k, &burn_circuit);
|
||||
|
||||
let xfer_builder = TransferCallBuilder {
|
||||
keypair: dao.keypair(),
|
||||
recipient: proposal.recipient,
|
||||
value: proposal.amount,
|
||||
token_id: proposal.token_id,
|
||||
rcpt_spend_hook,
|
||||
rcpt_user_data,
|
||||
rcpt_user_data_blind,
|
||||
change_spend_hook,
|
||||
change_user_data,
|
||||
change_user_data_blind,
|
||||
coins,
|
||||
tree: money_merkle_tree,
|
||||
mint_zkbin: mint_zkbin.clone(),
|
||||
mint_pk: mint_pk.clone(),
|
||||
burn_zkbin: burn_zkbin.clone(),
|
||||
burn_pk: burn_pk.clone(),
|
||||
clear_input: false,
|
||||
};
|
||||
|
||||
let xfer_debris = xfer_builder.build()?;
|
||||
|
||||
let mut data = vec![MoneyFunction::TransferV1 as u8];
|
||||
xfer_debris.params.encode(&mut data)?;
|
||||
let xfer_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
|
||||
|
||||
let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
|
||||
let Some(exec_zkbin) = zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_EXEC_NS)
|
||||
else {
|
||||
return Err(anyhow!("DAO Exec circuit not found"))
|
||||
};
|
||||
let exec_zkbin = ZkBinary::decode(&exec_zkbin.1)?;
|
||||
let exec_circuit = ZkCircuit::new(empty_witnesses(&exec_zkbin)?, &exec_zkbin);
|
||||
eprintln!("Creating DAO Exec circuit proving key");
|
||||
let exec_pk = ProvingKey::build(exec_zkbin.k, &exec_circuit);
|
||||
|
||||
// Count votes
|
||||
let mut total_yes_vote_value = 0;
|
||||
let mut total_all_vote_value = 0;
|
||||
let mut blind_total_vote = DaoBlindAggregateVote::default();
|
||||
let mut total_yes_vote_blind = pallas::Scalar::zero();
|
||||
let mut total_all_vote_blind = pallas::Scalar::zero();
|
||||
|
||||
for (_, vote) in votes.iter().enumerate() {
|
||||
total_yes_vote_blind += vote.yes_vote_blind;
|
||||
total_all_vote_blind += vote.all_vote_blind;
|
||||
|
||||
let yes_vote_value = vote.vote_option as u64 * vote.all_vote_value;
|
||||
eprintln!("yes_vote = {}", yes_vote_value);
|
||||
total_yes_vote_value += yes_vote_value;
|
||||
total_all_vote_value += vote.all_vote_value;
|
||||
|
||||
let yes_vote_commit = pedersen_commitment_u64(yes_vote_value, vote.yes_vote_blind);
|
||||
let all_vote_commit = pedersen_commitment_u64(vote.all_vote_value, vote.all_vote_blind);
|
||||
|
||||
let blind_vote = DaoBlindAggregateVote { yes_vote_commit, all_vote_commit };
|
||||
blind_total_vote.aggregate(blind_vote);
|
||||
}
|
||||
|
||||
eprintln!("yes = {}, all = {}", total_yes_vote_value, total_all_vote_value);
|
||||
|
||||
let prop_t = DaoProposalInfo {
|
||||
dest: proposal.recipient,
|
||||
amount: proposal.amount,
|
||||
token_id: proposal.token_id,
|
||||
blind: proposal.bulla_blind, // <-- FIXME: wtf
|
||||
};
|
||||
|
||||
// TODO: allvote/yesvote is 11 weirdly
|
||||
|
||||
let dao_t = DaoInfo {
|
||||
proposer_limit: dao.proposer_limit,
|
||||
quorum: dao.quorum,
|
||||
approval_ratio_quot: dao.approval_ratio_quot,
|
||||
approval_ratio_base: dao.approval_ratio_base,
|
||||
gov_token_id: dao.gov_token_id,
|
||||
public_key: PublicKey::from_secret(dao.secret_key),
|
||||
bulla_blind: dao.bulla_blind,
|
||||
};
|
||||
|
||||
// We need to extract stuff from the inputs and outputs that we'll also
|
||||
// use in the DAO::Exec call. This DAO API needs to be better.
|
||||
let mut input_value = 0;
|
||||
let mut input_value_blind = pallas::Scalar::ZERO;
|
||||
for (input, blind) in xfer_debris.spent_coins.iter().zip(xfer_debris.input_value_blinds) {
|
||||
input_value += input.note.value;
|
||||
input_value_blind += blind;
|
||||
}
|
||||
|
||||
// First output is change, second output is recipient.
|
||||
let dao_serial = xfer_debris.minted_coins[0].note.serial;
|
||||
let user_serial = xfer_debris.minted_coins[1].note.serial;
|
||||
|
||||
// TODO: FIXME: This is not checked anywhere!
|
||||
let exec_signature_secret = SecretKey::random(&mut OsRng);
|
||||
|
||||
let dao_exec_call = dao_client::DaoExecCall {
|
||||
proposal: prop_t,
|
||||
dao: dao_t,
|
||||
yes_vote_value: total_yes_vote_value,
|
||||
all_vote_value: total_all_vote_value,
|
||||
yes_vote_blind: total_yes_vote_blind,
|
||||
all_vote_blind: total_all_vote_blind,
|
||||
user_serial,
|
||||
dao_serial,
|
||||
input_value,
|
||||
input_value_blind,
|
||||
hook_dao_exec: DAO_CONTRACT_ID.inner(),
|
||||
signature_secret: exec_signature_secret,
|
||||
};
|
||||
|
||||
let (exec_params, exec_proofs) = dao_exec_call.make(&exec_zkbin, &exec_pk)?;
|
||||
|
||||
let mut data = vec![DaoFunction::Exec as u8];
|
||||
exec_params.encode(&mut data)?;
|
||||
let exec_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
|
||||
|
||||
let mut tx = Transaction {
|
||||
calls: vec![xfer_call, exec_call],
|
||||
proofs: vec![xfer_debris.proofs, exec_proofs],
|
||||
signatures: vec![],
|
||||
};
|
||||
|
||||
let xfer_sigs = tx.create_sigs(&mut OsRng, &xfer_debris.signature_secrets)?;
|
||||
let exec_sigs = tx.create_sigs(&mut OsRng, &[exec_signature_secret])?;
|
||||
tx.signatures = vec![xfer_sigs, exec_sigs];
|
||||
|
||||
Ok(tx)
|
||||
}
|
||||
}
|
||||
@@ -1,462 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use darkfi::{
|
||||
tx::Transaction,
|
||||
util::parse::encode_base10,
|
||||
zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses, Proof},
|
||||
zkas::ZkBinary,
|
||||
};
|
||||
use darkfi_money_contract::{
|
||||
client::{swap_v1::SwapCallBuilder, MoneyNote},
|
||||
model::{Coin, MoneyTransferParamsV1},
|
||||
MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
|
||||
};
|
||||
use darkfi_sdk::{
|
||||
crypto::{
|
||||
contract_id::MONEY_CONTRACT_ID, pedersen::pedersen_commitment_u64, poseidon_hash,
|
||||
PublicKey, SecretKey, TokenId,
|
||||
},
|
||||
pasta::pallas,
|
||||
tx::ContractCall,
|
||||
};
|
||||
use darkfi_serial::{deserialize, Encodable, SerialDecodable, SerialEncodable};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use super::Drk;
|
||||
|
||||
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
|
||||
/// Half of the swap data, includes the coin that is supposed to be sent,
|
||||
/// and the coin that is supposed to be received.
|
||||
pub struct PartialSwapData {
|
||||
params: MoneyTransferParamsV1,
|
||||
proofs: Vec<Proof>,
|
||||
value_pair: (u64, u64),
|
||||
token_pair: (TokenId, TokenId),
|
||||
value_blinds: Vec<pallas::Scalar>,
|
||||
token_blinds: Vec<pallas::Base>,
|
||||
}
|
||||
|
||||
impl fmt::Display for PartialSwapData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s =
|
||||
format!(
|
||||
"{:#?}\nValue pair: {}:{}\nToken pair: {}:{}\nValue blinds: {:?}\nToken blinds: {:?}\n",
|
||||
self.params, self.value_pair.0, self.value_pair.1, self.token_pair.0, self.token_pair.1,
|
||||
self.value_blinds, self.token_blinds,
|
||||
);
|
||||
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drk {
|
||||
/// Initialize the first half of an atomic swap
|
||||
pub async fn init_swap(
|
||||
&self,
|
||||
value_send: u64,
|
||||
token_send: TokenId,
|
||||
value_recv: u64,
|
||||
token_recv: TokenId,
|
||||
) -> Result<PartialSwapData> {
|
||||
// First we'll fetch all of our unspent coins from the wallet.
|
||||
let mut owncoins = self.get_coins(false).await?;
|
||||
// Then we see if we have one that we can send.
|
||||
owncoins.retain(|x| {
|
||||
x.0.note.value == value_send &&
|
||||
x.0.note.token_id == token_send &&
|
||||
x.0.note.spend_hook == pallas::Base::zero()
|
||||
});
|
||||
|
||||
if owncoins.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Did not find any unspent coins of value {} and token_id {}",
|
||||
value_send,
|
||||
token_send
|
||||
))
|
||||
}
|
||||
|
||||
// If there are any, we'll just spend the first one we see.
|
||||
let burn_coin = owncoins[0].0.clone();
|
||||
|
||||
// Fetch our default address
|
||||
let address = self.wallet_address(1).await?;
|
||||
|
||||
// We'll also need our Merkle tree
|
||||
let tree = self.get_money_tree().await?;
|
||||
|
||||
let contract_id = *MONEY_CONTRACT_ID;
|
||||
|
||||
// Now we need to do a lookup for the zkas proof bincodes, and create
|
||||
// the circuit objects and proving keys so we can build the transaction.
|
||||
// We also do this through the RPC.
|
||||
let zkas_bins = self.lookup_zkas(&contract_id).await?;
|
||||
|
||||
let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_MINT_NS_V1)
|
||||
else {
|
||||
return Err(anyhow!("Mint circuit not found"))
|
||||
};
|
||||
|
||||
let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_BURN_NS_V1)
|
||||
else {
|
||||
return Err(anyhow!("Burn circuit not found"))
|
||||
};
|
||||
|
||||
let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
|
||||
let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?;
|
||||
|
||||
let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
|
||||
let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin);
|
||||
|
||||
// Since we're creating the first half, we generate the blinds.
|
||||
let value_blinds = [pallas::Scalar::random(&mut OsRng), pallas::Scalar::random(&mut OsRng)];
|
||||
let token_blinds = [pallas::Base::random(&mut OsRng), pallas::Base::random(&mut OsRng)];
|
||||
|
||||
// Now we should have everything we need to build the swap half
|
||||
eprintln!("Creating Mint and Burn circuit proving keys");
|
||||
let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
|
||||
let burn_pk = ProvingKey::build(burn_zkbin.k, &burn_circuit);
|
||||
let builder = SwapCallBuilder {
|
||||
pubkey: address,
|
||||
value_send,
|
||||
token_id_send: token_send,
|
||||
value_recv,
|
||||
token_id_recv: token_recv,
|
||||
user_data_blind_send: pallas::Base::random(&mut OsRng), // <-- FIXME: Perhaps should be passed in
|
||||
spend_hook_recv: pallas::Base::zero(), // <-- FIXME: Should be passed in
|
||||
user_data_recv: pallas::Base::zero(), // <-- FIXME: Should be passed in
|
||||
value_blinds,
|
||||
token_blinds,
|
||||
coin: burn_coin,
|
||||
tree,
|
||||
mint_zkbin,
|
||||
mint_pk,
|
||||
burn_zkbin,
|
||||
burn_pk,
|
||||
};
|
||||
|
||||
eprintln!("Building first half of the swap transaction");
|
||||
let debris = builder.build()?;
|
||||
|
||||
// Now we have the half, so we can build `PartialSwapData` and return it.
|
||||
let ret = PartialSwapData {
|
||||
params: debris.params,
|
||||
proofs: debris.proofs,
|
||||
value_pair: (value_send, value_recv),
|
||||
token_pair: (token_send, token_recv),
|
||||
value_blinds: value_blinds.to_vec(),
|
||||
token_blinds: token_blinds.to_vec(),
|
||||
};
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Create a full transaction by inspecting and verifying given partial swap data,
|
||||
/// making the other half, and joining all this into a `Transaction` object.
|
||||
pub async fn join_swap(&self, partial: PartialSwapData) -> Result<Transaction> {
|
||||
// Our side of the tx in the pairs is the second half, so we try to find
|
||||
// an unspent coin like that in our wallet.
|
||||
let mut owncoins = self.get_coins(false).await?;
|
||||
owncoins.retain(|x| {
|
||||
x.0.note.value == partial.value_pair.1 && x.0.note.token_id == partial.token_pair.1
|
||||
});
|
||||
|
||||
if owncoins.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Did not find any unspent coins of value {} and token_id {}",
|
||||
partial.value_pair.1,
|
||||
partial.token_pair.1
|
||||
))
|
||||
}
|
||||
|
||||
// If there are any, we'll just spend the first one we see.
|
||||
let burn_coin = owncoins[0].0.clone();
|
||||
|
||||
// Fetch our default address // FIXME: Should actually be getting is_default
|
||||
let address = self.wallet_address(1).await?;
|
||||
|
||||
// We'll also need our Merkle tree
|
||||
let tree = self.get_money_tree().await?;
|
||||
|
||||
let contract_id = *MONEY_CONTRACT_ID;
|
||||
|
||||
// Now we need to do a lookup for the zkas proof bincodes, and create
|
||||
// the circuit objects and proving keys so we can build the transaction.
|
||||
// We also do this through the RPC.
|
||||
let zkas_bins = self.lookup_zkas(&contract_id).await?;
|
||||
|
||||
let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_MINT_NS_V1)
|
||||
else {
|
||||
return Err(anyhow!("Mint circuit not found"))
|
||||
};
|
||||
|
||||
let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_BURN_NS_V1)
|
||||
else {
|
||||
return Err(anyhow!("Burn circuit not found"))
|
||||
};
|
||||
|
||||
let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
|
||||
let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?;
|
||||
|
||||
let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
|
||||
let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin);
|
||||
|
||||
// TODO: Maybe some kind of verification at this point
|
||||
|
||||
// Now we should have everything we need to build the swap half
|
||||
eprintln!("Creating Mint and Burn circuit proving keys");
|
||||
let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
|
||||
let burn_pk = ProvingKey::build(burn_zkbin.k, &burn_circuit);
|
||||
let builder = SwapCallBuilder {
|
||||
pubkey: address,
|
||||
value_send: partial.value_pair.1,
|
||||
token_id_send: partial.token_pair.1,
|
||||
value_recv: partial.value_pair.0,
|
||||
token_id_recv: partial.token_pair.0,
|
||||
user_data_blind_send: pallas::Base::random(&mut OsRng), // <-- FIXME: Perhaps should be passed in
|
||||
spend_hook_recv: pallas::Base::zero(), // <-- FIXME: Should be passed in
|
||||
user_data_recv: pallas::Base::zero(), // <-- FIXME: Should be passed in
|
||||
value_blinds: [partial.value_blinds[1], partial.value_blinds[0]],
|
||||
token_blinds: [partial.token_blinds[1], partial.token_blinds[0]],
|
||||
coin: burn_coin,
|
||||
tree,
|
||||
mint_zkbin,
|
||||
mint_pk,
|
||||
burn_zkbin,
|
||||
burn_pk,
|
||||
};
|
||||
|
||||
eprintln!("Building second half of the swap transaction");
|
||||
let debris = builder.build()?;
|
||||
|
||||
let full_params = MoneyTransferParamsV1 {
|
||||
clear_inputs: vec![],
|
||||
inputs: vec![partial.params.inputs[0].clone(), debris.params.inputs[0].clone()],
|
||||
outputs: vec![partial.params.outputs[0].clone(), debris.params.outputs[0].clone()],
|
||||
};
|
||||
|
||||
let full_proofs = vec![
|
||||
partial.proofs[0].clone(),
|
||||
debris.proofs[0].clone(),
|
||||
partial.proofs[1].clone(),
|
||||
debris.proofs[1].clone(),
|
||||
];
|
||||
|
||||
let mut data = vec![MoneyFunction::OtcSwapV1 as u8];
|
||||
full_params.encode(&mut data)?;
|
||||
let mut tx = Transaction {
|
||||
calls: vec![ContractCall { contract_id, data }],
|
||||
proofs: vec![full_proofs],
|
||||
signatures: vec![],
|
||||
};
|
||||
eprintln!("Signing swap transaction");
|
||||
let sigs = tx.create_sigs(&mut OsRng, &[debris.signature_secret])?;
|
||||
tx.signatures = vec![sigs];
|
||||
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
/// Inspect and verify a given swap (half or full) transaction
|
||||
pub async fn inspect_swap(&self, bytes: Vec<u8>) -> Result<()> {
|
||||
let mut full: Option<Transaction> = None;
|
||||
let mut half: Option<PartialSwapData> = None;
|
||||
|
||||
if let Ok(v) = deserialize(&bytes) {
|
||||
full = Some(v)
|
||||
};
|
||||
|
||||
match deserialize(&bytes) {
|
||||
Ok(v) => half = Some(v),
|
||||
Err(_) => {
|
||||
if full.is_none() {
|
||||
return Err(anyhow!("Failed to deserialize to Transaction or PartialSwapData"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tx) = full {
|
||||
// We're inspecting a full transaction
|
||||
if tx.calls.len() != 1 {
|
||||
eprintln!(
|
||||
"Found {} contract calls in the transaction, there should be 1",
|
||||
tx.calls.len()
|
||||
);
|
||||
return Err(anyhow!("Inspection failed"))
|
||||
}
|
||||
|
||||
let params: MoneyTransferParamsV1 = deserialize(&tx.calls[0].data[1..])?;
|
||||
eprintln!("Parameters:\n{:#?}", params);
|
||||
|
||||
if params.inputs.len() != 2 {
|
||||
eprintln!("Found {} inputs, there should be 2", params.inputs.len());
|
||||
return Err(anyhow!("Inspection failed"))
|
||||
}
|
||||
|
||||
if params.outputs.len() != 2 {
|
||||
eprintln!("Found {} outputs, there should be 2", params.outputs.len());
|
||||
return Err(anyhow!("Inspection failed"))
|
||||
}
|
||||
|
||||
// Try to decrypt one of the outputs.
|
||||
let secret_keys = self.get_money_secrets().await?;
|
||||
let mut skey: Option<SecretKey> = None;
|
||||
let mut note: Option<MoneyNote> = None;
|
||||
let mut output_idx = 0;
|
||||
|
||||
for output in ¶ms.outputs {
|
||||
eprintln!("Trying to decrypt note in output {}", output_idx);
|
||||
|
||||
for secret in &secret_keys {
|
||||
if let Ok(d_note) = output.note.decrypt::<MoneyNote>(secret) {
|
||||
let s: SecretKey = deserialize(&d_note.memo)?;
|
||||
skey = Some(s);
|
||||
note = Some(d_note);
|
||||
eprintln!("Successfully decrypted and found an ephemeral secret");
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if note.is_some() {
|
||||
break
|
||||
}
|
||||
|
||||
output_idx += 1;
|
||||
}
|
||||
|
||||
let Some(note) = note else {
|
||||
eprintln!("Error: Could not decrypt notes of either output");
|
||||
return Err(anyhow!("Inspection failed"))
|
||||
};
|
||||
|
||||
eprintln!(
|
||||
"Output[{}] value: {} ({})",
|
||||
output_idx,
|
||||
note.value,
|
||||
encode_base10(note.value, 8)
|
||||
);
|
||||
eprintln!("Output[{}] token ID: {}", output_idx, note.token_id);
|
||||
|
||||
let skey = skey.unwrap();
|
||||
let (pub_x, pub_y) = PublicKey::from_secret(skey).xy();
|
||||
let coin = Coin::from(poseidon_hash([
|
||||
pub_x,
|
||||
pub_y,
|
||||
pallas::Base::from(note.value),
|
||||
note.token_id.inner(),
|
||||
note.serial,
|
||||
]));
|
||||
|
||||
if coin == params.outputs[output_idx].coin {
|
||||
eprintln!("Output[{}] coin matches decrypted note metadata", output_idx);
|
||||
} else {
|
||||
eprintln!("Error: Output[{}] coin does not match note metadata", output_idx);
|
||||
return Err(anyhow!("Inspection failed"))
|
||||
}
|
||||
|
||||
let valcom = pedersen_commitment_u64(note.value, note.value_blind);
|
||||
let tokcom = poseidon_hash([note.token_id.inner(), note.token_blind]);
|
||||
|
||||
if valcom != params.outputs[output_idx].value_commit {
|
||||
eprintln!(
|
||||
"Error: Output[{}] value commitment does not match note metadata",
|
||||
output_idx
|
||||
);
|
||||
return Err(anyhow!("Inspection failed"))
|
||||
}
|
||||
|
||||
if tokcom != params.outputs[output_idx].token_commit {
|
||||
eprintln!(
|
||||
"Error: Output[{}] token commitment does not match note metadata",
|
||||
output_idx
|
||||
);
|
||||
return Err(anyhow!("Inspection failed"))
|
||||
}
|
||||
|
||||
eprintln!("Value and token commitments match decrypted note metadata");
|
||||
|
||||
// Verify that the output commitments match the other input commitments
|
||||
match output_idx {
|
||||
0 => {
|
||||
if valcom != params.inputs[1].value_commit ||
|
||||
tokcom != params.inputs[1].token_commit
|
||||
{
|
||||
eprintln!("Error: Value/Token commits of output[0] do not match input[1]");
|
||||
return Err(anyhow!("Inspection failed"))
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
if valcom != params.inputs[0].value_commit ||
|
||||
tokcom != params.inputs[0].token_commit
|
||||
{
|
||||
eprintln!("Error: Value/Token commits of output[1] do not match input[0]");
|
||||
return Err(anyhow!("Inspection failed"))
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
eprintln!("Found matching pedersen commitments for outputs and inputs");
|
||||
|
||||
// TODO: Verify signature
|
||||
// TODO: Verify ZK proofs
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
// Inspect PartialSwapData
|
||||
let partial = half.unwrap();
|
||||
eprintln!("{}", partial);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sign a given transaction by retrieving the secret key from the encrypted
|
||||
/// note and prepending it to the transaction's signatures.
|
||||
pub async fn sign_swap(&self, tx: &mut Transaction) -> Result<()> {
|
||||
// We need our secret keys to try and decrypt the note
|
||||
let secret_keys = self.get_money_secrets().await?;
|
||||
let params: MoneyTransferParamsV1 = deserialize(&tx.calls[0].data[1..])?;
|
||||
|
||||
// Our output should be outputs[0] so we try to decrypt that.
|
||||
let encrypted_note = ¶ms.outputs[0].note;
|
||||
|
||||
eprintln!("Trying to decrypt note in outputs[0]");
|
||||
let mut skey = None;
|
||||
|
||||
for secret in &secret_keys {
|
||||
if let Ok(note) = encrypted_note.decrypt::<MoneyNote>(secret) {
|
||||
let s: SecretKey = deserialize(¬e.memo)?;
|
||||
eprintln!("Successfully decrypted and found an ephemeral secret");
|
||||
skey = Some(s);
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let Some(skey) = skey else {
|
||||
eprintln!("Error: Failed to decrypt note with any of our secret keys");
|
||||
return Err(anyhow!("Failed to decrypt note with any of our secret keys"))
|
||||
};
|
||||
|
||||
eprintln!("Signing swap transaction");
|
||||
let sigs = tx.create_sigs(&mut OsRng, &[skey])?;
|
||||
tx.signatures[0].insert(0, sigs[0]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use darkfi::{
|
||||
tx::Transaction,
|
||||
util::parse::decode_base10,
|
||||
zk::{proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses},
|
||||
zkas::ZkBinary,
|
||||
};
|
||||
use darkfi_money_contract::{
|
||||
client::{token_freeze_v1::TokenFreezeCallBuilder, token_mint_v1::TokenMintCallBuilder},
|
||||
MoneyFunction, MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
|
||||
};
|
||||
use darkfi_sdk::{
|
||||
crypto::{contract_id::MONEY_CONTRACT_ID, Keypair, PublicKey, TokenId},
|
||||
pasta::pallas,
|
||||
tx::ContractCall,
|
||||
};
|
||||
use darkfi_serial::Encodable;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use super::Drk;
|
||||
|
||||
impl Drk {
|
||||
/// Create a token mint transaction. Returns the transaction object on success.
|
||||
pub async fn mint_token(
|
||||
&self,
|
||||
amount: &str,
|
||||
recipient: PublicKey,
|
||||
token_id: TokenId,
|
||||
) -> Result<Transaction> {
|
||||
// TODO: Mint directly into DAO treasury
|
||||
let spend_hook = pallas::Base::zero();
|
||||
let user_data = pallas::Base::zero();
|
||||
|
||||
let amount = decode_base10(amount, 8, false)?;
|
||||
|
||||
let mut tokens = self.list_tokens().await?;
|
||||
tokens.retain(|x| x.0 == token_id);
|
||||
if tokens.is_empty() {
|
||||
return Err(anyhow!("Did not find mint authority for token ID {}", token_id))
|
||||
}
|
||||
assert!(tokens.len() == 1);
|
||||
|
||||
let mint_authority = Keypair::new(tokens[0].1);
|
||||
|
||||
if tokens[0].2 {
|
||||
return Err(anyhow!("This token mint is marked as frozen in the wallet"))
|
||||
}
|
||||
|
||||
// Now we need to do a lookup for the zkas proof bincodes, and create
|
||||
// the circuit objects and proving keys so we can build the transaction.
|
||||
// We also do this through the RPC.
|
||||
let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
|
||||
let zkas_ns = MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1;
|
||||
|
||||
let Some(token_mint_zkbin) = zkas_bins.iter().find(|x| x.0 == zkas_ns) else {
|
||||
return Err(anyhow!("Token mint circuit not found"))
|
||||
};
|
||||
|
||||
let token_mint_zkbin = ZkBinary::decode(&token_mint_zkbin.1)?;
|
||||
let token_mint_circuit =
|
||||
ZkCircuit::new(empty_witnesses(&token_mint_zkbin)?, &token_mint_zkbin);
|
||||
|
||||
eprintln!("Creating token mint circuit proving keys");
|
||||
let token_mint_pk = ProvingKey::build(token_mint_zkbin.k, &token_mint_circuit);
|
||||
let mint_builder = TokenMintCallBuilder {
|
||||
mint_authority,
|
||||
recipient,
|
||||
amount,
|
||||
spend_hook,
|
||||
user_data,
|
||||
token_mint_zkbin,
|
||||
token_mint_pk,
|
||||
};
|
||||
|
||||
eprintln!("Building transaction parameters");
|
||||
let debris = mint_builder.build()?;
|
||||
|
||||
// Encode and sign the transaction
|
||||
let mut data = vec![MoneyFunction::TokenMintV1 as u8];
|
||||
debris.params.encode(&mut data)?;
|
||||
let calls = vec![ContractCall { contract_id: *MONEY_CONTRACT_ID, data }];
|
||||
let proofs = vec![debris.proofs];
|
||||
let mut tx = Transaction { calls, proofs, signatures: vec![] };
|
||||
let sigs = tx.create_sigs(&mut OsRng, &[mint_authority.secret])?;
|
||||
tx.signatures = vec![sigs];
|
||||
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
/// Create a token freeze transaction. Returns the transaction object on success.
|
||||
pub async fn freeze_token(&self, token_id: TokenId) -> Result<Transaction> {
|
||||
let mut tokens = self.list_tokens().await?;
|
||||
tokens.retain(|x| x.0 == token_id);
|
||||
if tokens.is_empty() {
|
||||
return Err(anyhow!("Did not find mint authority for token ID {}", token_id))
|
||||
}
|
||||
assert!(tokens.len() == 1);
|
||||
|
||||
let mint_authority = Keypair::new(tokens[0].1);
|
||||
|
||||
if tokens[0].2 {
|
||||
return Err(anyhow!("This token is already marked as frozen in the wallet"))
|
||||
}
|
||||
|
||||
let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
|
||||
let zkas_ns = MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1;
|
||||
|
||||
let Some(token_freeze_zkbin) = zkas_bins.iter().find(|x| x.0 == zkas_ns) else {
|
||||
return Err(anyhow!("Token freeze circuit not found"))
|
||||
};
|
||||
|
||||
let token_freeze_zkbin = ZkBinary::decode(&token_freeze_zkbin.1)?;
|
||||
let token_freeze_circuit =
|
||||
ZkCircuit::new(empty_witnesses(&token_freeze_zkbin)?, &token_freeze_zkbin);
|
||||
|
||||
eprintln!("Creating token freeze circuit proving keys");
|
||||
let token_freeze_pk = ProvingKey::build(token_freeze_zkbin.k, &token_freeze_circuit);
|
||||
let freeze_builder =
|
||||
TokenFreezeCallBuilder { mint_authority, token_freeze_zkbin, token_freeze_pk };
|
||||
|
||||
eprintln!("Building transaction parameters");
|
||||
let debris = freeze_builder.build()?;
|
||||
|
||||
// Encode and sign the transaction
|
||||
let mut data = vec![MoneyFunction::TokenFreezeV1 as u8];
|
||||
debris.params.encode(&mut data)?;
|
||||
let calls = vec![ContractCall { contract_id: *MONEY_CONTRACT_ID, data }];
|
||||
let proofs = vec![debris.proofs];
|
||||
let mut tx = Transaction { calls, proofs, signatures: vec![] };
|
||||
let sigs = tx.create_sigs(&mut OsRng, &[mint_authority.secret])?;
|
||||
tx.signatures = vec![sigs];
|
||||
|
||||
Ok(tx)
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use darkfi::{
|
||||
tx::Transaction,
|
||||
util::parse::{decode_base10, encode_base10},
|
||||
zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses},
|
||||
zkas::ZkBinary,
|
||||
};
|
||||
use darkfi_dao_contract::model::DaoBulla;
|
||||
use darkfi_money_contract::{
|
||||
client::{transfer_v1::TransferCallBuilder, OwnCoin},
|
||||
MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
|
||||
};
|
||||
use darkfi_sdk::{
|
||||
crypto::{
|
||||
contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
|
||||
Keypair, PublicKey, TokenId,
|
||||
},
|
||||
pasta::pallas,
|
||||
tx::ContractCall,
|
||||
};
|
||||
use darkfi_serial::Encodable;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use super::Drk;
|
||||
|
||||
impl Drk {
|
||||
/// Create a payment transaction. Returns the transaction object on success.
|
||||
pub async fn transfer(
|
||||
&self,
|
||||
amount: &str,
|
||||
token_id: TokenId,
|
||||
recipient: PublicKey,
|
||||
dao: bool,
|
||||
dao_bulla: Option<String>,
|
||||
) -> Result<Transaction> {
|
||||
let dao_bulla: Option<DaoBulla> = if dao {
|
||||
let Some(dao_bulla) = dao_bulla else {
|
||||
return Err(anyhow!("Missing DAO bulla in parameters"))
|
||||
};
|
||||
|
||||
Some(DaoBulla::from_str(dao_bulla.as_str())?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let (spend_hook, user_data, user_data_blind) = if dao {
|
||||
(DAO_CONTRACT_ID.inner(), dao_bulla.unwrap().inner(), pallas::Base::random(&mut OsRng))
|
||||
} else {
|
||||
(pallas::Base::zero(), pallas::Base::zero(), pallas::Base::random(&mut OsRng))
|
||||
};
|
||||
|
||||
// First get all unspent OwnCoins to see what our balance is.
|
||||
eprintln!("Fetching OwnCoins");
|
||||
let owncoins = self.get_coins(false).await?;
|
||||
let mut owncoins: Vec<OwnCoin> = owncoins.iter().map(|x| x.0.clone()).collect();
|
||||
// We're only interested in the ones for the token_id we're sending
|
||||
// And the ones not owned by some protocol (meaning spend-hook should be 0)
|
||||
owncoins.retain(|x| x.note.token_id == token_id);
|
||||
owncoins.retain(|x| x.note.spend_hook == pallas::Base::zero());
|
||||
if owncoins.is_empty() {
|
||||
return Err(anyhow!("Did not find any coins with token ID: {}", token_id))
|
||||
}
|
||||
|
||||
// FIXME: Do not hardcode 8 decimals
|
||||
let amount = decode_base10(amount, 8, false)?;
|
||||
let mut balance = 0;
|
||||
for coin in owncoins.iter() {
|
||||
balance += coin.note.value;
|
||||
}
|
||||
|
||||
if balance < amount {
|
||||
return Err(anyhow!(
|
||||
"Not enough balance for token ID: {}, found: {}",
|
||||
token_id,
|
||||
encode_base10(balance, 8)
|
||||
))
|
||||
}
|
||||
|
||||
// We'll also need our Merkle tree
|
||||
let tree = self.get_money_tree().await?;
|
||||
|
||||
// TODO: Which keypair to actually use?
|
||||
let secrets = self.get_money_secrets().await?;
|
||||
let keypair = Keypair::new(secrets[0]);
|
||||
|
||||
let contract_id = *MONEY_CONTRACT_ID;
|
||||
|
||||
// Now we need to do a lookup for the zkas proof bincodes, and create
|
||||
// the circuit objects and proving keys so we can build the transaction.
|
||||
// We also do this through the RPC.
|
||||
let zkas_bins = self.lookup_zkas(&contract_id).await?;
|
||||
|
||||
let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_MINT_NS_V1)
|
||||
else {
|
||||
return Err(anyhow!("Mint circuit not found"))
|
||||
};
|
||||
|
||||
let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_BURN_NS_V1)
|
||||
else {
|
||||
return Err(anyhow!("Burn circuit not found"))
|
||||
};
|
||||
|
||||
let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
|
||||
let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?;
|
||||
|
||||
let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
|
||||
let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin);
|
||||
|
||||
eprintln!("Creating Mint and Burn circuit proving keys");
|
||||
let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
|
||||
let burn_pk = ProvingKey::build(burn_zkbin.k, &burn_circuit);
|
||||
let transfer_builder = TransferCallBuilder {
|
||||
keypair,
|
||||
recipient,
|
||||
value: amount,
|
||||
token_id,
|
||||
rcpt_spend_hook: spend_hook,
|
||||
rcpt_user_data: user_data,
|
||||
rcpt_user_data_blind: user_data_blind,
|
||||
change_spend_hook: pallas::Base::zero(),
|
||||
change_user_data: pallas::Base::zero(),
|
||||
change_user_data_blind: user_data_blind, // FIXME: I'm reusing this blind but dunno why
|
||||
coins: owncoins,
|
||||
tree,
|
||||
mint_zkbin,
|
||||
mint_pk,
|
||||
burn_zkbin,
|
||||
burn_pk,
|
||||
clear_input: false,
|
||||
};
|
||||
|
||||
eprintln!("Building transaction parameters");
|
||||
let debris = transfer_builder.build()?;
|
||||
|
||||
// Encode and sign the transaction
|
||||
let mut data = vec![MoneyFunction::TransferV1 as u8];
|
||||
debris.params.encode(&mut data)?;
|
||||
let calls = vec![ContractCall { contract_id, data }];
|
||||
let proofs = vec![debris.proofs];
|
||||
let mut tx = Transaction { calls, proofs, signatures: vec![] };
|
||||
let sigs = tx.create_sigs(&mut OsRng, &debris.signature_secrets)?;
|
||||
tx.signatures = vec![sigs];
|
||||
|
||||
// We need to mark the coins we've spent in our wallet
|
||||
for spent_coin in debris.spent_coins {
|
||||
self.mark_spent_coin(&spent_coin.coin).await?;
|
||||
}
|
||||
|
||||
Ok(tx)
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use anyhow::Result;
|
||||
use darkfi::rpc::jsonrpc::JsonRequest;
|
||||
use serde_json::json;
|
||||
|
||||
use super::Drk;
|
||||
|
||||
impl Drk {
|
||||
/// Initialize wallet with tables for drk
|
||||
pub async fn initialize_wallet(&self) -> Result<()> {
|
||||
let wallet_schema = include_str!("../wallet.sql");
|
||||
|
||||
// We perform a request to darkfid with the schema to initialize
|
||||
// the necessary tables in the wallet.
|
||||
let req = JsonRequest::new("wallet.exec_sql", json!([wallet_schema]));
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
if rep == true {
|
||||
eprintln!("Successfully initialized wallet schema for drk");
|
||||
} else {
|
||||
eprintln!("[initialize_wallet] Got unexpected reply from darkfid: {}", rep);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,788 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use darkfi::{rpc::jsonrpc::JsonRequest, tx::Transaction, wallet::walletdb::QueryType};
|
||||
use darkfi_money_contract::{
|
||||
client::{
|
||||
MoneyNote, OwnCoin, MONEY_ALIASES_COL_ALIAS, MONEY_ALIASES_COL_TOKEN_ID,
|
||||
MONEY_ALIASES_TABLE, MONEY_COINS_COL_COIN, MONEY_COINS_COL_IS_SPENT,
|
||||
MONEY_COINS_COL_LEAF_POSITION, MONEY_COINS_COL_MEMO, MONEY_COINS_COL_NULLIFIER,
|
||||
MONEY_COINS_COL_SECRET, MONEY_COINS_COL_SERIAL, MONEY_COINS_COL_SPEND_HOOK,
|
||||
MONEY_COINS_COL_TOKEN_BLIND, MONEY_COINS_COL_TOKEN_ID, MONEY_COINS_COL_USER_DATA,
|
||||
MONEY_COINS_COL_VALUE, MONEY_COINS_COL_VALUE_BLIND, MONEY_COINS_TABLE,
|
||||
MONEY_INFO_COL_LAST_SCANNED_SLOT, MONEY_INFO_TABLE, MONEY_KEYS_COL_IS_DEFAULT,
|
||||
MONEY_KEYS_COL_KEY_ID, MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_COL_SECRET, MONEY_KEYS_TABLE,
|
||||
MONEY_TOKENS_COL_IS_FROZEN, MONEY_TOKENS_COL_TOKEN_ID, MONEY_TOKENS_TABLE,
|
||||
MONEY_TREE_COL_TREE, MONEY_TREE_TABLE,
|
||||
},
|
||||
model::{
|
||||
Coin, MoneyTokenFreezeParamsV1, MoneyTokenMintParamsV1, MoneyTransferParamsV1, Output,
|
||||
},
|
||||
MoneyFunction,
|
||||
};
|
||||
use darkfi_sdk::{
|
||||
bridgetree,
|
||||
crypto::{
|
||||
pasta_prelude::Field, poseidon_hash, Keypair, MerkleNode, MerkleTree, Nullifier, PublicKey,
|
||||
SecretKey, TokenId, MONEY_CONTRACT_ID,
|
||||
},
|
||||
pasta::pallas,
|
||||
};
|
||||
use darkfi_serial::{deserialize, serialize};
|
||||
use rand::rngs::OsRng;
|
||||
use serde_json::json;
|
||||
|
||||
use super::Drk;
|
||||
use crate::cli_util::kaching;
|
||||
|
||||
impl Drk {
|
||||
/// Initialize wallet with tables for the Money contract
|
||||
pub async fn initialize_money(&self) -> Result<()> {
|
||||
let wallet_schema = include_str!("../../../src/contract/money/wallet.sql");
|
||||
|
||||
// We perform a request to darkfid with the schema to initialize
|
||||
// the necessary tables in the wallet.
|
||||
let req = JsonRequest::new("wallet.exec_sql", json!([wallet_schema]));
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
if rep == true {
|
||||
eprintln!("Successfully initialized wallet schema for the Money contract");
|
||||
} else {
|
||||
eprintln!("[initialize_money] Got unexpected reply from darkfid: {}", rep);
|
||||
}
|
||||
|
||||
// Check if we have to initialize the Merkle tree.
|
||||
// We check if we find a row in the tree table, and if not, we create a
|
||||
// new tree and push it into the table.
|
||||
let mut tree_needs_init = false;
|
||||
let query = format!("SELECT {} FROM {}", MONEY_TREE_COL_TREE, MONEY_TREE_TABLE);
|
||||
let params = json!([query, QueryType::Blob as u8, MONEY_TREE_COL_TREE]);
|
||||
let req = JsonRequest::new("wallet.query_row_single", params);
|
||||
|
||||
// For now, on success, we don't care what's returned, but in the future
|
||||
// we should actually check it.
|
||||
// TODO: The RPC needs a better variant for errors so detailed inspection
|
||||
// can be done with error codes and all that.
|
||||
if (self.rpc_client.request(req).await).is_err() {
|
||||
tree_needs_init = true;
|
||||
}
|
||||
|
||||
if tree_needs_init {
|
||||
eprintln!("Initializing Money Merkle tree");
|
||||
let mut tree = MerkleTree::new(100);
|
||||
tree.append(MerkleNode::from(pallas::Base::ZERO));
|
||||
let _ = tree.mark().unwrap();
|
||||
self.put_money_tree(&tree).await?;
|
||||
eprintln!("Successfully initialized Merkle tree for the Money contract");
|
||||
}
|
||||
|
||||
// We maintain the last scanned slot as part of the Money contract,
|
||||
// but at this moment it is also somewhat applicable to DAO scans.
|
||||
if (self.last_scanned_slot().await).is_err() {
|
||||
let query = format!(
|
||||
"INSERT INTO {} ({}) VALUES (?1);",
|
||||
MONEY_INFO_TABLE, MONEY_INFO_COL_LAST_SCANNED_SLOT
|
||||
);
|
||||
|
||||
let params = json!([query, QueryType::Integer as u8, 0]);
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let _ = self.rpc_client.request(req).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate a new keypair and place it into the wallet.
|
||||
pub async fn money_keygen(&self) -> Result<()> {
|
||||
eprintln!("Generating a new keypair");
|
||||
// TODO: We might want to have hierarchical deterministic key derivation.
|
||||
let keypair = Keypair::random(&mut OsRng);
|
||||
let is_default = 0;
|
||||
|
||||
let query = format!(
|
||||
"INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
|
||||
MONEY_KEYS_TABLE,
|
||||
MONEY_KEYS_COL_IS_DEFAULT,
|
||||
MONEY_KEYS_COL_PUBLIC,
|
||||
MONEY_KEYS_COL_SECRET,
|
||||
);
|
||||
|
||||
let params = json!([
|
||||
query,
|
||||
QueryType::Integer as u8,
|
||||
is_default,
|
||||
QueryType::Blob as u8,
|
||||
serialize(&keypair.public),
|
||||
QueryType::Blob as u8,
|
||||
serialize(&keypair.secret),
|
||||
]);
|
||||
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
if rep == true {
|
||||
eprintln!("Successfully added new keypair to wallet");
|
||||
} else {
|
||||
eprintln!("[money_keygen] Got unexpected reply from darkfid: {}", rep);
|
||||
}
|
||||
|
||||
eprintln!("New address:");
|
||||
println!("{}", keypair.public);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetch all secret keys from the wallet
|
||||
pub async fn get_money_secrets(&self) -> Result<Vec<SecretKey>> {
|
||||
let query = format!("SELECT {} FROM {};", MONEY_KEYS_COL_SECRET, MONEY_KEYS_TABLE);
|
||||
let params = json!([query, QueryType::Blob as u8, MONEY_KEYS_COL_SECRET]);
|
||||
let req = JsonRequest::new("wallet.query_row_multi", params);
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
// The returned thing should be an array of found rows.
|
||||
let Some(rows) = rep.as_array() else {
|
||||
return Err(anyhow!("[get_money_secrets] Unexpected response from darkfid: {}", rep))
|
||||
};
|
||||
|
||||
let mut secrets = Vec::with_capacity(rows.len());
|
||||
|
||||
// Let's scan through the rows and see if we got anything.
|
||||
for row in rows {
|
||||
let secret_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
|
||||
let secret = deserialize(&secret_bytes)?;
|
||||
secrets.push(secret);
|
||||
}
|
||||
|
||||
Ok(secrets)
|
||||
}
|
||||
|
||||
/// Import given secret keys into the wallet.
|
||||
/// The query uses INSERT, so if the key already exists, it will be skipped.
|
||||
/// Returns the respective PublicKey objects for the imported keys.
|
||||
pub async fn import_money_secrets(&self, secrets: Vec<SecretKey>) -> Result<Vec<PublicKey>> {
|
||||
let mut ret = Vec::with_capacity(secrets.len());
|
||||
|
||||
for secret in secrets {
|
||||
ret.push(PublicKey::from_secret(secret));
|
||||
let is_default = 0;
|
||||
let public = serialize(&PublicKey::from_secret(secret));
|
||||
let secret = serialize(&secret);
|
||||
|
||||
let query = format!(
|
||||
"INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
|
||||
MONEY_KEYS_TABLE,
|
||||
MONEY_KEYS_COL_IS_DEFAULT,
|
||||
MONEY_KEYS_COL_PUBLIC,
|
||||
MONEY_KEYS_COL_SECRET,
|
||||
);
|
||||
|
||||
let params = json!([
|
||||
query,
|
||||
QueryType::Integer as u8,
|
||||
is_default,
|
||||
QueryType::Blob as u8,
|
||||
public,
|
||||
QueryType::Blob as u8,
|
||||
secret,
|
||||
]);
|
||||
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let _ = self.rpc_client.request(req).await?;
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Fetch pubkeys from the wallet and return the requested index.
|
||||
pub async fn wallet_address(&self, idx: u64) -> Result<PublicKey> {
|
||||
let query = format!(
|
||||
"SELECT {} FROM {} WHERE {} = {};",
|
||||
MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_TABLE, MONEY_KEYS_COL_KEY_ID, idx
|
||||
);
|
||||
|
||||
let params = json!([query, QueryType::Blob as u8, MONEY_KEYS_COL_PUBLIC]);
|
||||
let req = JsonRequest::new("wallet.query_row_single", params);
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
let Some(arr) = rep.as_array() else {
|
||||
return Err(anyhow!("[wallet_address] Unexpected response from darkfid: {}", rep))
|
||||
};
|
||||
|
||||
if arr.len() != 1 {
|
||||
return Err(anyhow!("Did not find pubkey with index {}", idx))
|
||||
}
|
||||
|
||||
let key_bytes: Vec<u8> = serde_json::from_value(arr[0].clone())?;
|
||||
let public_key: PublicKey = deserialize(&key_bytes)?;
|
||||
|
||||
Ok(public_key)
|
||||
}
|
||||
|
||||
/// Fetch all coins and their metadata related to the Money contract from the wallet.
|
||||
/// Optionally also fetch spent ones.
|
||||
/// The boolean in the returned tuple notes if the coin was marked as spent.
|
||||
pub async fn get_coins(&self, fetch_spent: bool) -> Result<Vec<(OwnCoin, bool)>> {
|
||||
let query = if fetch_spent {
|
||||
format!("SELECT * FROM {}", MONEY_COINS_TABLE)
|
||||
} else {
|
||||
format!(
|
||||
"SELECT * FROM {} WHERE {} = {}",
|
||||
MONEY_COINS_TABLE, MONEY_COINS_COL_IS_SPENT, false,
|
||||
)
|
||||
};
|
||||
|
||||
let params = json!([
|
||||
query,
|
||||
QueryType::Blob as u8,
|
||||
MONEY_COINS_COL_COIN,
|
||||
QueryType::Integer as u8,
|
||||
MONEY_COINS_COL_IS_SPENT,
|
||||
QueryType::Blob as u8,
|
||||
MONEY_COINS_COL_SERIAL,
|
||||
QueryType::Blob as u8,
|
||||
MONEY_COINS_COL_VALUE,
|
||||
QueryType::Blob as u8,
|
||||
MONEY_COINS_COL_TOKEN_ID,
|
||||
QueryType::Blob as u8,
|
||||
MONEY_COINS_COL_SPEND_HOOK,
|
||||
QueryType::Blob as u8,
|
||||
MONEY_COINS_COL_USER_DATA,
|
||||
QueryType::Blob as u8,
|
||||
MONEY_COINS_COL_VALUE_BLIND,
|
||||
QueryType::Blob as u8,
|
||||
MONEY_COINS_COL_TOKEN_BLIND,
|
||||
QueryType::Blob as u8,
|
||||
MONEY_COINS_COL_SECRET,
|
||||
QueryType::Blob as u8,
|
||||
MONEY_COINS_COL_NULLIFIER,
|
||||
QueryType::Blob as u8,
|
||||
MONEY_COINS_COL_LEAF_POSITION,
|
||||
QueryType::Blob as u8,
|
||||
MONEY_COINS_COL_MEMO,
|
||||
]);
|
||||
|
||||
let req = JsonRequest::new("wallet.query_row_multi", params);
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
// The returned thing should be an array of found rows.
|
||||
let Some(rows) = rep.as_array() else {
|
||||
return Err(anyhow!("[get_coins] Unexpected response from darkfid: {}", rep))
|
||||
};
|
||||
|
||||
let mut owncoins = Vec::with_capacity(rows.len());
|
||||
|
||||
for row in rows {
|
||||
let Some(row) = row.as_array() else {
|
||||
return Err(anyhow!("[get_coins] Unexpected response from darkfid: {}", rep))
|
||||
};
|
||||
|
||||
let coin_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
|
||||
let coin: Coin = deserialize(&coin_bytes)?;
|
||||
|
||||
let is_spent: u64 = serde_json::from_value(row[1].clone())?;
|
||||
let is_spent = is_spent > 0;
|
||||
|
||||
let serial_bytes: Vec<u8> = serde_json::from_value(row[2].clone())?;
|
||||
let serial: pallas::Base = deserialize(&serial_bytes)?;
|
||||
|
||||
let value_bytes: Vec<u8> = serde_json::from_value(row[3].clone())?;
|
||||
let value: u64 = deserialize(&value_bytes)?;
|
||||
|
||||
let token_id_bytes: Vec<u8> = serde_json::from_value(row[4].clone())?;
|
||||
let token_id: TokenId = deserialize(&token_id_bytes)?;
|
||||
|
||||
let spend_hook_bytes: Vec<u8> = serde_json::from_value(row[5].clone())?;
|
||||
let spend_hook: pallas::Base = deserialize(&spend_hook_bytes)?;
|
||||
|
||||
let user_data_bytes: Vec<u8> = serde_json::from_value(row[6].clone())?;
|
||||
let user_data: pallas::Base = deserialize(&user_data_bytes)?;
|
||||
|
||||
let value_blind_bytes: Vec<u8> = serde_json::from_value(row[7].clone())?;
|
||||
let value_blind: pallas::Scalar = deserialize(&value_blind_bytes)?;
|
||||
|
||||
let token_blind_bytes: Vec<u8> = serde_json::from_value(row[8].clone())?;
|
||||
let token_blind: pallas::Base = deserialize(&token_blind_bytes)?;
|
||||
|
||||
let secret_bytes: Vec<u8> = serde_json::from_value(row[9].clone())?;
|
||||
let secret: SecretKey = deserialize(&secret_bytes)?;
|
||||
|
||||
let nullifier_bytes: Vec<u8> = serde_json::from_value(row[10].clone())?;
|
||||
let nullifier: Nullifier = deserialize(&nullifier_bytes)?;
|
||||
|
||||
let leaf_position_bytes: Vec<u8> = serde_json::from_value(row[11].clone())?;
|
||||
let leaf_position: bridgetree::Position = deserialize(&leaf_position_bytes)?;
|
||||
|
||||
let memo: Vec<u8> = serde_json::from_value(row[12].clone())?;
|
||||
|
||||
let note = MoneyNote {
|
||||
serial,
|
||||
value,
|
||||
token_id,
|
||||
spend_hook,
|
||||
user_data,
|
||||
value_blind,
|
||||
token_blind,
|
||||
memo,
|
||||
};
|
||||
let owncoin = OwnCoin { coin, note, secret, nullifier, leaf_position };
|
||||
|
||||
owncoins.push((owncoin, is_spent))
|
||||
}
|
||||
|
||||
Ok(owncoins)
|
||||
}
|
||||
|
||||
/// Mark a coin in the wallet as spent
|
||||
pub async fn mark_spent_coin(&self, coin: &Coin) -> Result<()> {
|
||||
let query = format!(
|
||||
"UPDATE {} SET {} = ?1 WHERE {} = ?2;",
|
||||
MONEY_COINS_TABLE, MONEY_COINS_COL_IS_SPENT, MONEY_COINS_COL_COIN
|
||||
);
|
||||
|
||||
let params = json!([
|
||||
query,
|
||||
QueryType::Integer as u8,
|
||||
1,
|
||||
QueryType::Blob as u8,
|
||||
serialize(&coin.inner())
|
||||
]);
|
||||
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let _ = self.rpc_client.request(req).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Marks all coins in the wallet as spent, if their nullifier is in the given set
|
||||
pub async fn mark_spent_coins(&self, nullifiers: &[Nullifier]) -> Result<()> {
|
||||
if nullifiers.is_empty() {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
for (coin, _) in self.get_coins(false).await? {
|
||||
if nullifiers.contains(&coin.nullifier) {
|
||||
self.mark_spent_coin(&coin.coin).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark a given coin in the wallet as unspent
|
||||
pub async fn unspend_coin(&self, coin: &Coin) -> Result<()> {
|
||||
let query = format!(
|
||||
"UPDATE {} SET {} = ?1 WHERE {} = ?2;",
|
||||
MONEY_COINS_TABLE, MONEY_COINS_COL_IS_SPENT, MONEY_COINS_COL_COIN,
|
||||
);
|
||||
|
||||
let params = json!([
|
||||
query,
|
||||
QueryType::Integer as u8,
|
||||
0,
|
||||
QueryType::Blob as u8,
|
||||
serialize(&coin.inner())
|
||||
]);
|
||||
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let _ = self.rpc_client.request(req).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Replace the Money Merkle tree in the wallet.
|
||||
pub async fn put_money_tree(&self, tree: &MerkleTree) -> Result<()> {
|
||||
let query = format!(
|
||||
"DELETE FROM {}; INSERT INTO {} ({}) VALUES (?1);",
|
||||
MONEY_TREE_TABLE, MONEY_TREE_TABLE, MONEY_TREE_COL_TREE,
|
||||
);
|
||||
|
||||
let params = json!([query, QueryType::Blob as u8, serialize(tree)]);
|
||||
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let _ = self.rpc_client.request(req).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetch the Money Merkle tree from the wallet
|
||||
pub async fn get_money_tree(&self) -> Result<MerkleTree> {
|
||||
let query = format!("SELECT * FROM {}", MONEY_TREE_TABLE);
|
||||
let params = json!([query, QueryType::Blob as u8, MONEY_TREE_COL_TREE]);
|
||||
let req = JsonRequest::new("wallet.query_row_single", params);
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
let tree_bytes: Vec<u8> = serde_json::from_value(rep[0].clone())?;
|
||||
let tree = deserialize(&tree_bytes)?;
|
||||
Ok(tree)
|
||||
}
|
||||
|
||||
/// Reset the Money Merkle tree in the wallet
|
||||
pub async fn reset_money_tree(&self) -> Result<()> {
|
||||
eprintln!("Resetting Money Merkle tree");
|
||||
let mut tree = MerkleTree::new(100);
|
||||
tree.append(MerkleNode::from(pallas::Base::ZERO));
|
||||
let _ = tree.mark().unwrap();
|
||||
self.put_money_tree(&tree).await?;
|
||||
eprintln!("Successfully reset Money Merkle tree");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reset the Money coins in the wallet
|
||||
pub async fn reset_money_coins(&self) -> Result<()> {
|
||||
eprintln!("Resetting coins");
|
||||
let query = format!("DELETE FROM {};", MONEY_COINS_TABLE);
|
||||
let params = json!([query]);
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let _ = self.rpc_client.request(req).await?;
|
||||
eprintln!("Successfully reset coins");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetch known unspent balances from the wallet and return them as a hashmap.
|
||||
pub async fn money_balance(&self) -> Result<HashMap<String, u64>> {
|
||||
let mut coins = self.get_coins(false).await?;
|
||||
coins.retain(|x| x.0.note.spend_hook == pallas::Base::zero());
|
||||
|
||||
// Fill this map with balances
|
||||
let mut balmap: HashMap<String, u64> = HashMap::new();
|
||||
|
||||
for coin in coins {
|
||||
let mut value = coin.0.note.value;
|
||||
|
||||
if let Some(prev) = balmap.get(&coin.0.note.token_id.to_string()) {
|
||||
value += prev;
|
||||
}
|
||||
|
||||
balmap.insert(coin.0.note.token_id.to_string(), value);
|
||||
}
|
||||
|
||||
Ok(balmap)
|
||||
}
|
||||
|
||||
/// Append data related to Money contract transactions into the wallet database.
|
||||
pub async fn apply_tx_money_data(&self, tx: &Transaction, _confirm: bool) -> Result<()> {
|
||||
let cid = *MONEY_CONTRACT_ID;
|
||||
|
||||
let mut nullifiers: Vec<Nullifier> = vec![];
|
||||
let mut outputs: Vec<Output> = vec![];
|
||||
let mut freezes: Vec<TokenId> = vec![];
|
||||
|
||||
for (i, call) in tx.calls.iter().enumerate() {
|
||||
if call.contract_id == cid && call.data[0] == MoneyFunction::TransferV1 as u8 {
|
||||
eprintln!("Found Money::TransferV1 in call {}", i);
|
||||
let params: MoneyTransferParamsV1 = deserialize(&call.data[1..])?;
|
||||
|
||||
for input in params.inputs {
|
||||
nullifiers.push(input.nullifier);
|
||||
}
|
||||
|
||||
for output in params.outputs {
|
||||
outputs.push(output);
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if call.contract_id == cid && call.data[0] == MoneyFunction::OtcSwapV1 as u8 {
|
||||
eprintln!("Found Money::OtcSwapV1 in call {}", i);
|
||||
let params: MoneyTransferParamsV1 = deserialize(&call.data[1..])?;
|
||||
|
||||
for input in params.inputs {
|
||||
nullifiers.push(input.nullifier);
|
||||
}
|
||||
|
||||
for output in params.outputs {
|
||||
outputs.push(output);
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if call.contract_id == cid && call.data[0] == MoneyFunction::TokenMintV1 as u8 {
|
||||
eprintln!("Found Money::MintV1 in call {}", i);
|
||||
let params: MoneyTokenMintParamsV1 = deserialize(&call.data[1..])?;
|
||||
outputs.push(params.output);
|
||||
continue
|
||||
}
|
||||
|
||||
if call.contract_id == cid && call.data[0] == MoneyFunction::TokenFreezeV1 as u8 {
|
||||
eprintln!("Found Money::FreezeV1 in call {}", i);
|
||||
let params: MoneyTokenFreezeParamsV1 = deserialize(&call.data[1..])?;
|
||||
let token_id = TokenId::derive_public(params.signature_public);
|
||||
freezes.push(token_id);
|
||||
}
|
||||
}
|
||||
|
||||
let secrets = self.get_money_secrets().await?;
|
||||
let dao_secrets = self.get_dao_secrets().await?;
|
||||
let mut tree = self.get_money_tree().await?;
|
||||
|
||||
let mut owncoins = vec![];
|
||||
|
||||
for output in outputs {
|
||||
let coin = output.coin;
|
||||
|
||||
// Append the new coin to the Merkle tree. Every coin has to be added.
|
||||
tree.append(MerkleNode::from(coin.inner()));
|
||||
|
||||
// Attempt to decrypt the note
|
||||
for secret in secrets.iter().chain(dao_secrets.iter()) {
|
||||
if let Ok(note) = output.note.decrypt::<MoneyNote>(secret) {
|
||||
eprintln!("Successfully decrypted a Money Note");
|
||||
eprintln!("Witnessing coin in Merkle tree");
|
||||
let leaf_position = tree.mark().unwrap();
|
||||
|
||||
let owncoin = OwnCoin {
|
||||
coin,
|
||||
note: note.clone(),
|
||||
secret: *secret,
|
||||
nullifier: Nullifier::from(poseidon_hash([secret.inner(), note.serial])),
|
||||
leaf_position,
|
||||
};
|
||||
|
||||
owncoins.push(owncoin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.put_money_tree(&tree).await?;
|
||||
if !nullifiers.is_empty() {
|
||||
self.mark_spent_coins(&nullifiers).await?;
|
||||
}
|
||||
|
||||
// This is the SQL query we'll be executing to insert new coins
|
||||
// into the wallet
|
||||
let query = format!(
|
||||
"INSERT INTO {} ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13);",
|
||||
MONEY_COINS_TABLE,
|
||||
MONEY_COINS_COL_COIN,
|
||||
MONEY_COINS_COL_IS_SPENT,
|
||||
MONEY_COINS_COL_SERIAL,
|
||||
MONEY_COINS_COL_VALUE,
|
||||
MONEY_COINS_COL_TOKEN_ID,
|
||||
MONEY_COINS_COL_SPEND_HOOK,
|
||||
MONEY_COINS_COL_USER_DATA,
|
||||
MONEY_COINS_COL_VALUE_BLIND,
|
||||
MONEY_COINS_COL_TOKEN_BLIND,
|
||||
MONEY_COINS_COL_SECRET,
|
||||
MONEY_COINS_COL_NULLIFIER,
|
||||
MONEY_COINS_COL_LEAF_POSITION,
|
||||
MONEY_COINS_COL_MEMO,
|
||||
);
|
||||
|
||||
eprintln!("Found {} OwnCoin(s) in transaction", owncoins.len());
|
||||
for owncoin in &owncoins {
|
||||
eprintln!("OwnCoin: {:?}", owncoin.coin);
|
||||
let params = json!([
|
||||
query,
|
||||
QueryType::Blob as u8,
|
||||
serialize(&owncoin.coin),
|
||||
QueryType::Integer as u8,
|
||||
0, // <-- is_spent
|
||||
QueryType::Blob as u8,
|
||||
serialize(&owncoin.note.serial),
|
||||
QueryType::Blob as u8,
|
||||
serialize(&owncoin.note.value),
|
||||
QueryType::Blob as u8,
|
||||
serialize(&owncoin.note.token_id),
|
||||
QueryType::Blob as u8,
|
||||
serialize(&owncoin.note.spend_hook),
|
||||
QueryType::Blob as u8,
|
||||
serialize(&owncoin.note.user_data),
|
||||
QueryType::Blob as u8,
|
||||
serialize(&owncoin.note.value_blind),
|
||||
QueryType::Blob as u8,
|
||||
serialize(&owncoin.note.token_blind),
|
||||
QueryType::Blob as u8,
|
||||
serialize(&owncoin.secret),
|
||||
QueryType::Blob as u8,
|
||||
serialize(&owncoin.nullifier),
|
||||
QueryType::Blob as u8,
|
||||
serialize(&owncoin.leaf_position),
|
||||
QueryType::Blob as u8,
|
||||
serialize(&owncoin.note.memo),
|
||||
]);
|
||||
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let _ = self.rpc_client.request(req).await?;
|
||||
}
|
||||
|
||||
for token_id in freezes {
|
||||
let query = format!(
|
||||
"UPDATE {} SET {} = 1 WHERE {} = ?1;",
|
||||
MONEY_TOKENS_TABLE, MONEY_TOKENS_COL_IS_FROZEN, MONEY_TOKENS_COL_TOKEN_ID,
|
||||
);
|
||||
|
||||
let params = json!([query, QueryType::Blob as u8, serialize(&token_id)]);
|
||||
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let _ = self.rpc_client.request(req).await?;
|
||||
}
|
||||
|
||||
if !owncoins.is_empty() {
|
||||
kaching().await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the last scanned slot from the wallet
|
||||
pub async fn last_scanned_slot(&self) -> Result<u64> {
|
||||
let query =
|
||||
format!("SELECT {} FROM {};", MONEY_INFO_COL_LAST_SCANNED_SLOT, MONEY_INFO_TABLE);
|
||||
|
||||
let params = json!([query, QueryType::Integer as u8, MONEY_INFO_COL_LAST_SCANNED_SLOT]);
|
||||
let req = JsonRequest::new("wallet.query_row_single", params);
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
Ok(serde_json::from_value(rep[0].clone())?)
|
||||
}
|
||||
|
||||
/// Create an alias record for provided Token ID
|
||||
pub async fn add_alias(&self, alias: String, token_id: TokenId) -> Result<()> {
|
||||
eprintln!("Generating alias {} for Token: {}", alias, token_id);
|
||||
let query = format!(
|
||||
"INSERT OR REPLACE INTO {} ({}, {}) VALUES (?1, ?2);",
|
||||
MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS, MONEY_ALIASES_COL_TOKEN_ID,
|
||||
);
|
||||
|
||||
let params = json!([
|
||||
query,
|
||||
QueryType::Blob as u8,
|
||||
serialize(&alias),
|
||||
QueryType::Blob as u8,
|
||||
serialize(&token_id),
|
||||
]);
|
||||
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
if rep == true {
|
||||
eprintln!("Successfully added new alias to wallet");
|
||||
} else {
|
||||
eprintln!("[add_alias] Got unexpected reply from darkfid: {}", rep);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetch all aliases from the wallet.
|
||||
/// Optionally filter using alias name and/or token id.
|
||||
pub async fn get_aliases(
|
||||
&self,
|
||||
alias_filter: Option<String>,
|
||||
token_id_filter: Option<TokenId>,
|
||||
) -> Result<HashMap<String, TokenId>> {
|
||||
let query = format!("SELECT * FROM {}", MONEY_ALIASES_TABLE);
|
||||
let params = json!([
|
||||
query,
|
||||
QueryType::Blob as u8,
|
||||
MONEY_ALIASES_COL_ALIAS,
|
||||
QueryType::Blob as u8,
|
||||
MONEY_ALIASES_COL_TOKEN_ID,
|
||||
]);
|
||||
|
||||
let req = JsonRequest::new("wallet.query_row_multi", params);
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
// The returned thing should be an array of found rows.
|
||||
let Some(rows) = rep.as_array() else {
|
||||
return Err(anyhow!("[get_aliases] Unexpected response from darkfid: {}", rep))
|
||||
};
|
||||
|
||||
// Fill this map with aliases
|
||||
let mut map: HashMap<String, TokenId> = HashMap::new();
|
||||
for row in rows {
|
||||
let Some(row) = row.as_array() else {
|
||||
return Err(anyhow!("[get_aliases] Unexpected response from darkfid: {}", rep))
|
||||
};
|
||||
|
||||
let alias_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
|
||||
let alias: String = deserialize(&alias_bytes)?;
|
||||
if alias_filter.is_some() && alias_filter.as_ref().unwrap() != &alias {
|
||||
continue
|
||||
}
|
||||
|
||||
let token_id_bytes: Vec<u8> = serde_json::from_value(row[1].clone())?;
|
||||
let token_id: TokenId = deserialize(&token_id_bytes)?;
|
||||
if token_id_filter.is_some() && token_id_filter.as_ref().unwrap() != &token_id {
|
||||
continue
|
||||
}
|
||||
|
||||
map.insert(alias, token_id);
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
/// Fetch all aliases from the wallet, mapped by token id.
|
||||
pub async fn get_aliases_mapped_by_token(&self) -> Result<HashMap<String, String>> {
|
||||
let aliases = self.get_aliases(None, None).await?;
|
||||
let mut map: HashMap<String, String> = HashMap::new();
|
||||
for (alias, token_id) in aliases {
|
||||
let aliases_string = if let Some(prev) = map.get(&token_id.to_string()) {
|
||||
format!("{}, {}", prev, alias)
|
||||
} else {
|
||||
alias
|
||||
};
|
||||
|
||||
map.insert(token_id.to_string(), aliases_string);
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
/// Retrieve token by provided string.
|
||||
/// Input string represents either an alias or a token id.
|
||||
pub async fn get_token(&self, input: String) -> Result<TokenId> {
|
||||
// Check if input is an alias(max 5 characters)
|
||||
if input.chars().count() <= 5 {
|
||||
let aliases = self.get_aliases(Some(input.clone()), None).await?;
|
||||
if let Some(token_id) = aliases.get(&input) {
|
||||
return Ok(*token_id)
|
||||
}
|
||||
}
|
||||
// Else parse input
|
||||
Ok(TokenId::from_str(input.as_str())?)
|
||||
}
|
||||
|
||||
/// Create an alias record for provided Token ID
|
||||
pub async fn remove_alias(&self, alias: String) -> Result<()> {
|
||||
eprintln!("Removing alias: {}", alias);
|
||||
let query =
|
||||
format!("DELETE FROM {} WHERE {} = ?1;", MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS,);
|
||||
|
||||
let params = json!([query, QueryType::Blob as u8, serialize(&alias),]);
|
||||
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
if rep == true {
|
||||
eprintln!("Successfully removed alias from wallet");
|
||||
} else {
|
||||
eprintln!("[remove_alias] Got unexpected reply from darkfid: {}", rep);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use darkfi::{rpc::jsonrpc::JsonRequest, wallet::walletdb::QueryType};
|
||||
use darkfi_money_contract::client::{
|
||||
MONEY_TOKENS_COL_IS_FROZEN, MONEY_TOKENS_COL_MINT_AUTHORITY, MONEY_TOKENS_COL_TOKEN_ID,
|
||||
MONEY_TOKENS_TABLE,
|
||||
};
|
||||
use darkfi_sdk::crypto::{SecretKey, TokenId};
|
||||
use darkfi_serial::{deserialize, serialize};
|
||||
use serde_json::json;
|
||||
|
||||
use super::Drk;
|
||||
|
||||
impl Drk {
|
||||
/// Import a token mint authority into the wallet
|
||||
pub async fn import_mint_authority(&self, mint_authority: SecretKey) -> Result<()> {
|
||||
let token_id = TokenId::derive(mint_authority);
|
||||
let is_frozen = 0;
|
||||
|
||||
let query = format!(
|
||||
"INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
|
||||
MONEY_TOKENS_TABLE,
|
||||
MONEY_TOKENS_COL_MINT_AUTHORITY,
|
||||
MONEY_TOKENS_COL_TOKEN_ID,
|
||||
MONEY_TOKENS_COL_IS_FROZEN,
|
||||
);
|
||||
|
||||
let params = json!([
|
||||
query,
|
||||
QueryType::Blob as u8,
|
||||
serialize(&mint_authority),
|
||||
QueryType::Blob as u8,
|
||||
serialize(&token_id),
|
||||
QueryType::Integer as u8,
|
||||
is_frozen,
|
||||
]);
|
||||
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let _ = self.rpc_client.request(req).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_tokens(&self) -> Result<Vec<(TokenId, SecretKey, bool)>> {
|
||||
let mut ret = vec![];
|
||||
|
||||
let query = format!("SELECT * FROM {};", MONEY_TOKENS_TABLE);
|
||||
|
||||
let params = json!([
|
||||
query,
|
||||
QueryType::Blob as u8,
|
||||
MONEY_TOKENS_COL_MINT_AUTHORITY,
|
||||
QueryType::Blob as u8,
|
||||
MONEY_TOKENS_COL_TOKEN_ID,
|
||||
QueryType::Integer as u8,
|
||||
MONEY_TOKENS_COL_IS_FROZEN,
|
||||
]);
|
||||
|
||||
let req = JsonRequest::new("wallet.query_row_multi", params);
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
let Some(rows) = rep.as_array() else {
|
||||
return Err(anyhow!("[list_tokens] Unexpected response from darkfid: {}", rep))
|
||||
};
|
||||
|
||||
for row in rows {
|
||||
let auth_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
|
||||
let mint_authority = deserialize(&auth_bytes)?;
|
||||
|
||||
let token_bytes: Vec<u8> = serde_json::from_value(row[1].clone())?;
|
||||
let token_id = deserialize(&token_bytes)?;
|
||||
|
||||
let frozen: i32 = serde_json::from_value(row[2].clone())?;
|
||||
|
||||
ret.push((token_id, mint_authority, frozen != 0));
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use darkfi::{rpc::jsonrpc::JsonRequest, tx::Transaction, wallet::walletdb::QueryType};
|
||||
use darkfi_serial::{deserialize, serialize};
|
||||
use serde_json::json;
|
||||
|
||||
use super::Drk;
|
||||
|
||||
// Wallet SQL table constant names. These have to represent the `wallet.sql`
|
||||
// SQL schema.
|
||||
const WALLET_TXS_HISTORY_TABLE: &str = "transactions_history";
|
||||
const WALLET_TXS_HISTORY_COL_TX_HASH: &str = "transaction_hash";
|
||||
const WALLET_TXS_HISTORY_COL_STATUS: &str = "status";
|
||||
const WALLET_TXS_HISTORY_COL_TX: &str = "tx";
|
||||
|
||||
impl Drk {
|
||||
/// Fetch all transactions history records, excluding bytes column.
|
||||
pub async fn get_txs_history(&self) -> Result<Vec<(String, String)>> {
|
||||
let mut ret = vec![];
|
||||
|
||||
let query = format!(
|
||||
"SELECT {}, {} FROM {};",
|
||||
WALLET_TXS_HISTORY_COL_TX_HASH, WALLET_TXS_HISTORY_COL_STATUS, WALLET_TXS_HISTORY_TABLE
|
||||
);
|
||||
|
||||
let params = json!([
|
||||
query,
|
||||
QueryType::Text as u8,
|
||||
WALLET_TXS_HISTORY_COL_TX_HASH,
|
||||
QueryType::Text as u8,
|
||||
WALLET_TXS_HISTORY_COL_STATUS,
|
||||
]);
|
||||
|
||||
let req = JsonRequest::new("wallet.query_row_multi", params);
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
let Some(rows) = rep.as_array() else {
|
||||
return Err(anyhow!("[txs_history] Unexpected response from darkfid: {}", rep))
|
||||
};
|
||||
|
||||
for row in rows {
|
||||
let tx_hash: String = serde_json::from_value(row[0].clone())?;
|
||||
let status: String = serde_json::from_value(row[1].clone())?;
|
||||
ret.push((tx_hash, status));
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Get a transaction history record.
|
||||
pub async fn get_tx_history_record(
|
||||
&self,
|
||||
tx_hash: &str,
|
||||
) -> Result<(String, String, Transaction)> {
|
||||
let query = format!(
|
||||
"SELECT * FROM {} WHERE {} = {};",
|
||||
WALLET_TXS_HISTORY_TABLE, WALLET_TXS_HISTORY_COL_TX_HASH, tx_hash
|
||||
);
|
||||
|
||||
let params = json!([
|
||||
query,
|
||||
QueryType::Text as u8,
|
||||
WALLET_TXS_HISTORY_COL_TX_HASH,
|
||||
QueryType::Text as u8,
|
||||
WALLET_TXS_HISTORY_COL_STATUS,
|
||||
QueryType::Text as u8,
|
||||
WALLET_TXS_HISTORY_COL_TX,
|
||||
]);
|
||||
|
||||
let req = JsonRequest::new("wallet.query_row_single", params);
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
let Some(arr) = rep.as_array() else {
|
||||
return Err(anyhow!("[get_tx_history_record] Unexpected response from darkfid: {}", rep))
|
||||
};
|
||||
|
||||
if arr.len() != 3 {
|
||||
return Err(anyhow!("Did not find transaction record with hash {}", tx_hash))
|
||||
}
|
||||
|
||||
let tx_hash: String = serde_json::from_value(arr[0].clone())?;
|
||||
|
||||
let status: String = serde_json::from_value(arr[1].clone())?;
|
||||
|
||||
let tx_encoded: String = serde_json::from_value(arr[2].clone())?;
|
||||
let tx_bytes: Vec<u8> = bs58::decode(&tx_encoded).into_vec()?;
|
||||
let tx: Transaction = deserialize(&tx_bytes)?;
|
||||
|
||||
Ok((tx_hash, status, tx))
|
||||
}
|
||||
|
||||
/// Insert a [`Transaction`] history record into the wallet.
|
||||
pub async fn insert_tx_history_record(&self, tx: &Transaction) -> Result<()> {
|
||||
let query = format!(
|
||||
"INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
|
||||
WALLET_TXS_HISTORY_TABLE,
|
||||
WALLET_TXS_HISTORY_COL_TX_HASH,
|
||||
WALLET_TXS_HISTORY_COL_STATUS,
|
||||
WALLET_TXS_HISTORY_COL_TX,
|
||||
);
|
||||
|
||||
let params = json!([
|
||||
query,
|
||||
QueryType::Text as u8,
|
||||
tx.hash().to_string(),
|
||||
QueryType::Text as u8,
|
||||
"Broadcasted",
|
||||
QueryType::Text as u8,
|
||||
bs58::encode(&serialize(tx)).into_string(),
|
||||
]);
|
||||
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let _ = self.rpc_client.request(req).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update a transactions history record status to the given one.
|
||||
pub async fn update_tx_history_record_status(&self, tx_hash: &str, status: &str) -> Result<()> {
|
||||
let query = format!(
|
||||
"UPDATE {} SET {} = ?1 WHERE {} = ?2;",
|
||||
WALLET_TXS_HISTORY_TABLE, WALLET_TXS_HISTORY_COL_STATUS, WALLET_TXS_HISTORY_COL_TX_HASH,
|
||||
);
|
||||
|
||||
let params = json!([query, QueryType::Text as u8, status, QueryType::Text as u8, tx_hash,]);
|
||||
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let _ = self.rpc_client.request(req).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update given transactions history record statuses to the given one.
|
||||
pub async fn update_tx_history_records_status(
|
||||
&self,
|
||||
txs: &Vec<Transaction>,
|
||||
status: &str,
|
||||
) -> Result<()> {
|
||||
if txs.is_empty() {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let txs_hashes: Vec<String> = txs.iter().map(|tx| tx.hash().to_string()).collect();
|
||||
let txs_hashes_string = format!("{:?}", txs_hashes).replace('[', "(").replace(']', ")");
|
||||
let query = format!(
|
||||
"UPDATE {} SET {} = ?1 WHERE {} IN {};",
|
||||
WALLET_TXS_HISTORY_TABLE,
|
||||
WALLET_TXS_HISTORY_COL_STATUS,
|
||||
WALLET_TXS_HISTORY_COL_TX_HASH,
|
||||
txs_hashes_string
|
||||
);
|
||||
|
||||
let params = json!([query, QueryType::Text as u8, status,]);
|
||||
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let _ = self.rpc_client.request(req).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update all transaction history records statuses to the given one.
|
||||
pub async fn update_all_tx_history_records_status(&self, status: &str) -> Result<()> {
|
||||
let query = format!(
|
||||
"UPDATE {} SET {} = ?1",
|
||||
WALLET_TXS_HISTORY_TABLE, WALLET_TXS_HISTORY_COL_STATUS,
|
||||
);
|
||||
|
||||
let params = json!([query, QueryType::Text as u8, status,]);
|
||||
|
||||
let req = JsonRequest::new("wallet.exec_sql", params);
|
||||
let _ = self.rpc_client.request(req).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
[package]
|
||||
name = "drk2"
|
||||
version = "0.4.1"
|
||||
homepage = "https://dark.fi"
|
||||
description = "Command-line client for darkfid"
|
||||
authors = ["Dyne.org foundation <foundation@dyne.org>"]
|
||||
repository = "https://github.com/darkrenaissance/darkfi"
|
||||
license = "AGPL-3.0-only"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
# Darkfi
|
||||
darkfi = {path = "../../", features = ["async-daemonize", "bs58", "rpc"]}
|
||||
darkfi_money_contract = {path = "../../src/contract/money", features = ["no-entrypoint", "client"]}
|
||||
darkfi_dao_contract = {path = "../../src/contract/dao", features = ["no-entrypoint", "client"]}
|
||||
darkfi-sdk = {path = "../../src/sdk", features = ["async"]}
|
||||
darkfi-serial = {path = "../../src/serial"}
|
||||
|
||||
# Misc
|
||||
blake3 = "1.5.0"
|
||||
bs58 = "0.5.0"
|
||||
log = "0.4.20"
|
||||
prettytable-rs = "0.10.0"
|
||||
rand = "0.8.5"
|
||||
rodio = {version = "0.17.3", default-features = false, features = ["minimp3"]}
|
||||
rusqlite = {version = "0.30.0", features = ["sqlcipher"]}
|
||||
url = "2.5.0"
|
||||
|
||||
# Daemon
|
||||
easy-parallel = "3.3.1"
|
||||
signal-hook-async-std = "0.2.2"
|
||||
signal-hook = "0.3.17"
|
||||
simplelog = "0.12.1"
|
||||
smol = "1.3.0"
|
||||
|
||||
# Argument parsing
|
||||
serde = {version = "1.0.195", features = ["derive"]}
|
||||
structopt = "0.3.26"
|
||||
structopt-toml = "0.5.1"
|
||||
@@ -1,469 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use std::{io::Cursor, process::exit, str::FromStr};
|
||||
|
||||
use rodio::{source::Source, Decoder, OutputStream};
|
||||
use structopt_toml::clap::{App, Arg, Shell, SubCommand};
|
||||
|
||||
use darkfi::{cli_desc, system::sleep, util::parse::decode_base10, Error, Result};
|
||||
use darkfi_sdk::crypto::TokenId;
|
||||
|
||||
use crate::{money::BALANCE_BASE10_DECIMALS, Drk};
|
||||
|
||||
/// Auxiliary function to parse provided string into a values pair.
|
||||
pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
|
||||
let v: Vec<&str> = s.split(':').collect();
|
||||
if v.len() != 2 {
|
||||
eprintln!("Invalid value pair. Use a pair such as 13.37:11.0");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let val0 = decode_base10(v[0], BALANCE_BASE10_DECIMALS, true);
|
||||
let val1 = decode_base10(v[1], BALANCE_BASE10_DECIMALS, true);
|
||||
|
||||
if val0.is_err() || val1.is_err() {
|
||||
eprintln!("Invalid value pair. Use a pair such as 13.37:11.0");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
Ok((val0.unwrap(), val1.unwrap()))
|
||||
}
|
||||
|
||||
/// Auxiliary function to parse provided string into a tokens pair.
|
||||
pub async fn parse_token_pair(drk: &Drk, s: &str) -> Result<(TokenId, TokenId)> {
|
||||
let v: Vec<&str> = s.split(':').collect();
|
||||
if v.len() != 2 {
|
||||
eprintln!("Invalid token pair. Use a pair such as:");
|
||||
eprintln!("WCKD:MLDY");
|
||||
eprintln!("or");
|
||||
eprintln!("A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let tok0 = drk.get_token(v[0].to_string()).await;
|
||||
let tok1 = drk.get_token(v[1].to_string()).await;
|
||||
|
||||
if tok0.is_err() || tok1.is_err() {
|
||||
eprintln!("Invalid token pair. Use a pair such as:");
|
||||
eprintln!("WCKD:MLDY");
|
||||
eprintln!("or");
|
||||
eprintln!("A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
Ok((tok0.unwrap(), tok1.unwrap()))
|
||||
}
|
||||
|
||||
/// Fun police go away
|
||||
pub async fn kaching() {
|
||||
const WALLET_MP3: &[u8] = include_bytes!("../wallet.mp3");
|
||||
|
||||
let cursor = Cursor::new(WALLET_MP3);
|
||||
|
||||
let Ok((_stream, stream_handle)) = OutputStream::try_default() else { return };
|
||||
|
||||
let Ok(source) = Decoder::new(cursor) else { return };
|
||||
|
||||
if stream_handle.play_raw(source.convert_samples()).is_err() {
|
||||
return
|
||||
}
|
||||
|
||||
sleep(2).await;
|
||||
}
|
||||
|
||||
/// Auxiliary function to generate provided shell completions.
|
||||
pub fn generate_completions(shell: &str) -> Result<()> {
|
||||
// Sub-commands
|
||||
|
||||
// Kaching
|
||||
let kaching = SubCommand::with_name("kaching").about("Fun");
|
||||
|
||||
// Ping
|
||||
let ping =
|
||||
SubCommand::with_name("ping").about("Send a ping request to the darkfid RPC endpoint");
|
||||
|
||||
// Completions
|
||||
let shell_arg = Arg::with_name("shell").help("The Shell you want to generate script for");
|
||||
|
||||
let completions = SubCommand::with_name("completions")
|
||||
.about("Generate a SHELL completion script and print to stdout")
|
||||
.arg(shell_arg);
|
||||
|
||||
// Wallet
|
||||
let initialize =
|
||||
Arg::with_name("initialize").long("initialize").help("Initialize wallet database");
|
||||
|
||||
let keygen =
|
||||
Arg::with_name("keygen").long("keygen").help("Generate a new keypair in the wallet");
|
||||
|
||||
let balance =
|
||||
Arg::with_name("balance").long("balance").help("Query the wallet for known balances");
|
||||
|
||||
let address =
|
||||
Arg::with_name("address").long("address").help("Get the default address in the wallet");
|
||||
|
||||
let addresses =
|
||||
Arg::with_name("addresses").long("addresses").help("Print all the addresses in the wallet");
|
||||
|
||||
let default_address = Arg::with_name("default-address")
|
||||
.long("default-address")
|
||||
.takes_value(true)
|
||||
.help("Set the default address in the wallet");
|
||||
|
||||
let secrets =
|
||||
Arg::with_name("secrets").long("secrets").help("Print all the secret keys from the wallet");
|
||||
|
||||
let import_secrets = Arg::with_name("import-secrets")
|
||||
.long("import-secrets")
|
||||
.help("Import secret keys from stdin into the wallet, separated by newlines");
|
||||
|
||||
let tree = Arg::with_name("tree").long("tree").help("Print the Merkle tree in the wallet");
|
||||
|
||||
let coins = Arg::with_name("coins").long("coins").help("Print all the coins in the wallet");
|
||||
|
||||
let wallet = SubCommand::with_name("wallet").about("Wallet operations").args(&vec![
|
||||
initialize,
|
||||
keygen,
|
||||
balance,
|
||||
address,
|
||||
addresses,
|
||||
default_address,
|
||||
secrets,
|
||||
import_secrets,
|
||||
tree,
|
||||
coins,
|
||||
]);
|
||||
|
||||
// Unspend
|
||||
let coin = Arg::with_name("coin").help("base58-encoded coin to mark as unspent");
|
||||
|
||||
let unspend = SubCommand::with_name("unspend").about("Unspend a coin").arg(coin);
|
||||
|
||||
// Transfer
|
||||
let amount = Arg::with_name("amount").help("Amount to send");
|
||||
|
||||
let token = Arg::with_name("token").help("Token ID to send");
|
||||
|
||||
let recipient = Arg::with_name("recipient").help("Recipient address");
|
||||
|
||||
let transfer = SubCommand::with_name("transfer")
|
||||
.about("Create a payment transaction")
|
||||
.args(&vec![amount, token, recipient]);
|
||||
|
||||
// Otc
|
||||
let value_pair = Arg::with_name("value-pair")
|
||||
.short("v")
|
||||
.long("value-pair")
|
||||
.takes_value(true)
|
||||
.help("Value pair to send:recv (11.55:99.42)");
|
||||
|
||||
let token_pair = Arg::with_name("token-pair")
|
||||
.short("t")
|
||||
.long("token-pair")
|
||||
.takes_value(true)
|
||||
.help("Token pair to send:recv (f00:b4r)");
|
||||
|
||||
let init = SubCommand::with_name("init")
|
||||
.about("Initialize the first half of the atomic swap")
|
||||
.args(&vec![value_pair, token_pair]);
|
||||
|
||||
let join =
|
||||
SubCommand::with_name("join").about("Build entire swap tx given the first half from stdin");
|
||||
|
||||
let inspect = SubCommand::with_name("inspect")
|
||||
.about("Inspect a swap half or the full swap tx from stdin");
|
||||
|
||||
let sign = SubCommand::with_name("sign")
|
||||
.about("Sign a transaction given from stdin as the first-half");
|
||||
|
||||
let otc = SubCommand::with_name("otc")
|
||||
.about("OTC atomic swap")
|
||||
.subcommands(vec![init, join, inspect, sign]);
|
||||
|
||||
// Inspect
|
||||
let inspect = SubCommand::with_name("inspect").about("Inspect a transaction from stdin");
|
||||
|
||||
// Broadcast
|
||||
let broadcast =
|
||||
SubCommand::with_name("broadcast").about("Read a transaction from stdin and broadcast it");
|
||||
|
||||
// Subscribe
|
||||
let subscribe = SubCommand::with_name("subscribe").about(
|
||||
"This subscription will listen for incoming blocks from darkfid and look \
|
||||
through their transactions to see if there's any that interest us. \
|
||||
With `drk` we look at transactions calling the money contract so we can \
|
||||
find coins sent to us and fill our wallet with the necessary metadata.",
|
||||
);
|
||||
|
||||
// DAO
|
||||
let proposer_limit = Arg::with_name("proposer-limit")
|
||||
.help("The minimum amount of governance tokens needed to open a proposal for this DAO");
|
||||
|
||||
let quorum = Arg::with_name("quorum")
|
||||
.help("Minimal threshold of participating total tokens needed for a proposal to pass");
|
||||
|
||||
let approval_ratio = Arg::with_name("approval-ratio")
|
||||
.help("The ratio of winning votes/total votes needed for a proposal to pass (2 decimals)");
|
||||
|
||||
let gov_token_id = Arg::with_name("gov-token-id").help("DAO's governance token ID");
|
||||
|
||||
let create = SubCommand::with_name("create").about("Create DAO parameters").args(&vec![
|
||||
proposer_limit,
|
||||
quorum,
|
||||
approval_ratio,
|
||||
gov_token_id,
|
||||
]);
|
||||
|
||||
let view = SubCommand::with_name("view").about("View DAO data from stdin");
|
||||
|
||||
let dao_name = Arg::with_name("dao-name").help("Named identifier for the DAO");
|
||||
|
||||
let import =
|
||||
SubCommand::with_name("import").about("Import DAO data from stdin").args(&vec![dao_name]);
|
||||
|
||||
let dao_alias = Arg::with_name("dao-alias").help("Numeric identifier for the DAO (optional)");
|
||||
|
||||
let list = SubCommand::with_name("list")
|
||||
.about("List imported DAOs (or info about a specific one)")
|
||||
.args(&vec![dao_alias]);
|
||||
|
||||
let dao_alias = Arg::with_name("dao-alias").help("Name or numeric identifier for the DAO");
|
||||
|
||||
let balance = SubCommand::with_name("balance")
|
||||
.about("Show the balance of a DAO")
|
||||
.args(&vec![dao_alias.clone()]);
|
||||
|
||||
let mint = SubCommand::with_name("mint")
|
||||
.about("Mint an imported DAO on-chain")
|
||||
.args(&vec![dao_alias.clone()]);
|
||||
|
||||
let recipient =
|
||||
Arg::with_name("recipient").help("Pubkey to send tokens to with proposal success");
|
||||
|
||||
let amount = Arg::with_name("amount").help("Amount to send from DAO with proposal success");
|
||||
|
||||
let token = Arg::with_name("token").help("Token ID to send from DAO with proposal success");
|
||||
|
||||
let propose = SubCommand::with_name("propose")
|
||||
.about("Create a proposal for a DAO")
|
||||
.args(&vec![dao_alias.clone(), recipient, amount, token]);
|
||||
|
||||
let proposals = SubCommand::with_name("proposals")
|
||||
.about("List DAO proposals")
|
||||
.args(&vec![dao_alias.clone()]);
|
||||
|
||||
let proposal_id = Arg::with_name("proposal-id").help("Numeric identifier for the proposal");
|
||||
|
||||
let proposal = SubCommand::with_name("proposal")
|
||||
.about("View a DAO proposal data")
|
||||
.args(&vec![dao_alias.clone(), proposal_id.clone()]);
|
||||
|
||||
let vote = Arg::with_name("vote").help("Vote (0 for NO, 1 for YES)");
|
||||
|
||||
let vote_weight =
|
||||
Arg::with_name("vote-weight").help("Vote weight (amount of governance tokens)");
|
||||
|
||||
let vote = SubCommand::with_name("vote").about("Vote on a given proposal").args(&vec![
|
||||
dao_alias.clone(),
|
||||
proposal_id.clone(),
|
||||
vote,
|
||||
vote_weight,
|
||||
]);
|
||||
|
||||
let exec = SubCommand::with_name("exec")
|
||||
.about("Execute a DAO proposal")
|
||||
.args(&vec![dao_alias, proposal_id]);
|
||||
|
||||
let dao = SubCommand::with_name("dao").about("DAO functionalities").subcommands(vec![
|
||||
create, view, import, list, balance, mint, propose, proposals, proposal, vote, exec,
|
||||
]);
|
||||
|
||||
// Scan
|
||||
let reset = Arg::with_name("reset")
|
||||
.long("reset")
|
||||
.help("Reset Merkle tree and start scanning from first block");
|
||||
|
||||
let list = Arg::with_name("list").long("list").help("List all available checkpoints");
|
||||
|
||||
let checkpoint = Arg::with_name("checkpoint")
|
||||
.long("checkpoint")
|
||||
.takes_value(true)
|
||||
.help("Reset Merkle tree to checkpoint index and start scanning");
|
||||
|
||||
let scan = SubCommand::with_name("scan")
|
||||
.about("Scan the blockchain and parse relevant transactions")
|
||||
.args(&vec![reset, list, checkpoint]);
|
||||
|
||||
// Explorer
|
||||
let tx_hash = Arg::with_name("tx-hash").help("Transaction hash");
|
||||
|
||||
let full = Arg::with_name("full").long("full").help("Print the full transaction information");
|
||||
|
||||
let encode = Arg::with_name("encode").long("encode").help("Encode transaction to base58");
|
||||
|
||||
let fetch_tx = SubCommand::with_name("fetch-tx")
|
||||
.about("Fetch a blockchain transaction by hash")
|
||||
.args(&vec![tx_hash, full, encode]);
|
||||
|
||||
let simulate_tx =
|
||||
SubCommand::with_name("simulate-tx").about("Read a transaction from stdin and simulate it");
|
||||
|
||||
let tx_hash = Arg::with_name("tx-hash").help("Transaction hash");
|
||||
|
||||
let encode = Arg::with_name("encode")
|
||||
.long("encode")
|
||||
.help("Encode specific history record transaction to base58");
|
||||
|
||||
let txs_history = SubCommand::with_name("txs-history")
|
||||
.about("Fetch broadcasted transactions history")
|
||||
.args(&vec![tx_hash, encode]);
|
||||
|
||||
let explorer = SubCommand::with_name("explorer")
|
||||
.about("Explorer related subcommands")
|
||||
.subcommands(vec![fetch_tx, simulate_tx, txs_history]);
|
||||
|
||||
// Alias
|
||||
let alias = Arg::with_name("alias").help("Token alias");
|
||||
|
||||
let token = Arg::with_name("token").help("Token to create alias for");
|
||||
|
||||
let add = SubCommand::with_name("add").about("Create a Token alias").args(&vec![alias, token]);
|
||||
|
||||
let alias = Arg::with_name("alias")
|
||||
.short("a")
|
||||
.long("alias")
|
||||
.takes_value(true)
|
||||
.help("Token alias to search for");
|
||||
|
||||
let token = Arg::with_name("token")
|
||||
.short("t")
|
||||
.long("token")
|
||||
.takes_value(true)
|
||||
.help("Token to search alias for");
|
||||
|
||||
let show = SubCommand::with_name("show")
|
||||
.about(
|
||||
"Print alias info of optional arguments. \
|
||||
If no argument is provided, list all the aliases in the wallet.",
|
||||
)
|
||||
.args(&vec![alias, token]);
|
||||
|
||||
let alias = Arg::with_name("alias").help("Token alias to remove");
|
||||
|
||||
let remove = SubCommand::with_name("remove").about("Remove a Token alias").arg(alias);
|
||||
|
||||
let alias = SubCommand::with_name("alias")
|
||||
.about("Manage Token aliases")
|
||||
.subcommands(vec![add, show, remove]);
|
||||
|
||||
// Token
|
||||
let import = SubCommand::with_name("import").about("Import a mint authority secret from stdin");
|
||||
|
||||
let generate_mint =
|
||||
SubCommand::with_name("generate-mint").about("Generate a new mint authority");
|
||||
|
||||
let list =
|
||||
SubCommand::with_name("list").about("List token IDs with available mint authorities");
|
||||
|
||||
let token = Arg::with_name("token").help("Token ID to mint");
|
||||
|
||||
let amount = Arg::with_name("amount").help("Amount to mint");
|
||||
|
||||
let recipient = Arg::with_name("recipient").help("Recipient of the minted tokens");
|
||||
|
||||
let mint =
|
||||
SubCommand::with_name("mint").about("Mint tokens").args(&vec![token, amount, recipient]);
|
||||
|
||||
let token = Arg::with_name("token").help("Token ID to freeze");
|
||||
|
||||
let freeze = SubCommand::with_name("freeze").about("Freeze a token mint").arg(token);
|
||||
|
||||
let token = SubCommand::with_name("token").about("Token functionalities").subcommands(vec![
|
||||
import,
|
||||
generate_mint,
|
||||
list,
|
||||
mint,
|
||||
freeze,
|
||||
]);
|
||||
|
||||
// Main arguments
|
||||
let config = Arg::with_name("config")
|
||||
.short("c")
|
||||
.long("config")
|
||||
.takes_value(true)
|
||||
.help("Configuration file to use");
|
||||
|
||||
let wallet_path = Arg::with_name("wallet_path")
|
||||
.long("wallet-path")
|
||||
.takes_value(true)
|
||||
.help("Path to wallet database");
|
||||
|
||||
let wallet_pass = Arg::with_name("wallet_pass")
|
||||
.long("wallet-pass")
|
||||
.takes_value(true)
|
||||
.help("Password for the wallet database");
|
||||
|
||||
let endpoint = Arg::with_name("endpoint")
|
||||
.short("e")
|
||||
.long("endpoint")
|
||||
.takes_value(true)
|
||||
.help("darkfid JSON-RPC endpoint");
|
||||
|
||||
let command = vec![
|
||||
kaching,
|
||||
ping,
|
||||
completions,
|
||||
wallet,
|
||||
unspend,
|
||||
transfer,
|
||||
otc,
|
||||
inspect,
|
||||
broadcast,
|
||||
subscribe,
|
||||
dao,
|
||||
scan,
|
||||
explorer,
|
||||
alias,
|
||||
token,
|
||||
];
|
||||
|
||||
let log = Arg::with_name("log")
|
||||
.short("l")
|
||||
.long("log")
|
||||
.takes_value(true)
|
||||
.help("Set log file to ouput into");
|
||||
|
||||
let verbose = Arg::with_name("verbose")
|
||||
.short("v")
|
||||
.multiple(true)
|
||||
.help("Increase verbosity (-vvv supported)");
|
||||
|
||||
let mut app = App::new("drk")
|
||||
.about(cli_desc!())
|
||||
.args(&vec![config, wallet_path, wallet_pass, endpoint, log, verbose])
|
||||
.subcommands(command);
|
||||
|
||||
let shell = match Shell::from_str(shell) {
|
||||
Ok(s) => s,
|
||||
Err(e) => return Err(Error::Custom(e)),
|
||||
};
|
||||
|
||||
app.gen_completions_to("./drk", shell, &mut std::io::stdout());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
1579
bin/drk2/src/main.rs
1579
bin/drk2/src/main.rs
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,9 +0,0 @@
|
||||
-- Wallet definitions for drk.
|
||||
-- We store data that is needed for wallet operations.
|
||||
|
||||
-- Broadcasted transactions history
|
||||
CREATE TABLE IF NOT EXISTS transactions_history (
|
||||
transaction_hash TEXT PRIMARY KEY NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
tx BLOB NOT NULL
|
||||
);
|
||||
Reference in New Issue
Block a user