Decomplect network creation: one CreateNetworkTx folds Convert+CreateSovereign

CreateNetworkTx{parent, owner, security, validators, chains, manager} creates a
network at ANY level in one tx — parent is the level axis (Primary⇒L1, L1⇒L2,
recurse; level=depth, never stored). security is a coproduct: SecuritySovereign
(own validators+manager) | SecurityInherited (restaked from parent). manager
lives on the Network so an inherited L2 holds local admin. NetworkValidator +
NetworkChain are shared value components (reused by CreateChainTx). Deleted
ConvertNetworkToL1Tx + CreateSovereignL1Tx (folded / migration artifacts).

Round-trip green: sovereign L1 (own validators+chains+manager) AND inherited L2
(parent recorded, no own validators, local manager) both survive. One and one
way to make a network at any depth.
This commit is contained in:
zeekay
2026-07-10 11:39:12 -07:00
parent e869b7bcd1
commit 4610978bcc
7 changed files with 420 additions and 628 deletions
@@ -1,269 +0,0 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"bytes"
"errors"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/node/vms/platformvm/signer"
"github.com/luxfi/node/vms/platformvm/warp/message"
"github.com/luxfi/runtime"
"github.com/luxfi/utils"
lux "github.com/luxfi/utxo"
"github.com/luxfi/utxo/secp256k1fx"
"github.com/luxfi/vm/types"
"github.com/luxfi/zap"
)
const MaxChainAddressLength = 4096
var (
_ UnsignedTx = (*ConvertNetworkToL1Tx)(nil)
_ utils.Sortable[*ConvertNetworkToL1Validator] = (*ConvertNetworkToL1Validator)(nil)
ErrConvertPermissionlessChain = errors.New("cannot convert a permissionless chain")
ErrAddressTooLong = errors.New("address is too long")
ErrConvertMustIncludeValidators = errors.New("conversion must include at least one validator")
ErrConvertValidatorsNotSortedAndUnique = errors.New("conversion validators must be sorted and unique")
ErrZeroWeight = errors.New("validator weight must be non-zero")
)
// ConvertNetworkToL1Validator is a plain value record (constructor input +
// accessor output). It is encoded into / decoded from the tx's zap buffer by
// the writeConvertValidators / readConvertValidators helpers below.
type ConvertNetworkToL1Validator struct {
NodeID types.JSONByteSlice `json:"nodeID"`
Weight uint64 `json:"weight"`
Balance uint64 `json:"balance"`
Signer signer.ProofOfPossession `json:"signer"`
RemainingBalanceOwner message.PChainOwner `json:"remainingBalanceOwner"`
DeactivationOwner message.PChainOwner `json:"deactivationOwner"`
}
func (v *ConvertNetworkToL1Validator) Compare(o *ConvertNetworkToL1Validator) int {
return bytes.Compare(v.NodeID, o.NodeID)
}
func (v *ConvertNetworkToL1Validator) Verify() error {
if v.Weight == 0 {
return ErrZeroWeight
}
nodeID, err := ids.ToNodeID(v.NodeID)
if err != nil {
return err
}
if nodeID == ids.EmptyNodeID {
return errEmptyNodeID
}
return verify.All(
&v.Signer,
&secp256k1fx.OutputOwners{Threshold: v.RemainingBalanceOwner.Threshold, Addrs: v.RemainingBalanceOwner.Addresses},
&secp256k1fx.OutputOwners{Threshold: v.DeactivationOwner.Threshold, Addrs: v.DeactivationOwner.Addresses},
)
}
// ---- ConvertNetworkToL1Validator list wire ----
//
// Fixed-stride entry (192 bytes); variable NodeID bytes live in a shared byte
// blob, owner addresses in a shared 20-byte-stride pool (each owner slices
// [start, start+count) into it).
const (
convVdrWeight = 0 // u64
convVdrBalance = 8 // u64
convVdrSignerPub = 16 // 48B
convVdrSignerPoP = 64 // 96B
convVdrNodeIDStart = 160 // u32 (into shared NodeID blob)
convVdrNodeIDLen = 164 // u32
convVdrRemThreshold = 168 // u32
convVdrRemAddrStart = 172 // u32 (into shared addr pool)
convVdrRemAddrCount = 176 // u32
convVdrDeacThresh = 180 // u32
convVdrDeacAddrStart = 184 // u32
convVdrDeacAddrCount = 188 // u32
convVdrStride = 192
)
// writeConvertValidators writes the fixed-stride validator list plus the two
// shared pools (NodeID bytes, owner addresses) into the builder, returning the
// list + pool pointers for the parent object.
func writeConvertValidators(b *zap.Builder, vdrs []*ConvertNetworkToL1Validator) (listOff, listCount int, nodeIDs []byte, addrs []ids.ShortID) {
if len(vdrs) == 0 {
return 0, 0, nil, nil
}
lb := b.StartList(convVdrStride)
for _, v := range vdrs {
var e [convVdrStride]byte
putU64(e[convVdrWeight:], v.Weight)
putU64(e[convVdrBalance:], v.Balance)
copy(e[convVdrSignerPub:], v.Signer.PublicKey[:])
copy(e[convVdrSignerPoP:], v.Signer.ProofOfPossession[:])
putU32(e[convVdrNodeIDStart:], uint32(len(nodeIDs)))
putU32(e[convVdrNodeIDLen:], uint32(len(v.NodeID)))
nodeIDs = append(nodeIDs, v.NodeID...)
putU32(e[convVdrRemThreshold:], v.RemainingBalanceOwner.Threshold)
putU32(e[convVdrRemAddrStart:], uint32(len(addrs)))
putU32(e[convVdrRemAddrCount:], uint32(len(v.RemainingBalanceOwner.Addresses)))
addrs = append(addrs, v.RemainingBalanceOwner.Addresses...)
putU32(e[convVdrDeacThresh:], v.DeactivationOwner.Threshold)
putU32(e[convVdrDeacAddrStart:], uint32(len(addrs)))
putU32(e[convVdrDeacAddrCount:], uint32(len(v.DeactivationOwner.Addresses)))
addrs = append(addrs, v.DeactivationOwner.Addresses...)
lb.AddBytes(e[:])
}
listOff, _ = lb.Finish()
listCount = len(vdrs) // AddBytes counts bytes, not elements
return listOff, listCount, nodeIDs, addrs
}
// readConvertValidators reconstructs the validator records from the list +
// pools at the given offsets.
func readConvertValidators(obj zap.Object, listOff, nodeIDPoolOff, addrPoolOff int) []*ConvertNetworkToL1Validator {
list := obj.ListStride(listOff, convVdrStride)
n := list.Len()
if n == 0 {
return nil
}
nodeIDBlob := obj.Bytes(nodeIDPoolOff)
addrs := obj.ListStride(addrPoolOff, addrStride)
out := make([]*ConvertNetworkToL1Validator, n)
for i := 0; i < n; i++ {
e := list.Object(i, convVdrStride)
v := &ConvertNetworkToL1Validator{
Weight: e.Uint64(convVdrWeight),
Balance: e.Uint64(convVdrBalance),
}
copy(v.Signer.PublicKey[:], e.BytesFixedSlice(convVdrSignerPub, 48))
copy(v.Signer.ProofOfPossession[:], e.BytesFixedSlice(convVdrSignerPoP, 96))
nStart, nLen := e.Uint32(convVdrNodeIDStart), e.Uint32(convVdrNodeIDLen)
if nLen > 0 && int(nStart)+int(nLen) <= len(nodeIDBlob) {
v.NodeID = append([]byte(nil), nodeIDBlob[nStart:nStart+nLen]...)
}
v.RemainingBalanceOwner = message.PChainOwner{
Threshold: e.Uint32(convVdrRemThreshold),
Addresses: sliceAddrs(addrs, e.Uint32(convVdrRemAddrStart), e.Uint32(convVdrRemAddrCount)),
}
v.DeactivationOwner = message.PChainOwner{
Threshold: e.Uint32(convVdrDeacThresh),
Addresses: sliceAddrs(addrs, e.Uint32(convVdrDeacAddrStart), e.Uint32(convVdrDeacAddrCount)),
}
out[i] = v
}
return out
}
// ---- ConvertNetworkToL1Tx (kindConvertNetworkToL1) ----
//
// Envelope + Chain@77 + ManagerChainID@109 + Address(bytes)@141 +
// ValidatorsList@149 + NodeIDPool(bytes)@157 + AddrPool@165 + ChainAuth@173.
const (
offConv_Chain = spendSize // 32B
offConv_ManagerChainID = spendSize + 32 // 32B
offConv_Address = spendSize + 64 // bytes ptr (8B)
offConv_Validators = spendSize + 72 // list ptr (8B)
offConv_NodeIDPool = spendSize + 80 // bytes ptr (8B)
offConv_AddrPool = spendSize + 88 // list ptr (8B)
offConv_ChainAuth = spendSize + 96 // sig-idx list ptr (8B)
sizeConvTx = spendSize + 104
)
type ConvertNetworkToL1Tx struct {
spendingTx
}
func NewConvertNetworkToL1Tx(
base *lux.BaseTx,
chain, managerChainID ids.ID,
address []byte,
validators []*ConvertNetworkToL1Validator,
chainAuth verify.Verifiable,
) (*ConvertNetworkToL1Tx, error) {
b := zap.NewBuilder(zap.HeaderSize + 512 + sizeConvTx + len(validators)*convVdrStride)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
vdrOff, vdrCount, nodeIDPool, addrPool := writeConvertValidators(b, validators)
authOff, authCount, err := writeAuth(b, chainAuth)
if err != nil {
return nil, err
}
addrOff, addrCount := 0, 0
if len(addrPool) > 0 {
alb := b.StartList(addrStride)
for _, a := range addrPool {
alb.AddBytes(a[:])
}
addrOff, _ = alb.Finish()
addrCount = len(addrPool)
}
ob := b.StartObject(sizeConvTx)
setEnvelope(ob, kindConvertNetworkToL1, base, p)
setID(ob, offConv_Chain, chain)
setID(ob, offConv_ManagerChainID, managerChainID)
ob.SetBytes(offConv_Address, address)
ob.SetList(offConv_Validators, vdrOff, vdrCount)
ob.SetBytes(offConv_NodeIDPool, nodeIDPool)
ob.SetList(offConv_AddrPool, addrOff, addrCount)
ob.SetList(offConv_ChainAuth, authOff, authCount)
ob.FinishAsRoot()
msg, _ := zap.Parse(b.Finish())
return &ConvertNetworkToL1Tx{spendingTx{msg: msg}}, nil
}
func (tx *ConvertNetworkToL1Tx) Chain() ids.ID { return readID(tx.root(), offConv_Chain) }
func (tx *ConvertNetworkToL1Tx) ManagerChainID() ids.ID { return readID(tx.root(), offConv_ManagerChainID) }
func (tx *ConvertNetworkToL1Tx) Address() []byte {
if a := tx.root().Bytes(offConv_Address); len(a) > 0 {
return append([]byte(nil), a...)
}
return nil
}
func (tx *ConvertNetworkToL1Tx) Validators() []*ConvertNetworkToL1Validator {
return readConvertValidators(tx.root(), offConv_Validators, offConv_NodeIDPool, offConv_AddrPool)
}
func (tx *ConvertNetworkToL1Tx) ChainAuth() verify.Verifiable {
return readAuth(tx.root(), offConv_ChainAuth)
}
func (tx *ConvertNetworkToL1Tx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
return ErrNilTx
case tx.Chain() == constants.PrimaryNetworkID:
return ErrConvertPermissionlessChain
case len(tx.Address()) > MaxChainAddressLength:
return ErrAddressTooLong
}
vdrs := tx.Validators()
if len(vdrs) == 0 {
return ErrConvertMustIncludeValidators
}
if !utils.IsSortedAndUnique(vdrs) {
return ErrConvertValidatorsNotSortedAndUnique
}
if err := verifyBaseTx(tx.baseTx(), rt); err != nil {
return err
}
for _, vdr := range vdrs {
if err := vdr.Verify(); err != nil {
return err
}
}
return tx.ChainAuth().Verify()
}
func (tx *ConvertNetworkToL1Tx) Visit(visitor Visitor) error {
return visitor.ConvertNetworkToL1Tx(tx)
}
+384 -34
View File
@@ -4,73 +4,423 @@
package txs
import (
"context"
"bytes"
"errors"
"unicode"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/node/vms/platformvm/fx"
"github.com/luxfi/node/vms/platformvm/signer"
"github.com/luxfi/node/vms/platformvm/warp/message"
"github.com/luxfi/runtime"
"github.com/luxfi/utils"
lux "github.com/luxfi/utxo"
"github.com/luxfi/utxo/secp256k1fx"
"github.com/luxfi/vm/types"
"github.com/luxfi/zap"
)
var _ UnsignedTx = (*CreateNetworkTx)(nil)
// CreateNetworkTx is an unsigned proposal to create a new network. The struct
// IS the wire: it embeds spendingTx (the envelope) and adds the owner header
// inline (threshold + locktime + addr-list ptr).
// CreateNetworkTx is the sole network constructor — the decomplected fold of
// the former CreateNetwork + ConvertNetworkToL1 + CreateSovereignL1 txs. It
// creates a network at ANY level of the hierarchy in one tx:
//
// Wire: zap header + object{ envelope@0..76, Owner{ threshold:u32@77,
// locktime:u64@81, addrs:listptr@89 } } (kind=kindCreateNetwork).
// - Parent = the parent network. Primary ⇒ an L1; an L1 ⇒ an L2; recurse
// for L3/L4. The tx is byte-identical at every level — only Parent differs.
// level = depth in the parent tree; it is derivable, never stored.
// - Owner authorises future admin against the network record.
// - Security is a coproduct: Sovereign (own Validators + Manager) or
// Inherited (restaked from Parent). SecurityMode selects it.
// - Chains are created at genesis (may be empty; more via CreateChainTx).
// - Manager (chain index + address) is the on-chain validator-manager; it
// lives on the Network, so even an Inherited L2 can hold local admin.
//
// The new network's ID is derived from this tx's hash. The primary network
// records the network but does not track or validate its blocks.
const (
SecurityInherited uint8 = 0 // restaked from Parent's validator set
SecuritySovereign uint8 = 1 // own validator set + Manager
)
const (
MaxChainAddressLength = 4096
MaxNetworkChains = 16
)
var (
_ UnsignedTx = (*CreateNetworkTx)(nil)
_ utils.Sortable[*NetworkValidator] = (*NetworkValidator)(nil)
ErrZeroWeight = errors.New("validator weight must be non-zero")
ErrAddressTooLong = errors.New("address is too long")
ErrValidatorsNotSortedAndUnique = errors.New("validators must be sorted and unique")
ErrSovereignMustIncludeValidator = errors.New("sovereign network must include at least one validator")
ErrInheritedMustNotHaveValidator = errors.New("inherited network must not carry its own validators")
ErrNetworkTooManyChains = errors.New("network exceeds MaxNetworkChains")
ErrNetworkManagerIdxOutOfRange = errors.New("managerChainIdx out of range for chains[]")
ErrChainNameTooLong = errors.New("chain name exceeds MaxNameLen")
ErrChainNameIllegal = errors.New("chain name contains illegal characters")
ErrChainVMIDEmpty = errors.New("chain VMID must not be empty")
ErrChainFxIDsNotSorted = errors.New("chain FxIDs must be sorted and unique")
ErrChainGenesisTooLong = errors.New("chain genesis exceeds MaxGenesisLen")
ErrUnknownSecurityMode = errors.New("unknown security mode")
)
// NetworkValidator is a genesis validator value (shared component; encoded
// into / decoded from the tx buffer).
type NetworkValidator struct {
NodeID types.JSONByteSlice
Weight uint64
Balance uint64
Signer signer.ProofOfPossession
RemainingBalanceOwner message.PChainOwner
DeactivationOwner message.PChainOwner
}
func (v *NetworkValidator) Compare(o *NetworkValidator) int { return bytes.Compare(v.NodeID, o.NodeID) }
func (v *NetworkValidator) Verify() error {
if v.Weight == 0 {
return ErrZeroWeight
}
nodeID, err := ids.ToNodeID(v.NodeID)
if err != nil {
return err
}
if nodeID == ids.EmptyNodeID {
return errEmptyNodeID
}
return verify.All(
&v.Signer,
&secp256k1fx.OutputOwners{Threshold: v.RemainingBalanceOwner.Threshold, Addrs: v.RemainingBalanceOwner.Addresses},
&secp256k1fx.OutputOwners{Threshold: v.DeactivationOwner.Threshold, Addrs: v.DeactivationOwner.Addresses},
)
}
// NetworkChain is a genesis-chain value (shared with CreateChainTx).
type NetworkChain struct {
BlockchainName string
VMID ids.ID
FxIDs []ids.ID
GenesisData []byte
}
func (ch *NetworkChain) Verify() error {
if len(ch.BlockchainName) > MaxNameLen {
return ErrChainNameTooLong
}
for _, r := range ch.BlockchainName {
if r > unicode.MaxASCII || (!unicode.IsLetter(r) && !unicode.IsNumber(r) && r != ' ') {
return ErrChainNameIllegal
}
}
if ch.VMID == ids.Empty {
return ErrChainVMIDEmpty
}
if !utils.IsSortedAndUnique(ch.FxIDs) {
return ErrChainFxIDsNotSorted
}
if len(ch.GenesisData) > MaxGenesisLen {
return ErrChainGenesisTooLong
}
return nil
}
// ---- shared NetworkValidator list wire (fixed-stride 192) ----
const (
nvWeight = 0
nvBalance = 8
nvSignerPub = 16
nvSignerPoP = 64
nvNodeIDStart = 160
nvNodeIDLen = 164
nvRemThreshold = 168
nvRemAddrStart = 172
nvRemAddrCount = 176
nvDeacThreshold = 180
nvDeacAddrStart = 184
nvDeacAddrCount = 188
nvStride = 192
)
func writeNetworkValidators(b *zap.Builder, vdrs []*NetworkValidator) (listOff, listCount int, nodeIDs []byte, addrs []ids.ShortID) {
if len(vdrs) == 0 {
return 0, 0, nil, nil
}
lb := b.StartList(nvStride)
for _, v := range vdrs {
var e [nvStride]byte
putU64(e[nvWeight:], v.Weight)
putU64(e[nvBalance:], v.Balance)
copy(e[nvSignerPub:], v.Signer.PublicKey[:])
copy(e[nvSignerPoP:], v.Signer.ProofOfPossession[:])
putU32(e[nvNodeIDStart:], uint32(len(nodeIDs)))
putU32(e[nvNodeIDLen:], uint32(len(v.NodeID)))
nodeIDs = append(nodeIDs, v.NodeID...)
putU32(e[nvRemThreshold:], v.RemainingBalanceOwner.Threshold)
putU32(e[nvRemAddrStart:], uint32(len(addrs)))
putU32(e[nvRemAddrCount:], uint32(len(v.RemainingBalanceOwner.Addresses)))
addrs = append(addrs, v.RemainingBalanceOwner.Addresses...)
putU32(e[nvDeacThreshold:], v.DeactivationOwner.Threshold)
putU32(e[nvDeacAddrStart:], uint32(len(addrs)))
putU32(e[nvDeacAddrCount:], uint32(len(v.DeactivationOwner.Addresses)))
addrs = append(addrs, v.DeactivationOwner.Addresses...)
lb.AddBytes(e[:])
}
listOff, _ = lb.Finish()
listCount = len(vdrs) // AddBytes counts bytes, not elements
return listOff, listCount, nodeIDs, addrs
}
func readNetworkValidators(obj zap.Object, listOff, nodeIDPoolOff, addrPoolOff int) []*NetworkValidator {
list := obj.ListStride(listOff, nvStride)
n := list.Len()
if n == 0 {
return nil
}
nodeIDBlob := obj.Bytes(nodeIDPoolOff)
addrs := obj.ListStride(addrPoolOff, addrStride)
out := make([]*NetworkValidator, n)
for i := 0; i < n; i++ {
e := list.Object(i, nvStride)
v := &NetworkValidator{Weight: e.Uint64(nvWeight), Balance: e.Uint64(nvBalance)}
copy(v.Signer.PublicKey[:], e.BytesFixedSlice(nvSignerPub, 48))
copy(v.Signer.ProofOfPossession[:], e.BytesFixedSlice(nvSignerPoP, 96))
if ns, nl := e.Uint32(nvNodeIDStart), e.Uint32(nvNodeIDLen); nl > 0 && int(ns)+int(nl) <= len(nodeIDBlob) {
v.NodeID = append([]byte(nil), nodeIDBlob[ns:ns+nl]...)
}
v.RemainingBalanceOwner = message.PChainOwner{Threshold: e.Uint32(nvRemThreshold), Addresses: sliceAddrs(addrs, e.Uint32(nvRemAddrStart), e.Uint32(nvRemAddrCount))}
v.DeactivationOwner = message.PChainOwner{Threshold: e.Uint32(nvDeacThreshold), Addresses: sliceAddrs(addrs, e.Uint32(nvDeacAddrStart), e.Uint32(nvDeacAddrCount))}
out[i] = v
}
return out
}
// ---- shared NetworkChain list wire (fixed-stride 56) ----
const (
ncVMID = 0
ncNameOff = 32
ncNameLen = 36
ncFxStart = 40
ncFxCount = 44
ncGenOff = 48
ncGenLen = 52
ncStride = 56
)
func writeNetworkChains(b *zap.Builder, chains []*NetworkChain) (listOff, listCount int, nameBlob []byte, fxIDs []ids.ID, genBlob []byte) {
if len(chains) == 0 {
return 0, 0, nil, nil, nil
}
lb := b.StartList(ncStride)
for _, ch := range chains {
var e [ncStride]byte
copy(e[ncVMID:], ch.VMID[:])
putU32(e[ncNameOff:], uint32(len(nameBlob)))
putU32(e[ncNameLen:], uint32(len(ch.BlockchainName)))
nameBlob = append(nameBlob, ch.BlockchainName...)
putU32(e[ncFxStart:], uint32(len(fxIDs)))
putU32(e[ncFxCount:], uint32(len(ch.FxIDs)))
fxIDs = append(fxIDs, ch.FxIDs...)
putU32(e[ncGenOff:], uint32(len(genBlob)))
putU32(e[ncGenLen:], uint32(len(ch.GenesisData)))
genBlob = append(genBlob, ch.GenesisData...)
lb.AddBytes(e[:])
}
listOff, _ = lb.Finish()
listCount = len(chains)
return listOff, listCount, nameBlob, fxIDs, genBlob
}
func readNetworkChains(obj zap.Object, listOff, namePoolOff, fxPoolOff, genPoolOff int) []*NetworkChain {
list := obj.ListStride(listOff, ncStride)
n := list.Len()
if n == 0 {
return nil
}
nameBlob := obj.Bytes(namePoolOff)
fxPool := obj.ListStride(fxPoolOff, 32)
genBlob := obj.Bytes(genPoolOff)
out := make([]*NetworkChain, n)
for i := 0; i < n; i++ {
e := list.Object(i, ncStride)
ch := &NetworkChain{VMID: readID(e, ncVMID)}
if ns, nl := e.Uint32(ncNameOff), e.Uint32(ncNameLen); nl > 0 && int(ns)+int(nl) <= len(nameBlob) {
ch.BlockchainName = string(nameBlob[ns : ns+nl])
}
ch.FxIDs = sliceIDs(fxPool, e.Uint32(ncFxStart), e.Uint32(ncFxCount))
if gs, gl := e.Uint32(ncGenOff), e.Uint32(ncGenLen); gl > 0 && int(gs)+int(gl) <= len(genBlob) {
ch.GenesisData = append([]byte(nil), genBlob[gs:gs+gl]...)
}
out[i] = ch
}
return out
}
// sliceIDs slices [start, start+count) from a 32-byte-stride id pool.
func sliceIDs(pool zap.List, start, count uint32) []ids.ID {
total := uint32(pool.Len())
if count == 0 || start > total || count > total-start {
return nil
}
out := make([]ids.ID, count)
for i := uint32(0); i < count; i++ {
out[i] = readID(pool.Object(int(start+i), 32), 0)
}
return out
}
// ---- CreateNetworkTx (kindCreateNetwork) ----
const (
offCN_Parent = spendSize // 32B
offCN_OwnerThreshold = spendSize + 32 // u32
offCN_OwnerLocktime = spendSize + 36 // u64
offCN_OwnerAddrPtr = spendSize + 44 // 8B
offCN_SecurityMode = spendSize + 52 // u8
offCN_Validators = spendSize + 53 // 8B list
offCN_ValNodeIDPool = spendSize + 61 // 8B bytes
offCN_ValAddrPool = spendSize + 69 // 8B list
offCN_Chains = spendSize + 77 // 8B list
offCN_ChNamePool = spendSize + 85 // 8B bytes
offCN_ChFxPool = spendSize + 93 // 8B list
offCN_ChGenPool = spendSize + 101 // 8B bytes
offCN_ManagerChainIdx = spendSize + 109 // u32
offCN_ManagerAddress = spendSize + 113 // 8B bytes
sizeCNTx = spendSize + 121
)
type CreateNetworkTx struct {
spendingTx
}
const (
offCreateNetOwnerThreshold = spendSize // u32
offCreateNetOwnerLocktime = 81 // u64
offCreateNetOwnerAddrs = 89 // list ptr (8B)
sizeCreateNet = 97
)
// NewCreateNetworkTx builds the tx into a fresh zap buffer.
func NewCreateNetworkTx(base *lux.BaseTx, owner fx.Owner) (*CreateNetworkTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 256 + sizeCreateNet)
func NewCreateNetworkTx(
base *lux.BaseTx,
parent ids.ID,
owner fx.Owner,
securityMode uint8,
validators []*NetworkValidator,
chains []*NetworkChain,
managerChainIdx uint32,
managerAddress []byte,
) (*CreateNetworkTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 1024 + sizeCNTx + len(validators)*nvStride + len(chains)*ncStride)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
threshold, locktime, addrOff, addrCount, err := writeOwner(b, owner)
oThreshold, oLocktime, oAddrOff, oAddrCount, err := writeOwner(b, owner)
if err != nil {
return nil, err
}
ob := b.StartObject(sizeCreateNet)
vdrOff, vdrCount, nodeIDPool, addrPool := writeNetworkValidators(b, validators)
valAddrOff, valAddrCount := 0, 0
if len(addrPool) > 0 {
alb := b.StartList(addrStride)
for _, a := range addrPool {
alb.AddBytes(a[:])
}
valAddrOff, _ = alb.Finish()
valAddrCount = len(addrPool)
}
chOff, chCount, nameBlob, fxIDs, genBlob := writeNetworkChains(b, chains)
fxOff, fxCount := 0, 0
if len(fxIDs) > 0 {
flb := b.StartList(32)
for _, id := range fxIDs {
flb.AddBytes(id[:])
}
fxOff, _ = flb.Finish()
fxCount = len(fxIDs)
}
ob := b.StartObject(sizeCNTx)
setEnvelope(ob, kindCreateNetwork, base, p)
setOwner(ob, offCreateNetOwnerThreshold, offCreateNetOwnerLocktime, offCreateNetOwnerAddrs, threshold, locktime, addrOff, addrCount)
setID(ob, offCN_Parent, parent)
setOwner(ob, offCN_OwnerThreshold, offCN_OwnerLocktime, offCN_OwnerAddrPtr, oThreshold, oLocktime, oAddrOff, oAddrCount)
ob.SetUint8(offCN_SecurityMode, securityMode)
ob.SetList(offCN_Validators, vdrOff, vdrCount)
ob.SetBytes(offCN_ValNodeIDPool, nodeIDPool)
ob.SetList(offCN_ValAddrPool, valAddrOff, valAddrCount)
ob.SetList(offCN_Chains, chOff, chCount)
ob.SetBytes(offCN_ChNamePool, nameBlob)
ob.SetList(offCN_ChFxPool, fxOff, fxCount)
ob.SetBytes(offCN_ChGenPool, genBlob)
ob.SetUint32(offCN_ManagerChainIdx, managerChainIdx)
ob.SetBytes(offCN_ManagerAddress, managerAddress)
ob.FinishAsRoot()
msg, err := zap.Parse(b.Finish())
if err != nil {
return nil, err
}
return &CreateNetworkTx{spendingTx{msg}}, nil
msg, _ := zap.Parse(b.Finish())
return &CreateNetworkTx{spendingTx{msg: msg}}, nil
}
// Owner is who is authorized to manage this network (offset read).
func (tx *CreateNetworkTx) Parent() ids.ID { return readID(tx.root(), offCN_Parent) }
func (tx *CreateNetworkTx) Owner() fx.Owner {
return readOwner(tx.root(), offCreateNetOwnerThreshold, offCreateNetOwnerLocktime, offCreateNetOwnerAddrs)
return readOwner(tx.root(), offCN_OwnerThreshold, offCN_OwnerLocktime, offCN_OwnerAddrPtr)
}
func (tx *CreateNetworkTx) SecurityMode() uint8 { return tx.root().Uint8(offCN_SecurityMode) }
func (tx *CreateNetworkTx) Sovereign() bool { return tx.SecurityMode() == SecuritySovereign }
func (tx *CreateNetworkTx) Validators() []*NetworkValidator {
return readNetworkValidators(tx.root(), offCN_Validators, offCN_ValNodeIDPool, offCN_ValAddrPool)
}
func (tx *CreateNetworkTx) Chains() []*NetworkChain {
return readNetworkChains(tx.root(), offCN_Chains, offCN_ChNamePool, offCN_ChFxPool, offCN_ChGenPool)
}
func (tx *CreateNetworkTx) ManagerChainIdx() uint32 { return tx.root().Uint32(offCN_ManagerChainIdx) }
func (tx *CreateNetworkTx) ManagerAddress() []byte {
if a := tx.root().Bytes(offCN_ManagerAddress); len(a) > 0 {
return append([]byte(nil), a...)
}
return nil
}
// SyntacticVerify verifies that this transaction is well-formed.
func (tx *CreateNetworkTx) SyntacticVerify(rt *runtime.Runtime) error {
if tx == nil {
return ErrNilTx
}
vdrs := tx.Validators()
chains := tx.Chains()
switch tx.SecurityMode() {
case SecuritySovereign:
if len(vdrs) == 0 {
return ErrSovereignMustIncludeValidator
}
case SecurityInherited:
if len(vdrs) != 0 {
return ErrInheritedMustNotHaveValidator
}
default:
return ErrUnknownSecurityMode
}
switch {
case !utils.IsSortedAndUnique(vdrs):
return ErrValidatorsNotSortedAndUnique
case len(chains) > MaxNetworkChains:
return ErrNetworkTooManyChains
case len(chains) > 0 && int(tx.ManagerChainIdx()) >= len(chains):
return ErrNetworkManagerIdxOutOfRange
case len(tx.ManagerAddress()) > MaxChainAddressLength:
return ErrAddressTooLong
}
if err := verifyBaseTx(tx.baseTx(), rt); err != nil {
return err
}
return tx.Owner().Verify()
if err := tx.Owner().Verify(); err != nil {
return err
}
for _, vdr := range vdrs {
if err := vdr.Verify(); err != nil {
return err
}
}
for _, ch := range chains {
if err := ch.Verify(); err != nil {
return err
}
}
return nil
}
func (tx *CreateNetworkTx) Visit(visitor Visitor) error {
return visitor.CreateNetworkTx(tx)
}
func (tx *CreateNetworkTx) Visit(visitor Visitor) error { return visitor.CreateNetworkTx(tx) }
func (tx *CreateNetworkTx) Initialize(context.Context) error { return nil }
var _ = constants.PrimaryNetworkID
@@ -1,283 +0,0 @@
// 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/ids"
"github.com/luxfi/node/vms/platformvm/fx"
"github.com/luxfi/runtime"
"github.com/luxfi/utils"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
// CreateSovereignL1Tx atomically registers a new sovereign L1 on the P-chain —
// the one-step alternative to CreateNetworkTx + AddChainValidatorTx(×N) +
// CreateChainTx(×K) + ConvertNetworkToL1Tx. The new L1's network ID is derived
// from this tx's hash; the primary network keeps a permanent record of the
// owner, the genesis validator set (NodeIDs + BLS PoPs + weights), the chain
// manifest, and the on-chain manager contract. The primary network does not
// track or validate the L1's blocks.
type CreateSovereignL1Tx struct {
spendingTx
}
// SovereignL1Chain describes one chain on a newly-minted sovereign L1 (a plain
// value record; encoded into / decoded from the tx's zap buffer).
type SovereignL1Chain struct {
BlockchainName string `json:"blockchainName"`
VMID ids.ID `json:"vmID"`
FxIDs []ids.ID `json:"fxIDs"`
GenesisData []byte `json:"genesisData"`
}
const 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")
)
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
}
// ---- SovereignL1Chain list wire ----
//
// Fixed-stride entry (56 bytes); name/genesis bytes live in shared blobs,
// FxIDs in a shared 32-byte-stride pool.
const (
sovChVMID = 0 // 32B
sovChNameOff = 32 // u32 (into name blob)
sovChNameLen = 36 // u32
sovChFxStart = 40 // u32 (into fx pool)
sovChFxCount = 44 // u32
sovChGenOff = 48 // u32 (into genesis blob)
sovChGenLen = 52 // u32
sovChStride = 56
)
func writeSovereignChains(b *zap.Builder, chains []*SovereignL1Chain) (listOff, listCount int, nameBlob []byte, fxIDs []ids.ID, genBlob []byte) {
if len(chains) == 0 {
return 0, 0, nil, nil, nil
}
lb := b.StartList(sovChStride)
for _, ch := range chains {
var e [sovChStride]byte
copy(e[sovChVMID:], ch.VMID[:])
putU32(e[sovChNameOff:], uint32(len(nameBlob)))
putU32(e[sovChNameLen:], uint32(len(ch.BlockchainName)))
nameBlob = append(nameBlob, ch.BlockchainName...)
putU32(e[sovChFxStart:], uint32(len(fxIDs)))
putU32(e[sovChFxCount:], uint32(len(ch.FxIDs)))
fxIDs = append(fxIDs, ch.FxIDs...)
putU32(e[sovChGenOff:], uint32(len(genBlob)))
putU32(e[sovChGenLen:], uint32(len(ch.GenesisData)))
genBlob = append(genBlob, ch.GenesisData...)
lb.AddBytes(e[:])
}
listOff, _ = lb.Finish()
listCount = len(chains) // AddBytes counts bytes, not elements
return listOff, listCount, nameBlob, fxIDs, genBlob
}
func readSovereignChains(obj zap.Object, listOff, namePoolOff, fxPoolOff, genPoolOff int) []*SovereignL1Chain {
list := obj.ListStride(listOff, sovChStride)
n := list.Len()
if n == 0 {
return nil
}
nameBlob := obj.Bytes(namePoolOff)
fxPool := obj.ListStride(fxPoolOff, 32)
genBlob := obj.Bytes(genPoolOff)
out := make([]*SovereignL1Chain, n)
for i := 0; i < n; i++ {
e := list.Object(i, sovChStride)
ch := &SovereignL1Chain{VMID: readID(e, sovChVMID)}
if ns, nl := e.Uint32(sovChNameOff), e.Uint32(sovChNameLen); nl > 0 && int(ns)+int(nl) <= len(nameBlob) {
ch.BlockchainName = string(nameBlob[ns : ns+nl])
}
ch.FxIDs = sliceIDs(fxPool, e.Uint32(sovChFxStart), e.Uint32(sovChFxCount))
if gs, gl := e.Uint32(sovChGenOff), e.Uint32(sovChGenLen); gl > 0 && int(gs)+int(gl) <= len(genBlob) {
ch.GenesisData = append([]byte(nil), genBlob[gs:gs+gl]...)
}
out[i] = ch
}
return out
}
// sliceIDs slices [start, start+count) from a 32-byte-stride id pool.
func sliceIDs(pool zap.List, start, count uint32) []ids.ID {
total := uint32(pool.Len())
if count == 0 || start > total || count > total-start {
return nil
}
out := make([]ids.ID, count)
for i := uint32(0); i < count; i++ {
out[i] = readID(pool.Object(int(start+i), 32), 0)
}
return out
}
// ---- CreateSovereignL1Tx (kindCreateSovereignL1) ----
const (
offSov_OwnerThreshold = spendSize // u32
offSov_OwnerLocktime = spendSize + 4 // u64
offSov_OwnerAddrPtr = spendSize + 12 // 8B
offSov_Validators = spendSize + 20 // 8B list
offSov_ConvNodeIDPool = spendSize + 28 // 8B bytes
offSov_ConvAddrPool = spendSize + 36 // 8B list
offSov_Chains = spendSize + 44 // 8B list
offSov_ChainNamePool = spendSize + 52 // 8B bytes
offSov_ChainFxPool = spendSize + 60 // 8B list
offSov_ChainGenPool = spendSize + 68 // 8B bytes
offSov_ManagerIdx = spendSize + 76 // u32
offSov_ManagerAddress = spendSize + 80 // 8B bytes
sizeSovTx = spendSize + 88
)
func NewCreateSovereignL1Tx(
base *lux.BaseTx,
owner fx.Owner,
validators []*ConvertNetworkToL1Validator,
chains []*SovereignL1Chain,
managerChainIdx uint32,
managerAddress []byte,
) (*CreateSovereignL1Tx, error) {
b := zap.NewBuilder(zap.HeaderSize + 1024 + sizeSovTx + len(validators)*convVdrStride + len(chains)*sovChStride)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
oThreshold, oLocktime, oAddrOff, oAddrCount, err := writeOwner(b, owner)
if err != nil {
return nil, err
}
vdrOff, vdrCount, nodeIDPool, addrPool := writeConvertValidators(b, validators)
convAddrOff, convAddrCount := 0, 0
if len(addrPool) > 0 {
alb := b.StartList(addrStride)
for _, a := range addrPool {
alb.AddBytes(a[:])
}
convAddrOff, _ = alb.Finish()
convAddrCount = len(addrPool)
}
chOff, chCount, nameBlob, fxIDs, genBlob := writeSovereignChains(b, chains)
fxOff, fxCount := 0, 0
if len(fxIDs) > 0 {
flb := b.StartList(32)
for _, id := range fxIDs {
flb.AddBytes(id[:])
}
fxOff, _ = flb.Finish()
fxCount = len(fxIDs)
}
ob := b.StartObject(sizeSovTx)
setEnvelope(ob, kindCreateSovereignL1, base, p)
setOwner(ob, offSov_OwnerThreshold, offSov_OwnerLocktime, offSov_OwnerAddrPtr, oThreshold, oLocktime, oAddrOff, oAddrCount)
ob.SetList(offSov_Validators, vdrOff, vdrCount)
ob.SetBytes(offSov_ConvNodeIDPool, nodeIDPool)
ob.SetList(offSov_ConvAddrPool, convAddrOff, convAddrCount)
ob.SetList(offSov_Chains, chOff, chCount)
ob.SetBytes(offSov_ChainNamePool, nameBlob)
ob.SetList(offSov_ChainFxPool, fxOff, fxCount)
ob.SetBytes(offSov_ChainGenPool, genBlob)
ob.SetUint32(offSov_ManagerIdx, managerChainIdx)
ob.SetBytes(offSov_ManagerAddress, managerAddress)
ob.FinishAsRoot()
msg, _ := zap.Parse(b.Finish())
return &CreateSovereignL1Tx{spendingTx{msg: msg}}, nil
}
func (tx *CreateSovereignL1Tx) Owner() fx.Owner {
return readOwner(tx.root(), offSov_OwnerThreshold, offSov_OwnerLocktime, offSov_OwnerAddrPtr)
}
func (tx *CreateSovereignL1Tx) Validators() []*ConvertNetworkToL1Validator {
return readConvertValidators(tx.root(), offSov_Validators, offSov_ConvNodeIDPool, offSov_ConvAddrPool)
}
func (tx *CreateSovereignL1Tx) Chains() []*SovereignL1Chain {
return readSovereignChains(tx.root(), offSov_Chains, offSov_ChainNamePool, offSov_ChainFxPool, offSov_ChainGenPool)
}
func (tx *CreateSovereignL1Tx) ManagerChainIdx() uint32 { return tx.root().Uint32(offSov_ManagerIdx) }
func (tx *CreateSovereignL1Tx) ManagerAddress() []byte {
if a := tx.root().Bytes(offSov_ManagerAddress); len(a) > 0 {
return append([]byte(nil), a...)
}
return nil
}
func (tx *CreateSovereignL1Tx) SyntacticVerify(rt *runtime.Runtime) error {
if tx == nil {
return ErrNilTx
}
vdrs := tx.Validators()
chains := tx.Chains()
switch {
case len(vdrs) == 0:
return ErrSovereignMustIncludeValidators
case !utils.IsSortedAndUnique(vdrs):
return ErrConvertValidatorsNotSortedAndUnique
case len(chains) == 0:
return ErrSovereignMustIncludeChains
case len(chains) > MaxSovereignL1Chains:
return ErrSovereignTooManyChains
case int(tx.ManagerChainIdx()) >= len(chains):
return ErrSovereignManagerIdxOutOfRange
case len(tx.ManagerAddress()) > MaxChainAddressLength:
return ErrAddressTooLong
}
if err := verifyBaseTx(tx.baseTx(), rt); err != nil {
return err
}
if err := tx.Owner().Verify(); err != nil {
return err
}
for _, vdr := range vdrs {
if err := vdr.Verify(); err != nil {
return err
}
}
for _, ch := range chains {
if err := ch.Verify(); err != nil {
return err
}
}
return nil
}
func (tx *CreateSovereignL1Tx) Visit(visitor Visitor) error {
return visitor.CreateSovereignL1Tx(tx)
}
-2
View File
@@ -27,8 +27,6 @@ const (
kindAddDelegator
kindAddPermissionlessValidator
kindAddPermissionlessDelegator
kindConvertNetworkToL1
kindCreateSovereignL1
kindRegisterL1Validator
kindSetL1ValidatorWeight
kindIncreaseL1ValidatorBalance
-4
View File
@@ -69,10 +69,6 @@ func parseUnsigned(buf []byte) (UnsignedTx, error) {
return &AddPermissionlessValidatorTx{spendingTx{msg: msg}}, nil
case kindAddPermissionlessDelegator:
return &AddPermissionlessDelegatorTx{spendingTx{msg: msg}}, nil
case kindConvertNetworkToL1:
return &ConvertNetworkToL1Tx{spendingTx{msg: msg}}, nil
case kindCreateSovereignL1:
return &CreateSovereignL1Tx{spendingTx{msg: msg}}, nil
case kindRegisterL1Validator:
return &RegisterL1ValidatorTx{spendingTx{msg: msg}}, nil
case kindSetL1ValidatorWeight:
+36 -27
View File
@@ -71,12 +71,12 @@ func TestRoundTrip_BaseTx_MultisigStakeable(t *testing.T) {
require.Equal([]byte(base.Memo), got.Memo())
}
func sampleConvertValidator() *ConvertNetworkToL1Validator {
func sampleNetworkValidator() *NetworkValidator {
nodeID := ids.GenerateTestNodeID()
v := &ConvertNetworkToL1Validator{
NodeID: nodeID[:],
Weight: 1000,
Balance: 500,
v := &NetworkValidator{
NodeID: nodeID[:],
Weight: 1000,
Balance: 500,
RemainingBalanceOwner: message.PChainOwner{Threshold: 1, Addresses: []ids.ShortID{ids.GenerateTestShortID()}},
DeactivationOwner: message.PChainOwner{Threshold: 2, Addresses: []ids.ShortID{ids.GenerateTestShortID(), ids.GenerateTestShortID()}},
}
@@ -89,39 +89,48 @@ func sampleConvertValidator() *ConvertNetworkToL1Validator {
return v
}
func TestRoundTrip_ConvertNetworkToL1(t *testing.T) {
// TestRoundTrip_CreateNetwork_SovereignL1 exercises the folded one-tx L1 birth:
// parent=Primary, own validator set, genesis chains, manager — all round-trip.
func TestRoundTrip_CreateNetwork_SovereignL1(t *testing.T) {
require := require.New(t)
base := spendBase()
vdr := sampleConvertValidator()
in, err := NewConvertNetworkToL1Tx(base, ids.GenerateTestID(), ids.GenerateTestID(), []byte("0xmanager"),
[]*ConvertNetworkToL1Validator{vdr}, &secp256k1fx.Input{SigIndices: []uint32{0}})
require.NoError(err)
got := roundTrip(t, in).(*ConvertNetworkToL1Tx)
require.Equal(in.Chain(), got.Chain())
require.Equal(in.ManagerChainID(), got.ManagerChainID())
require.Equal([]byte("0xmanager"), got.Address())
require.Equal([]*ConvertNetworkToL1Validator{vdr}, got.Validators(), "nested validator (NodeID+BLS PoP+2 owners) round-trips")
require.Equal(in.ChainAuth(), got.ChainAuth())
}
func TestRoundTrip_CreateSovereignL1(t *testing.T) {
require := require.New(t)
base := spendBase()
vdr := sampleConvertValidator()
vdr := sampleNetworkValidator()
var owner fx.Owner = &secp256k1fx.OutputOwners{Threshold: 1, Addrs: []ids.ShortID{ids.GenerateTestShortID()}}
chains := []*SovereignL1Chain{
chains := []*NetworkChain{
{BlockchainName: "evm chain", VMID: ids.GenerateTestID(), FxIDs: []ids.ID{ids.GenerateTestID()}, GenesisData: []byte(`{"g":1}`)},
}
in, err := NewCreateSovereignL1Tx(base, owner, []*ConvertNetworkToL1Validator{vdr}, chains, 0, []byte("mgr"))
in, err := NewCreateNetworkTx(base, ids.Empty /*Primary parent*/, owner, SecuritySovereign,
[]*NetworkValidator{vdr}, chains, 0, []byte("mgr"))
require.NoError(err)
got := roundTrip(t, in).(*CreateSovereignL1Tx)
got := roundTrip(t, in).(*CreateNetworkTx)
require.Equal(ids.Empty, got.Parent())
require.Equal(owner, got.Owner())
require.Equal([]*ConvertNetworkToL1Validator{vdr}, got.Validators())
require.Equal(chains, got.Chains(), "sovereign L1 chain manifest round-trips")
require.True(got.Sovereign())
require.Equal([]*NetworkValidator{vdr}, got.Validators(), "genesis validator set round-trips")
require.Equal(chains, got.Chains(), "genesis chain manifest round-trips")
require.EqualValues(0, got.ManagerChainIdx())
require.Equal([]byte("mgr"), got.ManagerAddress())
}
// TestRoundTrip_CreateNetwork_InheritedL2 exercises an L2 restaking its parent
// L1's security: parent=some L1, Inherited (no own validators), still holds a
// local manager and genesis chains.
func TestRoundTrip_CreateNetwork_InheritedL2(t *testing.T) {
require := require.New(t)
base := spendBase()
parentL1 := ids.GenerateTestID()
var owner fx.Owner = &secp256k1fx.OutputOwners{Threshold: 1, Addrs: []ids.ShortID{ids.GenerateTestShortID()}}
chains := []*NetworkChain{{BlockchainName: "l2 evm", VMID: ids.GenerateTestID()}}
in, err := NewCreateNetworkTx(base, parentL1, owner, SecurityInherited, nil, chains, 0, []byte("l2mgr"))
require.NoError(err)
got := roundTrip(t, in).(*CreateNetworkTx)
require.Equal(parentL1, got.Parent(), "L2 records its parent L1 (level = depth)")
require.False(got.Sovereign())
require.Empty(got.Validators(), "inherited security carries no own validators")
require.Equal(chains, got.Chains())
require.Equal([]byte("l2mgr"), got.ManagerAddress(), "inherited L2 still holds a local manager")
}
func TestRoundTrip_SignedWithCreds(t *testing.T) {
require := require.New(t)
in, err := NewBaseTx(spendBase())
-9
View File
@@ -23,15 +23,6 @@ type Visitor interface {
TransferChainOwnershipTx(*TransferChainOwnershipTx) error
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