mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
cert: the weight clause Go runs last, the set admitted whole, and one workspace
THE TOTAL. `Register` in `validator/registration.go` runs seven clauses, and this port had six. The seventh is `math.Add64` on the running weight: a set whose weights sum past what a `uint64` holds is refused entire, before any of it is admitted. Without it `total_stake` folded with `saturating_add` and `voted_stake` summed with `saturating_add`, so a set of four validators at 2^63 reported `u64::MAX` staked AND `u64::MAX` voted — and `u64::MAX > floor(2·u64::MAX/3)`. A certificate carrying HALF the stake cleared the two-thirds export floor, which is the rung a bridge reads. Clamping is what makes the two sides agree on a number neither can represent, so neither clamps any more. The set carries its total as a fact established at admission with `checked_add` — one source of truth, decided at the one place that still has a registration to refuse — and `voted_stake` is checked too: the voters are a subset of the members, so against a set this crate admitted the overflow is unreachable, and reaching it is evidence about the stake source rather than about the votes. `weighted_quasar` takes the same treatment: a vector with no representable total leaves by the same door as a zero total, since a plain sum WRAPS to a small total and sizes the count gate below the stake quorum it exists to anticipate. THE SET. `insert` admits one validator and reports on that one, which is a property no set can be built out of: a caller that inserts in a loop and logs what failed is left holding a set missing a member, with `n` short by one and the total short by that member's stake — the two numbers every threshold is read against, wrong, with nothing to say so. `ValidatorSet::register` is the port of Go's whole-set door: sorted by node id so which duplicate a set is refused on cannot depend on the order a caller built its input, and all-or-nothing because the set under construction is a local. `insert` stays for a membership change to a set already live, and now says so. THE BINDING. `pkg/typescript` path-depends on `pkg/rust` and could not compile: narrowing the node id from 32 bytes to 20 left two `NodeID::from([u8; 32])` calls with no impl. It is a 20-byte API now, and the 20 bytes come from 20-byte input — `arr[..20]` would reintroduce exactly the collision the narrowing exists to prevent, so a hex string of the wrong length is refused, never trimmed. The width is `size_of::<cert::NodeId>()`, taken from the core rather than restated, so it cannot drift again. `addValidator` also stopped discarding the core's answer, which reported a validator as added when the set had refused it. And the two crates are one workspace, because that is what they already were. Built separately they can disagree and did — nothing built both, so nothing noticed. `cargo test` at the root now builds both, which turns this class of break from a review finding into a compile error. It also surfaced two tests in the binding that had been failing unnoticed: they voted with unregistered ids, from before only members were counted. No signed byte moves. `canonical_vote_message` is untouched, the node id enters no signed message, and the Go corpus and the frozen `pop` vectors pass unchanged. Verified: 97 tests green (92 rust, 5 napi), clippy clean at the workspace root, the binding's 29 JS tests green against a linux build of it. Red's PoC — four validators of 2^63, two voting, read at QUASAR — returned `Ok(())` before and now dies at registration with `WeightOverflow`. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
+4
-1
@@ -86,7 +86,10 @@ node_modules
|
||||
pkg/rust/target
|
||||
.dev*
|
||||
|
||||
# Language binding build artifacts
|
||||
# Language binding build artifacts. The Rust crates are one workspace, so cargo
|
||||
# writes to /target at the root; the per-crate paths predate that and are kept
|
||||
# for a crate built on its own.
|
||||
/target/
|
||||
pkg/rust/target/
|
||||
pkg/python/.venv/
|
||||
pkg/python/__pycache__/
|
||||
|
||||
Generated
+332
-144
@@ -4,9 +4,9 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -19,27 +19,21 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.11"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
@@ -57,34 +51,34 @@ dependencies = [
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn",
|
||||
"shlex 1.3.0",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
version = "2.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.8.2"
|
||||
version = "1.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0"
|
||||
checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"constant_time_eq",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blst"
|
||||
version = "0.3.16"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45"
|
||||
checksum = "c20659f9bbee16cbbd2f7393e40ab6309f5a98f76a2eb57a995ec508b72387fe"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"glob",
|
||||
@@ -94,9 +88,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
@@ -106,12 +100,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.36"
|
||||
version = "1.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54"
|
||||
checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
"shlex 2.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -125,9 +119,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
@@ -158,9 +152,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
@@ -169,18 +163,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.47"
|
||||
version = "4.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931"
|
||||
checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.47"
|
||||
version = "4.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6"
|
||||
checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
@@ -188,24 +182,42 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.5"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.54"
|
||||
version = "0.1.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
|
||||
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
@@ -242,9 +254,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
@@ -252,18 +264,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
version = "0.9.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
@@ -272,38 +284,73 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
name = "ctor"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.1"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d"
|
||||
checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.6.0"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
|
||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||
checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
@@ -322,25 +369,26 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.78"
|
||||
version = "0.3.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738"
|
||||
checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.175"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@@ -354,13 +402,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.28"
|
||||
version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||
|
||||
[[package]]
|
||||
name = "lux-consensus"
|
||||
version = "1.22.3"
|
||||
version = "1.22.4"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"blst",
|
||||
@@ -372,11 +420,24 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lux-consensus-napi"
|
||||
version = "1.22.1"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"lux-consensus",
|
||||
"napi",
|
||||
"napi-build",
|
||||
"napi-derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
@@ -395,6 +456,66 @@ dependencies = [
|
||||
"cmake",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi"
|
||||
version = "2.16.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"ctor",
|
||||
"napi-derive",
|
||||
"napi-sys",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-build"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60fdf9b392c50e7c4170fa633bd909490ed7835cea4c046776d1a4dd8d2ae0ab"
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive"
|
||||
version = "2.16.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"convert_case",
|
||||
"napi-derive-backend",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive-backend"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"semver",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-sys"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@@ -426,9 +547,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
@@ -436,6 +557,12 @@ version = "11.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.7"
|
||||
@@ -471,32 +598,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.11.0"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
|
||||
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
@@ -514,9 +641,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.2"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -526,9 +653,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.10"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -537,9 +664,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.6"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
@@ -549,15 +676,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
@@ -569,35 +690,52 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 3.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.143"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -607,10 +745,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -637,10 +798,25 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.19"
|
||||
name = "tokio"
|
||||
version = "1.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||
checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
@@ -654,9 +830,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.101"
|
||||
version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b"
|
||||
checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -665,25 +841,11 @@ dependencies = [
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.101"
|
||||
version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d"
|
||||
checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -691,31 +853,31 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.101"
|
||||
version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa"
|
||||
checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"syn 2.0.119",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.101"
|
||||
version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1"
|
||||
checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.78"
|
||||
version = "0.3.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12"
|
||||
checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -732,35 +894,61 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.0"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.2"
|
||||
name = "zerocopy"
|
||||
version = "0.8.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.3"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
|
||||
checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
# The Rust side of this repository, as one workspace.
|
||||
#
|
||||
# `pkg/typescript` path-depends on `pkg/rust`, so they are already one unit; the
|
||||
# workspace is that fact written down. Built separately they could disagree —
|
||||
# and did: narrowing the node id from 32 bytes to 20 in `pkg/rust` left
|
||||
# `pkg/typescript` unable to compile at all, and nothing said so, because
|
||||
# nothing built both. One `cargo test` here builds both.
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["pkg/rust", "pkg/typescript"]
|
||||
|
||||
# Profiles apply to a whole workspace, so they live at its root. These are the
|
||||
# settings the napi binding shipped with; a member's own `[profile]` is ignored.
|
||||
[profile.release]
|
||||
lto = true
|
||||
strip = true
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lux-consensus"
|
||||
version = "1.22.3"
|
||||
version = "1.22.4"
|
||||
edition = "2021"
|
||||
description = "Lux Consensus SDK — pure-Rust Quasar post-quantum probabilistic consensus (Wave / FPC / Photon / Focus / Quasar PQ finality via hybrid BLS12-381 + Pulsar = P3Q)"
|
||||
license-file = "LICENSE"
|
||||
|
||||
+131
-13
@@ -128,6 +128,18 @@ pub enum CertError {
|
||||
/// A proof of possession that is not a valid signature by this key over its
|
||||
/// own (node, key) message under [`crate::pop::POP_DST`].
|
||||
PopInvalid,
|
||||
/// The set's weights sum past what a `u64` can hold. Go's
|
||||
/// `ErrWeightOverflow`, and refused for the reason Go refuses it: every
|
||||
/// threshold in this crate is read against the total, so a total that
|
||||
/// cannot be represented is a total two thirds of which means nothing.
|
||||
///
|
||||
/// It is the clause that keeps the two sides of the weighted predicate
|
||||
/// honest. Were the sum merely clamped, a set summing past `u64::MAX`
|
||||
/// would report `u64::MAX` staked AND `u64::MAX` voted, and
|
||||
/// `u64::MAX > floor(2·u64::MAX/3)` — half the stake would read as an
|
||||
/// export supermajority. So the set is refused at the door instead, and
|
||||
/// the predicate never sees a number that is not the sum it claims to be.
|
||||
WeightOverflow,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CertError {
|
||||
@@ -167,6 +179,7 @@ impl std::fmt::Display for CertError {
|
||||
CertError::DuplicateKey => write!(f, "public key is registered to more than one node"),
|
||||
CertError::DuplicateNode => write!(f, "node is registered more than once"),
|
||||
CertError::PopInvalid => write!(f, "proof of possession does not verify for this key"),
|
||||
CertError::WeightOverflow => write!(f, "weight overflowed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,7 +406,7 @@ impl QuorumCert {
|
||||
if total == 0 {
|
||||
return Err(CertError::StakeZero { epoch_height });
|
||||
}
|
||||
let voted = self.voted_stake(stake, epoch_height);
|
||||
let voted = self.voted_stake(stake, epoch_height)?;
|
||||
let half = half_stake_floor(total);
|
||||
if voted <= half {
|
||||
return Err(CertError::StakeBelowMajority {
|
||||
@@ -417,7 +430,7 @@ impl QuorumCert {
|
||||
if total == 0 {
|
||||
return Err(CertError::StakeZero { epoch_height });
|
||||
}
|
||||
let voted = self.voted_stake(stake, epoch_height);
|
||||
let voted = self.voted_stake(stake, epoch_height)?;
|
||||
let floor = two_thirds_stake_floor(total);
|
||||
if voted <= floor {
|
||||
return Err(CertError::StakeBelowSupermajority {
|
||||
@@ -429,19 +442,46 @@ impl QuorumCert {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Summed stake of the certificate's voters, saturating.
|
||||
/// Summed stake of the certificate's voters — checked, and refused rather
|
||||
/// than clamped.
|
||||
///
|
||||
/// Saturating and not wrapping: a set whose weights sum past `u64::MAX`
|
||||
/// must not wrap to a small number and read as below-threshold, nor wrap
|
||||
/// past a floor. It is only reached on a set that cannot exist, and it
|
||||
/// stays monotone if it ever is.
|
||||
fn voted_stake(&self, stake: &dyn StakeSource, epoch_height: u64) -> u64 {
|
||||
self.votes.iter().fold(0u64, |acc, v| {
|
||||
acc.saturating_add(stake.weight(&v.node_id, epoch_height))
|
||||
/// The voters are a subset of the members, so against any set this crate
|
||||
/// admitted this sum is bounded by a total [`ValidatorSet::insert`] already
|
||||
/// proved representable, and the overflow is unreachable. Reaching it is
|
||||
/// therefore evidence about the [`StakeSource`], not about the votes: it
|
||||
/// says the source is reporting weights that no admitted set could hold, and
|
||||
/// a source that cannot state its own total is not one a threshold can be
|
||||
/// read against.
|
||||
///
|
||||
/// It must not clamp. Clamping pins BOTH sides of the predicate to
|
||||
/// `u64::MAX`, and `u64::MAX > floor(2·u64::MAX/3)` — so a certificate
|
||||
/// carrying half the stake would clear the export floor. Monotone is not
|
||||
/// the property that matters here; being the sum it claims to be is.
|
||||
fn voted_stake(&self, stake: &dyn StakeSource, epoch_height: u64) -> Result<u64, CertError> {
|
||||
self.votes.iter().try_fold(0u64, |acc, v| {
|
||||
acc.checked_add(stake.weight(&v.node_id, epoch_height))
|
||||
.ok_or(CertError::WeightOverflow)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// One validator asking to be counted: the identity it claims, the key it will
|
||||
/// sign under, the proof binding the two, and the weight staked behind it.
|
||||
///
|
||||
/// The field-for-field twin of Go's `validators.Registration`, and the input to
|
||||
/// [`ValidatorSet::register`] — the door for a set that arrives whole.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Registration {
|
||||
/// The identity claimed, by the 20-byte NodeID the proof is bound to.
|
||||
pub node: NodeId,
|
||||
/// The BLS12-381 min_pk public key: compressed G1, 48 bytes.
|
||||
pub public_key: Vec<u8>,
|
||||
/// The node-bound proof of possession over `node ‖ key`: compressed G2, 96
|
||||
/// bytes. See [`crate::pop`] for the preimage and the domain.
|
||||
pub proof: Vec<u8>,
|
||||
pub weight: u64,
|
||||
}
|
||||
|
||||
/// A validator set: who is a member, what stake each carries, and the keys
|
||||
/// those members sign with.
|
||||
///
|
||||
@@ -489,6 +529,15 @@ pub struct ValidatorSet {
|
||||
/// Keyed on the canonical bytes re-derived from the DECODED point, never on
|
||||
/// the caller's input, so two spellings of one key cannot occupy two slots.
|
||||
owner: HashMap<[u8; PUBLIC_KEY_LEN], NodeId>,
|
||||
/// The summed weight of every member, keyed or not.
|
||||
///
|
||||
/// Established one admission at a time and never recomputed, because it is
|
||||
/// the admission that decides whether it can be represented at all — see
|
||||
/// the WEIGHT clause of [`ValidatorSet::insert`]. A total folded on demand
|
||||
/// has to decide what to do about a sum it cannot hold, at a place with no
|
||||
/// registration left to refuse; a total accumulated at the door already
|
||||
/// knows, and the set that would have overflowed does not exist.
|
||||
total: u64,
|
||||
}
|
||||
|
||||
impl ValidatorSet {
|
||||
@@ -496,6 +545,45 @@ impl ValidatorSet {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Admit a whole set of registrations, or none of them. This is the port of
|
||||
/// Go's `validators.Register`, and the way to build a set from input this
|
||||
/// node did not produce — a genesis file, a peer, a chain read.
|
||||
///
|
||||
/// [`ValidatorSet::insert`] admits ONE validator and reports on that one, so
|
||||
/// it cannot give a set any property. A caller that inserts in a loop and
|
||||
/// logs what failed is left holding a set that is missing a member: `n` is
|
||||
/// short by one and the total is short by that member's stake, and those two
|
||||
/// numbers are what every threshold in this crate is read against. Nothing
|
||||
/// announces the loss — the set is well formed, just not the set that was
|
||||
/// registered — so two nodes fed the same registrations disagree about the
|
||||
/// floor while both believe they agree.
|
||||
///
|
||||
/// So the set is admitted whole or not at all. One inadmissible registration
|
||||
/// fails the call and no partial set escapes: the set under construction is
|
||||
/// a local, and the caller receives either all of it or the first clause
|
||||
/// that failed.
|
||||
///
|
||||
/// Registrations are sorted by node id before any is checked, exactly as Go
|
||||
/// sorts them and with the same stable sort, so WHICH duplicate a set is
|
||||
/// refused on does not depend on the order a caller happened to build its
|
||||
/// input. One bad set is one error, everywhere.
|
||||
///
|
||||
/// The input is taken by value. Go clones its slice so the sort cannot be
|
||||
/// seen by the caller; here the caller has given the registrations up, so
|
||||
/// there is no caller-visible slice to protect and no copy to make.
|
||||
///
|
||||
/// Every registration must carry a key: this is the proof path, and Go's
|
||||
/// `Register` refuses a keyless one for the same reason. A member this node
|
||||
/// holds no key for comes in through [`ValidatorSet::insert_unkeyed`].
|
||||
pub fn register(mut registrations: Vec<Registration>) -> Result<Self, CertError> {
|
||||
registrations.sort_by_key(|r| r.node);
|
||||
let mut set = Self::new();
|
||||
for r in ®istrations {
|
||||
set.insert(r.node, r.weight, &r.public_key, &r.proof)?;
|
||||
}
|
||||
Ok(set)
|
||||
}
|
||||
|
||||
/// Admit a validator: the identity it claims, the key it will sign under,
|
||||
/// the proof binding the two, and the weight staked behind it.
|
||||
///
|
||||
@@ -532,7 +620,13 @@ impl ValidatorSet {
|
||||
/// or weight behind a caller that thought it was adding one.
|
||||
///
|
||||
/// Nothing is written unless every clause passes: a refused registration
|
||||
/// leaves no membership, no key and no ownership behind.
|
||||
/// leaves no membership, no key and no ownership behind. That is a promise
|
||||
/// about THIS registration and only this one — the set is left exactly as it
|
||||
/// was, which for a caller admitting several in a loop means it is left
|
||||
/// holding the ones that already went in. A set built from input this node
|
||||
/// did not produce wants [`ValidatorSet::register`], which is all-or-nothing
|
||||
/// over the whole set; this door is the incremental one, for a membership
|
||||
/// change to a set that is already live.
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
node: NodeId,
|
||||
@@ -576,10 +670,20 @@ impl ValidatorSet {
|
||||
if self.weights.contains_key(&node) {
|
||||
return Err(CertError::DuplicateNode);
|
||||
}
|
||||
// WEIGHT, last — the clause Go runs last, for the reason it runs it at
|
||||
// all. `checked_add`, so a set whose weights sum past `u64::MAX` is
|
||||
// refused here rather than clamped downstream, where clamping both the
|
||||
// total and the votes pins them equal and lets half the stake read as
|
||||
// two thirds of it.
|
||||
let total = self
|
||||
.total
|
||||
.checked_add(weight)
|
||||
.ok_or(CertError::WeightOverflow)?;
|
||||
|
||||
self.keys.insert(node, pk);
|
||||
self.weights.insert(node, weight);
|
||||
self.owner.insert(canonical, node);
|
||||
self.total = total;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -597,7 +701,16 @@ impl ValidatorSet {
|
||||
if self.weights.contains_key(&node) {
|
||||
return Err(CertError::DuplicateNode);
|
||||
}
|
||||
// The same WEIGHT clause. An unkeyed member cannot sign, but its stake
|
||||
// is counted in the total every threshold is read against — Go's
|
||||
// `FlattenValidatorSet` adds it and checks the same overflow — so it can
|
||||
// overflow the total exactly as a keyed one can.
|
||||
let total = self
|
||||
.total
|
||||
.checked_add(weight)
|
||||
.ok_or(CertError::WeightOverflow)?;
|
||||
self.weights.insert(node, weight);
|
||||
self.total = total;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -611,7 +724,12 @@ impl ValidatorSet {
|
||||
if let Some(old) = self.keys.remove(node) {
|
||||
self.owner.remove(&old.compress());
|
||||
}
|
||||
self.weights.remove(node);
|
||||
// The total is the sum of the weights still present, so it loses exactly
|
||||
// what the map loses. It was added to the total when the member was
|
||||
// admitted, so the subtraction is exact and cannot go below zero.
|
||||
if let Some(weight) = self.weights.remove(node) {
|
||||
self.total -= weight;
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of members, keyed or not — the `n` every threshold is read
|
||||
@@ -672,7 +790,7 @@ impl StakeSource for ValidatorSet {
|
||||
}
|
||||
|
||||
fn total_stake(&self, _epoch_height: u64) -> u64 {
|
||||
self.weights.values().fold(0u64, |a, w| a.saturating_add(*w))
|
||||
self.total
|
||||
}
|
||||
|
||||
fn validator_count(&self, _epoch_height: u64) -> i64 {
|
||||
|
||||
@@ -202,10 +202,19 @@ pub fn equal_stake_quasar(n: i64) -> i64 {
|
||||
|
||||
/// The minimum vote count that CAN reach the two-thirds-by-stake predicate for a
|
||||
/// weight vector: order heaviest first and count until the running stake first
|
||||
/// exceeds the floor. Returns 0 for an empty set or zero total — no stake model,
|
||||
/// fail closed.
|
||||
/// exceeds the floor. Returns 0 for an empty set, a zero total, or a vector with
|
||||
/// no representable total — no stake model, fail closed.
|
||||
pub fn weighted_quasar(weights: &[u64]) -> usize {
|
||||
let total: u64 = weights.iter().copied().sum();
|
||||
// Checked, and out the same door as a zero total. A vector summing past
|
||||
// `u64::MAX` has no total, so it has no two thirds of one to size a count
|
||||
// against. A plain sum would wrap to a SMALL total and hand back a count
|
||||
// below the real stake quorum — a count gate sized under the predicate it
|
||||
// exists to anticipate, which is the fail-open direction. No admitted set
|
||||
// can be such a vector: `ValidatorSet::insert` refuses the sum at the door.
|
||||
let total = match weights.iter().try_fold(0u64, |acc, &w| acc.checked_add(w)) {
|
||||
Some(total) => total,
|
||||
None => return 0,
|
||||
};
|
||||
if total == 0 || weights.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
@@ -216,6 +225,7 @@ pub fn weighted_quasar(weights: &[u64]) -> usize {
|
||||
let mut count = 0usize;
|
||||
for w in sorted {
|
||||
count += 1;
|
||||
// Bounded by `total`, which is representable, so the running sum is too.
|
||||
cum += w;
|
||||
if cum > floor {
|
||||
break;
|
||||
@@ -269,3 +279,29 @@ impl Finality {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::weighted_quasar;
|
||||
|
||||
/// A weight vector with no representable total takes the fail-closed exit,
|
||||
/// not the wrapped one. Two validators of 2^63 sum to 2^64: a plain sum
|
||||
/// wraps that to 0 in release and panics in debug, and a wrapped total of 0
|
||||
/// would hand `two_thirds_stake_floor` a floor the first voter clears — a
|
||||
/// count gate of 1 for a set no count can finalize. One less than that pair
|
||||
/// is a real total, and still counted exactly.
|
||||
#[test]
|
||||
fn a_weight_vector_with_no_total_is_fail_closed() {
|
||||
assert_eq!(weighted_quasar(&[1 << 63, 1 << 63]), 0);
|
||||
assert_eq!(weighted_quasar(&[u64::MAX, 1]), 0);
|
||||
assert_eq!(weighted_quasar(&[u64::MAX / 2, u64::MAX / 2]), 2);
|
||||
assert_eq!(weighted_quasar(&[u64::MAX]), 1);
|
||||
}
|
||||
|
||||
/// The exits that were already there stay where they were.
|
||||
#[test]
|
||||
fn no_stake_model_is_zero() {
|
||||
assert_eq!(weighted_quasar(&[]), 0);
|
||||
assert_eq!(weighted_quasar(&[0, 0, 0]), 0);
|
||||
}
|
||||
}
|
||||
|
||||
+251
-2
@@ -15,8 +15,8 @@
|
||||
|
||||
use blst::min_pk::SecretKey;
|
||||
use lux_consensus::cert::{
|
||||
CertError, NodeId, QuorumCert, StakeSource, ValidatorSet, Vote, VoteVerifier, DST,
|
||||
SIGNATURE_LEN,
|
||||
CertError, NodeId, QuorumCert, Registration, StakeSource, ValidatorSet, Vote, VoteVerifier,
|
||||
DST, SIGNATURE_LEN,
|
||||
};
|
||||
use lux_consensus::finality::{canonical_vote_message, Finality, Position};
|
||||
use lux_consensus::pop;
|
||||
@@ -927,3 +927,252 @@ fn the_issue_path_applies_the_stake_floor_not_only_the_count() {
|
||||
"nothing below the stake floor is finalized"
|
||||
);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------- the weight total
|
||||
|
||||
/// Go's `Register` runs one clause after uniqueness that this port had dropped:
|
||||
/// `math.Add64` on the running total, refusing the WHOLE set if it overflows.
|
||||
/// Four validators of 2^63 sum to 2^65. Without the clause both sides of the
|
||||
/// weighted predicate clamp to `u64::MAX` — total AND voted — and
|
||||
/// `u64::MAX > floor(2·u64::MAX/3)`, so a certificate carrying half the stake
|
||||
/// reads as an export supermajority. The set that would do it is refused here,
|
||||
/// at the member whose weight the total cannot hold.
|
||||
#[test]
|
||||
fn a_set_whose_weights_overflow_is_refused_at_admission() {
|
||||
let huge = 1u64 << 63;
|
||||
let signers: Vec<Signer> = (1..=4).map(|i| Signer::new(i, huge)).collect();
|
||||
let mut set = ValidatorSet::new();
|
||||
|
||||
let first = &signers[0];
|
||||
set.insert(first.id, first.weight, &first.public(), &first.pop())
|
||||
.expect("the first 2^63 fits");
|
||||
assert_eq!(set.total_stake(0), huge);
|
||||
|
||||
// 2^63 + 2^63 is 2^64. Refused, and nothing of it is kept.
|
||||
let second = &signers[1];
|
||||
assert_eq!(
|
||||
set.insert(second.id, second.weight, &second.public(), &second.pop()),
|
||||
Err(CertError::WeightOverflow)
|
||||
);
|
||||
assert_eq!(set.len(), 1, "a refused registration is not a member");
|
||||
assert_eq!(set.total_stake(0), huge, "nor does its weight count");
|
||||
assert!(!set.can_verify(&second.id), "nor is its key registered");
|
||||
|
||||
// And through the whole-set door, for the same reason.
|
||||
let registrations: Vec<Registration> = signers
|
||||
.iter()
|
||||
.map(|s| Registration {
|
||||
node: s.id,
|
||||
public_key: s.public().to_vec(),
|
||||
proof: s.pop(),
|
||||
weight: s.weight,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(
|
||||
ValidatorSet::register(registrations).unwrap_err(),
|
||||
CertError::WeightOverflow
|
||||
);
|
||||
}
|
||||
|
||||
/// The unkeyed door counts weight toward the same total, so it carries the same
|
||||
/// clause — as Go's `FlattenValidatorSet` does, which checks the overflow before
|
||||
/// it even looks at the key.
|
||||
#[test]
|
||||
fn an_unkeyed_member_cannot_overflow_the_total_either() {
|
||||
let mut set = ValidatorSet::new();
|
||||
set.insert_unkeyed(node(1), u64::MAX).expect("the first fits");
|
||||
assert_eq!(
|
||||
set.insert_unkeyed(node(2), 1),
|
||||
Err(CertError::WeightOverflow)
|
||||
);
|
||||
assert_eq!(set.len(), 1);
|
||||
assert_eq!(set.total_stake(0), u64::MAX);
|
||||
}
|
||||
|
||||
/// The largest representable set is admissible, and the total is exact at the
|
||||
/// boundary rather than clamped to it — a set that sums to `u64::MAX` is a real
|
||||
/// set, and only the member past it is refused. Retracting one frees exactly its
|
||||
/// weight, so the total stays the sum of what is actually there.
|
||||
#[test]
|
||||
fn the_total_is_exact_at_the_boundary_and_after_a_retraction() {
|
||||
let mut set = ValidatorSet::new();
|
||||
set.insert_unkeyed(node(1), u64::MAX - 10).expect("insert");
|
||||
set.insert_unkeyed(node(2), 10).expect("insert");
|
||||
assert_eq!(set.total_stake(0), u64::MAX);
|
||||
|
||||
assert_eq!(set.insert_unkeyed(node(3), 1), Err(CertError::WeightOverflow));
|
||||
|
||||
set.remove(&node(2));
|
||||
assert_eq!(set.total_stake(0), u64::MAX - 10);
|
||||
set.insert_unkeyed(node(3), 10).expect("the freed room is real");
|
||||
assert_eq!(set.total_stake(0), u64::MAX);
|
||||
}
|
||||
|
||||
/// The other half of the same clause, on the predicate side.
|
||||
///
|
||||
/// This is the exact certificate the missing clause admitted: four validators of
|
||||
/// 2^63, two of them voting — half the stake — read against a stake source that
|
||||
/// clamps, which is what the set itself used to do. The floor is computed from
|
||||
/// the total, so when both the total and the voted sum clamp to `u64::MAX` the
|
||||
/// comparison is `u64::MAX > floor(2·u64::MAX/3)` and QUASAR returns `Ok` on 50%.
|
||||
///
|
||||
/// It now returns the overflow instead. A sum that cannot be represented is not
|
||||
/// a sum, and the predicate says so rather than comparing two saturated numbers:
|
||||
/// no admitted set can reach it, so reaching it is evidence about the source.
|
||||
#[test]
|
||||
fn a_clamping_stake_source_is_refused_rather_than_read() {
|
||||
/// The pre-fix arithmetic, kept whole so the difference is the fix and
|
||||
/// nothing else: a total folded on demand with `saturating_add`.
|
||||
struct Clamping {
|
||||
weights: Vec<(NodeId, u64)>,
|
||||
}
|
||||
impl StakeSource for Clamping {
|
||||
fn weight(&self, node: &NodeId, _epoch_height: u64) -> u64 {
|
||||
self.weights
|
||||
.iter()
|
||||
.find(|(id, _)| id == node)
|
||||
.map(|(_, w)| *w)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
fn total_stake(&self, _epoch_height: u64) -> u64 {
|
||||
self.weights.iter().fold(0u64, |a, (_, w)| a.saturating_add(*w))
|
||||
}
|
||||
fn validator_count(&self, _epoch_height: u64) -> i64 {
|
||||
self.weights.len() as i64
|
||||
}
|
||||
}
|
||||
|
||||
let huge = 1u64 << 63;
|
||||
// Registered at a weight the set can hold, so the keys resolve and the
|
||||
// signatures verify — the stake is the only thing under test.
|
||||
let (signers, set) = committee(4, 1);
|
||||
let clamping = Clamping {
|
||||
weights: signers.iter().map(|s| (s.id, huge)).collect(),
|
||||
};
|
||||
assert_eq!(
|
||||
clamping.total_stake(0),
|
||||
u64::MAX,
|
||||
"the source clamps, as the set used to"
|
||||
);
|
||||
|
||||
// Two of four: half the stake, and every signature genuine.
|
||||
let cert = valid_cert(&signers, 2, 2);
|
||||
assert_eq!(cert.verify(&set, 0), Ok(()));
|
||||
assert_eq!(
|
||||
cert.verify_weighted(&set, &clamping, 0),
|
||||
Err(CertError::WeightOverflow),
|
||||
"half the stake is not two thirds of it, whatever the arithmetic clamps to"
|
||||
);
|
||||
|
||||
// The same refusal one rung down, so neither tier reads a clamped total.
|
||||
// Three of four, because Nova's signer floor is read before its stake and
|
||||
// two would stop there — the stake clause has to be reached to be tested.
|
||||
let pos = position();
|
||||
let message = canonical_vote_message(&pos, true);
|
||||
let votes: Vec<Vote> = signers[..3].iter().map(|s| s.vote(&message)).collect();
|
||||
let nova = QuorumCert::assemble(Finality::Nova, pos, 3, &votes).expect("assemble");
|
||||
assert_eq!(
|
||||
nova.verify_weighted(&set, &clamping, 0),
|
||||
Err(CertError::WeightOverflow)
|
||||
);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ the whole-set door
|
||||
|
||||
/// `register` admits the set or none of it. An inadmissible member fails the
|
||||
/// call, and the members that were already good do not survive it — which is the
|
||||
/// whole difference from a loop of `insert`, where they do, leaving a set whose
|
||||
/// `n` and total describe a set nobody registered.
|
||||
#[test]
|
||||
fn one_bad_registration_refuses_the_whole_set() {
|
||||
let signers: Vec<Signer> = (1..=4).map(|i| Signer::new(i, 100)).collect();
|
||||
let mut registrations: Vec<Registration> = signers
|
||||
.iter()
|
||||
.map(|s| Registration {
|
||||
node: s.id,
|
||||
public_key: s.public().to_vec(),
|
||||
proof: s.pop(),
|
||||
weight: s.weight,
|
||||
})
|
||||
.collect();
|
||||
// The last one presents a proof bound to another node.
|
||||
registrations[3].proof = signers[0].pop();
|
||||
|
||||
assert_eq!(
|
||||
ValidatorSet::register(registrations.clone()).unwrap_err(),
|
||||
CertError::PopInvalid
|
||||
);
|
||||
|
||||
// The same registrations one at a time: three go in, and the caller is left
|
||||
// holding a set of three where four were registered — n and the total both
|
||||
// short, with nothing to say so. That is what the whole-set door removes.
|
||||
let mut partial = ValidatorSet::new();
|
||||
let mut refused = 0;
|
||||
for r in ®istrations {
|
||||
if partial
|
||||
.insert(r.node, r.weight, &r.public_key, &r.proof)
|
||||
.is_err()
|
||||
{
|
||||
refused += 1;
|
||||
}
|
||||
}
|
||||
assert_eq!((partial.len(), partial.total_stake(0), refused), (3, 300, 1));
|
||||
|
||||
// Repaired, the set is admitted whole.
|
||||
registrations[3].proof = signers[3].pop();
|
||||
let set = ValidatorSet::register(registrations).expect("register");
|
||||
assert_eq!((set.len(), set.total_stake(0)), (4, 400));
|
||||
for s in &signers {
|
||||
assert!(set.can_verify(&s.id));
|
||||
}
|
||||
}
|
||||
|
||||
/// Which member a bad set is refused on is decided by node id, not by the order a
|
||||
/// caller happened to build its input — Go sorts before it checks for exactly
|
||||
/// this reason. Two nodes handed the same registrations in different orders must
|
||||
/// refuse the same set for the same reason, or they disagree about a set they
|
||||
/// both rejected.
|
||||
#[test]
|
||||
fn the_refusal_does_not_depend_on_the_input_order() {
|
||||
let signers: Vec<Signer> = (1..=4).map(|i| Signer::new(i, 100)).collect();
|
||||
let registration = |s: &Signer, weight: u64, proof: Vec<u8>| Registration {
|
||||
node: s.id,
|
||||
public_key: s.public().to_vec(),
|
||||
proof,
|
||||
weight,
|
||||
};
|
||||
|
||||
// Two faults, at two node ids: node 2 stakes nothing, node 4 proves nothing.
|
||||
// Sorted, node 2 is reached first, so ZeroWeight is the answer either way.
|
||||
let low = registration(&signers[1], 0, signers[1].pop());
|
||||
let high = registration(&signers[3], 100, signers[0].pop());
|
||||
let good = registration(&signers[0], 100, signers[0].pop());
|
||||
|
||||
assert_eq!(
|
||||
ValidatorSet::register(vec![good.clone(), low.clone(), high.clone()]).unwrap_err(),
|
||||
CertError::ZeroWeight
|
||||
);
|
||||
assert_eq!(
|
||||
ValidatorSet::register(vec![high, good, low]).unwrap_err(),
|
||||
CertError::ZeroWeight,
|
||||
"the same set refused on the same clause, whatever order it arrived in"
|
||||
);
|
||||
}
|
||||
|
||||
/// The proof path needs a key. Go's `Register` refuses a keyless registration
|
||||
/// rather than admitting a member that can never sign; a member this node holds
|
||||
/// no key for comes in through the unkeyed door instead.
|
||||
#[test]
|
||||
fn the_whole_set_door_refuses_a_keyless_registration() {
|
||||
let s = Signer::new(1, 100);
|
||||
assert_eq!(
|
||||
ValidatorSet::register(vec![Registration {
|
||||
node: s.id,
|
||||
public_key: Vec::new(),
|
||||
proof: s.pop(),
|
||||
weight: 100,
|
||||
}])
|
||||
.unwrap_err(),
|
||||
CertError::NoKey
|
||||
);
|
||||
}
|
||||
|
||||
Generated
-333
@@ -1,333 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.177"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lux-consensus"
|
||||
version = "1.22.0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"hex",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lux-consensus-napi"
|
||||
version = "1.22.0"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"lux-consensus",
|
||||
"napi",
|
||||
"napi-build",
|
||||
"napi-derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "napi"
|
||||
version = "2.16.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"ctor",
|
||||
"napi-derive",
|
||||
"napi-sys",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-build"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d376940fd5b723c6893cd1ee3f33abbfd86acb1cd1ec079f3ab04a2a3bc4d3b1"
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive"
|
||||
version = "2.16.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"convert_case",
|
||||
"napi-derive-backend",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive-backend"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"semver",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-sys"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.111"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lux-consensus-napi"
|
||||
version = "1.22.0"
|
||||
version = "1.22.1"
|
||||
edition = "2021"
|
||||
description = "TypeScript/Node.js bindings for Lux Consensus SDK"
|
||||
license = "MIT"
|
||||
@@ -23,9 +23,6 @@ lux-consensus = { path = "../rust" }
|
||||
[build-dependencies]
|
||||
napi-build = "2"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
strip = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -47,13 +47,16 @@ engine.addBlock({
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
// Record votes (need alpha votes for acceptance)
|
||||
// Register the validators, then vote. Only members are counted, and a node id
|
||||
// is 20 bytes — 40 hex characters.
|
||||
const config = testnetConfig();
|
||||
for (let i = 0; i < config.alpha; i++) {
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const voter = i.toString(16).padStart(40, '0');
|
||||
engine.addValidator(voter, 100);
|
||||
engine.recordVote({
|
||||
blockId: blockId,
|
||||
voteType: VoteType.Preference,
|
||||
voter: i.toString(16).padStart(64, '0'),
|
||||
voter: voter,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -142,7 +145,7 @@ interface Block {
|
||||
interface Vote {
|
||||
blockId: string;
|
||||
voteType: VoteType;
|
||||
voter: string; // 32-byte hex
|
||||
voter: string; // 20-byte hex — a node id, not a block id
|
||||
signature?: string;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+3
-3
@@ -79,7 +79,7 @@ export interface Vote {
|
||||
blockId: string
|
||||
/** Vote type (0=Preference, 1=Commit, 2=Cancel) */
|
||||
voteType: number
|
||||
/** Voter node ID (hex string) */
|
||||
/** Voter node ID (20-byte hex string) */
|
||||
voter: string
|
||||
/** Optional signature (hex string) */
|
||||
signature?: string
|
||||
@@ -105,7 +105,7 @@ export declare class BlockId {
|
||||
toBytes(): Array<number>
|
||||
isZero(): boolean
|
||||
}
|
||||
/** Node identifier (32-byte hex string) */
|
||||
/** Node identifier (20-byte hex string) */
|
||||
export declare class NodeId {
|
||||
constructor(hexString: string)
|
||||
toHex(): string
|
||||
@@ -153,7 +153,7 @@ export declare class ConsensusEngine {
|
||||
getStatus(blockId: string): BlockStatus
|
||||
/** Process multiple votes in batch (optimized) */
|
||||
recordVotesBatch(votes: Array<Vote>): number
|
||||
/** Add a validator to the engine */
|
||||
/** Add a validator to the engine, by its 20-byte node id */
|
||||
addValidator(nodeId: string, weight: number): void
|
||||
/** Get the current configuration */
|
||||
getConfig(): QuasarConfig
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@luxfi/consensus",
|
||||
"version": "1.22.0",
|
||||
"version": "1.22.1",
|
||||
"description": "Lux Consensus SDK for TypeScript/Node.js - Native performance with type safety",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
|
||||
+85
-20
@@ -22,6 +22,20 @@ use std::sync::{Arc, Mutex};
|
||||
// Import the Engine trait to access its methods
|
||||
use lux_consensus::Engine;
|
||||
|
||||
/// The width of a node id, in bytes, taken from the core type rather than
|
||||
/// restated here.
|
||||
///
|
||||
/// A validator is named by the 20-byte NodeID the network names it by, and the
|
||||
/// proof of possession it registers with signs `node ‖ key` over exactly those
|
||||
/// 20 — so a binding that spelled its own width is a binding that can disagree
|
||||
/// with the standard it wraps. This is derived, so it cannot.
|
||||
///
|
||||
/// It must never be reached by truncating a wider id: cutting 32 bytes down to
|
||||
/// 20 maps distinct validators onto one identity, and one identity is one signer
|
||||
/// slot and one share of the weight. A hex string of the wrong length is
|
||||
/// refused, never trimmed.
|
||||
const NODE_ID_LEN: usize = std::mem::size_of::<lux_consensus::cert::NodeId>();
|
||||
|
||||
// Re-use core types from lux-consensus
|
||||
use lux_consensus::{
|
||||
Block as CoreBlock,
|
||||
@@ -66,7 +80,7 @@ impl BlockId {
|
||||
|
||||
#[napi]
|
||||
pub fn to_hex(&self) -> String {
|
||||
hex::encode(&self.inner)
|
||||
hex::encode(self.inner)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
@@ -80,10 +94,10 @@ impl BlockId {
|
||||
}
|
||||
}
|
||||
|
||||
/// Node identifier (32-byte hex string)
|
||||
/// Node identifier (20-byte hex string)
|
||||
#[napi]
|
||||
pub struct NodeId {
|
||||
inner: [u8; 32],
|
||||
inner: [u8; NODE_ID_LEN],
|
||||
}
|
||||
|
||||
#[napi]
|
||||
@@ -92,17 +106,17 @@ impl NodeId {
|
||||
pub fn new(hex_string: String) -> Result<Self> {
|
||||
let bytes = hex::decode(&hex_string)
|
||||
.map_err(|e| Error::from_reason(format!("Invalid hex: {}", e)))?;
|
||||
if bytes.len() != 32 {
|
||||
return Err(Error::from_reason("Node ID must be 32 bytes"));
|
||||
if bytes.len() != NODE_ID_LEN {
|
||||
return Err(Error::from_reason("Node ID must be 20 bytes"));
|
||||
}
|
||||
let mut arr = [0u8; 32];
|
||||
let mut arr = [0u8; NODE_ID_LEN];
|
||||
arr.copy_from_slice(&bytes);
|
||||
Ok(NodeId { inner: arr })
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn to_hex(&self) -> String {
|
||||
hex::encode(&self.inner)
|
||||
hex::encode(self.inner)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
@@ -335,7 +349,7 @@ pub struct Vote {
|
||||
pub block_id: String,
|
||||
/// Vote type (0=Preference, 1=Commit, 2=Cancel)
|
||||
pub vote_type: u32,
|
||||
/// Voter node ID (hex string)
|
||||
/// Voter node ID (20-byte hex string)
|
||||
pub voter: String,
|
||||
/// Optional signature (hex string)
|
||||
pub signature: Option<String>,
|
||||
@@ -348,8 +362,11 @@ impl Vote {
|
||||
let voter = hex::decode(&self.voter)
|
||||
.map_err(|e| Error::from_reason(format!("Invalid voter ID: {}", e)))?;
|
||||
|
||||
if block_id.len() != 32 || voter.len() != 32 {
|
||||
return Err(Error::from_reason("IDs must be 32 bytes"));
|
||||
if block_id.len() != 32 {
|
||||
return Err(Error::from_reason("Block ID must be 32 bytes"));
|
||||
}
|
||||
if voter.len() != NODE_ID_LEN {
|
||||
return Err(Error::from_reason("Voter node ID must be 20 bytes"));
|
||||
}
|
||||
|
||||
let vote_type = match self.vote_type {
|
||||
@@ -360,7 +377,7 @@ impl Vote {
|
||||
};
|
||||
|
||||
let mut block_arr = [0u8; 32];
|
||||
let mut voter_arr = [0u8; 32];
|
||||
let mut voter_arr = [0u8; NODE_ID_LEN];
|
||||
block_arr.copy_from_slice(&block_id);
|
||||
voter_arr.copy_from_slice(&voter);
|
||||
|
||||
@@ -533,20 +550,25 @@ impl ConsensusEngine {
|
||||
Ok(engine.record_votes_batch(core_votes) as u32)
|
||||
}
|
||||
|
||||
/// Add a validator to the engine
|
||||
/// Add a validator to the engine, by its 20-byte node id
|
||||
#[napi]
|
||||
pub fn add_validator(&self, node_id: String, weight: u32) -> Result<()> {
|
||||
let id_bytes = hex::decode(&node_id)
|
||||
.map_err(|e| Error::from_reason(format!("Invalid node ID: {}", e)))?;
|
||||
if id_bytes.len() != 32 {
|
||||
return Err(Error::from_reason("Node ID must be 32 bytes"));
|
||||
if id_bytes.len() != NODE_ID_LEN {
|
||||
return Err(Error::from_reason("Node ID must be 20 bytes"));
|
||||
}
|
||||
let mut arr = [0u8; 32];
|
||||
let mut arr = [0u8; NODE_ID_LEN];
|
||||
arr.copy_from_slice(&id_bytes);
|
||||
|
||||
let mut engine = self.inner.lock()
|
||||
.map_err(|e| Error::from_reason(format!("Lock error: {}", e)))?;
|
||||
engine.add_validator(CoreNodeID::from(arr), weight as u64);
|
||||
// The core refuses a node already admitted, and a weight the set's total
|
||||
// cannot hold. Dropping that answer would report a validator as added
|
||||
// that is not in the set, so it is raised to the caller.
|
||||
engine
|
||||
.add_validator(CoreNodeID::from(arr), weight as u64)
|
||||
.map_err(|e| Error::from_reason(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -679,12 +701,24 @@ mod tests {
|
||||
};
|
||||
engine.add_block(block.clone()).unwrap();
|
||||
|
||||
// Record votes (need ceil(0.6 * 5) = 3 votes for testnet)
|
||||
for i in 0..5 {
|
||||
// Nine distinct voters, and every one of them a registered member.
|
||||
//
|
||||
// Members, because only members are counted: an engine holding no
|
||||
// validators counts nobody, which is the direction to fail in. Nine,
|
||||
// because testnet is k=5, beta=5: the count opens at k votes, and the
|
||||
// decision needs beta consecutive affirmative rounds after that — and a
|
||||
// round in this engine is a vote, so 4 + 5. A repeat voter is ignored,
|
||||
// so they have to be nine different validators.
|
||||
for i in 0..9 {
|
||||
engine
|
||||
.add_validator(format!("{:02x}", i).repeat(NODE_ID_LEN), 100)
|
||||
.unwrap();
|
||||
}
|
||||
for i in 0..9 {
|
||||
let vote = Vote {
|
||||
block_id: "01".repeat(32),
|
||||
vote_type: 0, // Preference
|
||||
voter: format!("{:02x}", i).repeat(32),
|
||||
voter: format!("{:02x}", i).repeat(NODE_ID_LEN),
|
||||
signature: None,
|
||||
};
|
||||
engine.record_vote(vote).unwrap();
|
||||
@@ -702,11 +736,42 @@ mod tests {
|
||||
assert!(id.is_zero());
|
||||
}
|
||||
|
||||
/// A node id is 20 bytes here because it is 20 bytes in the core, and a
|
||||
/// wider one is REFUSED rather than trimmed. Trimming is the failure this
|
||||
/// guards: 32 bytes cut to 20 maps distinct validators onto one identity,
|
||||
/// which is one signer slot and one share of the weight for two operators —
|
||||
/// so every door that takes a node id refuses the wrong width instead.
|
||||
#[test]
|
||||
fn a_node_id_is_twenty_bytes_and_a_wider_one_is_refused() {
|
||||
assert_eq!(NODE_ID_LEN, 20);
|
||||
|
||||
let id = NodeId::new("ab".repeat(NODE_ID_LEN)).unwrap();
|
||||
assert_eq!(id.to_hex(), "ab".repeat(NODE_ID_LEN));
|
||||
assert_eq!(id.to_bytes().len(), NODE_ID_LEN);
|
||||
|
||||
assert!(NodeId::new("ab".repeat(32)).is_err(), "not truncated");
|
||||
assert!(NodeId::new("ab".repeat(16)).is_err());
|
||||
|
||||
// The same width, at the other two doors a node id enters by.
|
||||
let engine = ConsensusEngine::new(testnet_config()).unwrap();
|
||||
assert!(engine.add_validator("aa".repeat(32), 100).is_err());
|
||||
engine.add_validator("aa".repeat(NODE_ID_LEN), 100).unwrap();
|
||||
|
||||
let wide = Vote {
|
||||
block_id: "01".repeat(32),
|
||||
vote_type: 0,
|
||||
voter: "bb".repeat(32),
|
||||
signature: None,
|
||||
};
|
||||
assert!(wide.to_core().is_err());
|
||||
assert!(Vote { voter: "bb".repeat(NODE_ID_LEN), ..wide }.to_core().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fpc_selector() {
|
||||
let selector = FpcSelector::new(0.5, 0.8, None).unwrap();
|
||||
let threshold = selector.threshold(0, 20);
|
||||
assert!(threshold >= 10 && threshold <= 16);
|
||||
assert!((10..=16).contains(&threshold));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -148,13 +148,17 @@ describe('Lux Quasar Consensus SDK', () => {
|
||||
|
||||
describe('NodeId', () => {
|
||||
it('should create from hex string', () => {
|
||||
const hexId = 'ab'.repeat(32);
|
||||
const hexId = 'ab'.repeat(20);
|
||||
const nodeId = new NodeId(hexId);
|
||||
assert.strictEqual(nodeId.toHex(), hexId);
|
||||
});
|
||||
|
||||
// A node id is 20 bytes, and a wider one is refused rather than trimmed:
|
||||
// 32 cut down to 20 maps two validators onto one identity, which is one
|
||||
// signer slot and one share of the weight for both of them.
|
||||
it('should reject wrong length', () => {
|
||||
assert.throws(() => new NodeId('ab'.repeat(16)), /32 bytes/);
|
||||
assert.throws(() => new NodeId('ab'.repeat(16)), /20 bytes/);
|
||||
assert.throws(() => new NodeId('ab'.repeat(32)), /20 bytes/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -192,12 +196,18 @@ describe('Lux Quasar Consensus SDK', () => {
|
||||
it('should record votes and reach consensus', () => {
|
||||
const blockId = '01'.repeat(32);
|
||||
|
||||
// Record enough votes to reach quorum (testnet alpha=0.6, k=5, need ceil(0.6*5)=3 votes)
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// Only members are counted, so the voters are registered first. Nine of
|
||||
// them: testnet is k=5, beta=5, the count opens at k votes and the
|
||||
// decision needs beta affirmative rounds after that, and a round here is
|
||||
// a vote. A node id is 20 bytes = 40 hex characters.
|
||||
for (let i = 0; i < 9; i++) {
|
||||
engine.addValidator(i.toString(16).padStart(40, '0'), 100);
|
||||
}
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const vote = {
|
||||
blockId: blockId,
|
||||
voteType: VoteType.Preference,
|
||||
voter: i.toString(16).padStart(64, '0'),
|
||||
voter: i.toString(16).padStart(40, '0'),
|
||||
};
|
||||
engine.recordVote(vote);
|
||||
}
|
||||
@@ -222,13 +232,15 @@ describe('Lux Quasar Consensus SDK', () => {
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
// Create batch of votes
|
||||
// Create batch of votes, from ten registered members
|
||||
const votes = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const voter = (100 + i).toString(16).padStart(40, '0');
|
||||
engine.addValidator(voter, 100);
|
||||
votes.push({
|
||||
blockId: blockId,
|
||||
voteType: VoteType.Preference,
|
||||
voter: (100 + i).toString(16).padStart(64, '0'),
|
||||
voter: voter,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -237,9 +249,13 @@ describe('Lux Quasar Consensus SDK', () => {
|
||||
});
|
||||
|
||||
it('should add validators', () => {
|
||||
const nodeId = 'aa'.repeat(32);
|
||||
const nodeId = 'aa'.repeat(20);
|
||||
// Should not throw
|
||||
engine.addValidator(nodeId, 100);
|
||||
|
||||
// The same node twice is refused, and a 32-byte id is not a node id.
|
||||
assert.throws(() => engine.addValidator(nodeId, 100));
|
||||
assert.throws(() => engine.addValidator('aa'.repeat(32), 100), /20 bytes/);
|
||||
});
|
||||
|
||||
it('should get height', () => {
|
||||
@@ -251,7 +267,7 @@ describe('Lux Quasar Consensus SDK', () => {
|
||||
const vote = {
|
||||
blockId: 'ff'.repeat(32),
|
||||
voteType: VoteType.Preference,
|
||||
voter: '00'.repeat(32),
|
||||
voter: '00'.repeat(20),
|
||||
};
|
||||
assert.throws(() => engine.recordVote(vote), /Block not found/);
|
||||
});
|
||||
@@ -294,13 +310,16 @@ describe('Lux Quasar Consensus SDK', () => {
|
||||
assert.strictEqual(engine.getStatus(block.id), BlockStatus.Processing);
|
||||
}
|
||||
|
||||
// Vote on all blocks (need at least ceil(0.6*5)=3 votes for testnet)
|
||||
// Vote on all blocks, from registered members — an engine holding no
|
||||
// validators counts nobody
|
||||
for (const block of blocks) {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const voter = (block.height * 10 + i).toString(16).padStart(40, '0');
|
||||
engine.addValidator(voter, 100);
|
||||
engine.recordVote({
|
||||
blockId: block.id,
|
||||
voteType: VoteType.Preference,
|
||||
voter: (block.height * 10 + i).toString(16).padStart(64, '0'),
|
||||
voter: voter,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -333,12 +352,14 @@ describe('Lux Quasar Consensus SDK', () => {
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
// Need ceil(0.69*21) = 15 votes for mainnet
|
||||
// Need ceil(0.69*21) = 15 votes for mainnet, all from members
|
||||
for (let i = 0; i < 21; i++) {
|
||||
const voter = i.toString(16).padStart(40, '0');
|
||||
engine.addValidator(voter, 100);
|
||||
engine.recordVote({
|
||||
blockId: blockId,
|
||||
voteType: VoteType.Preference,
|
||||
voter: i.toString(16).padStart(64, '0'),
|
||||
voter: voter,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -377,12 +398,14 @@ describe('Lux Quasar Consensus SDK', () => {
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
// Vote to reach consensus
|
||||
// Vote to reach consensus, from members
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const voter = i.toString(16).padStart(40, '0');
|
||||
engine.addValidator(voter, 100);
|
||||
engine.recordVote({
|
||||
blockId: blockId,
|
||||
voteType: VoteType.Preference,
|
||||
voter: i.toString(16).padStart(64, '0'),
|
||||
voter: voter,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -399,8 +422,9 @@ describe('Lux Quasar Consensus SDK', () => {
|
||||
});
|
||||
|
||||
describe('Cross-Language Compatibility', () => {
|
||||
it('should use 32-byte IDs for cross-language consensus', () => {
|
||||
// Verify ID format matches Rust SDK
|
||||
it('should use 32-byte block IDs for cross-language consensus', () => {
|
||||
// Verify ID format matches Rust SDK. A block id is 32 bytes; a node id is
|
||||
// 20 — see the NodeId suite — and the two are not interchangeable.
|
||||
const rustStyleId = 'deadbeef'.repeat(8); // 32 bytes
|
||||
const blockId = new BlockId(rustStyleId);
|
||||
assert.strictEqual(blockId.toHex(), rustStyleId);
|
||||
|
||||
Reference in New Issue
Block a user