From 3a44f0dcd3757f868ab53617817321fc8335b56f Mon Sep 17 00:00:00 2001 From: zeekay Date: Sat, 25 Jul 2026 14:59:04 -0700 Subject: [PATCH] genesis: refuse an M-Chain policy the validator set cannot satisfy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit M-Chain's committee is the validator set, and RunKeygen refuses a policy needing more parties than the committee has. mainnet's mchain.json asked for 7-of-10 against five genesis validators, so a custody key could never have been generated — the failure would have surfaced the first time someone tried to bridge, not at deploy. Adds the invariant as a test over every network's built genesis, and picks up luxfi/genesis v1.16.4 where the policies are sized to fit (3-of-5 on mainnet/testnet/devnet, 2-of-3 on localnet). Co-authored-by: Hanzo Dev --- genesis/builder/mchain_test.go | 37 ++++++++++++++++++++++++++++++++-- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/genesis/builder/mchain_test.go b/genesis/builder/mchain_test.go index 03cf9ac5e..d3bf5075d 100644 --- a/genesis/builder/mchain_test.go +++ b/genesis/builder/mchain_test.go @@ -97,9 +97,9 @@ func TestMChainGenesisPolicyIsUnambiguousAndDeployable(t *testing.T) { networkID uint32 want string }{ - {constants.MainnetID, "7-of-10"}, + {constants.MainnetID, "3-of-5"}, {constants.TestnetID, "3-of-5"}, - {constants.LocalID, "3-of-5"}, + {constants.LocalID, "2-of-3"}, } { cfg := GetConfig(tc.networkID) require.NotNil(t, cfg) @@ -138,3 +138,36 @@ func TestMChainGenesisHasNoAmbiguousThresholdFields(t *testing.T) { } } } + +// A policy is only real if the network has enough validators to hold its +// shares. M-Chain draws its committee from the validator set, and RunKeygen +// refuses a policy needing more parties than the committee has — so a genesis +// declaring 7-of-10 on a five-validator network fails closed forever: no +// custody key can ever be generated, and the failure only shows up the first +// time someone tries to bridge. +// +// mainnet's mchain.json shipped exactly that mismatch for as long as the field +// existed, harmlessly, because nothing read it. This test is what keeps it +// harmless now that the policy is authoritative. +func TestMChainPolicyIsSatisfiableByTheGenesisValidatorSet(t *testing.T) { + for _, networkID := range []uint32{constants.MainnetID, constants.TestnetID, constants.LocalID} { + cfg := GetConfig(networkID) + require.NotNil(t, cfg) + + raw, _, err := FromConfig(cfg) + require.NoError(t, err) + parsed, err := genesis.Parse(raw) + require.NoError(t, err) + + var blob struct { + Policy string `json:"policy"` + } + require.NoError(t, json.Unmarshal([]byte(cfg.MChainGenesis), &blob)) + _, n := parsePolicy(t, blob.Policy) + + require.LessOrEqualf(t, n, len(parsed.Validators), + "network %d: M-Chain policy %s needs %d parties but genesis declares %d validators; "+ + "keygen would fail closed and no custody key could ever exist", + networkID, blob.Policy, n, len(parsed.Validators)) + } +} diff --git a/go.mod b/go.mod index 4ca39dbfe..7f8727023 100644 --- a/go.mod +++ b/go.mod @@ -126,7 +126,7 @@ require ( github.com/luxfi/constants v1.6.2 github.com/luxfi/container v0.2.1 github.com/luxfi/filesystem v0.0.1 - github.com/luxfi/genesis v1.16.3 + github.com/luxfi/genesis v1.16.4 github.com/luxfi/genesis/pkg/genesis/security v1.13.8 github.com/luxfi/geth v1.20.1 github.com/luxfi/go-bip39 v1.2.0 diff --git a/go.sum b/go.sum index 91cda8ff4..988b73e13 100644 --- a/go.sum +++ b/go.sum @@ -335,8 +335,8 @@ github.com/luxfi/filesystem v0.0.1 h1:VZ6xMFKaAPBW/ddlMsDnI2G0VU1lV5rYaVcW5d+KwE github.com/luxfi/filesystem v0.0.1/go.mod h1:OQVSU6XNwqrr1AI+MqkID2taHUclx7NYmmr3svgttec= github.com/luxfi/formatting v1.1.1 h1:MJhVXIPh1dbysvYEjtaEA/Z0FUTiI7n0DwOF54FS08c= github.com/luxfi/formatting v1.1.1/go.mod h1:zhBWp6fLZduhpiAdPgVDdPVOyhw4FvwRUksF6+xKQCE= -github.com/luxfi/genesis v1.16.3 h1:P9/ixEOTC9yVvhhcRT3femRMMVIQxKINet1X1GwE4tQ= -github.com/luxfi/genesis v1.16.3/go.mod h1:0F6hV6GfwDSmIWsueB7/vqPZFxyS7A4Jo3p1QU87fFc= +github.com/luxfi/genesis v1.16.4 h1:8ZtqvgPnICgGpjBzAjYAPrI3qwr1g1DPbum+hgjc4bg= +github.com/luxfi/genesis v1.16.4/go.mod h1:0F6hV6GfwDSmIWsueB7/vqPZFxyS7A4Jo3p1QU87fFc= github.com/luxfi/genesis/pkg/genesis/security v1.13.8 h1:9ier0p55ErSpoXHRJpZ04WV5HwwMB1uDrU7PHGBKG2U= github.com/luxfi/genesis/pkg/genesis/security v1.13.8/go.mod h1:DzU+GYUFv12ja4Vc46bWKNBBmNYbcow3u/DASx4wpfI= github.com/luxfi/geth v1.20.1 h1:QUGQr4AKvADjwMi7t8a0OfoyxShgEcI9pwie1jFYfm0=