chore(node): kill subnet — chain/network vocabulary across node (#116)

* feat(platformvm): CreateSovereignL1Tx — single-tx sovereign L1 launch

Adds a new platformvm tx type that atomically registers a sovereign L1
in one P-chain commit. Replaces what is today the four-step flow:

  CreateNetworkTx + AddChainValidatorTx ×N + CreateChainTx ×K + ConvertNetworkToL1Tx

with one signed tx. After commit, the primary network has a permanent
record of the L1's network ID + initial validator set + chain manifest
+ on-chain validator-manager contract — but it does NOT track-chains
or validate the L1's blocks. The L1 runs its own consensus from
genesis. L2/L3/L4 follow the same pattern recursively.

Type shape:

  type CreateSovereignL1Tx struct {
    BaseTx
    Owner           fx.Owner                       // CreateNetworkTx parity
    Validators      []*ConvertNetworkToL1Validator // genesis validator set
    Chains          []*SovereignL1Chain            // VM ID + genesis blob per chain
    ManagerChainIdx uint32                         // index into Chains[]
    ManagerAddress  types.JSONByteSlice            // validator-manager contract
  }

  type SovereignL1Chain struct {
    BlockchainName string
    VMID           ids.ID
    FxIDs          []ids.ID
    GenesisData    []byte
  }

SyntacticVerify enforces:
  - at least one validator (sorted, unique)
  - at least one chain, ≤ MaxSovereignL1Chains (16)
  - ManagerChainIdx is in range of Chains[]
  - ManagerAddress ≤ MaxChainAddressLength
  - per-chain name + VMID + FxIDs + genesis bounds
  - BaseTx + Owner + each Validator each verify

Wired into:
  - Visitor interface
  - codec (registered as the next tx type after ConvertNetworkToL1Tx)
  - signer + complexity + metrics + executor stubs across all visitor
    implementations

Executor body is stubbed with a TODO. The atomic state transition
(mint new networkID from tx hash, seed validator-manager state,
register each Chain, charge fee) lands in a follow-up PR. This PR is
the type definition + interface plumbing so downstream tools (wallet,
CLI, fee calc, metrics) can target the tx type while the executor is
implemented.

* chore(node): kill subnet — chain/network vocabulary across node

Zero remaining `subnet|Subnet|SUBNET` in node Go source. Per canonical
no-subnet rule.

## Wire types (Go fields only; byte-level wire encoding unchanged)

  message/wire/types.go  TrackedSubnets    → TrackedChains
  message/wire/zap.go    same on Read/Write
  proto/p2p/p2p_zap.go   SubnetUptime alias → ChainUptime
                          (consumes luxfi/proto rename in companion PR)

## Comment scrub

  message/wire/types.go            "(chain, subnet) pair" → "(chain, network) pair"
  network/peer/handshake.go        "primary-network or subnet" → "primary-network or per-chain"
  node/node.go                     "per-subnet"   → "per-chain"
                                   "P→subnet warp" → "P→chain warp"
  genesis/builder/builder.go       "their own subnets" → "their own chains"
  vms/platformvm/client.go         dropped "subnet jargon" reference
  vms/platformvm/service.go        dropped "net / subnet jargon" reference
  vms/platformvm/config/internal.go  "subnet-spawned blockchain" → "per-chain blockchain"

## ICPSubnet (Internet Computer adapter)

  ICPSubnet → ICPNet  (type rename — unrelated to platform subnet,
                        but still a subnet word; killed for consistency)
  map field `subnets` → `nets`

## Examples

  wallet/network/primary/examples/bootstrap-hanzo/main.go:
    --subnet-id  → --network-id (CLI flag)
    existingSubnetID local var → existingNetID
    "subnet ID" log lines → "network ID"
    "SUBNET_ID=" output → "NETWORK_ID="
    "subnet-evm VM ID" → "EVM VM ID"
    All comments rephrased.

  wallet/network/primary/examples/heartbeat-tx/main.go: one comment
    rephrase.

go.work workspace cleanup:
  - Removed ./operator/go entry (placeholder; lux/operator polyglot
    layout pending the cross-repo migration)
  - Removed ./operator entry (mid-migration; nothing to build)

Build clean. Zero `grep -rIn "subnet|Subnet" --include="*.go"` matches.
This commit is contained in:
Hanzo Dev
2026-05-29 21:17:44 -07:00
committed by GitHub
parent cda97fa966
commit 09a4d1343d
29 changed files with 362 additions and 32 deletions
+1 -1
View File
@@ -620,7 +620,7 @@ func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
}
// G/K/A/I chains are loaded as plugins and instantiated via
// CreateChainTx post-genesis on their own subnets.
// CreateChainTx post-genesis on their own chains.
pChainGenesis, err := genesis.New(
xAssetID,
+2 -2
View File
@@ -52,7 +52,7 @@ type Ping struct {
// ChainPingEntry is the per-chain payload in Ping/Pong.
// In Lux's L1/L2 model each chain is its own validator set, so the legacy
// (chain, subnet) pair collapses to a single chain identifier.
// (chain, network) pair collapses to a single chain identifier.
type ChainPingEntry struct {
ChainId []byte
}
@@ -71,7 +71,7 @@ type Handshake struct {
IpPort uint32
IpSigningTime uint64
IpNodeIdSig []byte
TrackedSubnets [][]byte
TrackedChains [][]byte
Client *Client
SupportedAcps []uint32
ObjectedAcps []uint32
+2 -2
View File
@@ -418,7 +418,7 @@ func marshalHandshake(b *Buffer, m *Handshake) {
b.WriteUint32(m.IpPort)
b.WriteUint64(m.IpSigningTime)
b.WriteBytes(m.IpNodeIdSig)
b.WriteBytesSlice(m.TrackedSubnets)
b.WriteBytesSlice(m.TrackedChains)
if m.Client != nil {
b.WriteUint8(1)
b.WriteString(m.Client.Name)
@@ -674,7 +674,7 @@ func unmarshalHandshake(r *Reader) (*Handshake, error) {
if err != nil {
return nil, err
}
m.TrackedSubnets, err = r.ReadBytesSlice()
m.TrackedChains, err = r.ReadBytesSlice()
if err != nil {
return nil, err
}
+1 -1
View File
@@ -27,7 +27,7 @@ import (
// INIT (initiator → responder)
// ProtocolVersion u8 ; 0x01 today
// ProfileID u8 ; LuxStrictPQ etc.
// ChainID [32]byte ; primary-network or subnet
// ChainID [32]byte ; primary-network or per-chain
// KEMScheme u8 ; ML-KEM-768 / ML-KEM-1024
// NodeID [20]byte ; canonical Lux NodeID
// MLDSAPublicKey [PublicKeySize]byte ; FIPS 204 ML-DSA-65 pub
+2 -2
View File
@@ -132,7 +132,7 @@ func New(
// chainID || 0x42 || pubKey)[:20] under ids.NodeIDSchemeMLDSA65;
// classical-compat chains fall through to ids.NodeIDFromCert.
// chainID is the primary-network chain id (ids.Empty, encoded as
// "11111111111111111111111111111111LpoYY" in cb58); per-subnet
// "11111111111111111111111111111111LpoYY" in cb58); per-chain
// chain ids are bound at chain-creation time and don't affect
// the validator's primary identity.
derivedNodeID, err := config.StakingConfig.DeriveNodeID(ids.Empty)
@@ -1263,7 +1263,7 @@ func (n *Node) initChainManager(xAssetID ids.ID) error {
// asset creation and UTXO ops are first-class on the P-Chain (see
// CreateAssetTx/OperationTx in vms/platformvm/txs). All cross-chain
// flows that historically went P→X→C are replaced with direct
// P→subnet warp transfers.
// P→chain warp transfers.
var xChainID ids.ID
if createXVMTx, err := builder.VMGenesis(n.Config.GenesisBytes, constants.XVMID); err == nil {
xChainID = createXVMTx.ID()
+1 -1
View File
@@ -39,7 +39,7 @@ type (
Message_BFT = p2p.Message_BFT
Ping = p2p.Ping
Pong = p2p.Pong
SubnetUptime = p2p.SubnetUptime
ChainUptime = p2p.ChainUptime
Handshake = p2p.Handshake
Client = p2p.Client
BloomFilter = p2p.BloomFilter
+4 -4
View File
@@ -652,7 +652,7 @@ type ICPAdapter struct {
mu sync.RWMutex
config *ChainConfig
blocks map[uint64]*ICPBlock
subnets map[[32]byte]*ICPSubnet
nets map[[32]byte]*ICPNet
latestHeight uint64
initialized bool
}
@@ -666,7 +666,7 @@ type ICPBlock struct {
ChainID [32]byte `json:"chainId"`
}
type ICPSubnet struct {
type ICPNet struct {
ChainID [32]byte `json:"chainId"`
PublicKey []byte `json:"publicKey"` // Threshold BLS
Nodes [][32]byte `json:"nodes"`
@@ -675,7 +675,7 @@ type ICPSubnet struct {
func NewICPAdapter() *ICPAdapter {
return &ICPAdapter{
blocks: make(map[uint64]*ICPBlock),
subnets: make(map[[32]byte]*ICPSubnet),
nets: make(map[[32]byte]*ICPNet),
}
}
@@ -723,7 +723,7 @@ func (a *ICPAdapter) Close() error {
a.mu.Lock()
defer a.mu.Unlock()
a.blocks = nil
a.subnets = nil
a.nets = nil
a.initialized = false
return nil
}
+1 -1
View File
@@ -216,7 +216,7 @@ type ClientNet struct {
// ClientChain is the canonical client-side view of a chain registered
// on the platform. Replaces ClientNet with field naming that matches
// the user-facing concept ("chain") rather than the internal "net" /
// "subnet" jargon. Same shape — drop-in replacement.
// canonical naming.
type ClientChain = ClientNet
// GetChains returns information about the specified chains. Empty
+1 -1
View File
@@ -111,7 +111,7 @@ type Internal struct {
// P-only primary network: P-Chain is the sole mandatory chain (created out
// of band by the chain manager at startup). EVERY other chain — X-Chain
// (XVM), C-Chain (EVM), Q-Chain (Quantum), D-Chain (DEX), and any
// subnet-spawned blockchain — is opt-in via --track-chains / --track-all
// per-chain blockchain — opt-in via --track-chains / --track-all
// or by being present in platform genesis. Operators bring up exactly what
// their workload needs, nothing more.
func (c *Internal) CreateChain(blockchainID ids.ID, tx *txs.CreateChainTx) {
+7
View File
@@ -151,6 +151,13 @@ func (m *txMetrics) ConvertNetworkToL1Tx(*txs.ConvertNetworkToL1Tx) error {
return nil
}
func (m *txMetrics) CreateSovereignL1Tx(*txs.CreateSovereignL1Tx) error {
m.numTxs.With(metric.Labels{
txLabel: "create_sovereign_l1",
}).Inc()
return nil
}
func (m *txMetrics) RegisterL1ValidatorTx(*txs.RegisterL1ValidatorTx) error {
m.numTxs.With(metric.Labels{
txLabel: "register_l1_validator",
+1 -1
View File
@@ -603,7 +603,7 @@ func (s *Service) GetNets(_ *http.Request, args *GetNetsArgs, response *GetNetsR
// APIChain is the canonical wire-shape for a chain registered on the
// platform via CreateNetworkTx. Replaces APINet — same fields, named
// after the user-facing concept ("chain") rather than the internal
// "net" / "subnet" jargon. The wire encoding is byte-identical so a
// canonical naming. The wire encoding is byte-identical so a
// deserializer that targets APIChain reads APINet responses and vice
// versa; no proxies need to translate.
type APIChain struct {
+4
View File
@@ -106,6 +106,10 @@ func RegisterTypes(targetCodec linearcodec.Codec) error {
targetCodec.RegisterType(&BaseTx{}),
targetCodec.RegisterType(&ConvertNetworkToL1Tx{}),
// CreateSovereignL1Tx is the single-step alternative to
// CreateNetworkTx + AddChainValidatorTx ×N + CreateChainTx ×K
// + ConvertNetworkToL1Tx — registers a sovereign L1 atomically.
targetCodec.RegisterType(&CreateSovereignL1Tx{}),
targetCodec.RegisterType(&RegisterL1ValidatorTx{}),
targetCodec.RegisterType(&SetL1ValidatorWeightTx{}),
targetCodec.RegisterType(&IncreaseL1ValidatorBalanceTx{}),
@@ -199,6 +199,7 @@ func (*mockVisitor) AddPermissionlessDelegatorTx(*AddPermissionlessDelegatorTx)
func (*mockVisitor) TransferChainOwnershipTx(*TransferChainOwnershipTx) error { return nil }
func (*mockVisitor) BaseTx(*BaseTx) error { return nil }
func (*mockVisitor) ConvertNetworkToL1Tx(*ConvertNetworkToL1Tx) error { return nil }
func (*mockVisitor) CreateSovereignL1Tx(*CreateSovereignL1Tx) error { return nil }
func (*mockVisitor) RegisterL1ValidatorTx(*RegisterL1ValidatorTx) error { return nil }
func (*mockVisitor) SetL1ValidatorWeightTx(*SetL1ValidatorWeightTx) error { return nil }
func (*mockVisitor) IncreaseL1ValidatorBalanceTx(*IncreaseL1ValidatorBalanceTx) error { return nil }
@@ -0,0 +1,206 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"errors"
"unicode"
"github.com/luxfi/runtime"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
"github.com/luxfi/node/vms/platformvm/fx"
"github.com/luxfi/utils"
"github.com/luxfi/vm/types"
)
// CreateSovereignL1Tx atomically registers a new sovereign L1 on the
// primary network's P-chain. Combines what is today four sequential
// transactions —
//
// CreateNetworkTx + AddChainValidatorTx (×N) + CreateChainTx (×K) + ConvertNetworkToL1Tx
//
// into a single tx that commits or reverts atomically. After this tx
// finalises, the primary network has a permanent record of:
//
// - the L1's network ID (derived from the tx hash, like CreateNetworkTx)
// - the L1's genesis validator set (NodeIDs + BLS PoPs + weights)
// - the L1's chain manifest (VM ID + genesis blob per chain, e.g.
// EVM + DEX + FHE in one L1)
// - the on-chain validator-manager contract address (the L1's
// authority for future validator changes)
//
// The primary network does NOT track-chains or validate the L1's
// blocks. The L1's own validators run their own consensus
// independently from genesis. Subsequent validator-set changes flow
// through the on-chain manager via warp messages, not through
// AddChainValidatorTx.
//
// This is the canonical "fresh sovereign L1 from day 1" pattern.
// L2/L3/L4 share security with their parent L1 via the same flow —
// each child L1 references its parent in Owner.
//
// Replaces the 4-step flow for tenants who want sovereign L1 from
// genesis. The 4-step flow remains supported for existing chains that
// began as P-chain-managed and later converted.
type CreateSovereignL1Tx struct {
// Metadata, inputs and outputs (fee payment, etc.).
BaseTx `serialize:"true"`
// Owner of the network registration record. Equivalent to
// CreateNetworkTx.Owner. Authorises future admin txs against the
// network record on P-chain (delisting, owner rotation).
Owner fx.Owner `serialize:"true" json:"owner"`
// Genesis validator set. These NodeIDs are seeded into the
// validator-manager contract at L1 genesis; no separate
// AddPermissionlessValidator calls required. Same shape as
// ConvertNetworkToL1Validator so the verify/sort plumbing is
// shared.
Validators []*ConvertNetworkToL1Validator `serialize:"true" json:"validators"`
// Chains to register at L1 genesis. Each entry is one chain on
// the new L1 (e.g. EVM, DEX, FHE). The new L1's network ID is
// derived from this tx's hash; chains inherit it implicitly.
Chains []*SovereignL1Chain `serialize:"true" json:"chains"`
// Index into Chains[] of the chain that hosts the validator
// manager contract. The L1's validator-set authority lives at
// (Chains[ManagerChainIdx], ManagerAddress). After this tx
// commits, all validator changes go through that contract via
// warp; the primary P-chain no longer participates in the L1's
// validator set.
ManagerChainIdx uint32 `serialize:"true" json:"managerChainIdx"`
// Address of the validator-manager contract on the manager chain.
// Bounded by MaxChainAddressLength (shared with ConvertNetworkToL1Tx).
ManagerAddress types.JSONByteSlice `serialize:"true" json:"managerAddress"`
}
// SovereignL1Chain describes one chain on a newly-minted sovereign L1.
// Mirrors the per-chain fields of CreateChainTx; the parent
// CreateSovereignL1Tx supplies the network ID implicitly (derived from
// the tx hash, like CreateNetworkTx).
type SovereignL1Chain struct {
// Human-readable name; need not be unique. Subject to the same
// charset rules as CreateChainTx.BlockchainName.
BlockchainName string `serialize:"true" json:"blockchainName"`
// VM ID running on this chain (e.g. EVM, DEX, FHE precompile set).
VMID ids.ID `serialize:"true" json:"vmID"`
// Feature-extension IDs running on this chain. Must be sorted+unique.
FxIDs []ids.ID `serialize:"true" json:"fxIDs"`
// Genesis state blob for this chain. Bounded by MaxGenesisLen.
GenesisData []byte `serialize:"true" json:"genesisData"`
}
const (
// MaxSovereignL1Chains caps the per-tx chain count to keep tx size
// bounded. Most L1s ship 15 chains at genesis (EVM/DEX/FHE/etc.).
MaxSovereignL1Chains = 16
)
var (
_ UnsignedTx = (*CreateSovereignL1Tx)(nil)
ErrSovereignMustIncludeValidators = errors.New("sovereign L1 must include at least one validator")
ErrSovereignMustIncludeChains = errors.New("sovereign L1 must include at least one chain")
ErrSovereignTooManyChains = errors.New("sovereign L1 exceeds MaxSovereignL1Chains")
ErrSovereignManagerIdxOutOfRange = errors.New("managerChainIdx is out of range for chains[]")
ErrSovereignChainNameTooLong = errors.New("sovereign L1 chain name exceeds MaxNameLen")
ErrSovereignChainNameIllegal = errors.New("sovereign L1 chain name contains illegal characters")
ErrSovereignVMIDEmpty = errors.New("sovereign L1 chain VMID must not be empty")
ErrSovereignFxIDsNotSorted = errors.New("sovereign L1 chain FxIDs must be sorted and unique")
ErrSovereignGenesisTooLong = errors.New("sovereign L1 chain genesis exceeds MaxGenesisLen")
)
// InitRuntime sets the runtime context on the inputs and outputs so
// addresses can be JSON-marshalled into human-readable form.
func (tx *CreateSovereignL1Tx) InitRuntime(rt *runtime.Runtime) {
tx.BaseTx.InitRuntime(rt)
// Owner does not have InitRuntime; matches CreateNetworkTx.
}
// SyntacticVerify checks shape-only invariants. State-dependent checks
// (input UTXOs spendable, owner sigs valid, balance funds the genesis
// validators' initial Balance, etc.) live in the StandardTx executor.
func (tx *CreateSovereignL1Tx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
return ErrNilTx
case tx.SyntacticallyVerified:
return nil
case len(tx.Validators) == 0:
return ErrSovereignMustIncludeValidators
case !utils.IsSortedAndUnique(tx.Validators):
return ErrConvertValidatorsNotSortedAndUnique
case len(tx.Chains) == 0:
return ErrSovereignMustIncludeChains
case len(tx.Chains) > MaxSovereignL1Chains:
return ErrSovereignTooManyChains
case int(tx.ManagerChainIdx) >= len(tx.Chains):
return ErrSovereignManagerIdxOutOfRange
case len(tx.ManagerAddress) > MaxChainAddressLength:
return ErrAddressTooLong
}
if err := tx.BaseTx.SyntacticVerify(rt); err != nil {
return err
}
if err := tx.Owner.Verify(); err != nil {
return err
}
for _, vdr := range tx.Validators {
if err := vdr.Verify(); err != nil {
return err
}
}
for _, ch := range tx.Chains {
if err := ch.Verify(); err != nil {
return err
}
}
tx.SyntacticallyVerified = true
return nil
}
// Visit dispatches to the per-tx-type Visitor (StandardTx executor,
// mempool verifier, etc.). The matching visitor.go change adds the
// CreateSovereignL1Tx method to the interface.
func (tx *CreateSovereignL1Tx) Visit(visitor Visitor) error {
return visitor.CreateSovereignL1Tx(tx)
}
// Verify validates a single SovereignL1Chain entry.
func (ch *SovereignL1Chain) Verify() error {
if len(ch.BlockchainName) > MaxNameLen {
return ErrSovereignChainNameTooLong
}
for _, r := range ch.BlockchainName {
if r > unicode.MaxASCII || (!unicode.IsLetter(r) && !unicode.IsNumber(r) && r != ' ') {
return ErrSovereignChainNameIllegal
}
}
if ch.VMID == ids.Empty {
return ErrSovereignVMIDEmpty
}
if !utils.IsSortedAndUnique(ch.FxIDs) {
return ErrSovereignFxIDsNotSorted
}
if len(ch.GenesisData) > MaxGenesisLen {
return ErrSovereignGenesisTooLong
}
return nil
}
// PrimaryNetworkSafety: the tx cannot register the primary network
// itself. The new network ID is derived from the tx hash exactly like
// CreateNetworkTx — a new ids.ID, never constants.PrimaryNetworkID.
// Executors must enforce `derivedNetworkID != constants.PrimaryNetworkID`
// and reject otherwise. State-dependent check, lives in the executor.
var _ = constants.PrimaryNetworkID
@@ -112,6 +112,10 @@ func (*atomicTxExecutor) ConvertNetworkToL1Tx(*txs.ConvertNetworkToL1Tx) error {
return ErrWrongTxType
}
func (*atomicTxExecutor) CreateSovereignL1Tx(*txs.CreateSovereignL1Tx) error {
return ErrWrongTxType
}
func (*atomicTxExecutor) RegisterL1ValidatorTx(*txs.RegisterL1ValidatorTx) error {
return ErrWrongTxType
}
@@ -131,6 +131,10 @@ func (*proposalTxExecutor) ConvertNetworkToL1Tx(*txs.ConvertNetworkToL1Tx) error
return ErrWrongTxType
}
func (*proposalTxExecutor) CreateSovereignL1Tx(*txs.CreateSovereignL1Tx) error {
return ErrWrongTxType
}
func (*proposalTxExecutor) RegisterL1ValidatorTx(*txs.RegisterL1ValidatorTx) error {
return ErrWrongTxType
}
@@ -609,6 +609,33 @@ func (e *standardTxExecutor) BaseTx(tx *txs.BaseTx) error {
return nil
}
// CreateSovereignL1Tx executes a sovereign-L1 launch — atomically
// registers a new network, seeds its genesis validator set, registers
// every chain in tx.Chains, and records the on-chain validator-manager
// contract as the L1's authority. Replaces what is today the 4-step
// CreateNetworkTx + AddChainValidatorTx + CreateChainTx + ConvertNetworkToL1Tx
// flow. After commit, the primary network has a permanent record of
// the L1 but does NOT track-chains or validate its blocks.
//
// TODO(sovereign-l1): full executor body lands in a follow-up PR.
// Outline of the steps the body needs:
// - SyntacticVerify (already does this via tx.Visit pipeline)
// - VerifyMemoFieldLength
// - VerifyAndApplyInputs (Owner sigs, UTXO consumption)
// - Derive new networkID from tx hash
// - Reject if derivedNetworkID == constants.PrimaryNetworkID
// - Seed validator-manager state with tx.Validators
// - Register tx.Chains[i] for each i (VMID + genesis blob, parent
// network = derivedNetworkID)
// - Record (tx.Chains[tx.ManagerChainIdx], tx.ManagerAddress) as
// the validator authority for the new L1
// - Charge fee via feeCalculator.CalculateFee(tx)
// - Produce change UTXOs
// - Emit on-chain event so warp-aware validators learn of the L1
func (e *standardTxExecutor) CreateSovereignL1Tx(tx *txs.CreateSovereignL1Tx) error {
return fmt.Errorf("CreateSovereignL1Tx executor: not yet implemented (follow-up PR)")
}
func (e *standardTxExecutor) ConvertNetworkToL1Tx(tx *txs.ConvertNetworkToL1Tx) error {
currentTimestamp := e.state.GetTimestamp()
@@ -90,6 +90,10 @@ func (v *MempoolTxVerifier) ConvertNetworkToL1Tx(tx *txs.ConvertNetworkToL1Tx) e
return v.standardTx(tx)
}
func (v *MempoolTxVerifier) CreateSovereignL1Tx(tx *txs.CreateSovereignL1Tx) error {
return v.standardTx(tx)
}
func (v *MempoolTxVerifier) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx) error {
return v.standardTx(tx)
}
@@ -106,6 +106,10 @@ func (*warpVerifier) ConvertNetworkToL1Tx(*txs.ConvertNetworkToL1Tx) error {
return nil
}
func (*warpVerifier) CreateSovereignL1Tx(*txs.CreateSovereignL1Tx) error {
return nil
}
func (*warpVerifier) IncreaseL1ValidatorBalanceTx(*txs.IncreaseL1ValidatorBalanceTx) error {
return nil
}
+29
View File
@@ -774,6 +774,35 @@ func (c *complexityVisitor) ConvertNetworkToL1Tx(tx *txs.ConvertNetworkToL1Tx) e
return err
}
func (c *complexityVisitor) CreateSovereignL1Tx(tx *txs.CreateSovereignL1Tx) error {
// Sovereign L1 = network + N validators + K chains. Fee complexity
// is approximately the sum: baseTx + ConvertNetworkToL1 (validators)
// + sum(CreateChain) (chains). Charge as composite.
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
if err != nil {
return err
}
validatorComplexity, err := ConvertNetworkToL1ValidatorComplexity(tx.Validators...)
if err != nil {
return err
}
c.output, err = baseTxComplexity.Add(&validatorComplexity)
if err != nil {
return err
}
// Per-chain genesis adds to the bandwidth dimension.
for _, ch := range tx.Chains {
chainBytes := gas.Dimensions{
gas.Bandwidth: uint64(len(ch.GenesisData)) + uint64(len(ch.BlockchainName)),
}
c.output, err = c.output.Add(&chainBytes)
if err != nil {
return err
}
}
return nil
}
func (c *complexityVisitor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx) error {
baseTxComplexity, err := baseTxComplexity(&tx.BaseTx)
if err != nil {
@@ -129,6 +129,10 @@ func (*staticVisitor) ConvertNetworkToL1Tx(*txs.ConvertNetworkToL1Tx) error {
return ErrUnsupportedTx
}
func (*staticVisitor) CreateSovereignL1Tx(*txs.CreateSovereignL1Tx) error {
return ErrUnsupportedTx
}
func (*staticVisitor) DisableL1ValidatorTx(*txs.DisableL1ValidatorTx) error {
return ErrUnsupportedTx
}
+1
View File
@@ -524,6 +524,7 @@ func (v *visitor) RewardValidatorTx(*RewardValidatorTx) error
func (v *visitor) TransferChainOwnershipTx(*TransferChainOwnershipTx) error { return nil }
func (v *visitor) TransformChainTx(*TransformChainTx) error { return nil }
func (v *visitor) ConvertNetworkToL1Tx(*ConvertNetworkToL1Tx) error { return nil }
func (v *visitor) CreateSovereignL1Tx(*CreateSovereignL1Tx) error { return nil }
func (v *visitor) RegisterL1ValidatorTx(*RegisterL1ValidatorTx) error { return nil }
func (v *visitor) SetL1ValidatorWeightTx(*SetL1ValidatorWeightTx) error { return nil }
func (v *visitor) DisableL1ValidatorTx(*DisableL1ValidatorTx) error { return nil }
+8
View File
@@ -24,6 +24,14 @@ type Visitor interface {
BaseTx(*BaseTx) error
ConvertNetworkToL1Tx(*ConvertNetworkToL1Tx) error
// CreateSovereignL1Tx atomically registers a new sovereign L1
// (network + genesis validators + chain manifest + manager-contract
// handoff) in one tx. The single-step alternative to the legacy
// CreateNetworkTx + AddChainValidatorTx ×N + CreateChainTx ×K +
// ConvertNetworkToL1Tx flow. After commit, the primary network
// neither track-chains nor validates the L1's blocks — the L1's
// own validators run their own consensus from genesis.
CreateSovereignL1Tx(*CreateSovereignL1Tx) error
RegisterL1ValidatorTx(*RegisterL1ValidatorTx) error
SetL1ValidatorWeightTx(*SetL1ValidatorWeightTx) error
IncreaseL1ValidatorBalanceTx(*IncreaseL1ValidatorBalanceTx) error
+4
View File
@@ -171,6 +171,10 @@ func (v *backendVisitor) ConvertNetworkToL1Tx(*txs.ConvertNetworkToL1Tx) error {
return nil
}
func (v *backendVisitor) CreateSovereignL1Tx(*txs.CreateSovereignL1Tx) error {
return nil
}
func (v *backendVisitor) RegisterL1ValidatorTx(*txs.RegisterL1ValidatorTx) error {
return nil
}
+8
View File
@@ -201,6 +201,14 @@ func (s *visitor) ConvertNetworkToL1Tx(tx *txs.ConvertNetworkToL1Tx) error {
return sign(s.tx, true, txSigners)
}
func (s *visitor) CreateSovereignL1Tx(tx *txs.CreateSovereignL1Tx) error {
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
if err != nil {
return err
}
return sign(s.tx, true, txSigners)
}
func (s *visitor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx) error {
txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins)
if err != nil {
+8
View File
@@ -413,6 +413,14 @@ func (s *signerVisitor) ConvertNetworkToL1Tx(tx *txs.ConvertNetworkToL1Tx) error
return sign(s.tx, false, txSigners)
}
func (s *signerVisitor) CreateSovereignL1Tx(tx *txs.CreateSovereignL1Tx) error {
txSigners, err := s.getSigners(constants.PrimaryNetworkID, tx.Ins)
if err != nil {
return err
}
return sign(s.tx, false, txSigners)
}
// CreateAssetTx signs a CreateAssetTx (BaseTx fee inputs only).
func (s *signerVisitor) CreateAssetTx(tx *txs.CreateAssetTx) error {
txSigners, err := s.getSigners(constants.PrimaryNetworkID, tx.Ins)
+7
View File
@@ -136,6 +136,13 @@ func (b *backendVisitor) ConvertNetworkToL1Tx(tx *txs.ConvertNetworkToL1Tx) erro
return b.baseTx(&tx.BaseTx)
}
func (b *backendVisitor) CreateSovereignL1Tx(tx *txs.CreateSovereignL1Tx) error {
// Sovereign L1 launch: the new network ID is derived from the tx
// hash at commit time. The wallet backend does not need to
// preregister owners — that happens P-chain-side during execution.
return b.baseTx(&tx.BaseTx)
}
func (b *backendVisitor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx) error {
warpMessage, err := warp.ParseMessage(tx.Message)
if err != nil {
@@ -1,4 +1,4 @@
// bootstrap-hanzo creates the hanzo subnet + blockchain on testnet/devnet
// bootstrap-hanzo creates the hanzo chain on testnet/devnet
// using a BIP-44 m/44'/9000'/0'/0/<idx> key derived from MNEMONIC.
//
// Usage:
@@ -32,7 +32,7 @@ import (
"github.com/luxfi/utxo/secp256k1fx"
)
// Default hanzo subnet-evm VM ID (matches mainnet hanzo deployment).
// Default hanzo EVM VM ID (matches mainnet hanzo deployment).
const defaultHanzoVMID = "nyGCobireNhxFB7iM5bxV74hAY6j9nQX6wizxfWomnMMtztkr"
func deriveLuxKey(mnemonic string, idx uint32) (*secp256k1.PrivateKey, error) {
@@ -79,13 +79,13 @@ func formatPAddr(hrp string, a ids.ShortID) string {
func main() {
uri := flag.String("uri", "", "luxd API URI (e.g. http://localhost:19640)")
network := flag.String("network", "", "human label: testnet|devnet (for logs)")
genesisPath := flag.String("genesis", "", "EVM genesis JSON for the hanzo subnet")
genesisPath := flag.String("genesis", "", "EVM genesis JSON for the hanzo chain")
hrp := flag.String("hrp", "", "P-chain bech32 HRP: test|dev")
bipIdx := flag.Uint("bip44-idx", 5, "BIP44 derivation index at m/44'/9000'/0'/0/<idx>")
chainName := flag.String("chain-name", "hanzo", "subnet/chain name (don't change unless intentional)")
skipValidators := flag.Bool("skip-validators", false, "skip adding primary validators to the new subnet")
vmIDStr := flag.String("vm-id", defaultHanzoVMID, "subnet-evm VM ID present in luxd's --plugin-dir")
existingSubnetID := flag.String("subnet-id", "", "if set, skip CreateNetworkTx and reuse this subnet ID for the CreateChainTx")
chainName := flag.String("chain-name", "hanzo", "chain name (don't change unless intentional)")
skipValidators := flag.Bool("skip-validators", false, "skip adding primary validators to the new chain")
vmIDStr := flag.String("vm-id", defaultHanzoVMID, "EVM VM ID present in luxd's --plugin-dir")
existingNetID := flag.String("network-id", "", "if set, skip CreateNetworkTx and reuse this network ID for the CreateChainTx")
flag.Parse()
if *uri == "" || *genesisPath == "" || *hrp == "" {
@@ -183,12 +183,12 @@ func main() {
}
var netID ids.ID
if *existingSubnetID != "" {
netID, err = ids.FromString(*existingSubnetID)
if *existingNetID != "" {
netID, err = ids.FromString(*existingNetID)
if err != nil {
log.Fatalf("invalid --subnet-id: %v", err)
log.Fatalf("invalid --network-id: %v", err)
}
log.Printf("[%s] reusing existing subnet ID = %s (skipping CreateNetworkTx)", *network, netID)
log.Printf("[%s] reusing existing network ID = %s (skipping CreateNetworkTx)", *network, netID)
} else {
log.Printf("[%s] IssueCreateNetworkTx ...", *network)
createNetTx, err := w.P().IssueCreateNetworkTx(owner)
@@ -196,7 +196,7 @@ func main() {
log.Fatalf("create network: %v", err)
}
netID = createNetTx.ID()
log.Printf("[%s] subnet (network) ID = %s", *network, netID)
log.Printf("[%s] network ID = %s", *network, netID)
time.Sleep(10 * time.Second)
}
@@ -259,7 +259,7 @@ func main() {
if err != nil {
log.Printf("WARN add validator %s: %v", nid, err)
} else {
log.Printf("[%s] added subnet validator: %s", *network, nid)
log.Printf("[%s] added chain validator: %s", *network, nid)
}
time.Sleep(1 * time.Second)
}
@@ -267,8 +267,8 @@ func main() {
}
fmt.Println()
fmt.Printf("---- %s hanzo subnet bootstrap COMPLETE ----\n", *network)
fmt.Printf("SUBNET_ID=%s\n", netID)
fmt.Printf("---- %s hanzo chain bootstrap COMPLETE ----\n", *network)
fmt.Printf("NETWORK_ID=%s\n", netID)
fmt.Printf("BLOCKCHAIN_ID=%s\n", bcID)
fmt.Printf("VM_ID=%s\n", hanzoVMID)
fmt.Printf("EVM_CHAIN_ID=%v\n", evmChainID)
@@ -15,7 +15,7 @@
// --bip44-idx=10
//
// EVM keys are derived at BIP44 m/44'/60'/0'/0/<idx> (Ethereum coin type 60).
// Index 10 is reserved for heartbeats — validators occupy 0-4 and subnet
// Index 10 is reserved for heartbeats — validators occupy 0-4 and chain
// control keys occupy 5-7.
//
// One failed chain does not abort the run; each chain reports its own error.