mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
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:
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -27,8 +27,6 @@ const (
|
||||
kindAddDelegator
|
||||
kindAddPermissionlessValidator
|
||||
kindAddPermissionlessDelegator
|
||||
kindConvertNetworkToL1
|
||||
kindCreateSovereignL1
|
||||
kindRegisterL1Validator
|
||||
kindSetL1ValidatorWeight
|
||||
kindIncreaseL1ValidatorBalance
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user