mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
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:
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 1–5 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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user