From b1f392c27200e3d9486759cf94fe42eb3191cf0e Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Tue, 26 Nov 2024 16:20:09 +0400 Subject: [PATCH] feat!: add sidechain eviction proof transaction --- Cargo.lock | 244 +++--- Cargo.toml | 3 +- applications/minotari_app_grpc/Cargo.toml | 1 + .../minotari_app_grpc/proto/base_node.proto | 18 +- .../proto/sidechain_types.proto | 141 +++- .../minotari_app_grpc/proto/types.proto | 4 +- .../minotari_app_grpc/proto/wallet.proto | 12 + .../minotari_app_grpc/src/conversions/mod.rs | 48 +- .../src/conversions/output_features.rs | 6 +- .../src/conversions/sidechain_feature.rs | 534 +++++++++++-- .../src/conversions/signature.rs | 30 +- .../src/conversions/validator_node_change.rs | 20 - .../validator_node_registration.rs | 28 - .../src/grpc/wallet_grpc_server.rs | 48 ++ .../src/grpc/base_node_grpc_server.rs | 86 +- base_layer/common_types/Cargo.toml | 2 + base_layer/common_types/src/epoch.rs | 29 +- .../common_types/src/types/fixed_hash.rs | 1 + base_layer/core/Cargo.toml | 1 + .../comms_interface/comms_request.rs | 33 +- .../comms_interface/comms_response.rs | 11 +- .../comms_interface/inbound_handlers.rs | 122 +-- .../comms_interface/local_interface.rs | 23 +- base_layer/core/src/base_node/rpc/service.rs | 2 +- .../chain_storage/active_validator_node.rs | 36 +- base_layer/core/src/chain_storage/async_db.rs | 7 +- .../src/chain_storage/blockchain_backend.rs | 45 +- .../src/chain_storage/blockchain_database.rs | 79 +- .../chain_storage/lmdb_db/composite_key.rs | 157 +++- .../core/src/chain_storage/lmdb_db/cursors.rs | 105 ++- .../core/src/chain_storage/lmdb_db/lmdb_db.rs | 485 +++++++++--- .../lmdb_db/validator_node_store.rs | 746 +++++++++++------- base_layer/core/src/chain_storage/mod.rs | 4 +- .../tests/blockchain_database.rs | 58 +- .../core/src/consensus/consensus_constants.rs | 52 +- base_layer/core/src/covenants/test.rs | 8 +- .../core/src/proto/sidechain_feature.proto | 155 +++- .../core/src/proto/sidechain_feature.rs | 440 +++++++++-- base_layer/core/src/proto/transaction.rs | 6 +- base_layer/core/src/proto/types_impls.rs | 28 +- .../core/src/test_helpers/blockchain.rs | 74 +- .../src/transactions/key_manager/interface.rs | 2 +- .../src/transactions/key_manager/wrapper.rs | 4 +- .../transaction_components/mod.rs | 3 +- .../transaction_components/output_features.rs | 69 +- .../transaction_components/output_type.rs | 43 +- .../range_proof_type.rs | 21 + .../side_chain/confidential_output.rs | 13 +- .../transaction_components/side_chain/mod.rs | 10 +- .../side_chain/sidechain_feature.rs | 104 ++- .../side_chain/template_registration.rs | 18 +- .../side_chain/validator_node_registration.rs | 38 +- .../side_chain/validator_node_signature.rs | 59 +- .../transaction_input.rs | 34 +- .../transaction_output.rs | 52 +- .../aggregate_body_chain_validator.rs | 51 +- .../aggregate_body_internal_validator.rs | 139 ++-- .../block_body/block_body_full_validator.rs | 9 +- base_layer/core/src/validation/error.rs | 38 +- base_layer/core/src/validation/helpers.rs | 86 +- .../transaction_chain_validator.rs | 5 +- base_layer/sidechain/Cargo.toml | 15 + base_layer/sidechain/src/command.rs | 44 ++ base_layer/sidechain/src/commit_proof.rs | 252 ++++++ base_layer/sidechain/src/error.rs | 24 + base_layer/sidechain/src/eviction_proof.rs | 74 ++ base_layer/sidechain/src/lib.rs | 10 + base_layer/sidechain/src/validations.rs | 203 +++++ base_layer/wallet/Cargo.toml | 1 + .../recovery/standard_outputs_recoverer.rs | 2 + .../storage/output_source.rs | 4 + .../wallet/src/transaction_service/error.rs | 2 + .../wallet/src/transaction_service/handle.rs | 53 +- .../wallet/src/transaction_service/service.rs | 129 +-- clippy.toml | 1 + hashing/Cargo.toml | 8 +- hashing/src/borsh_hasher.rs | 5 + hashing/src/domains.rs | 8 + hashing/src/layer2.rs | 54 ++ hashing/src/lib.rs | 3 + 80 files changed, 4142 insertions(+), 1480 deletions(-) delete mode 100644 applications/minotari_app_grpc/src/conversions/validator_node_change.rs delete mode 100644 applications/minotari_app_grpc/src/conversions/validator_node_registration.rs create mode 100644 base_layer/sidechain/Cargo.toml create mode 100644 base_layer/sidechain/src/command.rs create mode 100644 base_layer/sidechain/src/commit_proof.rs create mode 100644 base_layer/sidechain/src/error.rs create mode 100644 base_layer/sidechain/src/eviction_proof.rs create mode 100644 base_layer/sidechain/src/lib.rs create mode 100644 base_layer/sidechain/src/validations.rs create mode 100644 hashing/src/layer2.rs diff --git a/Cargo.lock b/Cargo.lock index ad29f1a83c..edc4d05336 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -186,7 +186,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -208,7 +208,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -219,7 +219,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -373,7 +373,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978e81a45367d2409ecd33369a45dda2e9a3ca516153ec194de1fbda4b9fb79d" dependencies = [ - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -539,9 +539,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03" dependencies = [ "borsh-derive", "cfg_aliases", @@ -549,16 +549,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244" dependencies = [ "once_cell", "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.79", - "syn_derive", + "syn 2.0.87", ] [[package]] @@ -923,7 +922,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1358,7 +1357,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1436,7 +1435,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.79", + "syn 2.0.87", "synthez", ] @@ -1481,7 +1480,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1529,7 +1528,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1551,7 +1550,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1698,7 +1697,7 @@ dependencies = [ "console", "shell-words", "tempfile", - "thiserror", + "thiserror 1.0.69", "zeroize", ] @@ -1730,7 +1729,7 @@ dependencies = [ "dsl_auto_type", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1750,7 +1749,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" dependencies = [ - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1837,7 +1836,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1955,7 +1954,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -2221,7 +2220,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -2333,9 +2332,9 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.79", + "syn 2.0.87", "textwrap 0.16.0", - "thiserror", + "thiserror 1.0.69", "typed-builder", ] @@ -2560,7 +2559,7 @@ dependencies = [ "once_cell", "radix_trie", "rand", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", ] @@ -2587,7 +2586,7 @@ dependencies = [ "ring", "rustls", "rustls-pemfile 2.2.0", - "thiserror", + "thiserror 1.0.69", "time", "tinyvec", "tokio", @@ -2612,7 +2611,7 @@ dependencies = [ "rand", "resolv-conf", "smallvec", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", ] @@ -3174,7 +3173,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -3228,7 +3227,7 @@ dependencies = [ "ledger-transport 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc", "log", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3404,7 +3403,7 @@ dependencies = [ "serde-value", "serde_json", "serde_yaml", - "thiserror", + "thiserror 1.0.69", "thread-id", "typemap-ors", "winapi", @@ -3579,8 +3578,9 @@ dependencies = [ "tari_features", "tari_max_size", "tari_script", + "tari_sidechain", "tari_utilities", - "thiserror", + "thiserror 1.0.69", "tokio", "tonic 0.12.3", "tonic-build", @@ -3604,7 +3604,7 @@ dependencies = [ "tari_comms", "tari_features", "tari_utilities", - "thiserror", + "thiserror 1.0.69", "tokio", "tonic 0.12.3", ] @@ -3630,7 +3630,7 @@ dependencies = [ "tari_p2p", "tari_shutdown", "tari_utilities", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -3679,7 +3679,7 @@ dependencies = [ "tari_script", "tari_shutdown", "tari_utilities", - "thiserror", + "thiserror 1.0.69", "tokio", "tonic 0.12.3", "tui", @@ -3716,7 +3716,7 @@ dependencies = [ "tari_crypto", "tari_script", "tari_utilities", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3754,7 +3754,7 @@ dependencies = [ "tari_key_manager", "tari_max_size", "tari_utilities", - "thiserror", + "thiserror 1.0.69", "tokio", "tonic 0.12.3", "tracing", @@ -3793,7 +3793,7 @@ dependencies = [ "tari_crypto", "tari_max_size", "tari_utilities", - "thiserror", + "thiserror 1.0.69", "tokio", "tonic 0.12.3", ] @@ -3813,7 +3813,7 @@ dependencies = [ "tari_crypto", "tari_features", "tari_utilities", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -3860,7 +3860,7 @@ dependencies = [ "tari_shutdown", "tari_storage", "tari_utilities", - "thiserror", + "thiserror 1.0.69", "tokio", "toml 0.5.11", "tonic 0.12.3", @@ -3916,10 +3916,11 @@ dependencies = [ "tari_script", "tari_service_framework", "tari_shutdown", + "tari_sidechain", "tari_test_utils", "tari_utilities", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", "tower", "zeroize", @@ -3961,7 +3962,7 @@ dependencies = [ "tari_test_utils", "tari_utilities", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", "zeroize", ] @@ -3972,7 +3973,7 @@ version = "1.7.0-pre.3" dependencies = [ "minotari_app_grpc", "tari_common_types", - "thiserror", + "thiserror 1.0.69", "tokio", "tonic 0.12.3", ] @@ -4024,7 +4025,7 @@ dependencies = [ "hex-literal 0.4.1", "sealed", "serde", - "thiserror", + "thiserror 1.0.69", "tiny-keccak", ] @@ -4214,7 +4215,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -4333,7 +4334,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -4569,7 +4570,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" dependencies = [ "memchr", - "thiserror", + "thiserror 1.0.69", "ucd-trie", ] @@ -4593,7 +4594,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -4673,7 +4674,7 @@ dependencies = [ "sha3", "signature", "smallvec", - "thiserror", + "thiserror 1.0.69", "twofish", "x25519-dalek", "zeroize", @@ -4738,7 +4739,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -4796,7 +4797,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -4930,7 +4931,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -4960,7 +4961,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ - "thiserror", + "thiserror 1.0.69", "toml 0.5.11", ] @@ -5018,7 +5019,7 @@ dependencies = [ "memchr", "parking_lot 0.12.1", "protobuf", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5080,7 +5081,7 @@ dependencies = [ "prost 0.13.3", "prost-types 0.13.3", "regex", - "syn 2.0.79", + "syn 2.0.87", "tempfile", ] @@ -5107,7 +5108,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -5250,7 +5251,7 @@ checksum = "9abb8f2aa3432700c2b64a67406ac0da4956d78991f50559509cecc2b6abf249" dependencies = [ "bitflags 1.3.2", "libc", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5311,7 +5312,7 @@ checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", "redox_syscall 0.2.16", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5711,7 +5712,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -5803,7 +5804,7 @@ checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -5825,7 +5826,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -6035,7 +6036,7 @@ checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -6265,27 +6266,15 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "syn_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.79", -] - [[package]] name = "sync_wrapper" version = "0.1.2" @@ -6316,7 +6305,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d2c2202510a1e186e63e596d9318c91a8cbe85cd1a56a7be0c333e5f59ec8d" dependencies = [ - "syn 2.0.79", + "syn 2.0.87", "synthez-codegen", "synthez-core", ] @@ -6327,7 +6316,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f724aa6d44b7162f3158a57bccd871a77b39a4aef737e01bcdff41f4772c7746" dependencies = [ - "syn 2.0.79", + "syn 2.0.87", "synthez-core", ] @@ -6340,7 +6329,7 @@ dependencies = [ "proc-macro2", "quote", "sealed", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -6425,7 +6414,7 @@ dependencies = [ "tari_service_framework", "tari_shutdown", "tari_storage", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -6450,7 +6439,7 @@ dependencies = [ "tari_features", "tari_test_utils", "tempfile", - "thiserror", + "thiserror 1.0.69", "toml 0.5.11", ] @@ -6464,7 +6453,7 @@ dependencies = [ "serde", "tari_test_utils", "tari_utilities", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -6489,8 +6478,9 @@ dependencies = [ "strum_macros", "tari_common", "tari_crypto", + "tari_hashing", "tari_utilities", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -6532,7 +6522,7 @@ dependencies = [ "tari_test_utils", "tari_utilities", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tokio-util 0.6.10", @@ -6580,7 +6570,7 @@ dependencies = [ "tari_test_utils", "tari_utilities", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tower", @@ -6630,7 +6620,7 @@ dependencies = [ "tari_test_utils", "tari_utilities", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", "tower", "uuid", @@ -6701,11 +6691,12 @@ dependencies = [ "tari_script", "tari_service_framework", "tari_shutdown", + "tari_sidechain", "tari_storage", "tari_test_utils", "tari_utilities", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", "toml 0.5.11", "tracing", @@ -6792,7 +6783,7 @@ dependencies = [ "tari_shutdown", "tari_utilities", "tempfile", - "thiserror", + "thiserror 1.0.69", "time", "tokio", "tonic 0.12.3", @@ -6828,7 +6819,7 @@ dependencies = [ "tari_service_framework", "tari_utilities", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", "zeroize", ] @@ -6854,7 +6845,7 @@ dependencies = [ "borsh", "serde", "tari_utilities", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -6867,7 +6858,7 @@ dependencies = [ "once_cell", "prometheus", "reqwest", - "thiserror", + "thiserror 1.0.69", "tokio", "warp", ] @@ -6887,7 +6878,7 @@ dependencies = [ "serde_json", "tari_crypto", "tari_utilities", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -6920,7 +6911,7 @@ dependencies = [ "tari_test_utils", "tari_utilities", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "toml 0.5.11", @@ -6943,7 +6934,7 @@ dependencies = [ "tari_crypto", "tari_max_size", "tari_utilities", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -6957,7 +6948,7 @@ dependencies = [ "log", "tari_shutdown", "tari_test_utils", - "thiserror", + "thiserror 1.0.69", "tokio", "tower", "tower-service", @@ -6971,6 +6962,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tari_sidechain" +version = "1.7.0-pre.3" +dependencies = [ + "borsh", + "serde", + "tari_common_types", + "tari_crypto", + "tari_hashing", + "tari_utilities", + "thiserror 2.0.3", +] + [[package]] name = "tari_storage" version = "1.7.0-pre.3" @@ -6981,7 +6985,7 @@ dependencies = [ "rand", "serde", "tari_utilities", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -7081,22 +7085,42 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.3", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -7241,7 +7265,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -7421,7 +7445,7 @@ dependencies = [ "prost-build 0.13.3", "prost-types 0.13.3", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -7434,7 +7458,7 @@ dependencies = [ "hex-literal 0.3.4", "rand", "sha1 0.6.0", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -7490,7 +7514,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -7563,7 +7587,7 @@ checksum = "29a3151c41d0b13e3d011f98adc24434560ef06673a155a6c7f66b9879eecce2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -7835,7 +7859,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", "wasm-bindgen-shared", ] @@ -7869,7 +7893,7 @@ checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8246,7 +8270,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -8266,7 +8290,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index faf499d373..5da5560363 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "base_layer/mmr", "base_layer/p2p", "base_layer/service_framework", + "base_layer/sidechain", "base_layer/wallet", "base_layer/wallet_ffi", "base_layer/tari_mining_helper_ffi", @@ -39,7 +40,7 @@ members = [ "applications/minotari_ledger_wallet/comms", "applications/minotari_ledger_wallet/common", "integration_tests", - "hashing" + "hashing", ] [workspace.dependencies] diff --git a/applications/minotari_app_grpc/Cargo.toml b/applications/minotari_app_grpc/Cargo.toml index 9ac896e91d..e97d00afcc 100644 --- a/applications/minotari_app_grpc/Cargo.toml +++ b/applications/minotari_app_grpc/Cargo.toml @@ -14,6 +14,7 @@ tari_core = { path = "../../base_layer/core" } tari_crypto = { version = "0.21.0" } tari_script = { path = "../../infrastructure/tari_script" } tari_max_size = { path = "../../infrastructure/max_size" } +tari_sidechain = { path = "../../base_layer/sidechain" } tari_utilities = { version = "0.8" } argon2 = { version = "0.4.1", features = ["std", "password-hash"] } diff --git a/applications/minotari_app_grpc/proto/base_node.proto b/applications/minotari_app_grpc/proto/base_node.proto index 07b2220391..b3b99203d4 100644 --- a/applications/minotari_app_grpc/proto/base_node.proto +++ b/applications/minotari_app_grpc/proto/base_node.proto @@ -46,7 +46,7 @@ service BaseNode { // Returns Block Fees rpc GetBlockFees (BlockGroupRequest) returns (BlockGroupResponse); // Get Version - rpc GetVersion(Empty) returns (StringValue); + rpc GetVersion(Empty) returns (BaseNodeGetVersionResponse); // Check for new updates rpc CheckForUpdates(Empty) returns (SoftwareUpdate); // Get coins in circulation @@ -233,9 +233,9 @@ message IntegerValue { uint64 value = 1; } -// A generic String value -message StringValue { - string value = 1; +message BaseNodeGetVersionResponse { + string version = 1; + uint32 network = 2; } /// GetBlockSize / GetBlockFees Request @@ -488,9 +488,8 @@ message GetActiveValidatorNodesResponse { } message GetValidatorNodeChangesRequest { - uint64 start_height = 1; - uint64 end_height = 2; - bytes sidechain_id = 3; + uint64 epoch = 1; + bytes sidechain_id = 2; } enum ValidatorNodeChangeState { @@ -501,7 +500,7 @@ enum ValidatorNodeChangeState { message ValidatorNodeChange { bytes public_key = 1; ValidatorNodeChangeState state = 2; - uint64 start_height = 3; + uint64 activation_epoch = 3; ValidatorNodeRegistration registration = 4; uint64 minimum_value_promise = 5; } @@ -511,13 +510,12 @@ message GetValidatorNodeChangesResponse { } message GetShardKeyRequest { - uint64 height = 1; + uint64 epoch = 1; bytes public_key = 2; } message GetShardKeyResponse { bytes shard_key = 1; - bool found = 2; } message GetTemplateRegistrationsRequest { diff --git a/applications/minotari_app_grpc/proto/sidechain_types.proto b/applications/minotari_app_grpc/proto/sidechain_types.proto index b0c9728ecc..0f301f33c9 100644 --- a/applications/minotari_app_grpc/proto/sidechain_types.proto +++ b/applications/minotari_app_grpc/proto/sidechain_types.proto @@ -26,50 +26,51 @@ package tari.rpc; import "types.proto"; message SideChainFeature { - oneof side_chain_feature { - ValidatorNodeRegistration validator_node_registration = 1; - TemplateRegistration template_registration = 2; - ConfidentialOutputData confidential_output = 3; - } + oneof feature { + ValidatorNodeRegistration validator_node_registration = 1; + TemplateRegistration template_registration = 2; + ConfidentialOutputData confidential_output = 3; + EvictionProof eviction_proof = 4; + } + SideChainId sidechain_id = 5; +} + +message SideChainId { + bytes public_key = 1; + Signature knowledge_proof = 2; } message ValidatorNodeRegistration { - bytes public_key = 1; - Signature signature = 2; - bytes claim_public_key = 3; - bytes sidechain_id = 4; - Signature sidechain_id_knowledge_proof = 5; + bytes public_key = 1; + Signature signature = 2; + bytes claim_public_key = 3; } message TemplateRegistration { - bytes author_public_key = 1; - Signature author_signature = 2; - string template_name = 3; - uint32 template_version = 4; - TemplateType template_type = 5; - BuildInfo build_info = 6; - bytes binary_sha = 7; - string binary_url = 8; - bytes sidechain_id = 9; - Signature sidechain_id_knowledge_proof = 10; + bytes author_public_key = 1; + Signature author_signature = 2; + string template_name = 3; + uint32 template_version = 4; + TemplateType template_type = 5; + BuildInfo build_info = 6; + bytes binary_sha = 7; + string binary_url = 8; } message ConfidentialOutputData { - bytes claim_public_key = 1; - bytes sidechain_id = 2; - Signature sidechain_id_knowledge_proof = 3; + bytes claim_public_key = 1; } message TemplateType { - oneof template_type { - WasmInfo wasm = 1; - FlowInfo flow = 2; - ManifestInfo manifest = 3; - } + oneof template_type { + WasmInfo wasm = 1; + FlowInfo flow = 2; + ManifestInfo manifest = 3; + } } message WasmInfo { - uint32 abi_version = 1; + uint32 abi_version = 1; } message FlowInfo { @@ -79,6 +80,84 @@ message ManifestInfo { } message BuildInfo { - string repo_url = 1; - bytes commit_hash = 2; + string repo_url = 1; + bytes commit_hash = 2; +} + +message EvictionProof { + CommitProof proof = 1; +} + +message CommitProof { + oneof version { + CommitProofV1 v1 = 1; + } +} + +message CommitProofV1 { + bytes command = 1; + SidechainBlockCommitProof commit_proof = 2; +} + +message SidechainBlockCommitProof { + SidechainBlockHeader header = 1; + repeated CommitProofElement proof_elements = 2; +} + +message CommitProofElement { + oneof proof_element { + QuorumCertificate quorum_certificate = 1; + DummyChain dummy_chain = 2; + } +} + +message DummyChain { + repeated ChainLink chain_links = 1; } + +message ChainLink { + bytes header_hash = 1; + bytes parent_id = 2; +} + +message SidechainBlockHeader { + uint32 network = 1; + bytes parent_id = 2; + bytes justify_id = 3; + uint64 height = 4; + uint64 epoch = 5; + uint32 shard_group = 6; + bytes proposed_by = 7; + uint64 total_leader_fee = 8; + bytes state_merkle_root = 9; + bytes command_merkle_root = 10; + bool is_dummy = 11; + bytes foreign_indexes_hash = 12; + Signature signature = 13; + uint64 timestamp = 14; + uint64 base_layer_block_height = 15; + bytes base_layer_block_hash = 16; + bytes extra_data_hash = 17; +} + +message EvictAtom { + bytes public_key = 1; +} + +message QuorumCertificate { + bytes header_hash = 1; + bytes parent_id = 2; + repeated ValidatorSignature signatures = 3; + QuorumDecision decision = 4; +} + +enum QuorumDecision { + Accept = 0; + Reject = 1; +} + +message ValidatorSignature { + bytes public_key = 1; + Signature signature = 2; +} + diff --git a/applications/minotari_app_grpc/proto/types.proto b/applications/minotari_app_grpc/proto/types.proto index f44696fc30..2642c232fa 100644 --- a/applications/minotari_app_grpc/proto/types.proto +++ b/applications/minotari_app_grpc/proto/types.proto @@ -126,8 +126,8 @@ message ConsensusConstants { uint64 block_weight_kernels = 16; uint64 pre_mine_value = 17; uint64 max_script_byte_size = 18; - uint64 vn_registration_max_vns_initial_epoch = 19; - uint64 vn_registration_max_vns_per_epoch = 20; + uint32 vn_registration_max_vns_initial_epoch = 19; + uint32 vn_registration_max_vns_per_epoch = 20; uint64 effective_from_height = 21; Range valid_blockchain_version_range = 22; uint64 max_randomx_seed_height = 23; diff --git a/applications/minotari_app_grpc/proto/wallet.proto b/applications/minotari_app_grpc/proto/wallet.proto index 7ade0301e6..f6aabe6cd7 100644 --- a/applications/minotari_app_grpc/proto/wallet.proto +++ b/applications/minotari_app_grpc/proto/wallet.proto @@ -80,6 +80,7 @@ service Wallet { rpc StreamTransactionEvents(TransactionEventRequest) returns (stream TransactionEventResponse); rpc RegisterValidatorNode(RegisterValidatorNodeRequest) returns (RegisterValidatorNodeResponse); + rpc SubmitValidatorEvictionProof(SubmitValidatorEvictionProofRequest) returns (SubmitValidatorEvictionProofResponse); } message GetVersionRequest {} @@ -359,3 +360,14 @@ message RegisterValidatorNodeResponse { bool is_success = 2; string failure_message = 3; } + +message SubmitValidatorEvictionProofRequest { + EvictionProof proof = 1; + uint64 fee_per_gram = 2; + string message = 3; + bytes sidechain_deployment_key = 4; +} + +message SubmitValidatorEvictionProofResponse { + uint64 tx_id = 1; +} diff --git a/applications/minotari_app_grpc/src/conversions/mod.rs b/applications/minotari_app_grpc/src/conversions/mod.rs index 7748035be5..e2b6225040 100644 --- a/applications/minotari_app_grpc/src/conversions/mod.rs +++ b/applications/minotari_app_grpc/src/conversions/mod.rs @@ -20,28 +20,26 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -pub mod aggregate_body; -pub mod base_node_state; -pub mod block; -pub mod block_header; -pub mod chain_metadata; -pub mod com_and_pub_signature; -pub mod commitment_signature; -pub mod consensus_constants; -pub mod historical_block; -pub mod new_block_template; -pub mod output_features; -pub mod peer; -pub mod proof_of_work; -pub mod sidechain_feature; -pub mod signature; -pub mod transaction; -pub mod transaction_input; -pub mod transaction_kernel; -pub mod transaction_output; -pub mod unblinded_output; -pub mod validator_node_change; -pub mod validator_node_registration; +mod aggregate_body; +mod base_node_state; +mod block; +mod block_header; +mod chain_metadata; +mod com_and_pub_signature; +mod commitment_signature; +mod consensus_constants; +mod historical_block; +mod new_block_template; +mod output_features; +mod peer; +mod proof_of_work; +mod sidechain_feature; +mod signature; +mod transaction; +mod transaction_input; +mod transaction_kernel; +mod transaction_output; +mod unblinded_output; use prost_types::Timestamp; @@ -61,12 +59,6 @@ impl From for grpc::IntegerValue { } } -impl From for grpc::StringValue { - fn from(value: String) -> Self { - Self { value } - } -} - impl From for grpc::HeightRequest { fn from(b: BlockGroupRequest) -> Self { Self { diff --git a/applications/minotari_app_grpc/src/conversions/output_features.rs b/applications/minotari_app_grpc/src/conversions/output_features.rs index e7927a1626..99991df387 100644 --- a/applications/minotari_app_grpc/src/conversions/output_features.rs +++ b/applications/minotari_app_grpc/src/conversions/output_features.rs @@ -38,11 +38,7 @@ impl TryFrom for OutputFeatures { type Error = String; fn try_from(features: grpc::OutputFeatures) -> Result { - let sidechain_feature = features - .sidechain_feature - .and_then(|f| f.side_chain_feature) - .map(SideChainFeature::try_from) - .transpose()?; + let sidechain_feature = features.sidechain_feature.map(SideChainFeature::try_from).transpose()?; let output_type = features .output_type diff --git a/applications/minotari_app_grpc/src/conversions/sidechain_feature.rs b/applications/minotari_app_grpc/src/conversions/sidechain_feature.rs index 1538782020..133e8915f8 100644 --- a/applications/minotari_app_grpc/src/conversions/sidechain_feature.rs +++ b/applications/minotari_app_grpc/src/conversions/sidechain_feature.rs @@ -22,17 +22,42 @@ use std::convert::{TryFrom, TryInto}; -use tari_common_types::types::{PublicKey, Signature}; -use tari_core::transactions::transaction_components::{ - BuildInfo, - CodeTemplateRegistration, - ConfidentialOutputData, - SideChainFeature, - TemplateType, - ValidatorNodeRegistration, - ValidatorNodeSignature, +use prost::Message; +use tari_common_types::{ + epoch::VnEpoch, + types::{PublicKey, Signature}, +}; +use tari_core::{ + base_node::comms_interface::{ValidatorNodeChange, ValidatorNodeChangeState}, + transactions::{ + tari_amount::MicroMinotari, + transaction_components::{ + BuildInfo, + CodeTemplateRegistration, + ConfidentialOutputData, + SideChainFeature, + SideChainFeatureData, + SideChainId, + TemplateType, + ValidatorNodeRegistration, + ValidatorNodeSignature, + }, + }, }; use tari_max_size::MaxSizeString; +use tari_sidechain::{ + ChainLink, + CommandCommitProof, + CommandCommitProofV1, + CommitProofElement, + EvictNodeAtom, + EvictionProof, + QuorumCertificate, + QuorumDecision, + SidechainBlockCommitProof, + SidechainBlockHeader, + ValidatorQcSignature, +}; use tari_utilities::ByteArray; use crate::tari_rpc as grpc; @@ -41,45 +66,90 @@ use crate::tari_rpc as grpc; impl From for grpc::SideChainFeature { fn from(value: SideChainFeature) -> Self { Self { - side_chain_feature: Some(value.into()), + feature: Some(value.data.into()), + sidechain_id: value.sidechain_id.as_ref().map(Into::into), } } } -impl From for grpc::side_chain_feature::SideChainFeature { - fn from(value: SideChainFeature) -> Self { +impl TryFrom for SideChainFeature { + type Error = String; + + fn try_from(value: grpc::SideChainFeature) -> Result { + Ok(Self { + data: value.feature.ok_or("Feature not provided")?.try_into()?, + sidechain_id: value.sidechain_id.map(TryInto::try_into).transpose()?, + }) + } +} + +impl From for grpc::side_chain_feature::Feature { + fn from(value: SideChainFeatureData) -> Self { match value { - SideChainFeature::ValidatorNodeRegistration(template_reg) => { - grpc::side_chain_feature::SideChainFeature::ValidatorNodeRegistration(template_reg.into()) + SideChainFeatureData::ValidatorNodeRegistration(template_reg) => { + grpc::side_chain_feature::Feature::ValidatorNodeRegistration(template_reg.into()) }, - SideChainFeature::CodeTemplateRegistration(template_reg) => { - grpc::side_chain_feature::SideChainFeature::TemplateRegistration(template_reg.into()) + SideChainFeatureData::CodeTemplateRegistration(template_reg) => { + grpc::side_chain_feature::Feature::TemplateRegistration(template_reg.into()) }, - SideChainFeature::ConfidentialOutput(output_data) => { - grpc::side_chain_feature::SideChainFeature::ConfidentialOutput(output_data.into()) + SideChainFeatureData::ConfidentialOutput(output_data) => { + grpc::side_chain_feature::Feature::ConfidentialOutput(output_data.into()) + }, + SideChainFeatureData::EvictionProof(proof) => { + grpc::side_chain_feature::Feature::EvictionProof(grpc::EvictionProof::from(&proof)) }, } } } -impl TryFrom for SideChainFeature { +impl TryFrom for SideChainFeatureData { type Error = String; - fn try_from(features: grpc::side_chain_feature::SideChainFeature) -> Result { + fn try_from(features: grpc::side_chain_feature::Feature) -> Result { match features { - grpc::side_chain_feature::SideChainFeature::ValidatorNodeRegistration(vn_reg) => { - Ok(SideChainFeature::ValidatorNodeRegistration(vn_reg.try_into()?)) + grpc::side_chain_feature::Feature::ValidatorNodeRegistration(vn_reg) => { + Ok(SideChainFeatureData::ValidatorNodeRegistration(vn_reg.try_into()?)) + }, + grpc::side_chain_feature::Feature::TemplateRegistration(template_reg) => { + Ok(SideChainFeatureData::CodeTemplateRegistration(template_reg.try_into()?)) }, - grpc::side_chain_feature::SideChainFeature::TemplateRegistration(template_reg) => { - Ok(SideChainFeature::CodeTemplateRegistration(template_reg.try_into()?)) + grpc::side_chain_feature::Feature::ConfidentialOutput(output_data) => { + Ok(SideChainFeatureData::ConfidentialOutput(output_data.try_into()?)) }, - grpc::side_chain_feature::SideChainFeature::ConfidentialOutput(output_data) => { - Ok(SideChainFeature::ConfidentialOutput(output_data.try_into()?)) + grpc::side_chain_feature::Feature::EvictionProof(proof) => { + Ok(SideChainFeatureData::EvictionProof(proof.try_into()?)) }, } } } +// -------------------------------- SideChainId -------------------------------- // + +impl TryFrom for SideChainId { + type Error = String; + + fn try_from(value: grpc::SideChainId) -> Result { + let public_key = + PublicKey::from_canonical_bytes(&value.public_key).map_err(|e| format!("sidechain_id: {}", e))?; + let knowledge_proof = value + .knowledge_proof + .ok_or("sidechain_id knowledge_proof not provided")?; + let knowledge_proof = + Signature::try_from(knowledge_proof).map_err(|e| format!("sidechain_id_knowledge_proof: {}", e))?; + + Ok(Self::new(public_key, knowledge_proof)) + } +} + +impl From<&SideChainId> for grpc::SideChainId { + fn from(value: &SideChainId) -> Self { + Self { + public_key: value.public_key().to_vec(), + knowledge_proof: Some(value.knowledge_proof().into()), + } + } +} + // -------------------------------- ValidatorNodeRegistration -------------------------------- // impl TryFrom for ValidatorNodeRegistration { type Error = String; @@ -90,16 +160,6 @@ impl TryFrom for ValidatorNodeRegistration { let claim_public_key = PublicKey::from_canonical_bytes(&value.claim_public_key) .map_err(|e| format!("Invalid claim public key: {}", e))?; - let sidechain_id = if value.sidechain_id.is_empty() { - None - } else { - Some(PublicKey::from_canonical_bytes(&value.sidechain_id).map_err(|e| format!("sidechain_id: {}", e))?) - }; - let sidechain_id_knowledge_proof = value - .sidechain_id_knowledge_proof - .map(|v| Signature::try_from(v).map_err(|e| format!("sidechain_id_knowledge_proof: {}", e))) - .transpose()?; - Ok(ValidatorNodeRegistration::new( ValidatorNodeSignature::new( public_key, @@ -109,21 +169,25 @@ impl TryFrom for ValidatorNodeRegistration { .ok_or("signature not provided")??, ), claim_public_key, - sidechain_id, - sidechain_id_knowledge_proof, )) } } +impl From<&ValidatorNodeRegistration> for crate::tari_rpc::ValidatorNodeRegistration { + fn from(registration: &ValidatorNodeRegistration) -> Self { + Self { + public_key: registration.public_key().to_vec(), + signature: Some(crate::tari_rpc::Signature { + public_nonce: registration.signature().get_public_nonce().to_vec(), + signature: registration.signature().get_signature().to_vec(), + }), + claim_public_key: registration.claim_public_key().to_vec(), + } + } +} impl From for grpc::ValidatorNodeRegistration { fn from(value: ValidatorNodeRegistration) -> Self { - Self { - public_key: value.public_key().to_vec(), - signature: Some(value.signature().into()), - claim_public_key: value.claim_public_key().to_vec(), - sidechain_id: value.sidechain_id().map(|v| v.to_vec()).unwrap_or_default(), - sidechain_id_knowledge_proof: value.sidechain_id_knowledge_proof().map(|v| v.into()), - } + Self::from(&value) } } @@ -132,15 +196,6 @@ impl TryFrom for CodeTemplateRegistration { type Error = String; fn try_from(value: grpc::TemplateRegistration) -> Result { - let sidechain_id = if value.sidechain_id.is_empty() { - None - } else { - Some(PublicKey::from_canonical_bytes(&value.sidechain_id).map_err(|e| format!("sidechain_id: {}", e))?) - }; - let sidechain_id_knowledge_proof = value - .sidechain_id_knowledge_proof - .map(|v| Signature::try_from(v).map_err(|e| format!("sidechain_id_knowledge_proof: {}", e))) - .transpose()?; Ok(Self { author_public_key: PublicKey::from_canonical_bytes(&value.author_public_key).map_err(|e| e.to_string())?, author_signature: value @@ -162,8 +217,6 @@ impl TryFrom for CodeTemplateRegistration { .ok_or("Build info not provided")??, binary_sha: value.binary_sha.try_into().map_err(|_| "Invalid commit sha")?, binary_url: MaxSizeString::try_from(value.binary_url).map_err(|e| e.to_string())?, - sidechain_id, - sidechain_id_knowledge_proof, }) } } @@ -179,8 +232,6 @@ impl From for grpc::TemplateRegistration { build_info: Some(value.build_info.into()), binary_sha: value.binary_sha.to_vec(), binary_url: value.binary_url.to_string(), - sidechain_id: value.sidechain_id.map(|v| v.to_vec()).unwrap_or_default(), - sidechain_id_knowledge_proof: value.sidechain_id_knowledge_proof.map(|v| v.into()), } } } @@ -190,19 +241,8 @@ impl TryFrom for ConfidentialOutputData { type Error = String; fn try_from(value: grpc::ConfidentialOutputData) -> Result { - let sidechain_id = if value.sidechain_id.is_empty() { - None - } else { - Some(PublicKey::from_canonical_bytes(&value.sidechain_id).map_err(|e| format!("sidechain_id: {}", e))?) - }; - let sidechain_id_knowledge_proof = value - .sidechain_id_knowledge_proof - .map(|v| Signature::try_from(v).map_err(|e| format!("sidechain_id_knowledge_proof: {}", e))) - .transpose()?; Ok(ConfidentialOutputData { claim_public_key: PublicKey::from_canonical_bytes(&value.claim_public_key).map_err(|e| e.to_string())?, - sidechain_id, - sidechain_id_knowledge_proof, }) } } @@ -211,8 +251,6 @@ impl From for grpc::ConfidentialOutputData { fn from(value: ConfidentialOutputData) -> Self { Self { claim_public_key: value.claim_public_key.to_vec(), - sidechain_id: value.sidechain_id.map(|v| v.to_vec()).unwrap_or_default(), - sidechain_id_knowledge_proof: value.sidechain_id_knowledge_proof.map(|v| v.into()), } } } @@ -272,3 +310,357 @@ impl From for grpc::BuildInfo { } } } + +// -------------------------------- EvictionProof -------------------------------- // + +impl TryFrom for EvictionProof { + type Error = String; + + fn try_from(value: grpc::EvictionProof) -> Result { + let proof = value.proof.ok_or("proof not provided")?.try_into()?; + Ok(EvictionProof::new(proof)) + } +} + +impl From<&EvictionProof> for grpc::EvictionProof { + fn from(value: &EvictionProof) -> Self { + Self { + proof: Some(value.proof().into()), + } + } +} + +// -------------------------------- Commit proof -------------------------------- // + +impl TryFrom for CommandCommitProof { + type Error = String; + + fn try_from(value: grpc::CommitProof) -> Result { + match value.version.ok_or("version not provided")? { + grpc::commit_proof::Version::V1(v1) => Ok(Self::V1(v1.try_into()?)), + } + } +} + +impl From<&CommandCommitProof> for grpc::CommitProof { + fn from(value: &CommandCommitProof) -> Self { + match value { + CommandCommitProof::V1(v1) => Self { + version: Some(grpc::commit_proof::Version::V1(v1.into())), + }, + } + } +} + +impl TryFrom for CommandCommitProofV1 { + type Error = String; + + fn try_from(value: grpc::CommitProofV1) -> Result { + let command = grpc::EvictAtom::decode(value.command.as_slice()).map_err(|e| e.to_string())?; + Ok(CommandCommitProofV1 { + command: command.try_into()?, + commit_proof: value.commit_proof.ok_or("commit_proof not provided")?.try_into()?, + }) + } +} + +impl From<&CommandCommitProofV1> for grpc::CommitProofV1 { + fn from(value: &CommandCommitProofV1) -> Self { + Self { + command: grpc::EvictAtom::from(value.command()).encode_to_vec(), + commit_proof: Some(value.commit_proof().into()), + } + } +} + +// -------------------------------- SidechainBlockCommitProof -------------------------------- // + +impl TryFrom for SidechainBlockCommitProof { + type Error = String; + + fn try_from(value: grpc::SidechainBlockCommitProof) -> Result { + Ok(Self { + header: value.header.ok_or("header not provided")?.try_into()?, + proof_elements: value + .proof_elements + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + }) + } +} + +impl From<&SidechainBlockCommitProof> for grpc::SidechainBlockCommitProof { + fn from(value: &SidechainBlockCommitProof) -> Self { + Self { + header: Some(value.header().into()), + proof_elements: value.proof_elements().iter().map(Into::into).collect(), + } + } +} + +// -------------------------------- SidechainBlockHeader -------------------------------- // + +impl TryFrom for SidechainBlockHeader { + type Error = String; + + fn try_from(value: grpc::SidechainBlockHeader) -> Result { + let network_byte = u8::try_from(value.network).map_err(|_| "Invalid network byte: overflows u8".to_string())?; + Ok(Self { + network: network_byte, + parent_id: value.parent_id.try_into().map_err(|_| "Invalid parent id")?, + justify_id: value.justify_id.try_into().map_err(|_| "Invalid justify id")?, + height: value.height, + epoch: value.epoch, + shard_group: value.shard_group, + proposed_by: PublicKey::from_canonical_bytes(&value.proposed_by) + .map_err(|_| "Invalid proposed_by public key")?, + total_leader_fee: value.total_leader_fee, + state_merkle_root: value + .state_merkle_root + .try_into() + .map_err(|_| "Invalid state merkle root")?, + command_merkle_root: value + .command_merkle_root + .try_into() + .map_err(|_| "Invalid command merkle root")?, + is_dummy: value.is_dummy, + foreign_indexes_hash: value + .foreign_indexes_hash + .try_into() + .map_err(|_| "Invalid foreign indexes hash")?, + signature: value + .signature + .ok_or("SidechainBlockHeader signature not provided")? + .try_into() + .map_err(|_| "Invalid signature")?, + timestamp: value.timestamp, + base_layer_block_height: value.base_layer_block_height, + base_layer_block_hash: value + .base_layer_block_hash + .try_into() + .map_err(|_| "Invalid base layer block hash")?, + extra_data_hash: value + .extra_data_hash + .try_into() + .map_err(|_| "Invalid extra data hash")?, + }) + } +} + +impl From<&SidechainBlockHeader> for grpc::SidechainBlockHeader { + fn from(value: &SidechainBlockHeader) -> Self { + Self { + network: u32::from(value.network), + parent_id: value.parent_id.to_vec(), + justify_id: value.justify_id.to_vec(), + height: value.height, + epoch: value.epoch, + shard_group: value.shard_group, + proposed_by: value.proposed_by.to_vec(), + total_leader_fee: value.total_leader_fee, + state_merkle_root: value.state_merkle_root.to_vec(), + command_merkle_root: value.command_merkle_root.to_vec(), + is_dummy: value.is_dummy, + foreign_indexes_hash: value.foreign_indexes_hash.to_vec(), + signature: Some(value.signature().into()), + timestamp: value.timestamp, + base_layer_block_height: value.base_layer_block_height, + base_layer_block_hash: value.base_layer_block_hash.to_vec(), + extra_data_hash: value.extra_data_hash.to_vec(), + } + } +} + +// -------------------------------- CommitProofElement -------------------------------- // + +impl TryFrom for CommitProofElement { + type Error = String; + + fn try_from(value: grpc::CommitProofElement) -> Result { + match value.proof_element.ok_or("proof element not provided")? { + grpc::commit_proof_element::ProofElement::QuorumCertificate(qc) => { + Ok(CommitProofElement::QuorumCertificate(qc.try_into()?)) + }, + grpc::commit_proof_element::ProofElement::DummyChain(chain) => Ok(CommitProofElement::DummyChain( + chain + .chain_links + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + )), + } + } +} + +impl From<&CommitProofElement> for grpc::CommitProofElement { + fn from(value: &CommitProofElement) -> Self { + match value { + CommitProofElement::QuorumCertificate(qc) => Self { + proof_element: Some(grpc::commit_proof_element::ProofElement::QuorumCertificate(qc.into())), + }, + CommitProofElement::DummyChain(chain) => Self { + proof_element: Some(grpc::commit_proof_element::ProofElement::DummyChain(grpc::DummyChain { + chain_links: chain.iter().map(Into::into).collect(), + })), + }, + } + } +} + +// -------------------------------- ChainLink -------------------------------- // + +impl TryFrom for ChainLink { + type Error = String; + + fn try_from(value: grpc::ChainLink) -> Result { + Ok(Self { + header_contents_hash: value.header_hash.try_into().map_err(|_| "Invalid block id")?, + parent_id: value.parent_id.try_into().map_err(|_| "Invalid parent id")?, + }) + } +} + +impl From<&ChainLink> for grpc::ChainLink { + fn from(value: &ChainLink) -> Self { + Self { + header_hash: value.header_contents_hash.to_vec(), + parent_id: value.parent_id.to_vec(), + } + } +} + +// -------------------------------- QuorumCertificate -------------------------------- // + +impl TryFrom for QuorumCertificate { + type Error = String; + + fn try_from(value: grpc::QuorumCertificate) -> Result { + Ok(Self { + header_hash: value.header_hash.try_into().map_err(|_| "Invalid block body hash")?, + parent_id: value.parent_id.try_into().map_err(|_| "Invalid parent id")?, + signatures: value + .signatures + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + decision: grpc::QuorumDecision::try_from(value.decision) + .map_err(|e| format!("Invalid QuorumDecision: {e}"))? + .into(), + }) + } +} + +impl From<&QuorumCertificate> for grpc::QuorumCertificate { + fn from(value: &QuorumCertificate) -> Self { + Self { + parent_id: value.parent_id.to_vec(), + header_hash: value.header_hash.to_vec(), + signatures: value.signatures.iter().map(Into::into).collect(), + decision: grpc::QuorumDecision::from(value.decision).into(), + } + } +} + +// -------------------------------- QuorumDecision -------------------------------- // + +impl From for QuorumDecision { + fn from(value: grpc::QuorumDecision) -> Self { + match value { + grpc::QuorumDecision::Accept => QuorumDecision::Accept, + grpc::QuorumDecision::Reject => QuorumDecision::Reject, + } + } +} + +impl From for grpc::QuorumDecision { + fn from(value: QuorumDecision) -> Self { + match value { + QuorumDecision::Accept => grpc::QuorumDecision::Accept, + QuorumDecision::Reject => grpc::QuorumDecision::Reject, + } + } +} + +// -------------------------------- ValidatorSignature -------------------------------- // + +impl TryFrom for ValidatorQcSignature { + type Error = String; + + fn try_from(value: grpc::ValidatorSignature) -> Result { + Ok(Self { + public_key: PublicKey::from_canonical_bytes(&value.public_key).map_err(|e| e.to_string())?, + signature: value.signature.ok_or("signature not provided")?.try_into()?, + }) + } +} + +impl From<&ValidatorQcSignature> for grpc::ValidatorSignature { + fn from(value: &ValidatorQcSignature) -> Self { + Self { + public_key: value.public_key().to_vec(), + signature: Some(value.signature().into()), + } + } +} + +// -------------------------------- EvictNodeAtom -------------------------------- // + +impl TryFrom for EvictNodeAtom { + type Error = String; + + fn try_from(value: grpc::EvictAtom) -> Result { + Ok(Self::new( + PublicKey::from_canonical_bytes(&value.public_key).map_err(|e| e.to_string())?, + )) + } +} + +impl From<&EvictNodeAtom> for grpc::EvictAtom { + fn from(value: &EvictNodeAtom) -> Self { + Self { + public_key: value.node_to_evict().to_vec(), + } + } +} + +// -------------------------------- ValidatorNodeChange -------------------------------- // + +impl TryFrom for ValidatorNodeChange { + type Error = String; + + fn try_from(value: grpc::ValidatorNodeChange) -> Result { + let public_key = PublicKey::from_canonical_bytes(&value.public_key).map_err(|e| e.to_string())?; + let state = grpc::ValidatorNodeChangeState::try_from(value.state).map_err(|e| e.to_string())?; + let state = match state { + grpc::ValidatorNodeChangeState::Add => ValidatorNodeChangeState::Add, + grpc::ValidatorNodeChangeState::Remove => ValidatorNodeChangeState::Remove, + }; + let activation_epoch = VnEpoch(value.activation_epoch); + let registration = value.registration.ok_or("registration not provided")?.try_into()?; + let minimum_value_promise = MicroMinotari(value.minimum_value_promise); + + Ok(Self { + public_key, + state, + activation_epoch, + registration, + minimum_value_promise, + }) + } +} + +impl From<&ValidatorNodeChange> for grpc::ValidatorNodeChange { + fn from(node_change: &ValidatorNodeChange) -> Self { + Self { + public_key: node_change.public_key.to_vec(), + state: match node_change.state { + ValidatorNodeChangeState::Add => grpc::ValidatorNodeChangeState::Add.into(), + ValidatorNodeChangeState::Remove => grpc::ValidatorNodeChangeState::Remove.into(), + }, + activation_epoch: node_change.activation_epoch.as_u64(), + registration: Some((&node_change.registration).into()), + minimum_value_promise: node_change.minimum_value_promise.into(), + } + } +} diff --git a/applications/minotari_app_grpc/src/conversions/signature.rs b/applications/minotari_app_grpc/src/conversions/signature.rs index c3e5e8c2c5..a0f0fa93ca 100644 --- a/applications/minotari_app_grpc/src/conversions/signature.rs +++ b/applications/minotari_app_grpc/src/conversions/signature.rs @@ -20,31 +20,37 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{borrow::Borrow, convert::TryFrom}; +use std::convert::TryFrom; -use tari_common_types::types::{PrivateKey, PublicKey, Signature}; +use tari_common_types::types::{PrivateKey, PublicKey}; +use tari_crypto::{hashing::DomainSeparation, signatures::SchnorrSignature}; use tari_utilities::ByteArray; use crate::tari_rpc as grpc; -impl TryFrom for Signature { +impl TryFrom for SchnorrSignature { type Error = String; fn try_from(sig: grpc::Signature) -> Result { - let public_nonce = - PublicKey::from_canonical_bytes(&sig.public_nonce).map_err(|_| "Could not get public nonce".to_string())?; - let signature = - PrivateKey::from_canonical_bytes(&sig.signature).map_err(|_| "Could not get signature".to_string())?; + let public_nonce = PublicKey::from_canonical_bytes(&sig.public_nonce).map_err(|e| e.to_string())?; + let signature = PrivateKey::from_canonical_bytes(&sig.signature).map_err(|e| e.to_string())?; Ok(Self::new(public_nonce, signature)) } } - -impl> From for grpc::Signature { - fn from(sig: T) -> Self { +impl From<&SchnorrSignature> for grpc::Signature { + fn from(sig: &SchnorrSignature) -> Self { + Self { + public_nonce: sig.get_public_nonce().to_vec(), + signature: sig.get_signature().to_vec(), + } + } +} +impl From> for grpc::Signature { + fn from(sig: SchnorrSignature) -> Self { Self { - public_nonce: sig.borrow().get_public_nonce().to_vec(), - signature: sig.borrow().get_signature().to_vec(), + public_nonce: sig.get_public_nonce().to_vec(), + signature: sig.get_signature().to_vec(), } } } diff --git a/applications/minotari_app_grpc/src/conversions/validator_node_change.rs b/applications/minotari_app_grpc/src/conversions/validator_node_change.rs deleted file mode 100644 index f083e5bc3f..0000000000 --- a/applications/minotari_app_grpc/src/conversions/validator_node_change.rs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2024 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -use tari_core::base_node::comms_interface::{ValidatorNodeChange, ValidatorNodeChangeState}; -use tari_utilities::ByteArray; - -impl From<&ValidatorNodeChange> for crate::tari_rpc::ValidatorNodeChange { - fn from(node_change: &ValidatorNodeChange) -> Self { - Self { - public_key: node_change.public_key.to_vec(), - state: match node_change.state { - ValidatorNodeChangeState::ADD => crate::tari_rpc::ValidatorNodeChangeState::Add.into(), - ValidatorNodeChangeState::REMOVE => crate::tari_rpc::ValidatorNodeChangeState::Remove.into(), - }, - start_height: node_change.height, - registration: Some((&node_change.registration).into()), - minimum_value_promise: node_change.minimum_value_promise.into(), - } - } -} diff --git a/applications/minotari_app_grpc/src/conversions/validator_node_registration.rs b/applications/minotari_app_grpc/src/conversions/validator_node_registration.rs deleted file mode 100644 index 69c9e71dfa..0000000000 --- a/applications/minotari_app_grpc/src/conversions/validator_node_registration.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2024 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -use tari_core::transactions::transaction_components::ValidatorNodeRegistration; -use tari_utilities::ByteArray; - -impl From<&ValidatorNodeRegistration> for crate::tari_rpc::ValidatorNodeRegistration { - fn from(registration: &ValidatorNodeRegistration) -> Self { - Self { - public_key: registration.public_key().to_vec(), - signature: Some(crate::tari_rpc::Signature { - public_nonce: registration.signature().get_public_nonce().to_vec(), - signature: registration.signature().get_signature().to_vec(), - }), - claim_public_key: registration.claim_public_key().to_vec(), - sidechain_id: match registration.sidechain_id() { - None => vec![], - Some(id) => id.to_vec(), - }, - sidechain_id_knowledge_proof: registration.sidechain_id_knowledge_proof().map(|signature| { - crate::tari_rpc::Signature { - public_nonce: signature.get_public_nonce().to_vec(), - signature: signature.get_signature().to_vec(), - } - }), - } - } -} diff --git a/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs b/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs index 197c6aff98..80f25768e4 100644 --- a/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs +++ b/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs @@ -70,6 +70,8 @@ use minotari_app_grpc::tari_rpc::{ SendShaAtomicSwapResponse, SetBaseNodeRequest, SetBaseNodeResponse, + SubmitValidatorEvictionProofRequest, + SubmitValidatorEvictionProofResponse, TransactionDirection, TransactionEvent, TransactionEventRequest, @@ -1075,6 +1077,52 @@ impl wallet_server::Wallet for WalletGrpcServer { }; Ok(Response::new(response)) } + + async fn submit_validator_eviction_proof( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + let mut transaction_service = self.get_transaction_service(); + + let sidechain_key = Some(request.sidechain_deployment_key) + .filter(|k| !k.is_empty()) + .map(|k| PrivateKey::from_canonical_bytes(&k)) + .transpose() + .map_err(|_| Status::invalid_argument("sidechain_deployment_key is malformed"))?; + + let proof = request + .proof + .map(TryInto::try_into) + .ok_or_else(|| Status::invalid_argument("Proof is missing"))? + .map_err(|e| { + error!(target: LOG_TARGET, "Failed to convert proof: {}", e); + Status::invalid_argument(format!("Invalid proof: {e}")) + })?; + + let constants = self.get_consensus_constants().map_err(|e| { + error!(target: LOG_TARGET, "Failed to get consensus constants: {}", e); + Status::internal("failed to fetch consensus constants") + })?; + + let response = match transaction_service + .submit_validator_eviction_proof( + constants.validator_node_registration_min_deposit_amount(), + proof, + request.fee_per_gram.into(), + sidechain_key, + request.message, + ) + .await + { + Ok(tx) => SubmitValidatorEvictionProofResponse { tx_id: tx.as_u64() }, + Err(e) => { + error!(target: LOG_TARGET, "Transaction service error: {}", e); + return Err(Status::unknown(e.to_string())); + }, + }; + Ok(Response::new(response)) + } } async fn handle_completed_tx( diff --git a/applications/minotari_node/src/grpc/base_node_grpc_server.rs b/applications/minotari_node/src/grpc/base_node_grpc_server.rs index 2594d021c4..15661f6a88 100644 --- a/applications/minotari_node/src/grpc/base_node_grpc_server.rs +++ b/applications/minotari_node/src/grpc/base_node_grpc_server.rs @@ -35,6 +35,7 @@ use minotari_app_grpc::{ tari_rpc::{CalcType, GetValidatorNodeChangesRequest, GetValidatorNodeChangesResponse, Sorting}, }; use tari_common_types::{ + epoch::VnEpoch, key_branches::TransactionKeyManagerBranch, tari_address::TariAddress, types::{Commitment, FixedHash, PublicKey, Signature}, @@ -1944,9 +1945,16 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { .await } - async fn get_version(&self, _request: Request) -> Result, Status> { + async fn get_version( + &self, + _request: Request, + ) -> Result, Status> { self.check_method_enabled(GrpcMethod::GetVersion)?; - Ok(Response::new(env!("CARGO_PKG_VERSION").to_string().into())) + let resp = tari_rpc::BaseNodeGetVersionResponse { + version: env!("CARGO_PKG_VERSION").to_string(), + network: u32::from(self.network.as_network().as_byte()), + }; + Ok(Response::new(resp)) } async fn check_for_updates( @@ -2266,21 +2274,26 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { let public_key = PublicKey::from_canonical_bytes(&request.public_key) .map_err(|e| obscure_error_if_true(report_error_flag, Status::invalid_argument(e.to_string())))?; - let shard_key = handler.get_shard_key(request.height, public_key).await.map_err(|e| { - error!(target: LOG_TARGET, "Error {}", e); - obscure_error_if_true(report_error_flag, Status::internal(e.to_string())) - })?; - if let Some(shard_key) = shard_key { - Ok(Response::new(tari_rpc::GetShardKeyResponse { - shard_key: shard_key.to_vec(), - found: true, - })) - } else { - Ok(Response::new(tari_rpc::GetShardKeyResponse { - shard_key: vec![], - found: false, - })) + let epoch = request.epoch; + + let validator_node = handler + .get_validator_node(None, public_key) + .await + .map_err(|e| { + error!(target: LOG_TARGET, "Error {}", e); + obscure_error_if_true(report_error_flag, Status::internal(e.to_string())) + })? + .ok_or_else(|| Status::not_found("Validator node not found"))?; + + if validator_node.activation_epoch.as_u64() > epoch { + return Err(Status::not_found(format!( + "Validator node found but not active for epoch {epoch}" + ))); } + + Ok(Response::new(tari_rpc::GetShardKeyResponse { + shard_key: validator_node.shard_key.to_vec(), + })) } async fn get_active_validator_nodes( @@ -2438,12 +2451,7 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { .filter(|x| !x.is_empty()) .map(FixedHash::try_from) .transpose() - .map_err(|e| { - obscure_error_if_true( - report_error_flag, - Status::invalid_argument(format!("Invalid start_hash '{}'", e)), - ) - })?; + .map_err(|e| Status::invalid_argument(format!("Invalid start_hash '{}'", e)))?; let mut node_service = self.node_service.clone(); @@ -2452,14 +2460,12 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { .get_header_by_hash(hash) .await .map_err(|err| obscure_error_if_true(self.report_grpc_error, Status::internal(err.to_string())))? - .ok_or_else(|| obscure_error_if_true(report_error_flag, Status::not_found("Start hash not found")))?, + .ok_or_else(|| Status::not_found("Start hash not found"))?, None => node_service .get_header(0) .await .map_err(|err| obscure_error_if_true(self.report_grpc_error, Status::internal(err.to_string())))? - .ok_or_else(|| { - obscure_error_if_true(report_error_flag, Status::unavailable("Genesis block not available")) - })?, + .ok_or_else(|| Status::unavailable("Genesis block not available"))?, }; if request.count == 0 { @@ -2467,12 +2473,9 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { } let start_height = start_header.height(); - let end_height = start_height.checked_add(request.count - 1).ok_or_else(|| { - obscure_error_if_true( - report_error_flag, - Status::invalid_argument("Request start height + count overflows u64"), - ) - })?; + let end_height = start_height + .checked_add(request.count - 1) + .ok_or_else(|| Status::invalid_argument("Request start height + count overflows u64"))?; task::spawn(async move { let mut current_header = start_header; @@ -2500,7 +2503,7 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { let sidechain_outputs = utxos .into_iter() - .filter(|u| u.features.output_type.is_sidechain_type()) + .filter(|u| u.features.output_type.is_sidechain_type() || u.is_burned_to_sidechain()) .map(TryInto::try_into) .collect::, _>>(); @@ -2563,17 +2566,16 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { let mut handler = self.node_service.clone(); - let sidechain_id = if request.sidechain_id.is_empty() { - None - } else { - Some( - PublicKey::from_canonical_bytes(&request.sidechain_id) - .map_err(|e| Status::invalid_argument(format!("Invalid sidechain_id '{}'", e)))?, - ) - }; + let sidechain_id = Some(request.sidechain_id) + .filter(|id| !id.is_empty()) + .map(|id| { + PublicKey::from_canonical_bytes(&id) + .map_err(|e| Status::invalid_argument(format!("Invalid sidechain_id '{}'", e))) + }) + .transpose()?; let changes = handler - .get_validator_node_changes(request.start_height, request.end_height, sidechain_id) + .get_validator_node_changes(sidechain_id, VnEpoch(request.epoch)) .await .map_err(|error| { warn!(target: LOG_TARGET, "Base node service error: {}", error); diff --git a/base_layer/common_types/Cargo.toml b/base_layer/common_types/Cargo.toml index c7fa39724c..02af2b8b95 100644 --- a/base_layer/common_types/Cargo.toml +++ b/base_layer/common_types/Cargo.toml @@ -11,6 +11,8 @@ tari_crypto = { version = "0.21.0" } tari_utilities = { version = "0.8" } tari_common = { path = "../../common", version = "1.7.0-pre.3" } minotari_ledger_wallet_common = { path = "../../applications/minotari_ledger_wallet/common" } +tari_hashing = { path = "../../hashing", version = "1.7.0-pre.3" } + chacha20poly1305 = "0.10.1" bitflags = { version = "2.4", features = ["serde"] } borsh = "1.5" diff --git a/base_layer/common_types/src/epoch.rs b/base_layer/common_types/src/epoch.rs index dbe84d3058..5c2d8c9de9 100644 --- a/base_layer/common_types/src/epoch.rs +++ b/base_layer/common_types/src/epoch.rs @@ -21,15 +21,34 @@ // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH // DAMAGE. -use std::str::FromStr; +use std::{fmt::Display, str::FromStr}; +use borsh::{BorshDeserialize, BorshSerialize}; use newtype_ops::newtype_ops; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + Default, + BorshSerialize, + BorshDeserialize, +)] pub struct VnEpoch(pub u64); impl VnEpoch { + pub const fn zero() -> Self { + VnEpoch(0) + } + pub fn as_u64(&self) -> u64 { self.0 } @@ -54,3 +73,9 @@ impl FromStr for VnEpoch { Ok(VnEpoch(s.parse::().map_err(|e| e.to_string())?)) } } + +impl Display for VnEpoch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Epoch({})", self.0) + } +} diff --git a/base_layer/common_types/src/types/fixed_hash.rs b/base_layer/common_types/src/types/fixed_hash.rs index 253c6358a9..dbb4930ab3 100644 --- a/base_layer/common_types/src/types/fixed_hash.rs +++ b/base_layer/common_types/src/types/fixed_hash.rs @@ -52,6 +52,7 @@ pub struct FixedHashSizeError; BorshSerialize, BorshDeserialize, )] +#[serde(transparent)] pub struct FixedHash([u8; FixedHash::byte_size()]); impl FixedHash { diff --git a/base_layer/core/Cargo.toml b/base_layer/core/Cargo.toml index acfe58d00f..edf0541fa7 100644 --- a/base_layer/core/Cargo.toml +++ b/base_layer/core/Cargo.toml @@ -49,6 +49,7 @@ tari_key_manager = { path = "../key_manager", features = [ ], version = "1.7.0-pre.3" } tari_common_sqlite = { path = "../../common_sqlite" } tari_hashing = { path = "../../hashing" } +tari_sidechain = { path = "../sidechain" } async-trait = { version = "0.1.50" } bincode = "1.1.4" diff --git a/base_layer/core/src/base_node/comms_interface/comms_request.rs b/base_layer/core/src/base_node/comms_interface/comms_request.rs index b7250fc472..154a8dcb6c 100644 --- a/base_layer/core/src/base_node/comms_interface/comms_request.rs +++ b/base_layer/core/src/base_node/comms_interface/comms_request.rs @@ -26,7 +26,10 @@ use std::{ }; use serde::{Deserialize, Serialize}; -use tari_common_types::types::{BlockHash, Commitment, HashOutput, PrivateKey, PublicKey, Signature}; +use tari_common_types::{ + epoch::VnEpoch, + types::{BlockHash, Commitment, HashOutput, PrivateKey, PublicKey, Signature}, +}; use tari_utilities::hex::Hex; use crate::{blocks::NewBlockTemplate, chain_storage::MmrTree, proof_of_work::PowAlgorithm}; @@ -40,7 +43,7 @@ pub struct MmrStateRequest { } /// API Request enum -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum NodeCommsRequest { GetChainMetadata, FetchHeaders(RangeInclusive), @@ -66,12 +69,11 @@ pub enum NodeCommsRequest { validator_network: Option, }, FetchValidatorNodeChanges { - start_height: u64, - end_height: u64, + epoch: VnEpoch, sidechain_id: Option, }, - GetShardKey { - height: u64, + GetValidatorNode { + sidechain_id: Option, public_key: PublicKey, }, FetchTemplateRegistrations { @@ -83,7 +85,7 @@ pub enum NodeCommsRequest { }, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct GetNewBlockTemplateRequest { pub algo: PowAlgorithm, pub max_weight: u64, @@ -133,8 +135,11 @@ impl Display for NodeCommsRequest { .unwrap_or_else(|| "None".to_string()) ) }, - GetShardKey { height, public_key } => { - write!(f, "GetShardKey height ({}), public key ({:?})", height, public_key) + GetValidatorNode { + sidechain_id, + public_key, + } => { + write!(f, "GetShardKey ({:?}), public key ({:?})", sidechain_id, public_key) }, FetchTemplateRegistrations { start_height: start, @@ -145,15 +150,11 @@ impl Display for NodeCommsRequest { FetchUnspentUtxosInBlock { block_hash } => { write!(f, "FetchUnspentUtxosInBlock ({})", block_hash) }, - FetchValidatorNodeChanges { - start_height, - end_height, - sidechain_id, - } => { + FetchValidatorNodeChanges { epoch, sidechain_id } => { write!( f, - "FetchValidatorNodeChanges (Side chain ID:{:?}), Height range: {}-{}", - sidechain_id, start_height, end_height + "FetchValidatorNodeChanges (Side chain ID:{:?}), Epoch: {}", + sidechain_id, epoch ) }, } diff --git a/base_layer/core/src/base_node/comms_interface/comms_response.rs b/base_layer/core/src/base_node/comms_interface/comms_response.rs index 3d55bdfc81..bfb7444239 100644 --- a/base_layer/core/src/base_node/comms_interface/comms_response.rs +++ b/base_layer/core/src/base_node/comms_interface/comms_response.rs @@ -27,6 +27,7 @@ use std::{ use tari_common_types::{ chain_metadata::ChainMetadata, + epoch::VnEpoch, types::{HashOutput, PrivateKey, PublicKey}, }; @@ -63,7 +64,7 @@ pub enum NodeCommsResponse { FetchMempoolTransactionsByExcessSigsResponse(FetchMempoolTransactionsResponse), FetchValidatorNodesKeysResponse(Vec), FetchValidatorNodeChangesResponse(Vec), - GetShardKeyResponse(Option<[u8; 32]>), + GetValidatorNode(Option), FetchTemplateRegistrationsResponse(Vec), } @@ -100,7 +101,7 @@ impl Display for NodeCommsResponse { resp.not_found.len() ), FetchValidatorNodesKeysResponse(_) => write!(f, "FetchValidatorNodesKeysResponse"), - GetShardKeyResponse(_) => write!(f, "GetShardKeyResponse"), + GetValidatorNode(_) => write!(f, "GetShardKeyResponse"), FetchTemplateRegistrationsResponse(_) => write!(f, "FetchTemplateRegistrationsResponse"), FetchValidatorNodeChangesResponse(_) => write!(f, "FetchValidatorNodeChangesResponse"), } @@ -117,8 +118,8 @@ pub struct FetchMempoolTransactionsResponse { /// Represents a validator node state #[derive(Debug, Clone)] pub enum ValidatorNodeChangeState { - ADD, - REMOVE, + Add, + Remove, } /// Represents a validator node state change @@ -127,6 +128,6 @@ pub struct ValidatorNodeChange { pub public_key: PublicKey, pub state: ValidatorNodeChangeState, pub registration: ValidatorNodeRegistration, + pub activation_epoch: VnEpoch, pub minimum_value_promise: MicroMinotari, - pub height: u64, } diff --git a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs index c514cf8546..646b0c0753 100644 --- a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs +++ b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs @@ -22,16 +22,11 @@ #[cfg(feature = "metrics")] use std::convert::{TryFrom, TryInto}; -use std::{ - cmp::max, - collections::{HashMap, HashSet}, - sync::Arc, - time::Instant, -}; +use std::{cmp::max, collections::HashSet, sync::Arc, time::Instant}; use log::*; use strum_macros::Display; -use tari_common_types::types::{BlockHash, FixedHash, HashOutput, PublicKey}; +use tari_common_types::types::{BlockHash, FixedHash, HashOutput}; use tari_comms::{connectivity::ConnectivityRequester, peer_manager::NodeId}; use tari_utilities::hex::Hex; use tokio::sync::RwLock; @@ -428,9 +423,12 @@ where B: BlockchainBackend + 'static active_validator_nodes, )) }, - NodeCommsRequest::GetShardKey { height, public_key } => { - let shard_key = self.blockchain_db.get_shard_key(height, public_key).await?; - Ok(NodeCommsResponse::GetShardKeyResponse(shard_key)) + NodeCommsRequest::GetValidatorNode { + sidechain_id, + public_key, + } => { + let vn = self.blockchain_db.get_validator_node(sidechain_id, public_key).await?; + Ok(NodeCommsResponse::GetValidatorNode(vn)) }, NodeCommsRequest::FetchTemplateRegistrations { start_height, @@ -448,71 +446,36 @@ where B: BlockchainBackend + 'static let utxos = self.blockchain_db.fetch_outputs_in_block(block_hash).await?; Ok(NodeCommsResponse::TransactionOutputs(utxos)) }, - NodeCommsRequest::FetchValidatorNodeChanges { - start_height, - end_height, - sidechain_id, - } => { - let constants = self.consensus_manager.consensus_constants(start_height); - #[allow(clippy::mutable_key_type)] - let mut node_changes = HashMap::::new(); - let mut nodes = self + NodeCommsRequest::FetchValidatorNodeChanges { epoch, sidechain_id } => { + let added_validators = self .blockchain_db - .fetch_active_validator_nodes(start_height, sidechain_id.clone()) + .fetch_validators_activating_in_epoch(sidechain_id.clone(), epoch) .await?; - for height in start_height + 1..=end_height { - let current_nodes = self - .blockchain_db - .fetch_active_validator_nodes(height, sidechain_id.clone()) - .await?; - - // remove nodes - nodes.iter().for_each(|prev_node| { - let prev_exists_in_new_set = current_nodes - .iter() - .any(|current_node| prev_node.public_key == current_node.public_key); - if !prev_exists_in_new_set { - node_changes.insert(prev_node.public_key.clone(), ValidatorNodeChange { - public_key: prev_node.public_key.clone(), - state: ValidatorNodeChangeState::REMOVE, - registration: prev_node.original_registration.clone(), - minimum_value_promise: prev_node.minimum_value_promise, - height: constants.epoch_to_block_height(prev_node.start_epoch), - }); - } - }); - // add nodes - current_nodes.iter().for_each(|current_node| { - let new_exists_in_prev = nodes - .iter() - .any(|prev_node| current_node.public_key == prev_node.public_key); - if !new_exists_in_prev { - node_changes.insert(current_node.public_key.clone(), ValidatorNodeChange { - public_key: current_node.public_key.clone(), - state: ValidatorNodeChangeState::ADD, - registration: current_node.original_registration.clone(), - minimum_value_promise: current_node.minimum_value_promise, - height: constants.epoch_to_block_height(current_node.start_epoch), - }); - } - }); - - nodes = current_nodes; - } + let exit_validators = self + .blockchain_db + .fetch_validators_exiting_in_epoch(sidechain_id.clone(), epoch) + .await?; - Ok(NodeCommsResponse::FetchValidatorNodeChangesResponse( - node_changes - .iter() - .map(|(pub_key, change)| ValidatorNodeChange { - public_key: pub_key.clone(), - state: change.state.clone(), - registration: change.registration.clone(), - minimum_value_promise: change.minimum_value_promise, - height: change.height, - }) - .collect(), - )) + let mut node_changes = Vec::with_capacity(added_validators.len() + exit_validators.len()); + + node_changes.extend(added_validators.into_iter().map(|vn| ValidatorNodeChange { + public_key: vn.public_key, + state: ValidatorNodeChangeState::Add, + registration: vn.original_registration, + activation_epoch: vn.activation_epoch, + minimum_value_promise: vn.minimum_value_promise, + })); + + node_changes.extend(exit_validators.into_iter().map(|vn| ValidatorNodeChange { + public_key: vn.public_key, + state: ValidatorNodeChangeState::Remove, + registration: vn.original_registration, + activation_epoch: vn.activation_epoch, + minimum_value_promise: vn.minimum_value_promise, + })); + + Ok(NodeCommsResponse::FetchValidatorNodeChangesResponse(node_changes)) }, } } @@ -984,22 +947,7 @@ where B: BlockchainBackend + 'static details: format!("Output {} to be spent does not exist in db", input.output_hash()), })?; - let rp_hash = match output_mined_info.output.proof { - Some(proof) => proof.hash(), - None => FixedHash::zero(), - }; - input.add_output_data( - output_mined_info.output.version, - output_mined_info.output.features, - output_mined_info.output.commitment, - output_mined_info.output.script, - output_mined_info.output.sender_offset_public_key, - output_mined_info.output.covenant, - output_mined_info.output.encrypted_data, - output_mined_info.output.metadata_signature, - rp_hash, - output_mined_info.output.minimum_value_promise, - ); + input.add_output_data(output_mined_info.output); } debug!( target: LOG_TARGET, diff --git a/base_layer/core/src/base_node/comms_interface/local_interface.rs b/base_layer/core/src/base_node/comms_interface/local_interface.rs index 5f8af2d2a7..e32330823c 100644 --- a/base_layer/core/src/base_node/comms_interface/local_interface.rs +++ b/base_layer/core/src/base_node/comms_interface/local_interface.rs @@ -24,6 +24,7 @@ use std::{ops::RangeInclusive, sync::Arc}; use tari_common_types::{ chain_metadata::ChainMetadata, + epoch::VnEpoch, types::{BlockHash, Commitment, HashOutput, PublicKey, Signature}, }; use tari_service_framework::{reply_channel::SenderService, Service}; @@ -302,17 +303,12 @@ impl LocalNodeCommsInterface { pub async fn get_validator_node_changes( &mut self, - start_height: u64, - end_height: u64, sidechain_id: Option, + epoch: VnEpoch, ) -> Result, CommsInterfaceError> { match self .request_sender - .call(NodeCommsRequest::FetchValidatorNodeChanges { - start_height, - end_height, - sidechain_id, - }) + .call(NodeCommsRequest::FetchValidatorNodeChanges { epoch, sidechain_id }) .await?? { NodeCommsResponse::FetchValidatorNodeChangesResponse(validator_node_change) => Ok(validator_node_change), @@ -320,17 +316,20 @@ impl LocalNodeCommsInterface { } } - pub async fn get_shard_key( + pub async fn get_validator_node( &mut self, - height: u64, + sidechain_id: Option, public_key: PublicKey, - ) -> Result, CommsInterfaceError> { + ) -> Result, CommsInterfaceError> { match self .request_sender - .call(NodeCommsRequest::GetShardKey { height, public_key }) + .call(NodeCommsRequest::GetValidatorNode { + sidechain_id, + public_key, + }) .await?? { - NodeCommsResponse::GetShardKeyResponse(shard_key) => Ok(shard_key), + NodeCommsResponse::GetValidatorNode(vn) => Ok(vn), _ => Err(CommsInterfaceError::UnexpectedApiResponse), } } diff --git a/base_layer/core/src/base_node/rpc/service.rs b/base_layer/core/src/base_node/rpc/service.rs index 34b8a30d46..65c1bb041d 100644 --- a/base_layer/core/src/base_node/rpc/service.rs +++ b/base_layer/core/src/base_node/rpc/service.rs @@ -309,7 +309,7 @@ impl BaseNodeWalletService for BaseNodeWalletRpc let signature = Signature::try_from(sig).map_err(|_| RpcStatus::bad_request("Signature was invalid"))?; let response: TxQueryResponse = self.fetch_kernel(signature.clone()).await?; responses.push(TxQueryBatchResponse { - signature: Some(SignatureProto::from(signature)), + signature: Some(SignatureProto::from(&signature)), location: response.location, best_block_hash: response.best_block_hash, confirmations: response.confirmations, diff --git a/base_layer/core/src/chain_storage/active_validator_node.rs b/base_layer/core/src/chain_storage/active_validator_node.rs index 184d58a18a..1ca749008a 100644 --- a/base_layer/core/src/chain_storage/active_validator_node.rs +++ b/base_layer/core/src/chain_storage/active_validator_node.rs @@ -20,21 +20,47 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::cmp; + use serde::{Deserialize, Serialize}; use tari_common_types::{ epoch::VnEpoch, types::{Commitment, PublicKey}, }; -use crate::transactions::{tari_amount::MicroMinotari, transaction_components::ValidatorNodeRegistration}; +use crate::transactions::tari_amount::MicroMinotari; -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ValidatorNodeEntry { pub shard_key: [u8; 32], - pub start_epoch: VnEpoch, + /// The epoch in which this validator node was (or will be) activated + pub activation_epoch: VnEpoch, + /// The epoch in which the validator registartion UTXO was submitted + pub registration_epoch: VnEpoch, pub public_key: PublicKey, pub commitment: Commitment, - pub sidechain_id: Option, - pub registration: ValidatorNodeRegistration, + pub sidechain_public_key: Option, pub minimum_value_promise: MicroMinotari, } + +impl Ord for ValidatorNodeEntry { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.sidechain_public_key + .cmp(&other.sidechain_public_key) + .then_with(|| self.shard_key.cmp(&other.shard_key)) + } +} + +impl PartialOrd for ValidatorNodeEntry { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for ValidatorNodeEntry { + fn eq(&self, other: &Self) -> bool { + self.sidechain_public_key == other.sidechain_public_key && self.shard_key == other.shard_key + } +} + +impl Eq for ValidatorNodeEntry {} diff --git a/base_layer/core/src/chain_storage/async_db.rs b/base_layer/core/src/chain_storage/async_db.rs index 96721141ad..cdc275b291 100644 --- a/base_layer/core/src/chain_storage/async_db.rs +++ b/base_layer/core/src/chain_storage/async_db.rs @@ -31,6 +31,7 @@ use primitive_types::U256; use rand::{rngs::OsRng, RngCore}; use tari_common_types::{ chain_metadata::ChainMetadata, + epoch::VnEpoch, types::{BlockHash, Commitment, HashOutput, PublicKey, Signature}, }; use tari_utilities::epoch_time::EpochTime; @@ -284,7 +285,11 @@ impl AsyncBlockchainDb { make_async_fn!(fetch_active_validator_nodes(height: u64, validator_network: Option) -> Vec, "fetch_active_validator_nodes"); - make_async_fn!(get_shard_key(height:u64, public_key: PublicKey) -> Option<[u8;32]>, "get_shard_key"); + make_async_fn!(fetch_validators_activating_in_epoch(sidechain_pk: Option, epoch: VnEpoch) -> Vec, "fetch_validators_activating_in_epoch"); + + make_async_fn!(fetch_validators_exiting_in_epoch(sidechain_pk: Option, epoch: VnEpoch) -> Vec, "fetch_validators_exiting_in_epoch"); + + make_async_fn!(get_validator_node(sidechain_id: Option, public_key: PublicKey) -> Option, "get_validator_node"); make_async_fn!(fetch_template_registrations>(range: T) -> Vec, "fetch_template_registrations"); diff --git a/base_layer/core/src/chain_storage/blockchain_backend.rs b/base_layer/core/src/chain_storage/blockchain_backend.rs index 87542c49ec..9f646cf383 100644 --- a/base_layer/core/src/chain_storage/blockchain_backend.rs +++ b/base_layer/core/src/chain_storage/blockchain_backend.rs @@ -3,6 +3,7 @@ use tari_common_types::{ chain_metadata::ChainMetadata, + epoch::VnEpoch, types::{Commitment, HashOutput, PublicKey, Signature}, }; @@ -181,11 +182,49 @@ pub trait BlockchainBackend: Send + Sync { /// block body ordering. fn fetch_active_validator_nodes( &self, + sidechain_pk: Option<&PublicKey>, height: u64, - validator_network: Option, ) -> Result, ChainStorageError>; - /// Returns the shard key for the validator node if valid at the given height. - fn get_shard_key(&self, height: u64, public_key: PublicKey) -> Result, ChainStorageError>; + + fn fetch_validators_activating_in_epoch( + &self, + sidechain_pk: Option<&PublicKey>, + epoch: VnEpoch, + ) -> Result, ChainStorageError>; + + fn fetch_validators_exiting_in_epoch( + &self, + sidechain_pk: Option<&PublicKey>, + epoch: VnEpoch, + ) -> Result, ChainStorageError>; + + /// Returns true if the validator node is registered + fn validator_node_is_active( + &self, + sidechain_pk: Option<&PublicKey>, + height: u64, + validator_node_pk: &PublicKey, + ) -> Result; + + fn validator_node_is_active_for_shard_group( + &self, + sidechain_pk: Option<&PublicKey>, + height: u64, + validator_node_pk: &PublicKey, + shard_group: u32, + ) -> Result; + fn validator_nodes_count_for_shard_group( + &self, + sidechain_pk: Option<&PublicKey>, + end_epoch: VnEpoch, + shard_group: u32, + ) -> Result; + /// Returns the validator node for the given sidechain and public key if it exists + fn get_validator_node( + &self, + sidechain_pk: Option<&PublicKey>, + public_key: PublicKey, + ) -> Result, ChainStorageError>; /// Returns all template registrations within (inclusive) the given height range. fn fetch_template_registrations( &self, diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index ff467de6c4..9ef1e1d193 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -38,6 +38,7 @@ use primitive_types::U256; use serde::{Deserialize, Serialize}; use tari_common_types::{ chain_metadata::ChainMetadata, + epoch::VnEpoch, types::{BlockHash, Commitment, FixedHash, HashOutput, PublicKey, Signature}, }; use tari_hashing::TransactionHashDomain; @@ -945,9 +946,13 @@ where B: BlockchainBackend db.fetch_mmr_size(tree) } - pub fn get_shard_key(&self, height: u64, public_key: PublicKey) -> Result, ChainStorageError> { + pub fn get_validator_node( + &self, + sidechain_pk: Option, + public_key: PublicKey, + ) -> Result, ChainStorageError> { let db = self.db_read_access()?; - db.get_shard_key(height, public_key) + db.get_validator_node(sidechain_pk.as_ref(), public_key) } /// Tries to add a block to the longest chain. @@ -1297,10 +1302,28 @@ where B: BlockchainBackend pub fn fetch_active_validator_nodes( &self, height: u64, - validator_network: Option, + sidechain_pk: Option, + ) -> Result, ChainStorageError> { + let db = self.db_read_access()?; + db.fetch_active_validator_nodes(sidechain_pk.as_ref(), height) + } + + pub fn fetch_validators_activating_in_epoch( + &self, + sidechain_pk: Option, + epoch: VnEpoch, + ) -> Result, ChainStorageError> { + let db = self.db_read_access()?; + db.fetch_validators_activating_in_epoch(sidechain_pk.as_ref(), epoch) + } + + pub fn fetch_validators_exiting_in_epoch( + &self, + sidechain_pk: Option, + epoch: VnEpoch, ) -> Result, ChainStorageError> { let db = self.db_read_access()?; - db.fetch_active_validator_nodes(height, validator_network) + db.fetch_validators_exiting_in_epoch(sidechain_pk.as_ref(), epoch) } pub fn fetch_template_registrations>( @@ -1431,7 +1454,7 @@ pub fn calculate_mmr_roots( let validator_nodes = db.fetch_all_active_validator_nodes(block_height)?; (calculate_validator_node_mr(&validator_nodes)?, validator_nodes.len()) } else { - // MR is unchanged except for epoch boundary + // Active validator set never changes within epochs, so we can reuse the previous block VN MR let tip_header = fetch_header(db, block_height.saturating_sub(1))?; (tip_header.validator_node_mr, 0) }; @@ -1499,24 +1522,24 @@ pub fn calculate_validator_node_mr( } in validator_nodes { hash_map - .entry(sidechain_id.as_ref().map(|n| n.to_vec()).unwrap_or(vec![0u8; 32])) + .entry(sidechain_id.as_ref().map_or([0u8; 32].as_slice(), |n| n.as_bytes())) .or_insert_with(Vec::new) .push((pk, shard_key)); } let mut roots = HashMap::new(); - for (validator_network, set) in hash_map { + for (sidechain_pk, set) in hash_map { let mut sorted_set = set; sorted_set.sort_unstable_by(|(pk1, _), (pk2, _)| pk1.as_bytes().cmp(pk2.as_bytes())); let vn_bmt = ValidatorNodeBMT::create(sorted_set.iter().map(hash_node).collect()); - roots.insert(validator_network, vn_bmt.get_merkle_root()); + roots.insert(sidechain_pk, vn_bmt.get_merkle_root()); } - let mut keys = roots.keys().cloned().collect::>(); + let mut keys = roots.keys().copied().collect::>(); keys.sort(); let mut root_mt = SparseMerkleTree::::new(); for key in keys { - let node_key = NodeKey::try_from(key.as_slice())?; + let node_key = NodeKey::try_from(key)?; let value_hash = ValueHash::try_from(roots[&key].as_slice())?; root_mt.insert(node_key, value_hash)?; } @@ -1726,22 +1749,7 @@ fn fetch_block(db: &T, height: u64, compact: bool) -> Resu Err(e) => return Err(e), }; - let rp_hash = match utxo_mined_info.output.proof { - Some(proof) => proof.hash(), - None => FixedHash::zero(), - }; - compact_input.add_output_data( - utxo_mined_info.output.version, - utxo_mined_info.output.features, - utxo_mined_info.output.commitment, - utxo_mined_info.output.script, - utxo_mined_info.output.sender_offset_public_key, - utxo_mined_info.output.covenant, - utxo_mined_info.output.encrypted_data, - utxo_mined_info.output.metadata_signature, - rp_hash, - utxo_mined_info.output.minimum_value_promise, - ); + compact_input.add_output_data(utxo_mined_info.output); Ok(compact_input) }) .collect::, _>>()?; @@ -1791,16 +1799,23 @@ fn fetch_block_by_utxo_commitment( db: &T, commitment: &Commitment, ) -> Result, ChainStorageError> { - let output = db.fetch_unspent_output_hash_by_commitment(commitment)?; - match output { - Some(hash) => match db.fetch_output(&hash)? { - Some(mined_info) => fetch_block_by_hash(db, mined_info.header_hash, false), - None => Ok(None), - }, + match fetch_utxo_by_commitment(db, commitment)? { + Some(mined_info) => fetch_block_by_hash(db, mined_info.header_hash, false), None => Ok(None), } } +fn fetch_utxo_by_commitment( + db: &T, + commitment: &Commitment, +) -> Result, ChainStorageError> { + let output = db.fetch_unspent_output_hash_by_commitment(commitment)?; + output + .map(|hash| db.fetch_output(&hash)) + .transpose() + .map(|output| output.flatten()) +} + fn fetch_block_by_hash( db: &T, hash: BlockHash, diff --git a/base_layer/core/src/chain_storage/lmdb_db/composite_key.rs b/base_layer/core/src/chain_storage/lmdb_db/composite_key.rs index 6620166cfd..b977125e3c 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/composite_key.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/composite_key.rs @@ -22,18 +22,50 @@ use std::{ fmt::{Display, Formatter}, - ops::{Deref, DerefMut}, + ops::Deref, }; use lmdb_zero::traits::AsLmdbBytes; use tari_common_types::types::FixedHash; -use tari_utilities::hex::to_hex; use crate::chain_storage::ChainStorageError; +#[derive(Debug, Clone)] +enum SmallBytes { + Stack([u8; L]), + Heap(Box<[u8; L]>), +} + +impl SmallBytes { + pub fn as_slice(&self) -> &[u8] { + match self { + SmallBytes::Stack(b) => b.as_ref(), + SmallBytes::Heap(b) => b.as_ref(), + } + } + + pub fn as_mut_slice(&mut self) -> &mut [u8] { + match self { + SmallBytes::Stack(b) => b.as_mut(), + SmallBytes::Heap(b) => b.as_mut(), + } + } +} + +impl Deref for SmallBytes { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + match self { + SmallBytes::Stack(b) => b, + SmallBytes::Heap(b) => &**b, + } + } +} + #[derive(Debug, Clone)] pub(super) struct CompositeKey { - bytes: Box<[u8; L]>, + bytes: SmallBytes, len: usize, } @@ -61,28 +93,48 @@ impl CompositeKey { if new_len > L { return false; } - self.bytes[self.len..new_len].copy_from_slice(b); + self.bytes.as_mut_slice()[self.len..new_len].copy_from_slice(b); self.len = new_len; true } pub fn as_bytes(&self) -> &[u8] { - &self.bytes[..self.len] + &self.bytes.as_slice()[..self.len] } - fn as_bytes_mut(&mut self) -> &mut [u8] { - &mut self.bytes[..self.len] + pub fn to_be_u64(&self, offset: usize) -> Result { + if offset + 8 > self.len { + return Err(ChainStorageError::CompositeKeyLengthExceeded); + } + let mut buf = [0u8; 8]; + buf.copy_from_slice(&self.bytes[offset..offset + 8]); + Ok(u64::from_le_bytes(buf)) } /// Returns a fixed 0-filled byte array. - fn new_buf() -> Box<[u8; L]> { - Box::new([0x0u8; L]) + fn new_buf() -> SmallBytes { + if L <= 64 { + return SmallBytes::Stack([0u8; L]); + } + SmallBytes::Heap(Box::new([0x0u8; L])) + } + + pub fn section_iter(&self, sections: [usize; SECTIONS]) -> SectionIter<'_, SECTIONS> { + SectionIter { + sections, + current: 0, + pointer: 0, + slice: self.as_bytes(), + } } } impl Display for CompositeKey { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", to_hex(self.as_bytes())) + for b in self.as_bytes() { + write!(f, "{:02x}", b)?; + } + Ok(()) } } @@ -94,12 +146,6 @@ impl Deref for CompositeKey { } } -impl DerefMut for CompositeKey { - fn deref_mut(&mut self) -> &mut Self::Target { - self.as_bytes_mut() - } -} - impl AsRef<[u8]> for CompositeKey { fn as_ref(&self) -> &[u8] { self.as_bytes() @@ -139,3 +185,82 @@ impl InputKey { self.0 } } + +pub(super) struct SectionIter<'a, const SECTIONS: usize> { + sections: [usize; SECTIONS], + current: usize, + pointer: usize, + slice: &'a [u8], +} + +impl<'a, const SECTIONS: usize> SectionIter<'a, SECTIONS> { + /// Returns the next 8 bytes as a u64. + /// + /// # Panics + /// Panics if the next section is not 8 bytes long. + pub fn next_be_u64(&mut self) -> Option { + let bytes = self.next()?; + assert_eq!(bytes.len(), 8); + let mut buf = [0u8; 8]; + buf.copy_from_slice(bytes); + Some(u64::from_be_bytes(buf)) + } +} + +impl<'a, const SECTIONS: usize> Iterator for SectionIter<'a, SECTIONS> { + type Item = &'a [u8]; + + fn next(&mut self) -> Option { + let cur_section = *self.sections.get(self.current)?; + + let lower = self.pointer; + let upper = self.pointer + cur_section; + self.current += 1; + self.pointer = upper; + + if upper > self.slice.len() { + return None; + } + + Some(&self.slice[lower..upper]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod section_iter { + use super::*; + + #[test] + fn it_returns_section_slices() { + let key = CompositeKey::<10>::try_from_parts(&[&[1, 3][..], &[6], &[7, 8, 9]]).unwrap(); + let mut iter = key.section_iter([2, 1, 3, 0]); + assert_eq!(iter.next(), Some(&[1, 3][..])); + assert_eq!(iter.next(), Some(&[6][..])); + assert_eq!(iter.next(), Some(&[7, 8, 9][..])); + assert_eq!(iter.next(), Some(&[][..])); + assert_eq!(iter.next(), None); + } + + #[test] + fn it_returns_none_if_sections_dont_exist() { + let key = CompositeKey::<10>::try_from_parts(&[&[1, 3][..]]).unwrap(); + let mut iter = key.section_iter([2, 1, 3]); + assert_eq!(iter.next(), Some(&[1, 3][..])); + assert_eq!(iter.next(), None); + assert_eq!(iter.next(), None); + assert_eq!(iter.next(), None); + } + + #[test] + fn it_returns_none_for_less_sections_than_len() { + let key = CompositeKey::<10>::try_from_parts(&[&[1, 3][..], &[1, 1, 1]]).unwrap(); + let mut iter = key.section_iter([3]); + assert_eq!(iter.next(), Some(&[1, 3, 1][..])); + assert_eq!(iter.next(), None); + assert_eq!(iter.next(), None); + } + } +} diff --git a/base_layer/core/src/chain_storage/lmdb_db/cursors.rs b/base_layer/core/src/chain_storage/lmdb_db/cursors.rs index 0718a97be2..d963b13d8a 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/cursors.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/cursors.rs @@ -96,32 +96,78 @@ where V: DeserializeOwned } } -pub struct LmdbReadCursor<'a, V> { +pub struct LmdbReadCursor<'a, K, V> { cursor: Cursor<'a, 'a>, - value_type: PhantomData, access: ConstAccessor<'a>, + seek_value: Option<(K, V)>, } -impl<'a, V: DeserializeOwned> LmdbReadCursor<'a, V> { +impl<'a, K: FromKeyBytes, V: DeserializeOwned> LmdbReadCursor<'a, K, V> { pub(super) fn new(cursor: Cursor<'a, 'a>, access: ConstAccessor<'a>) -> Self { Self { cursor, access, - value_type: PhantomData, + seek_value: None, + } + } + + pub fn seek_first(&mut self) -> Result { + if let Some((k, v)) = convert_result(self.cursor.first(&self.access))? { + self.seek_value = Some((k, v)); + return Ok(true); } + Ok(false) } /// Returns the item at the cursor, progressing forwards until there are no more elements - pub fn next(&mut self) -> Result, ChainStorageError> { + pub fn next(&mut self) -> Result, ChainStorageError> { + if let Some(value) = self.seek_value.take() { + return Ok(Some(value)); + } convert_result(self.cursor.next(&self.access)) } - pub fn next_dup(&mut self) -> Result, ChainStorageError> { + /// Returns the key of the item at the cursor, progressing forwards until there are no more elements + /// This is useful when the value is not needed, saving on deserialization costs + pub fn next_key(&mut self) -> Result, ChainStorageError> { + if let Some((key, _)) = self.seek_value.take() { + return Ok(Some(key)); + } + let result = self.cursor.next::<_, [u8]>(&self.access); + match result.to_opt()? { + Some((k, _)) => Ok(Some(K::from_key_bytes(k)?)), + None => Ok(None), + } + } + + /// Return count of duplicates for current key. + /// + /// This call is only valid on `DUPSORT` databases. + pub fn count_dups(&mut self) -> Result { + let count = self.cursor.count()?; + Ok(count) + } + + /// Advances the cursor to the next value in the current key. + /// + /// This only makes sense on `DUPSORT` databases. This call returns None if + /// there are no more values in the current key. + pub fn next_dup(&mut self) -> Result, ChainStorageError> { + if let Some(value) = self.seek_value.take() { + return Ok(Some(value)); + } convert_result(self.cursor.next_dup(&self.access)) } - pub fn seek_range(&mut self, key: &[u8]) -> Result, ChainStorageError> { - convert_result(self.cursor.seek_range_k(&self.access, key)) + /// Positions the cursor at the first item whose key is greater than or equal to key. + /// If there is such a key, true is returned and calling [LmdbReadCursor::next] is guaranteed to return Some. + /// Conversely, if false is returned, calling [LmdbReadCursor::next] is guaranteed to return None. + pub fn seek_range(&mut self, key: &[u8]) -> Result { + if let Some((k, v)) = convert_result(self.cursor.seek_range_k(&self.access, key))? { + self.seek_value = Some((k, v)); + return Ok(true); + } + Ok(false) } } @@ -158,7 +204,14 @@ impl FromKeyBytes for CompositeKey { } } -fn convert_result( +impl FromKeyBytes for Vec { + fn from_key_bytes(bytes: &[u8]) -> Result + where Self: Sized { + Ok(bytes.to_vec()) + } +} + +fn convert_result( result: lmdb_zero::Result<(&[u8], &[u8])>, ) -> Result, ChainStorageError> { match result.to_opt()? { @@ -170,18 +223,19 @@ fn convert_result( #[cfg(test)] mod tests { + use super::*; use crate::chain_storage::{ lmdb_db::lmdb::{lmdb_get_prefix_cursor, lmdb_insert}, tests::temp_db::TempLmdbDatabase, }; #[test] - fn test_lmdb_get_prefix_cursor() { + fn test_lmdb_cursors() { let database = TempLmdbDatabase::new(); let db = database.default_db(); { let txn = database.write_transaction(); - lmdb_insert(&txn, db, &[0xffu8, 0, 0, 0], &1u64, "test").unwrap(); + lmdb_insert(&txn, db, &[0xfeu8, 0, 0, 0], &1u64, "test").unwrap(); lmdb_insert(&txn, db, &[0x2bu8, 0, 0, 1], &2u64, "test").unwrap(); lmdb_insert(&txn, db, &[0x2bu8, 0, 1, 1], &3u64, "test").unwrap(); lmdb_insert(&txn, db, &[0x2bu8, 1, 1, 0], &4u64, "test").unwrap(); @@ -190,6 +244,35 @@ mod tests { txn.commit().unwrap(); } + // Test LmdbReadCursor + { + let txn = database.read_transaction(); + let access = txn.access(); + let cursor = txn.cursor(db.clone()).unwrap(); + let mut cursor = LmdbReadCursor::<_, u64>::new(cursor, access); + assert!(cursor.seek_range(&[0x2bu8]).unwrap()); + let kv = cursor.next().unwrap().unwrap(); + assert_eq!(kv, (vec![0x2b, 0, 0, 1], 2)); + let kv = cursor.next().unwrap().unwrap(); + assert_eq!(kv, (vec![0x2b, 0, 1, 1], 3)); + let kv = cursor.next().unwrap().unwrap(); + assert_eq!(kv, (vec![0x2b, 1, 1, 0], 4)); + let kv = cursor.next().unwrap().unwrap(); + assert_eq!(kv, (vec![0x2b, 1, 1, 1], 5)); + let kv = cursor.next().unwrap().unwrap(); + assert_eq!(kv, (vec![0xfe, 0, 0, 0], 1)); + assert_eq!(cursor.next().unwrap(), None); + assert_eq!(cursor.next().unwrap(), None); + // Test seeking more than once on a cursor + cursor.seek_range(&[0x2b, 1, 1]).unwrap(); + let kv = cursor.next().unwrap().unwrap(); + assert_eq!(kv, (vec![0x2b, 1, 1, 0], 4)); + + assert!(!cursor.seek_range(&[0xffu8]).unwrap()); + assert_eq!(cursor.next().unwrap(), None); + } + + // Test prefix cursor { let txn = database.read_transaction(); let mut cursor = lmdb_get_prefix_cursor::(&txn, db, &[0x2b]).unwrap(); diff --git a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs index a51e9f203c..5f108ff94b 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs @@ -111,6 +111,8 @@ use crate::{ tari_amount::MicroMinotari, transaction_components::{ OutputType, + SideChainFeatureData, + SideChainId, SpentOutput, TransactionInput, TransactionKernel, @@ -151,12 +153,14 @@ const LMDB_DB_BAD_BLOCK_LIST: &str = "bad_blocks"; const LMDB_DB_REORGS: &str = "reorgs"; const LMDB_DB_VALIDATOR_NODES: &str = "validator_nodes"; const LMDB_DB_VALIDATOR_NODES_MAPPING: &str = "validator_nodes_mapping"; +const LMDB_DB_VALIDATOR_NODES_ACTIVATION: &str = "validator_nodes_activation_queue"; +const LMDB_DB_VALIDATOR_NODES_EXIT: &str = "validator_nodes_exit"; const LMDB_DB_TEMPLATE_REGISTRATIONS: &str = "template_registrations"; /// HeaderHash(32), mmr_pos(8), hash(32) type KernelKey = CompositeKey<72>; /// Height(8), Hash(32) -type ValidatorNodeRegistrationKey = CompositeKey<40>; +type CodeTemplateRegistrationKey = CompositeKey<40>; pub fn create_lmdb_database>( path: P, @@ -199,7 +203,9 @@ pub fn create_lmdb_database>( .add_database(LMDB_DB_BAD_BLOCK_LIST, flags) .add_database(LMDB_DB_REORGS, flags | db::INTEGERKEY) .add_database(LMDB_DB_VALIDATOR_NODES, flags) + .add_database(LMDB_DB_VALIDATOR_NODES_ACTIVATION, flags | db::DUPSORT) .add_database(LMDB_DB_VALIDATOR_NODES_MAPPING, flags) + .add_database(LMDB_DB_VALIDATOR_NODES_EXIT, flags) .add_database(LMDB_DB_TEMPLATE_REGISTRATIONS, flags | db::DUPSORT) .build() .map_err(|err| ChainStorageError::CriticalError(format!("Could not create LMDB store:{}", err)))?; @@ -257,11 +263,15 @@ pub struct LMDBDatabase { bad_blocks: DatabaseRef, /// Stores reorgs by epochtime and Reorg reorgs: DatabaseRef, - /// Maps -> ActiveValidatorNode + /// Maps -> ValidatorNodeEntry validator_nodes: DatabaseRef, - /// Maps -> VN Shard Key + /// Maps -> \[PK\] + validator_nodes_activation_queue: DatabaseRef, + /// Maps -> VN Shard Key validator_nodes_mapping: DatabaseRef, - /// Maps CodeTemplateRegistration -> TemplateRegistration + /// Maps -> ValidatorNodeEntry + validator_nodes_exit_queue: DatabaseRef, + /// Maps CodeTemplateRegistration -> TemplateRegistration template_registrations: DatabaseRef, _file_lock: Arc, consensus_manager: ConsensusManager, @@ -300,7 +310,9 @@ impl LMDBDatabase { bad_blocks: get_database(store, LMDB_DB_BAD_BLOCK_LIST)?, reorgs: get_database(store, LMDB_DB_REORGS)?, validator_nodes: get_database(store, LMDB_DB_VALIDATOR_NODES)?, + validator_nodes_activation_queue: get_database(store, LMDB_DB_VALIDATOR_NODES_ACTIVATION)?, validator_nodes_mapping: get_database(store, LMDB_DB_VALIDATOR_NODES_MAPPING)?, + validator_nodes_exit_queue: get_database(store, LMDB_DB_VALIDATOR_NODES_EXIT)?, template_registrations: get_database(store, LMDB_DB_TEMPLATE_REGISTRATIONS)?, env, env_config: store.env_config(), @@ -497,7 +509,7 @@ impl LMDBDatabase { Ok(()) } - fn all_dbs(&self) -> [(&'static str, &DatabaseRef); 26] { + fn all_dbs(&self) -> [(&'static str, &DatabaseRef); 28] { [ (LMDB_DB_METADATA, &self.metadata_db), (LMDB_DB_HEADERS, &self.headers_db), @@ -529,7 +541,12 @@ impl LMDBDatabase { (LMDB_DB_BAD_BLOCK_LIST, &self.bad_blocks), (LMDB_DB_REORGS, &self.reorgs), (LMDB_DB_VALIDATOR_NODES, &self.validator_nodes), + ( + LMDB_DB_VALIDATOR_NODES_ACTIVATION, + &self.validator_nodes_activation_queue, + ), (LMDB_DB_VALIDATOR_NODES_MAPPING, &self.validator_nodes_mapping), + (LMDB_DB_VALIDATOR_NODES_EXIT, &self.validator_nodes_exit_queue), (LMDB_DB_TEMPLATE_REGISTRATIONS, &self.template_registrations), ] } @@ -630,16 +647,14 @@ impl LMDBDatabase { fn input_with_output_data( &self, txn: &WriteTransaction<'_>, - input: TransactionInput, + mut input: TransactionInput, ) -> Result { let input_with_output_data = match input.spent_output { SpentOutput::OutputData { .. } => input, SpentOutput::OutputHash(output_hash) => match self.fetch_output_in_txn(txn, output_hash.as_slice()) { - Ok(Some(utxo_mined_info)) => TransactionInput { - version: input.version, - spent_output: SpentOutput::create_from_output(utxo_mined_info.output), - input_data: input.input_data, - script_signature: input.script_signature, + Ok(Some(utxo_mined_info)) => { + input.add_output_data(utxo_mined_info.output); + input }, Ok(None) => { error!( @@ -953,6 +968,8 @@ impl LMDBDatabase { let inputs = lmdb_delete_keys_starting_with::(txn, &self.inputs_db, block_hash)?; debug!(target: LOG_TARGET, "Deleted {} input(s)...", inputs.len()); + let constants = self.get_consensus_constants(height); + for utxo in &output_rows { trace!(target: LOG_TARGET, "Deleting UTXO `{}`", to_hex(utxo.hash.as_slice())); lmdb_delete( @@ -969,15 +986,31 @@ impl LMDBDatabase { continue; } - if let Some(vn_reg) = utxo - .output - .features - .sidechain_feature - .as_ref() - .and_then(|f| f.validator_node_registration()) - { - self.validator_node_store(txn) - .delete(height, vn_reg.public_key(), &utxo.output.commitment)?; + if let Some(sidechain_features) = utxo.output.features.sidechain_feature.as_ref() { + match &sidechain_features.data { + SideChainFeatureData::ValidatorNodeRegistration(vn_reg) => { + self.validator_node_store(txn) + .delete(sidechain_features.sidechain_public_key(), vn_reg.public_key())?; + }, + SideChainFeatureData::CodeTemplateRegistration(_) => { + let key = CodeTemplateRegistrationKey::try_from_parts(&[ + height.to_be_bytes().as_slice(), + output_hash.as_slice(), + ])?; + lmdb_delete(txn, &self.template_registrations, &key, "template_registrations")?; + }, + SideChainFeatureData::ConfidentialOutput(_) => { + // Nothing to do + }, + SideChainFeatureData::EvictionProof(evict) => { + let epoch = constants.block_height_to_epoch(height); + self.validator_node_store(txn).unevict( + sidechain_features.sidechain_public_key(), + epoch, + evict.node_to_evict(), + )?; + }, + } } // if an output was burned, it was never created as an unspent utxo @@ -1029,22 +1062,7 @@ impl LMDBDatabase { } })?; - let rp_hash = match utxo_mined_info.output.proof { - Some(proof) => proof.hash(), - None => FixedHash::zero(), - }; - input.add_output_data( - utxo_mined_info.output.version, - utxo_mined_info.output.features, - utxo_mined_info.output.commitment, - utxo_mined_info.output.script, - utxo_mined_info.output.sender_offset_public_key, - utxo_mined_info.output.covenant, - utxo_mined_info.output.encrypted_data, - utxo_mined_info.output.metadata_signature, - rp_hash, - utxo_mined_info.output.minimum_value_promise, - ); + input.add_output_data(utxo_mined_info.output); let smt_key = NodeKey::try_from(input.commitment()?.as_bytes())?; let smt_node = ValueHash::try_from(input.smt_hash(utxo_mined_info.mined_height).as_slice())?; if let Err(e) = output_smt.insert(smt_key, smt_node) { @@ -1282,30 +1300,8 @@ impl LMDBDatabase { } } - let output_hash = output.hash(); - if let Some(vn_reg) = output - .features - .sidechain_feature - .as_ref() - .and_then(|f| f.validator_node_registration()) - { - self.insert_validator_node(txn, header, &output.commitment, output.minimum_value_promise, vn_reg)?; - } - - if let Some(template_reg) = output - .features - .sidechain_feature - .as_ref() - .and_then(|f| f.code_template_registration()) - { - let record = TemplateRegistrationEntry { - registration_data: template_reg.clone(), - output_hash, - block_height: header.height, - block_hash, - }; - - self.insert_template_registration(txn, &record)?; + if output.features.output_type.is_sidechain_type() || output.is_burned_to_sidechain() { + self.handle_sidechain_utxo(txn, header, &output)?; } self.insert_output(txn, &block_hash, header.height, header.timestamp().as_u64(), &output)?; } @@ -1327,16 +1323,11 @@ impl LMDBDatabase { }; let features = input_with_output_data.features()?; - if let Some(vn_reg) = features - .sidechain_feature - .as_ref() - .and_then(|f| f.validator_node_registration()) - { - self.validator_node_store(txn).delete( - header.height, - vn_reg.public_key(), - input_with_output_data.commitment()?, - )?; + if let Some(sidechain_feature) = features.sidechain_feature.as_ref() { + if let Some(vn_reg) = sidechain_feature.validator_node_registration() { + self.validator_node_store(txn) + .delete(sidechain_feature.sidechain_public_key(), vn_reg.public_key())?; + } } trace!( target: LOG_TARGET, @@ -1366,7 +1357,61 @@ impl LMDBDatabase { &'a self, txn: &'a T, ) -> ValidatorNodeStore<'a, T> { - ValidatorNodeStore::new(txn, self.validator_nodes.clone(), self.validator_nodes_mapping.clone()) + ValidatorNodeStore::new( + txn, + self.validator_nodes.clone(), + self.validator_nodes_activation_queue.clone(), + self.validator_nodes_exit_queue.clone(), + ) + } + + fn handle_sidechain_utxo( + &self, + txn: &WriteTransaction<'_>, + header: &BlockHeader, + output: &TransactionOutput, + ) -> Result<(), ChainStorageError> { + let sidechain_feature = output.features.sidechain_feature.as_ref().ok_or_else(|| { + ChainStorageError::InvalidOperation( + "Output does not have a sidechain feature but is a sidechain type".to_string(), + ) + })?; + match &sidechain_feature.data { + SideChainFeatureData::ValidatorNodeRegistration(vn_reg) => { + self.insert_validator_node( + txn, + header, + &output.commitment, + output.minimum_value_promise, + sidechain_feature.sidechain_id(), + vn_reg, + )?; + }, + SideChainFeatureData::CodeTemplateRegistration(template_reg) => { + let output_hash = output.hash(); + let record = TemplateRegistrationEntry { + registration_data: template_reg.clone(), + output_hash, + block_height: header.height, + block_hash: header.hash(), + }; + + self.insert_template_registration(txn, &record)?; + }, + SideChainFeatureData::ConfidentialOutput(_) => { + // Nothing to do + }, + SideChainFeatureData::EvictionProof(proof) => { + let store = self.validator_node_store(txn); + let evict_node = proof.node_to_evict(); + let constants = self.get_consensus_constants(header.height); + let epoch = constants.block_height_to_epoch(header.height); + let sidechain_pk = sidechain_feature.sidechain_id().map(|id| id.public_key()); + store.evict(sidechain_pk, evict_node, epoch)?; + }, + } + + Ok(()) } fn insert_validator_node( @@ -1375,56 +1420,53 @@ impl LMDBDatabase { header: &BlockHeader, commitment: &Commitment, minimum_value_promise: MicroMinotari, + sidechain_id: Option<&SideChainId>, vn_reg: &ValidatorNodeRegistration, ) -> Result<(), ChainStorageError> { let store = self.validator_node_store(txn); let constants = self.get_consensus_constants(header.height); let current_epoch = constants.block_height_to_epoch(header.height); - // skip already added validator node - if store.is_vn_exists(current_epoch, vn_reg.public_key(), vn_reg.sidechain_id().cloned())? { - warn!(target: LOG_TARGET, "Validator node has been already registered: {:?}", vn_reg.public_key().to_string()); - return Ok(()); - } + let sidechain_pk = sidechain_id.map(|id| id.public_key()); - let prev_shard_key = store.get_shard_key( - 0, - current_epoch.as_u64() * constants.epoch_length(), - vn_reg.public_key(), - )?; let shard_key = vn_reg.derive_shard_key( - prev_shard_key, + None, current_epoch, constants.validator_node_registration_shuffle_interval(), &header.prev_hash, ); - let mut next_epoch = current_epoch + VnEpoch(1); - - // looking for next available epoch - let current_vn_count = store.get_vn_count_until_epoch(current_epoch, vn_reg.sidechain_id().cloned())?; - let mut vn_count = store.get_vn_count_in_epoch(next_epoch, vn_reg.sidechain_id().cloned())?; + let activation_epoch = store.get_next_activation_epoch( + sidechain_pk, + current_epoch, + constants.vn_registration_max_vns_initial_epoch() as usize, + constants.vn_registration_max_vns_per_epoch() as usize, + )?; - if (current_vn_count == 0 && vn_count >= constants.vn_registration_max_vns_initial_epoch()) || - (current_vn_count > 0 && vn_count >= constants.vn_registration_max_vns_per_epoch()) - { - while vn_count >= constants.vn_registration_max_vns_per_epoch() { - next_epoch += VnEpoch(1); - vn_count = store.get_vn_count_in_epoch(next_epoch, vn_reg.sidechain_id().cloned())?; - } - } + info!( + target: LOG_TARGET, + "Inserting ValidatorNode: public_key: {}, activation_epoch: {}, registration_epoch: {}, shard_key: {}, \ + commitment: {}, sidechain_public_key: {:?}, minimum_value_promise: {}", + vn_reg.public_key(), + activation_epoch, + current_epoch, + to_hex(&shard_key), + commitment.as_public_key(), + sidechain_pk.map(|pk| pk.to_hex()), + minimum_value_promise + ); let validator_node = ValidatorNodeEntry { shard_key, - start_epoch: next_epoch, + activation_epoch, + registration_epoch: current_epoch, public_key: vn_reg.public_key().clone(), commitment: commitment.clone(), - sidechain_id: vn_reg.sidechain_id().cloned(), - registration: vn_reg.clone(), + sidechain_public_key: sidechain_pk.cloned(), minimum_value_promise, }; - store.insert(header.height, &validator_node)?; + store.insert(&validator_node)?; Ok(()) } @@ -1666,7 +1708,7 @@ impl LMDBDatabase { txn: &WriteTransaction<'_>, template_registration: &TemplateRegistrationEntry, ) -> Result<(), ChainStorageError> { - let key = ValidatorNodeRegistrationKey::try_from_parts(&[ + let key = CodeTemplateRegistrationKey::try_from_parts(&[ template_registration.block_height.to_le_bytes().as_slice(), template_registration.output_hash.as_slice(), ])?; @@ -1736,6 +1778,28 @@ impl LMDBDatabase { fn get_consensus_constants(&self, height: u64) -> &ConsensusConstants { self.consensus_manager.consensus_constants(height) } + + fn fetch_utxo_by_commitment( + &self, + txn: &ConstTransaction<'_>, + commitment: &Commitment, + ) -> Result { + let output_hash = lmdb_get::<_, HashOutput>(txn, &self.utxo_commitment_index, commitment.as_bytes())? + .ok_or_else(|| ChainStorageError::ValueNotFound { + entity: "UTXO (in fetch_utxo_by_commitment)", + field: "commitment", + value: commitment.to_hex(), + })?; + let output = + self.fetch_output_in_txn(txn, output_hash.as_slice())? + .ok_or_else(|| ChainStorageError::ValueNotFound { + entity: "UTXO (in fetch_utxo_by_commitment)", + field: "hash", + value: output_hash.to_string(), + })?; + + Ok(output) + } } pub fn create_recovery_lmdb_database>(path: P) -> Result<(), ChainStorageError> { @@ -2500,35 +2564,224 @@ impl BlockchainBackend for LMDBDatabase { // Get the current epoch for the height let end_epoch = constants.block_height_to_epoch(height); - // Convert these back to height as validators regs are indexed by height - let end_height = end_epoch.as_u64() * constants.epoch_length(); - let nodes = vn_store.get_vn_set(0, end_height)?; + // TODO: custom limit + let vns = vn_store.get_entire_vn_set(VnEpoch::zero(), end_epoch, 1_000_000)?; + + let mut nodes = Vec::with_capacity(vns.len()); + for node in vns { + let output = self.fetch_utxo_by_commitment(&txn, &node.commitment)?; + let reg = output + .output + .features + .sidechain_feature + .as_ref() + .and_then(|f| f.validator_node_registration()) + .ok_or_else(|| ChainStorageError::DataInconsistencyDetected { + function: "fetch_all_active_validator_nodes", + details: "Output does not have a sidechain feature".to_string(), + })?; + nodes.push(ValidatorNodeRegistrationInfo { + public_key: node.public_key, + sidechain_id: node.sidechain_public_key, + shard_key: node.shard_key, + activation_epoch: node.activation_epoch, + original_registration: reg.clone(), + minimum_value_promise: output.output.minimum_value_promise, + }); + } + Ok(nodes) } fn fetch_active_validator_nodes( &self, + sidechain_pk: Option<&PublicKey>, height: u64, - validator_network: Option, ) -> Result, ChainStorageError> { - let nodes = self - .fetch_all_active_validator_nodes(height)? - .into_iter() - .filter(|vn| vn.sidechain_id == validator_network) - .collect(); + let txn = self.read_transaction()?; + let vn_store = self.validator_node_store(&txn); + let constants = self.consensus_manager.consensus_constants(height); + + // Get the current epoch for the height + let end_epoch = constants.block_height_to_epoch(height); + // TODO: custom limit + let vns = vn_store.get_vn_set(sidechain_pk, VnEpoch::zero(), end_epoch, 1_000_000)?; + + let mut nodes = Vec::with_capacity(vns.len()); + for node in vns { + let output = self.fetch_utxo_by_commitment(&txn, &node.commitment)?; + let reg = output + .output + .features + .sidechain_feature + .as_ref() + .and_then(|f| f.validator_node_registration()) + .ok_or_else(|| ChainStorageError::DataInconsistencyDetected { + function: "fetch_all_active_validator_nodes", + details: "Output does not have a sidechain feature".to_string(), + })?; + nodes.push(ValidatorNodeRegistrationInfo { + public_key: node.public_key, + sidechain_id: node.sidechain_public_key, + shard_key: node.shard_key, + activation_epoch: node.activation_epoch, + original_registration: reg.clone(), + minimum_value_promise: output.output.minimum_value_promise, + }); + } + + Ok(nodes) + } + + fn fetch_validators_activating_in_epoch( + &self, + sidechain_pk: Option<&PublicKey>, + epoch: VnEpoch, + ) -> Result, ChainStorageError> { + let txn = self.read_transaction()?; + let vn_store = self.validator_node_store(&txn); + let vns = vn_store.get_activating_in_epoch(sidechain_pk, epoch)?; + let mut nodes = Vec::with_capacity(vns.len()); + for node in vns { + let output = self.fetch_utxo_by_commitment(&txn, &node.commitment)?; + let reg = output + .output + .features + .sidechain_feature + .as_ref() + .and_then(|f| f.validator_node_registration()) + .ok_or_else(|| ChainStorageError::DataInconsistencyDetected { + function: "fetch_validators_activating_in_epoch", + details: "Output does not have a sidechain feature".to_string(), + })?; + nodes.push(ValidatorNodeRegistrationInfo { + public_key: node.public_key, + sidechain_id: node.sidechain_public_key, + shard_key: node.shard_key, + activation_epoch: node.activation_epoch, + original_registration: reg.clone(), + minimum_value_promise: output.output.minimum_value_promise, + }); + } Ok(nodes) } - fn get_shard_key(&self, height: u64, public_key: PublicKey) -> Result, ChainStorageError> { + fn fetch_validators_exiting_in_epoch( + &self, + sidechain_pk: Option<&PublicKey>, + epoch: VnEpoch, + ) -> Result, ChainStorageError> { + let txn = self.read_transaction()?; + let vn_store = self.validator_node_store(&txn); + let vns = vn_store.get_exiting_in_epoch(sidechain_pk, epoch)?; + let mut nodes = Vec::with_capacity(vns.len()); + for node in vns { + let output = self.fetch_utxo_by_commitment(&txn, &node.commitment)?; + let reg = output + .output + .features + .sidechain_feature + .as_ref() + .and_then(|f| f.validator_node_registration()) + .ok_or_else(|| ChainStorageError::DataInconsistencyDetected { + function: "fetch_validators_exiting_in_epoch", + details: "Output does not have a sidechain feature".to_string(), + })?; + nodes.push(ValidatorNodeRegistrationInfo { + public_key: node.public_key, + sidechain_id: node.sidechain_public_key, + shard_key: node.shard_key, + activation_epoch: node.activation_epoch, + original_registration: reg.clone(), + minimum_value_promise: output.output.minimum_value_promise, + }); + } + Ok(nodes) + } + + fn validator_node_is_active( + &self, + sidechain_pk: Option<&PublicKey>, + height: u64, + validator_node_pk: &PublicKey, + ) -> Result { + let txn = self.read_transaction()?; + let vn_store = self.validator_node_store(&txn); + let constants = self.consensus_manager.consensus_constants(height); + + // Get the current epoch for the height + let end_epoch = constants.block_height_to_epoch(height); + let is_active = vn_store.is_vn_active(sidechain_pk, validator_node_pk, end_epoch)?; + Ok(is_active) + } + + fn validator_node_is_active_for_shard_group( + &self, + sidechain_pk: Option<&PublicKey>, + height: u64, + validator_node_pk: &PublicKey, + _shard_group: u32, + ) -> Result { + // TODO: account for shard group + self.validator_node_is_active(sidechain_pk, height, validator_node_pk) + } + + fn validator_nodes_count_for_shard_group( + &self, + sidechain_pk: Option<&PublicKey>, + end_epoch: VnEpoch, + _shard_group: u32, + ) -> Result { + let txn = self.read_transaction()?; + let vn_store = self.validator_node_store(&txn); + vn_store.count_active_validators(sidechain_pk, end_epoch) + } + + fn get_validator_node( + &self, + sidechain_pk: Option<&PublicKey>, + public_key: PublicKey, + ) -> Result, ChainStorageError> { let txn = self.read_transaction()?; let store = self.validator_node_store(&txn); - let constants = self.get_consensus_constants(height); + let Some(vn) = store.get(sidechain_pk, &public_key)? else { + return Ok(None); + }; + + let hash = self + .fetch_unspent_output_hash_by_commitment(&vn.commitment)? + .ok_or_else(|| ChainStorageError::ValueNotFound { + entity: "UTXO (in fetch_unspent_output_hash_by_commitment)", + field: "commitment", + value: vn.commitment.to_hex(), + })?; + let output = self + .fetch_output(&hash)? + .ok_or_else(|| ChainStorageError::ValueNotFound { + entity: "UTXO (in fetch_unspent_output_hash_by_commitment)", + field: "hash", + value: hash.to_hex(), + })?; + + let reg = output + .output + .features + .sidechain_feature + .as_ref() + .and_then(|f| f.validator_node_registration()) + .ok_or_else(|| ChainStorageError::DataInconsistencyDetected { + function: "get_validator_node", + details: "Output does not have a sidechain feature".to_string(), + })?; - // Get the epoch height boundaries for our query - let current_epoch = constants.block_height_to_epoch(height); - let end_height = current_epoch.as_u64() * constants.epoch_length(); - let maybe_shard_id = store.get_shard_key(0, end_height, &public_key)?; - Ok(maybe_shard_id) + Ok(Some(ValidatorNodeRegistrationInfo { + public_key, + sidechain_id: sidechain_pk.cloned(), + shard_key: vn.shard_key, + activation_epoch: vn.activation_epoch, + original_registration: reg.clone(), + minimum_value_promise: vn.minimum_value_promise, + })) } fn fetch_template_registrations( diff --git a/base_layer/core/src/chain_storage/lmdb_db/validator_node_store.rs b/base_layer/core/src/chain_storage/lmdb_db/validator_node_store.rs index d82407e193..f764f9233a 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/validator_node_store.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/validator_node_store.rs @@ -20,13 +20,15 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{collections::HashMap, ops::Deref}; +use std::{ + collections::{BTreeMap, BTreeSet}, + ops::Deref, +}; use lmdb_zero::{ConstTransaction, WriteTransaction}; -use tari_common_types::{ - epoch::VnEpoch, - types::{Commitment, PublicKey}, -}; +use log::debug; +use serde::de::DeserializeOwned; +use tari_common_types::{epoch::VnEpoch, types::PublicKey}; use tari_storage::lmdb_store::DatabaseRef; use tari_utilities::ByteArray; @@ -34,269 +36,529 @@ use crate::chain_storage::{ lmdb_db::{ composite_key::CompositeKey, cursors::{FromKeyBytes, LmdbReadCursor}, - lmdb::{lmdb_delete, lmdb_insert}, + lmdb::{lmdb_delete, lmdb_delete_key_value, lmdb_get, lmdb_insert, lmdb_insert_dup, lmdb_len}, }, ChainStorageError, ValidatorNodeEntry, - ValidatorNodeRegistrationInfo, }; -pub type ShardKey = [u8; 32]; -// -type ValidatorNodeStoreKey = CompositeKey<72>; -// -type ShardIdIndexKey = CompositeKey<72>; +const LOG_TARGET: &str = "c::cs::lmdb_db::validator_node_store"; + +const U64_SIZE: usize = size_of::(); +const PK_SIZE: usize = 32; + +/// +type ValidatorNodeStoreKey = CompositeKey<{ 2 * PK_SIZE }>; +/// +type ExitQueueKey = CompositeKey<{ PK_SIZE + U64_SIZE + PK_SIZE }>; +/// +type ActivationQueueKey = CompositeKey<{ PK_SIZE + U64_SIZE }>; pub struct ValidatorNodeStore<'a, Txn> { txn: &'a Txn, db_validator_nodes: DatabaseRef, - db_validator_nodes_mapping: DatabaseRef, + db_validator_activation_queue: DatabaseRef, + db_validator_nodes_exit: DatabaseRef, } impl<'a, Txn: Deref>> ValidatorNodeStore<'a, Txn> { - pub fn new(txn: &'a Txn, db_height_to_vn: DatabaseRef, idx_public_key_to_shard: DatabaseRef) -> Self { + pub fn new( + txn: &'a Txn, + db_validator_nodes: DatabaseRef, + db_validator_activation_queue: DatabaseRef, + db_validator_nodes_exit: DatabaseRef, + ) -> Self { Self { txn, - db_validator_nodes: db_height_to_vn, - db_validator_nodes_mapping: idx_public_key_to_shard, + db_validator_nodes, + db_validator_activation_queue, + db_validator_nodes_exit, } } } impl ValidatorNodeStore<'_, WriteTransaction<'_>> { - pub fn insert(&self, height: u64, validator: &ValidatorNodeEntry) -> Result<(), ChainStorageError> { - let key = ValidatorNodeStoreKey::try_from_parts(&[ - height.to_be_bytes().as_slice(), - validator.public_key.as_bytes(), - validator.commitment.as_bytes(), - ]) - .expect("insert: Composite key length is incorrect"); - lmdb_insert(self.txn, &self.db_validator_nodes, &key, &validator, "Validator node")?; - - let key = ShardIdIndexKey::try_from_parts(&[ - validator.public_key.as_bytes(), - height.to_be_bytes().as_slice(), - validator.commitment.as_bytes(), - ]) - .expect("insert: Composite key length is incorrect"); + pub fn insert(&self, validator: &ValidatorNodeEntry) -> Result<(), ChainStorageError> { + let key = create_activation_key(validator.sidechain_public_key.as_ref(), validator.activation_epoch); + lmdb_insert_dup( + self.txn, + &self.db_validator_activation_queue, + &key, + &validator.public_key, + )?; + + let key = create_vn_key(validator.sidechain_public_key.as_ref(), &validator.public_key); + lmdb_insert(self.txn, &self.db_validator_nodes, &key, validator, "Validator node")?; + + Ok(()) + } + + pub fn delete(&self, sidechain_pk: Option<&PublicKey>, public_key: &PublicKey) -> Result<(), ChainStorageError> { + let key = create_vn_key(sidechain_pk, public_key); + let vn = lmdb_get::<_, ValidatorNodeEntry>(self.txn, &self.db_validator_nodes, &key)?.ok_or_else(|| { + ChainStorageError::ValueNotFound { + entity: "Validator node (delete)", + field: "public key", + value: public_key.to_string(), + } + })?; + lmdb_delete(self.txn, &self.db_validator_nodes, &key, "validator_nodes")?; + + let key = create_activation_key(sidechain_pk, vn.activation_epoch); + lmdb_delete_key_value(self.txn, &self.db_validator_activation_queue, &key, &vn.public_key)?; + + Ok(()) + } + + pub fn evict( + &self, + sidechain_pk: Option<&PublicKey>, + evict_node: &PublicKey, + evict_epoch: VnEpoch, + ) -> Result<(), ChainStorageError> { + let vn_key = create_vn_key(sidechain_pk, evict_node); + let vn = lmdb_get::<_, ValidatorNodeEntry>(self.txn, &self.db_validator_nodes, &vn_key)? + .expect("Found node key but not node in db_validator_nodes"); + + lmdb_delete(self.txn, &self.db_validator_nodes, &vn_key, "validator_nodes")?; + + let key = create_exit_queue_key(sidechain_pk, evict_epoch, evict_node); lmdb_insert( self.txn, - &self.db_validator_nodes_mapping, + &self.db_validator_nodes_exit, &key, - &validator.shard_key, - "Validator node", + &vn, + "validator_nodes_exit", )?; Ok(()) } - pub fn delete( + pub fn unevict( &self, - height: u64, - public_key: &PublicKey, - commitment: &Commitment, + sidechain_pk: Option<&PublicKey>, + evict_epoch: VnEpoch, + evict_node: &PublicKey, ) -> Result<(), ChainStorageError> { - let key = ValidatorNodeStoreKey::try_from_parts(&[ - height.to_be_bytes().as_slice(), - public_key.as_bytes(), - commitment.as_bytes(), - ]) - .expect("delete: Composite key length is incorrect"); - lmdb_delete(self.txn, &self.db_validator_nodes, &key, "validator_nodes")?; + let exit_key = create_exit_queue_key(sidechain_pk, evict_epoch, evict_node); + let vn = lmdb_get::<_, ValidatorNodeEntry>(self.txn, &self.db_validator_nodes_exit, &exit_key)?.ok_or_else( + || ChainStorageError::ValueNotFound { + entity: "Validator node exit", + field: "public key (unevict)", + value: evict_node.to_string(), + }, + )?; - let key = ShardIdIndexKey::try_from_parts(&[ - public_key.as_bytes(), - height.to_be_bytes().as_slice(), - commitment.as_bytes(), - ]) - .expect("delete: Composite key length is incorrect"); lmdb_delete( self.txn, - &self.db_validator_nodes_mapping, - &key, - "validator_nodes_mapping", + &self.db_validator_nodes_exit, + &exit_key, + "validator_nodes_exit", )?; + + // Re-insert. Since we know the node was already activated in the past, we do not need to re-insert into the + // historical activation queue. + let key = create_vn_key(sidechain_pk, &vn.public_key); + lmdb_insert(self.txn, &self.db_validator_nodes, &key, &vn, "Validator node")?; + Ok(()) } } impl<'a, Txn: Deref>> ValidatorNodeStore<'a, Txn> { - fn db_read_cursor(&self) -> Result, ChainStorageError> { - let cursor = self.txn.cursor(self.db_validator_nodes.clone())?; - let access = self.txn.access(); - let cursor = LmdbReadCursor::new(cursor, access); - Ok(cursor) + fn db_read_cursor( + &self, + ) -> Result, ChainStorageError> { + self.new_read_cursor(self.db_validator_nodes.clone()) + } + + fn activation_queue_read_cursor( + &self, + ) -> Result, ChainStorageError> { + self.new_read_cursor(self.db_validator_activation_queue.clone()) + } + + fn exit_queue_read_cursor( + &self, + ) -> Result, ChainStorageError> { + self.new_read_cursor(self.db_validator_nodes_exit.clone()) } - fn index_read_cursor(&self) -> Result, ChainStorageError> { - let cursor = self.txn.cursor(self.db_validator_nodes_mapping.clone())?; + fn new_read_cursor( + &self, + db: DatabaseRef, + ) -> Result, ChainStorageError> { + let cursor = self.txn.cursor(db)?; let access = self.txn.access(); let cursor = LmdbReadCursor::new(cursor, access); Ok(cursor) } - /// Checks if the given validator node (by it's public key and side chain ID) + /// Checks if the given validator node (by its public key and side chain ID) /// exists until a given `end_epoch`. - pub fn is_vn_exists( + pub fn is_vn_active( &self, - end_epoch: VnEpoch, + sidechain_id: Option<&PublicKey>, public_key: &PublicKey, - sidechain_id: Option, + end_epoch: VnEpoch, ) -> Result { - let mut cursor = self.db_read_cursor()?; - while let Ok(Some((_, vn_entry))) = cursor.next::() { - if vn_entry.public_key == *public_key && - vn_entry.sidechain_id == sidechain_id && - vn_entry.start_epoch <= end_epoch - { - return Ok(true); + let key = create_vn_key(sidechain_id, public_key); + let Some(vn) = lmdb_get::<_, ValidatorNodeEntry>(self.txn, &self.db_validator_nodes, &key)? else { + return Ok(false); + }; + + Ok(vn.activation_epoch <= end_epoch) + } + + pub fn get_next_activation_epoch( + &self, + sidechain_id: Option<&PublicKey>, + current_epoch: VnEpoch, + initial_validators: usize, + validators_per_epoch: usize, + ) -> Result { + // Node activates earliest in the next epoch + let mut activation_epoch = current_epoch + VnEpoch(1); + // If there are less than the initial validators, we activate all new validators in the next epoch + let len = lmdb_len(self.txn, &self.db_validator_nodes)?; + if len < initial_validators { + return Ok(activation_epoch); + } + + let mut cursor = self.activation_queue_read_cursor()?; + loop { + let key = create_activation_key(sidechain_id, activation_epoch); + if !cursor.seek_range(&key)? { + break; + } + + // If there are less than the required number of validators in the queue for the epoch, we'll activate the + // next validator in this epoch + let num_queued = cursor.count_dups()?; + if num_queued < validators_per_epoch { + break; } + activation_epoch += VnEpoch(1); } - Ok(false) + Ok(activation_epoch) } - /// Returns validator nodes count until a given epoch. - pub fn get_vn_count_until_epoch( + pub fn count_active_validators( &self, - epoch: VnEpoch, - sidechain_id: Option, - ) -> Result { - let mut cursor = self.db_read_cursor()?; - let mut result = 0; - while let Ok(Some((_, value))) = cursor.next::() { - if value.sidechain_id == sidechain_id && value.start_epoch <= epoch { - result += 1; + sidechain_pk: Option<&PublicKey>, + end_epoch: VnEpoch, + ) -> Result { + let mut cursor = self.activation_queue_read_cursor()?; + let sidechain_bytes = sid_as_slice(sidechain_pk); + if !cursor.seek_range(sidechain_bytes)? { + return Ok(0); + } + + // TODO(perf): count is O(n) where n is number of active validators, if this becomes slow we can cache the + // counts per sidechain + let mut count = 0; + while let Some(key) = cursor.next_key()? { + if key[..PK_SIZE] != *sidechain_bytes { + // No further entries for this sidechain + break; + } + let epoch = key.to_be_u64(PK_SIZE)?; + if epoch > end_epoch.as_u64() { + break; } + count += 1; } - Ok(result) + Ok(count) } - /// Returns validator nodes count in a given epoch. - pub fn get_vn_count_in_epoch( + /// Returns a set of tuples ordered by epoch of registration. + /// This set contains no duplicates. If a duplicate registration is found, the last registration is included. + pub fn get_entire_vn_set( &self, - epoch: VnEpoch, - sidechain_id: Option, - ) -> Result { - let mut cursor = self.db_read_cursor()?; - let mut result = 0; - while let Ok(Some((_, value))) = cursor.next::() { - if value.sidechain_id == sidechain_id && value.start_epoch == epoch { - result += 1; + start_epoch: VnEpoch, + end_epoch: VnEpoch, + limit: usize, + ) -> Result, ChainStorageError> { + if end_epoch < start_epoch { + return Err(ChainStorageError::InvalidQuery(format!( + "get_vn_set: End epoch is less than start epoch: {} < {}", + end_epoch, start_epoch + ))); + } + + if limit == 0 { + return Ok(BTreeSet::new()); + } + + // Nodes in db_validator_nodes are not ordered by epoch, meaning retreival until an end epoch will have to + // search the entire database incl > end_epoch. Instead, we first gather all public keys from the + // activation queue which is ordered by epoch. + let selected_nodes = { + let mut cursor = self.activation_queue_read_cursor()?; + cursor.seek_first()?; + + let mut total_returned = 0usize; + let mut selected_nodes = BTreeMap::new(); + while let Some((key, pk)) = cursor.next()? { + let mut sections = key.section_iter([PK_SIZE, U64_SIZE]); + let sidechain_pk = sections + .next() + .ok_or_else(|| ChainStorageError::DataInconsistencyDetected { + function: "ValidatorNodeStore::get_entire_vn_set", + details: "Malformed activation queue key".to_string(), + })?; + + let activation_epoch = + sections + .next_be_u64() + .ok_or_else(|| ChainStorageError::DataInconsistencyDetected { + function: "ValidatorNodeStore::get_entire_vn_set", + details: "Malformed activation queue key".to_string(), + })?; + + if activation_epoch > end_epoch.as_u64() { + continue; + } + let count = cursor.count_dups()?; + let mut pks = Vec::with_capacity(count); + // Collect all the public keys for the pair + pks.push(pk); + loop { + total_returned += 1; + if total_returned == limit { + break; + } + match cursor.next_dup()? { + Some((_, pk)) => pks.push(pk), + None => break, + } + } + + let mut sid = [0u8; 32]; + sid.copy_from_slice(sidechain_pk); + selected_nodes.insert(sid, pks); + if total_returned == limit { + break; + } + } + selected_nodes + }; + + let mut nodes = BTreeSet::new(); + + for (sid, pks) in selected_nodes { + for pk in pks { + let key = create_vn_key_raw(&sid, pk.as_bytes()); + let vn = + lmdb_get::<_, ValidatorNodeEntry>(self.txn, &self.db_validator_nodes, &key)?.ok_or_else(|| { + ChainStorageError::DataInconsistencyDetected { + function: "ValidatorNodeStore::get_entire_vn_set", + details: format!( + "Validator node in db_validator_activation_queue but not found in store for public \ + key {}", + key + ), + } + })?; + nodes.insert(vn); } } - Ok(result) + Ok(nodes) } - /// Returns a set of tuples ordered by height of registration. + /// Returns a set of tuples ordered by epoch of registration. /// This set contains no duplicates. If a duplicate registration is found, the last registration is included. pub fn get_vn_set( &self, - start_height: u64, - end_height: u64, - ) -> Result, ChainStorageError> { + sidechain_pk: Option<&PublicKey>, + start_epoch: VnEpoch, + end_epoch: VnEpoch, + limit: usize, + ) -> Result, ChainStorageError> { + if end_epoch < start_epoch { + return Err(ChainStorageError::InvalidQuery(format!( + "get_vn_set: End epoch is less than start epoch: {} < {}", + end_epoch, start_epoch + ))); + } + + if limit == 0 { + return Ok(BTreeSet::new()); + } + let mut cursor = self.db_read_cursor()?; - let mut nodes = Vec::new(); - // Public key does not mutate once compressed and will always produce the same hash - #[allow(clippy::mutable_key_type)] - let mut dedup_map = HashMap::new(); - match cursor.seek_range::(&start_height.to_be_bytes())? { - Some((key, vn)) => { - let height = u64::from_key_bytes(&key[0..8])?; - if height > end_height { - return Ok(Vec::new()); - } - dedup_map.insert(vn.public_key.clone(), 0); - nodes.push(Some(ValidatorNodeRegistrationInfo { - public_key: vn.public_key, - sidechain_id: vn.sidechain_id, - shard_key: vn.shard_key, - start_epoch: vn.start_epoch, - original_registration: vn.registration.clone(), - minimum_value_promise: vn.minimum_value_promise, - })); - }, - None => return Ok(Vec::new()), + let prefix = create_vn_store_prefix_key(sidechain_pk, start_epoch); + if !cursor.seek_range(&prefix)? { + return Ok(BTreeSet::new()); } - // Start from index 1 because we already have the first entry - let mut i = 1; - while let Some((key, vn)) = cursor.next_dup::()? { - let height = u64::from_key_bytes(&key[0..8])?; - if height > end_height { + let sidechain_bytes = sid_as_slice(sidechain_pk); + let mut nodes = BTreeSet::new(); + while let Some((key, vn)) = cursor.next()? { + if key[..PK_SIZE] != *sidechain_bytes { + // No further entries for this sidechain + break; + } + + if vn.activation_epoch > end_epoch { break; } - if let Some(dup_idx) = dedup_map.insert(vn.public_key.clone(), i) { - // Remove duplicate registrations within the set without changing index order - let node_mut = nodes - .get_mut(dup_idx) - .expect("get_vn_set: internal dedeup map is not in sync with nodes"); - *node_mut = None; + + nodes.insert(vn); + if nodes.len() == limit { + break; } - nodes.push(Some(ValidatorNodeRegistrationInfo { - public_key: vn.public_key, - sidechain_id: vn.sidechain_id, - shard_key: vn.shard_key, - start_epoch: vn.start_epoch, - original_registration: vn.registration, - minimum_value_promise: vn.minimum_value_promise, - })); - i += 1; } - let mut vn_set = nodes.into_iter().flatten().collect::>(); - vn_set.sort_by(|vn_a, vn_b| { - vn_a.sidechain_id - .cmp(&vn_b.sidechain_id) - .then(vn_a.shard_key.cmp(&vn_b.shard_key)) - }); - Ok(vn_set) + Ok(nodes) } - pub fn get_shard_key( + pub fn get_activating_in_epoch( &self, - start_height: u64, - end_height: u64, - public_key: &PublicKey, - ) -> Result, ChainStorageError> { - let mut cursor = self.index_read_cursor()?; - let key = ShardIdIndexKey::try_from_parts(&[public_key.as_bytes(), &start_height.to_be_bytes()]) - .expect("fetch_shard_key: Composite key length is incorrect"); - - // Find the first entry at or above start_height - let mut shard_key = match cursor.seek_range::(key.as_bytes())? { - Some((key, s)) => { - if key[0..32] != *public_key.as_bytes() { - return Ok(None); - } - let height = u64::from_key_bytes(&key[32..40])?; - if height > end_height { - return Ok(None); - } - Some(s) - }, - None => return Ok(None), + sidechain_pk: Option<&PublicKey>, + epoch: VnEpoch, + ) -> Result, ChainStorageError> { + let keys = { + let mut cursor = self.activation_queue_read_cursor()?; + let key = create_activation_key(sidechain_pk, epoch); + if !cursor.seek_range(&key)? { + return Ok(Vec::new()); + } + + let num_keys = cursor.count_dups()?; + let mut keys = Vec::with_capacity(num_keys); + while let Some((_, pk)) = cursor.next_dup()? { + let key = create_vn_key(sidechain_pk, &pk); + keys.push(key); + } + keys }; - // If there are any subsequent entries less than the end height, use that instead. - while let Some((key, s)) = cursor.next::()? { - if key[0..32] != *public_key.as_bytes() { + debug!(target: LOG_TARGET, "Found {} activating validators in epoch {}", keys.len(), epoch); + + let mut validators = Vec::with_capacity(keys.len()); + for key in keys { + let vn = lmdb_get::<_, ValidatorNodeEntry>(self.txn, &self.db_validator_nodes, &key)?.ok_or_else(|| { + ChainStorageError::DataInconsistencyDetected { + function: "ValidatorNodeStore::get_activating_in_epoch", + details: format!( + "Validator node in db_validator_activation_queue but not found in store for public key {}", + key + ), + } + })?; + + validators.push(vn); + } + + Ok(validators) + } + + pub fn get_exiting_in_epoch( + &self, + sidechain_pk: Option<&PublicKey>, + epoch: VnEpoch, + ) -> Result, ChainStorageError> { + let mut cursor = self.exit_queue_read_cursor()?; + let prefix = create_exit_queue_prefix_key(sidechain_pk, epoch); + if !cursor.seek_range(&prefix)? { + return Ok(Vec::new()); + } + + let sidechain_bytes = sid_as_slice(sidechain_pk); + let mut validators = Vec::new(); + while let Some((key, vn)) = cursor.next()? { + let mut sections = key.section_iter([PK_SIZE, U64_SIZE]); + let sid = sections + .next() + .ok_or_else(|| ChainStorageError::DataInconsistencyDetected { + function: "ValidatorNodeStore::get_exiting_in_epoch", + details: "Malformed exit queue key".to_string(), + })?; + + if sid != sidechain_bytes { + // No further entries for this sidechain break; } - let height = u64::from_key_bytes(&key[32..40])?; - if height > end_height { + + let rec_epoch = sections + .next_be_u64() + .ok_or_else(|| ChainStorageError::DataInconsistencyDetected { + function: "ValidatorNodeStore::get_exiting_in_epoch", + details: "Malformed exit queue key".to_string(), + })?; + if rec_epoch != epoch.as_u64() { + // No further entries for this epoch break; } - shard_key = Some(s); + + validators.push(vn); } - Ok(shard_key) + + Ok(validators) + } + + pub fn get( + &self, + sidechain_pk: Option<&PublicKey>, + public_key: &PublicKey, + ) -> Result, ChainStorageError> { + let key = create_vn_key(sidechain_pk, public_key); + let vn = lmdb_get::<_, ValidatorNodeEntry>(self.txn, &self.db_validator_nodes, &key)?; + Ok(vn) } } +fn create_vn_key(sidechain_pk: Option<&PublicKey>, public_key: &PublicKey) -> ValidatorNodeStoreKey { + create_vn_key_raw(sid_as_slice(sidechain_pk), public_key.as_bytes()) +} +fn create_vn_key_raw(sidechain_pk: &[u8], public_key: &[u8]) -> ValidatorNodeStoreKey { + ValidatorNodeStoreKey::try_from_parts(&[sidechain_pk, public_key]) + .expect("create_key: Composite key length is incorrect") +} + +fn create_exit_queue_key(sidechain_pk: Option<&PublicKey>, epoch: VnEpoch, public_key: &PublicKey) -> ExitQueueKey { + ExitQueueKey::try_from_parts(&[ + sid_as_slice(sidechain_pk), + public_key.as_bytes(), + epoch.to_be_bytes().as_slice(), + ]) + .expect("create_key: Composite key length is incorrect") +} + +fn create_exit_queue_prefix_key(sidechain_pk: Option<&PublicKey>, epoch: VnEpoch) -> [u8; PK_SIZE + U64_SIZE] { + let mut buf = [0u8; PK_SIZE + U64_SIZE]; + if let Some(pk) = sidechain_pk { + buf[..PK_SIZE].copy_from_slice(pk.as_bytes()); + } + buf[PK_SIZE..].copy_from_slice(&epoch.to_be_bytes()); + buf +} + +fn create_activation_key(sidechain_pk: Option<&PublicKey>, epoch: VnEpoch) -> ActivationQueueKey { + ActivationQueueKey::try_from_parts(&[sid_as_slice(sidechain_pk), &epoch.to_be_bytes()]) + .expect("create_activation_key: Composite key length is incorrect") +} + +fn create_vn_store_prefix_key(sidechain_pk: Option<&PublicKey>, epoch: VnEpoch) -> [u8; PK_SIZE + U64_SIZE] { + let mut buf = [0u8; PK_SIZE + U64_SIZE]; + if let Some(pk) = sidechain_pk { + buf[..PK_SIZE].copy_from_slice(pk.as_bytes()); + } + buf[PK_SIZE..].copy_from_slice(&epoch.to_be_bytes()); + buf +} + +fn sid_as_slice(sidechain_pk: Option<&PublicKey>) -> &[u8] { + sidechain_pk.map_or([0u8; 32].as_slice(), |pk| pk.as_bytes()) +} + #[cfg(test)] mod tests { + use tari_common_types::types::Commitment; use tari_test_utils::unpack_enum; use super::*; @@ -305,44 +567,47 @@ mod tests { test_helpers::{make_hash, new_public_key}, }; - const DBS: &[&str] = &["validator_node_store", "validator_node_index"]; + const DBS: &[&str] = &[ + "validator_node_store", + "validator_node_activation_queue", + "validator_node_exit_queue", + ]; fn create_store<'a, Txn: Deref>>( db: &TempLmdbDatabase, txn: &'a Txn, ) -> ValidatorNodeStore<'a, Txn> { let store_db = db.get_db(DBS[0]).clone(); - let index_db = db.get_db(DBS[1]).clone(); - ValidatorNodeStore::new(txn, store_db, index_db) + let activation_queue = db.get_db(DBS[1]).clone(); + let exit_db = db.get_db(DBS[2]).clone(); + ValidatorNodeStore::new(txn, store_db, activation_queue, exit_db) } fn insert_n_vns( store: &ValidatorNodeStore<'_, WriteTransaction<'_>>, - start_height: u64, + start_epoch: u64, n: usize, - ) -> Vec { - let mut nodes = Vec::new(); + ) -> Vec { + let mut nodes = Vec::with_capacity(n); for i in 0..n { let public_key = new_public_key(); let shard_key = make_hash(public_key.as_bytes()); - store - .insert(start_height + i as u64, &ValidatorNodeEntry { - public_key: public_key.clone(), - shard_key, - commitment: Commitment::from_public_key(&new_public_key()), - ..Default::default() - }) - .unwrap(); - nodes.push(ValidatorNodeRegistrationInfo { - public_key, - sidechain_id: None, + let start_epoch = VnEpoch(start_epoch + i as u64); + let entry = ValidatorNodeEntry { + public_key: public_key.clone(), shard_key, - start_epoch: Default::default(), - original_registration: Default::default(), - minimum_value_promise: Default::default(), - }); + commitment: Commitment::from_public_key(&new_public_key()), + activation_epoch: start_epoch, + ..Default::default() + }; + store.insert(&entry).unwrap(); + nodes.push(entry); } - nodes.sort_by(|a, b| a.sidechain_id.cmp(&b.sidechain_id).then(a.shard_key.cmp(&b.shard_key))); + nodes.sort_by(|a, b| { + a.sidechain_public_key + .cmp(&b.sidechain_public_key) + .then(a.shard_key.cmp(&b.shard_key)) + }); nodes } @@ -355,10 +620,11 @@ mod tests { let txn = db.write_transaction(); let store = create_store(&db, &txn); let nodes = insert_n_vns(&store, 1, 3); - let set = store.get_vn_set(1, 3).unwrap(); - assert_eq!(set[0], nodes[0]); - assert_eq!(set[1], nodes[1]); - assert_eq!(set[2], nodes[2]); + let set = store.get_vn_set(None, VnEpoch(1), VnEpoch(3), 4).unwrap(); + for (i, node) in set.iter().enumerate() { + assert_eq!(*node, nodes[i]); + } + assert_eq!(set.len(), 3); } #[test] @@ -373,17 +639,13 @@ mod tests { commitment: Commitment::from_public_key(&new_public_key()), ..Default::default() }; - store.insert(1, &entry).unwrap(); - let err = store.insert(1, &entry).unwrap_err(); + store.insert(&entry).unwrap(); + let err = store.insert(&entry).unwrap_err(); unpack_enum!(ChainStorageError::KeyExists { .. } = err); } - } - - mod get_vn_set { - use super::*; #[test] - fn it_returns_a_deduped_set_of_validator_nodes() { + fn it_returns_key_exists_if_duplicate_inserted() { let db = TempLmdbDatabase::with_dbs(DBS); let txn = db.write_transaction(); let store = create_store(&db, &txn); @@ -391,80 +653,30 @@ mod tests { // Node 0 and 1 re-register at height 4 let s0 = make_hash(nodes[0].shard_key); - store - .insert(4, &ValidatorNodeEntry { + let err = store + .insert(&ValidatorNodeEntry { public_key: nodes[0].public_key.clone(), shard_key: s0, commitment: Commitment::from_public_key(&new_public_key()), ..Default::default() }) - .unwrap(); - - let s1 = make_hash(nodes[1].shard_key); - // The commitment is used last in the key and so changes the order they appear in the LMDB btree. - // We insert them in reverse order to demonstrate that insert order does not necessarily match the vn set - // order. - let mut ordered_commitments = vec![ - Commitment::from_public_key(&new_public_key()), - Commitment::from_public_key(&new_public_key()), - ]; - ordered_commitments.sort(); - store - .insert(5, &ValidatorNodeEntry { - public_key: nodes[1].public_key.clone(), - shard_key: make_hash(s1), - commitment: ordered_commitments[1].clone(), - ..Default::default() - }) - .unwrap(); - // This insert is counted as before the previous one because the commitment is "less" - store - .insert(5, &ValidatorNodeEntry { - public_key: nodes[1].public_key.clone(), - shard_key: s1, - commitment: ordered_commitments[0].clone(), - ..Default::default() - }) - .unwrap(); - - let set = store.get_vn_set(1, 5).unwrap(); - // s1 and s2 have replaced the previous shard keys, and are now ordered last since they come after node2 - assert_eq!(set.len(), 3); - assert_eq!(set.iter().filter(|s| s.public_key == nodes[1].public_key).count(), 1); + .unwrap_err(); + assert!(matches!(err, ChainStorageError::KeyExists { .. })); } } - mod get_shard_key { + mod get { use super::*; #[test] - fn it_returns_latest_shard_key() { + fn it_returns_the_validator_node() { let db = TempLmdbDatabase::with_dbs(DBS); let txn = db.write_transaction(); let store = create_store(&db, &txn); let nodes = insert_n_vns(&store, 1, 3); - let new_shard_key = make_hash(nodes[0].shard_key); - store - .insert(4, &ValidatorNodeEntry { - public_key: nodes[0].public_key.clone(), - shard_key: new_shard_key, - commitment: Commitment::from_public_key(&new_public_key()), - ..Default::default() - }) - .unwrap(); - - // Height 0-3 has original shard key - let s = store.get_shard_key(0, 3, &nodes[0].public_key).unwrap().unwrap(); - assert_eq!(s, nodes[0].shard_key); - // Height 0-4 has shard key that was replaced at height 4 - let s = store.get_shard_key(0, 4, &nodes[0].public_key).unwrap().unwrap(); - assert_eq!(s, new_shard_key); - assert!(store.get_shard_key(5, 5, &nodes[0].public_key).unwrap().is_none()); - let s = store.get_shard_key(0, 3, &nodes[1].public_key).unwrap().unwrap(); - assert_eq!(s, nodes[1].shard_key); - let s = store.get_shard_key(0, 3, &nodes[2].public_key).unwrap().unwrap(); - assert_eq!(s, nodes[2].shard_key); + let s = store.get(None, &nodes[0].public_key).unwrap().unwrap(); + assert_eq!(s, nodes[0]); } } } diff --git a/base_layer/core/src/chain_storage/mod.rs b/base_layer/core/src/chain_storage/mod.rs index 27020e3633..68d0dd4607 100644 --- a/base_layer/core/src/chain_storage/mod.rs +++ b/base_layer/core/src/chain_storage/mod.rs @@ -99,12 +99,12 @@ pub struct ChainTipData { pub total_accumulated_difficulty: U256, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct ValidatorNodeRegistrationInfo { pub public_key: PublicKey, pub sidechain_id: Option, pub shard_key: [u8; 32], - pub start_epoch: VnEpoch, + pub activation_epoch: VnEpoch, pub original_registration: ValidatorNodeRegistration, pub minimum_value_promise: MicroMinotari, } diff --git a/base_layer/core/src/chain_storage/tests/blockchain_database.rs b/base_layer/core/src/chain_storage/tests/blockchain_database.rs index 0e1de44bb1..40d82e29c2 100644 --- a/base_layer/core/src/chain_storage/tests/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/tests/blockchain_database.rs @@ -586,14 +586,13 @@ mod validator_node_merkle_root { use std::convert::TryFrom; use rand::rngs::OsRng; - use tari_comms::types::Signature; + use tari_common_types::epoch::VnEpoch; use tari_crypto::{keys::PublicKey as PublicKeyTrait, ristretto::RistrettoPublicKey}; use tari_mmr::sparse_merkle_tree::SparseMerkleTree; - use tari_utilities::ByteArray; use super::*; use crate::{ - chain_storage::{calculate_validator_node_mr, ValidatorNodeRegistrationInfo}, + chain_storage::calculate_validator_node_mr, transactions::{ key_manager::create_memory_db_key_manager, transaction_components::{OutputFeatures, ValidatorNodeSignature}, @@ -617,14 +616,8 @@ mod validator_node_merkle_root { let (blocks, outputs) = add_many_chained_blocks(1, &db, &key_manager).await; let (sk, public_key) = RistrettoPublicKey::random_keypair(&mut OsRng); - let signature = ValidatorNodeSignature::sign(&sk, &public_key, &[]); - let features = OutputFeatures::for_validator_node_registration( - public_key.clone(), - signature.signature().clone(), - public_key.clone(), - None, - None, - ); + let signature = ValidatorNodeSignature::sign(&sk, None, &public_key, VnEpoch::zero()); + let features = OutputFeatures::for_validator_node_registration(signature, public_key.clone(), None); let (tx, _outputs) = schema_to_transaction( &[txn_schema!( from: vec![outputs[0].clone()], @@ -649,20 +642,8 @@ mod validator_node_merkle_root { let consts = db.consensus_constants().unwrap(); let (_, _) = add_many_chained_blocks(usize::try_from(consts.epoch_length()).unwrap(), &db, &key_manager).await; - let shard_key = db - .get_shard_key(consts.epoch_length(), public_key.clone()) - .unwrap() - .unwrap(); - - let merkle_root = calculate_validator_node_mr(&[ValidatorNodeRegistrationInfo { - public_key, - sidechain_id: None, - shard_key, - start_epoch: Default::default(), - original_registration: Default::default(), - minimum_value_promise: Default::default(), - }]) - .unwrap(); + let vn = db.get_validator_node(None, public_key.clone()).unwrap().unwrap(); + let merkle_root = calculate_validator_node_mr(&[vn]).unwrap(); let tip = db.fetch_tip_header().unwrap(); assert_eq!(tip.header().validator_node_mr, merkle_root); @@ -675,16 +656,10 @@ mod validator_node_merkle_root { let (blocks, outputs) = add_many_chained_blocks(1, &db, &key_manager).await; let (sk, public_key) = RistrettoPublicKey::random_keypair(&mut OsRng); - let signature = ValidatorNodeSignature::sign(&sk, &public_key, &[]); let (sidechain_private, sidechain_public) = RistrettoPublicKey::random_keypair(&mut OsRng); - let sidechain_signature = Signature::sign(&sidechain_private, public_key.as_bytes(), &mut OsRng).unwrap(); - let features = OutputFeatures::for_validator_node_registration( - public_key.clone(), - signature.signature().clone(), - public_key.clone(), - Some(sidechain_public.clone()), - Some(sidechain_signature), - ); + let signature = ValidatorNodeSignature::sign(&sk, Some(&sidechain_public), &public_key, VnEpoch::zero()); + let features = + OutputFeatures::for_validator_node_registration(signature, public_key.clone(), Some(&sidechain_private)); let (tx, _outputs) = schema_to_transaction( &[txn_schema!( from: vec![outputs[0].clone()], @@ -709,20 +684,11 @@ mod validator_node_merkle_root { let consts = db.consensus_constants().unwrap(); let (_, _) = add_many_chained_blocks(usize::try_from(consts.epoch_length()).unwrap(), &db, &key_manager).await; - let shard_key = db - .get_shard_key(consts.epoch_length(), public_key.clone()) + let vn = db + .get_validator_node(Some(sidechain_public.clone()), public_key.clone()) .unwrap() .unwrap(); - - let merkle_root = calculate_validator_node_mr(&[ValidatorNodeRegistrationInfo { - public_key, - sidechain_id: Some(sidechain_public), - shard_key, - start_epoch: Default::default(), - original_registration: Default::default(), - minimum_value_promise: Default::default(), - }]) - .unwrap(); + let merkle_root = calculate_validator_node_mr(&[vn]).unwrap(); let tip = db.fetch_tip_header().unwrap(); assert_eq!(tip.header().validator_node_mr, merkle_root); diff --git a/base_layer/core/src/consensus/consensus_constants.rs b/base_layer/core/src/consensus/consensus_constants.rs index baad3bf93e..11129ad16d 100644 --- a/base_layer/core/src/consensus/consensus_constants.rs +++ b/base_layer/core/src/consensus/consensus_constants.rs @@ -123,9 +123,9 @@ pub struct ConsensusConstants { vn_registration_shuffle_interval: VnEpoch, /// Maximum number of validator nodes activated initially /// (in the first epoch when we do not have any vns yet). - vn_registration_max_vns_initial_epoch: u64, + vn_registration_max_vns_initial_epoch: u32, /// Maximum number of validator nodes activated in an epoch. - vn_registration_max_vns_per_epoch: u64, + vn_registration_max_vns_per_epoch: u32, } #[derive(Debug, Clone)] @@ -367,11 +367,11 @@ impl ConsensusConstants { epoch.as_u64() * self.vn_epoch_length } - pub fn vn_registration_max_vns_initial_epoch(&self) -> u64 { + pub fn vn_registration_max_vns_initial_epoch(&self) -> u32 { self.vn_registration_max_vns_initial_epoch } - pub fn vn_registration_max_vns_per_epoch(&self) -> u64 { + pub fn vn_registration_max_vns_per_epoch(&self) -> u32 { self.vn_registration_max_vns_per_epoch } @@ -1081,47 +1081,13 @@ mod test { } } - // This function is to ensure all OutputType variants are assessed in the tests - fn cycle_output_type_enum(output_type: OutputType) -> OutputType { - match output_type { - OutputType::Standard => OutputType::Coinbase, - OutputType::Coinbase => OutputType::Burn, - OutputType::Burn => OutputType::ValidatorNodeRegistration, - OutputType::ValidatorNodeRegistration => OutputType::CodeTemplateRegistration, - OutputType::CodeTemplateRegistration => OutputType::Standard, - } - } - - // This function is to ensure all RangeProofType variants are assessed in the tests - fn cycle_range_proof_type_enum(range_proof_type: RangeProofType) -> RangeProofType { - match range_proof_type { - RangeProofType::BulletProofPlus => RangeProofType::RevealedValue, - RangeProofType::RevealedValue => RangeProofType::BulletProofPlus, - } - } - #[test] fn range_proof_types_coverage() { - let mut output_type_enums = vec![OutputType::Standard]; - loop { - let next_variant = cycle_output_type_enum(*output_type_enums.last().unwrap()); - if output_type_enums.contains(&next_variant) { - break; - } - output_type_enums.push(next_variant); - } - - let mut range_proof_type_enums = vec![RangeProofType::BulletProofPlus]; - loop { - let next_variant = cycle_range_proof_type_enum(*range_proof_type_enums.last().unwrap()); - if range_proof_type_enums.contains(&next_variant) { - break; - } - range_proof_type_enums.push(next_variant); - } + let output_type_variants = OutputType::all(); + let range_proof_type_variants = RangeProofType::all(); let permitted_range_proof_types = ConsensusConstants::current_permitted_range_proof_types().to_vec(); - for item in &output_type_enums { + for item in output_type_variants { let entries = permitted_range_proof_types .iter() .filter(|&&x| x.0 == *item) @@ -1131,13 +1097,13 @@ mod test { } let permitted_range_proof_types = ConsensusConstants::all_range_proof_types().to_vec(); - for output_type in &output_type_enums { + for output_type in output_type_variants { let entries = permitted_range_proof_types .iter() .filter(|&&x| x.0 == *output_type) .collect::>(); assert_eq!(entries.len(), 1); - for range_proof_type in &range_proof_type_enums { + for range_proof_type in range_proof_type_variants { assert!(entries[0].1.iter().any(|&x| x == *range_proof_type)); } } diff --git a/base_layer/core/src/covenants/test.rs b/base_layer/core/src/covenants/test.rs index 14f8309d95..a6151f7a6c 100644 --- a/base_layer/core/src/covenants/test.rs +++ b/base_layer/core/src/covenants/test.rs @@ -31,6 +31,7 @@ use crate::{ BuildInfo, CodeTemplateRegistration, SideChainFeature, + SideChainFeatureData, TemplateType, TransactionInput, TransactionOutput, @@ -76,8 +77,9 @@ pub fn make_sample_sidechain_feature() -> SideChainFeature { }, binary_sha: Default::default(), binary_url: "https://github.com/tari-project/tari.git".try_into().unwrap(), - sidechain_id: None, - sidechain_id_knowledge_proof: None, }; - SideChainFeature::CodeTemplateRegistration(template_reg) + SideChainFeature { + data: SideChainFeatureData::CodeTemplateRegistration(template_reg), + sidechain_id: None, + } } diff --git a/base_layer/core/src/proto/sidechain_feature.proto b/base_layer/core/src/proto/sidechain_feature.proto index 49484de997..533d76f75c 100644 --- a/base_layer/core/src/proto/sidechain_feature.proto +++ b/base_layer/core/src/proto/sidechain_feature.proto @@ -8,60 +8,139 @@ import "types.proto"; package tari.types; message SideChainFeature { - oneof side_chain_feature { - ValidatorNodeRegistration validator_node_registration = 1; - TemplateRegistration template_registration = 2; - ConfidentialOutputData confidential_output = 3; - } + oneof side_chain_feature { + ValidatorNodeRegistration validator_node_registration = 1; + TemplateRegistration template_registration = 2; + ConfidentialOutputData confidential_output = 3; + EvictionProof eviction_proof = 4; + } + SidechainId sidechain_id = 5; +} + +message SidechainId { + bytes public_key = 1; + Signature knowledge_proof = 2; } message ValidatorNodeRegistration { - bytes public_key = 1; - Signature signature = 2; - bytes claim_public_key = 3; - bytes sidechain_id = 4; - Signature sidechain_id_knowledge_proof = 5; + bytes public_key = 1; + Signature signature = 2; + bytes claim_public_key = 3; } message TemplateRegistration { - bytes author_public_key = 1; - Signature author_signature = 2; - string template_name = 3; - uint32 template_version = 4; - TemplateType template_type = 5; - BuildInfo build_info = 6; - bytes binary_sha = 7; - string binary_url = 8; - bytes sidechain_id = 9; - Signature sidechain_id_knowledge_proof = 10; + bytes author_public_key = 1; + Signature author_signature = 2; + string template_name = 3; + uint32 template_version = 4; + TemplateType template_type = 5; + BuildInfo build_info = 6; + bytes binary_sha = 7; + string binary_url = 8; } message ConfidentialOutputData { - bytes claim_public_key = 1; - bytes sidechain_id = 2; - Signature sidechain_id_knowledge_proof = 3; + bytes claim_public_key = 1; } message TemplateType { - oneof template_type { - WasmInfo wasm = 1; - FlowInfo flow = 2; - ManifestInfo manifest =3; - } + oneof template_type { + WasmInfo wasm = 1; + FlowInfo flow = 2; + ManifestInfo manifest = 3; + } +} +message WasmInfo { + uint32 abi_version = 1; +} + +message FlowInfo { + +} + +message ManifestInfo { + +} + +message BuildInfo { + string repo_url = 1; + bytes commit_hash = 2; +} + +message EvictionProof { + CommitProof proof = 1; +} + +message CommitProof { + oneof version { + CommitProofV1 v1 = 1; + } } - message WasmInfo { - uint32 abi_version = 1; - } - message FlowInfo { +message CommitProofV1 { + bytes command = 1; + SidechainBlockCommitProof commit_proof = 2; +} + +message SidechainBlockCommitProof { + SidechainBlockHeader header = 1; + repeated CommitProofElement proof_elements = 2; +} + +message CommitProofElement { + oneof proof_element { + QuorumCertificate quorum_certificate = 1; + DummyChain dummy_chain = 2; + } +} - } +message DummyChain { + repeated ChainLink chain_links = 1; +} - message ManifestInfo { +message ChainLink { + bytes header_hash = 1; + bytes parent_id = 2; +} - } +message SidechainBlockHeader { + uint32 network = 1; + bytes parent_id = 2; + bytes justify_id = 3; + uint64 height = 4; + uint64 epoch = 5; + uint32 shard_group = 6; + bytes proposed_by = 7; + uint64 total_leader_fee = 8; + bytes state_merkle_root = 9; + bytes command_merkle_root = 10; + bool is_dummy = 11; + bytes foreign_indexes_hash = 12; + Signature signature = 13; + uint64 timestamp = 14; + uint64 base_layer_block_height = 15; + bytes base_layer_block_hash = 16; + bytes extra_data_hash = 17; +} - message BuildInfo { - string repo_url = 1; - bytes commit_hash = 2; +message EvictAtom { + bytes public_key = 1; } + +message QuorumCertificate { + bytes header_hash = 1; + bytes parent_id = 2; + repeated ValidatorSignature signatures = 3; + QuorumDecision decision = 4; +} + +enum QuorumDecision { + Accept = 0; + Reject = 1; +} + +message ValidatorSignature { + bytes public_key = 1; + Signature signature = 2; +} + diff --git a/base_layer/core/src/proto/sidechain_feature.rs b/base_layer/core/src/proto/sidechain_feature.rs index 3126010b11..68e174c77b 100644 --- a/base_layer/core/src/proto/sidechain_feature.rs +++ b/base_layer/core/src/proto/sidechain_feature.rs @@ -24,8 +24,23 @@ use std::convert::{TryFrom, TryInto}; +use prost::Message; +use tari_common::configuration::Network; use tari_common_types::types::{PublicKey, Signature}; use tari_max_size::MaxSizeString; +use tari_sidechain::{ + ChainLink, + CommandCommitProof, + CommandCommitProofV1, + CommitProofElement, + EvictNodeAtom, + EvictionProof, + QuorumCertificate, + QuorumDecision, + SidechainBlockCommitProof, + SidechainBlockHeader, + ValidatorQcSignature, +}; use tari_utilities::ByteArray; use crate::{ @@ -35,6 +50,8 @@ use crate::{ CodeTemplateRegistration, ConfidentialOutputData, SideChainFeature, + SideChainFeatureData, + SideChainId, TemplateType, ValidatorNodeRegistration, ValidatorNodeSignature, @@ -45,40 +62,61 @@ use crate::{ impl From for proto::types::SideChainFeature { fn from(value: SideChainFeature) -> Self { Self { - side_chain_feature: Some(value.into()), + side_chain_feature: Some(value.data.into()), + sidechain_id: value.sidechain_id.as_ref().map(Into::into), } } } -impl From for proto::types::side_chain_feature::SideChainFeature { - fn from(value: SideChainFeature) -> Self { +impl TryFrom for SideChainFeature { + type Error = String; + + fn try_from(features: proto::types::SideChainFeature) -> Result { + Ok(Self { + data: features + .side_chain_feature + .map(TryInto::try_into) + .ok_or("sidec_hain_feature not provided")??, + sidechain_id: features.sidechain_id.map(TryInto::try_into).transpose()?, + }) + } +} + +impl From for proto::types::side_chain_feature::SideChainFeature { + fn from(value: SideChainFeatureData) -> Self { match value { - SideChainFeature::ValidatorNodeRegistration(template_reg) => { + SideChainFeatureData::ValidatorNodeRegistration(template_reg) => { proto::types::side_chain_feature::SideChainFeature::ValidatorNodeRegistration(template_reg.into()) }, - SideChainFeature::CodeTemplateRegistration(template_reg) => { + SideChainFeatureData::CodeTemplateRegistration(template_reg) => { proto::types::side_chain_feature::SideChainFeature::TemplateRegistration(template_reg.into()) }, - SideChainFeature::ConfidentialOutput(output_data) => { + SideChainFeatureData::ConfidentialOutput(output_data) => { proto::types::side_chain_feature::SideChainFeature::ConfidentialOutput(output_data.into()) }, + SideChainFeatureData::EvictionProof(ref proof) => { + proto::types::side_chain_feature::SideChainFeature::EvictionProof(proof.into()) + }, } } } -impl TryFrom for SideChainFeature { +impl TryFrom for SideChainFeatureData { type Error = String; fn try_from(features: proto::types::side_chain_feature::SideChainFeature) -> Result { match features { proto::types::side_chain_feature::SideChainFeature::ValidatorNodeRegistration(vn_reg) => { - Ok(SideChainFeature::ValidatorNodeRegistration(vn_reg.try_into()?)) + Ok(SideChainFeatureData::ValidatorNodeRegistration(vn_reg.try_into()?)) }, proto::types::side_chain_feature::SideChainFeature::TemplateRegistration(template_reg) => { - Ok(SideChainFeature::CodeTemplateRegistration(template_reg.try_into()?)) + Ok(SideChainFeatureData::CodeTemplateRegistration(template_reg.try_into()?)) }, proto::types::side_chain_feature::SideChainFeature::ConfidentialOutput(output_data) => { - Ok(SideChainFeature::ConfidentialOutput(output_data.try_into()?)) + Ok(SideChainFeatureData::ConfidentialOutput(output_data.try_into()?)) + }, + proto::types::side_chain_feature::SideChainFeature::EvictionProof(proof) => { + Ok(SideChainFeatureData::EvictionProof(proof.try_into()?)) }, } } @@ -94,16 +132,6 @@ impl TryFrom for ValidatorNodeRegistrat let claim_public_key = PublicKey::from_canonical_bytes(&value.claim_public_key).map_err(|e| format!("claim_public_key: {}", e))?; - let sidechain_id = if value.sidechain_id.is_empty() { - None - } else { - Some(PublicKey::from_canonical_bytes(&value.sidechain_id).map_err(|e| format!("sidechain_id: {}", e))?) - }; - let sidechain_id_knowledge_proof = value - .sidechain_id_knowledge_proof - .map(|v| Signature::try_from(v).map_err(|e| format!("sidechain_id_knowledge_proof: {}", e))) - .transpose()?; - Ok(Self::new( ValidatorNodeSignature::new( public_key, @@ -113,8 +141,6 @@ impl TryFrom for ValidatorNodeRegistrat .ok_or("signature not provided")??, ), claim_public_key, - sidechain_id, - sidechain_id_knowledge_proof, )) } } @@ -125,8 +151,6 @@ impl From for proto::types::ValidatorNodeRegistration public_key: value.public_key().to_vec(), signature: Some(value.signature().into()), claim_public_key: value.claim_public_key().to_vec(), - sidechain_id: value.sidechain_id().map(|v| v.to_vec()).unwrap_or_default(), - sidechain_id_knowledge_proof: value.sidechain_id_knowledge_proof().map(|v| v.into()), } } } @@ -136,15 +160,6 @@ impl TryFrom for CodeTemplateRegistration { type Error = String; fn try_from(value: proto::types::TemplateRegistration) -> Result { - let sidechain_id = if value.sidechain_id.is_empty() { - None - } else { - Some(PublicKey::from_canonical_bytes(&value.sidechain_id).map_err(|e| format!("sidechain_id: {}", e))?) - }; - let sidechain_id_knowledge_proof = value - .sidechain_id_knowledge_proof - .map(|v| Signature::try_from(v).map_err(|e| format!("sidechain_id_knowledge_proof: {}", e))) - .transpose()?; Ok(Self { author_public_key: PublicKey::from_canonical_bytes(&value.author_public_key).map_err(|e| e.to_string())?, author_signature: value @@ -166,8 +181,6 @@ impl TryFrom for CodeTemplateRegistration { .ok_or("Build info not provided")??, binary_sha: value.binary_sha.try_into().map_err(|_| "Invalid commit sha")?, binary_url: MaxSizeString::try_from(value.binary_url).map_err(|e| e.to_string())?, - sidechain_id, - sidechain_id_knowledge_proof, }) } } @@ -183,8 +196,6 @@ impl From for proto::types::TemplateRegistration { build_info: Some(value.build_info.into()), binary_sha: value.binary_sha.to_vec(), binary_url: value.binary_url.to_string(), - sidechain_id: value.sidechain_id.map(|v| v.to_vec()).unwrap_or_default(), - sidechain_id_knowledge_proof: value.sidechain_id_knowledge_proof.map(|v| v.into()), } } } @@ -194,19 +205,8 @@ impl TryFrom for ConfidentialOutputData { type Error = String; fn try_from(value: proto::types::ConfidentialOutputData) -> Result { - let sidechain_id = if value.sidechain_id.is_empty() { - None - } else { - Some(PublicKey::from_canonical_bytes(&value.sidechain_id).map_err(|e| format!("sidechain_id: {}", e))?) - }; - let sidechain_id_knowledge_proof = value - .sidechain_id_knowledge_proof - .map(|v| Signature::try_from(v).map_err(|e| format!("sidechain_id_knowledge_proof: {}", e))) - .transpose()?; Ok(ConfidentialOutputData { claim_public_key: PublicKey::from_canonical_bytes(&value.claim_public_key).map_err(|e| e.to_string())?, - sidechain_id, - sidechain_id_knowledge_proof, }) } } @@ -215,8 +215,6 @@ impl From for proto::types::ConfidentialOutputData { fn from(value: ConfidentialOutputData) -> Self { Self { claim_public_key: value.claim_public_key.to_vec(), - sidechain_id: value.sidechain_id.map(|v| v.to_vec()).unwrap_or_default(), - sidechain_id_knowledge_proof: value.sidechain_id_knowledge_proof.map(|v| v.into()), } } } @@ -282,3 +280,345 @@ impl From for proto::types::BuildInfo { } } } + +// -------------------------------- SidechainId -------------------------------- // + +impl TryFrom for SideChainId { + type Error = String; + + fn try_from(value: proto::types::SidechainId) -> Result { + let public_key = PublicKey::from_canonical_bytes(&value.public_key).map_err(|e| e.to_string())?; + let knowledge_proof = value + .knowledge_proof + .map(Signature::try_from) + .ok_or("knowledge_proof not provided")??; + Ok(Self::new(public_key, knowledge_proof)) + } +} + +impl From<&SideChainId> for proto::types::SidechainId { + fn from(value: &SideChainId) -> Self { + Self { + public_key: value.public_key().to_vec(), + knowledge_proof: Some(value.knowledge_proof().into()), + } + } +} + +// -------------------------------- EvictionProof -------------------------------- // + +impl TryFrom for EvictionProof { + type Error = String; + + fn try_from(value: proto::types::EvictionProof) -> Result { + let proof = value.proof.ok_or("proof not provided")?.try_into()?; + Ok(EvictionProof::new(proof)) + } +} + +impl From<&EvictionProof> for proto::types::EvictionProof { + fn from(value: &EvictionProof) -> Self { + Self { + proof: Some(value.proof().into()), + } + } +} + +// -------------------------------- Commit proof -------------------------------- // + +impl TryFrom for CommandCommitProof { + type Error = String; + + fn try_from(value: proto::types::CommitProof) -> Result { + match value.version.ok_or("version not provided")? { + proto::types::commit_proof::Version::V1(v1) => Ok(Self::V1(v1.try_into()?)), + } + } +} + +impl From<&CommandCommitProof> for proto::types::CommitProof { + fn from(value: &CommandCommitProof) -> Self { + match value { + CommandCommitProof::V1(v1) => Self { + version: Some(proto::types::commit_proof::Version::V1(v1.into())), + }, + } + } +} + +impl TryFrom for CommandCommitProofV1 { + type Error = String; + + fn try_from(value: proto::types::CommitProofV1) -> Result { + let command = proto::types::EvictAtom::decode(value.command.as_slice()).map_err(|e| e.to_string())?; + Ok(CommandCommitProofV1 { + command: command.try_into()?, + commit_proof: value.commit_proof.ok_or("commit_proof not provided")?.try_into()?, + }) + } +} + +impl From<&CommandCommitProofV1> for proto::types::CommitProofV1 { + fn from(value: &CommandCommitProofV1) -> Self { + Self { + command: proto::types::EvictAtom::from(value.command()).encode_to_vec(), + commit_proof: Some(value.commit_proof().into()), + } + } +} + +// -------------------------------- SidechainBlockCommitProof -------------------------------- // + +impl TryFrom for SidechainBlockCommitProof { + type Error = String; + + fn try_from(value: proto::types::SidechainBlockCommitProof) -> Result { + Ok(Self { + header: value.header.ok_or("header not provided")?.try_into()?, + proof_elements: value + .proof_elements + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + }) + } +} + +impl From<&SidechainBlockCommitProof> for proto::types::SidechainBlockCommitProof { + fn from(value: &SidechainBlockCommitProof) -> Self { + Self { + header: Some(value.header().into()), + proof_elements: value.proof_elements().iter().map(Into::into).collect(), + } + } +} + +// -------------------------------- SidechainBlockHeader -------------------------------- // + +impl TryFrom for SidechainBlockHeader { + type Error = String; + + fn try_from(value: proto::types::SidechainBlockHeader) -> Result { + let network_byte = u8::try_from(value.network).map_err(|_| "Invalid network byte: overflows u8".to_string())?; + Network::try_from(network_byte).map_err(|err| format!("Invalid network byte: {}", err))?; + Ok(Self { + network: network_byte, + parent_id: value.parent_id.try_into().map_err(|_| "Invalid parent id")?, + justify_id: value.justify_id.try_into().map_err(|_| "Invalid justify id")?, + height: value.height, + epoch: value.epoch, + shard_group: value.shard_group, + proposed_by: PublicKey::from_canonical_bytes(&value.proposed_by) + .map_err(|_| "Invalid proposed_by public key")?, + total_leader_fee: value.total_leader_fee, + state_merkle_root: value + .state_merkle_root + .try_into() + .map_err(|_| "Invalid state merkle root")?, + command_merkle_root: value + .command_merkle_root + .try_into() + .map_err(|_| "Invalid command merkle root")?, + is_dummy: value.is_dummy, + foreign_indexes_hash: value + .foreign_indexes_hash + .try_into() + .map_err(|_| "Invalid foreign indexes hash")?, + signature: value + .signature + .ok_or("SidechainBlockHeader signature not provided")? + .try_into() + .map_err(|_| "Invalid signature")?, + timestamp: value.timestamp, + base_layer_block_height: value.base_layer_block_height, + base_layer_block_hash: value + .base_layer_block_hash + .try_into() + .map_err(|_| "Invalid base layer block hash")?, + extra_data_hash: value + .extra_data_hash + .try_into() + .map_err(|_| "Invalid extra data hash")?, + }) + } +} + +impl From<&SidechainBlockHeader> for proto::types::SidechainBlockHeader { + fn from(value: &SidechainBlockHeader) -> Self { + Self { + network: u32::from(value.network), + parent_id: value.parent_id.to_vec(), + justify_id: value.justify_id.to_vec(), + height: value.height, + epoch: value.epoch, + shard_group: value.shard_group, + proposed_by: value.proposed_by.to_vec(), + total_leader_fee: value.total_leader_fee, + state_merkle_root: value.state_merkle_root.to_vec(), + command_merkle_root: value.command_merkle_root.to_vec(), + is_dummy: value.is_dummy, + foreign_indexes_hash: value.foreign_indexes_hash.to_vec(), + signature: Some(value.signature().into()), + timestamp: value.timestamp, + base_layer_block_height: value.base_layer_block_height, + base_layer_block_hash: value.base_layer_block_hash.to_vec(), + extra_data_hash: value.extra_data_hash.to_vec(), + } + } +} + +// -------------------------------- CommitProofElement -------------------------------- // + +impl TryFrom for CommitProofElement { + type Error = String; + + fn try_from(value: proto::types::CommitProofElement) -> Result { + match value.proof_element.ok_or("proof element not provided")? { + proto::types::commit_proof_element::ProofElement::QuorumCertificate(qc) => { + Ok(CommitProofElement::QuorumCertificate(qc.try_into()?)) + }, + proto::types::commit_proof_element::ProofElement::DummyChain(chain) => Ok(CommitProofElement::DummyChain( + chain + .chain_links + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + )), + } + } +} + +impl From<&CommitProofElement> for proto::types::CommitProofElement { + fn from(value: &CommitProofElement) -> Self { + match value { + CommitProofElement::QuorumCertificate(qc) => Self { + proof_element: Some(proto::types::commit_proof_element::ProofElement::QuorumCertificate( + qc.into(), + )), + }, + CommitProofElement::DummyChain(chain) => Self { + proof_element: Some(proto::types::commit_proof_element::ProofElement::DummyChain( + proto::types::DummyChain { + chain_links: chain.iter().map(Into::into).collect(), + }, + )), + }, + } + } +} + +// -------------------------------- ChainLink -------------------------------- // + +impl TryFrom for ChainLink { + type Error = String; + + fn try_from(value: proto::types::ChainLink) -> Result { + Ok(Self { + header_contents_hash: value.header_hash.try_into().map_err(|_| "Invalid block id")?, + parent_id: value.parent_id.try_into().map_err(|_| "Invalid parent id")?, + }) + } +} + +impl From<&ChainLink> for proto::types::ChainLink { + fn from(value: &ChainLink) -> Self { + Self { + header_hash: value.header_contents_hash.to_vec(), + parent_id: value.parent_id.to_vec(), + } + } +} + +// -------------------------------- QuorumCertificate -------------------------------- // + +impl TryFrom for QuorumCertificate { + type Error = String; + + fn try_from(value: proto::types::QuorumCertificate) -> Result { + Ok(Self { + header_hash: value.header_hash.try_into().map_err(|_| "Invalid block body hash")?, + parent_id: value.parent_id.try_into().map_err(|_| "Invalid parent id")?, + signatures: value + .signatures + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?, + decision: proto::types::QuorumDecision::try_from(value.decision) + .map_err(|e| format!("Invalid QuorumDecision: {e}"))? + .into(), + }) + } +} + +impl From<&QuorumCertificate> for proto::types::QuorumCertificate { + fn from(value: &QuorumCertificate) -> Self { + Self { + parent_id: value.parent_id.to_vec(), + header_hash: value.header_hash.to_vec(), + signatures: value.signatures.iter().map(Into::into).collect(), + decision: proto::types::QuorumDecision::from(value.decision).into(), + } + } +} + +// -------------------------------- QuorumDecision -------------------------------- // + +impl From for QuorumDecision { + fn from(value: proto::types::QuorumDecision) -> Self { + match value { + proto::types::QuorumDecision::Accept => QuorumDecision::Accept, + proto::types::QuorumDecision::Reject => QuorumDecision::Reject, + } + } +} + +impl From for proto::types::QuorumDecision { + fn from(value: QuorumDecision) -> Self { + match value { + QuorumDecision::Accept => proto::types::QuorumDecision::Accept, + QuorumDecision::Reject => proto::types::QuorumDecision::Reject, + } + } +} + +// -------------------------------- ValidatorSignature -------------------------------- // + +impl TryFrom for ValidatorQcSignature { + type Error = String; + + fn try_from(value: proto::types::ValidatorSignature) -> Result { + Ok(Self { + public_key: PublicKey::from_canonical_bytes(&value.public_key).map_err(|e| e.to_string())?, + signature: value.signature.ok_or("signature not provided")?.try_into()?, + }) + } +} + +impl From<&ValidatorQcSignature> for proto::types::ValidatorSignature { + fn from(value: &ValidatorQcSignature) -> Self { + Self { + public_key: value.public_key().to_vec(), + signature: Some(value.signature().into()), + } + } +} + +// -------------------------------- EvictNodeAtom -------------------------------- // + +impl TryFrom for EvictNodeAtom { + type Error = String; + + fn try_from(value: proto::types::EvictAtom) -> Result { + Ok(Self::new( + PublicKey::from_canonical_bytes(&value.public_key).map_err(|e| e.to_string())?, + )) + } +} + +impl From<&EvictNodeAtom> for proto::types::EvictAtom { + fn from(value: &EvictNodeAtom) -> Self { + Self { + public_key: value.node_to_evict().to_vec(), + } + } +} diff --git a/base_layer/core/src/proto/transaction.rs b/base_layer/core/src/proto/transaction.rs index 683a05f511..749e585cf1 100644 --- a/base_layer/core/src/proto/transaction.rs +++ b/base_layer/core/src/proto/transaction.rs @@ -340,11 +340,7 @@ impl TryFrom for OutputFeatures { type Error = String; fn try_from(features: proto::types::OutputFeatures) -> Result { - let sidechain_feature = features - .sidechain_feature - .and_then(|features| features.side_chain_feature) - .map(SideChainFeature::try_from) - .transpose()?; + let sidechain_feature = features.sidechain_feature.map(SideChainFeature::try_from).transpose()?; let output_type = features .output_type diff --git a/base_layer/core/src/proto/types_impls.rs b/base_layer/core/src/proto/types_impls.rs index 6cf22b993c..4f12f33e17 100644 --- a/base_layer/core/src/proto/types_impls.rs +++ b/base_layer/core/src/proto/types_impls.rs @@ -20,12 +20,10 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{ - borrow::Borrow, - convert::{TryFrom, TryInto}, -}; +use std::convert::{TryFrom, TryInto}; -use tari_common_types::types::{ComAndPubSignature, Commitment, HashOutput, PrivateKey, PublicKey, Signature}; +use tari_common_types::types::{ComAndPubSignature, Commitment, HashOutput, PrivateKey, PublicKey}; +use tari_crypto::{hashing::DomainSeparation, signatures::SchnorrSignature}; use tari_utilities::{ByteArray, ByteArrayError}; use super::types as proto; @@ -49,7 +47,8 @@ impl From for proto::Commitment { } //---------------------------------- Signature --------------------------------------------// -impl TryFrom for Signature { + +impl TryFrom for SchnorrSignature { type Error = String; fn try_from(sig: proto::Signature) -> Result { @@ -59,12 +58,19 @@ impl TryFrom for Signature { Ok(Self::new(public_nonce, signature)) } } - -impl> From for proto::Signature { - fn from(sig: T) -> Self { +impl From<&SchnorrSignature> for proto::Signature { + fn from(sig: &SchnorrSignature) -> Self { + Self { + public_nonce: sig.get_public_nonce().to_vec(), + signature: sig.get_signature().to_vec(), + } + } +} +impl From> for proto::Signature { + fn from(sig: SchnorrSignature) -> Self { Self { - public_nonce: sig.borrow().get_public_nonce().to_vec(), - signature: sig.borrow().get_signature().to_vec(), + public_nonce: sig.get_public_nonce().to_vec(), + signature: sig.get_signature().to_vec(), } } } diff --git a/base_layer/core/src/test_helpers/blockchain.rs b/base_layer/core/src/test_helpers/blockchain.rs index 28b312ebda..9f00b64f85 100644 --- a/base_layer/core/src/test_helpers/blockchain.rs +++ b/base_layer/core/src/test_helpers/blockchain.rs @@ -31,6 +31,7 @@ use std::{ use tari_common::configuration::Network; use tari_common_types::{ chain_metadata::ChainMetadata, + epoch::VnEpoch, tari_address::TariAddress, types::{Commitment, FixedHash, HashOutput, PublicKey, Signature}, }; @@ -415,17 +416,82 @@ impl BlockchainBackend for TempDatabase { fn fetch_active_validator_nodes( &self, + sidechain_pk: Option<&PublicKey>, height: u64, - validator_network: Option, ) -> Result, ChainStorageError> { self.db .as_ref() .unwrap() - .fetch_active_validator_nodes(height, validator_network) + .fetch_active_validator_nodes(sidechain_pk, height) } - fn get_shard_key(&self, height: u64, public_key: PublicKey) -> Result, ChainStorageError> { - self.db.as_ref().unwrap().get_shard_key(height, public_key) + fn fetch_validators_activating_in_epoch( + &self, + sidechain_pk: Option<&PublicKey>, + epoch: VnEpoch, + ) -> Result, ChainStorageError> { + self.db + .as_ref() + .unwrap() + .fetch_validators_activating_in_epoch(sidechain_pk, epoch) + } + + fn fetch_validators_exiting_in_epoch( + &self, + sidechain_pk: Option<&PublicKey>, + epoch: VnEpoch, + ) -> Result, ChainStorageError> { + self.db + .as_ref() + .unwrap() + .fetch_validators_exiting_in_epoch(sidechain_pk, epoch) + } + + fn validator_node_is_active( + &self, + sidechain_pk: Option<&PublicKey>, + height: u64, + validator_node_pk: &PublicKey, + ) -> Result { + self.db + .as_ref() + .unwrap() + .validator_node_is_active(sidechain_pk, height, validator_node_pk) + } + + fn validator_node_is_active_for_shard_group( + &self, + sidechain_pk: Option<&PublicKey>, + height: u64, + validator_node_pk: &PublicKey, + shard_group: u32, + ) -> Result { + self.db.as_ref().unwrap().validator_node_is_active_for_shard_group( + sidechain_pk, + height, + validator_node_pk, + shard_group, + ) + } + + fn validator_nodes_count_for_shard_group( + &self, + sidechain_pk: Option<&PublicKey>, + end_epoch: VnEpoch, + shard_group: u32, + ) -> Result { + self.db + .as_ref() + .unwrap() + .validator_nodes_count_for_shard_group(sidechain_pk, end_epoch, shard_group) + } + + fn get_validator_node( + &self, + sidechain_pk: Option<&PublicKey>, + public_key: PublicKey, + ) -> Result, ChainStorageError> { + self.db.as_ref().unwrap().get_validator_node(sidechain_pk, public_key) } fn fetch_template_registrations( diff --git a/base_layer/core/src/transactions/key_manager/interface.rs b/base_layer/core/src/transactions/key_manager/interface.rs index 44a19cc555..45c21da0e5 100644 --- a/base_layer/core/src/transactions/key_manager/interface.rs +++ b/base_layer/core/src/transactions/key_manager/interface.rs @@ -241,7 +241,7 @@ pub trait TransactionKeyManagerInterface: KeyManagerInterface { &self, private_key_id: &TariKeyId, nonce: &TariKeyId, - challenge: &[u8; 64], + message: &[u8; 64], ) -> Result; async fn get_receiver_partial_metadata_signature( diff --git a/base_layer/core/src/transactions/key_manager/wrapper.rs b/base_layer/core/src/transactions/key_manager/wrapper.rs index 77385031e5..e00a546c3f 100644 --- a/base_layer/core/src/transactions/key_manager/wrapper.rs +++ b/base_layer/core/src/transactions/key_manager/wrapper.rs @@ -506,12 +506,12 @@ where TBackend: KeyManagerBackend + 'static &self, private_key_id: &TariKeyId, nonce: &TariKeyId, - challenge: &[u8; 64], + message: &[u8; 64], ) -> Result { self.transaction_key_manager_inner .read() .await - .sign_with_nonce_and_challenge(private_key_id, nonce, challenge) + .sign_with_nonce_and_challenge(private_key_id, nonce, message) .await } diff --git a/base_layer/core/src/transactions/transaction_components/mod.rs b/base_layer/core/src/transactions/transaction_components/mod.rs index 3d31a0fe36..1428da7310 100644 --- a/base_layer/core/src/transactions/transaction_components/mod.rs +++ b/base_layer/core/src/transactions/transaction_components/mod.rs @@ -62,8 +62,7 @@ pub use output_features::CoinBaseExtra; mod output_features_version; mod output_type; mod range_proof_type; -mod side_chain; - +pub mod side_chain; mod transaction; mod transaction_builder; mod transaction_input; diff --git a/base_layer/core/src/transactions/transaction_components/output_features.rs b/base_layer/core/src/transactions/transaction_components/output_features.rs index 4a7becef4f..013cb0528c 100644 --- a/base_layer/core/src/transactions/transaction_components/output_features.rs +++ b/base_layer/core/src/transactions/transaction_components/output_features.rs @@ -28,10 +28,11 @@ use std::{ use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use tari_common_types::types::{PublicKey, Signature}; +use tari_common_types::types::{PrivateKey, PublicKey}; use tari_max_size::MaxSizeBytes; +use tari_sidechain::EvictionProof; -use super::OutputFeaturesVersion; +use super::{OutputFeaturesVersion, SideChainFeatureData, SideChainId}; use crate::transactions::transaction_components::{ range_proof_type::RangeProofType, side_chain::SideChainFeature, @@ -46,7 +47,7 @@ use crate::transactions::transaction_components::{ pub type CoinBaseExtra = MaxSizeBytes<256>; /// Options for UTXO's -#[derive(Debug, Clone, Hash, PartialEq, Deserialize, Serialize, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq, BorshSerialize, BorshDeserialize)] pub struct OutputFeatures { pub version: OutputFeaturesVersion, /// Flags are the feature flags that differentiate between outputs, eg Coinbase all of which has different rules @@ -129,46 +130,68 @@ impl OutputFeatures { /// creates output features for a burned output with confidential output data pub fn create_burn_confidential_output( claim_public_key: PublicKey, - sidechain_id: Option, - sidechain_id_knowledge_proof: Option, + sidechain_deployment_key: Option<&PrivateKey>, ) -> OutputFeatures { + let output_data = ConfidentialOutputData { claim_public_key }; + let sidechain_id = sidechain_deployment_key.map(|k| SideChainId::sign(k, output_data.sidechain_id_message())); + OutputFeatures { output_type: OutputType::Burn, - sidechain_feature: Some(SideChainFeature::ConfidentialOutput(ConfidentialOutputData { - claim_public_key, + sidechain_feature: Some(SideChainFeature { + data: SideChainFeatureData::ConfidentialOutput(output_data), sidechain_id, - sidechain_id_knowledge_proof, - })), + }), ..Default::default() } } /// Creates template registration output features - pub fn for_template_registration(template_registration: CodeTemplateRegistration) -> OutputFeatures { + pub fn for_template_registration( + template_registration: CodeTemplateRegistration, + sidechain_deployment_key: Option<&PrivateKey>, + ) -> OutputFeatures { + let sidechain_id = + sidechain_deployment_key.map(|k| SideChainId::sign(k, template_registration.sidechain_id_message())); + OutputFeatures { output_type: OutputType::CodeTemplateRegistration, - sidechain_feature: Some(SideChainFeature::CodeTemplateRegistration(template_registration)), + sidechain_feature: Some(SideChainFeature { + data: SideChainFeatureData::CodeTemplateRegistration(template_registration), + sidechain_id, + }), ..Default::default() } } pub fn for_validator_node_registration( - public_key: PublicKey, - signature: Signature, + signature: ValidatorNodeSignature, claim_public_key: PublicKey, - sidechain_id: Option, - sidechain_id_knowledge_proof: Option, + sidechain_deployment_key: Option<&PrivateKey>, ) -> OutputFeatures { + let vn_reg = ValidatorNodeRegistration::new(signature, claim_public_key); + let sidechain_id = sidechain_deployment_key.map(|k| SideChainId::sign(k, vn_reg.sidechain_id_message())); OutputFeatures { output_type: OutputType::ValidatorNodeRegistration, - sidechain_feature: Some(SideChainFeature::ValidatorNodeRegistration( - ValidatorNodeRegistration::new( - ValidatorNodeSignature::new(public_key, signature), - claim_public_key, - sidechain_id, - sidechain_id_knowledge_proof, - ), - )), + sidechain_feature: Some(SideChainFeature { + data: SideChainFeatureData::ValidatorNodeRegistration(vn_reg), + sidechain_id, + }), + ..Default::default() + } + } + + pub fn for_validator_node_eviction( + eviction_proof: EvictionProof, + sidechain_deployment_key: Option<&PrivateKey>, + ) -> OutputFeatures { + let sidechain_id = + sidechain_deployment_key.map(|k| SideChainId::sign(k, eviction_proof.sidechain_id_message())); + OutputFeatures { + output_type: OutputType::SidechainProof, + sidechain_feature: Some(SideChainFeature { + data: SideChainFeatureData::EvictionProof(eviction_proof), + sidechain_id, + }), ..Default::default() } } diff --git a/base_layer/core/src/transactions/transaction_components/output_type.rs b/base_layer/core/src/transactions/transaction_components/output_type.rs index 4e7115b14a..90ad3bcce4 100644 --- a/base_layer/core/src/transactions/transaction_components/output_type.rs +++ b/base_layer/core/src/transactions/transaction_components/output_type.rs @@ -46,16 +46,20 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; #[repr(u8)] #[borsh(use_discriminant = true)] pub enum OutputType { - /// An standard output. + /// A standard output. Standard = 0, /// Output is a coinbase output, must not be spent until maturity. Coinbase = 1, /// Output is a burned output and can not be spent ever. Burn = 2, - /// Output defines a validator node registration + /// Output containing a validator node registration ValidatorNodeRegistration = 3, - /// Output defines a new re-usable code template. + /// Output containing a new re-usable code template. CodeTemplateRegistration = 4, + /// Output containing a sidechain checkpoint + SidechainCheckpoint = 5, + /// Output containing a sidechain proof. + SidechainProof = 6, } impl OutputType { @@ -77,13 +81,18 @@ impl OutputType { OutputType::Burn, OutputType::ValidatorNodeRegistration, OutputType::CodeTemplateRegistration, + OutputType::SidechainCheckpoint, + OutputType::SidechainProof, ] } pub fn is_sidechain_type(&self) -> bool { matches!( self, - OutputType::ValidatorNodeRegistration | OutputType::CodeTemplateRegistration | OutputType::Burn + OutputType::ValidatorNodeRegistration | + OutputType::CodeTemplateRegistration | + OutputType::SidechainCheckpoint | + OutputType::SidechainProof ) } @@ -108,7 +117,31 @@ impl Display for OutputType { #[cfg(test)] mod tests { use super::*; + #[test] + fn it_contains_all_enum_variants() { + let mut variant_bits = 0u8; + + fn check_duplicate(variant_bits: u8, mask: u8) { + if variant_bits & mask != 0 { + panic!("Duplicate variant"); + } + } + for variant in OutputType::all() { + let mask = 1 << *variant as u8; + check_duplicate(variant_bits, mask); + match variant { + OutputType::Standard => variant_bits |= mask, + OutputType::Coinbase => variant_bits |= mask, + OutputType::Burn => variant_bits |= mask, + OutputType::ValidatorNodeRegistration => variant_bits |= mask, + OutputType::CodeTemplateRegistration => variant_bits |= mask, + OutputType::SidechainCheckpoint => variant_bits |= mask, + OutputType::SidechainProof => variant_bits |= mask, + } + } + assert_eq!(variant_bits, 0b1111111); + } #[test] fn it_converts_from_byte_to_output_type() { assert_eq!(OutputType::from_byte(0), Some(OutputType::Standard)); @@ -116,6 +149,8 @@ mod tests { assert_eq!(OutputType::from_byte(2), Some(OutputType::Burn)); assert_eq!(OutputType::from_byte(3), Some(OutputType::ValidatorNodeRegistration)); assert_eq!(OutputType::from_byte(4), Some(OutputType::CodeTemplateRegistration)); + assert_eq!(OutputType::from_byte(5), Some(OutputType::SidechainCheckpoint)); + assert_eq!(OutputType::from_byte(6), Some(OutputType::SidechainProof)); for i in 5..=255 { assert_eq!(OutputType::from_byte(i), None); } diff --git a/base_layer/core/src/transactions/transaction_components/range_proof_type.rs b/base_layer/core/src/transactions/transaction_components/range_proof_type.rs index 50fb8b1195..4be9658f58 100644 --- a/base_layer/core/src/transactions/transaction_components/range_proof_type.rs +++ b/base_layer/core/src/transactions/transaction_components/range_proof_type.rs @@ -93,6 +93,27 @@ impl FromStr for RangeProofType { mod tests { use super::*; + #[test] + fn it_contains_all_enum_variants() { + let mut variant_bits = 0u8; + + fn check_duplicate(variant_bits: u8, mask: u8) { + if variant_bits & mask != 0 { + panic!("Duplicate variant"); + } + } + + for variant in RangeProofType::all() { + let mask = 1 << *variant as u8; + check_duplicate(variant_bits, mask); + match variant { + RangeProofType::BulletProofPlus => variant_bits |= mask, + RangeProofType::RevealedValue => variant_bits |= mask, + } + } + assert_eq!(variant_bits, 0b11); + } + #[test] fn it_converts_from_byte_to_output_type() { assert_eq!(RangeProofType::default(), RangeProofType::BulletProofPlus); diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/confidential_output.rs b/base_layer/core/src/transactions/transaction_components/side_chain/confidential_output.rs index 2cc5cba3f6..f74bc16795 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/confidential_output.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/confidential_output.rs @@ -22,11 +22,16 @@ use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use tari_common_types::types::{PublicKey, Signature}; +use tari_common_types::types::PublicKey; +use tari_utilities::ByteArray; -#[derive(Debug, Clone, Hash, PartialEq, Deserialize, Serialize, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq, BorshSerialize, BorshDeserialize)] pub struct ConfidentialOutputData { pub claim_public_key: PublicKey, - pub sidechain_id: Option, - pub sidechain_id_knowledge_proof: Option, +} + +impl ConfidentialOutputData { + pub fn sidechain_id_message(&self) -> &[u8] { + self.claim_public_key.as_bytes() + } } diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs b/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs index 6ccb03ea8a..e4ff939b2a 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs @@ -21,7 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. mod sidechain_feature; -pub use sidechain_feature::SideChainFeature; +pub use sidechain_feature::*; mod confidential_output; mod template_registration; @@ -29,12 +29,12 @@ mod validator_node_registration; mod validator_node_signature; use blake2::Blake2b; -pub use confidential_output::ConfidentialOutputData; +pub use confidential_output::*; use digest::consts::U32; use tari_crypto::{hash_domain, hashing::DomainSeparatedHasher}; -pub use template_registration::{BuildInfo, CodeTemplateRegistration, TemplateType}; -pub use validator_node_registration::ValidatorNodeRegistration; -pub use validator_node_signature::{ValidatorNodeHashDomain, ValidatorNodeSignature}; +pub use template_registration::*; +pub use validator_node_registration::*; +pub use validator_node_signature::*; hash_domain!( ContractAcceptanceHashDomain, diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/sidechain_feature.rs b/base_layer/core/src/transactions/transaction_components/side_chain/sidechain_feature.rs index 6d41c7d1ff..527aa8c975 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/sidechain_feature.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/sidechain_feature.rs @@ -21,7 +21,11 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use borsh::{BorshDeserialize, BorshSerialize}; +use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; +use tari_common_types::types::{PrivateKey, PublicKey, Signature}; +use tari_crypto::{keys::PublicKey as _, ristretto::RistrettoSchnorr}; +use tari_sidechain::EvictionProof; use crate::transactions::transaction_components::{ side_chain::confidential_output::ConfidentialOutputData, @@ -29,32 +33,110 @@ use crate::transactions::transaction_components::{ ValidatorNodeRegistration, }; -#[derive(Debug, Clone, Hash, PartialEq, Deserialize, Serialize, Eq, BorshSerialize, BorshDeserialize)] -pub enum SideChainFeature { - ValidatorNodeRegistration(ValidatorNodeRegistration), - CodeTemplateRegistration(CodeTemplateRegistration), - ConfidentialOutput(ConfidentialOutputData), +// NOTE: tari_mining_helper_ffi makes use of borsh encoding (not serde/bincode), therefore we need to +// implement BorshDeserialize on all types + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +pub struct SideChainFeature { + pub data: SideChainFeatureData, + pub sidechain_id: Option, } impl SideChainFeature { + pub fn is_sidechain_id_valid(&self) -> bool { + let Some(sidechain_id) = self.sidechain_id.as_ref() else { + return true; + }; + + match &self.data { + SideChainFeatureData::ValidatorNodeRegistration(reg) => sidechain_id.is_valid(reg.sidechain_id_message()), + SideChainFeatureData::CodeTemplateRegistration(reg) => sidechain_id.is_valid(reg.sidechain_id_message()), + SideChainFeatureData::ConfidentialOutput(output) => sidechain_id.is_valid(output.sidechain_id_message()), + SideChainFeatureData::EvictionProof(proof) => sidechain_id.is_valid(proof.sidechain_id_message()), + } + } + + pub fn data(&self) -> &SideChainFeatureData { + &self.data + } + + pub fn sidechain_id(&self) -> Option<&SideChainId> { + self.sidechain_id.as_ref() + } + + pub fn sidechain_public_key(&self) -> Option<&PublicKey> { + self.sidechain_id.as_ref().map(|id| id.public_key()) + } + pub fn code_template_registration(&self) -> Option<&CodeTemplateRegistration> { - match self { - Self::CodeTemplateRegistration(v) => Some(v), + match &self.data { + SideChainFeatureData::CodeTemplateRegistration(v) => Some(v), _ => None, } } pub fn validator_node_registration(&self) -> Option<&ValidatorNodeRegistration> { - match self { - Self::ValidatorNodeRegistration(v) => Some(v), + match &self.data { + SideChainFeatureData::ValidatorNodeRegistration(v) => Some(v), + _ => None, + } + } + + pub fn eviction_proof(&self) -> Option<&EvictionProof> { + match &self.data { + SideChainFeatureData::EvictionProof(v) => Some(v), _ => None, } } pub fn confidential_output_data(&self) -> Option<&ConfidentialOutputData> { - match self { - Self::ConfidentialOutput(v) => Some(v), + match &self.data { + SideChainFeatureData::ConfidentialOutput(v) => Some(v), _ => None, } } } + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +pub enum SideChainFeatureData { + ValidatorNodeRegistration(ValidatorNodeRegistration), + CodeTemplateRegistration(CodeTemplateRegistration), + ConfidentialOutput(ConfidentialOutputData), + EvictionProof(EvictionProof), +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +pub struct SideChainId { + public_key: PublicKey, + knowledge_proof: Signature, +} + +impl SideChainId { + pub fn new(public_key: PublicKey, knowledge_proof: Signature) -> Self { + Self { + public_key, + knowledge_proof, + } + } + + pub fn public_key(&self) -> &PublicKey { + &self.public_key + } + + pub fn knowledge_proof(&self) -> &Signature { + &self.knowledge_proof + } + + pub fn sign>(private_key: &PrivateKey, message: T) -> Self { + let public_key = PublicKey::from_secret_key(private_key); + Self { + public_key, + knowledge_proof: RistrettoSchnorr::sign(private_key, message, &mut OsRng) + .expect("RistrettoSchnorr::sign is completely infallible"), + } + } + + pub fn is_valid>(&self, message: T) -> bool { + self.knowledge_proof.verify(&self.public_key, message) + } +} diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/template_registration.rs b/base_layer/core/src/transactions/transaction_components/side_chain/template_registration.rs index e574f79cb4..66965aaeff 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/template_registration.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/template_registration.rs @@ -27,11 +27,10 @@ use serde::{Deserialize, Serialize}; use tari_common_types::types::{FixedHash, PublicKey, Signature}; use tari_hashing::TransactionHashDomain; use tari_max_size::{MaxSizeBytes, MaxSizeString}; -use tari_utilities::ByteArray; use crate::consensus::DomainSeparatedConsensusHasher; -#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] pub struct CodeTemplateRegistration { pub author_public_key: PublicKey, pub author_signature: Signature, @@ -41,20 +40,27 @@ pub struct CodeTemplateRegistration { pub build_info: BuildInfo, pub binary_sha: FixedHash, pub binary_url: MaxSizeString<255>, - pub sidechain_id: Option, - pub sidechain_id_knowledge_proof: Option, } impl CodeTemplateRegistration { - pub fn create_challenge(&self, public_nonce: &PublicKey) -> [u8; 64] { + /// Creates a signature message used to prove knowledge of the author secret key + pub fn create_signature_message(&self, public_nonce: &PublicKey) -> [u8; 64] { DomainSeparatedConsensusHasher::>::new("template_registration") .chain(&self.author_public_key) .chain(public_nonce) + .chain(&self.template_name) + .chain(&self.template_version) + .chain(&self.template_type) + .chain(&self.build_info) .chain(&self.binary_sha) - .chain(&self.sidechain_id.as_ref().map(|n| n.to_vec()).unwrap_or(vec![0u8; 32])) + .chain(&self.binary_url) .finalize() .into() } + + pub fn sidechain_id_message(&self) -> [u8; 64] { + self.create_signature_message(self.author_signature.get_public_nonce()) + } } // -------------------------------- TemplateType -------------------------------- // diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_registration.rs b/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_registration.rs index d6eb49722f..53e5a2576e 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_registration.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_registration.rs @@ -34,31 +34,23 @@ use tari_utilities::ByteArray; use crate::{consensus::DomainSeparatedConsensusHasher, transactions::transaction_components::ValidatorNodeSignature}; -#[derive(Default, Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] pub struct ValidatorNodeRegistration { signature: ValidatorNodeSignature, claim_public_key: PublicKey, - sidechain_id: Option, - sidechain_id_knowledge_proof: Option, } impl ValidatorNodeRegistration { - pub fn new( - signature: ValidatorNodeSignature, - claim_public_key: PublicKey, - sidechain_id: Option, - sidechain_id_knowledge_proof: Option, - ) -> Self { + pub fn new(signature: ValidatorNodeSignature, claim_public_key: PublicKey) -> Self { Self { signature, claim_public_key, - sidechain_id, - sidechain_id_knowledge_proof, } } - pub fn is_valid_signature_for(&self, msg: &[u8]) -> bool { - self.signature.is_valid_signature_for(&self.claim_public_key, msg) + pub fn is_valid_signature_for(&self, sidechain_pk: Option<&PublicKey>, epoch: VnEpoch) -> bool { + self.signature + .is_valid_signature_for(sidechain_pk, &self.claim_public_key, epoch) } pub fn derive_shard_key( @@ -92,12 +84,8 @@ impl ValidatorNodeRegistration { self.signature.signature() } - pub fn sidechain_id(&self) -> Option<&PublicKey> { - self.sidechain_id.as_ref() - } - - pub fn sidechain_id_knowledge_proof(&self) -> Option<&Signature> { - self.sidechain_id_knowledge_proof.as_ref() + pub fn sidechain_id_message(&self) -> &[u8] { + self.public_key().as_bytes() } } @@ -130,10 +118,8 @@ mod test { let claim_public_key = PublicKey::from_secret_key(&sk); ValidatorNodeRegistration::new( - ValidatorNodeSignature::sign(&sk, &claim_public_key, b"valid"), + ValidatorNodeSignature::sign(&sk, None, &claim_public_key, VnEpoch(1)), claim_public_key, - None, - None, ) } @@ -143,13 +129,13 @@ mod test { #[test] fn it_returns_true_for_valid_signature() { let reg = create_instance(); - assert!(reg.is_valid_signature_for(b"valid")); + assert!(reg.is_valid_signature_for(None, VnEpoch(1))); } #[test] fn it_returns_false_for_invalid_challenge() { let reg = create_instance(); - assert!(!reg.is_valid_signature_for(b"there's wally")); + assert!(!reg.is_valid_signature_for(None, VnEpoch(2))); } #[test] @@ -158,10 +144,8 @@ mod test { reg = ValidatorNodeRegistration::new( ValidatorNodeSignature::new(reg.public_key().clone(), Signature::default()), Default::default(), - None, - None, ); - assert!(!reg.is_valid_signature_for(b"valid")); + assert!(!reg.is_valid_signature_for(None, VnEpoch(1))); } } diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_signature.rs b/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_signature.rs index a9ba9cd48c..9c08f6bffd 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_signature.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_signature.rs @@ -20,22 +20,17 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use blake2::Blake2b; use borsh::{BorshDeserialize, BorshSerialize}; -use digest::consts::U64; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; -use tari_common_types::types::{PrivateKey, PublicKey, Signature}; -use tari_crypto::{hash_domain, hashing::DomainSeparatedHasher, keys::PublicKey as PublicKeyT}; -use tari_utilities::ByteArray; +use tari_common_types::{ + epoch::VnEpoch, + types::{PrivateKey, PublicKey, Signature}, +}; +use tari_crypto::keys::PublicKey as PublicKeyT; +use tari_hashing::layer2::validator_registration_hasher; -hash_domain!( - ValidatorNodeHashDomain, - "com.tari.base_layer.core.transactions.side_chain.validator_node", - 0 -); - -#[derive(Default, Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] pub struct ValidatorNodeSignature { public_key: PublicKey, signature: Signature, @@ -46,35 +41,49 @@ impl ValidatorNodeSignature { Self { public_key, signature } } - pub fn sign(private_key: &PrivateKey, claim_public_key: &PublicKey, msg: &[u8]) -> Self { + pub fn sign( + private_key: &PrivateKey, + sidechain_pk: Option<&PublicKey>, + claim_public_key: &PublicKey, + epoch: VnEpoch, + ) -> Self { let (secret_nonce, public_nonce) = PublicKey::random_keypair(&mut OsRng); let public_key = PublicKey::from_secret_key(private_key); - let challenge = Self::construct_challenge(&public_key, &public_nonce, claim_public_key, msg); + let challenge = + Self::construct_signature_message(&public_key, &public_nonce, sidechain_pk, claim_public_key, epoch); let signature = Signature::sign_raw_uniform(private_key, secret_nonce, &challenge) .expect("Sign cannot fail with 64-byte challenge and a RistrettoPublicKey"); Self { public_key, signature } } - fn construct_challenge( + fn construct_signature_message( public_key: &PublicKey, public_nonce: &PublicKey, + sidechain_pk: Option<&PublicKey>, claim_public_key: &PublicKey, - msg: &[u8], + epoch: VnEpoch, ) -> [u8; 64] { - let hasher = DomainSeparatedHasher::, ValidatorNodeHashDomain>::new_with_label("registration") - .chain(public_key.as_bytes()) - .chain(public_nonce.as_bytes()) - .chain(claim_public_key.as_bytes()) - .chain(msg); - digest::Digest::finalize(hasher).into() + validator_registration_hasher() + .chain(public_key) + .chain(public_nonce) + .chain(&sidechain_pk) + .chain(claim_public_key) + .chain(&epoch) + .finalize_into_array() } - pub fn is_valid_signature_for(&self, claim_public_key: &PublicKey, msg: &[u8]) -> bool { - let challenge = Self::construct_challenge( + pub fn is_valid_signature_for( + &self, + sidechain_pk: Option<&PublicKey>, + claim_public_key: &PublicKey, + epoch: VnEpoch, + ) -> bool { + let challenge = Self::construct_signature_message( &self.public_key, self.signature.get_public_nonce(), + sidechain_pk, claim_public_key, - msg, + epoch, ); self.signature.verify_raw_uniform(&self.public_key, &challenge) } diff --git a/base_layer/core/src/transactions/transaction_components/transaction_input.rs b/base_layer/core/src/transactions/transaction_components/transaction_input.rs index d659e4bfe4..09559c7a28 100644 --- a/base_layer/core/src/transactions/transaction_components/transaction_input.rs +++ b/base_layer/core/src/transactions/transaction_components/transaction_input.rs @@ -141,30 +141,18 @@ impl TransactionInput { } /// Populate the spent output data fields - pub fn add_output_data( - &mut self, - version: TransactionOutputVersion, - features: OutputFeatures, - commitment: Commitment, - script: TariScript, - sender_offset_public_key: PublicKey, - covenant: Covenant, - encrypted_data: EncryptedData, - metadata_signature: ComAndPubSignature, - rangeproof_hash: FixedHash, - minimum_value_promise: MicroMinotari, - ) { + pub fn add_output_data(&mut self, output: TransactionOutput) { self.spent_output = SpentOutput::OutputData { - version, - features, - commitment, - script, - sender_offset_public_key, - covenant, - encrypted_data, - metadata_signature, - rangeproof_hash, - minimum_value_promise, + version: output.version, + features: output.features, + commitment: output.commitment, + script: output.script, + sender_offset_public_key: output.sender_offset_public_key, + covenant: output.covenant, + encrypted_data: output.encrypted_data, + metadata_signature: output.metadata_signature, + rangeproof_hash: output.proof.map(|p| p.hash()).unwrap_or_else(FixedHash::zero), + minimum_value_promise: output.minimum_value_promise, }; } diff --git a/base_layer/core/src/transactions/transaction_components/transaction_output.rs b/base_layer/core/src/transactions/transaction_components/transaction_output.rs index c8854a16e3..ceb05f5baa 100644 --- a/base_layer/core/src/transactions/transaction_components/transaction_output.rs +++ b/base_layer/core/src/transactions/transaction_components/transaction_output.rs @@ -33,15 +33,18 @@ use borsh::{BorshDeserialize, BorshSerialize}; use digest::consts::{U32, U64}; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; -use tari_common_types::types::{ - ComAndPubSignature, - Commitment, - CommitmentFactory, - FixedHash, - PrivateKey, - PublicKey, - RangeProof, - RangeProofService, +use tari_common_types::{ + epoch::VnEpoch, + types::{ + ComAndPubSignature, + Commitment, + CommitmentFactory, + FixedHash, + PrivateKey, + PublicKey, + RangeProof, + RangeProofService, + }, }; use tari_crypto::{ commitment::HomomorphicCommitmentFactory, @@ -340,17 +343,22 @@ impl TransactionOutput { } pub fn verify_validator_node_signature(&self) -> Result<(), TransactionError> { - if let Some(validator_node_reg) = self - .features - .sidechain_feature - .as_ref() - .and_then(|f| f.validator_node_registration()) - { - if !validator_node_reg.is_valid_signature_for(&[]) { - return Err(TransactionError::InvalidSignatureError( - "Validator node signature is not valid!".to_string(), - )); - } + let Some(sidechain_features) = self.features.sidechain_feature.as_ref() else { + return Ok(()); + }; + + let Some(validator_node_reg) = sidechain_features.validator_node_registration() else { + return Ok(()); + }; + + if !validator_node_reg.is_valid_signature_for( + sidechain_features.sidechain_id.as_ref().map(|id| id.public_key()), + // TODO: use actual epoch + VnEpoch::zero(), + ) { + return Err(TransactionError::InvalidSignatureError( + "Validator node signature is not valid!".to_string(), + )); } Ok(()) } @@ -382,6 +390,10 @@ impl TransactionOutput { matches!(self.features.output_type, OutputType::Burn) } + pub fn is_burned_to_sidechain(&self) -> bool { + self.is_burned() && self.features.sidechain_feature.is_some() + } + /// Convenience function that calculates the challenge for the metadata commitment signature pub fn build_metadata_signature_challenge( version: &TransactionOutputVersion, diff --git a/base_layer/core/src/validation/aggregate_body/aggregate_body_chain_validator.rs b/base_layer/core/src/validation/aggregate_body/aggregate_body_chain_validator.rs index 840f3f0994..53f33cafeb 100644 --- a/base_layer/core/src/validation/aggregate_body/aggregate_body_chain_validator.rs +++ b/base_layer/core/src/validation/aggregate_body/aggregate_body_chain_validator.rs @@ -23,10 +23,10 @@ use std::collections::HashSet; use log::warn; -use tari_common_types::types::FixedHash; use tari_utilities::hex::Hex; use crate::{ + blocks::BlockHeader, chain_storage::BlockchainBackend, consensus::{ConsensusConstants, ConsensusManager}, transactions::{ @@ -35,10 +35,12 @@ use crate::{ }, validation::{ helpers::{ + check_eviction_proof, check_input_is_utxo, check_not_duplicate_txo, check_tari_encrypted_data_byte_size, check_tari_script_byte_size, + check_validator_node_registration, }, ValidationError, }, @@ -60,13 +62,13 @@ impl AggregateBodyChainLinkedValidator { pub fn validate( &self, body: &AggregateBody, - height: u64, + header: &BlockHeader, db: &B, ) -> Result { - let constants = self.consensus_manager.consensus_constants(height); + let constants = self.consensus_manager.consensus_constants(header.height); self.validate_consensus(body, db)?; - let body = self.validate_input_and_maturity(body, db, constants, height)?; + let body = self.validate_body(body, db, constants, header)?; Ok(body) } @@ -76,12 +78,12 @@ impl AggregateBodyChainLinkedValidator { Ok(()) } - fn validate_input_and_maturity( + fn validate_body( &self, body: &AggregateBody, db: &B, constants: &ConsensusConstants, - height: u64, + header: &BlockHeader, ) -> Result { // inputs may be "slim", only containing references to outputs // so we need to resolve those references, creating a new body in the process @@ -89,12 +91,12 @@ impl AggregateBodyChainLinkedValidator { // UNCHECKED: sorting has been checked by the AggregateBodyInternalConsistencyValidator let body = AggregateBody::new_sorted_unchecked(inputs, body.outputs().to_vec(), body.kernels().to_vec()); - validate_input_maturity(&body, height)?; + validate_input_maturity(&body, header.height)?; check_inputs_are_utxos(db, &body)?; - check_outputs(db, constants, &body)?; + check_outputs(db, constants, &body, header.height)?; verify_no_duplicated_inputs_outputs(&body)?; check_total_burned(&body)?; - verify_timelocks(&body, height)?; + verify_timelocks(&body, header.height)?; Ok(body) } @@ -107,18 +109,23 @@ fn validate_input_not_pruned( let mut inputs: Vec = body.inputs().clone(); for input in &mut inputs { if input.is_compact() { - let output = match db.fetch_output(&input.output_hash()) { + let input_output_hash = input.output_hash(); + // TODO: we clone the block body 3 times in validation and the inputs 1 more time here. We also discard + // the hydrated block in all cases expect block sync. This is unnecessarily slow and wasteful. + // SIMPLE REFACTOR: populate/hydrate the block inputs (which is owned, no cloning necessary) before + // performing validation. If hydration fails with UnknownInput, the block is invalid. + let output = match db.fetch_output(&input_output_hash) { Ok(val) => match val { Some(output_mined_info) => output_mined_info.output, None => { - let input_output_hash = input.output_hash(); + // Input is found in this block if let Some(found) = body.outputs().iter().find(|o| o.hash() == input_output_hash) { found.clone() } else { warn!( target: LOG_TARGET, "Input not found in database or block, commitment: {}, hash: {}", - input.commitment()?.to_hex(), input_output_hash.to_hex() + input.commitment()?.as_public_key(), input_output_hash, ); return Err(ValidationError::UnknownInput); } @@ -127,22 +134,7 @@ fn validate_input_not_pruned( Err(e) => return Err(ValidationError::from(e)), }; - let rp_hash = match output.proof { - Some(proof) => proof.hash(), - None => FixedHash::zero(), - }; - input.add_output_data( - output.version, - output.features, - output.commitment, - output.script, - output.sender_offset_public_key, - output.covenant, - output.encrypted_data, - output.metadata_signature, - rp_hash, - output.minimum_value_promise, - ); + input.add_output_data(output); } } @@ -225,6 +217,7 @@ pub fn check_outputs( db: &B, constants: &ConsensusConstants, body: &AggregateBody, + height: u64, ) -> Result<(), ValidationError> { let max_script_size = constants.max_script_byte_size(); let max_encrypted_data_size = constants.max_extra_encrypted_data_byte_size(); @@ -232,6 +225,8 @@ pub fn check_outputs( check_tari_script_byte_size(&output.script, max_script_size)?; check_tari_encrypted_data_byte_size(&output.encrypted_data, max_encrypted_data_size)?; check_not_duplicate_txo(db, output)?; + check_validator_node_registration(db, output, height)?; + check_eviction_proof(db, output, constants)?; } Ok(()) } diff --git a/base_layer/core/src/validation/aggregate_body/aggregate_body_internal_validator.rs b/base_layer/core/src/validation/aggregate_body/aggregate_body_internal_validator.rs index df097bcb92..ec215e575a 100644 --- a/base_layer/core/src/validation/aggregate_body/aggregate_body_internal_validator.rs +++ b/base_layer/core/src/validation/aggregate_body/aggregate_body_internal_validator.rs @@ -23,14 +23,17 @@ use std::{collections::HashSet, convert::TryInto}; use log::{trace, warn}; -use tari_common_types::types::{Commitment, CommitmentFactory, HashOutput, PrivateKey, PublicKey, RangeProofService}; +use tari_common_types::{ + epoch::VnEpoch, + types::{Commitment, CommitmentFactory, HashOutput, PrivateKey, PublicKey, RangeProofService}, +}; use tari_crypto::{ commitment::HomomorphicCommitmentFactory, keys::PublicKey as PublicKeyTrait, ristretto::pedersen::PedersenCommitment, }; use tari_script::ScriptContext; -use tari_utilities::{hex::Hex, ByteArray}; +use tari_utilities::hex::Hex; use crate::{ consensus::{ConsensusConstants, ConsensusManager}, @@ -40,6 +43,7 @@ use crate::{ transaction_components::{ transaction_output::batch_verify_range_proofs, KernelSum, + SideChainFeature, TransactionError, TransactionInput, TransactionKernel, @@ -117,15 +121,13 @@ impl AggregateBodyInternalConsistencyValidator { check_encrypted_data_byte_size(output, constants.max_extra_encrypted_data_byte_size())?; check_covenant_length(&output.covenant, constants.max_covenant_length())?; check_permitted_range_proof_types(constants, output)?; - check_validator_node_registration_utxo(constants, output)?; - check_template_registration_utxo(output)?; - check_confidential_output_utxo(output)?; + check_sidechain_features(constants, output)?; } check_weight(body, height, constants)?; check_sorting_and_duplicates(body)?; - // Check that the inputs are are allowed to be spent + // Check that inputs are allowed to be spent check_maturity(height, body.inputs())?; check_kernel_lock_height(height, body.kernels())?; @@ -147,58 +149,16 @@ impl AggregateBodyInternalConsistencyValidator { } } -fn check_confidential_output_utxo(output: &TransactionOutput) -> Result<(), ValidationError> { - if let Some(conf_output) = output.features.confidential_output_data() { - if conf_output.sidechain_id.is_some() || conf_output.sidechain_id_knowledge_proof.is_some() { - // If one of these is set, both must be set - if conf_output.sidechain_id.is_none() || conf_output.sidechain_id_knowledge_proof.is_none() { - return Err(ValidationError::ConfidentialOutputSidechainNotSet); - } - // If set, the signature must be valid - let sig_pub_key = conf_output.sidechain_id.as_ref().unwrap(); - if !conf_output - .sidechain_id_knowledge_proof - .as_ref() - .unwrap() - .verify(sig_pub_key, conf_output.claim_public_key.to_vec()) - { - return Err(ValidationError::ConfidentialOutputSidechainIdKnowledgeProofNotValid); - } - } - } - Ok(()) -} - -fn check_template_registration_utxo(output: &TransactionOutput) -> Result<(), ValidationError> { - if let Some(temp) = output.features.code_template_registration() { - let challenge = temp.create_challenge(temp.author_signature.get_public_nonce()); +fn check_template_registration_utxo(sidechain_feature: &SideChainFeature) -> Result<(), ValidationError> { + if let Some(template_reg) = sidechain_feature.code_template_registration() { + let message = template_reg.create_signature_message(template_reg.author_signature.get_public_nonce()); - if !temp + if !template_reg .author_signature - .verify_raw_uniform(&temp.author_public_key, &challenge) + .verify_raw_uniform(&template_reg.author_public_key, &message) { return Err(ValidationError::TemplateAuthorSignatureNotValid); } - - if temp.sidechain_id.is_some() || temp.sidechain_id_knowledge_proof.is_some() { - // If one of these is set, both must be set - if temp.sidechain_id.is_none() || temp.sidechain_id_knowledge_proof.is_none() { - return Err(ValidationError::TemplateRegistrationSidechainNotSet); - } - - // If set, the signature must be valid - let sig_pub_key = temp.sidechain_id.as_ref().unwrap(); - // TODO: I've used the author pub key here. The author signature includes the network - // as part of it's challenge. Should there be other fields in here as well? - if !temp - .sidechain_id_knowledge_proof - .as_ref() - .unwrap() - .verify(sig_pub_key, temp.author_public_key.to_vec()) - { - return Err(ValidationError::TemplateInvalidSidechainIdKnowledgeProof); - } - } } Ok(()) } @@ -449,45 +409,54 @@ fn check_total_burned(body: &AggregateBody) -> Result<(), ValidationError> { Ok(()) } +fn check_sidechain_features(constants: &ConsensusConstants, output: &TransactionOutput) -> Result<(), ValidationError> { + let Some(sidechain_feature) = output.features.sidechain_feature.as_ref() else { + return Ok(()); + }; + check_sidechain_id_proof_of_knowledge(sidechain_feature)?; + check_validator_node_registration_utxo(constants, output, sidechain_feature)?; + check_template_registration_utxo(sidechain_feature)?; + + Ok(()) +} +fn check_sidechain_id_proof_of_knowledge(sidechain_feature: &SideChainFeature) -> Result<(), ValidationError> { + if !sidechain_feature.is_sidechain_id_valid() { + return Err(ValidationError::ValidatorNodeInvalidSidechainIdKnowledgeProof); + } + + Ok(()) +} fn check_validator_node_registration_utxo( consensus_constants: &ConsensusConstants, utxo: &TransactionOutput, + sidechain_feature: &SideChainFeature, ) -> Result<(), ValidationError> { - if let Some(reg) = utxo.features.validator_node_registration() { - if utxo.minimum_value_promise < consensus_constants.validator_node_registration_min_deposit_amount() { - return Err(ValidationError::ValidatorNodeRegistrationMinDepositAmount { - min: consensus_constants.validator_node_registration_min_deposit_amount(), - actual: utxo.minimum_value_promise, - }); - } - if utxo.features.maturity < consensus_constants.validator_node_registration_min_lock_height() { - return Err(ValidationError::ValidatorNodeRegistrationMinLockHeight { - min: consensus_constants.validator_node_registration_min_lock_height(), - actual: utxo.features.maturity, - }); - } + let Some(reg) = sidechain_feature.validator_node_registration() else { + return Ok(()); + }; - // TODO: This should be the claim public key that is being signed - if !reg.is_valid_signature_for(&[]) { - return Err(ValidationError::InvalidValidatorNodeSignature); - } + if utxo.minimum_value_promise < consensus_constants.validator_node_registration_min_deposit_amount() { + return Err(ValidationError::ValidatorNodeRegistrationMinDepositAmount { + min: consensus_constants.validator_node_registration_min_deposit_amount(), + actual: utxo.minimum_value_promise, + }); + } + if utxo.features.maturity < consensus_constants.validator_node_registration_min_lock_height() { + return Err(ValidationError::ValidatorNodeRegistrationMinLockHeight { + min: consensus_constants.validator_node_registration_min_lock_height(), + actual: utxo.features.maturity, + }); + } - if reg.sidechain_id().is_some() || reg.sidechain_id_knowledge_proof().is_some() { - // If one of these is set, both must be set - if reg.sidechain_id().is_none() || reg.sidechain_id_knowledge_proof().is_none() { - return Err(ValidationError::ValidatorNodeRegistrationSidechainNotSet); - } - // If set, the signature must be valid - let sig_pub_key = reg.sidechain_id().unwrap(); - if !reg - .sidechain_id_knowledge_proof() - .unwrap() - .verify(sig_pub_key, reg.public_key().to_vec()) - { - return Err(ValidationError::ValidatorNodeInvalidSidechainIdKnowledgeProof); - } - } + // TODO: some additional "single use data" e.g. epoch should be used to prevent replay + // (assuming that we disallow the same validator node to register multiple times). + if !reg.is_valid_signature_for( + sidechain_feature.sidechain_id.as_ref().map(|id| id.public_key()), + VnEpoch::zero(), + ) { + return Err(ValidationError::InvalidValidatorNodeSignature); } + Ok(()) } diff --git a/base_layer/core/src/validation/block_body/block_body_full_validator.rs b/base_layer/core/src/validation/block_body/block_body_full_validator.rs index 5e02ced3e9..adc65a465e 100644 --- a/base_layer/core/src/validation/block_body/block_body_full_validator.rs +++ b/base_layer/core/src/validation/block_body/block_body_full_validator.rs @@ -75,11 +75,12 @@ impl BlockBodyFullValidator { } // validate the block body against the current db - let body = &block.body; - let height = block.header.height; // the inputs may be only references to outputs, that's why the validator returns a new body and we need a new - // block - let body = self.aggregate_body_chain_validator.validate(body, height, backend)?; + // block (TODO: refactor to avoid cloning the block so many times (here and elsewhere) by hydrating the inputs + // before calling the validator) + let body = self + .aggregate_body_chain_validator + .validate(&block.body, &block.header, backend)?; let block = Block::new(block.header.clone(), body); // validate the internal consistency of the block body diff --git a/base_layer/core/src/validation/error.rs b/base_layer/core/src/validation/error.rs index 9a734ee20f..a535dddf28 100644 --- a/base_layer/core/src/validation/error.rs +++ b/base_layer/core/src/validation/error.rs @@ -20,7 +20,11 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use tari_common_types::types::HashOutput; +use tari_common_types::{ + epoch::VnEpoch, + types::{HashOutput, PublicKey}, +}; +use tari_sidechain::SidechainProofValidationError; use thiserror::Error; use crate::{ @@ -123,29 +127,14 @@ pub enum ValidationError { ValidatorNodeRegistrationMinDepositAmount { min: MicroMinotari, actual: MicroMinotari }, #[error("Validator registration has invalid maturity {actual}, must be at least {min}")] ValidatorNodeRegistrationMinLockHeight { min: u64, actual: u64 }, - #[error( - "Sidechain not set for template registration. If sidechain_id is set, then sidechain_id_knowledge_proof must \ - also be set" - )] - TemplateRegistrationSidechainNotSet, #[error("Sidechain ID knowledge proof not valid for template registration")] TemplateInvalidSidechainIdKnowledgeProof, #[error("Author signature not valid for template registration")] TemplateAuthorSignatureNotValid, - #[error( - "Sidechain not set for confidential output. If sidechain_id is set, then sidechain_id_knowledge_proof must \ - also be set" - )] - ConfidentialOutputSidechainNotSet, #[error("Sidechain ID knowledge proof not valid for confidential output")] ConfidentialOutputSidechainIdKnowledgeProofNotValid, #[error("Validator node registration signature failed verification")] InvalidValidatorNodeSignature, - #[error( - "Sidechain not set for validator node registration. If sidechain_id is set, then sidechain_id_knowledge_proof \ - must also be set" - )] - ValidatorNodeRegistrationSidechainNotSet, #[error("Sidechain ID knowledge proof not valid for validator node registration")] ValidatorNodeInvalidSidechainIdKnowledgeProof, #[error( @@ -157,6 +146,16 @@ pub enum ValidationError { DifficultyError(#[from] DifficultyError), #[error("Covenant too large. Max size: {max_size}, Actual size: {actual_size}")] CovenantTooLarge { max_size: usize, actual_size: usize }, + #[error("Sidechain proof invalid: `{0}`")] + SidechainProofInvalid(#[from] SidechainProofValidationError), + #[error("Sidechain eviction proof submitted for unregistered validator {validator_pk}")] + SidechainEvictionProofValidatorNotFound { validator_pk: PublicKey }, + #[error( + "Sidechain eviction proof invalid: given epoch {epoch} is greater than the epoch at tip height {tip_height}" + )] + SidechainEvictionProofInvalidEpoch { epoch: VnEpoch, tip_height: u64 }, + #[error("Validator node already registered: {public_key}")] + ValidatorNodeAlreadyRegistered { public_key: PublicKey }, } // ChainStorageError has a ValidationError variant, so to prevent a cyclic dependency we use a string representation in @@ -205,15 +204,16 @@ impl ValidationError { err @ ValidationError::ValidatorNodeRegistrationMinDepositAmount { .. } | err @ ValidationError::ValidatorNodeRegistrationMinLockHeight { .. } | err @ ValidationError::InvalidValidatorNodeSignature | - err @ ValidationError::ValidatorNodeRegistrationSidechainNotSet | err @ ValidationError::ValidatorNodeInvalidSidechainIdKnowledgeProof | - err @ ValidationError::TemplateRegistrationSidechainNotSet | err @ ValidationError::TemplateInvalidSidechainIdKnowledgeProof | err @ ValidationError::TemplateAuthorSignatureNotValid | - err @ ValidationError::ConfidentialOutputSidechainNotSet | err @ ValidationError::ConfidentialOutputSidechainIdKnowledgeProofNotValid | err @ ValidationError::DifficultyError(_) | err @ ValidationError::CoinbaseExceedsMaxLimit | + err @ ValidationError::SidechainEvictionProofValidatorNotFound { .. } | + err @ ValidationError::SidechainProofInvalid(_) | + err @ ValidationError::SidechainEvictionProofInvalidEpoch { .. } | + err @ ValidationError::ValidatorNodeAlreadyRegistered { .. } | err @ ValidationError::CovenantTooLarge { .. } => Some(BanReason { reason: err.to_string(), ban_duration: BanPeriod::Long, diff --git a/base_layer/core/src/validation/helpers.rs b/base_layer/core/src/validation/helpers.rs index 160706defe..b226e8d9a4 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -23,9 +23,10 @@ use std::convert::TryFrom; use log::*; -use tari_common_types::types::FixedHash; +use tari_common_types::types::{FixedHash, PublicKey}; use tari_crypto::tari_utilities::{epoch_time::EpochTime, hex::Hex}; use tari_script::TariScript; +use tari_sidechain::SidechainProofValidationError; use crate::{ blocks::{BlockHeader, BlockHeaderValidationError, BlockValidationError}, @@ -262,6 +263,89 @@ pub fn check_not_duplicate_txo( Ok(()) } +/// This function checks the validity of the validator node registration if appliable +pub fn check_validator_node_registration( + db: &B, + output: &TransactionOutput, + height: u64, +) -> Result<(), ValidationError> { + let Some(sidechain_features) = output.features.sidechain_feature.as_ref() else { + return Ok(()); + }; + let Some(vn_reg) = sidechain_features.validator_node_registration() else { + return Ok(()); + }; + + if db.validator_node_is_active(sidechain_features.sidechain_public_key(), height, vn_reg.public_key())? { + return Err(ValidationError::ValidatorNodeAlreadyRegistered { + public_key: vn_reg.public_key().clone(), + }); + } + + Ok(()) +} + +/// This function checks the validity of the eviction proof if appliable +pub fn check_eviction_proof( + db: &B, + output: &TransactionOutput, + constants: &ConsensusConstants, +) -> Result<(), ValidationError> { + let Some(sidechain_features) = output.features.sidechain_feature.as_ref() else { + return Ok(()); + }; + let Some(eviction_proof) = sidechain_features.eviction_proof() else { + return Ok(()); + }; + + let epoch = eviction_proof.epoch(); + let shard_group = eviction_proof.shard_group(); + let height = constants.epoch_to_block_height(epoch); + + let chain_metadata = db.fetch_chain_metadata()?; + if height > chain_metadata.best_block_height() { + return Err(ValidationError::SidechainEvictionProofInvalidEpoch { + epoch, + tip_height: chain_metadata.best_block_height(), + }); + } + + let validator_pk = eviction_proof.node_to_evict(); + + // Only allow a single exit or evict on an active validator + if db.validator_node_is_active_for_shard_group( + sidechain_features.sidechain_public_key(), + height, + validator_pk, + shard_group, + )? { + return Err(ValidationError::SidechainEvictionProofValidatorNotFound { + validator_pk: validator_pk.clone(), + }); + } + + let committee_size = + db.validator_nodes_count_for_shard_group(sidechain_features.sidechain_public_key(), epoch, shard_group)?; + let quorum_threshold = committee_size - (committee_size - 1) / 3; + + let sidechain_pk = sidechain_features.sidechain_public_key(); + + let check_vn = |public_key: &PublicKey| { + let is_active = db + .validator_node_is_active_for_shard_group(sidechain_pk, height, public_key, shard_group) + .map_err(SidechainProofValidationError::internal_error)?; + if !is_active { + return Err(SidechainProofValidationError::InvalidProof { + details: format!("QC was signed by public key {public_key} that is not in the active validator set"), + }); + } + Ok(()) + }; + + eviction_proof.validate(quorum_threshold, &check_vn)?; + + Ok(()) +} pub fn check_mmr_roots(header: &BlockHeader, mmr_roots: &MmrRoots) -> Result<(), ValidationError> { if header.kernel_mr != mmr_roots.kernel_mr { diff --git a/base_layer/core/src/validation/transaction/transaction_chain_validator.rs b/base_layer/core/src/validation/transaction/transaction_chain_validator.rs index 7879cd50f5..a9c33d0b36 100644 --- a/base_layer/core/src/validation/transaction/transaction_chain_validator.rs +++ b/base_layer/core/src/validation/transaction/transaction_chain_validator.rs @@ -62,8 +62,9 @@ impl TransactionValidator for TransactionChainLinkedValida { let db = self.db.db_read_access()?; - let tip_height = db.fetch_chain_metadata()?.best_block_height(); - self.aggregate_body_validator.validate(&tx.body, tip_height, &*db)?; + let tip_header = db.fetch_tip_header()?; + self.aggregate_body_validator + .validate(&tx.body, tip_header.header(), &*db)?; }; Ok(()) diff --git a/base_layer/sidechain/Cargo.toml b/base_layer/sidechain/Cargo.toml new file mode 100644 index 0000000000..02279ffaaf --- /dev/null +++ b/base_layer/sidechain/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "tari_sidechain" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[dependencies] +tari_hashing = { workspace = true } +tari_crypto = { version = "0.21.0", features = ["borsh"] } +tari_utilities = "0.8" +tari_common_types = { workspace = true } + +thiserror = "2.0" +borsh = "1.5" +serde = { version = "1.0", features = ["derive"] } \ No newline at end of file diff --git a/base_layer/sidechain/src/command.rs b/base_layer/sidechain/src/command.rs new file mode 100644 index 0000000000..3acf4bda1d --- /dev/null +++ b/base_layer/sidechain/src/command.rs @@ -0,0 +1,44 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; +use tari_common_types::types::FixedHash; +use tari_hashing::layer2::command_hasher; + +use crate::eviction_proof::EvictNodeAtom; + +pub trait ToCommand { + fn to_command(&self) -> Command; +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +pub enum Command { + LocalOnly, + Prepare, + LocalPrepare, + AllPrepare, + SomePrepare, + LocalAccept, + AllAccept, + SomeAccept, + ForeignProposal, + MintConfidentialOutput, + SuspendNode, + ResumeNode, + EvictNode(EvictNodeAtom), + EndEpoch, +} + +impl Command { + pub fn evict_node(&self) -> Option<&EvictNodeAtom> { + match self { + Self::EvictNode(evict_node_atom) => Some(evict_node_atom), + _ => None, + } + } + + pub fn hash(&self) -> FixedHash { + command_hasher().chain(self).finalize().into() + } +} diff --git a/base_layer/sidechain/src/commit_proof.rs b/base_layer/sidechain/src/commit_proof.rs new file mode 100644 index 0000000000..f8ebca8c8f --- /dev/null +++ b/base_layer/sidechain/src/commit_proof.rs @@ -0,0 +1,252 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; +use tari_common_types::{ + epoch::VnEpoch, + types::{FixedHash, PrivateKey, PublicKey}, +}; +use tari_crypto::signatures::SchnorrSignature; +use tari_hashing::{ + layer2::{block_hasher, vote_signature_hasher}, + ValidatorNodeHashDomain, +}; + +use super::error::SidechainProofValidationError; +use crate::{ + command::{Command, ToCommand}, + validations::{check_command_inclusion_proof, check_proof_elements}, +}; + +pub type ValidatorBlockSignature = SchnorrSignature; +pub type CheckVnFunc<'a> = dyn Fn(&PublicKey) -> Result<(), SidechainProofValidationError> + 'a; + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +pub enum CommandCommitProof { + V1(CommandCommitProofV1), +} + +impl CommandCommitProof { + pub fn new(command: C, commit_proof: SidechainBlockCommitProof) -> Self { + Self::V1(CommandCommitProofV1 { command, commit_proof }) + } + + pub fn command(&self) -> &C { + match self { + CommandCommitProof::V1(v1) => &v1.command, + } + } + + pub fn epoch(&self) -> VnEpoch { + match self { + CommandCommitProof::V1(v1) => VnEpoch(v1.commit_proof.header().epoch), + } + } + + pub fn shard_group(&self) -> u32 { + match self { + CommandCommitProof::V1(v1) => v1.commit_proof.header().shard_group, + } + } + + pub fn validate_committed( + &self, + quorum_threshold: usize, + check_vn: &CheckVnFunc<'_>, + ) -> Result<(), SidechainProofValidationError> { + #[allow(clippy::single_match)] + match self { + CommandCommitProof::V1(v1) => v1.validate_committed(quorum_threshold, check_vn), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +pub struct CommandCommitProofV1 { + // TODO: Implement MerkleProof + // command_merkle_proof: MerkleProof, + pub command: C, + pub commit_proof: SidechainBlockCommitProof, +} + +impl CommandCommitProofV1 { + pub fn command(&self) -> &C { + &self.command + } + + pub fn commit_proof(&self) -> &SidechainBlockCommitProof { + &self.commit_proof + } + + pub fn validate_committed( + &self, + quorum_threshold: usize, + check_vn: &CheckVnFunc<'_>, + ) -> Result<(), SidechainProofValidationError> { + let command = self.command.to_command(); + self.commit_proof + .validate_committed(&command, quorum_threshold, check_vn) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +pub struct SidechainBlockCommitProof { + pub header: SidechainBlockHeader, + pub proof_elements: Vec, +} + +impl SidechainBlockCommitProof { + pub fn validate_committed( + &self, + command: &Command, + quorum_threshold: usize, + check_vn: &CheckVnFunc<'_>, + ) -> Result<(), SidechainProofValidationError> { + check_command_inclusion_proof(&self.header, command)?; + check_proof_elements( + &self.header, + &self.proof_elements, + check_vn, + QuorumDecision::Accept, + quorum_threshold, + )?; + + Ok(()) + } + + pub fn proof_elements(&self) -> &[CommitProofElement] { + &self.proof_elements + } + + pub fn header(&self) -> &SidechainBlockHeader { + &self.header + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +pub enum CommitProofElement { + QuorumCertificate(QuorumCertificate), + DummyChain(Vec), +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +pub struct ChainLink { + pub header_contents_hash: FixedHash, + pub parent_id: FixedHash, +} + +impl ChainLink { + pub fn calc_block_id(&self) -> FixedHash { + block_hasher() + .chain(&self.header_contents_hash) + .chain(&self.parent_id) + .finalize() + .into() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +pub struct SidechainBlockHeader { + pub network: u8, + pub parent_id: FixedHash, + pub justify_id: FixedHash, + pub height: u64, + pub epoch: u64, + pub shard_group: u32, + pub proposed_by: PublicKey, + pub total_leader_fee: u64, + pub state_merkle_root: FixedHash, + pub command_merkle_root: FixedHash, + /// If the block is a dummy block. + pub is_dummy: bool, + pub foreign_indexes_hash: FixedHash, + /// Signature of block by the proposer. + pub signature: ValidatorBlockSignature, + pub timestamp: u64, + pub base_layer_block_height: u64, + pub base_layer_block_hash: FixedHash, + pub extra_data_hash: FixedHash, +} + +impl SidechainBlockHeader { + pub fn calculate_contents_hash(&self) -> FixedHash { + block_hasher() + .chain(&self.network) + .chain(&self.justify_id) + .chain(&self.height) + .chain(&self.total_leader_fee) + .chain(&self.epoch) + .chain(&self.shard_group) + .chain(&self.proposed_by) + .chain(&self.state_merkle_root) + .chain(&self.is_dummy) + .chain(&self.command_merkle_root) + .chain(&self.foreign_indexes_hash) + .chain(&self.timestamp) + .chain(&self.base_layer_block_height) + .chain(&self.base_layer_block_hash) + .chain(&self.extra_data_hash) + .finalize() + .into() + } + + pub fn calculate_block_id(&self) -> FixedHash { + let contents_hash = self.calculate_contents_hash(); + block_hasher() + .chain(&self.parent_id) + .chain(&contents_hash) + .finalize() + .into() + } + + pub fn signature(&self) -> &ValidatorBlockSignature { + &self.signature + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +pub struct QuorumCertificate { + pub header_hash: FixedHash, + pub parent_id: FixedHash, + pub signatures: Vec, + pub decision: QuorumDecision, +} + +impl QuorumCertificate { + pub fn justifies_block(&self) -> FixedHash { + block_hasher() + .chain(&self.header_hash) + .chain(&self.parent_id) + .finalize() + .into() + } +} + +#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub enum QuorumDecision { + Accept, + Reject, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +pub struct ValidatorQcSignature { + pub public_key: PublicKey, + pub signature: ValidatorBlockSignature, +} + +impl ValidatorQcSignature { + #[must_use] + pub fn verify(&self, block_id: &FixedHash, decision: QuorumDecision) -> bool { + let message = vote_signature_hasher().chain(block_id).chain(&decision).finalize(); + self.signature.verify(&self.public_key, message) + } + + pub fn public_key(&self) -> &PublicKey { + &self.public_key + } + + pub fn signature(&self) -> &ValidatorBlockSignature { + &self.signature + } +} diff --git a/base_layer/sidechain/src/error.rs b/base_layer/sidechain/src/error.rs new file mode 100644 index 0000000000..65777d32f7 --- /dev/null +++ b/base_layer/sidechain/src/error.rs @@ -0,0 +1,24 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +#[derive(Debug, thiserror::Error)] +pub enum SidechainProofValidationError { + #[error("Unexpected command: {details}")] + UnexpectedCommand { details: String }, + #[error("Invalid proof: {details}")] + InvalidProof { details: String }, + #[error("Internal error: {details}")] + InternalError { details: String }, +} + +impl SidechainProofValidationError { + pub fn is_internal_error(&self) -> bool { + matches!(self, Self::InternalError { .. }) + } + + pub fn internal_error(details: T) -> Self { + Self::InternalError { + details: details.to_string(), + } + } +} diff --git a/base_layer/sidechain/src/eviction_proof.rs b/base_layer/sidechain/src/eviction_proof.rs new file mode 100644 index 0000000000..ce76482de1 --- /dev/null +++ b/base_layer/sidechain/src/eviction_proof.rs @@ -0,0 +1,74 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; +use tari_common_types::{epoch::VnEpoch, types::PublicKey}; +use tari_utilities::ByteArray; + +use super::error::SidechainProofValidationError; +use crate::{ + command::{Command, ToCommand}, + commit_proof::CommandCommitProof, + CheckVnFunc, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +pub struct EvictionProof { + proof: CommandCommitProof, +} + +impl EvictionProof { + pub fn new(proof: CommandCommitProof) -> Self { + Self { proof } + } + + pub fn proof(&self) -> &CommandCommitProof { + &self.proof + } + + pub fn epoch(&self) -> VnEpoch { + self.proof.epoch() + } + + pub fn shard_group(&self) -> u32 { + self.proof.shard_group() + } + + pub fn node_to_evict(&self) -> &PublicKey { + self.proof.command().node_to_evict() + } + + pub fn validate( + &self, + quorum_threshold: usize, + check_vn: &CheckVnFunc<'_>, + ) -> Result<(), SidechainProofValidationError> { + self.proof.validate_committed(quorum_threshold, check_vn) + } + + pub fn sidechain_id_message(&self) -> &[u8] { + self.node_to_evict().as_bytes() + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +pub struct EvictNodeAtom { + public_key: PublicKey, +} + +impl EvictNodeAtom { + pub fn new(public_key: PublicKey) -> Self { + Self { public_key } + } + + pub fn node_to_evict(&self) -> &PublicKey { + &self.public_key + } +} + +impl ToCommand for EvictNodeAtom { + fn to_command(&self) -> Command { + Command::EvictNode(self.clone()) + } +} diff --git a/base_layer/sidechain/src/lib.rs b/base_layer/sidechain/src/lib.rs new file mode 100644 index 0000000000..c45c0a2269 --- /dev/null +++ b/base_layer/sidechain/src/lib.rs @@ -0,0 +1,10 @@ +mod command; +mod commit_proof; +mod error; +mod eviction_proof; +mod validations; + +pub use command::*; +pub use commit_proof::*; +pub use error::*; +pub use eviction_proof::*; diff --git a/base_layer/sidechain/src/validations.rs b/base_layer/sidechain/src/validations.rs new file mode 100644 index 0000000000..09a84f1e22 --- /dev/null +++ b/base_layer/sidechain/src/validations.rs @@ -0,0 +1,203 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use std::{collections::HashSet, hash::Hash}; + +use tari_common_types::types::FixedHash; + +use crate::{ + CheckVnFunc, + Command, + CommitProofElement, + QuorumCertificate, + QuorumDecision, + SidechainBlockHeader, + SidechainProofValidationError, +}; + +pub fn check_command_inclusion_proof( + _header: &SidechainBlockHeader, + // inclusion_proof: &InclusionProof, + _command: &Command, +) -> Result<(), SidechainProofValidationError> { + // TODO: Implement + Ok(()) +} + +pub fn check_proof_elements( + header: &SidechainBlockHeader, + proof_elements: &[CommitProofElement], + check_vn: &CheckVnFunc<'_>, + expected_decision: QuorumDecision, + quorum_threshold: usize, +) -> Result<(), SidechainProofValidationError> { + check_proof_elements_num_qcs(proof_elements, 4)?; + + let mut last_parent = None::<&FixedHash>; + let mut proven_3_chain = 0usize; + for elem in proof_elements.iter().rev() { + match elem { + CommitProofElement::QuorumCertificate(qc) => { + validate_qc(qc, quorum_threshold, check_vn, expected_decision)?; + let justifies = qc.justifies_block(); + + if let Some(last_parent) = last_parent { + if *last_parent != justifies { + return Err(SidechainProofValidationError::InvalidProof { + details: "Parent block ID does not match the parent block ID in the quorum certificate" + .to_string(), + }); + } + } + + if proven_3_chain < 3 { + proven_3_chain += 1; + } + + last_parent = Some(&qc.parent_id); + }, + CommitProofElement::DummyChain(chain) => { + if proven_3_chain != 3 { + return Err(SidechainProofValidationError::InvalidProof { + details: format!( + "A 3-chain must be proven before a dummy chain. A chain of length {proven_3_chain} was \ + proven" + ), + }); + } + + let Some(parent) = last_parent else { + return Err(SidechainProofValidationError::InvalidProof { + details: "Dummy chain must be preceded by a quorum certificate".to_string(), + }); + }; + if chain.is_empty() { + return Err(SidechainProofValidationError::InvalidProof { + details: "Dummy chain must contain at least one element".to_string(), + }); + } + let mut check = *parent; + for link in chain { + let block_id = link.calc_block_id(); + if block_id != check { + return Err(SidechainProofValidationError::InvalidProof { + details: format!( + "Block ID in dummy chain does not match the parent block ID in the quorum \ + certificate. Expected {}, but got {}", + check, block_id + ), + }); + } + check = block_id; + last_parent = Some(&link.parent_id); + } + }, + } + } + + let Some(last_parent) = last_parent else { + // Not reachable because we check length of proof elements above + return Err(SidechainProofValidationError::InvalidProof { + details: "BUG: Proof must contain at least one quorum certificate".to_string(), + }); + }; + + let header_block_id = header.calculate_block_id(); + if *last_parent != header_block_id { + return Err(SidechainProofValidationError::InvalidProof { + details: format!( + "Last parent block ID does not match the block ID in the header. Expected {}, but got {}", + header_block_id, last_parent, + ), + }); + } + + Ok(()) +} +pub fn check_proof_elements_num_qcs( + proof_elems: &[CommitProofElement], + expected_len: usize, +) -> Result<(), SidechainProofValidationError> { + const MAX_PROOF_ELEMS: usize = 20; + if proof_elems.len() < MAX_PROOF_ELEMS { + return Err(SidechainProofValidationError::InvalidProof { + details: format!( + "Commit Proof contained too many proof elements. Expected at most {} but got {}", + MAX_PROOF_ELEMS, + proof_elems.len() + ), + }); + } + let num_qcs = proof_elems + .iter() + .filter(|elem| matches!(elem, CommitProofElement::QuorumCertificate(_))) + .count(); + if num_qcs < expected_len { + return Err(SidechainProofValidationError::InvalidProof { + details: format!( + "Expected at least {} proof elements, but got {}", + expected_len, + proof_elems.len() + ), + }); + } + Ok(()) +} + +fn validate_qc( + qc: &QuorumCertificate, + quorum_threshold: usize, + check_vn: &CheckVnFunc<'_>, + quorum_decision: QuorumDecision, +) -> Result<(), SidechainProofValidationError> { + if qc.signatures.len() < quorum_threshold { + return Err(SidechainProofValidationError::InvalidProof { + details: format!( + "Quorum certificate must contain at least {} signatures but contained {}", + quorum_threshold, + qc.signatures.len() + ), + }); + } + + if qc.decision != quorum_decision { + return Err(SidechainProofValidationError::InvalidProof { + details: format!( + "Quorum certificate decision must be {:?} but was {:?}", + quorum_decision, qc.decision + ), + }); + } + + if has_duplicates(qc.signatures.iter().map(|s| &s.public_key)) { + return Err(SidechainProofValidationError::InvalidProof { + details: "Quorum certificate contains more than one signature from a single validator".to_string(), + }); + } + + let block_id = qc.justifies_block(); + for sig in &qc.signatures { + check_vn(&sig.public_key)?; + + if !sig.verify(&block_id, quorum_decision) { + return Err(SidechainProofValidationError::InvalidProof { + details: "Invalid signature".to_string(), + }); + } + } + Ok(()) +} + +fn has_duplicates(iter: I) -> bool +where + I: IntoIterator + ExactSizeIterator, + T: Eq + Hash, +{ + let mut set = HashSet::with_capacity(iter.len()); + for item in iter { + if !set.insert(item) { + return true; + } + } + false +} diff --git a/base_layer/wallet/Cargo.toml b/base_layer/wallet/Cargo.toml index c4616ea4a6..82516f35fd 100644 --- a/base_layer/wallet/Cargo.toml +++ b/base_layer/wallet/Cargo.toml @@ -29,6 +29,7 @@ tari_p2p = { path = "../p2p", features = [ tari_script = { path = "../../infrastructure/tari_script", version = "1.7.0-pre.3" } tari_service_framework = { path = "../service_framework", version = "1.7.0-pre.3" } tari_shutdown = { path = "../../infrastructure/shutdown", version = "1.7.0-pre.3" } +tari_sidechain = { path = "../../base_layer/sidechain", version = "1.7.0-pre.3" } tari_utilities = { version = "0.8" } # Uncomment for tokio tracing via tokio-console (needs "tracing" features) diff --git a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs index 5d111f5cda..8ab5315cb7 100644 --- a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs +++ b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs @@ -194,6 +194,8 @@ where OutputType::Burn => OutputSource::Burn, OutputType::ValidatorNodeRegistration => OutputSource::ValidatorNodeRegistration, OutputType::CodeTemplateRegistration => OutputSource::CodeTemplateRegistration, + OutputType::SidechainCheckpoint => OutputSource::SidechainCheckpoint, + OutputType::SidechainProof => OutputSource::SidechainProof, } } diff --git a/base_layer/wallet/src/output_manager_service/storage/output_source.rs b/base_layer/wallet/src/output_manager_service/storage/output_source.rs index 19d178f0df..a738be063c 100644 --- a/base_layer/wallet/src/output_manager_service/storage/output_source.rs +++ b/base_layer/wallet/src/output_manager_service/storage/output_source.rs @@ -39,6 +39,8 @@ pub enum OutputSource { Burn, ValidatorNodeRegistration, CodeTemplateRegistration, + SidechainCheckpoint, + SidechainProof, } impl TryFrom for OutputSource { @@ -56,6 +58,8 @@ impl TryFrom for OutputSource { 7 => OutputSource::Burn, 8 => OutputSource::ValidatorNodeRegistration, 9 => OutputSource::CodeTemplateRegistration, + 10 => OutputSource::SidechainCheckpoint, + 11 => OutputSource::SidechainProof, _ => { return Err(OutputManagerStorageError::ConversionError { reason: "Was expecting value between 0 and 7 for OutputSource".to_string(), diff --git a/base_layer/wallet/src/transaction_service/error.rs b/base_layer/wallet/src/transaction_service/error.rs index f883862da1..becbef7a77 100644 --- a/base_layer/wallet/src/transaction_service/error.rs +++ b/base_layer/wallet/src/transaction_service/error.rs @@ -204,6 +204,8 @@ pub enum TransactionServiceError { NotSupported(String), #[error("Tari script error: {0}")] ScriptError(#[from] ScriptError), + #[error("Invalid validator node signature")] + InvalidValidatorNodeSignature, } impl From for TransactionServiceError { diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index 3de77deb1d..017b0e9838 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -55,6 +55,7 @@ use tari_crypto::ristretto::pedersen::PedersenCommitment; use tari_max_size::MaxSizeString; use tari_script::CheckSigSchnorrSignature; use tari_service_framework::reply_channel::SenderService; +use tari_sidechain::EvictionProof; use tari_utilities::hex::Hex; use tokio::sync::broadcast; use tower::Service; @@ -150,6 +151,13 @@ pub enum TransactionServiceRequest { fee_per_gram: MicroMinotari, sidechain_deployment_key: Option, }, + SubmitValidatorEvictionProof { + amount: MicroMinotari, + proof: EvictionProof, + fee_per_gram: MicroMinotari, + message: String, + sidechain_deployment_key: Option, + }, SendOneSidedTransaction { destination: TariAddress, amount: MicroMinotari, @@ -379,9 +387,25 @@ impl fmt::Display for TransactionServiceRequest { Self::GetFeePerGramStatsPerBlock { count } => { write!(f, "GetFeePerGramEstimatesPerBlock(count: {})", count,) }, - TransactionServiceRequest::RegisterCodeTemplate { template_name, .. } => { + Self::RegisterCodeTemplate { template_name, .. } => { write!(f, "RegisterCodeTemplate: {}", template_name) }, + Self::SubmitValidatorEvictionProof { + amount, + proof, + fee_per_gram, + message, + .. + } => { + write!( + f, + "SubmitValidatorEvictionProof (amount: {}, evicts: {}, fee_per_gram: {}, message: {})", + amount, + proof.node_to_evict(), + fee_per_gram, + message, + ) + }, } } } @@ -431,6 +455,9 @@ pub enum TransactionServiceResponse { tx_id: TxId, template_address: FixedHash, }, + ValidatorEvictionProofSent { + tx_id: TxId, + }, } #[derive(Clone, Debug, Hash, PartialEq, Eq, Default)] @@ -714,6 +741,30 @@ impl TransactionServiceHandle { } } + pub async fn submit_validator_eviction_proof( + &mut self, + amount: MicroMinotari, + proof: EvictionProof, + fee_per_gram: MicroMinotari, + sidechain_deployment_key: Option, + message: String, + ) -> Result { + match self + .handle + .call(TransactionServiceRequest::SubmitValidatorEvictionProof { + amount, + proof, + fee_per_gram, + message, + sidechain_deployment_key, + }) + .await?? + { + TransactionServiceResponse::TransactionSent(tx_id) => Ok(tx_id), + _ => Err(TransactionServiceError::UnexpectedApiResponse), + } + } + pub async fn send_one_sided_transaction( &mut self, destination: TariAddress, diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 260cbb75be..16ead339f6 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -36,6 +36,7 @@ use sha2::Sha256; use tari_common::configuration::Network; use tari_common_types::{ burnt_proof::BurntProof, + epoch::VnEpoch, key_branches::TransactionKeyManagerBranch, tari_address::{TariAddress, TariAddressFeatures}, transaction::{ImportStatus, TransactionDirection, TransactionStatus, TxId}, @@ -62,6 +63,7 @@ use tari_core::{ TemplateType, Transaction, TransactionOutput, + ValidatorNodeSignature, WalletOutputBuilder, }, transaction_protocol::{ @@ -77,7 +79,6 @@ use tari_core::{ use tari_crypto::{ keys::{PublicKey as PKtrait, SecretKey}, ristretto::{pedersen::PedersenCommitment, RistrettoPublicKey}, - signatures::SchnorrSignature, tari_utilities::ByteArray, }; use tari_key_manager::key_manager_service::KeyId; @@ -86,6 +87,7 @@ use tari_p2p::domain_message::DomainMessage; use tari_script::{push_pubkey_script, script, CheckSigSchnorrSignature, ExecutionStack, ScriptContext, TariScript}; use tari_service_framework::{reply_channel, reply_channel::Receiver}; use tari_shutdown::ShutdownSignal; +use tari_sidechain::EvictionProof; use tokio::{ sync::{mpsc, mpsc::Sender, oneshot, Mutex}, task::JoinHandle, @@ -836,6 +838,28 @@ where template_address, }) }, + TransactionServiceRequest::SubmitValidatorEvictionProof { + amount, + proof, + fee_per_gram, + message, + sidechain_deployment_key, + } => { + let rp = reply_channel.take().expect("Cannot be missing"); + self.submit_validator_eviction_proof( + amount, + proof, + sidechain_deployment_key, + UtxoSelectionCriteria::default(), + fee_per_gram, + message, + send_transaction_join_handles, + transaction_broadcast_join_handles, + rp, + ) + .await?; + return Ok(()); + }, TransactionServiceRequest::SendShaAtomicSwapTransaction( destination, amount, @@ -2167,20 +2191,11 @@ where "A sidechain deployment key was provided without a claim public key".to_string(), )); } - let (sidechain_id, sidechain_id_knowledge_proof) = match sidechain_deployment_key { - Some(key) => { - let sidechain_id = PublicKey::from_secret_key(&key); - let sidechain_id_knowledge_proof = - SchnorrSignature::sign(&key, claim_public_key.as_ref().unwrap().as_bytes(), &mut OsRng) - .map_err(|e| TransactionServiceError::InvalidBurnTransaction(format!("Error: {:?}", e)))?; - (Some(sidechain_id), Some(sidechain_id_knowledge_proof)) - }, - None => (None, None), - }; + let output_features = claim_public_key .as_ref() .cloned() - .map(|c| OutputFeatures::create_burn_confidential_output(c, sidechain_id, sidechain_id_knowledge_proof)) + .map(|c| OutputFeatures::create_burn_confidential_output(c, sidechain_deployment_key.as_ref())) .unwrap_or_else(OutputFeatures::create_burn_output); // Prepare sender part of the transaction @@ -2391,7 +2406,7 @@ where })) } - pub async fn register_validator_node( + async fn register_validator_node( &mut self, amount: MicroMinotari, validator_node_public_key: CommsPublicKey, @@ -2409,22 +2424,16 @@ where >, reply_channel: oneshot::Sender>, ) -> Result<(), TransactionServiceError> { - let (sidechain_id, sidechain_id_knowledge_proof) = match sidechain_deployment_key { - Some(k) => { - let sidechain_id = PublicKey::from_secret_key(&k); - let sidechain_id_knowledge_proof = - SchnorrSignature::sign(&k, validator_node_public_key.to_vec(), &mut OsRng) - .map_err(|e| TransactionServiceError::SidechainSigningError(e.to_string()))?; - (Some(sidechain_id), Some(sidechain_id_knowledge_proof)) - }, - None => (None, None), - }; + let signature = ValidatorNodeSignature::new(validator_node_public_key, validator_node_signature); + let sidechain_pk = sidechain_deployment_key.as_ref().map(PublicKey::from_secret_key); + if !signature.is_valid_signature_for(sidechain_pk.as_ref(), &validator_node_claim_public_key, VnEpoch(0)) { + return Err(TransactionServiceError::InvalidValidatorNodeSignature); + } + let output_features = OutputFeatures::for_validator_node_registration( - validator_node_public_key, - validator_node_signature, + signature, validator_node_claim_public_key, - sidechain_id, - sidechain_id_knowledge_proof, + sidechain_deployment_key.as_ref(), ); self.send_transaction( self.resources.interactive_tari_address.clone(), @@ -2442,7 +2451,41 @@ where Ok(()) } - pub async fn register_code_template( + async fn submit_validator_eviction_proof( + &mut self, + amount: MicroMinotari, + eviction_proof: EvictionProof, + sidechain_deployment_key: Option, + selection_criteria: UtxoSelectionCriteria, + fee_per_gram: MicroMinotari, + message: String, + join_handles: &mut FuturesUnordered< + JoinHandle>>, + >, + transaction_broadcast_join_handles: &mut FuturesUnordered< + JoinHandle>>, + >, + reply_channel: oneshot::Sender>, + ) -> Result<(), TransactionServiceError> { + let output_features = + OutputFeatures::for_validator_node_eviction(eviction_proof, sidechain_deployment_key.as_ref()); + self.send_transaction( + self.resources.interactive_tari_address.clone(), + amount, + selection_criteria, + output_features, + fee_per_gram, + message, + TransactionMetadata::default(), + join_handles, + transaction_broadcast_join_handles, + reply_channel, + ) + .await?; + Ok(()) + } + + async fn register_code_template( &mut self, fee_per_gram: MicroMinotari, template_name: String, @@ -2459,10 +2502,15 @@ where JoinHandle>>, >, ) -> Result<(TxId, FixedHash), TransactionServiceError> { + let author_key_id = self + .resources + .transaction_key_manager_service + .get_static_key(TransactionKeyManagerBranch::CodeTemplateAuthor.get_branch_key()) + .await?; let author_key = self .resources .transaction_key_manager_service - .get_next_key(TransactionKeyManagerBranch::CodeTemplateAuthor.get_branch_key()) + .get_public_key_at_key_id(&author_key_id) .await?; let (nonce_secret, nonce_pub) = RistrettoPublicKey::random_keypair(&mut OsRng); let nonce_id = self @@ -2470,19 +2518,8 @@ where .transaction_key_manager_service .import_key(nonce_secret) .await?; - let (sidechain_id, sidechain_id_knowledge_proof) = match sidechain_deployment_key { - Some(k) => ( - Some(PublicKey::from_secret_key(&k)), - Some( - SchnorrSignature::sign(&k, author_key.pub_key.as_bytes(), &mut OsRng) - .map_err(|e| TransactionServiceError::SidechainSigningError(e.to_string()))?, - ), - ), - None => (None, None), - }; - let mut template_registration = CodeTemplateRegistration { - author_public_key: author_key.pub_key.clone(), + author_public_key: author_key.clone(), author_signature: Signature::default(), template_name: template_name .try_into() @@ -2494,21 +2531,21 @@ where build_info, binary_sha, binary_url, - sidechain_id, - sidechain_id_knowledge_proof, }; - let challenge = template_registration.create_challenge(&nonce_pub); + let signature_message = template_registration.create_signature_message(&nonce_pub); + let author_sig = self .resources .transaction_key_manager_service - .sign_with_nonce_and_challenge(&author_key.key_id, &nonce_id, &challenge) + .sign_with_nonce_and_challenge(&author_key_id, &nonce_id, &signature_message) .await .map_err(|e| TransactionServiceError::SidechainSigningError(e.to_string()))?; template_registration.author_signature = author_sig; - let output_features = OutputFeatures::for_template_registration(template_registration); + let output_features = + OutputFeatures::for_template_registration(template_registration, sidechain_deployment_key.as_ref()); let tx_id = TxId::new_random(); let (fee, transaction) = self .resources diff --git a/clippy.toml b/clippy.toml index 8fcbb43341..f717bf989c 100644 --- a/clippy.toml +++ b/clippy.toml @@ -2,3 +2,4 @@ cognitive-complexity-threshold = 15 too-many-arguments-threshold = 12 # Set from 200 to size of a RistrettoPublicKey enum-variant-size-threshold = 216 +ignore-interior-mutability = ["bytes::Bytes", "tari_crypto::ristretto::RistrettoPublicKey"] \ No newline at end of file diff --git a/hashing/Cargo.toml b/hashing/Cargo.toml index a55fbc3984..a6ddd48fc4 100644 --- a/hashing/Cargo.toml +++ b/hashing/Cargo.toml @@ -12,11 +12,9 @@ license = "BSD-3-Clause" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tari_crypto = { version = "0.21.0", default-features = false, features = [ - "borsh", -] } +tari_crypto = { version = "0.21.0", default-features = false, features = ["borsh"] } + borsh = { version = "1.5", default-features = false } digest = { version = "0.10", default-features = false } +blake2 = { version = "0.10", default-features = false } -[dev-dependencies] -blake2 = "0.10" diff --git a/hashing/src/borsh_hasher.rs b/hashing/src/borsh_hasher.rs index b494149e36..2e7dace311 100644 --- a/hashing/src/borsh_hasher.rs +++ b/hashing/src/borsh_hasher.rs @@ -53,6 +53,11 @@ impl DomainSeparatedBorshHasher self.writer.0.finalize() } + pub fn finalize_into_array(self) -> [u8; I] + where [u8; I]: From> { + self.writer.0.finalize().into() + } + /// Update the hasher using the Borsh encoding of the input, which is assumed to be canonical. pub fn update_consensus_encode(&mut self, data: &T) { BorshSerialize::serialize(data, &mut self.writer) diff --git a/hashing/src/domains.rs b/hashing/src/domains.rs index f91795c27a..933c066e04 100644 --- a/hashing/src/domains.rs +++ b/hashing/src/domains.rs @@ -14,11 +14,13 @@ hash_domain!( "com.tari.base_layer.core.transactions.secure_nonce_kdf", 0 ); + hash_domain!( ValidatorNodeMerkleHashDomain, "com.tari.base_layer.core.validator_node_mmr", 1 ); + hash_domain!( WalletOutputEncryptionKeysDomain, "com.tari.base_layer.wallet.output_encryption_keys", @@ -36,3 +38,9 @@ hash_domain!( "com.tari.base_layer.core.transactions.key_manager", 1 ); + +hash_domain!( + ValidatorNodeHashDomain, + "com.tari.base_layer.core.transactions.side_chain.validator_node", + 0 +); diff --git a/hashing/src/layer2.rs b/hashing/src/layer2.rs new file mode 100644 index 0000000000..72d1b537d2 --- /dev/null +++ b/hashing/src/layer2.rs @@ -0,0 +1,54 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use blake2::{digest::consts::U32, Blake2b}; +use digest::consts::U64; +use tari_crypto::{ + hash_domain, + hashing::{DomainSeparatedHasher, DomainSeparation}, +}; + +use crate::{domains::ValidatorNodeMerkleHashDomain, DomainSeparatedBorshHasher, ValidatorNodeHashDomain}; + +hash_domain!(TariDanConsensusHashDomain, "com.tari.consensus", 0); + +pub type TariDomainHasher = DomainSeparatedBorshHasher>; +pub type TariConsensusHasher = TariDomainHasher; + +pub fn tari_hasher64(label: &'static str) -> TariDomainHasher { + TariDomainHasher::::new_with_label(label) +} + +fn tari_consensus_hasher(label: &'static str) -> TariConsensusHasher { + TariConsensusHasher::new_with_label(label) +} + +pub fn validator_registration_hasher() -> TariDomainHasher { + tari_hasher64("registration") +} + +pub fn block_hasher() -> TariConsensusHasher { + tari_consensus_hasher("Block") +} + +pub fn command_hasher() -> TariConsensusHasher { + tari_consensus_hasher("Command") +} + +pub fn quorum_certificate_hasher() -> TariConsensusHasher { + tari_consensus_hasher("QuorumCertificate") +} + +pub fn vote_signature_hasher() -> TariConsensusHasher { + tari_consensus_hasher("VoteSignature") +} + +pub fn extra_data_hasher() -> TariConsensusHasher { + tari_consensus_hasher("ExtraData") +} + +pub fn foreign_indexes_hasher() -> TariConsensusHasher { + tari_consensus_hasher("ForeignIndexes") +} + +pub type ValidatorNodeBmtHasherBlake2b = DomainSeparatedHasher, ValidatorNodeMerkleHashDomain>; diff --git a/hashing/src/lib.rs b/hashing/src/lib.rs index ce560d90b4..07de0f23df 100644 --- a/hashing/src/lib.rs +++ b/hashing/src/lib.rs @@ -27,4 +27,7 @@ mod domains; pub use domains::*; mod borsh_hasher; + pub use borsh_hasher::*; + +pub mod layer2;