dchain: pin the vmID, and say what it is

The id is declared twice — here beside the Factory and in luxfi/constants,
which is the one genesis writes into the CreateChainTx — and nothing made them
agree. If this repo's literal moved, a node would look for a plugin under one
name while genesis named the other, and the chain would never appear: a plugin
that is absent and a plugin nobody asked for look identical to a directory
scan.

The second test records the property that decides how a plugin gets pinned. The
id is an ASCII name padded to 32 bytes, not a digest of the binary, and it must
not become one: genesis references it from a transaction already signed and
already in the chain, so an id derived from the build would change on every
rebuild and stop resolving the chain it created. Identity is stable across
versions by construction.

Which leaves integrity as a separate value. Nothing states which build of dexd
is the right one, and the registry runs whatever sits at the filename. What
CB58 does buy is that a mistyped filename fails its checksum instead of
resolving to nothing.
This commit is contained in:
2026-09-07 14:59:59 -07:00
parent 582ed0ea5e
commit d0a7727277
3 changed files with 102 additions and 1 deletions
+1 -1
View File
@@ -99,7 +99,7 @@ require (
github.com/luxfi/cache v1.3.1 // indirect
github.com/luxfi/compress v0.1.1 // indirect
github.com/luxfi/concurrent v0.1.1 // indirect
github.com/luxfi/constants v1.6.2 // indirect
github.com/luxfi/constants v1.6.5 // indirect
github.com/luxfi/container v0.2.1 // indirect
github.com/luxfi/crypto/ipa v1.2.4 // indirect
github.com/luxfi/filesystem v0.0.1 // indirect
+2
View File
@@ -345,6 +345,8 @@ github.com/luxfi/consensus v1.36.3 h1:pEfHf7asnIKQ2Dr2tWe5nKv/0lRRa/iAQ9IyevcRU3
github.com/luxfi/consensus v1.36.3/go.mod h1:m4aMFbKYxwM2X4oPYRjq4LmDSShfCgAHnhmxIGKDyFU=
github.com/luxfi/constants v1.6.2 h1:pXHdKIFbfE9qX4xOjq2LxYvagNhhNvspUVEbPcIEKfA=
github.com/luxfi/constants v1.6.2/go.mod h1:r0oH8C/+r/XFYBq1AJxt6zWRKKRKgDzrEMop/CCs9rI=
github.com/luxfi/constants v1.6.5 h1:VvjjtOJ/nkNPTPG5Xe5jxJCKZnNzIA24wMYLRGKLGw4=
github.com/luxfi/constants v1.6.5/go.mod h1:7ia7OwQpfRfWsHOHKcwFjzsui0k6w7gId27Hu1pyQrE=
github.com/luxfi/container v0.2.1 h1:MTnfKXzS5+oxV5jKZerdOxSA6iMPaQI9/FWGufizzaw=
github.com/luxfi/container v0.2.1/go.mod h1:B+uM0wP0lGvt/SSK7QOEn/qBcsHzILVHlKikdCyzSgM=
github.com/luxfi/corona v0.10.4 h1:/+Uy5iOWBMkr+XACnRRiyrbb5ebsZLsUXjHfJW3sFyw=
+99
View File
@@ -0,0 +1,99 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package dchain
import (
"testing"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
)
// canonicalVMIDCB58 is the D-Chain's vmID in the encoding the node's plugin
// registry uses. The dexd binary MUST be installed under exactly this
// filename: the registry resolves a CreateChainTx's vmID to an implementation
// by looking for a file with this name in the plugin directory.
//
// Written out literally rather than computed, so a change to the vmID — for
// any reason, including an "obviously equivalent" refactor — fails here
// instead of silently producing a chain no node can start.
const canonicalVMIDCB58 = "mDVT5EWMumBp3LCqvKwuyZQeY1VXr1jvjGNAt8nL4UFiXvqXr"
// TestVMID_IsCanonicalAndStable checks the two declarations of the D-Chain's
// vmID are one value. This repo declares VMID beside the Factory; luxfi/
// constants declares DexVMID, and genesis writes THAT into the CreateChainTx.
// Nothing makes them agree. If this repo's literal moves, a node looks for a
// plugin under one name while genesis named the other, and the chain simply
// never appears — with no error, because a plugin that is not there and a
// plugin that was never asked for look identical to a directory scan.
func TestVMID_IsCanonicalAndStable(t *testing.T) {
if VMID != constants.DexVMID {
t.Fatalf("VMID = %s, want constants.DexVMID = %s", VMID, constants.DexVMID)
}
if got := VMID.String(); got != canonicalVMIDCB58 {
t.Fatalf("VMID CB58 = %q, want %q\n"+
"A vmID cannot change after a chain is created with it. If this is an\n"+
"intentional pre-launch change, every declaration must move together:\n"+
" luxfi/constants vm_ids.go DexVMID\n"+
" luxfi/dex pkg/dchain/factory.go VMID, this test",
got, canonicalVMIDCB58)
}
if VMID == ids.Empty {
t.Fatal("VMID is the empty id")
}
}
// TestVMID_IsANameNotADigest is the property that decides how a plugin is
// pinned, so it is worth asserting rather than remembering.
//
// The id is an ASCII name padded into 32 bytes — it is NOT a hash of the
// binary, and it must not become one. Genesis references the vmID in a
// CreateChainTx that is already signed and already in the chain, so an id
// derived from the binary would change on every rebuild and the chain it
// created would stop resolving. Identity has to be stable across versions.
//
// Which leaves integrity as a SEPARATE value: nothing here says which build of
// dexd is the right one, and the registry runs whatever sits at the filename.
// That pin belongs beside the artifact, not inside the identity.
func TestVMID_IsANameNotADigest(t *testing.T) {
want := ids.ID{'d', 'e', 'x', 'v', 'm'}
if VMID != want {
t.Fatalf("VMID bytes = %v, want the ASCII name %q padded to 32 bytes", VMID[:8], "dexvm")
}
}
// TestVMID_IsNotAnotherChains: the shims across the estate are near-copies of
// each other, and a copy that kept the wrong id would still compile, still
// pass a byte test if that were copied too, and produce two plugins racing for
// one filename.
func TestVMID_IsNotAnotherChains(t *testing.T) {
for name, other := range map[string]ids.ID{
"BridgeVMID": constants.BridgeVMID,
"MPCVMID": constants.MPCVMID,
"OracleVMID": constants.OracleVMID,
"RelayVMID": constants.RelayVMID,
"IdentityVMID": constants.IdentityVMID,
"EVMID": constants.EVMID,
} {
if VMID == other {
t.Fatalf("dchain.VMID equals constants.%s (%s)", name, other)
}
}
}
// TestVMID_FilenameTypoIsRefused is the one property CB58 buys as a filename:
// base58 plus a four-byte checksum, so a mistyped plugin name fails to decode
// instead of resolving to a different id or to nothing at all.
func TestVMID_FilenameTypoIsRefused(t *testing.T) {
typo := []byte(canonicalVMIDCB58)
if typo[len(typo)-1] == 'q' {
typo[len(typo)-1] = 'r'
} else {
typo[len(typo)-1] = 'q'
}
if _, err := ids.FromString(string(typo)); err == nil {
t.Fatal("a one-character change in the plugin filename decoded cleanly; " +
"the checksum is what makes a typo an error rather than a missing chain")
}
}