Pure zap tx: 18 type files converted (parallel) + verifyBaseTx reconciled

Batches 1-3 (proposal + spending + validator-family) landed pure: struct is
the buffer, New*Tx builds, accessors read, SyntacticVerify via verifyBaseTx.
Remaining package-internal: 5 complex types (Convert/CreateSovereign/CreateAsset/
Operation/Slash) + staker interface methods + consumer flip.
This commit is contained in:
zeekay
2026-07-10 09:17:15 -07:00
parent 8d2fb750b6
commit 949210730d
18 changed files with 1258 additions and 877 deletions
+64 -51
View File
@@ -1,4 +1,4 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//
// File deprecation notice: this file defines AddChainValidatorTx, the
@@ -6,14 +6,10 @@
// (sovereign-L1), validators join a network — never a chain — via
// AddValidatorTx. Chains live on networks (created via CreateChainTx);
// the canonical model has validators validate networks, not chains. A
// sovereign L1 IS a primary network at its own networkID; the Lux
// primary networks live at 1/2/3/1337, and any downstream consumer
// running its own primary picks any other uint32. AddValidatorTx is
// the universal add-validator-to-a-network entry point for all of
// them. New code must use AddValidatorTx. The wire codec entries,
// Visitor method, and wallet IssueAddChainValidatorTx helper are kept
// for one release cycle so existing pre-LP-018 P-chain history
// continues to decode and replay.
// sovereign L1 IS a primary network at its own networkID. AddValidatorTx
// is the universal add-validator-to-a-network entry point. New code must
// use AddValidatorTx. This type is retained for one release cycle so
// existing pre-LP-018 P-chain history continues to decode and replay.
package txs
@@ -21,75 +17,93 @@ import (
"context"
"errors"
"github.com/luxfi/runtime"
"github.com/luxfi/constants"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/ids"
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/runtime"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
var (
_ StakerTx = (*AddChainValidatorTx)(nil)
_ ScheduledStaker = (*AddChainValidatorTx)(nil)
_ UnsignedTx = (*AddChainValidatorTx)(nil)
errAddPrimaryNetworkValidator = errors.New("can't add primary network validator with AddChainValidatorTx")
)
// AddChainValidatorTx is the legacy per-chain (pre-LP-018: per-L1)
// validator registration tx.
// AddChainValidatorTx is the legacy per-chain validator registration tx.
// The struct IS the wire: it holds the zap buffer and reads its fields by
// offset. No codec, no marshal.
//
// Deprecated: Use AddValidatorTx. Under LP-018 sovereign-L1, validators
// join a network (Lux primary 1/2/3/1337 or any sovereign L1's own
// primary at its EVM chainID). Chains live on networks; validators no
// longer register per-chain. This type is retained for one release
// cycle for wire/codec compat with pre-LP-018 binaries. The Visitor
// method, codec slots, and wallet IssueAddChainValidatorTx helper are
// preserved so older P-chain history still decodes and replays.
// Wire: zap header + object{ envelope@0..76, Validator@77, Chain@121,
// ChainAuth@153 }.
//
// Deprecated: Use AddValidatorTx. Retained for one release cycle for
// wire/codec compat with pre-LP-018 binaries.
type AddChainValidatorTx struct {
// Metadata, inputs and outputs
BaseTx `serialize:"true"`
// The validator
ChainValidator `serialize:"true" json:"validator"`
// Auth that will be allowing this validator into the network
ChainAuth verify.Verifiable `serialize:"true" json:"chainAuthorization"`
spendingTx
}
func (tx *AddChainValidatorTx) NodeID() ids.NodeID {
return tx.ChainValidator.NodeID
const (
offACVValidator = spendSize // 77: inline Validator (44B)
offACVChain = offACVValidator + validatorSize // 121: chain id (32B)
offACVChainAuth = offACVChain + 32 // 153: chain-auth list ptr (8B)
addChainValidatorSize = offACVChainAuth + 8 // 161
)
// NewAddChainValidatorTx builds the tx into a fresh zap buffer — the one
// place a field becomes bytes (construction, not serialization).
func NewAddChainValidatorTx(base *lux.BaseTx, validator Validator, chain ids.ID, chainAuth verify.Verifiable) (*AddChainValidatorTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 1024 + addChainValidatorSize)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
authOff, authCount, err := writeAuth(b, chainAuth)
if err != nil {
return nil, err
}
ob := b.StartObject(addChainValidatorSize)
setEnvelope(ob, kindAddChainValidator, base, p)
setValidator(ob, offACVValidator, validator)
setID(ob, offACVChain, chain)
ob.SetList(offACVChainAuth, authOff, authCount)
ob.FinishAsRoot()
msg, _ := zap.Parse(b.Finish())
return &AddChainValidatorTx{spendingTx{msg}}, nil
}
func (*AddChainValidatorTx) PublicKey() (*bls.PublicKey, bool, error) {
return nil, false, nil
// Validator is the inline validator descriptor (offset read).
func (tx *AddChainValidatorTx) Validator() Validator {
return readValidator(tx.root(), offACVValidator)
}
func (*AddChainValidatorTx) PendingPriority() Priority {
return ChainPermissionedValidatorPendingPriority
// Chain is the network id this validator registers under (offset read).
func (tx *AddChainValidatorTx) Chain() ids.ID { return readID(tx.root(), offACVChain) }
// ChainAuth proves the issuer may add this validator to the network.
func (tx *AddChainValidatorTx) ChainAuth() verify.Verifiable {
return readAuth(tx.root(), offACVChainAuth)
}
func (*AddChainValidatorTx) CurrentPriority() Priority {
return ChainPermissionedValidatorCurrentPriority
}
// SyntacticVerify returns nil iff [tx] is valid
// SyntacticVerify returns nil iff [tx] is valid.
func (tx *AddChainValidatorTx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
if tx == nil {
return ErrNilTx
case tx.SyntacticallyVerified: // already passed syntactic verification
return nil
case tx.Chain == constants.PrimaryNetworkID:
}
if tx.Chain() == constants.PrimaryNetworkID {
return errAddPrimaryNetworkValidator
}
if err := tx.BaseTx.SyntacticVerify(rt); err != nil {
if err := verifyBaseTx(tx.baseTx(), rt); err != nil {
return err
}
if err := verify.All(&tx.Validator, tx.ChainAuth); err != nil {
v := tx.Validator()
if err := verify.All(&v, tx.ChainAuth()); err != nil {
return err
}
// cache that this is valid
tx.SyntacticallyVerified = true
return nil
}
@@ -97,8 +111,7 @@ func (tx *AddChainValidatorTx) Visit(visitor Visitor) error {
return visitor.AddChainValidatorTx(tx)
}
// InitializeWithRuntime initializes the transaction with Runtime
// Initialize is a no-op; Runtime is passed explicitly to InitRuntime.
func (tx *AddChainValidatorTx) Initialize(ctx context.Context) error {
// Initialize any context-dependent fields here
return nil
}
+66 -67
View File
@@ -1,4 +1,4 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
@@ -8,95 +8,98 @@ import (
"errors"
"fmt"
"github.com/luxfi/runtime"
"github.com/luxfi/constants"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/ids"
safemath "github.com/luxfi/math"
lux "github.com/luxfi/utxo"
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/node/vms/platformvm/fx"
"github.com/luxfi/utxo/secp256k1fx"
"github.com/luxfi/runtime"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
var (
_ DelegatorTx = (*AddDelegatorTx)(nil)
_ ScheduledStaker = (*AddDelegatorTx)(nil)
_ UnsignedTx = (*AddDelegatorTx)(nil)
errDelegatorWeightMismatch = errors.New("delegator weight is not equal to total stake weight")
errStakeMustBeLUX = errors.New("stake must be LUX")
)
// AddDelegatorTx is an unsigned addDelegatorTx
// AddDelegatorTx is an unsigned addDelegatorTx. The struct IS the wire: it
// holds the zap buffer and reads its fields by offset. No codec, no marshal.
//
// Wire: zap header + object{ envelope@0..76, Validator@77, StakeOuts@121,
// DelegationRewardsOwner@137 }.
type AddDelegatorTx struct {
// Metadata, inputs and outputs
BaseTx `serialize:"true"`
// Describes the delegatee
Validator `serialize:"true" json:"validator"`
// Where to send staked tokens when done validating
StakeOuts []*lux.TransferableOutput `serialize:"true" json:"stake"`
// Where to send staking rewards when done validating
DelegationRewardsOwner fx.Owner `serialize:"true" json:"rewardsOwner"`
spendingTx
}
// InitRuntime sets the FxID fields in the inputs and outputs of this
// [UnsignedAddDelegatorTx]. Also sets the [rt] to the given [vm.rt] so that
// the addresses can be json marshalled into human readable format
func (tx *AddDelegatorTx) InitRuntime(rt *runtime.Runtime) {
tx.BaseTx.InitRuntime(rt)
for _, out := range tx.StakeOuts {
out.FxID = secp256k1fx.ID
out.InitRuntime(rt)
const (
offADValidator = spendSize // 77: inline Validator (44B)
offADStakeOuts = offADValidator + validatorSize // 121: stake-outs list ptr (8B)
offADStakeAddrs = offADStakeOuts + 8 // 129: stake-outs owner-addr array ptr (8B)
offADRewardsThreshold = offADStakeAddrs + 8 // 137: rewards owner threshold (u32)
offADRewardsLocktime = offADRewardsThreshold + 4 // 141: rewards owner locktime (u64)
offADRewardsAddrs = offADRewardsLocktime + 8 // 149: rewards owner addr array ptr (8B)
addDelegatorSize = offADRewardsAddrs + 8 // 157
)
// NewAddDelegatorTx builds the tx into a fresh zap buffer.
func NewAddDelegatorTx(base *lux.BaseTx, validator Validator, stakeOuts []*lux.TransferableOutput, delegationRewardsOwner fx.Owner) (*AddDelegatorTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 1024 + addDelegatorSize)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
// Owner doesn't have InitRuntime method
stakeListOff, stakeListCount, stakeAddrOff, stakeAddrCount, err := writeExtraOuts(b, stakeOuts)
if err != nil {
return nil, err
}
ownerThreshold, ownerLocktime, ownerAddrOff, ownerAddrCount, err := writeOwner(b, delegationRewardsOwner)
if err != nil {
return nil, err
}
ob := b.StartObject(addDelegatorSize)
setEnvelope(ob, kindAddDelegator, base, p)
setValidator(ob, offADValidator, validator)
ob.SetList(offADStakeOuts, stakeListOff, stakeListCount)
ob.SetList(offADStakeAddrs, stakeAddrOff, stakeAddrCount)
setOwner(ob, offADRewardsThreshold, offADRewardsLocktime, offADRewardsAddrs, ownerThreshold, ownerLocktime, ownerAddrOff, ownerAddrCount)
ob.FinishAsRoot()
msg, _ := zap.Parse(b.Finish())
return &AddDelegatorTx{spendingTx{msg}}, nil
}
func (*AddDelegatorTx) ChainID() ids.ID {
return constants.PrimaryNetworkID
// Validator describes the delegatee (offset read).
func (tx *AddDelegatorTx) Validator() Validator { return readValidator(tx.root(), offADValidator) }
// StakeOuts is where staked tokens go when done validating (offset read).
func (tx *AddDelegatorTx) StakeOuts() []*lux.TransferableOutput {
return readExtraOuts(tx.root(), offADStakeOuts, offADStakeAddrs)
}
func (tx *AddDelegatorTx) NodeID() ids.NodeID {
return tx.Validator.NodeID
// DelegationRewardsOwner is where staking rewards go when done validating.
func (tx *AddDelegatorTx) DelegationRewardsOwner() fx.Owner {
return readOwner(tx.root(), offADRewardsThreshold, offADRewardsLocktime, offADRewardsAddrs)
}
func (*AddDelegatorTx) PublicKey() (*bls.PublicKey, bool, error) {
return nil, false, nil
}
func (*AddDelegatorTx) PendingPriority() Priority {
return PrimaryNetworkDelegatorLegacyPendingPriority
}
func (*AddDelegatorTx) CurrentPriority() Priority {
return PrimaryNetworkDelegatorCurrentPriority
}
func (tx *AddDelegatorTx) Stake() []*lux.TransferableOutput {
return tx.StakeOuts
}
func (tx *AddDelegatorTx) RewardsOwner() fx.Owner {
return tx.DelegationRewardsOwner
}
// SyntacticVerify returns nil iff [tx] is valid
// SyntacticVerify returns nil iff [tx] is valid.
func (tx *AddDelegatorTx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
if tx == nil {
return ErrNilTx
case tx.SyntacticallyVerified: // already passed syntactic verification
return nil
}
if err := tx.BaseTx.SyntacticVerify(rt); err != nil {
if err := verifyBaseTx(tx.baseTx(), rt); err != nil {
return err
}
if err := verify.All(&tx.Validator, tx.DelegationRewardsOwner); err != nil {
v := tx.Validator()
if err := verify.All(&v, tx.DelegationRewardsOwner()); err != nil {
return fmt.Errorf("failed to verify validator or rewards owner: %w", err)
}
stakeOuts := tx.StakeOuts()
totalStakeWeight := uint64(0)
for _, out := range tx.StakeOuts {
for _, out := range stakeOuts {
if err := out.Verify(); err != nil {
return fmt.Errorf("output verification failed: %w", err)
}
@@ -114,18 +117,15 @@ func (tx *AddDelegatorTx) SyntacticVerify(rt *runtime.Runtime) error {
}
switch {
case !lux.IsSortedTransferableOutputs(tx.StakeOuts):
case !lux.IsSortedTransferableOutputs(stakeOuts):
return errOutputsNotSorted
case totalStakeWeight != tx.Wght:
case totalStakeWeight != v.Wght:
return fmt.Errorf("%w, delegator weight %d total stake weight %d",
errDelegatorWeightMismatch,
tx.Wght,
v.Wght,
totalStakeWeight,
)
}
// cache that this is valid
tx.SyntacticallyVerified = true
return nil
}
@@ -133,8 +133,7 @@ func (tx *AddDelegatorTx) Visit(visitor Visitor) error {
return visitor.AddDelegatorTx(tx)
}
// InitializeWithRuntime initializes the transaction with Runtime
// Initialize is a no-op; Runtime is passed explicitly to InitRuntime.
func (tx *AddDelegatorTx) Initialize(ctx context.Context) error {
// Initialize any context-dependent fields here
return nil
}
@@ -1,4 +1,4 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
@@ -7,110 +7,113 @@ import (
"context"
"fmt"
"github.com/luxfi/runtime"
"github.com/luxfi/constants"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/ids"
safemath "github.com/luxfi/math"
lux "github.com/luxfi/utxo"
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/node/vms/platformvm/fx"
"github.com/luxfi/utxo/secp256k1fx"
"github.com/luxfi/runtime"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
var (
_ DelegatorTx = (*AddPermissionlessDelegatorTx)(nil)
_ ScheduledStaker = (*AddPermissionlessDelegatorTx)(nil)
)
var _ UnsignedTx = (*AddPermissionlessDelegatorTx)(nil)
// AddPermissionlessDelegatorTx is an unsigned addPermissionlessDelegatorTx
// AddPermissionlessDelegatorTx is an unsigned addPermissionlessDelegatorTx.
// The struct IS the wire: it holds the zap buffer and reads its fields by
// offset. No codec, no marshal.
//
// Wire: zap header + object{ envelope@0..76, Validator@77, Chain@121,
// StakeOuts@153, DelegationRewardsOwner@169 }.
type AddPermissionlessDelegatorTx struct {
// Metadata, inputs and outputs
BaseTx `serialize:"true"`
// Describes the validator
Validator `serialize:"true" json:"validator"`
// ID of the chain this validator is validating
Chain ids.ID `serialize:"true" json:"chainID"`
// Where to send staked tokens when done validating
StakeOuts []*lux.TransferableOutput `serialize:"true" json:"stake"`
// Where to send staking rewards when done validating
DelegationRewardsOwner fx.Owner `serialize:"true" json:"rewardsOwner"`
spendingTx
}
// InitRuntime sets the FxID fields in the inputs and outputs of this
// [AddPermissionlessDelegatorTx]. Also sets the [rt] to the given [vm.rt] so
// that the addresses can be json marshalled into human readable format
func (tx *AddPermissionlessDelegatorTx) InitRuntime(rt *runtime.Runtime) {
tx.BaseTx.InitRuntime(rt)
for _, out := range tx.StakeOuts {
out.FxID = secp256k1fx.ID
out.InitRuntime(rt)
const (
offAPDValidator = spendSize // 77: inline Validator (44B)
offAPDChain = offAPDValidator + validatorSize // 121: chain id (32B)
offAPDStakeOuts = offAPDChain + 32 // 153: stake-outs list ptr (8B)
offAPDStakeAddrs = offAPDStakeOuts + 8 // 161: stake-outs owner-addr array ptr (8B)
offAPDRewardsThreshold = offAPDStakeAddrs + 8 // 169: rewards owner threshold (u32)
offAPDRewardsLocktime = offAPDRewardsThreshold + 4 // 173: rewards owner locktime (u64)
offAPDRewardsAddrs = offAPDRewardsLocktime + 8 // 181: rewards owner addr array ptr (8B)
addPermissionlessDelegatorSize = offAPDRewardsAddrs + 8 // 189
)
// NewAddPermissionlessDelegatorTx builds the tx into a fresh zap buffer.
func NewAddPermissionlessDelegatorTx(base *lux.BaseTx, validator Validator, chain ids.ID, stakeOuts []*lux.TransferableOutput, delegationRewardsOwner fx.Owner) (*AddPermissionlessDelegatorTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 1024 + addPermissionlessDelegatorSize)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
// Owner doesn't have InitRuntime method
}
func (tx *AddPermissionlessDelegatorTx) ChainID() ids.ID {
return tx.Chain
}
func (tx *AddPermissionlessDelegatorTx) NodeID() ids.NodeID {
return tx.Validator.NodeID
}
func (*AddPermissionlessDelegatorTx) PublicKey() (*bls.PublicKey, bool, error) {
return nil, false, nil
}
func (tx *AddPermissionlessDelegatorTx) PendingPriority() Priority {
if tx.Chain == constants.PrimaryNetworkID {
return PrimaryNetworkDelegatorPermissionlessPendingPriority
stakeListOff, stakeListCount, stakeAddrOff, stakeAddrCount, err := writeExtraOuts(b, stakeOuts)
if err != nil {
return nil, err
}
return ChainPermissionlessDelegatorPendingPriority
}
func (tx *AddPermissionlessDelegatorTx) CurrentPriority() Priority {
if tx.Chain == constants.PrimaryNetworkID {
return PrimaryNetworkDelegatorCurrentPriority
ownerThreshold, ownerLocktime, ownerAddrOff, ownerAddrCount, err := writeOwner(b, delegationRewardsOwner)
if err != nil {
return nil, err
}
return ChainPermissionlessDelegatorCurrentPriority
ob := b.StartObject(addPermissionlessDelegatorSize)
setEnvelope(ob, kindAddPermissionlessDelegator, base, p)
setValidator(ob, offAPDValidator, validator)
setID(ob, offAPDChain, chain)
ob.SetList(offAPDStakeOuts, stakeListOff, stakeListCount)
ob.SetList(offAPDStakeAddrs, stakeAddrOff, stakeAddrCount)
setOwner(ob, offAPDRewardsThreshold, offAPDRewardsLocktime, offAPDRewardsAddrs, ownerThreshold, ownerLocktime, ownerAddrOff, ownerAddrCount)
ob.FinishAsRoot()
msg, _ := zap.Parse(b.Finish())
return &AddPermissionlessDelegatorTx{spendingTx{msg}}, nil
}
func (tx *AddPermissionlessDelegatorTx) Stake() []*lux.TransferableOutput {
return tx.StakeOuts
// Validator describes the validator being delegated to (offset read).
func (tx *AddPermissionlessDelegatorTx) Validator() Validator {
return readValidator(tx.root(), offAPDValidator)
}
func (tx *AddPermissionlessDelegatorTx) RewardsOwner() fx.Owner {
return tx.DelegationRewardsOwner
// Chain is the id of the chain this validator is validating (offset read).
func (tx *AddPermissionlessDelegatorTx) Chain() ids.ID { return readID(tx.root(), offAPDChain) }
// StakeOuts is where staked tokens go when done validating (offset read).
func (tx *AddPermissionlessDelegatorTx) StakeOuts() []*lux.TransferableOutput {
return readExtraOuts(tx.root(), offAPDStakeOuts, offAPDStakeAddrs)
}
// SyntacticVerify returns nil iff [tx] is valid
// DelegationRewardsOwner is where staking rewards go when done validating.
func (tx *AddPermissionlessDelegatorTx) DelegationRewardsOwner() fx.Owner {
return readOwner(tx.root(), offAPDRewardsThreshold, offAPDRewardsLocktime, offAPDRewardsAddrs)
}
// SyntacticVerify returns nil iff [tx] is valid.
func (tx *AddPermissionlessDelegatorTx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
if tx == nil {
return ErrNilTx
case tx.SyntacticallyVerified: // already passed syntactic verification
return nil
case len(tx.StakeOuts) == 0: // Ensure there is provided stake
}
stakeOuts := tx.StakeOuts()
if len(stakeOuts) == 0 { // Ensure there is provided stake
return errNoStake
}
if err := tx.BaseTx.SyntacticVerify(rt); err != nil {
if err := verifyBaseTx(tx.baseTx(), rt); err != nil {
return fmt.Errorf("failed to verify BaseTx: %w", err)
}
if err := verify.All(&tx.Validator, tx.DelegationRewardsOwner); err != nil {
v := tx.Validator()
if err := verify.All(&v, tx.DelegationRewardsOwner()); err != nil {
return fmt.Errorf("failed to verify validator or rewards owner: %w", err)
}
for _, out := range tx.StakeOuts {
for _, out := range stakeOuts {
if err := out.Verify(); err != nil {
return fmt.Errorf("failed to verify output: %w", err)
}
}
firstStakeOutput := tx.StakeOuts[0]
firstStakeOutput := stakeOuts[0]
stakedAssetID := firstStakeOutput.AssetID()
totalStakeWeight := firstStakeOutput.Output().Amount()
for _, out := range tx.StakeOuts[1:] {
for _, out := range stakeOuts[1:] {
newWeight, err := safemath.Add(totalStakeWeight, out.Output().Amount())
if err != nil {
return err
@@ -124,18 +127,15 @@ func (tx *AddPermissionlessDelegatorTx) SyntacticVerify(rt *runtime.Runtime) err
}
switch {
case !lux.IsSortedTransferableOutputs(tx.StakeOuts):
case !lux.IsSortedTransferableOutputs(stakeOuts):
return errOutputsNotSorted
case totalStakeWeight != tx.Wght:
case totalStakeWeight != v.Wght:
return fmt.Errorf("%w, delegator weight %d total stake weight %d",
errDelegatorWeightMismatch,
tx.Wght,
v.Wght,
totalStakeWeight,
)
}
// cache that this is valid
tx.SyntacticallyVerified = true
return nil
}
@@ -143,8 +143,7 @@ func (tx *AddPermissionlessDelegatorTx) Visit(visitor Visitor) error {
return visitor.AddPermissionlessDelegatorTx(tx)
}
// InitializeWithRuntime initializes the transaction with Runtime
// Initialize is a no-op; Runtime is passed explicitly to InitRuntime.
func (tx *AddPermissionlessDelegatorTx) Initialize(ctx context.Context) error {
// Initialize any context-dependent fields here
return nil
}
@@ -1,4 +1,4 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
@@ -8,22 +8,20 @@ import (
"errors"
"fmt"
"github.com/luxfi/runtime"
"github.com/luxfi/constants"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/ids"
safemath "github.com/luxfi/math"
lux "github.com/luxfi/utxo"
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/node/vms/platformvm/fx"
"github.com/luxfi/node/vms/platformvm/reward"
"github.com/luxfi/node/vms/platformvm/signer"
"github.com/luxfi/utxo/secp256k1fx"
"github.com/luxfi/runtime"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
var (
_ ValidatorTx = (*AddPermissionlessValidatorTx)(nil)
_ ScheduledStaker = (*AddPermissionlessDelegatorTx)(nil)
_ UnsignedTx = (*AddPermissionlessValidatorTx)(nil)
errEmptyNodeID = errors.New("validator nodeID cannot be empty")
errNoStake = errors.New("no stake")
@@ -32,117 +30,131 @@ var (
errValidatorWeightMismatch = errors.New("validator weight mismatch")
)
// AddPermissionlessValidatorTx is an unsigned addPermissionlessValidatorTx
// AddPermissionlessValidatorTx is an unsigned addPermissionlessValidatorTx.
// The struct IS the wire: it holds the zap buffer and reads its fields by
// offset. No codec, no marshal.
//
// Wire: zap header + object{ envelope@0..76, Validator@77, Chain@121,
// Signer@153, StakeOuts@298, ValidatorRewardsOwner@314,
// DelegatorRewardsOwner@334, DelegationShares@354 }.
type AddPermissionlessValidatorTx struct {
// Metadata, inputs and outputs
BaseTx `serialize:"true"`
// Describes the validator
Validator `serialize:"true" json:"validator"`
// ID of the chain this validator is validating
Chain ids.ID `serialize:"true" json:"chainID"`
// If the [Chain] is the primary network, [Signer] is the BLS key for this
// validator. If the [Chain] is not the primary network, this value is the
// empty signer
// Note: We do not enforce that the BLS key is unique across all validators.
// This means that validators can share a key if they so choose.
// However, a NodeID does uniquely map to a BLS key
Signer signer.Signer `serialize:"true" json:"signer"`
// Where to send staked tokens when done validating
StakeOuts []*lux.TransferableOutput `serialize:"true" json:"stake"`
// Where to send validation rewards when done validating
ValidatorRewardsOwner fx.Owner `serialize:"true" json:"validationRewardsOwner"`
// Where to send delegation rewards when done validating
DelegatorRewardsOwner fx.Owner `serialize:"true" json:"delegationRewardsOwner"`
// Fee this validator charges delegators as a percentage, times 10,000
// For example, if this validator has DelegationShares=300,000 then they
// take 30% of rewards from delegators
DelegationShares uint32 `serialize:"true" json:"shares"`
spendingTx
}
// InitRuntime sets the FxID fields in the inputs and outputs of this
// [AddPermissionlessValidatorTx]. Also sets the [rt] to the given [vm.rt] so
// that the addresses can be json marshalled into human readable format
func (tx *AddPermissionlessValidatorTx) InitRuntime(rt *runtime.Runtime) {
tx.BaseTx.InitRuntime(rt)
for _, out := range tx.StakeOuts {
out.FxID = secp256k1fx.ID
out.InitRuntime(rt)
const (
offAPVValidator = spendSize // 77: inline Validator (44B)
offAPVChain = offAPVValidator + validatorSize // 121: chain id (32B)
offAPVSigner = offAPVChain + 32 // 153: inline Signer (145B)
offAPVStakeOuts = offAPVSigner + signerSize // 298: stake-outs list ptr (8B)
offAPVStakeAddrs = offAPVStakeOuts + 8 // 306: stake-outs owner-addr array ptr (8B)
offAPVValRewardsThreshold = offAPVStakeAddrs + 8 // 314: validator rewards owner threshold (u32)
offAPVValRewardsLocktime = offAPVValRewardsThreshold + 4 // 318: validator rewards owner locktime (u64)
offAPVValRewardsAddrs = offAPVValRewardsLocktime + 8 // 326: validator rewards owner addr array ptr (8B)
offAPVDelRewardsThreshold = offAPVValRewardsAddrs + 8 // 334: delegator rewards owner threshold (u32)
offAPVDelRewardsLocktime = offAPVDelRewardsThreshold + 4 // 338: delegator rewards owner locktime (u64)
offAPVDelRewardsAddrs = offAPVDelRewardsLocktime + 8 // 346: delegator rewards owner addr array ptr (8B)
offAPVDelegationShares = offAPVDelRewardsAddrs + 8 // 354: delegation shares (u32)
addPermissionlessValidatorSize = offAPVDelegationShares + 4 // 358
)
// NewAddPermissionlessValidatorTx builds the tx into a fresh zap buffer.
func NewAddPermissionlessValidatorTx(base *lux.BaseTx, validator Validator, chain ids.ID, sig signer.Signer, stakeOuts []*lux.TransferableOutput, validatorRewardsOwner fx.Owner, delegatorRewardsOwner fx.Owner, delegationShares uint32) (*AddPermissionlessValidatorTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 1024 + addPermissionlessValidatorSize)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
// Owner doesn't have InitRuntime method
// tx.ValidatorRewardsOwner.InitRuntime(ctx)
// tx.DelegatorRewardsOwner.InitRuntime(ctx)
}
func (tx *AddPermissionlessValidatorTx) ChainID() ids.ID {
return tx.Chain
}
func (tx *AddPermissionlessValidatorTx) NodeID() ids.NodeID {
return tx.Validator.NodeID
}
func (tx *AddPermissionlessValidatorTx) PublicKey() (*bls.PublicKey, bool, error) {
if err := tx.Signer.Verify(); err != nil {
return nil, false, err
stakeListOff, stakeListCount, stakeAddrOff, stakeAddrCount, err := writeExtraOuts(b, stakeOuts)
if err != nil {
return nil, err
}
key := tx.Signer.Key()
return key, key != nil, nil
}
func (tx *AddPermissionlessValidatorTx) PendingPriority() Priority {
if tx.Chain == constants.PrimaryNetworkID {
return PrimaryNetworkValidatorPendingPriority
valThreshold, valLocktime, valAddrOff, valAddrCount, err := writeOwner(b, validatorRewardsOwner)
if err != nil {
return nil, err
}
return ChainPermissionlessValidatorPendingPriority
}
func (tx *AddPermissionlessValidatorTx) CurrentPriority() Priority {
if tx.Chain == constants.PrimaryNetworkID {
return PrimaryNetworkValidatorCurrentPriority
delThreshold, delLocktime, delAddrOff, delAddrCount, err := writeOwner(b, delegatorRewardsOwner)
if err != nil {
return nil, err
}
return ChainPermissionlessValidatorCurrentPriority
ob := b.StartObject(addPermissionlessValidatorSize)
setEnvelope(ob, kindAddPermissionlessValidator, base, p)
setValidator(ob, offAPVValidator, validator)
setID(ob, offAPVChain, chain)
if err := setSigner(ob, offAPVSigner, sig); err != nil {
return nil, err
}
ob.SetList(offAPVStakeOuts, stakeListOff, stakeListCount)
ob.SetList(offAPVStakeAddrs, stakeAddrOff, stakeAddrCount)
setOwner(ob, offAPVValRewardsThreshold, offAPVValRewardsLocktime, offAPVValRewardsAddrs, valThreshold, valLocktime, valAddrOff, valAddrCount)
setOwner(ob, offAPVDelRewardsThreshold, offAPVDelRewardsLocktime, offAPVDelRewardsAddrs, delThreshold, delLocktime, delAddrOff, delAddrCount)
ob.SetUint32(offAPVDelegationShares, delegationShares)
ob.FinishAsRoot()
msg, _ := zap.Parse(b.Finish())
return &AddPermissionlessValidatorTx{spendingTx{msg}}, nil
}
func (tx *AddPermissionlessValidatorTx) Stake() []*lux.TransferableOutput {
return tx.StakeOuts
// Validator describes the validator (offset read).
func (tx *AddPermissionlessValidatorTx) Validator() Validator {
return readValidator(tx.root(), offAPVValidator)
}
func (tx *AddPermissionlessValidatorTx) ValidationRewardsOwner() fx.Owner {
return tx.ValidatorRewardsOwner
// Chain is the id of the chain this validator is validating (offset read).
func (tx *AddPermissionlessValidatorTx) Chain() ids.ID { return readID(tx.root(), offAPVChain) }
// Signer is the BLS key for this validator (empty signer off the primary
// network) (offset read).
func (tx *AddPermissionlessValidatorTx) Signer() signer.Signer {
return readSigner(tx.root(), offAPVSigner)
}
func (tx *AddPermissionlessValidatorTx) DelegationRewardsOwner() fx.Owner {
return tx.DelegatorRewardsOwner
// StakeOuts is where staked tokens go when done validating (offset read).
func (tx *AddPermissionlessValidatorTx) StakeOuts() []*lux.TransferableOutput {
return readExtraOuts(tx.root(), offAPVStakeOuts, offAPVStakeAddrs)
}
func (tx *AddPermissionlessValidatorTx) Shares() uint32 {
return tx.DelegationShares
// ValidatorRewardsOwner is where validation rewards go when done validating.
func (tx *AddPermissionlessValidatorTx) ValidatorRewardsOwner() fx.Owner {
return readOwner(tx.root(), offAPVValRewardsThreshold, offAPVValRewardsLocktime, offAPVValRewardsAddrs)
}
// SyntacticVerify returns nil iff [tx] is valid
// DelegatorRewardsOwner is where delegation rewards go when done validating.
func (tx *AddPermissionlessValidatorTx) DelegatorRewardsOwner() fx.Owner {
return readOwner(tx.root(), offAPVDelRewardsThreshold, offAPVDelRewardsLocktime, offAPVDelRewardsAddrs)
}
// DelegationShares is the fee (times 10,000) charged to delegators.
func (tx *AddPermissionlessValidatorTx) DelegationShares() uint32 {
return tx.root().Uint32(offAPVDelegationShares)
}
// SyntacticVerify returns nil iff [tx] is valid.
func (tx *AddPermissionlessValidatorTx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
if tx == nil {
return ErrNilTx
case tx.SyntacticallyVerified: // already passed syntactic verification
return nil
case tx.Validator.NodeID == ids.EmptyNodeID:
}
v := tx.Validator()
stakeOuts := tx.StakeOuts()
switch {
case v.NodeID == ids.EmptyNodeID:
return errEmptyNodeID
case len(tx.StakeOuts) == 0: // Ensure there is provided stake
case len(stakeOuts) == 0: // Ensure there is provided stake
return errNoStake
case tx.DelegationShares > reward.PercentDenominator:
case tx.DelegationShares() > reward.PercentDenominator:
return errTooManyShares
}
if err := tx.BaseTx.SyntacticVerify(rt); err != nil {
if err := verifyBaseTx(tx.baseTx(), rt); err != nil {
return fmt.Errorf("failed to verify BaseTx: %w", err)
}
if err := verify.All(&tx.Validator, tx.Signer, tx.ValidatorRewardsOwner, tx.DelegatorRewardsOwner); err != nil {
sig := tx.Signer()
if err := verify.All(&v, sig, tx.ValidatorRewardsOwner(), tx.DelegatorRewardsOwner()); err != nil {
return fmt.Errorf("failed to verify validator, signer, or rewards owners: %w", err)
}
hasKey := tx.Signer.Key() != nil
isPrimaryNetwork := tx.Chain == constants.PrimaryNetworkID
hasKey := sig.Key() != nil
isPrimaryNetwork := tx.Chain() == constants.PrimaryNetworkID
if hasKey != isPrimaryNetwork {
return fmt.Errorf(
"%w: hasKey=%v != isPrimaryNetwork=%v",
@@ -152,16 +164,16 @@ func (tx *AddPermissionlessValidatorTx) SyntacticVerify(rt *runtime.Runtime) err
)
}
for _, out := range tx.StakeOuts {
for _, out := range stakeOuts {
if err := out.Verify(); err != nil {
return fmt.Errorf("failed to verify output: %w", err)
}
}
firstStakeOutput := tx.StakeOuts[0]
firstStakeOutput := stakeOuts[0]
stakedAssetID := firstStakeOutput.AssetID()
totalStakeWeight := firstStakeOutput.Output().Amount()
for _, out := range tx.StakeOuts[1:] {
for _, out := range stakeOuts[1:] {
newWeight, err := safemath.Add(totalStakeWeight, out.Output().Amount())
if err != nil {
return err
@@ -175,14 +187,11 @@ func (tx *AddPermissionlessValidatorTx) SyntacticVerify(rt *runtime.Runtime) err
}
switch {
case !lux.IsSortedTransferableOutputs(tx.StakeOuts):
case !lux.IsSortedTransferableOutputs(stakeOuts):
return errOutputsNotSorted
case totalStakeWeight != tx.Wght:
return fmt.Errorf("%w: weight %d != stake %d", errValidatorWeightMismatch, tx.Wght, totalStakeWeight)
case totalStakeWeight != v.Wght:
return fmt.Errorf("%w: weight %d != stake %d", errValidatorWeightMismatch, v.Wght, totalStakeWeight)
}
// cache that this is valid
tx.SyntacticallyVerified = true
return nil
}
@@ -190,8 +199,7 @@ func (tx *AddPermissionlessValidatorTx) Visit(visitor Visitor) error {
return visitor.AddPermissionlessValidatorTx(tx)
}
// InitializeWithRuntime initializes the transaction with Runtime
// Initialize is a no-op; Runtime is passed explicitly to InitRuntime.
func (tx *AddPermissionlessValidatorTx) Initialize(ctx context.Context) error {
// Initialize any context-dependent fields here
return nil
}
+72 -79
View File
@@ -1,4 +1,4 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
@@ -7,109 +7,106 @@ import (
"context"
"fmt"
"github.com/luxfi/runtime"
"github.com/luxfi/constants"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/ids"
safemath "github.com/luxfi/math"
lux "github.com/luxfi/utxo"
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/node/vms/platformvm/fx"
"github.com/luxfi/node/vms/platformvm/reward"
"github.com/luxfi/utxo/secp256k1fx"
"github.com/luxfi/runtime"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
var (
_ ValidatorTx = (*AddValidatorTx)(nil)
_ ScheduledStaker = (*AddValidatorTx)(nil)
_ UnsignedTx = (*AddValidatorTx)(nil)
errTooManyShares = fmt.Errorf("a staker can only require at most %d shares from delegators", reward.PercentDenominator)
)
// AddValidatorTx is an unsigned addValidatorTx
// AddValidatorTx is an unsigned addValidatorTx. The struct IS the wire: it
// holds the zap buffer and reads its fields by offset. No codec, no marshal.
//
// Wire: zap header + object{ envelope@0..76, Validator@77, StakeOuts@121,
// RewardsOwner@137, DelegationShares@157 }.
type AddValidatorTx struct {
// Metadata, inputs and outputs
BaseTx `serialize:"true"`
// Describes the delegatee
Validator `serialize:"true" json:"validator"`
// Where to send staked tokens when done validating
StakeOuts []*lux.TransferableOutput `serialize:"true" json:"stake"`
// Where to send staking rewards when done validating
RewardsOwner fx.Owner `serialize:"true" json:"rewardsOwner"`
// Fee this validator charges delegators as a percentage, times 10,000
// For example, if this validator has DelegationShares=300,000 then they
// take 30% of rewards from delegators
DelegationShares uint32 `serialize:"true" json:"shares"`
spendingTx
}
// InitRuntime sets the FxID fields in the inputs and outputs of this
// [AddValidatorTx]. Also sets the [rt] to the given [vm.rt] so that
// the addresses can be json marshalled into human readable format
func (tx *AddValidatorTx) InitRuntime(rt *runtime.Runtime) {
tx.BaseTx.InitRuntime(rt)
for _, out := range tx.StakeOuts {
out.FxID = secp256k1fx.ID
out.InitRuntime(rt)
const (
offAVValidator = spendSize // 77: inline Validator (44B)
offAVStakeOuts = offAVValidator + validatorSize // 121: stake-outs list ptr (8B)
offAVStakeAddrs = offAVStakeOuts + 8 // 129: stake-outs owner-addr array ptr (8B)
offAVRewardsThreshold = offAVStakeAddrs + 8 // 137: rewards owner threshold (u32)
offAVRewardsLocktime = offAVRewardsThreshold + 4 // 141: rewards owner locktime (u64)
offAVRewardsAddrs = offAVRewardsLocktime + 8 // 149: rewards owner addr array ptr (8B)
offAVDelegationShares = offAVRewardsAddrs + 8 // 157: delegation shares (u32)
addValidatorSize = offAVDelegationShares + 4 // 161
)
// NewAddValidatorTx builds the tx into a fresh zap buffer.
func NewAddValidatorTx(base *lux.BaseTx, validator Validator, stakeOuts []*lux.TransferableOutput, rewardsOwner fx.Owner, delegationShares uint32) (*AddValidatorTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 1024 + addValidatorSize)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
// Owner doesn't have InitRuntime method
stakeListOff, stakeListCount, stakeAddrOff, stakeAddrCount, err := writeExtraOuts(b, stakeOuts)
if err != nil {
return nil, err
}
ownerThreshold, ownerLocktime, ownerAddrOff, ownerAddrCount, err := writeOwner(b, rewardsOwner)
if err != nil {
return nil, err
}
ob := b.StartObject(addValidatorSize)
setEnvelope(ob, kindAddValidator, base, p)
setValidator(ob, offAVValidator, validator)
ob.SetList(offAVStakeOuts, stakeListOff, stakeListCount)
ob.SetList(offAVStakeAddrs, stakeAddrOff, stakeAddrCount)
setOwner(ob, offAVRewardsThreshold, offAVRewardsLocktime, offAVRewardsAddrs, ownerThreshold, ownerLocktime, ownerAddrOff, ownerAddrCount)
ob.SetUint32(offAVDelegationShares, delegationShares)
ob.FinishAsRoot()
msg, _ := zap.Parse(b.Finish())
return &AddValidatorTx{spendingTx{msg}}, nil
}
func (*AddValidatorTx) ChainID() ids.ID {
return constants.PrimaryNetworkID
// Validator describes the delegatee (offset read).
func (tx *AddValidatorTx) Validator() Validator { return readValidator(tx.root(), offAVValidator) }
// StakeOuts is where staked tokens go when done validating (offset read).
func (tx *AddValidatorTx) StakeOuts() []*lux.TransferableOutput {
return readExtraOuts(tx.root(), offAVStakeOuts, offAVStakeAddrs)
}
func (tx *AddValidatorTx) NodeID() ids.NodeID {
return tx.Validator.NodeID
// RewardsOwner is where staking rewards go when done validating.
func (tx *AddValidatorTx) RewardsOwner() fx.Owner {
return readOwner(tx.root(), offAVRewardsThreshold, offAVRewardsLocktime, offAVRewardsAddrs)
}
func (*AddValidatorTx) PublicKey() (*bls.PublicKey, bool, error) {
return nil, false, nil
}
// DelegationShares is the fee (times 10,000) charged to delegators.
func (tx *AddValidatorTx) DelegationShares() uint32 { return tx.root().Uint32(offAVDelegationShares) }
func (*AddValidatorTx) PendingPriority() Priority {
return PrimaryNetworkValidatorPendingPriority
}
func (*AddValidatorTx) CurrentPriority() Priority {
return PrimaryNetworkValidatorCurrentPriority
}
func (tx *AddValidatorTx) Stake() []*lux.TransferableOutput {
return tx.StakeOuts
}
func (tx *AddValidatorTx) ValidationRewardsOwner() fx.Owner {
return tx.RewardsOwner
}
func (tx *AddValidatorTx) DelegationRewardsOwner() fx.Owner {
return tx.RewardsOwner
}
func (tx *AddValidatorTx) Shares() uint32 {
return tx.DelegationShares
}
// SyntacticVerify returns nil iff [tx] is valid
// SyntacticVerify returns nil iff [tx] is valid.
func (tx *AddValidatorTx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
if tx == nil {
return ErrNilTx
case tx.SyntacticallyVerified: // already passed syntactic verification
return nil
case tx.DelegationShares > reward.PercentDenominator: // Ensure delegators shares are in the allowed amount
}
if tx.DelegationShares() > reward.PercentDenominator { // shares in the allowed amount
return errTooManyShares
}
if err := tx.BaseTx.SyntacticVerify(rt); err != nil {
if err := verifyBaseTx(tx.baseTx(), rt); err != nil {
return fmt.Errorf("failed to verify BaseTx: %w", err)
}
if err := verify.All(&tx.Validator, tx.RewardsOwner); err != nil {
v := tx.Validator()
if err := verify.All(&v, tx.RewardsOwner()); err != nil {
return fmt.Errorf("failed to verify validator or rewards owner: %w", err)
}
stakeOuts := tx.StakeOuts()
totalStakeWeight := uint64(0)
for _, out := range tx.StakeOuts {
for _, out := range stakeOuts {
if err := out.Verify(); err != nil {
return fmt.Errorf("failed to verify output: %w", err)
}
@@ -127,14 +124,11 @@ func (tx *AddValidatorTx) SyntacticVerify(rt *runtime.Runtime) error {
}
switch {
case !lux.IsSortedTransferableOutputs(tx.StakeOuts):
case !lux.IsSortedTransferableOutputs(stakeOuts):
return errOutputsNotSorted
case totalStakeWeight != tx.Wght:
return fmt.Errorf("%w: weight %d != stake %d", errValidatorWeightMismatch, tx.Wght, totalStakeWeight)
case totalStakeWeight != v.Wght:
return fmt.Errorf("%w: weight %d != stake %d", errValidatorWeightMismatch, v.Wght, totalStakeWeight)
}
// cache that this is valid
tx.SyntacticallyVerified = true
return nil
}
@@ -142,8 +136,7 @@ func (tx *AddValidatorTx) Visit(visitor Visitor) error {
return visitor.AddValidatorTx(tx)
}
// InitializeWithRuntime initializes the transaction with Runtime
// Initialize is a no-op; Runtime is passed explicitly to InitRuntime.
func (tx *AddValidatorTx) Initialize(ctx context.Context) error {
// Initialize any context-dependent fields here
return nil
}
+44 -61
View File
@@ -1,4 +1,4 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
@@ -9,11 +9,9 @@ import (
"fmt"
"github.com/luxfi/runtime"
"github.com/luxfi/ids"
"github.com/luxfi/math/set"
lux "github.com/luxfi/utxo"
"github.com/luxfi/utils"
"github.com/luxfi/utxo/secp256k1fx"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
var (
@@ -25,86 +23,71 @@ var (
errInputsNotSortedUnique = errors.New("inputs not sorted and unique")
)
// BaseTx contains fields common to many transaction types. It should be
// embedded in transaction implementations.
// BaseTx is the bare spending envelope: kind + NetworkID/BlockchainID/Outs/
// Ins/Memo, no delta fields. The struct IS the wire — it embeds spendingTx,
// which holds the zap buffer and serves the whole envelope surface (Bytes/
// NetworkID/Outputs/InputIDs/Memo/...). Delta-carrying tx types embed
// spendingTx the same way and add their own field accessors.
//
// Wire: zap header + object{ envelope@0..76 } (kind=kindBase).
type BaseTx struct {
lux.BaseTx `serialize:"true"`
// true iff this transaction has already passed syntactic verification
SyntacticallyVerified bool `json:"-"`
unsignedBytes []byte // Unsigned byte representation of this data
spendingTx
}
func (tx *BaseTx) SetBytes(unsignedBytes []byte) {
tx.unsignedBytes = unsignedBytes
}
func (tx *BaseTx) Bytes() []byte {
return tx.unsignedBytes
}
func (tx *BaseTx) InputIDs() set.Set[ids.ID] {
inputIDs := set.NewSet[ids.ID](len(tx.Ins))
for _, in := range tx.Ins {
inputIDs.Add(in.InputID())
// NewBaseTx builds a bare spending tx into a fresh zap buffer — the one place
// the envelope fields become bytes (construction, not serialization).
func NewBaseTx(base *lux.BaseTx) (*BaseTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 256 + spendSize)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
return inputIDs
}
func (tx *BaseTx) Outputs() []*lux.TransferableOutput {
return tx.Outs
}
// InitRuntime sets the FxID fields in the inputs and outputs of this [BaseTx]. Also
// sets the [rt] to the given [vm.rt] so that the addresses can be json
// marshalled into human readable format
func (tx *BaseTx) InitRuntime(rt *runtime.Runtime) {
for _, in := range tx.BaseTx.Ins {
in.FxID = secp256k1fx.ID
}
for _, out := range tx.BaseTx.Outs {
out.FxID = secp256k1fx.ID
out.InitRuntime(rt)
ob := b.StartObject(spendSize)
setEnvelope(ob, kindBase, base, p)
ob.FinishAsRoot()
msg, err := zap.Parse(b.Finish())
if err != nil {
return nil, err
}
return &BaseTx{spendingTx{msg}}, nil
}
// InitializeRuntime is a no-op. Runtime is passed explicitly.
func (tx *BaseTx) Initialize(ctx context.Context) error {
return nil
}
// SyntacticVerify returns nil iff this tx is well formed
// SyntacticVerify returns nil iff this tx is well formed.
func (tx *BaseTx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
if tx == nil {
return ErrNilTx
case tx.SyntacticallyVerified: // already passed syntactic verification
return nil
}
if err := tx.BaseTx.Verify(rt); err != nil {
return verifyBaseTx(tx.baseTx(), rt)
}
func (tx *BaseTx) Visit(visitor Visitor) error { return visitor.BaseTx(tx) }
func (tx *BaseTx) Initialize(context.Context) error { return nil }
// verifyBaseTx runs the shared spending-envelope checks (metadata, per-output
// and per-input verification, canonical ordering). Every spending tx type
// composes this over its own delta-field checks — one envelope validator,
// reused.
func verifyBaseTx(base lux.BaseTx, rt *runtime.Runtime) error {
if err := base.Verify(rt); err != nil {
return fmt.Errorf("metadata failed verification: %w", err)
}
for _, out := range tx.Outs {
for _, out := range base.Outs {
if err := out.Verify(); err != nil {
return fmt.Errorf("output failed verification: %w", err)
}
}
for _, in := range tx.Ins {
for _, in := range base.Ins {
if err := in.Verify(); err != nil {
return fmt.Errorf("input failed verification: %w", err)
}
}
switch {
case !lux.IsSortedTransferableOutputs(tx.Outs):
case !lux.IsSortedTransferableOutputs(base.Outs):
return errOutputsNotSorted
case !utils.IsSortedAndUnique(tx.Ins):
case !utils.IsSortedAndUnique(base.Ins):
return errInputsNotSortedUnique
default:
return nil
}
}
func (tx *BaseTx) Visit(visitor Visitor) error {
return visitor.BaseTx(tx)
}
+88 -37
View File
@@ -1,4 +1,4 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
@@ -8,11 +8,13 @@ import (
"errors"
"unicode"
"github.com/luxfi/runtime"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/runtime"
"github.com/luxfi/utils"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
const (
@@ -32,65 +34,114 @@ var (
errIllegalNameCharacter = errors.New("illegal name character")
)
// CreateChainTx is an unsigned createChainTx
// CreateChainTx creates a blockchain on a chain. The struct IS the wire: it
// embeds the spending envelope and reads its delta fields by offset.
//
// Delta layout (fixed section, after the 77-byte spending envelope):
//
// ChainID 32B @ 77 (id: chain that validates this blockchain)
// VMID 32B @ 109 (id: VM running on the new blockchain)
// BlockchainName 8B @ 141 (text ptr: human readable name)
// FxIDs 8B @ 149 (id list ptr: feature extensions)
// GenesisData 8B @ 157 (bytes ptr: genesis state)
// ChainAuth 8B @ 165 (auth ptr: sig-index list)
type CreateChainTx struct {
// Metadata, inputs and outputs
BaseTx `serialize:"true"`
// ID of the Chain that validates this blockchain
ChainID ids.ID `serialize:"true" json:"chainID"`
// A human readable name for the blockchain; need not be unique
BlockchainName string `serialize:"true" json:"blockchainName"`
// ID of the VM running on the new blockchain
VMID ids.ID `serialize:"true" json:"vmID"`
// IDs of the feature extensions running on the new blockchain
FxIDs []ids.ID `serialize:"true" json:"fxIDs"`
// Byte representation of genesis state of the new blockchain
GenesisData []byte `serialize:"true" json:"genesisData"`
// Authorizes this blockchain to be added to this chain
ChainAuth verify.Verifiable `serialize:"true" json:"chainAuthorization"`
spendingTx
}
const (
offCreateChainID = spendSize // 77
offCreateChainVMID = 109
offCreateChainName = 141
offCreateChainFxIDs = 149
offCreateChainGenesis = 157
offCreateChainAuth = 165
sizeCreateChain = 173
)
// NewCreateChainTx builds the tx into a fresh zap buffer.
func NewCreateChainTx(base *lux.BaseTx, chainID ids.ID, blockchainName string, vmID ids.ID, fxIDs []ids.ID, genesisData []byte, chainAuth verify.Verifiable) (*CreateChainTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 512 + sizeCreateChain)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
fxOff, fxCount := writeIDList(b, fxIDs)
authOff, authCount, err := writeAuth(b, chainAuth)
if err != nil {
return nil, err
}
ob := b.StartObject(sizeCreateChain)
setEnvelope(ob, kindCreateChain, base, p)
setID(ob, offCreateChainID, chainID)
setID(ob, offCreateChainVMID, vmID)
ob.SetText(offCreateChainName, blockchainName)
ob.SetList(offCreateChainFxIDs, fxOff, fxCount)
ob.SetBytes(offCreateChainGenesis, genesisData)
ob.SetList(offCreateChainAuth, authOff, authCount)
ob.FinishAsRoot()
msg, _ := zap.Parse(b.Finish())
return &CreateChainTx{spendingTx{msg}}, nil
}
// ChainID is the ID of the chain that validates this blockchain (offset read).
func (tx *CreateChainTx) ChainID() ids.ID { return readID(tx.root(), offCreateChainID) }
// VMID is the ID of the VM running on the new blockchain.
func (tx *CreateChainTx) VMID() ids.ID { return readID(tx.root(), offCreateChainVMID) }
// BlockchainName is a human-readable (non-unique) name for the blockchain.
func (tx *CreateChainTx) BlockchainName() string { return tx.root().Text(offCreateChainName) }
// FxIDs are the IDs of the feature extensions running on the new blockchain.
func (tx *CreateChainTx) FxIDs() []ids.ID { return readIDList(tx.root(), offCreateChainFxIDs) }
// GenesisData is the byte representation of the new blockchain's genesis state.
func (tx *CreateChainTx) GenesisData() []byte {
if g := tx.root().Bytes(offCreateChainGenesis); len(g) > 0 {
return append([]byte(nil), g...)
}
return nil
}
// ChainAuth authorizes this blockchain to be added to the chain.
func (tx *CreateChainTx) ChainAuth() verify.Verifiable {
return readAuth(tx.root(), offCreateChainAuth)
}
func (tx *CreateChainTx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
return ErrNilTx
case tx.SyntacticallyVerified: // already passed syntactic verification
return nil
case tx.ChainID == constants.PrimaryNetworkID:
case tx.ChainID() == constants.PrimaryNetworkID:
return ErrCantValidatePrimaryNetwork
case len(tx.BlockchainName) > MaxNameLen:
case len(tx.BlockchainName()) > MaxNameLen:
return errNameTooLong
case tx.VMID == ids.Empty:
case tx.VMID() == ids.Empty:
return errInvalidVMID
case !utils.IsSortedAndUnique(tx.FxIDs):
case !utils.IsSortedAndUnique(tx.FxIDs()):
return errFxIDsNotSortedAndUnique
case len(tx.GenesisData) > MaxGenesisLen:
case len(tx.GenesisData()) > MaxGenesisLen:
return errGenesisTooLong
}
for _, r := range tx.BlockchainName {
for _, r := range tx.BlockchainName() {
if r > unicode.MaxASCII || (!unicode.IsLetter(r) && !unicode.IsNumber(r) && r != ' ') {
return errIllegalNameCharacter
}
}
if err := tx.BaseTx.SyntacticVerify(rt); err != nil {
if err := verifyBaseTx(tx.baseTx(), rt); err != nil {
return err
}
if err := tx.ChainAuth.Verify(); err != nil {
return err
}
tx.SyntacticallyVerified = true
return nil
return tx.ChainAuth().Verify()
}
func (tx *CreateChainTx) Visit(visitor Visitor) error {
return visitor.CreateChainTx(tx)
}
// InitializeWithRuntime initializes the transaction with Runtime
func (tx *CreateChainTx) Initialize(ctx context.Context) error {
// Initialize any context-dependent fields here
return nil
}
// Initialize is a no-op; the struct is already the wire.
func (tx *CreateChainTx) Initialize(ctx context.Context) error { return nil }
+48 -32
View File
@@ -1,4 +1,4 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
@@ -6,55 +6,71 @@ package txs
import (
"context"
"github.com/luxfi/runtime"
"github.com/luxfi/node/vms/platformvm/fx"
"github.com/luxfi/runtime"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
var _ UnsignedTx = (*CreateNetworkTx)(nil)
// CreateNetworkTx is an unsigned proposal to create a new chain
// 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).
//
// Wire: zap header + object{ envelope@0..76, Owner{ threshold:u32@77,
// locktime:u64@81, addrs:listptr@89 } } (kind=kindCreateNetwork).
type CreateNetworkTx struct {
// Metadata, inputs and outputs
BaseTx `serialize:"true"`
// Who is authorized to manage this chain
Owner fx.Owner `serialize:"true" json:"owner"`
spendingTx
}
// InitRuntime sets the FxID fields in the inputs and outputs of this
// [CreateNetworkTx]. Also sets the [rt] to the given [vm.rt] so that
// the addresses can be json marshalled into human readable format
func (tx *CreateNetworkTx) InitRuntime(rt *runtime.Runtime) {
tx.BaseTx.InitRuntime(rt)
// Owner doesn't have InitRuntime method
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)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
threshold, locktime, addrOff, addrCount, err := writeOwner(b, owner)
if err != nil {
return nil, err
}
ob := b.StartObject(sizeCreateNet)
setEnvelope(ob, kindCreateNetwork, base, p)
setOwner(ob, offCreateNetOwnerThreshold, offCreateNetOwnerLocktime, offCreateNetOwnerAddrs, threshold, locktime, addrOff, addrCount)
ob.FinishAsRoot()
msg, err := zap.Parse(b.Finish())
if err != nil {
return nil, err
}
return &CreateNetworkTx{spendingTx{msg}}, nil
}
// SyntacticVerify verifies that this transaction is well-formed
// Owner is who is authorized to manage this network (offset read).
func (tx *CreateNetworkTx) Owner() fx.Owner {
return readOwner(tx.root(), offCreateNetOwnerThreshold, offCreateNetOwnerLocktime, offCreateNetOwnerAddrs)
}
// SyntacticVerify verifies that this transaction is well-formed.
func (tx *CreateNetworkTx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
if tx == nil {
return ErrNilTx
case tx.SyntacticallyVerified: // already passed syntactic verification
return nil
}
if err := tx.BaseTx.SyntacticVerify(rt); err != nil {
if err := verifyBaseTx(tx.baseTx(), rt); err != nil {
return err
}
if err := tx.Owner.Verify(); err != nil {
return err
}
tx.SyntacticallyVerified = true
return nil
return tx.Owner().Verify()
}
func (tx *CreateNetworkTx) Visit(visitor Visitor) error {
return visitor.CreateNetworkTx(tx)
}
// InitializeWithRuntime initializes the transaction with Runtime
func (tx *CreateNetworkTx) Initialize(ctx context.Context) error {
// Initialize any context-dependent fields here
return nil
}
func (tx *CreateNetworkTx) Initialize(context.Context) error { return nil }
+53 -22
View File
@@ -1,44 +1,75 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"github.com/luxfi/runtime"
"github.com/luxfi/ids"
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/runtime"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
var _ UnsignedTx = (*DisableL1ValidatorTx)(nil)
// DisableL1ValidatorTx disables an L1 validator, proving authorization with a
// credential. The struct IS the wire: it embeds spendingTx (the envelope) and
// adds the validation id plus an auth sig-index list.
//
// Wire: zap header + object{ envelope@0..76, ValidationID:32B@77,
// DisableAuth:listptr@109 } (kind=kindDisableL1Validator).
type DisableL1ValidatorTx struct {
// Metadata, inputs and outputs
BaseTx `serialize:"true"`
// ID corresponding to the validator
ValidationID ids.ID `serialize:"true" json:"validationID"`
// Authorizes this validator to be disabled
DisableAuth verify.Verifiable `serialize:"true" json:"disableAuthorization"`
spendingTx
}
const (
offDisableValidationID = spendSize // 32B
offDisableAuth = 109 // list ptr (8B)
sizeDisable = 117
)
// NewDisableL1ValidatorTx builds the tx into a fresh zap buffer.
func NewDisableL1ValidatorTx(base *lux.BaseTx, validationID ids.ID, disableAuth verify.Verifiable) (*DisableL1ValidatorTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 256 + sizeDisable)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
authOff, authCount, err := writeAuth(b, disableAuth)
if err != nil {
return nil, err
}
ob := b.StartObject(sizeDisable)
setEnvelope(ob, kindDisableL1Validator, base, p)
setID(ob, offDisableValidationID, validationID)
ob.SetList(offDisableAuth, authOff, authCount)
ob.FinishAsRoot()
msg, err := zap.Parse(b.Finish())
if err != nil {
return nil, err
}
return &DisableL1ValidatorTx{spendingTx{msg}}, nil
}
// ValidationID is the id of the validator being disabled (offset read).
func (tx *DisableL1ValidatorTx) ValidationID() ids.ID {
return readID(tx.root(), offDisableValidationID)
}
// DisableAuth authorizes this validator to be disabled (offset read).
func (tx *DisableL1ValidatorTx) DisableAuth() verify.Verifiable {
return readAuth(tx.root(), offDisableAuth)
}
func (tx *DisableL1ValidatorTx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
if tx == nil {
return ErrNilTx
case tx.SyntacticallyVerified:
// already passed syntactic verification
return nil
}
if err := tx.BaseTx.SyntacticVerify(rt); err != nil {
if err := verifyBaseTx(tx.baseTx(), rt); err != nil {
return err
}
if err := tx.DisableAuth.Verify(); err != nil {
return err
}
tx.SyntacticallyVerified = true
return nil
return tx.DisableAuth().Verify()
}
func (tx *DisableL1ValidatorTx) Visit(visitor Visitor) error {
+58 -38
View File
@@ -1,20 +1,18 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"context"
"github.com/luxfi/runtime"
"errors"
"fmt"
"github.com/luxfi/ids"
lux "github.com/luxfi/utxo"
"github.com/luxfi/node/vms/platformvm/stakeable"
"github.com/luxfi/utxo/secp256k1fx"
"github.com/luxfi/runtime"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
var (
@@ -24,44 +22,71 @@ var (
errNoExportOutputs = errors.New("no export outputs")
)
// ExportTx is an unsigned exportTx
// ExportTx sends funds to another chain. The struct IS the wire: it embeds the
// spending envelope and reads its delta fields by offset.
//
// Delta layout (fixed section, after the 77-byte spending envelope):
//
// DestinationChain 32B @ 77 (id: chain to send the funds to)
// ExportedOutputs 8B @ 109 (output list ptr)
// OwnerAddrs 8B @ 117 (shared owner-address array ptr)
type ExportTx struct {
BaseTx `serialize:"true"`
// Which chain to send the funds to
DestinationChain ids.ID `serialize:"true" json:"destinationChain"`
// Outputs that are exported to the chain
ExportedOutputs []*lux.TransferableOutput `serialize:"true" json:"exportedOutputs"`
spendingTx
}
// InitRuntime sets the FxID fields in the inputs and outputs of this
// [UnsignedExportTx]. Also sets the [rt] to the given [vm.rt] so that
// the addresses can be json marshalled into human readable format
func (tx *ExportTx) InitRuntime(rt *runtime.Runtime) {
tx.BaseTx.InitRuntime(rt)
for _, out := range tx.ExportedOutputs {
out.FxID = secp256k1fx.ID
out.InitRuntime(rt)
const (
offExportDestChain = spendSize // 77
offExportOutputs = 109
offExportAddrs = 117
sizeExport = 125
)
// NewExportTx builds the tx into a fresh zap buffer.
func NewExportTx(base *lux.BaseTx, destinationChain ids.ID, exportedOutputs []*lux.TransferableOutput) (*ExportTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 512 + sizeExport)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
listOff, listCount, addrOff, addrCount, err := writeExtraOuts(b, exportedOutputs)
if err != nil {
return nil, err
}
ob := b.StartObject(sizeExport)
setEnvelope(ob, kindExport, base, p)
setID(ob, offExportDestChain, destinationChain)
ob.SetList(offExportOutputs, listOff, listCount)
ob.SetList(offExportAddrs, addrOff, addrCount)
ob.FinishAsRoot()
msg, _ := zap.Parse(b.Finish())
return &ExportTx{spendingTx{msg}}, nil
}
// SyntacticVerify this transaction is well-formed
// DestinationChain is the chain the exported funds are sent to (offset read).
func (tx *ExportTx) DestinationChain() ids.ID { return readID(tx.root(), offExportDestChain) }
// ExportedOutputs are the outputs exported to the destination chain.
func (tx *ExportTx) ExportedOutputs() []*lux.TransferableOutput {
return readExtraOuts(tx.root(), offExportOutputs, offExportAddrs)
}
// SyntacticVerify this transaction is well-formed.
func (tx *ExportTx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
if tx == nil {
return ErrNilTx
case tx.SyntacticallyVerified: // already passed syntactic verification
return nil
case len(tx.ExportedOutputs) == 0:
}
outs := tx.ExportedOutputs()
if len(outs) == 0 {
return errNoExportOutputs
}
if err := tx.BaseTx.SyntacticVerify(rt); err != nil {
if err := verifyBaseTx(tx.baseTx(), rt); err != nil {
return err
}
for _, out := range tx.ExportedOutputs {
for _, out := range outs {
if err := out.Verify(); err != nil {
return fmt.Errorf("output failed verification: %w", err)
}
@@ -69,11 +94,9 @@ func (tx *ExportTx) SyntacticVerify(rt *runtime.Runtime) error {
return ErrWrongLocktime
}
}
if !lux.IsSortedTransferableOutputs(tx.ExportedOutputs) {
if !lux.IsSortedTransferableOutputs(outs) {
return errOutputsNotSorted
}
tx.SyntacticallyVerified = true
return nil
}
@@ -81,8 +104,5 @@ func (tx *ExportTx) Visit(visitor Visitor) error {
return visitor.ExportTx(tx)
}
// InitializeWithRuntime initializes the transaction with Runtime
func (tx *ExportTx) Initialize(ctx context.Context) error {
// Initialize any context-dependent fields here
return nil
}
// Initialize is a no-op; the struct is already the wire.
func (tx *ExportTx) Initialize(ctx context.Context) error { return nil }
+66 -45
View File
@@ -1,21 +1,19 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"context"
"github.com/luxfi/runtime"
"errors"
"fmt"
"github.com/luxfi/ids"
"github.com/luxfi/math/set"
lux "github.com/luxfi/utxo"
"github.com/luxfi/runtime"
"github.com/luxfi/utils"
"github.com/luxfi/utxo/secp256k1fx"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
var (
@@ -24,67 +22,93 @@ var (
errNoImportInputs = errors.New("tx has no imported inputs")
)
// ImportTx is an unsigned importTx
// ImportTx consumes funds produced on another chain. The struct IS the wire:
// it embeds the spending envelope and reads its delta fields by offset.
//
// Delta layout (fixed section, after the 77-byte spending envelope):
//
// SourceChain 32B @ 77 (id: chain to consume the funds from)
// ImportedInputs 8B @ 109 (input list ptr)
// SigIndices 8B @ 117 (shared input sig-index array ptr)
type ImportTx struct {
BaseTx `serialize:"true"`
// Which chain to consume the funds from
SourceChain ids.ID `serialize:"true" json:"sourceChain"`
// Inputs that consume UTXOs produced on the chain
ImportedInputs []*lux.TransferableInput `serialize:"true" json:"importedInputs"`
spendingTx
}
// InitRuntime sets the FxID fields in the inputs and outputs of this
// [ImportTx]. Also sets the [rt] to the given [vm.rt] so that
// the addresses can be json marshalled into human readable format
func (tx *ImportTx) InitRuntime(rt *runtime.Runtime) {
tx.BaseTx.InitRuntime(rt)
for _, in := range tx.ImportedInputs {
in.FxID = secp256k1fx.ID
const (
offImportSourceChain = spendSize // 77
offImportInputs = 109
offImportSigIndices = 117
sizeImport = 125
)
// NewImportTx builds the tx into a fresh zap buffer.
func NewImportTx(base *lux.BaseTx, sourceChain ids.ID, importedInputs []*lux.TransferableInput) (*ImportTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 512 + sizeImport)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
listOff, listCount, sigOff, sigCount, err := writeExtraIns(b, importedInputs)
if err != nil {
return nil, err
}
ob := b.StartObject(sizeImport)
setEnvelope(ob, kindImport, base, p)
setID(ob, offImportSourceChain, sourceChain)
ob.SetList(offImportInputs, listOff, listCount)
ob.SetList(offImportSigIndices, sigOff, sigCount)
ob.FinishAsRoot()
msg, _ := zap.Parse(b.Finish())
return &ImportTx{spendingTx{msg}}, nil
}
// InputUTXOs returns the UTXOIDs of the imported funds
// SourceChain is the chain the imported funds are consumed from (offset read).
func (tx *ImportTx) SourceChain() ids.ID { return readID(tx.root(), offImportSourceChain) }
// ImportedInputs are the inputs consuming UTXOs produced on the source chain.
func (tx *ImportTx) ImportedInputs() []*lux.TransferableInput {
return readExtraIns(tx.root(), offImportInputs, offImportSigIndices)
}
// InputUTXOs returns the UTXOIDs of the imported funds.
func (tx *ImportTx) InputUTXOs() set.Set[ids.ID] {
set := set.NewSet[ids.ID](len(tx.ImportedInputs))
for _, in := range tx.ImportedInputs {
set.Add(in.InputID())
ins := tx.ImportedInputs()
s := set.NewSet[ids.ID](len(ins))
for _, in := range ins {
s.Add(in.InputID())
}
return set
return s
}
func (tx *ImportTx) InputIDs() set.Set[ids.ID] {
inputs := tx.BaseTx.InputIDs()
atomicInputs := tx.InputUTXOs()
return inputs.Union(atomicInputs)
inputs := tx.spendingTx.InputIDs()
return inputs.Union(tx.InputUTXOs())
}
// SyntacticVerify this transaction is well-formed
// SyntacticVerify this transaction is well-formed.
func (tx *ImportTx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
if tx == nil {
return ErrNilTx
case tx.SyntacticallyVerified: // already passed syntactic verification
return nil
case len(tx.ImportedInputs) == 0:
}
ins := tx.ImportedInputs()
if len(ins) == 0 {
return errNoImportInputs
}
if err := tx.BaseTx.SyntacticVerify(rt); err != nil {
if err := verifyBaseTx(tx.baseTx(), rt); err != nil {
return err
}
for _, in := range tx.ImportedInputs {
for _, in := range ins {
if err := in.Verify(); err != nil {
return fmt.Errorf("input failed verification: %w", err)
}
}
if !utils.IsSortedAndUnique(tx.ImportedInputs) {
if !utils.IsSortedAndUnique(ins) {
return errInputsNotSortedUnique
}
tx.SyntacticallyVerified = true
return nil
}
@@ -92,8 +116,5 @@ func (tx *ImportTx) Visit(visitor Visitor) error {
return visitor.ImportTx(tx)
}
// InitializeWithRuntime initializes the transaction with Runtime
func (tx *ImportTx) Initialize(ctx context.Context) error {
// Initialize any context-dependent fields here
return nil
}
// Initialize is a no-op; the struct is already the wire.
func (tx *ImportTx) Initialize(ctx context.Context) error { return nil }
@@ -1,14 +1,15 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"github.com/luxfi/runtime"
"errors"
"github.com/luxfi/ids"
"github.com/luxfi/runtime"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
var (
@@ -17,32 +18,60 @@ var (
ErrZeroBalance = errors.New("balance must be greater than 0")
)
// IncreaseL1ValidatorBalanceTx tops up an L1 validator's continuous-fee
// balance. The struct IS the wire: it embeds spendingTx (the envelope) and
// adds two delta fields read by offset.
//
// Wire: zap header + object{ envelope@0..76, ValidationID:32B@77,
// Balance:u64@109 } (kind=kindIncreaseL1ValidatorBalance).
type IncreaseL1ValidatorBalanceTx struct {
// Metadata, inputs and outputs
BaseTx `serialize:"true"`
// ID corresponding to the validator
ValidationID ids.ID `serialize:"true" json:"validationID"`
// Balance <= sum($LUX inputs) - sum($LUX outputs) - TxFee
Balance uint64 `serialize:"true" json:"balance"`
spendingTx
}
const (
offIncreaseValidationID = spendSize // 32B
offIncreaseBalance = 109 // u64
sizeIncrease = 117
)
// NewIncreaseL1ValidatorBalanceTx builds the tx into a fresh zap buffer.
func NewIncreaseL1ValidatorBalanceTx(base *lux.BaseTx, validationID ids.ID, balance uint64) (*IncreaseL1ValidatorBalanceTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 256 + sizeIncrease)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
ob := b.StartObject(sizeIncrease)
setEnvelope(ob, kindIncreaseL1ValidatorBalance, base, p)
setID(ob, offIncreaseValidationID, validationID)
ob.SetUint64(offIncreaseBalance, balance)
ob.FinishAsRoot()
msg, err := zap.Parse(b.Finish())
if err != nil {
return nil, err
}
return &IncreaseL1ValidatorBalanceTx{spendingTx{msg}}, nil
}
// ValidationID is the id of the validator being topped up (offset read).
func (tx *IncreaseL1ValidatorBalanceTx) ValidationID() ids.ID {
return readID(tx.root(), offIncreaseValidationID)
}
// Balance is the amount to add; Balance <= sum($LUX inputs) - sum($LUX outputs)
// - TxFee (offset read).
func (tx *IncreaseL1ValidatorBalanceTx) Balance() uint64 {
return tx.root().Uint64(offIncreaseBalance)
}
func (tx *IncreaseL1ValidatorBalanceTx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
return ErrNilTx
case tx.SyntacticallyVerified:
// already passed syntactic verification
return nil
case tx.Balance == 0:
case tx.Balance() == 0:
return ErrZeroBalance
}
if err := tx.BaseTx.SyntacticVerify(rt); err != nil {
return err
}
tx.SyntacticallyVerified = true
return nil
return verifyBaseTx(tx.baseTx(), rt)
}
func (tx *IncreaseL1ValidatorBalanceTx) Visit(visitor Visitor) error {
+60 -24
View File
@@ -1,44 +1,80 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"github.com/luxfi/runtime"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/vm/types"
"github.com/luxfi/runtime"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
var _ UnsignedTx = (*RegisterL1ValidatorTx)(nil)
// RegisterL1ValidatorTx registers an L1 validator. The struct IS the wire: it
// embeds the spending envelope and reads its delta fields by offset.
//
// Delta layout (fixed section, after the 77-byte spending envelope):
//
// Balance u64 @ 77 (Balance <= sum($LUX inputs) - sum($LUX outputs) - TxFee)
// ProofOfPossession 96B @ 85 (fixed, BLS PoP of the key in the Message)
// Message 8B @ 181 (bytes ptr: signed Warp AddressedCall payload)
type RegisterL1ValidatorTx struct {
// Metadata, inputs and outputs
BaseTx `serialize:"true"`
// Balance <= sum($LUX inputs) - sum($LUX outputs) - TxFee.
Balance uint64 `serialize:"true" json:"balance"`
// ProofOfPossession of the BLS key that is included in the Message.
ProofOfPossession [bls.SignatureLen]byte `serialize:"true" json:"proofOfPossession"`
// Message is expected to be a signed Warp message containing an
// AddressedCall payload with the RegisterL1Validator message.
Message types.JSONByteSlice `serialize:"true" json:"message"`
spendingTx
}
const (
offRegisterBalance = spendSize // 77
offRegisterPoP = 85
offRegisterMessage = 181
sizeRegisterL1Validator = 189
)
// NewRegisterL1ValidatorTx builds the tx into a fresh zap buffer.
func NewRegisterL1ValidatorTx(base *lux.BaseTx, balance uint64, proofOfPossession [bls.SignatureLen]byte, message []byte) (*RegisterL1ValidatorTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 512 + sizeRegisterL1Validator)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
ob := b.StartObject(sizeRegisterL1Validator)
setEnvelope(ob, kindRegisterL1Validator, base, p)
ob.SetUint64(offRegisterBalance, balance)
ob.SetBytesFixed(offRegisterPoP, proofOfPossession[:])
ob.SetBytes(offRegisterMessage, message)
ob.FinishAsRoot()
msg, _ := zap.Parse(b.Finish())
return &RegisterL1ValidatorTx{spendingTx{msg}}, nil
}
// Balance is the initial balance funding this L1 validator (offset read).
func (tx *RegisterL1ValidatorTx) Balance() uint64 { return tx.root().Uint64(offRegisterBalance) }
// ProofOfPossession is the BLS PoP of the key included in the Message.
func (tx *RegisterL1ValidatorTx) ProofOfPossession() [bls.SignatureLen]byte {
var pop [bls.SignatureLen]byte
copy(pop[:], tx.root().BytesFixedSlice(offRegisterPoP, bls.SignatureLen))
return pop
}
// Message is the signed Warp message carrying the RegisterL1Validator payload.
func (tx *RegisterL1ValidatorTx) Message() []byte {
if m := tx.root().Bytes(offRegisterMessage); len(m) > 0 {
return append([]byte(nil), m...)
}
return nil
}
func (tx *RegisterL1ValidatorTx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
if tx == nil {
return ErrNilTx
case tx.SyntacticallyVerified:
// already passed syntactic verification
return nil
}
if err := tx.BaseTx.SyntacticVerify(rt); err != nil {
return err
}
tx.SyntacticallyVerified = true
return nil
base := BaseTx{BaseTx: tx.baseTx()}
return base.SyntacticVerify(rt)
}
func (tx *RegisterL1ValidatorTx) Visit(visitor Visitor) error {
+62 -27
View File
@@ -1,4 +1,4 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
@@ -7,10 +7,12 @@ import (
"context"
"errors"
"github.com/luxfi/runtime"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/runtime"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
var (
@@ -19,45 +21,78 @@ var (
ErrRemovePrimaryNetworkValidator = errors.New("can't remove primary network validator with RemoveChainValidatorTx")
)
// RemoveChainValidatorTx removes a validator from a chain.
// RemoveChainValidatorTx removes a validator from a chain. The struct IS the
// wire: it embeds spendingTx (the envelope) and adds the node id, chain id,
// and an auth sig-index list.
//
// Wire: zap header + object{ envelope@0..76, NodeID:20B@77, Chain:32B@97,
// ChainAuth:listptr@129 } (kind=kindRemoveChainValidator).
type RemoveChainValidatorTx struct {
BaseTx `serialize:"true"`
// The node to remove from the chain.
NodeID ids.NodeID `serialize:"true" json:"nodeID"`
// The chain to remove the node from.
Chain ids.ID `serialize:"true" json:"chainID"`
// Proves that the issuer has the right to remove the node from the chain.
ChainAuth verify.Verifiable `serialize:"true" json:"chainAuthorization"`
spendingTx
}
const (
offRemoveNodeID = spendSize // 20B (ids.NodeIDLen)
offRemoveChain = 97 // 32B
offRemoveChainAuth = 129 // list ptr (8B)
sizeRemove = 137
)
// NewRemoveChainValidatorTx builds the tx into a fresh zap buffer.
func NewRemoveChainValidatorTx(base *lux.BaseTx, nodeID ids.NodeID, chain ids.ID, chainAuth verify.Verifiable) (*RemoveChainValidatorTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 256 + sizeRemove)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
authOff, authCount, err := writeAuth(b, chainAuth)
if err != nil {
return nil, err
}
ob := b.StartObject(sizeRemove)
setEnvelope(ob, kindRemoveChainValidator, base, p)
setNodeID(ob, offRemoveNodeID, nodeID)
setID(ob, offRemoveChain, chain)
ob.SetList(offRemoveChainAuth, authOff, authCount)
ob.FinishAsRoot()
msg, err := zap.Parse(b.Finish())
if err != nil {
return nil, err
}
return &RemoveChainValidatorTx{spendingTx{msg}}, nil
}
// NodeID is the node to remove from the chain (offset read).
func (tx *RemoveChainValidatorTx) NodeID() ids.NodeID {
return readNodeID(tx.root(), offRemoveNodeID)
}
// Chain is the chain to remove the node from (offset read).
func (tx *RemoveChainValidatorTx) Chain() ids.ID {
return readID(tx.root(), offRemoveChain)
}
// ChainAuth proves the issuer's right to remove the node from the chain
// (offset read).
func (tx *RemoveChainValidatorTx) ChainAuth() verify.Verifiable {
return readAuth(tx.root(), offRemoveChainAuth)
}
func (tx *RemoveChainValidatorTx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
return ErrNilTx
case tx.SyntacticallyVerified:
// already passed syntactic verification
return nil
case tx.Chain == constants.PrimaryNetworkID:
case tx.Chain() == constants.PrimaryNetworkID:
return ErrRemovePrimaryNetworkValidator
}
if err := tx.BaseTx.SyntacticVerify(rt); err != nil {
if err := verifyBaseTx(tx.baseTx(), rt); err != nil {
return err
}
if err := tx.ChainAuth.Verify(); err != nil {
return err
}
tx.SyntacticallyVerified = true
return nil
return tx.ChainAuth().Verify()
}
func (tx *RemoveChainValidatorTx) Visit(visitor Visitor) error {
return visitor.RemoveChainValidatorTx(tx)
}
// InitializeWithRuntime initializes the transaction with Runtime
func (tx *RemoveChainValidatorTx) Initialize(ctx context.Context) error {
// Initialize any context-dependent fields here
return nil
}
func (tx *RemoveChainValidatorTx) Initialize(context.Context) error { return nil }
+36 -33
View File
@@ -1,4 +1,4 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
@@ -6,11 +6,11 @@ package txs
import (
"context"
"github.com/luxfi/runtime"
"github.com/luxfi/ids"
"github.com/luxfi/math/set"
"github.com/luxfi/runtime"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
var _ UnsignedTx = (*RewardValidatorTx)(nil)
@@ -25,41 +25,44 @@ var _ UnsignedTx = (*RewardValidatorTx)(nil)
// If this transaction is accepted and the next block accepted is an Abort
// block, the validator is removed and the address that the validator specified
// receives the staked LUX but no reward.
//
// The struct IS the wire: it holds the zap buffer and reads its field by
// offset. No codec, no marshal.
//
// Wire: zap header + object{ kind:u8@0, TxID:32B@1 }.
type RewardValidatorTx struct {
// ID of the tx that created the delegator/validator being removed/rewarded
TxID ids.ID `serialize:"true" json:"txID"`
unsignedBytes []byte // Unsigned byte representation of this data
msg *zap.Message
}
func (tx *RewardValidatorTx) SetBytes(unsignedBytes []byte) {
tx.unsignedBytes = unsignedBytes
// offRewardTxID is the wire position of the removed staker's tx id (after
// kind@0).
const offRewardTxID = 1 // 32B, after kind@0
// NewRewardValidatorTx builds the tx into a fresh zap buffer — the one place a
// field becomes bytes (construction, not serialization).
func NewRewardValidatorTx(txID ids.ID) *RewardValidatorTx {
b := zap.NewBuilder(zap.HeaderSize + 16 + 33)
ob := b.StartObject(33)
ob.SetUint8(offKind, uint8(kindRewardValidator))
setID(ob, offRewardTxID, txID)
ob.FinishAsRoot()
msg, _ := zap.Parse(b.Finish())
return &RewardValidatorTx{msg: msg}
}
func (*RewardValidatorTx) InitRuntime(*runtime.Runtime) {}
// TxID is the id of the tx that created the delegator/validator being
// removed/rewarded (offset read).
func (tx *RewardValidatorTx) TxID() ids.ID { return readID(tx.msg.Root(), offRewardTxID) }
func (tx *RewardValidatorTx) Bytes() []byte {
return tx.unsignedBytes
}
// Bytes returns the underlying zap buffer literally — no encode.
func (tx *RewardValidatorTx) Bytes() []byte { return tx.msg.Bytes() }
func (*RewardValidatorTx) InputIDs() set.Set[ids.ID] {
return nil
}
// SetBytes wraps b as this tx's buffer (zero copy); the Parse dispatch uses it.
func (tx *RewardValidatorTx) SetBytes(b []byte) { tx.msg, _ = zap.Parse(b) }
func (*RewardValidatorTx) Outputs() []*lux.TransferableOutput {
return nil
}
func (*RewardValidatorTx) SyntacticVerify(*runtime.Runtime) error {
return nil
}
func (tx *RewardValidatorTx) Visit(visitor Visitor) error {
return visitor.RewardValidatorTx(tx)
}
// InitializeWithRuntime initializes the transaction with Runtime
func (tx *RewardValidatorTx) Initialize(ctx context.Context) error {
// Initialize any context-dependent fields here
return nil
}
func (*RewardValidatorTx) InitRuntime(*runtime.Runtime) {}
func (*RewardValidatorTx) InputIDs() set.Set[ids.ID] { return nil }
func (*RewardValidatorTx) Outputs() []*lux.TransferableOutput { return nil }
func (*RewardValidatorTx) SyntacticVerify(*runtime.Runtime) error { return nil }
func (tx *RewardValidatorTx) Visit(visitor Visitor) error { return visitor.RewardValidatorTx(tx) }
func (tx *RewardValidatorTx) Initialize(context.Context) error { return nil }
@@ -1,39 +1,64 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"github.com/luxfi/runtime"
"github.com/luxfi/vm/types"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
var _ UnsignedTx = (*SetL1ValidatorWeightTx)(nil)
// SetL1ValidatorWeightTx carries a signed Warp message that sets an L1
// validator's weight. The struct IS the wire: it embeds spendingTx (the
// envelope) and adds the Warp Message as a variable-length bytes tail.
//
// Wire: zap header + object{ envelope@0..76, Message:bytesptr@77 }
// (kind=kindSetL1ValidatorWeight).
type SetL1ValidatorWeightTx struct {
// Metadata, inputs and outputs
BaseTx `serialize:"true"`
// Message is expected to be a signed Warp message containing an
// AddressedCall payload with the SetL1ValidatorWeight message.
Message types.JSONByteSlice `serialize:"true" json:"message"`
spendingTx
}
const (
offSetWeightMessage = spendSize // bytes ptr (8B)
sizeSetWeight = 85
)
// NewSetL1ValidatorWeightTx builds the tx into a fresh zap buffer.
func NewSetL1ValidatorWeightTx(base *lux.BaseTx, message []byte) (*SetL1ValidatorWeightTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 256 + sizeSetWeight)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
ob := b.StartObject(sizeSetWeight)
setEnvelope(ob, kindSetL1ValidatorWeight, base, p)
ob.SetBytes(offSetWeightMessage, message)
ob.FinishAsRoot()
msg, err := zap.Parse(b.Finish())
if err != nil {
return nil, err
}
return &SetL1ValidatorWeightTx{spendingTx{msg}}, nil
}
// Message is the signed Warp message (an AddressedCall carrying the
// SetL1ValidatorWeight payload). Returns a copy so callers can't mutate the
// buffer.
func (tx *SetL1ValidatorWeightTx) Message() []byte {
if m := tx.root().Bytes(offSetWeightMessage); len(m) > 0 {
return append([]byte(nil), m...)
}
return nil
}
func (tx *SetL1ValidatorWeightTx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
if tx == nil {
return ErrNilTx
case tx.SyntacticallyVerified:
// already passed syntactic verification
return nil
}
if err := tx.BaseTx.SyntacticVerify(rt); err != nil {
return err
}
tx.SyntacticallyVerified = true
return nil
return verifyBaseTx(tx.baseTx(), rt)
}
func (tx *SetL1ValidatorWeightTx) Visit(visitor Visitor) error {
@@ -1,20 +1,19 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"context"
"github.com/luxfi/runtime"
"errors"
"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/utxo/secp256k1fx"
"github.com/luxfi/runtime"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
var (
@@ -23,56 +22,86 @@ var (
ErrTransferPermissionlessChain = errors.New("cannot transfer ownership of a permissionless chain")
)
// TransferChainOwnershipTx re-owns a chain. The struct IS the wire: it embeds
// the spending envelope and reads its delta fields by offset.
//
// Delta layout (fixed section, after the 77-byte spending envelope):
//
// Chain 32B @ 77 (id)
// ChainAuth 8B @ 109 (auth ptr: sig-index list)
// Owner.Threshold u32 @ 117
// Owner.Locktime u64 @ 121
// Owner.Addrs 8B @ 129 (addr list ptr)
type TransferChainOwnershipTx struct {
// Metadata, inputs and outputs
BaseTx `serialize:"true"`
// ID of the chain this tx is modifying
Chain ids.ID `serialize:"true" json:"chainID"`
// Proves that the issuer has the right to modify the chain.
ChainAuth verify.Verifiable `serialize:"true" json:"chainAuthorization"`
// Who is now authorized to manage this chain
Owner fx.Owner `serialize:"true" json:"newOwner"`
spendingTx
}
// InitRuntime sets the FxID fields in the inputs and outputs of this
// [TransferChainOwnershipTx]. Also sets the [rt] to the given [vm.rt] so
// that the addresses can be json marshalled into human readable format
func (tx *TransferChainOwnershipTx) InitRuntime(rt *runtime.Runtime) {
tx.BaseTx.InitRuntime(rt)
// Owner doesn't have InitRuntime method
if owner, ok := tx.Owner.(*secp256k1fx.OutputOwners); ok {
owner.InitRuntime(rt)
const (
offTransferChain = spendSize // 77
offTransferChainAuth = 109
offTransferOwnerThreshold = 117
offTransferOwnerLocktime = 121
offTransferOwnerAddrs = 129
sizeTransferChainOwnership = 137
)
// NewTransferChainOwnershipTx builds the tx into a fresh zap buffer.
func NewTransferChainOwnershipTx(base *lux.BaseTx, chain ids.ID, chainAuth verify.Verifiable, owner fx.Owner) (*TransferChainOwnershipTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 512 + sizeTransferChainOwnership)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
authOff, authCount, err := writeAuth(b, chainAuth)
if err != nil {
return nil, err
}
threshold, locktime, addrOff, addrCount, err := writeOwner(b, owner)
if err != nil {
return nil, err
}
ob := b.StartObject(sizeTransferChainOwnership)
setEnvelope(ob, kindTransferChainOwnership, base, p)
setID(ob, offTransferChain, chain)
ob.SetList(offTransferChainAuth, authOff, authCount)
setOwner(ob, offTransferOwnerThreshold, offTransferOwnerLocktime, offTransferOwnerAddrs, threshold, locktime, addrOff, addrCount)
ob.FinishAsRoot()
msg, _ := zap.Parse(b.Finish())
return &TransferChainOwnershipTx{spendingTx{msg}}, nil
}
// Chain is the ID of the chain this tx is modifying (offset read).
func (tx *TransferChainOwnershipTx) Chain() ids.ID { return readID(tx.root(), offTransferChain) }
// ChainAuth proves the issuer has the right to modify the chain.
func (tx *TransferChainOwnershipTx) ChainAuth() verify.Verifiable {
return readAuth(tx.root(), offTransferChainAuth)
}
// Owner is who is now authorized to manage this chain.
func (tx *TransferChainOwnershipTx) Owner() fx.Owner {
return readOwner(tx.root(), offTransferOwnerThreshold, offTransferOwnerLocktime, offTransferOwnerAddrs)
}
func (tx *TransferChainOwnershipTx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
return ErrNilTx
case tx.SyntacticallyVerified:
// already passed syntactic verification
return nil
case tx.Chain == constants.PrimaryNetworkID:
case tx.Chain() == constants.PrimaryNetworkID:
return ErrTransferPermissionlessChain
}
if err := tx.BaseTx.SyntacticVerify(rt); err != nil {
if err := verifyBaseTx(tx.baseTx(), rt); err != nil {
return err
}
if err := verify.All(tx.ChainAuth, tx.Owner); err != nil {
return err
}
tx.SyntacticallyVerified = true
return nil
return verify.All(tx.ChainAuth(), tx.Owner())
}
func (tx *TransferChainOwnershipTx) Visit(visitor Visitor) error {
return visitor.TransferChainOwnershipTx(tx)
}
// InitializeWithRuntime initializes the transaction with Runtime
func (tx *TransferChainOwnershipTx) Initialize(ctx context.Context) error {
// Initialize any context-dependent fields here
return nil
}
// Initialize is a no-op; the struct is already the wire.
func (tx *TransferChainOwnershipTx) Initialize(ctx context.Context) error { return nil }
+197 -107
View File
@@ -1,4 +1,4 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
@@ -8,12 +8,13 @@ import (
"errors"
"fmt"
"github.com/luxfi/runtime"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/node/vms/platformvm/reward"
"github.com/luxfi/runtime"
lux "github.com/luxfi/utxo"
"github.com/luxfi/zap"
)
var (
@@ -38,141 +39,230 @@ var (
errUptimeRequirementTooLarge = fmt.Errorf("uptime requirement must be less than or equal to %d", reward.PercentDenominator)
)
// TransformChainTx is an unsigned transformChainTx
// TransformChainTx transforms a chain into a permissionless one. The struct IS
// the wire: it embeds the spending envelope and reads its delta fields by
// offset.
//
// Delta layout (fixed section, after the 77-byte spending envelope):
//
// Chain 32B @ 77
// AssetID 32B @ 109
// InitialSupply u64 @ 141
// MaximumSupply u64 @ 149
// MinConsumptionRate u64 @ 157
// MaxConsumptionRate u64 @ 165
// MinValidatorStake u64 @ 173
// MaxValidatorStake u64 @ 181
// MinStakeDuration u32 @ 189
// MaxStakeDuration u32 @ 193
// MinDelegationFee u32 @ 197
// MinDelegatorStake u64 @ 201
// MaxValidatorWeightFactor u8 @ 209
// UptimeRequirement u32 @ 210
// ChainAuth 8B @ 214 (auth ptr: sig-index list)
type TransformChainTx struct {
// Metadata, inputs and outputs
BaseTx `serialize:"true"`
// ID of the Chain to transform
// Restrictions:
// - Must not be the Primary Network ID
Chain ids.ID `serialize:"true" json:"chainID"`
// Asset to use when staking on the Net
// Restrictions:
// - Must not be the Empty ID
// - Must not be the LUX ID
AssetID ids.ID `serialize:"true" json:"assetID"`
// Amount to initially specify as the current supply
// Restrictions:
// - Must be > 0
InitialSupply uint64 `serialize:"true" json:"initialSupply"`
// Amount to specify as the maximum token supply
// Restrictions:
// - Must be >= [InitialSupply]
MaximumSupply uint64 `serialize:"true" json:"maximumSupply"`
// MinConsumptionRate is the rate to allocate funds if the validator's stake
// duration is 0
MinConsumptionRate uint64 `serialize:"true" json:"minConsumptionRate"`
// MaxConsumptionRate is the rate to allocate funds if the validator's stake
// duration is equal to the minting period
// Restrictions:
// - Must be >= [MinConsumptionRate]
// - Must be <= [reward.PercentDenominator]
MaxConsumptionRate uint64 `serialize:"true" json:"maxConsumptionRate"`
// MinValidatorStake is the minimum amount of funds required to become a
// validator.
// Restrictions:
// - Must be > 0
// - Must be <= [InitialSupply]
MinValidatorStake uint64 `serialize:"true" json:"minValidatorStake"`
// MaxValidatorStake is the maximum amount of funds a single validator can
// be allocated, including delegated funds.
// Restrictions:
// - Must be >= [MinValidatorStake]
// - Must be <= [MaximumSupply]
MaxValidatorStake uint64 `serialize:"true" json:"maxValidatorStake"`
// MinStakeDuration is the minimum number of seconds a staker can stake for.
// Restrictions:
// - Must be > 0
MinStakeDuration uint32 `serialize:"true" json:"minStakeDuration"`
// MaxStakeDuration is the maximum number of seconds a staker can stake for.
// Restrictions:
// - Must be >= [MinStakeDuration]
// - Must be <= [GlobalMaxStakeDuration]
MaxStakeDuration uint32 `serialize:"true" json:"maxStakeDuration"`
// MinDelegationFee is the minimum percentage a validator must charge a
// delegator for delegating.
// Restrictions:
// - Must be <= [reward.PercentDenominator]
MinDelegationFee uint32 `serialize:"true" json:"minDelegationFee"`
// MinDelegatorStake is the minimum amount of funds required to become a
// delegator.
// Restrictions:
// - Must be > 0
MinDelegatorStake uint64 `serialize:"true" json:"minDelegatorStake"`
// MaxValidatorWeightFactor is the factor which calculates the maximum
// amount of delegation a validator can receive.
// Note: a value of 1 effectively disables delegation.
// Restrictions:
// - Must be > 0
MaxValidatorWeightFactor byte `serialize:"true" json:"maxValidatorWeightFactor"`
// UptimeRequirement is the minimum percentage a validator must be online
// and responsive to receive a reward.
// Restrictions:
// - Must be <= [reward.PercentDenominator]
UptimeRequirement uint32 `serialize:"true" json:"uptimeRequirement"`
// Authorizes this transformation
ChainAuth verify.Verifiable `serialize:"true" json:"chainAuthorization"`
spendingTx
}
const (
offTransformChain = spendSize // 77
offTransformAssetID = 109
offTransformInitialSupply = 141
offTransformMaximumSupply = 149
offTransformMinConsumptionRate = 157
offTransformMaxConsumptionRate = 165
offTransformMinValidatorStake = 173
offTransformMaxValidatorStake = 181
offTransformMinStakeDuration = 189
offTransformMaxStakeDuration = 193
offTransformMinDelegationFee = 197
offTransformMinDelegatorStake = 201
offTransformMaxValidatorWeightFactor = 209
offTransformUptimeRequirement = 210
offTransformChainAuth = 214
sizeTransformChain = 222
)
// NewTransformChainTx builds the tx into a fresh zap buffer.
func NewTransformChainTx(
base *lux.BaseTx,
chain ids.ID,
assetID ids.ID,
initialSupply uint64,
maximumSupply uint64,
minConsumptionRate uint64,
maxConsumptionRate uint64,
minValidatorStake uint64,
maxValidatorStake uint64,
minStakeDuration uint32,
maxStakeDuration uint32,
minDelegationFee uint32,
minDelegatorStake uint64,
maxValidatorWeightFactor byte,
uptimeRequirement uint32,
chainAuth verify.Verifiable,
) (*TransformChainTx, error) {
b := zap.NewBuilder(zap.HeaderSize + 512 + sizeTransformChain)
p, err := writeSpending(b, base)
if err != nil {
return nil, err
}
authOff, authCount, err := writeAuth(b, chainAuth)
if err != nil {
return nil, err
}
ob := b.StartObject(sizeTransformChain)
setEnvelope(ob, kindTransformChain, base, p)
setID(ob, offTransformChain, chain)
setID(ob, offTransformAssetID, assetID)
ob.SetUint64(offTransformInitialSupply, initialSupply)
ob.SetUint64(offTransformMaximumSupply, maximumSupply)
ob.SetUint64(offTransformMinConsumptionRate, minConsumptionRate)
ob.SetUint64(offTransformMaxConsumptionRate, maxConsumptionRate)
ob.SetUint64(offTransformMinValidatorStake, minValidatorStake)
ob.SetUint64(offTransformMaxValidatorStake, maxValidatorStake)
ob.SetUint32(offTransformMinStakeDuration, minStakeDuration)
ob.SetUint32(offTransformMaxStakeDuration, maxStakeDuration)
ob.SetUint32(offTransformMinDelegationFee, minDelegationFee)
ob.SetUint64(offTransformMinDelegatorStake, minDelegatorStake)
ob.SetUint8(offTransformMaxValidatorWeightFactor, maxValidatorWeightFactor)
ob.SetUint32(offTransformUptimeRequirement, uptimeRequirement)
ob.SetList(offTransformChainAuth, authOff, authCount)
ob.FinishAsRoot()
msg, _ := zap.Parse(b.Finish())
return &TransformChainTx{spendingTx{msg}}, nil
}
// Chain is the ID of the chain to transform (offset read).
func (tx *TransformChainTx) Chain() ids.ID { return readID(tx.root(), offTransformChain) }
// AssetID is the asset to use when staking on the chain.
func (tx *TransformChainTx) AssetID() ids.ID { return readID(tx.root(), offTransformAssetID) }
// InitialSupply is the amount to initially specify as the current supply.
func (tx *TransformChainTx) InitialSupply() uint64 {
return tx.root().Uint64(offTransformInitialSupply)
}
// MaximumSupply is the amount to specify as the maximum token supply.
func (tx *TransformChainTx) MaximumSupply() uint64 {
return tx.root().Uint64(offTransformMaximumSupply)
}
// MinConsumptionRate is the rate to allocate funds if the validator's stake
// duration is 0.
func (tx *TransformChainTx) MinConsumptionRate() uint64 {
return tx.root().Uint64(offTransformMinConsumptionRate)
}
// MaxConsumptionRate is the rate to allocate funds if the validator's stake
// duration is equal to the minting period.
func (tx *TransformChainTx) MaxConsumptionRate() uint64 {
return tx.root().Uint64(offTransformMaxConsumptionRate)
}
// MinValidatorStake is the minimum amount of funds required to become a
// validator.
func (tx *TransformChainTx) MinValidatorStake() uint64 {
return tx.root().Uint64(offTransformMinValidatorStake)
}
// MaxValidatorStake is the maximum amount of funds a single validator can be
// allocated, including delegated funds.
func (tx *TransformChainTx) MaxValidatorStake() uint64 {
return tx.root().Uint64(offTransformMaxValidatorStake)
}
// MinStakeDuration is the minimum number of seconds a staker can stake for.
func (tx *TransformChainTx) MinStakeDuration() uint32 {
return tx.root().Uint32(offTransformMinStakeDuration)
}
// MaxStakeDuration is the maximum number of seconds a staker can stake for.
func (tx *TransformChainTx) MaxStakeDuration() uint32 {
return tx.root().Uint32(offTransformMaxStakeDuration)
}
// MinDelegationFee is the minimum percentage a validator must charge a
// delegator for delegating.
func (tx *TransformChainTx) MinDelegationFee() uint32 {
return tx.root().Uint32(offTransformMinDelegationFee)
}
// MinDelegatorStake is the minimum amount of funds required to become a
// delegator.
func (tx *TransformChainTx) MinDelegatorStake() uint64 {
return tx.root().Uint64(offTransformMinDelegatorStake)
}
// MaxValidatorWeightFactor is the factor which calculates the maximum amount of
// delegation a validator can receive.
func (tx *TransformChainTx) MaxValidatorWeightFactor() byte {
return tx.root().Uint8(offTransformMaxValidatorWeightFactor)
}
// UptimeRequirement is the minimum percentage a validator must be online and
// responsive to receive a reward.
func (tx *TransformChainTx) UptimeRequirement() uint32 {
return tx.root().Uint32(offTransformUptimeRequirement)
}
// ChainAuth authorizes this transformation.
func (tx *TransformChainTx) ChainAuth() verify.Verifiable {
return readAuth(tx.root(), offTransformChainAuth)
}
func (tx *TransformChainTx) SyntacticVerify(rt *runtime.Runtime) error {
switch {
case tx == nil:
return ErrNilTx
case tx.SyntacticallyVerified: // already passed syntactic verification
return nil
case tx.Chain == constants.PrimaryNetworkID:
case tx.Chain() == constants.PrimaryNetworkID:
return errCantTransformPrimaryNetwork
case tx.AssetID == ids.Empty:
case tx.AssetID() == ids.Empty:
return errEmptyAssetID
case tx.AssetID == rt.UTXOAssetID:
case tx.AssetID() == rt.UTXOAssetID:
return errAssetIDCantBeLUX
case tx.InitialSupply == 0:
case tx.InitialSupply() == 0:
return errInitialSupplyZero
case tx.InitialSupply > tx.MaximumSupply:
case tx.InitialSupply() > tx.MaximumSupply():
return errInitialSupplyGreaterThanMaxSupply
case tx.MinConsumptionRate > tx.MaxConsumptionRate:
case tx.MinConsumptionRate() > tx.MaxConsumptionRate():
return errMinConsumptionRateTooLarge
case tx.MaxConsumptionRate > reward.PercentDenominator:
case tx.MaxConsumptionRate() > reward.PercentDenominator:
return errMaxConsumptionRateTooLarge
case tx.MinValidatorStake == 0:
case tx.MinValidatorStake() == 0:
return errMinValidatorStakeZero
case tx.MinValidatorStake > tx.InitialSupply:
case tx.MinValidatorStake() > tx.InitialSupply():
return errMinValidatorStakeAboveSupply
case tx.MinValidatorStake > tx.MaxValidatorStake:
case tx.MinValidatorStake() > tx.MaxValidatorStake():
return errMinValidatorStakeAboveMax
case tx.MaxValidatorStake > tx.MaximumSupply:
case tx.MaxValidatorStake() > tx.MaximumSupply():
return errMaxValidatorStakeTooLarge
case tx.MinStakeDuration == 0:
case tx.MinStakeDuration() == 0:
return errMinStakeDurationZero
case tx.MinStakeDuration > tx.MaxStakeDuration:
case tx.MinStakeDuration() > tx.MaxStakeDuration():
return errMinStakeDurationTooLarge
case tx.MinDelegationFee > reward.PercentDenominator:
case tx.MinDelegationFee() > reward.PercentDenominator:
return errMinDelegationFeeTooLarge
case tx.MinDelegatorStake == 0:
case tx.MinDelegatorStake() == 0:
return errMinDelegatorStakeZero
case tx.MaxValidatorWeightFactor == 0:
case tx.MaxValidatorWeightFactor() == 0:
return errMaxValidatorWeightFactorZero
case tx.UptimeRequirement > reward.PercentDenominator:
case tx.UptimeRequirement() > reward.PercentDenominator:
return errUptimeRequirementTooLarge
}
if err := tx.BaseTx.SyntacticVerify(rt); err != nil {
if err := verifyBaseTx(tx.baseTx(), rt); err != nil {
return err
}
if err := tx.ChainAuth.Verify(); err != nil {
return err
}
tx.SyntacticallyVerified = true
return nil
return tx.ChainAuth().Verify()
}
func (tx *TransformChainTx) Visit(visitor Visitor) error {
return visitor.TransformChainTx(tx)
}
// InitializeWithRuntime initializes the transaction with Runtime
func (tx *TransformChainTx) Initialize(ctx context.Context) error {
// Initialize any context-dependent fields here
return nil
}
// Initialize is a no-op; the struct is already the wire.
func (tx *TransformChainTx) Initialize(ctx context.Context) error { return nil }