drk is back in the menu boys

This commit is contained in:
skoupidi
2024-01-25 18:52:03 +02:00
parent 2f6bb5748f
commit 3062597fca
36 changed files with 1511 additions and 6977 deletions

3
.gitignore vendored
View File

@@ -16,6 +16,9 @@
/bin/darkfi-mmproxy/darkfi-mmproxy
/darkfi-mmproxy
/bin/drk/drk
/drk
/bin/darkirc/darkirc
/darkirc

397
Cargo.lock generated
View File

@@ -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"

View File

@@ -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",

View File

@@ -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)" \

View File

@@ -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"

View File

@@ -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)

View File

@@ -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(())
}

View File

@@ -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(())
}

File diff suppressed because it is too large Load Diff

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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 &params.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 = &params.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(&note.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(())
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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(())
}
}

View File

@@ -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)
}
}

View File

@@ -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(())
}
}

View File

@@ -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"

View File

@@ -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(())
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -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
);