mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
The module already called itself github.com/luxfi/node while containing no node in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and the Go column of the matrix measured a VM plugin. A repo that fetches the thing it claims to be has not replaced it, so the Go sources move in — 1515 files, the daemon and everything under it — and the build reaches outside this module for nothing. Rust and C++ sit beside it unchanged and are still measured against it by the differential. That is the point of one repo: the reference implementation and the two ports are the same checkout, so parity is a thing you can run rather than a thing you assert. Verified: builds, and the closure carries no lux-private — checked at build time rather than believed. The daemon starts twelve chains on a local network (P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
308 lines
11 KiB
Go
308 lines
11 KiB
Go
// 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/ids"
|
|
"github.com/luxfi/node/vms/components/verify"
|
|
"github.com/luxfi/node/vms/platformvm/fx"
|
|
"github.com/luxfi/node/vms/platformvm/security"
|
|
"github.com/luxfi/node/vms/platformvm/signer"
|
|
"github.com/luxfi/node/vms/platformvm/warp/message"
|
|
"github.com/luxfi/runtime"
|
|
"github.com/luxfi/util"
|
|
lux "github.com/luxfi/utxo"
|
|
"github.com/luxfi/utxo/secp256k1fx"
|
|
"github.com/luxfi/vm/types"
|
|
"github.com/luxfi/zap"
|
|
)
|
|
|
|
// CreateNetworkTx is the sole network constructor — the ∅→Network birth of the
|
|
// decomplected model. It creates a network at ANY level of the hierarchy in one
|
|
// tx:
|
|
//
|
|
// - 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; derivable, never stored.
|
|
// - Owner authorises future admin against the network record.
|
|
// - Security (security.Mode) is the two-axis model: RestakeParent and/or an
|
|
// own validator set (Admission + Manager). One definition, shared with the
|
|
// executor and state.
|
|
// - ManagerChainID (+ address) names the chain hosting a Contract-governed
|
|
// own set's staking contract; ids.Empty ⇒ P-Chain-governed (owner is the
|
|
// authority). Symmetric with ConvertNetworkTx's manager reference.
|
|
//
|
|
// Chains are NOT created here — CreateChainTx is the sole chain constructor
|
|
// (one and only one way). An L1 spawn is a BLOCK of CreateNetworkTx + N
|
|
// CreateChainTx (block-atomic), not a tx that materialises chains.
|
|
//
|
|
// 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 MaxChainAddressLength = 4096
|
|
|
|
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")
|
|
ErrOwnSetMustIncludeValidator = errors.New("sovereign (non-restaking) network must include at least one genesis validator")
|
|
ErrNoOwnSetButHasValidators = errors.New("network with no own set must not carry validators")
|
|
ErrContractManagerNeedsAddress = errors.New("contract-governed own set requires a manager address")
|
|
)
|
|
|
|
// 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},
|
|
)
|
|
}
|
|
|
|
// ---- 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
|
|
}
|
|
|
|
// ---- CreateNetworkTx (kindCreateNetwork) ----
|
|
const (
|
|
offCN_Parent = spendSize // 32B
|
|
offCN_OwnerThreshold = spendSize + 32 // u32
|
|
offCN_OwnerLocktime = spendSize + 36 // u64
|
|
offCN_OwnerAddrPtr = spendSize + 44 // 8B
|
|
offCN_RestakeParent = spendSize + 52 // u8 (security.Mode axis 1)
|
|
offCN_Admission = spendSize + 53 // u8 (security.Admission)
|
|
offCN_Manager = spendSize + 54 // u8 (security.Manager)
|
|
offCN_Threshold = spendSize + 55 // u64 (Open-admission min stake)
|
|
offCN_Validators = spendSize + 63 // 8B list
|
|
offCN_ValNodeIDPool = spendSize + 71 // 8B bytes
|
|
offCN_ValAddrPool = spendSize + 79 // 8B list
|
|
offCN_ManagerChainID = spendSize + 87 // 32B ids.ID (direct chain ref; ids.Empty ⇒ P-Chain-governed)
|
|
offCN_ManagerAddress = spendSize + 119 // 8B bytes
|
|
sizeCNTx = spendSize + 127
|
|
)
|
|
|
|
// setSecurity / readSecurity encode a security.Mode across four fixed object
|
|
// fields. Shared by CreateNetworkTx and ConvertNetworkTx — one wire encoding.
|
|
func setSecurity(ob zap.ObjectBuilder, offRestake, offAdmission, offManager, offThreshold int, m security.Mode) {
|
|
var restake uint8
|
|
if m.RestakeParent {
|
|
restake = 1
|
|
}
|
|
ob.SetUint8(offRestake, restake)
|
|
ob.SetUint8(offAdmission, uint8(m.Admission))
|
|
ob.SetUint8(offManager, uint8(m.Manager))
|
|
ob.SetUint64(offThreshold, m.Threshold)
|
|
}
|
|
|
|
func readSecurity(o zap.Object, offRestake, offAdmission, offManager, offThreshold int) security.Mode {
|
|
return security.Mode{
|
|
RestakeParent: o.Uint8(offRestake) != 0,
|
|
Admission: security.Admission(o.Uint8(offAdmission)),
|
|
Manager: security.Manager(o.Uint8(offManager)),
|
|
Threshold: o.Uint64(offThreshold),
|
|
}
|
|
}
|
|
|
|
type CreateNetworkTx struct {
|
|
spendingTx
|
|
}
|
|
|
|
func NewCreateNetworkTx(
|
|
base *lux.BaseTx,
|
|
parent ids.ID,
|
|
owner fx.Owner,
|
|
sec security.Mode,
|
|
validators []*NetworkValidator,
|
|
managerChainID ids.ID,
|
|
managerAddress []byte,
|
|
) (*CreateNetworkTx, error) {
|
|
b := zap.NewBuilder(zap.HeaderSize + 1024 + sizeCNTx + len(validators)*nvStride)
|
|
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 := 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)
|
|
}
|
|
|
|
ob := b.StartObject(sizeCNTx)
|
|
setEnvelope(ob, kindCreateNetwork, base, p)
|
|
setID(ob, offCN_Parent, parent)
|
|
setOwner(ob, offCN_OwnerThreshold, offCN_OwnerLocktime, offCN_OwnerAddrPtr, oThreshold, oLocktime, oAddrOff, oAddrCount)
|
|
setSecurity(ob, offCN_RestakeParent, offCN_Admission, offCN_Manager, offCN_Threshold, sec)
|
|
ob.SetList(offCN_Validators, vdrOff, vdrCount)
|
|
ob.SetBytes(offCN_ValNodeIDPool, nodeIDPool)
|
|
ob.SetList(offCN_ValAddrPool, valAddrOff, valAddrCount)
|
|
setID(ob, offCN_ManagerChainID, managerChainID)
|
|
ob.SetBytes(offCN_ManagerAddress, managerAddress)
|
|
ob.FinishAsRoot()
|
|
msg, _ := zap.Parse(b.Finish())
|
|
return &CreateNetworkTx{spendingTx{msg: msg}}, nil
|
|
}
|
|
|
|
func (tx *CreateNetworkTx) Parent() ids.ID { return readID(tx.root(), offCN_Parent) }
|
|
func (tx *CreateNetworkTx) Owner() fx.Owner {
|
|
return readOwner(tx.root(), offCN_OwnerThreshold, offCN_OwnerLocktime, offCN_OwnerAddrPtr)
|
|
}
|
|
func (tx *CreateNetworkTx) Security() security.Mode {
|
|
return readSecurity(tx.root(), offCN_RestakeParent, offCN_Admission, offCN_Manager, offCN_Threshold)
|
|
}
|
|
func (tx *CreateNetworkTx) Sovereign() bool { return tx.Security().Sovereign() }
|
|
func (tx *CreateNetworkTx) Validators() []*NetworkValidator {
|
|
return readNetworkValidators(tx.root(), offCN_Validators, offCN_ValNodeIDPool, offCN_ValAddrPool)
|
|
}
|
|
func (tx *CreateNetworkTx) ManagerChainID() ids.ID { return readID(tx.root(), offCN_ManagerChainID) }
|
|
func (tx *CreateNetworkTx) ManagerAddress() []byte {
|
|
if a := tx.root().Bytes(offCN_ManagerAddress); len(a) > 0 {
|
|
return append([]byte(nil), a...)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (tx *CreateNetworkTx) SyntacticVerify(rt *runtime.Runtime) error {
|
|
if tx == nil {
|
|
return ErrNilTx
|
|
}
|
|
vdrs := tx.Validators()
|
|
sec := tx.Security()
|
|
// enum ranges + cross-axis invariant (RestakeParent || own set) live on Mode.
|
|
if err := sec.Valid(); err != nil {
|
|
return err
|
|
}
|
|
switch {
|
|
case !sec.Sovereign() && len(vdrs) != 0:
|
|
// no own set ⇒ the tx must not carry validators.
|
|
return ErrNoOwnSetButHasValidators
|
|
case !sec.RestakeParent && len(vdrs) == 0:
|
|
// a network that does not restake its parent must ship a bootstrap
|
|
// validator to produce its first block.
|
|
return ErrOwnSetMustIncludeValidator
|
|
case sec.Sovereign() && sec.Manager == security.Contract && len(tx.ManagerAddress()) == 0:
|
|
return ErrContractManagerNeedsAddress
|
|
case !utils.IsSortedAndUnique(vdrs):
|
|
return ErrValidatorsNotSortedAndUnique
|
|
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
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (tx *CreateNetworkTx) Visit(visitor Visitor) error { return visitor.CreateNetworkTx(tx) }
|