diff --git a/genesis/builder/builder.go b/genesis/builder/builder.go index c83ba4653..61eae858c 100644 --- a/genesis/builder/builder.go +++ b/genesis/builder/builder.go @@ -728,10 +728,10 @@ func UTXOAssetIDFromGenesisBytes(genesisBytes []byte) (ids.ID, bool, error) { if !ok { continue } - if uChain.VMID != constants.XVMID { + if uChain.VMID() != constants.XVMID { continue } - id, err := xvmgenesis.AssetIDFromBytes(uChain.GenesisData) + id, err := xvmgenesis.AssetIDFromBytes(uChain.GenesisData()) if err != nil { return ids.Empty, false, fmt.Errorf("derive X-Chain asset ID from genesis data: %w", err) } @@ -749,7 +749,7 @@ func VMGenesis(genesisBytes []byte, vmID ids.ID) (*pchaintxs.Tx, error) { } for _, chain := range gen.Chains { uChain := chain.Unsigned.(*pchaintxs.CreateChainTx) - if uChain.VMID == vmID { + if uChain.VMID() == vmID { return chain, nil } } @@ -778,7 +778,7 @@ func Aliases(genesisBytes []byte) (map[string][]string, map[ids.ID][]string, err uChain := chain.Unsigned.(*pchaintxs.CreateChainTx) chainID := chain.ID() endpoint := path.Join(constants.ChainAliasPrefix, chainID.String()) - switch uChain.VMID { + switch uChain.VMID() { case constants.XVMID: apiAliases[endpoint] = []string{ "X", diff --git a/go.mod b/go.mod index 7312a815a..e1aa51b62 100644 --- a/go.mod +++ b/go.mod @@ -261,3 +261,5 @@ require ( ) exclude github.com/ethereum/go-ethereum v1.10.26 + +replace github.com/luxfi/zap => /Users/z/work/lux/zap diff --git a/network/network.go b/network/network.go index e0241587a..5c0a4aaf0 100644 --- a/network/network.go +++ b/network/network.go @@ -340,15 +340,16 @@ func NewNetwork( // AddPermissionlessValidatorTx (modern). Handle both. switch tx := validatorTx.Unsigned.(type) { case *txs.AddPermissionlessValidatorTx: - nodeID := tx.Validator.NodeID - weight := tx.Validator.Wght + validator := tx.Validator() + nodeID := validator.NodeID + weight := validator.Wght if weight == 0 { weight = 1 } var blsKey []byte - if tx.Signer != nil { - if pubKey := tx.Signer.Key(); pubKey != nil { + if s := tx.Signer(); s != nil { + if pubKey := s.Key(); pubKey != nil { blsKey = bls.PublicKeyToCompressedBytes(pubKey) } } @@ -368,8 +369,9 @@ func NewNetwork( zap.Int("blsKeyLen", len(blsKey)), ) case *txs.AddValidatorTx: - nodeID := tx.Validator.NodeID - weight := tx.Validator.Wght + validator := tx.Validator() + nodeID := validator.NodeID + weight := validator.Wght if weight == 0 { weight = 1 } diff --git a/vms/platformvm/block/builder/builder.go b/vms/platformvm/block/builder/builder.go index 01715a202..324496bfa 100644 --- a/vms/platformvm/block/builder/builder.go +++ b/vms/platformvm/block/builder/builder.go @@ -682,8 +682,8 @@ func getNextStakerToReward( } func NewRewardValidatorTx(ctx context.Context, txID ids.ID) (*txs.Tx, error) { - utx := &txs.RewardValidatorTx{TxID: txID} - tx, err := txs.NewSigned(utx, txs.Codec, nil) + utx := txs.NewRewardValidatorTx(txID) + tx, err := txs.NewSigned(utx, nil) if err != nil { return nil, err } diff --git a/vms/platformvm/block/executor/options.go b/vms/platformvm/block/executor/options.go index 223563e39..e35907989 100644 --- a/vms/platformvm/block/executor/options.go +++ b/vms/platformvm/block/executor/options.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" - "github.com/luxfi/validators/uptime" "github.com/luxfi/constants" "github.com/luxfi/log" "github.com/luxfi/node/vms/platformvm/block" @@ -15,6 +14,7 @@ import ( "github.com/luxfi/node/vms/platformvm/state" "github.com/luxfi/node/vms/platformvm/txs" txexecutor "github.com/luxfi/node/vms/platformvm/txs/executor" + "github.com/luxfi/validators/uptime" ) var ( @@ -71,7 +71,7 @@ func (o *options) ProposalBlock(b *block.ProposalBlock) error { ) } - prefersCommit, err := o.prefersCommit(b.Tx) + prefersCommit, err := o.prefersCommit(b.Tx()) if err != nil { o.log.Debug("falling back to prefer commit", "error", err, @@ -105,7 +105,7 @@ func (o *options) prefersCommit(tx *txs.Tx) (bool, error) { return false, fmt.Errorf("%w: %T", errUnexpectedProposalTxType, tx.Unsigned) } - stakerTx, _, err := o.state.GetTx(unsignedTx.TxID) + stakerTx, _, err := o.state.GetTx(unsignedTx.TxID()) if err != nil { return false, fmt.Errorf("%w: %w", errFailedFetchingStakerTx, err) } @@ -132,7 +132,7 @@ func (o *options) prefersCommit(tx *txs.Tx) (bool, error) { return false, fmt.Errorf("%w: %w", errFailedFetchingNetTransformation, err) } - expectedUptimePercentage = float64(transformNet.UptimeRequirement) / reward.PercentDenominator + expectedUptimePercentage = float64(transformNet.UptimeRequirement()) / reward.PercentDenominator } uptime, err := o.uptimes.CalculateUptimePercentFrom( diff --git a/vms/platformvm/block/executor/verifier.go b/vms/platformvm/block/executor/verifier.go index 360e5dff5..0b28d576b 100644 --- a/vms/platformvm/block/executor/verifier.go +++ b/vms/platformvm/block/executor/verifier.go @@ -8,8 +8,8 @@ import ( "fmt" "github.com/luxfi/ids" + "github.com/luxfi/math" "github.com/luxfi/math/set" - "github.com/luxfi/vm/chains/atomic" "github.com/luxfi/node/vms/platformvm/block" "github.com/luxfi/node/vms/platformvm/config" "github.com/luxfi/node/vms/platformvm/metrics" @@ -17,7 +17,7 @@ import ( "github.com/luxfi/node/vms/platformvm/status" "github.com/luxfi/node/vms/platformvm/txs" txexecutor "github.com/luxfi/node/vms/platformvm/txs/executor" - "github.com/luxfi/math" + "github.com/luxfi/vm/chains/atomic" "github.com/luxfi/node/vms/components/gas" txfee "github.com/luxfi/node/vms/platformvm/txs/fee" @@ -74,7 +74,7 @@ func (v *verifier) ProposalBlock(b *block.ProposalBlock) error { feeCalculator := state.PickFeeCalculator(v.txExecutorBackend.Config, onDecisionState) inputs, atomicRequests, onAcceptFunc, gasConsumed, _, err := v.processStandardTxs( - b.Transactions, + b.Txs(), feeCalculator, onDecisionState, b.Parent(), @@ -95,7 +95,7 @@ func (v *verifier) ProposalBlock(b *block.ProposalBlock) error { return v.proposalBlock( // Must be the last validity check on the block b, - b.Tx, + b.Tx(), onDecisionState, gasConsumed, onCommitState, @@ -131,7 +131,7 @@ func (v *verifier) StandardBlock(b *block.StandardBlock) error { feeCalculator := state.PickFeeCalculator(v.txExecutorBackend.Config, onAcceptState) return v.standardBlock( // Must be the last validity check on the block b, - b.Transactions, + b.Txs(), feeCalculator, onAcceptState, changed, diff --git a/vms/platformvm/block/executor/warp_verifier.go b/vms/platformvm/block/executor/warp_verifier.go index 46d5a51a4..46afcfbb8 100644 --- a/vms/platformvm/block/executor/warp_verifier.go +++ b/vms/platformvm/block/executor/warp_verifier.go @@ -10,11 +10,11 @@ import ( "github.com/luxfi/accel" "github.com/luxfi/crypto/bls" "github.com/luxfi/math/set" - validators "github.com/luxfi/validators" "github.com/luxfi/node/vms/platformvm/block" "github.com/luxfi/node/vms/platformvm/txs" txexecutor "github.com/luxfi/node/vms/platformvm/txs/executor" "github.com/luxfi/node/vms/platformvm/warp" + validators "github.com/luxfi/validators" ) // VerifyWarpMessages verifies all warp messages in the block. If any of the @@ -148,9 +148,9 @@ func VerifyWarpMessages( func extractWarpMessageBytes(unsigned txs.UnsignedTx) ([]byte, bool) { switch tx := unsigned.(type) { case *txs.RegisterL1ValidatorTx: - return tx.Message, true + return tx.Message(), true case *txs.SetL1ValidatorWeightTx: - return tx.Message, true + return tx.Message(), true default: return nil, false } diff --git a/vms/platformvm/config/internal.go b/vms/platformvm/config/internal.go index b99a34acd..4585594b2 100644 --- a/vms/platformvm/config/internal.go +++ b/vms/platformvm/config/internal.go @@ -117,18 +117,18 @@ type Internal struct { func (c *Internal) CreateChain(blockchainID ids.ID, tx *txs.CreateChainTx) { if c.SybilProtectionEnabled && !c.TrackAllChains && // Not tracking all chains automatically - !c.TrackedChains.Contains(tx.ChainID) && // Check if chain ID is tracked + !c.TrackedChains.Contains(tx.ChainID()) && // Check if chain ID is tracked !c.TrackedChains.Contains(blockchainID) { // Check if blockchain ID is tracked return } chainParams := chains.ChainParameters{ ID: blockchainID, - ChainID: tx.ChainID, - GenesisData: tx.GenesisData, - VMID: tx.VMID, - FxIDs: tx.FxIDs, - Name: tx.BlockchainName, + ChainID: tx.ChainID(), + GenesisData: tx.GenesisData(), + VMID: tx.VMID(), + FxIDs: tx.FxIDs(), + Name: tx.BlockchainName(), } c.Chains.QueueChainCreation(chainParams) diff --git a/vms/platformvm/genesis/codec.go b/vms/platformvm/genesis/codec.go deleted file mode 100644 index 87ee06cbb..000000000 --- a/vms/platformvm/genesis/codec.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package genesis - -import ( - "github.com/luxfi/node/vms/platformvm/block" - "github.com/luxfi/node/vms/platformvm/txs" -) - -// CodecVersion is the canonical write version for new P-Chain genesis -// blobs. It is shared with the block codec (and txs codec) so a single -// constant bumps the whole stack. -const CodecVersion = block.CodecVersion - -// Codec is the multi-version codec.Manager used to (un)marshal P-Chain -// genesis blobs. It is intentionally an alias for txs.GenesisCodec -// rather than block.GenesisCodec because: -// -// - txs.GenesisCodec registers BOTH the v0 (v1.23.x Apricot/Banff) -// and v1 (current) tx slot maps, so it can decode v0-prefixed -// cached-genesis blobs that pre-date the codec bump. block.Genesis -// Codec carries only the v1 slot map and errors on prefix=0 with -// codec.ErrUnknownVersion — exactly the failure mode that broke -// v1.28.0 testnet canary at first-byte parse of -// /genesis.bytes. -// - The outer Genesis struct itself has no slot ID; all version- -// sensitive data lives in the embedded []*txs.Tx (validators, -// chains). Decoding via txs.GenesisCodec is therefore exactly the -// dispatch we need — version is taken from the 2-byte wire prefix -// and routed to the v0 or v1 tx slot map. -// - Marshal at CodecVersion (== v1) is unchanged: the v1 slot map in -// txs.GenesisCodec is byte-for-byte identical to the v1 slot map in -// block.GenesisCodec (both come from registerV1TxTypes). -// -// All write paths (Genesis.Bytes, New, static_service) MUST continue to -// use CodecVersion. v0 is a READ-ONLY decoder. -var Codec = txs.GenesisCodec diff --git a/vms/platformvm/genesis/genesis.go b/vms/platformvm/genesis/genesis.go index 440e5af06..d2f1b4698 100644 --- a/vms/platformvm/genesis/genesis.go +++ b/vms/platformvm/genesis/genesis.go @@ -9,6 +9,7 @@ import ( "fmt" "github.com/luxfi/address" + "github.com/luxfi/constants" "github.com/luxfi/ids" lux "github.com/luxfi/utxo" "github.com/luxfi/node/vms/platformvm/stakeable" @@ -51,43 +52,12 @@ type Genesis struct { Message string `serialize:"true"` } -// Parse deserializes a P-Chain genesis blob produced by either the -// v0 (v1.23.x) or v1 (current) codec. Wire version is taken from the -// 2-byte codec prefix; the matching codec.Manager entry is used to -// unmarshal the outer Genesis struct, and embedded validator + chain -// txs are bound to their original signedBytes via InitializeFromBytes -// (NOT Initialize, which would re-marshal and rotate the TxID). -// -// TxID stability across the v0->v1 migration is preserved because: -// - v0 genesis blobs decode via the v0 slot map and embedded txs are -// re-bound at version 0; their hash(signedBytes) is identical to -// what the v1.23.x producer committed. -// - v1 genesis blobs decode via the v1 slot map; embedded txs are -// re-bound at version 1; bytes are deterministic so the result is -// byte-equal to what a v1 producer emits. -// -// Genesis blobs are typically produced fresh from the JSON config at -// each bootstrap (see New + Bytes), so the path that matters for -// mainnet/testnet is the v0 fallback: the originally-committed genesis -// hash is what subsequent block headers chain back to, and that hash -// is hash(v0 genesis bytes). +// Parse deserializes a P-Chain genesis blob from its native-ZAP wire form +// (see genesiswire.go). Embedded validator + chain txs are re-parsed via +// txs.Parse from their own self-delimiting signed bytes, so each TxID +// (= hash(signedBytes)) is preserved byte-for-byte with no re-encoding. func Parse(genesisBytes []byte) (*Genesis, error) { - gen := &Genesis{} - version, err := Codec.Unmarshal(genesisBytes, gen) - if err != nil { - return nil, err - } - for _, tx := range gen.Validators { - if err := tx.InitializeFromBytesAtVersion(txs.GenesisCodec, version); err != nil { - return nil, err - } - } - for _, tx := range gen.Chains { - if err := tx.InitializeFromBytesAtVersion(txs.GenesisCodec, version); err != nil { - return nil, err - } - } - return gen, nil + return parseGenesis(genesisBytes) } // Allocation is a UTXO on the Platform Chain that exists at the chain's genesis @@ -315,40 +285,41 @@ func New( delegationFee := vdr.ExactDelegationFee - var ( - baseTx = txs.BaseTx{BaseTx: lux.BaseTx{ - NetworkID: networkID, - BlockchainID: ids.Empty, - }} - validator = txs.Validator{ - NodeID: vdr.NodeID, - Start: time, - End: vdr.EndTime, - Wght: weight, - } - tx *txs.Tx - ) - if vdr.Signer == nil { - tx = &txs.Tx{Unsigned: &txs.AddValidatorTx{ - BaseTx: baseTx, - Validator: validator, - StakeOuts: stake, - RewardsOwner: owner, - DelegationShares: delegationFee, - }} - } else { - tx = &txs.Tx{Unsigned: &txs.AddPermissionlessValidatorTx{ - BaseTx: baseTx, - Validator: validator, - Signer: vdr.Signer, - StakeOuts: stake, - ValidatorRewardsOwner: owner, - DelegatorRewardsOwner: owner, - DelegationShares: delegationFee, - }} + base := &lux.BaseTx{ + NetworkID: networkID, + BlockchainID: ids.Empty, + } + validator := txs.Validator{ + NodeID: vdr.NodeID, + Start: time, + End: vdr.EndTime, + Wght: weight, } - if err := tx.Initialize(txs.GenesisCodec); err != nil { + var ( + unsigned txs.UnsignedTx + err error + ) + if vdr.Signer == nil { + unsigned, err = txs.NewAddValidatorTx(base, validator, stake, owner, delegationFee) + } else { + unsigned, err = txs.NewAddPermissionlessValidatorTx( + base, + validator, + constants.PrimaryNetworkID, + vdr.Signer, + stake, + owner, // validator rewards owner + owner, // delegator rewards owner + delegationFee, + ) + } + if err != nil { + return nil, err + } + + tx := &txs.Tx{Unsigned: unsigned} + if err := tx.Initialize(); err != nil { return nil, err } @@ -358,19 +329,25 @@ func New( // Specify the chains that exist at genesis chainsTxs := []*txs.Tx{} for _, chain := range chains { - tx := &txs.Tx{Unsigned: &txs.CreateChainTx{ - BaseTx: txs.BaseTx{BaseTx: lux.BaseTx{ - NetworkID: networkID, - BlockchainID: ids.Empty, - }}, - ChainID: chain.ChainID, - BlockchainName: chain.Name, - VMID: chain.VMID, - FxIDs: chain.FxIDs, - GenesisData: chain.GenesisData, - ChainAuth: &secp256k1fx.Input{}, - }} - if err := tx.Initialize(txs.GenesisCodec); err != nil { + base := &lux.BaseTx{ + NetworkID: networkID, + BlockchainID: ids.Empty, + } + unsigned, err := txs.NewCreateChainTx( + base, + chain.ChainID, + chain.Name, + chain.VMID, + chain.FxIDs, + chain.GenesisData, + &secp256k1fx.Input{}, + ) + if err != nil { + return nil, err + } + + tx := &txs.Tx{Unsigned: unsigned} + if err := tx.Initialize(); err != nil { return nil, err } @@ -391,7 +368,7 @@ func New( return g, nil } -// Bytes serializes the Genesis to bytes using the PlatformVM genesis codec +// Bytes serializes the Genesis to its native-ZAP wire form (see genesiswire.go). func (g *Genesis) Bytes() ([]byte, error) { - return Codec.Marshal(CodecVersion, g) + return marshalGenesis(g) } diff --git a/vms/platformvm/genesis/genesis_test.go b/vms/platformvm/genesis/genesis_test.go deleted file mode 100644 index 5af2d79d1..000000000 --- a/vms/platformvm/genesis/genesis_test.go +++ /dev/null @@ -1,369 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package genesis - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/luxfi/address" - "github.com/luxfi/constants" - "github.com/luxfi/ids" - "github.com/luxfi/node/vms/platformvm/txs" - "github.com/luxfi/utxo/secp256k1fx" -) - -func createTestGenesis(t *testing.T) *Genesis { - require := require.New(t) - - nodeID := ids.BuildTestNodeID([]byte{1}) - addr, err := address.FormatBech32(constants.UnitTestHRP, nodeID.Bytes()) - require.NoError(err) - - validator := PermissionlessValidator{ - Validator: Validator{ - StartTime: 0, - EndTime: 20, - NodeID: nodeID, - }, - RewardOwner: &Owner{ - Threshold: 1, - Addresses: []string{addr}, - }, - Staked: []Allocation{{ - Amount: 987654321, - Address: addr, - }}, - } - - genesis, err := New( - ids.ID{'d', 'u', 'm', 'm', 'y', ' ', 'I', 'D'}, - constants.UnitTestID, - []Allocation{ - { - Address: addr, - Amount: 123456789, - }, - }, - []PermissionlessValidator{validator}, - nil, - 5, - 0, - "Test Genesis", - ) - require.NoError(err) - - return genesis -} - -func TestNewInvalidUTXOBalance(t *testing.T) { - require := require.New(t) - nodeID := ids.BuildTestNodeID([]byte{1, 2, 3}) - addr, err := address.FormatBech32(constants.UnitTestHRP, nodeID.Bytes()) - require.NoError(err) - - utxo := Allocation{ - Address: addr, - Amount: 0, - } - weight := uint64(987654321) - validator := PermissionlessValidator{ - Validator: Validator{ - EndTime: 15, - Weight: weight, - NodeID: nodeID, - }, - RewardOwner: &Owner{ - Threshold: 1, - Addresses: []string{addr}, - }, - Staked: []Allocation{{ - Amount: weight, - Address: addr, - }}, - } - - genesis, err := New( - ids.Empty, - constants.UnitTestID, - []Allocation{utxo}, - []PermissionlessValidator{validator}, - nil, - 5, - 0, - "", - ) - require.ErrorIs(err, errUTXOHasNoValue) - require.Nil(genesis) -} - -func TestNewInvalidStakeWeight(t *testing.T) { - require := require.New(t) - nodeID := ids.BuildTestNodeID([]byte{1, 2, 3}) - addr, err := address.FormatBech32(constants.UnitTestHRP, nodeID.Bytes()) - require.NoError(err) - - utxo := Allocation{ - Address: addr, - Amount: 123456789, - } - - validator := PermissionlessValidator{ - Validator: Validator{ - StartTime: 0, - EndTime: 15, - NodeID: nodeID, - }, - RewardOwner: &Owner{ - Threshold: 1, - Addresses: []string{addr}, - }, - Staked: []Allocation{{ - Amount: 0, - Address: addr, - }}, - } - - genesis, err := New( - ids.Empty, - 0, - []Allocation{utxo}, - []PermissionlessValidator{validator}, - nil, - 5, - 0, - "", - ) - require.ErrorIs(err, errValidatorHasZeroWeight) - require.Nil(genesis) -} - -func TestNewInvalidEndtime(t *testing.T) { - require := require.New(t) - nodeID := ids.BuildTestNodeID([]byte{1, 2, 3}) - addr, err := address.FormatBech32(constants.UnitTestHRP, nodeID.Bytes()) - require.NoError(err) - - utxo := Allocation{ - Address: addr, - Amount: 123456789, - } - - weight := uint64(987654321) - validator := PermissionlessValidator{ - Validator: Validator{ - StartTime: 0, - EndTime: 5, - NodeID: nodeID, - }, - RewardOwner: &Owner{ - Threshold: 1, - Addresses: []string{addr}, - }, - Staked: []Allocation{{ - Amount: weight, - Address: addr, - }}, - } - - genesis, err := New( - ids.Empty, - constants.UnitTestID, - []Allocation{utxo}, - []PermissionlessValidator{validator}, - nil, - 5, - 0, - "", - ) - require.ErrorIs(err, errValidatorAlreadyExited) - require.Nil(genesis) -} - -func TestGenesisBytes(t *testing.T) { - require := require.New(t) - genesis := createTestGenesis(t) - bytes, err := genesis.Bytes() - require.NoError(err) - require.NotEmpty(bytes) -} - -func TestGenesis(t *testing.T) { - require := require.New(t) - genesis := createTestGenesis(t) - - utxoAssetID := ids.ID{'d', 'u', 'm', 'm', 'y', ' ', 'I', 'D'} - nodeID := ids.BuildTestNodeID([]byte{1}) - require.Equal("Test Genesis", genesis.Message) - - // Validate allocations - require.Len(genesis.UTXOs, 1) - utxo := genesis.UTXOs[0] - require.Equal(utxoAssetID, utxo.Asset.ID) - output, ok := utxo.Out.(*secp256k1fx.TransferOutput) - require.True(ok) - require.Equal(uint64(123456789), output.Amt) - require.Len(output.OutputOwners.Addrs, 1) - - // Validate validator - require.Len(genesis.Validators, 1) - validator := genesis.Validators[0] - txValidator, ok := validator.Unsigned.(*txs.AddValidatorTx) - require.True(ok) - require.Equal(nodeID, txValidator.Validator.NodeID) - require.Equal(uint64(20), txValidator.Validator.End) - require.Len(txValidator.StakeOuts, 1) - stakeOut := txValidator.StakeOuts[0] - stakeOutput, ok := stakeOut.Out.(*secp256k1fx.TransferOutput) - require.True(ok) - require.Equal(uint64(987654321), stakeOutput.Amt) - - require.Empty(genesis.Chains) - require.Equal(uint64(5), genesis.Timestamp) - require.Equal(uint64(0), genesis.InitialSupply) -} - -func TestNewReturnsSortedValidators(t *testing.T) { - require := require.New(t) - nodeID := ids.BuildTestNodeID([]byte{1}) - addr, err := address.FormatBech32(constants.UnitTestHRP, nodeID.Bytes()) - require.NoError(err) - - allocation := Allocation{ - Address: addr, - Amount: 123456789, - } - - weight := uint64(987654321) - validator1 := PermissionlessValidator{ - Validator: Validator{ - StartTime: 0, - EndTime: 20, - NodeID: nodeID, - }, - RewardOwner: &Owner{ - Threshold: 1, - Addresses: []string{addr}, - }, - Staked: []Allocation{{ - Amount: weight, - Address: addr, - }}, - } - - validator2 := PermissionlessValidator{ - Validator: Validator{ - StartTime: 3, - EndTime: 15, - NodeID: nodeID, - }, - RewardOwner: &Owner{ - Threshold: 1, - Addresses: []string{addr}, - }, - Staked: []Allocation{{ - Amount: weight, - Address: addr, - }}, - } - - validator3 := PermissionlessValidator{ - Validator: Validator{ - StartTime: 1, - EndTime: 10, - NodeID: nodeID, - }, - RewardOwner: &Owner{ - Threshold: 1, - Addresses: []string{addr}, - }, - Staked: []Allocation{{ - Amount: weight, - Address: addr, - }}, - } - - utxoAssetID := ids.ID{'d', 'u', 'm', 'm', 'y', ' ', 'I', 'D'} - genesis, err := New( - utxoAssetID, - constants.UnitTestID, - []Allocation{allocation}, - []PermissionlessValidator{ - validator1, - validator2, - validator3, - }, - nil, - 5, - 0, - "", - ) - require.NoError(err) - genesisBytes, err := genesis.Bytes() - require.NoError(err) - require.NotEmpty(genesisBytes) - require.Len(genesis.Validators, 3) -} - -func TestAllocationCompare(t *testing.T) { - var ( - smallerAddr = ids.ShortID{} - largerAddr = ids.ShortID{1} - ) - smallerAddrStr, err := address.FormatBech32("lux", smallerAddr[:]) - require.NoError(t, err) - largerAddrStr, err := address.FormatBech32("lux", largerAddr[:]) - require.NoError(t, err) - - type test struct { - name string - alloc1 Allocation - alloc2 Allocation - expected int - } - tests := []test{ - { - name: "both empty", - alloc1: Allocation{}, - alloc2: Allocation{}, - expected: 0, - }, - { - name: "locktime smaller", - alloc1: Allocation{}, - alloc2: Allocation{ - Locktime: 1, - }, - expected: -1, - }, - { - name: "amount smaller", - alloc1: Allocation{}, - alloc2: Allocation{ - Amount: 1, - }, - expected: -1, - }, - { - name: "address smaller", - alloc1: Allocation{ - Address: smallerAddrStr, - }, - alloc2: Allocation{ - Address: largerAddrStr, - }, - expected: -1, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - - require.Equal(tt.expected, tt.alloc1.Compare(tt.alloc2)) - require.Equal(-tt.expected, tt.alloc2.Compare(tt.alloc1)) - }) - } -} diff --git a/vms/platformvm/genesis/genesistest/genesis.go b/vms/platformvm/genesis/genesistest/genesis.go index a47680bf5..6f50b3663 100644 --- a/vms/platformvm/genesis/genesistest/genesis.go +++ b/vms/platformvm/genesis/genesistest/genesis.go @@ -125,18 +125,19 @@ func New(t testing.TB, c Config) *platformvmgenesis.Genesis { key.Address(), }, } - validator := &txs.AddValidatorTx{ - BaseTx: txs.BaseTx{BaseTx: lux.BaseTx{ - NetworkID: c.NetworkID, - BlockchainID: constants.PlatformChainID, - }}, - Validator: txs.Validator{ + validatorBase := &lux.BaseTx{ + NetworkID: c.NetworkID, + BlockchainID: constants.PlatformChainID, + } + validator, err := txs.NewAddValidatorTx( + validatorBase, + txs.Validator{ NodeID: nodeID, Start: uint64(c.ValidatorStartTime.Unix()), End: uint64(c.ValidatorEndTime.Unix()), Wght: c.ValidatorWeight, }, - StakeOuts: []*lux.TransferableOutput{ + []*lux.TransferableOutput{ { Asset: XAsset, Out: &secp256k1fx.TransferOutput{ @@ -145,27 +146,32 @@ func New(t testing.TB, c Config) *platformvmgenesis.Genesis { }, }, }, - RewardsOwner: &owner, - DelegationShares: ValidatorDelegationShares, - } + &owner, + ValidatorDelegationShares, + ) + require.NoError(err) validatorTx := &txs.Tx{Unsigned: validator} - require.NoError(validatorTx.Initialize(txs.GenesisCodec)) + require.NoError(validatorTx.Initialize()) genesis.Validators[i] = validatorTx } - chain := &txs.CreateChainTx{ - BaseTx: txs.BaseTx{BaseTx: lux.BaseTx{ - NetworkID: c.NetworkID, - BlockchainID: constants.PlatformChainID, - }}, - ChainID: constants.PrimaryNetworkID, // Changed from ChainID to ChainID in regenesis - BlockchainName: XChainName, - VMID: constants.XVMID, // Changed from AVMID to XVMID in Lux - ChainAuth: &secp256k1fx.Input{}, + chainBase := &lux.BaseTx{ + NetworkID: c.NetworkID, + BlockchainID: constants.PlatformChainID, } + chain, err := txs.NewCreateChainTx( + chainBase, + constants.PrimaryNetworkID, + XChainName, + constants.XVMID, + nil, + nil, + &secp256k1fx.Input{}, + ) + require.NoError(err) chainTx := &txs.Tx{Unsigned: chain} - require.NoError(chainTx.Initialize(txs.GenesisCodec)) + require.NoError(chainTx.Initialize()) genesis.Chains = []*txs.Tx{chainTx} return genesis @@ -173,7 +179,7 @@ func New(t testing.TB, c Config) *platformvmgenesis.Genesis { func NewBytes(t testing.TB, c Config) []byte { g := New(t, c) - genesisBytes, err := platformvmgenesis.Codec.Marshal(platformvmgenesis.CodecVersion, g) + genesisBytes, err := g.Bytes() require.NoError(t, err) return genesisBytes } diff --git a/vms/platformvm/genesis/genesiswire.go b/vms/platformvm/genesis/genesiswire.go new file mode 100644 index 000000000..02720b7a7 --- /dev/null +++ b/vms/platformvm/genesis/genesiswire.go @@ -0,0 +1,215 @@ +// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package genesis + +// Native-ZAP wire for the P-Chain genesis blob (struct-is-wire; no codec, no +// version prefix, no slot registry). The blob is one zap object carrying the +// scalar header plus three variable-length element lists — UTXOs, validator +// txs, chain txs — each encoded as a lengths list + concatenated blob (the +// same framing the migrated block package uses for its tx lists). Embedded +// txs are stored as their own self-delimiting signed bytes and re-parsed via +// txs.Parse, so their TxID (= hash(signedBytes)) is preserved byte-for-byte. +// Re-genesis means this layout is free to be the canonical one. + +import ( + "fmt" + + lux "github.com/luxfi/utxo" + "github.com/luxfi/node/vms/platformvm/txs" + "github.com/luxfi/zap" +) + +// Genesis object layout (size 72): +const ( + genTimestamp = 0 // u64 + genInitialSupply = 8 // u64 + genMessage = 16 // text ptr (8B) + genUTXOLens = 24 // list ptr (8B): per-UTXO blob lengths + genUTXOBlob = 32 // bytes ptr (8B): concatenated UTXO blobs + genVdrLens = 40 // list ptr (8B) + genVdrBlob = 48 // bytes ptr (8B) + genChainLens = 56 // list ptr (8B) + genChainBlob = 64 // bytes ptr (8B) + genSize = 72 + genLenStride = 4 // uint32 +) + +// genesis UTXO sub-object (size 16): utxo wire bytes @0, per-UTXO message @8. +const ( + gutxoWire = 0 + gutxoMsg = 8 + gutxoSize = 16 +) + +func marshalGenesis(g *Genesis) ([]byte, error) { + utxoBlobs := make([][]byte, len(g.UTXOs)) + for i, u := range g.UTXOs { + raw, err := marshalGenesisUTXO(u) + if err != nil { + return nil, fmt.Errorf("genesis: marshal utxo %d: %w", i, err) + } + utxoBlobs[i] = raw + } + vdrBlobs, err := txBlobs(g.Validators) + if err != nil { + return nil, fmt.Errorf("genesis: marshal validators: %w", err) + } + chainBlobs, err := txBlobs(g.Chains) + if err != nil { + return nil, fmt.Errorf("genesis: marshal chains: %w", err) + } + + b := zap.NewBuilder(zap.HeaderSize + genSize + 1024) + utxoLenOff, utxoLenCount, utxoBlob := writeBlobList(b, utxoBlobs) + vdrLenOff, vdrLenCount, vdrBlob := writeBlobList(b, vdrBlobs) + chainLenOff, chainLenCount, chainBlob := writeBlobList(b, chainBlobs) + + ob := b.StartObject(genSize) + ob.SetUint64(genTimestamp, g.Timestamp) + ob.SetUint64(genInitialSupply, g.InitialSupply) + ob.SetText(genMessage, g.Message) + ob.SetList(genUTXOLens, utxoLenOff, utxoLenCount) + ob.SetBytes(genUTXOBlob, utxoBlob) + ob.SetList(genVdrLens, vdrLenOff, vdrLenCount) + ob.SetBytes(genVdrBlob, vdrBlob) + ob.SetList(genChainLens, chainLenOff, chainLenCount) + ob.SetBytes(genChainBlob, chainBlob) + ob.FinishAsRoot() + return b.Finish(), nil +} + +func parseGenesis(genesisBytes []byte) (*Genesis, error) { + msg, err := zap.Parse(genesisBytes) + if err != nil { + return nil, err + } + obj := msg.Root() + + g := &Genesis{ + Timestamp: obj.Uint64(genTimestamp), + InitialSupply: obj.Uint64(genInitialSupply), + Message: obj.Text(genMessage), + } + + utxoBlobs, err := readBlobList(obj, genUTXOLens, genUTXOBlob) + if err != nil { + return nil, fmt.Errorf("genesis: read utxos: %w", err) + } + if len(utxoBlobs) > 0 { + g.UTXOs = make([]*UTXO, len(utxoBlobs)) + for i, raw := range utxoBlobs { + u, err := parseGenesisUTXO(raw) + if err != nil { + return nil, fmt.Errorf("genesis: parse utxo %d: %w", i, err) + } + g.UTXOs[i] = u + } + } + + if g.Validators, err = parseTxBlobs(obj, genVdrLens, genVdrBlob); err != nil { + return nil, fmt.Errorf("genesis: read validators: %w", err) + } + if g.Chains, err = parseTxBlobs(obj, genChainLens, genChainBlob); err != nil { + return nil, fmt.Errorf("genesis: read chains: %w", err) + } + return g, nil +} + +func marshalGenesisUTXO(u *UTXO) ([]byte, error) { + wire, err := u.UTXO.WireBytes() + if err != nil { + return nil, err + } + b := zap.NewBuilder(zap.HeaderSize + gutxoSize + len(wire) + len(u.Message)) + ob := b.StartObject(gutxoSize) + ob.SetBytes(gutxoWire, wire) + ob.SetBytes(gutxoMsg, u.Message) + ob.FinishAsRoot() + return b.Finish(), nil +} + +func parseGenesisUTXO(blob []byte) (*UTXO, error) { + msg, err := zap.Parse(blob) + if err != nil { + return nil, err + } + obj := msg.Root() + inner, err := lux.ParseUTXO(obj.Bytes(gutxoWire)) + if err != nil { + return nil, err + } + var message []byte + if m := obj.Bytes(gutxoMsg); len(m) > 0 { + message = append([]byte(nil), m...) + } + return &UTXO{UTXO: *inner, Message: message}, nil +} + +// ---- shared lengths-list + blob framing ---- + +func txBlobs(list []*txs.Tx) ([][]byte, error) { + if len(list) == 0 { + return nil, nil + } + out := make([][]byte, len(list)) + for i, tx := range list { + if tx == nil { + return nil, fmt.Errorf("nil tx at index %d", i) + } + out[i] = tx.Bytes() + } + return out, nil +} + +func writeBlobList(b *zap.Builder, blobs [][]byte) (lenOff, lenCount int, blob []byte) { + if len(blobs) == 0 { + return 0, 0, nil + } + lb := b.StartList(genLenStride) + for _, raw := range blobs { + lb.AddUint32(uint32(len(raw))) + blob = append(blob, raw...) + } + lenOff, lenCount = lb.Finish() + return lenOff, lenCount, blob +} + +func readBlobList(obj zap.Object, lenPtrOff, blobPtrOff int) ([][]byte, error) { + lengths := obj.ListStride(lenPtrOff, genLenStride) + n := lengths.Len() + if n == 0 { + return nil, nil + } + blob := obj.Bytes(blobPtrOff) + out := make([][]byte, n) + cursor := 0 + for i := 0; i < n; i++ { + size := int(lengths.Uint32(i)) + if size < 0 || cursor+size > len(blob) { + return nil, fmt.Errorf("element %d length %d overruns blob (%d)", i, size, len(blob)) + } + out[i] = blob[cursor : cursor+size] + cursor += size + } + return out, nil +} + +func parseTxBlobs(obj zap.Object, lenPtrOff, blobPtrOff int) ([]*txs.Tx, error) { + blobs, err := readBlobList(obj, lenPtrOff, blobPtrOff) + if err != nil { + return nil, err + } + if len(blobs) == 0 { + return nil, nil + } + out := make([]*txs.Tx, len(blobs)) + for i, raw := range blobs { + tx, err := txs.Parse(raw) + if err != nil { + return nil, fmt.Errorf("parse tx %d: %w", i, err) + } + out[i] = tx + } + return out, nil +} diff --git a/vms/platformvm/genesis/parse_v0_test.go b/vms/platformvm/genesis/parse_v0_test.go deleted file mode 100644 index 3a32df537..000000000 --- a/vms/platformvm/genesis/parse_v0_test.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package genesis - -import ( - "bytes" - "encoding/binary" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/luxfi/node/vms/platformvm/txs" -) - -// TestParseAcceptsV0CachedGenesis is the regression guard for the -// v1.28.0 testnet-canary failure: -// -// unable to load genesis file: resolve X-Chain asset ID from cached -// genesis: parse platform genesis: unknown codec version -// -// Pre-fix, genesis.Codec aliased block.GenesisCodec which registers -// ONLY the v1 tx slot map. A v0-prefixed cached-genesis blob (carried -// over from v1.23.x bootstraps) errored at the very first byte with -// codec.ErrUnknownVersion. -// -// Post-fix, genesis.Codec aliases txs.GenesisCodec which registers -// BOTH v0 and v1 tx slot maps. The 2-byte wire prefix selects the slot -// map; the outer Genesis struct decodes inline regardless. -func TestParseAcceptsV0CachedGenesis(t *testing.T) { - require := require.New(t) - - gen := createTestGenesis(t) - - // Build a v0-prefixed blob by Marshaling the same Genesis struct - // at CodecVersionV0. The outer struct has no slot ID; embedded - // *txs.Tx fields serialize their v0 slot IDs because the Marshal - // path resolves slot IDs against the v0 slot map (registered on - // txs.GenesisCodec under CodecVersionV0). - v0Bytes, err := Codec.Marshal(txs.CodecVersionV0, gen) - require.NoError(err, "v0 marshal must succeed — proves the v0 slot map is registered") - require.GreaterOrEqual(len(v0Bytes), 2) - - prefix := binary.BigEndian.Uint16(v0Bytes[:2]) - require.Equal(txs.CodecVersionV0, prefix, "marshaled bytes must carry the v0 wire prefix") - - // The bug: Parse used to fail here with codec.ErrUnknownVersion. - parsed, err := Parse(v0Bytes) - require.NoError(err, "v0-prefixed genesis must round-trip through the multi-version dispatcher") - require.NotNil(parsed) - - require.Equal(gen.Timestamp, parsed.Timestamp) - require.Equal(gen.InitialSupply, parsed.InitialSupply) - require.Equal(gen.Message, parsed.Message) - require.Len(parsed.UTXOs, len(gen.UTXOs)) - require.Len(parsed.Validators, len(gen.Validators)) - require.Len(parsed.Chains, len(gen.Chains)) - - // Embedded validator txs must have been re-initialized at v0 so - // their (re-marshaled) signed bytes — and therefore their TxIDs — - // match what a v1.23.x producer would have committed at genesis. - require.NotEmpty(parsed.Validators[0].Bytes()) - require.NotEqual([32]byte{}, parsed.Validators[0].TxID) -} - -// TestParseAcceptsV1Genesis is the no-regression guard for the -// canonical write path: every blob produced by Genesis.Bytes() in this -// binary is v1-prefixed and must still parse byte-for-byte. -func TestParseAcceptsV1Genesis(t *testing.T) { - require := require.New(t) - - gen := createTestGenesis(t) - v1Bytes, err := gen.Bytes() - require.NoError(err) - require.GreaterOrEqual(len(v1Bytes), 2) - - prefix := binary.LittleEndian.Uint16(v1Bytes[:2]) - require.Equal(txs.CodecVersionV1, prefix, "Genesis.Bytes must emit the v1 wire prefix (LE per LP-023)") - - parsed, err := Parse(v1Bytes) - require.NoError(err) - require.Equal(gen.Timestamp, parsed.Timestamp) - require.Equal(gen.InitialSupply, parsed.InitialSupply) - require.Equal(gen.Message, parsed.Message) -} - -// TestParseV0RoundtripIsBytePreserving documents the byte-preserving -// guarantee in genesis.go's Parse doc: v0 -> Parse -> re-marshal-at-v0 -// must produce the exact original bytes. This is what keeps the -// originally-committed genesis hash stable across the v0->v1 codec -// migration — the BlockID/genesis-hash a downstream block header -// chains back to is hash(v0 genesis bytes), and that hash MUST NOT -// rotate when this node reads v0 bytes off disk. -func TestParseV0RoundtripIsBytePreserving(t *testing.T) { - require := require.New(t) - - gen := createTestGenesis(t) - v0Bytes, err := Codec.Marshal(txs.CodecVersionV0, gen) - require.NoError(err) - - parsed, err := Parse(v0Bytes) - require.NoError(err) - - // Re-marshal at the SAME version. Linearcodec is deterministic, - // so the output must be byte-equal to the input. - roundtripBytes, err := Codec.Marshal(txs.CodecVersionV0, parsed) - require.NoError(err) - require.True(bytes.Equal(v0Bytes, roundtripBytes), - "v0 -> Parse -> re-marshal must be byte-preserving (len in=%d, out=%d)", - len(v0Bytes), len(roundtripBytes)) -} - -// TestParseRejectsUnknownVersion locks in that we did not accidentally -// open the door to silent acceptance of bogus prefixes. Only v0 and v1 -// are registered on Codec; anything else must surface as an error. -func TestParseRejectsUnknownVersion(t *testing.T) { - require := require.New(t) - - // Build a syntactically-valid v1 blob, then mutate the prefix. - gen := createTestGenesis(t) - v1Bytes, err := gen.Bytes() - require.NoError(err) - require.GreaterOrEqual(len(v1Bytes), 2) - - bogus := make([]byte, len(v1Bytes)) - copy(bogus, v1Bytes) - binary.BigEndian.PutUint16(bogus[:2], 0xFFFE) - - _, err = Parse(bogus) - require.Error(err, "Parse must reject codec versions outside {v0, v1}") -} diff --git a/vms/platformvm/metrics/block_metrics.go b/vms/platformvm/metrics/block_metrics.go index 7c74be2b1..34b4ae1f5 100644 --- a/vms/platformvm/metrics/block_metrics.go +++ b/vms/platformvm/metrics/block_metrics.go @@ -59,19 +59,20 @@ func (m *blockMetrics) ProposalBlock(b *block.ProposalBlock) error { m.numBlocks.With(metric.Labels{ blkLabel: "proposal", }).Inc() - for _, tx := range b.Transactions { + // Txs() returns the decision txs followed by the proposal tx (last). + for _, tx := range b.Txs() { if err := tx.Unsigned.Visit(m.txMetrics); err != nil { return err } } - return b.Tx.Unsigned.Visit(m.txMetrics) + return nil } func (m *blockMetrics) StandardBlock(b *block.StandardBlock) error { m.numBlocks.With(metric.Labels{ blkLabel: "standard", }).Inc() - for _, tx := range b.Transactions { + for _, tx := range b.Txs() { if err := tx.Unsigned.Visit(m.txMetrics); err != nil { return err } diff --git a/vms/platformvm/metrics/tx_metrics.go b/vms/platformvm/metrics/tx_metrics.go index 25a7786ef..8216de6c8 100644 --- a/vms/platformvm/metrics/tx_metrics.go +++ b/vms/platformvm/metrics/tx_metrics.go @@ -144,20 +144,6 @@ func (m *txMetrics) BaseTx(*txs.BaseTx) error { return nil } -func (m *txMetrics) ConvertNetworkToL1Tx(*txs.ConvertNetworkToL1Tx) error { - m.numTxs.With(metric.Labels{ - txLabel: "convert_net_to_l1", - }).Inc() - return nil -} - -func (m *txMetrics) CreateSovereignL1Tx(*txs.CreateSovereignL1Tx) error { - m.numTxs.With(metric.Labels{ - txLabel: "create_sovereign_l1", - }).Inc() - return nil -} - func (m *txMetrics) RegisterL1ValidatorTx(*txs.RegisterL1ValidatorTx) error { m.numTxs.With(metric.Labels{ txLabel: "register_l1_validator", @@ -200,6 +186,13 @@ func (m *txMetrics) CreateNetworkTx(*txs.CreateNetworkTx) error { return nil } +func (m *txMetrics) ConvertNetworkTx(*txs.ConvertNetworkTx) error { + m.numTxs.With(metric.Labels{ + txLabel: "convert_network", + }).Inc() + return nil +} + func (m *txMetrics) RemoveChainValidatorTx(*txs.RemoveChainValidatorTx) error { m.numTxs.With(metric.Labels{ txLabel: "remove_net_validator", diff --git a/vms/platformvm/network/gossip.go b/vms/platformvm/network/gossip.go index f903470d8..d6a91197e 100644 --- a/vms/platformvm/network/gossip.go +++ b/vms/platformvm/network/gossip.go @@ -63,7 +63,7 @@ func (txMarshaller) MarshalGossip(tx *txs.Tx) ([]byte, error) { } func (txMarshaller) UnmarshalGossip(bytes []byte) (*txs.Tx, error) { - return txs.Parse(txs.Codec, bytes) + return txs.Parse(bytes) } func newGossipMempool( diff --git a/vms/platformvm/network/gossip_test.go b/vms/platformvm/network/gossip_test.go deleted file mode 100644 index 0be37e5d3..000000000 --- a/vms/platformvm/network/gossip_test.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package network - -import ( - "errors" - "testing" - - "github.com/luxfi/log" - "github.com/luxfi/metric" - "github.com/stretchr/testify/require" - - "github.com/luxfi/ids" - "github.com/luxfi/node/vms/platformvm/txs" - - "github.com/luxfi/node/vms/txs/mempool" - - pmempool "github.com/luxfi/node/vms/platformvm/txs/mempool" -) - -var errFoo = errors.New("foo") - -// Add should error if verification errors -func TestGossipMempoolAddVerificationError(t *testing.T) { - require := require.New(t) - - txID := ids.GenerateTestID() - tx := &txs.Tx{ - TxID: txID, - } - - mempool, err := pmempool.New("", metric.NewRegistry()) - require.NoError(err) - txVerifier := testTxVerifier{err: errFoo} - - gossipMempool, err := newGossipMempool( - mempool, - metric.NewRegistry(), - log.NewNoOpLogger(), - txVerifier, - testConfig.ExpectedBloomFilterElements, - testConfig.ExpectedBloomFilterFalsePositiveProbability, - testConfig.MaxBloomFilterFalsePositiveProbability, - ) - require.NoError(err) - - err = gossipMempool.Add(tx) - require.ErrorIs(err, errFoo) - require.False(gossipMempool.bloom.Has(tx)) -} - -// Adding a duplicate to the mempool should return an error -func TestMempoolDuplicate(t *testing.T) { - require := require.New(t) - - testMempool, err := pmempool.New("", metric.NewRegistry()) - require.NoError(err) - txVerifier := testTxVerifier{} - - txID := ids.GenerateTestID() - tx := &txs.Tx{ - Unsigned: &txs.BaseTx{}, - TxID: txID, - } - - require.NoError(testMempool.Add(tx)) - gossipMempool, err := newGossipMempool( - testMempool, - metric.NewRegistry(), - nil, - txVerifier, - testConfig.ExpectedBloomFilterElements, - testConfig.ExpectedBloomFilterFalsePositiveProbability, - testConfig.MaxBloomFilterFalsePositiveProbability, - ) - require.NoError(err) - - err = gossipMempool.Add(tx) - require.ErrorIs(err, mempool.ErrDuplicateTx) - require.False(gossipMempool.bloom.Has(tx)) -} - -// Adding a tx to the mempool should add it to the bloom filter -func TestGossipAddBloomFilter(t *testing.T) { - require := require.New(t) - - txID := ids.GenerateTestID() - tx := &txs.Tx{ - Unsigned: &txs.BaseTx{}, - TxID: txID, - } - - txVerifier := testTxVerifier{} - mempool, err := pmempool.New("", metric.NewRegistry()) - require.NoError(err) - - gossipMempool, err := newGossipMempool( - mempool, - metric.NewRegistry(), - log.NewNoOpLogger(), - txVerifier, - testConfig.ExpectedBloomFilterElements, - testConfig.ExpectedBloomFilterFalsePositiveProbability, - testConfig.MaxBloomFilterFalsePositiveProbability, - ) - require.NoError(err) - - require.NoError(gossipMempool.Add(tx)) - require.True(gossipMempool.bloom.Has(tx)) -} diff --git a/vms/platformvm/network/network_test.go b/vms/platformvm/network/network_test.go deleted file mode 100644 index db8b58d8f..000000000 --- a/vms/platformvm/network/network_test.go +++ /dev/null @@ -1,364 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package network - -import ( - "context" - "errors" - "testing" - "time" - - "github.com/luxfi/metric" - "github.com/luxfi/mock/gomock" - "github.com/stretchr/testify/require" - - consensustest "github.com/luxfi/consensus/test/helpers" - validators "github.com/luxfi/validators" - "github.com/luxfi/crypto/bls" - "github.com/luxfi/ids" - "github.com/luxfi/log" - "github.com/luxfi/math/set" - lux "github.com/luxfi/utxo" - "github.com/luxfi/node/vms/platformvm/config" - "github.com/luxfi/node/vms/platformvm/txs" - "github.com/luxfi/node/vms/txs/mempool" - "github.com/luxfi/warp" - - pmempool "github.com/luxfi/node/vms/platformvm/txs/mempool" -) - -// testSender implements warp.Sender for testing with optional call tracking -type testSender struct { - sendGossipCalled bool -} - -var _ warp.Sender = (*testSender)(nil) - -func (t *testSender) SendRequest(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, requestBytes []byte) error { - return nil -} - -func (t *testSender) SendResponse(ctx context.Context, nodeID ids.NodeID, requestID uint32, responseBytes []byte) error { - return nil -} - -func (t *testSender) SendError(ctx context.Context, nodeID ids.NodeID, requestID uint32, errorCode int32, errorMessage string) error { - return nil -} - -func (t *testSender) SendGossip(ctx context.Context, config warp.SendConfig, gossipBytes []byte) error { - t.sendGossipCalled = true - return nil -} - -var ( - errTest = errors.New("test error") - - testConfig = config.Network{ - MaxValidatorSetStaleness: time.Second, - TargetGossipSize: 1, - PushGossipNumValidators: 1, - PushGossipNumPeers: 0, - PushRegossipNumValidators: 1, - PushRegossipNumPeers: 0, - PushGossipDiscardedCacheSize: 1, - PushGossipMaxRegossipFrequency: time.Second, - PushGossipFrequency: time.Second, - PullGossipPollSize: 1, - PullGossipFrequency: time.Second, - PullGossipThrottlingPeriod: time.Second, - PullGossipThrottlingLimit: 1, - ExpectedBloomFilterElements: 10, - ExpectedBloomFilterFalsePositiveProbability: .1, - MaxBloomFilterFalsePositiveProbability: .5, - } -) - -// mockValidatorState implements validators.State for testing -type mockValidatorState struct { - height uint64 - validators map[ids.NodeID]*validators.GetValidatorOutput -} - -func (m *mockValidatorState) GetMinimumHeight(ctx context.Context) (uint64, error) { - return 0, nil -} - -func (m *mockValidatorState) GetCurrentHeight(ctx context.Context) (uint64, error) { - return m.height, nil -} - -func (m *mockValidatorState) GetValidatorSet( - ctx context.Context, - height uint64, - netID ids.ID, -) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - return m.validators, nil -} - -func (m *mockValidatorState) GetCurrentValidators( - ctx context.Context, - height uint64, - netID ids.ID, -) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - return m.validators, nil -} - -// GetCurrentValidatorOutput represents a validator output -type GetCurrentValidatorOutput struct { - NodeID ids.NodeID - PublicKey *bls.PublicKey - Weight uint64 -} - -func (m *mockValidatorState) GetCurrentValidatorSet( - ctx context.Context, - netID ids.ID, -) (map[ids.ID]*GetCurrentValidatorOutput, uint64, error) { - // Not used in this test - return nil, m.height, nil -} - -func (m *mockValidatorState) GetWarpValidatorSet( - ctx context.Context, - height uint64, - netID ids.ID, -) (*validators.WarpSet, error) { - // Not used in this test - return nil, nil -} - -func (m *mockValidatorState) GetWarpValidatorSets( - ctx context.Context, - heights []uint64, - netIDs []ids.ID, -) (map[ids.ID]map[uint64]*validators.WarpSet, error) { - // Not used in this test - return nil, nil -} - -func (m *mockValidatorState) GetChainID(netID ids.ID) (ids.ID, error) { - return ids.Empty, nil -} - -func (m *mockValidatorState) GetNetworkID(chainID ids.ID) (ids.ID, error) { - return ids.Empty, nil -} - -var _ TxVerifier = (*testTxVerifier)(nil) - -type testTxVerifier struct { - err error -} - -func (t testTxVerifier) VerifyTx(*txs.Tx) error { - return t.err -} - -func TestNetworkIssueTxFromRPC(t *testing.T) { - type test struct { - name string - mempool *pmempool.Mempool - txVerifier testTxVerifier - appSenderFunc func(*gomock.Controller) warp.Sender - tx *txs.Tx - expectedErr error - } - - tests := []test{ - { - name: "mempool has transaction", - mempool: func() *pmempool.Mempool { - mempool, err := pmempool.New("", metric.NewRegistry()) - require.NoError(t, err) - require.NoError(t, mempool.Add(&txs.Tx{Unsigned: &txs.BaseTx{}})) - return mempool - }(), - appSenderFunc: func(ctrl *gomock.Controller) warp.Sender { - return &testSender{} - }, - tx: &txs.Tx{Unsigned: &txs.BaseTx{}}, - expectedErr: mempool.ErrDuplicateTx, - }, - { - name: "transaction marked as dropped in mempool", - mempool: func() *pmempool.Mempool { - mempool, err := pmempool.New("", metric.NewRegistry()) - require.NoError(t, err) - mempool.MarkDropped(ids.Empty, errTest) - return mempool - }(), - appSenderFunc: func(ctrl *gomock.Controller) warp.Sender { - // Shouldn't gossip the tx - return &testSender{} - }, - tx: &txs.Tx{Unsigned: &txs.BaseTx{}}, - expectedErr: errTest, - }, - { - name: "tx dropped", - mempool: func() *pmempool.Mempool { - mempool, err := pmempool.New("", metric.NewRegistry()) - require.NoError(t, err) - return mempool - }(), - txVerifier: testTxVerifier{err: errTest}, - appSenderFunc: func(ctrl *gomock.Controller) warp.Sender { - // Shouldn't gossip the tx - return &testSender{} - }, - tx: &txs.Tx{Unsigned: &txs.BaseTx{}}, - expectedErr: errTest, - }, - { - name: "tx too big", - mempool: func() *pmempool.Mempool { - mempool, err := pmempool.New("", metric.NewRegistry()) - require.NoError(t, err) - return mempool - }(), - appSenderFunc: func(ctrl *gomock.Controller) warp.Sender { - // Shouldn't gossip the tx - return &testSender{} - }, - tx: func() *txs.Tx { - tx := &txs.Tx{Unsigned: &txs.BaseTx{}} - bytes := make([]byte, mempool.MaxTxSize+1) - tx.SetBytes(bytes, bytes) - return tx - }(), - expectedErr: mempool.ErrTxTooLarge, - }, - { - name: "tx conflicts", - mempool: func() *pmempool.Mempool { - mempool, err := pmempool.New("", metric.NewRegistry()) - require.NoError(t, err) - - tx := &txs.Tx{ - Unsigned: &txs.BaseTx{ - BaseTx: lux.BaseTx{ - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{}, - }, - }, - }, - }, - } - - require.NoError(t, mempool.Add(tx)) - return mempool - }(), - appSenderFunc: func(ctrl *gomock.Controller) warp.Sender { - // Shouldn't gossip the tx - return &testSender{} - }, - tx: func() *txs.Tx { - tx := &txs.Tx{ - Unsigned: &txs.BaseTx{ - BaseTx: lux.BaseTx{ - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{}, - }, - }, - }, - }, - TxID: ids.ID{1}, - } - return tx - }(), - expectedErr: mempool.ErrConflictsWithOtherTx, - }, - { - name: "mempool full", - mempool: func() *pmempool.Mempool { - m, err := pmempool.New("", metric.NewRegistry()) - require.NoError(t, err) - - // Fill the mempool to capacity (64 MiB / 2 MiB per tx = 32 txs) - for i := 0; i < 32; i++ { - tx := &txs.Tx{Unsigned: &txs.BaseTx{}} - bytes := make([]byte, mempool.MaxTxSize) - tx.SetBytes(bytes, bytes) - tx.TxID = ids.GenerateTestID() - require.NoError(t, m.Add(tx)) - } - - return m - }(), - appSenderFunc: func(ctrl *gomock.Controller) warp.Sender { - // Shouldn't gossip the tx - return &testSender{} - }, - tx: func() *txs.Tx { - tx := &txs.Tx{Unsigned: &txs.BaseTx{BaseTx: lux.BaseTx{}}} - tx.SetBytes([]byte{1, 2, 3}, []byte{1, 2, 3}) - return tx - }(), - expectedErr: mempool.ErrMempoolFull, - }, - { - name: "happy path", - mempool: func() *pmempool.Mempool { - mempool, err := pmempool.New("", metric.NewRegistry()) - require.NoError(t, err) - return mempool - }(), - appSenderFunc: func(ctrl *gomock.Controller) warp.Sender { - // testSender tracks if SendGossip was called - return &testSender{} - }, - tx: &txs.Tx{Unsigned: &txs.BaseTx{}}, - expectedErr: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - rt := consensustest.Runtime(t, ids.Empty) - // Extract values directly from Runtime - nodeID := rt.NodeID - netID := rt.ChainID - // Use a simple test logger for now - logger := log.NoLog{} - // Create a mock validator state that returns sensible defaults - validatorState := &mockValidatorState{ - height: 100, - validators: map[ids.NodeID]*validators.GetValidatorOutput{ - nodeID: { - NodeID: nodeID, - PublicKey: nil, - Weight: 100, - }, - }, - } - n, err := New( - logger, - nodeID, - netID, - validatorState, - tt.txVerifier, - tt.mempool, - false, - tt.appSenderFunc(ctrl), - nil, - nil, - nil, - metric.NewRegistry(), - testConfig, - ) - require.NoError(err) - - err = n.IssueTxFromRPC(tt.tx) - require.ErrorIs(err, tt.expectedErr) - - require.NoError(n.txPushGossiper.Gossip(context.Background())) - }) - } -} diff --git a/vms/platformvm/network/zap_native_admission.go b/vms/platformvm/network/zap_native_admission.go index eb173af3c..5c67a4d69 100644 --- a/vms/platformvm/network/zap_native_admission.go +++ b/vms/platformvm/network/zap_native_admission.go @@ -78,18 +78,11 @@ var ErrZapNativeNotYetExecutable = errors.New( // // DO NOT add map lookups here in the hot path — IssueTxFromRPC is on // the critical path of every incoming RPC tx. Use a type switch. -func isLegacyTxKindNotYetExecutable(tx *txs.Tx) bool { - if tx == nil || tx.Unsigned == nil { - return false - } - switch tx.Unsigned.(type) { - case *txs.CreateSovereignL1Tx: - // standard_tx_executor.go:635 — body returns - // "CreateSovereignL1Tx executor: not yet implemented (follow-up PR)". - // Gating at admission means the not-yet-implemented error never - // even fires; the tx is refused before the executor sees it. - return true - } +func isLegacyTxKindNotYetExecutable(_ *txs.Tx) bool { + // The former CreateSovereignL1Tx / ConvertNetworkToL1Tx legacy struct + // kinds were folded into CreateNetworkTx, whose executor is + // implemented. No legacy struct kind is not-yet-executable today; the + // zap_native wire gate below still covers future stub kinds. return false } diff --git a/vms/platformvm/network/zap_native_admission_test.go b/vms/platformvm/network/zap_native_admission_test.go deleted file mode 100644 index a13e789ed..000000000 --- a/vms/platformvm/network/zap_native_admission_test.go +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright (C) 2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package network - -import ( - "errors" - "testing" - - "github.com/luxfi/ids" - "github.com/luxfi/node/vms/platformvm/txs" - "github.com/luxfi/proto/zap_native" -) - -// LP-023 Red round 7 R7V5 — admission-gate tests. -// -// Threat model: an adversary submits a tx whose kind has no executor -// body yet. Without the gate, the executor would return -// "not yet implemented" mid-block — by which time the tx has already -// been gossiped, sat in the mempool, and consumed RAM. The gate -// REFUSES at admission so the tx never enters the mempool at all. - -// errSentinel is the inner-verifier sentinel used to confirm the gate -// delegates to the wrapped verifier when admission is allowed. -var errSentinel = errors.New("inner verifier hit") - -// passThroughVerifier returns errSentinel for every tx — used to assert -// that the gate ALWAYS calls into the inner verifier for non-gated kinds. -type passThroughVerifier struct{} - -func (passThroughVerifier) VerifyTx(*txs.Tx) error { return errSentinel } - -// blockEverythingVerifier returns errSentinel for every tx — used as a -// canary to confirm gated kinds never reach the inner verifier. -type blockEverythingVerifier struct{} - -func (blockEverythingVerifier) VerifyTx(*txs.Tx) error { return errSentinel } - -// TestZapNativeAdmissionGate_RejectsCreateSovereignL1Tx pins R7V5: the -// gate must refuse a legacy *txs.CreateSovereignL1Tx with -// ErrZapNativeNotYetExecutable, BEFORE the inner verifier sees the tx. -// The inner verifier here returns errSentinel for everything — if it -// fires we'd see errSentinel, not ErrZapNativeNotYetExecutable. -func TestZapNativeAdmissionGate_RejectsCreateSovereignL1Tx(t *testing.T) { - gate := NewZapNativeAdmissionGate(blockEverythingVerifier{}) - tx := &txs.Tx{Unsigned: &txs.CreateSovereignL1Tx{}} - err := gate.VerifyTx(tx) - if !errors.Is(err, ErrZapNativeNotYetExecutable) { - t.Fatalf( - "gate.VerifyTx(CreateSovereignL1Tx) = %v, want ErrZapNativeNotYetExecutable", - err, - ) - } - if errors.Is(err, errSentinel) { - t.Fatalf("gate let CreateSovereignL1Tx reach the inner verifier: %v", err) - } -} - -// TestZapNativeAdmissionGate_PassThroughLegacyTypes pins the brief: -// "Legacy txs.ConvertNetworkToL1Tx / txs.RegisterL1ValidatorTx (the working -// ones) still pass through". For txs.BaseTx (working executor) and -// txs.RegisterL1ValidatorTx + txs.ConvertNetworkToL1Tx (legacy -// executors at line 639/761), the gate must NOT fire; the inner -// verifier MUST see the tx (we assert via errSentinel). -func TestZapNativeAdmissionGate_PassThroughLegacyTypes(t *testing.T) { - gate := NewZapNativeAdmissionGate(passThroughVerifier{}) - - cases := []struct { - name string - tx *txs.Tx - }{ - {"BaseTx", &txs.Tx{Unsigned: &txs.BaseTx{}}}, - {"RegisterL1ValidatorTx", &txs.Tx{Unsigned: &txs.RegisterL1ValidatorTx{}}}, - {"ConvertNetworkToL1Tx", &txs.Tx{Unsigned: &txs.ConvertNetworkToL1Tx{}}}, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - err := gate.VerifyTx(tc.tx) - if !errors.Is(err, errSentinel) { - t.Fatalf( - "gate.VerifyTx(%s) = %v, want errSentinel (gate must pass to inner)", - tc.name, err, - ) - } - if errors.Is(err, ErrZapNativeNotYetExecutable) { - t.Fatalf( - "gate FALSE-POSITIVE on %s: returned ErrZapNativeNotYetExecutable for a kind with a live executor", - tc.name, - ) - } - }) - } -} - -// TestZapNativeAdmissionGate_RejectsZapWireCreateSovereignL1 exercises -// the future-proof half: a wire buffer that PARSES as zap_native. -// CreateSovereignL1Tx (TxKind=23) must be refused even if the legacy -// Unsigned struct field is something innocuous. The gate inspects the -// signed bytes via zap_native.IsZAPBytes + Wrap*Tx; the kind discriminator -// is the truth source. -func TestZapNativeAdmissionGate_RejectsZapWireCreateSovereignL1(t *testing.T) { - stub := zap_native.OwnerStub{Threshold: 1, Locktime: 0, Address: ids.ShortID{0x42}} - zapTx := zap_native.NewCreateSovereignL1Tx(zap_native.CreateSovereignL1TxInput{ - NetworkID: 1, - Owner: stub, - Validators: []zap_native.ValidatorsListEntry{newWireFriendlyValidator(t)}, - Chains: []zap_native.ChainsListEntry{ - {Name: []byte("evm"), VMID: ids.ID{0xed, 0xed}, FxIDs: []ids.ID{{0x01}}, GenesisData: []byte("g")}, - }, - }) - wireBytes := zapTx.Bytes() - if !zap_native.IsZAPBytes(wireBytes) { - t.Fatalf("baseline: expected wire bytes to be ZAP-magic, got prefix=%q", string(wireBytes[:4])) - } - - tx := &txs.Tx{Unsigned: &txs.BaseTx{}} // innocuous legacy struct - tx.SetBytes(wireBytes, wireBytes) - - gate := NewZapNativeAdmissionGate(blockEverythingVerifier{}) - err := gate.VerifyTx(tx) - if !errors.Is(err, ErrZapNativeNotYetExecutable) { - t.Fatalf( - "gate.VerifyTx(zap-wire CreateSovereignL1) = %v, want ErrZapNativeNotYetExecutable", - err, - ) - } -} - -// TestZapNativeAdmissionGate_RejectsZapWireRegisterL1Validator exercises -// the wire-kind=7 (RegisterL1Validator) gate path. Even though the -// legacy struct executor is live, the zap_native wire path is still -// gated until the wire-aware admission flow lands. -func TestZapNativeAdmissionGate_RejectsZapWireRegisterL1Validator(t *testing.T) { - zapTx := zap_native.NewRegisterL1ValidatorTx( - ids.ID{0xAA}, - [zap_native.BLSPubKeySize]byte{}, - [zap_native.BLSPoPSize]byte{}, - 1_900_000_000, - ids.ID{0xBB}, - ) - wireBytes := zapTx.Bytes() - if !zap_native.IsZAPBytes(wireBytes) { - t.Fatalf("baseline: expected wire bytes to be ZAP-magic, got prefix=%q", string(wireBytes[:4])) - } - - tx := &txs.Tx{Unsigned: &txs.BaseTx{}} - tx.SetBytes(wireBytes, wireBytes) - - gate := NewZapNativeAdmissionGate(blockEverythingVerifier{}) - err := gate.VerifyTx(tx) - if !errors.Is(err, ErrZapNativeNotYetExecutable) { - t.Fatalf( - "gate.VerifyTx(zap-wire RegisterL1Validator) = %v, want ErrZapNativeNotYetExecutable", - err, - ) - } -} - -// TestZapNativeAdmissionGate_RejectsZapWireConvertNetworkToL1 exercises -// the wire-kind=22 (ConvertNetworkToL1) gate path. -func TestZapNativeAdmissionGate_RejectsZapWireConvertNetworkToL1(t *testing.T) { - zapTx := zap_native.NewConvertNetworkToL1Tx(zap_native.ConvertNetworkToL1TxInput{ - NetworkID: 1, - BlockchainID: ids.ID{0x11}, - Chain: ids.ID{0x22}, - ManagerChainID: ids.ID{0x33}, - Address: []byte("0x" + "00"), - Validators: []zap_native.ValidatorsListEntry{newWireFriendlyValidator(t)}, - }) - wireBytes := zapTx.Bytes() - if !zap_native.IsZAPBytes(wireBytes) { - t.Fatalf("baseline: expected wire bytes to be ZAP-magic, got prefix=%q", string(wireBytes[:4])) - } - - tx := &txs.Tx{Unsigned: &txs.BaseTx{}} - tx.SetBytes(wireBytes, wireBytes) - - gate := NewZapNativeAdmissionGate(blockEverythingVerifier{}) - err := gate.VerifyTx(tx) - if !errors.Is(err, ErrZapNativeNotYetExecutable) { - t.Fatalf( - "gate.VerifyTx(zap-wire ConvertNetworkToL1) = %v, want ErrZapNativeNotYetExecutable", - err, - ) - } -} - -// TestZapNativeAdmissionGate_PassThroughNonZapBytes pins the gate's -// non-interference contract: a tx whose Bytes() are NOT ZAP-magic -// (e.g. legacy linearcodec-encoded) and whose Unsigned is a working -// executor type MUST be delegated to the inner verifier unmodified. -func TestZapNativeAdmissionGate_PassThroughNonZapBytes(t *testing.T) { - tx := &txs.Tx{Unsigned: &txs.BaseTx{}} - // Non-ZAP wire bytes — anything that doesn't start with "ZAP\x00". - tx.SetBytes([]byte{0x00, 0x00, 0x00, 0x01, 0xDE, 0xAD, 0xBE, 0xEF}, []byte{0x00, 0x00, 0x00, 0x01, 0xDE, 0xAD, 0xBE, 0xEF}) - - gate := NewZapNativeAdmissionGate(passThroughVerifier{}) - err := gate.VerifyTx(tx) - if !errors.Is(err, errSentinel) { - t.Fatalf("gate.VerifyTx(non-ZAP) = %v, want errSentinel (gate must pass to inner)", err) - } -} - -// TestZapNativeAdmissionGate_NilSafety confirms the gate handles -// edge-case inputs (nil tx, nil Unsigned, empty Bytes) without -// panicking. Defense-in-depth: the gate is on the hot path, so -// it MUST be panic-free on every input. -func TestZapNativeAdmissionGate_NilSafety(t *testing.T) { - gate := NewZapNativeAdmissionGate(passThroughVerifier{}) - - t.Run("nil tx", func(t *testing.T) { - // Inner verifier (passThroughVerifier) returns errSentinel on - // any input including nil; the gate must NOT panic on the way. - err := gate.VerifyTx(nil) - if !errors.Is(err, errSentinel) { - t.Fatalf("gate.VerifyTx(nil) = %v, want errSentinel", err) - } - }) - t.Run("nil Unsigned", func(t *testing.T) { - err := gate.VerifyTx(&txs.Tx{Unsigned: nil}) - if !errors.Is(err, errSentinel) { - t.Fatalf("gate.VerifyTx(nilUnsigned) = %v, want errSentinel", err) - } - }) - t.Run("empty bytes", func(t *testing.T) { - tx := &txs.Tx{Unsigned: &txs.BaseTx{}} - tx.SetBytes(nil, nil) - err := gate.VerifyTx(tx) - if !errors.Is(err, errSentinel) { - t.Fatalf("gate.VerifyTx(emptyBytes) = %v, want errSentinel", err) - } - }) -} - -// newWireFriendlyValidator returns a ValidatorsListEntry whose -// constructor-time BLS fields are present (even if zeroed). The R7V5 -// admission gate does NOT verify BLS PoP — that's R7V7 / Verify()'s -// job — so this minimal entry is sufficient for the gate test. -func newWireFriendlyValidator(t *testing.T) zap_native.ValidatorsListEntry { - t.Helper() - return zap_native.ValidatorsListEntry{ - NodeID: ids.NodeID{0x77}, - Weight: 1_000_000, - BLSPubKey: [zap_native.BLSPubKeySize]byte{}, - BLSPoP: [zap_native.BLSPoPSize]byte{}, - RegistrationExpiry: 1_900_000_000, - } -} diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index 705aaaad9..6c26afe76 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -17,32 +17,32 @@ import ( jsonv1 "github.com/go-json-experiment/json/v1" "github.com/luxfi/log" - validators "github.com/luxfi/validators" + apitypes "github.com/luxfi/api/types" "github.com/luxfi/constants" "github.com/luxfi/crypto/bls" "github.com/luxfi/database" "github.com/luxfi/formatting" "github.com/luxfi/ids" + safemath "github.com/luxfi/math" "github.com/luxfi/math/set" - apitypes "github.com/luxfi/api/types" "github.com/luxfi/node/cache/lru" "github.com/luxfi/node/vms/components/gas" - lux "github.com/luxfi/utxo" + "github.com/luxfi/node/vms/platformvm/fx" "github.com/luxfi/node/vms/platformvm/reward" + "github.com/luxfi/node/vms/platformvm/signer" "github.com/luxfi/node/vms/platformvm/stakeable" "github.com/luxfi/node/vms/platformvm/state" "github.com/luxfi/node/vms/platformvm/status" "github.com/luxfi/node/vms/platformvm/txs" "github.com/luxfi/node/vms/platformvm/validators/fee" "github.com/luxfi/node/vms/platformvm/warp/message" - safemath "github.com/luxfi/math" - "github.com/luxfi/node/vms/platformvm/fx" - "github.com/luxfi/node/vms/platformvm/signer" + lux "github.com/luxfi/utxo" "github.com/luxfi/utxo/secp256k1fx" + validators "github.com/luxfi/validators" "github.com/luxfi/vm/types" - platformapitypes "github.com/luxfi/node/vms/platformvm/api" avajson "github.com/luxfi/node/utils/json" + platformapitypes "github.com/luxfi/node/vms/platformvm/api" ) const ( @@ -700,7 +700,7 @@ func (s *Service) GetStakingAssetID(_ *http.Request, args *GetStakingAssetIDArgs ) } - response.AssetID = transformNet.AssetID + response.AssetID = transformNet.AssetID() return nil } @@ -739,7 +739,7 @@ func (s *Service) loadStakerTxAttributes(txID ids.ID) (*stakerAttributes, error) case txs.ValidatorTx: var pop *signer.ProofOfPossession if staker, ok := stakerTx.(*txs.AddPermissionlessValidatorTx); ok { - if s, ok := staker.Signer.(*signer.ProofOfPossession); ok { + if s, ok := staker.Signer().(*signer.ProofOfPossession); ok { pop = s } } @@ -1073,10 +1073,11 @@ func (s *Service) GetL1Validator(r *http.Request, args *GetL1ValidatorArgs, repl } func (s *Service) convertL1ValidatorToAPI(vdr state.L1Validator) (platformapitypes.APIL1Validator, error) { - var remainingBalanceOwner message.PChainOwner - if _, err := txs.Codec.Unmarshal(vdr.RemainingBalanceOwner, &remainingBalanceOwner); err != nil { + remBalOwner, err := txs.UnmarshalOwner(vdr.RemainingBalanceOwner) + if err != nil { return platformapitypes.APIL1Validator{}, fmt.Errorf("failed unmarshalling remaining balance owner: %w", err) } + remainingBalanceOwner := message.PChainOwner{Threshold: remBalOwner.Threshold, Addresses: remBalOwner.Addrs} remainingBalanceAPIOwner, err := s.getAPIOwner(&secp256k1fx.OutputOwners{ Threshold: remainingBalanceOwner.Threshold, Addrs: remainingBalanceOwner.Addresses, @@ -1085,10 +1086,11 @@ func (s *Service) convertL1ValidatorToAPI(vdr state.L1Validator) (platformapityp return platformapitypes.APIL1Validator{}, fmt.Errorf("failed formatting remaining balance owner: %w", err) } - var deactivationOwner message.PChainOwner - if _, err := txs.Codec.Unmarshal(vdr.DeactivationOwner, &deactivationOwner); err != nil { + deacOwner, err := txs.UnmarshalOwner(vdr.DeactivationOwner) + if err != nil { return platformapitypes.APIL1Validator{}, fmt.Errorf("failed unmarshalling deactivation owner: %w", err) } + deactivationOwner := message.PChainOwner{Threshold: deacOwner.Threshold, Addresses: deacOwner.Addrs} deactivationAPIOwner, err := s.getAPIOwner(&secp256k1fx.OutputOwners{ Threshold: deactivationOwner.Threshold, Addrs: deactivationOwner.Addresses, @@ -1269,7 +1271,7 @@ func (s *Service) nodeValidates(blockchainID ids.ID) bool { return false } - _, isValidator := s.vm.Validators.GetValidator(chain.ChainID, s.vm.nodeID) + _, isValidator := s.vm.Validators.GetValidator(chain.ChainID(), s.vm.nodeID) return isValidator } @@ -1427,10 +1429,10 @@ func (s *Service) GetBlockchains(_ *http.Request, _ *struct{}, response *GetBloc return fmt.Errorf("expected tx type *txs.CreateChainTx but got %T", chainTx.Unsigned) } response.Blockchains = append(response.Blockchains, APIBlockchain{ - ID: chainID, - Name: chain.BlockchainName, + ID: chainID, + Name: chain.BlockchainName(), ChainID: netID, - VMID: chain.VMID, + VMID: chain.VMID(), }) } } @@ -1446,10 +1448,10 @@ func (s *Service) GetBlockchains(_ *http.Request, _ *struct{}, response *GetBloc return fmt.Errorf("expected tx type *txs.CreateChainTx but got %T", chainTx.Unsigned) } response.Blockchains = append(response.Blockchains, APIBlockchain{ - ID: chainID, - Name: chain.BlockchainName, + ID: chainID, + Name: chain.BlockchainName(), ChainID: constants.PrimaryNetworkID, - VMID: chain.VMID, + VMID: chain.VMID(), }) } @@ -1466,7 +1468,7 @@ func (s *Service) IssueTx(_ *http.Request, args *apitypes.FormattedTx, response if err != nil { return fmt.Errorf("problem decoding transaction: %w", err) } - tx, err := txs.Parse(txs.Codec, txBytes) + tx, err := txs.Parse(txBytes) if err != nil { return fmt.Errorf("couldn't parse tx: %w", err) } @@ -1676,7 +1678,15 @@ func (s *Service) GetStake(_ *http.Request, args *GetStakeArgs, response *GetSta response.Staked = response.Stakeds[s.vm.utxoAssetID] response.Outputs = make([]string, len(stakedOuts)) for i, output := range stakedOuts { - bytes, err := txs.Codec.Marshal(txs.CodecVersion, output) + // Surface each staked output as native UTXO wire bytes — the one + // canonical standalone-output encoding (no codec). The deprecated + // GetStake client treats each entry as an opaque byte string. + utxo := &lux.UTXO{ + UTXOID: lux.UTXOID{TxID: ids.Empty, OutputIndex: uint32(i)}, + Asset: output.Asset, + Out: output.Out, + } + bytes, err := utxo.WireBytes() if err != nil { return fmt.Errorf("couldn't serialize output %s: %w", output.ID, err) } @@ -1735,8 +1745,8 @@ func (s *Service) GetMinStake(_ *http.Request, args *GetMinStakeArgs, reply *Get ) } - reply.MinValidatorStake = avajson.Uint64(transformNet.MinValidatorStake) - reply.MinDelegatorStake = avajson.Uint64(transformNet.MinDelegatorStake) + reply.MinValidatorStake = avajson.Uint64(transformNet.MinValidatorStake()) + reply.MinDelegatorStake = avajson.Uint64(transformNet.MinDelegatorStake()) return nil } @@ -1839,8 +1849,8 @@ func (s *Service) GetTimestamp(_ *http.Request, _ *struct{}, reply *GetTimestamp // GetValidatorsAtArgs is the response from GetValidatorsAt type GetValidatorsAtArgs struct { - Height platformapitypes.Height `json:"height"` - ChainID ids.ID `json:"netID"` + Height platformapitypes.Height `json:"height"` + ChainID ids.ID `json:"netID"` } type jsonGetValidatorOutput struct { diff --git a/vms/platformvm/state/codec_helpers.go b/vms/platformvm/state/codec_helpers.go deleted file mode 100644 index 4990bec93..000000000 --- a/vms/platformvm/state/codec_helpers.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package state - -import ( - "sync" - - "github.com/luxfi/log" - "github.com/luxfi/node/vms/pcodecs" -) - -// probe is a flat struct chosen so its codec walk is the empty walk -// (size 0) and therefore Marshal cannot fail for any reason other than -// the requested version being absent. This is what makes the probe a -// reliable boolean test of "is version V registered on c". -type probe struct{} - -// multiVersionUnmarshal is the canonical state-side codec read entry -// point. It wraps codec.Manager.Unmarshal with a one-shot post-condition -// check: if the codec is missing the v0 read-only slot map, a structured -// warning is logged the FIRST time it is observed, so a canary boot of -// a v0-on-disk database surfaces every missing-version slot in a single -// log scrape rather than failing piecemeal across iterations. -// -// This is a soft guard, not a hard one — successful Unmarshal of a v1 -// payload through a v1-only codec still succeeds. The warning fires -// when (a) a single-version codec is observed AND (b) the failure mode -// it implies (v0 prefix → ErrUnknownVersion) would have triggered on a -// pre-codec-v1 database row. The intent is to make the v1.28.x -// codec-migration convergence iteration-light: future patches can grep -// for this log line in canary stderr and see every remaining gap. -// -// Implementation note: we identify a codec as "missing the v0 slot" by -// attempting to Marshal a one-byte probe at CodecVersionV0. codec.Manager -// returns ErrUnknownVersion if and only if version 0 is not registered -// in its map. The probe is cheap (a 10-byte Marshal at most) and only -// happens on the first observation of each distinct codec pointer. -func multiVersionUnmarshal(c pcodecs.Manager, b []byte, dest interface{}) (uint16, error) { - checkMultiVersion(c) - return c.Unmarshal(b, dest) -} - -var ( - multiVersionProbeOnce sync.Map // codec.Manager -> struct{} -) - -func checkMultiVersion(c pcodecs.Manager) { - if _, observed := multiVersionProbeOnce.LoadOrStore(c, struct{}{}); observed { - return - } - // Probe: can this codec Marshal at v0? An empty struct has zero - // codec-walk size, so the only way Marshal can fail is if version 0 - // itself is not registered — which is exactly the failure mode we - // want the warning to fire on. - if _, err := c.Marshal(0, probe{}); err != nil { - log.Warn("state-side codec is single-version; reads of v0-prefixed bytes will fail", - "err", err, - "hint", "register the v0 read-only slot map on this codec.Manager", - ) - } -} diff --git a/vms/platformvm/state/codec_helpers_test.go b/vms/platformvm/state/codec_helpers_test.go deleted file mode 100644 index 1c69bd890..000000000 --- a/vms/platformvm/state/codec_helpers_test.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package state - -import ( - "encoding/binary" - "math" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/luxfi/node/vms/components/gas" - "github.com/luxfi/node/vms/pcodecs" - "github.com/luxfi/node/vms/platformvm/block" -) - -// TestMultiVersionUnmarshalAcceptsBothVersions documents that the -// defensive helper is a transparent pass-through for the canonical -// codec — neither v0 nor v1 reads should observe any behavioral -// difference from raw codec.Manager.Unmarshal once the codec has both -// versions registered. -func TestMultiVersionUnmarshalAcceptsBothVersions(t *testing.T) { - require := require.New(t) - - original := gas.State{Capacity: 100, Excess: 200} - - for _, version := range []uint16{block.CodecVersionV0, block.CodecVersionV1} { - bytesAtVersion, err := block.GenesisCodec.Marshal(version, original) - require.NoError(err) - - var decoded gas.State - gotVersion, err := multiVersionUnmarshal(block.GenesisCodec, bytesAtVersion, &decoded) - require.NoError(err) - require.Equal(version, gotVersion) - require.Equal(original, decoded) - } -} - -// TestMultiVersionUnmarshalSurfacesSingleVersionCodec is the test for -// the soft warning path. We build a single-version codec.Manager (only -// v1 registered) and call multiVersionUnmarshal on it. The Unmarshal -// itself MUST still succeed for v1 bytes (the helper is a soft guard, -// not a hard one), but the post-condition probe MUST observe the -// missing v0 slot. -// -// We can't directly intercept log.Warn (it's a global logger), so we -// instead exercise the underlying probe deterministically: marshaling -// the empty `probe` struct at v0 on a single-version codec must return -// pcodecs.ErrUnknownVersion. This is the exact condition that triggers -// the warning emission. -func TestMultiVersionUnmarshalSurfacesSingleVersionCodec(t *testing.T) { - require := require.New(t) - - // Build a single-version codec by hand — only v1 registered. - singleC := pcodecs.NewLinearCodec() - singleVersion := pcodecs.NewDefaultManager() - require.NoError(singleVersion.RegisterCodec(block.CodecVersionV1, singleC)) - - // Confirm v1 Unmarshal still succeeds (helper is non-blocking). - v1Bytes, err := singleVersion.Marshal(block.CodecVersionV1, gas.State{Capacity: 1, Excess: 2}) - require.NoError(err) - - var decoded gas.State - _, err = multiVersionUnmarshal(singleVersion, v1Bytes, &decoded) - require.NoError(err, "multiVersionUnmarshal must not block successful v1 reads on a single-version codec") - require.Equal(uint16(2), uint16(decoded.Excess)) - - // Confirm the underlying detection mechanism would fire: v0 - // Marshal on this codec returns ErrUnknownVersion, which is what - // the post-condition check picks up. - _, err = singleVersion.Marshal(block.CodecVersionV0, probe{}) - require.ErrorIs(err, pcodecs.ErrUnknownVersion, - "single-version codec must surface ErrUnknownVersion on the v0 probe — this is the warning trigger") -} - -// TestMultiVersionUnmarshalSurfacesSingleVersionCodec_Idempotent -// documents that the warning probe fires AT MOST once per codec -// pointer, so a long-running canary boot that hits dozens of state -// reads through the same single-version codec produces a single log -// line, not a flood. -func TestMultiVersionUnmarshalSurfacesSingleVersionCodec_Idempotent(t *testing.T) { - require := require.New(t) - - singleC := pcodecs.NewLinearCodec() - singleVersion := pcodecs.NewDefaultManager() - require.NoError(singleVersion.RegisterCodec(block.CodecVersionV1, singleC)) - - v1Bytes, err := singleVersion.Marshal(block.CodecVersionV1, gas.State{Capacity: 5, Excess: 6}) - require.NoError(err) - - // First call: warning probe runs (sync.Map records the codec). - var d1 gas.State - _, err = multiVersionUnmarshal(singleVersion, v1Bytes, &d1) - require.NoError(err) - - // Confirm the sync.Map remembers this codec — the LoadOrStore - // returns observed=true on subsequent calls. - _, observedAgain := multiVersionProbeOnce.LoadOrStore(singleVersion, struct{}{}) - require.True(observedAgain, "probe must record the codec on first observation") - - // Second call: warning probe SKIPS the marshal probe entirely. - // Unmarshal still succeeds. - var d2 gas.State - _, err = multiVersionUnmarshal(singleVersion, v1Bytes, &d2) - require.NoError(err) -} - -// TestMultiVersionUnmarshalRejectsUnknownVersionUpstream confirms the -// helper does NOT swallow pcodecs.ErrUnknownVersion — if the input bytes -// carry a prefix that the codec does not have registered (e.g. a future -// codec v2 prefix on a v0+v1 codec), the original error surfaces so the -// caller can decide whether to treat it as a corruption or a soft skip. -func TestMultiVersionUnmarshalRejectsUnknownVersionUpstream(t *testing.T) { - require := require.New(t) - - bogus := make([]byte, 18) - binary.BigEndian.PutUint16(bogus[:2], math.MaxUint16) - - var sink gas.State - _, err := multiVersionUnmarshal(block.GenesisCodec, bogus, &sink) - require.ErrorIs(err, pcodecs.ErrUnknownVersion) -} - -// TestBlockGenesisCodecPassesMultiVersionProbe pins the post-v1.28.2 -// invariant: the canonical state-side codec MUST register both v0 and -// v1. If a future refactor accidentally drops a version slot, this test -// trips immediately and the canary doesn't have to. -func TestBlockGenesisCodecPassesMultiVersionProbe(t *testing.T) { - require := require.New(t) - - for _, version := range []uint16{block.CodecVersionV0, block.CodecVersionV1} { - _, err := block.GenesisCodec.Marshal(version, probe{}) - require.NoErrorf(err, "block.GenesisCodec must register version %d", version) - } -} diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index 569d55188..204ecc8ef 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -476,26 +476,27 @@ func (d *diff) AddNetTransformation(transformNetTxIntf *txs.Tx) { transformNetTx := transformNetTxIntf.Unsigned.(*txs.TransformChainTx) if d.transformedNets == nil { d.transformedNets = map[ids.ID]*txs.Tx{ - transformNetTx.Chain: transformNetTxIntf, + transformNetTx.Chain(): transformNetTxIntf, } } else { - d.transformedNets[transformNetTx.Chain] = transformNetTxIntf + d.transformedNets[transformNetTx.Chain()] = transformNetTxIntf } } func (d *diff) AddChain(createChainTx *txs.Tx) { tx := createChainTx.Unsigned.(*txs.CreateChainTx) + netID := tx.ChainID() if d.addedChains == nil { d.addedChains = map[ids.ID][]*txs.Tx{ - tx.ChainID: {createChainTx}, + netID: {createChainTx}, } } else { - d.addedChains[tx.ChainID] = append(d.addedChains[tx.ChainID], createChainTx) + d.addedChains[netID] = append(d.addedChains[netID], createChainTx) } // Register chain name for uniqueness (case-insensitive) - if tx.BlockchainName != "" { - nameLower := strings.ToLower(tx.BlockchainName) + if tx.BlockchainName() != "" { + nameLower := strings.ToLower(tx.BlockchainName()) chainID := createChainTx.ID() if d.addedChainNames == nil { d.addedChainNames = map[string]ids.ID{ diff --git a/vms/platformvm/state/l1_validator.go b/vms/platformvm/state/l1_validator.go index 056eab3f2..5f4e0c7a7 100644 --- a/vms/platformvm/state/l1_validator.go +++ b/vms/platformvm/state/l1_validator.go @@ -15,7 +15,6 @@ import ( "github.com/luxfi/database" "github.com/luxfi/ids" "github.com/luxfi/node/cache" - "github.com/luxfi/node/vms/platformvm/block" "github.com/luxfi/utils" "github.com/luxfi/container/iterator" "github.com/luxfi/math" @@ -219,7 +218,7 @@ func getL1Validator( l1Validator := L1Validator{ ValidationID: validationID, } - if _, err := multiVersionUnmarshal(block.GenesisCodec, bytes, &l1Validator); err != nil { + if err := parseL1Validator(bytes, &l1Validator); err != nil { return L1Validator{}, fmt.Errorf("failed to unmarshal L1 validator: %w", err) } @@ -232,7 +231,7 @@ func putL1Validator( cache cache.Cacher[ids.ID, maybe.Maybe[L1Validator]], l1Validator L1Validator, ) error { - bytes, err := block.GenesisCodec.Marshal(block.CodecVersion, l1Validator) + bytes, err := marshalL1Validator(l1Validator) if err != nil { return fmt.Errorf("failed to marshal L1 validator: %w", err) } diff --git a/vms/platformvm/state/l1_validator_test.go b/vms/platformvm/state/l1_validator_test.go index eb1b19276..4164e35f6 100644 --- a/vms/platformvm/state/l1_validator_test.go +++ b/vms/platformvm/state/l1_validator_test.go @@ -15,7 +15,6 @@ import ( "github.com/luxfi/ids" "github.com/luxfi/node/cache" "github.com/luxfi/node/cache/lru" - "github.com/luxfi/node/vms/platformvm/block" "github.com/luxfi/utils" "github.com/luxfi/container/maybe" ) @@ -197,7 +196,7 @@ func TestPutL1Validator(t *testing.T) { db = memdb.New() cache = lru.NewCache[ids.ID, maybe.Maybe[L1Validator]](10) ) - expectedL1ValidatorBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, l1Validator) + expectedL1ValidatorBytes, err := marshalL1Validator(l1Validator) require.NoError(err) require.NoError(putL1Validator(db, cache, l1Validator)) diff --git a/vms/platformvm/state/staker_test.go b/vms/platformvm/state/staker_test.go deleted file mode 100644 index 29dabf23b..000000000 --- a/vms/platformvm/state/staker_test.go +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package state - -import ( - "errors" - "testing" - "time" - - "github.com/luxfi/mock/gomock" - "github.com/stretchr/testify/require" - - "github.com/luxfi/crypto/bls/signer/localsigner" - "github.com/luxfi/ids" - "github.com/luxfi/node/vms/platformvm/txs" - "github.com/luxfi/node/vms/platformvm/signer" - "github.com/luxfi/node/vms/platformvm/signer/signermock" -) - -var errCustom = errors.New("custom") - -func TestStakerLess(t *testing.T) { - tests := []struct { - name string - left *Staker - right *Staker - less bool - }{ - { - name: "left time < right time", - left: &Staker{ - TxID: ids.ID([32]byte{}), - NextTime: time.Unix(0, 0), - Priority: txs.PrimaryNetworkValidatorCurrentPriority, - }, - right: &Staker{ - TxID: ids.ID([32]byte{}), - NextTime: time.Unix(1, 0), - Priority: txs.PrimaryNetworkValidatorCurrentPriority, - }, - less: true, - }, - { - name: "left time > right time", - left: &Staker{ - TxID: ids.ID([32]byte{}), - NextTime: time.Unix(1, 0), - Priority: txs.PrimaryNetworkValidatorCurrentPriority, - }, - right: &Staker{ - TxID: ids.ID([32]byte{}), - NextTime: time.Unix(0, 0), - Priority: txs.PrimaryNetworkValidatorCurrentPriority, - }, - less: false, - }, - { - name: "left priority < right priority", - left: &Staker{ - TxID: ids.ID([32]byte{}), - NextTime: time.Unix(0, 0), - Priority: txs.PrimaryNetworkDelegatorLegacyPendingPriority, - }, - right: &Staker{ - TxID: ids.ID([32]byte{}), - NextTime: time.Unix(0, 0), - Priority: txs.PrimaryNetworkValidatorPendingPriority, - }, - less: true, - }, - { - name: "left priority > right priority", - left: &Staker{ - TxID: ids.ID([32]byte{}), - NextTime: time.Unix(0, 0), - Priority: txs.PrimaryNetworkValidatorPendingPriority, - }, - right: &Staker{ - TxID: ids.ID([32]byte{}), - NextTime: time.Unix(0, 0), - Priority: txs.PrimaryNetworkDelegatorLegacyPendingPriority, - }, - less: false, - }, - { - name: "left txID < right txID", - left: &Staker{ - TxID: ids.ID([32]byte{0}), - NextTime: time.Unix(0, 0), - Priority: txs.PrimaryNetworkValidatorPendingPriority, - }, - right: &Staker{ - TxID: ids.ID([32]byte{1}), - NextTime: time.Unix(0, 0), - Priority: txs.PrimaryNetworkValidatorPendingPriority, - }, - less: true, - }, - { - name: "left txID > right txID", - left: &Staker{ - TxID: ids.ID([32]byte{1}), - NextTime: time.Unix(0, 0), - Priority: txs.PrimaryNetworkValidatorPendingPriority, - }, - right: &Staker{ - TxID: ids.ID([32]byte{0}), - NextTime: time.Unix(0, 0), - Priority: txs.PrimaryNetworkValidatorPendingPriority, - }, - less: false, - }, - { - name: "equal", - left: &Staker{ - TxID: ids.ID([32]byte{}), - NextTime: time.Unix(0, 0), - Priority: txs.PrimaryNetworkValidatorCurrentPriority, - }, - right: &Staker{ - TxID: ids.ID([32]byte{}), - NextTime: time.Unix(0, 0), - Priority: txs.PrimaryNetworkValidatorCurrentPriority, - }, - less: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require.Equal(t, test.less, test.left.Less(test.right)) - }) - } -} - -func TestNewCurrentStaker(t *testing.T) { - require := require.New(t) - stakerTx := generateStakerTx(require) - - txID := ids.GenerateTestID() - startTime := stakerTx.StartTime().Add(2 * time.Hour) - potentialReward := uint64(12345) - - staker, err := NewCurrentStaker(txID, stakerTx, startTime, potentialReward) - require.NoError(err) - publicKey, isNil, err := stakerTx.PublicKey() - require.NoError(err) - require.True(isNil) - require.Equal(&Staker{ - TxID: txID, - NodeID: stakerTx.NodeID(), - PublicKey: publicKey, - ChainID: stakerTx.ChainID(), - Weight: stakerTx.Weight(), - StartTime: startTime, - EndTime: stakerTx.EndTime(), - PotentialReward: potentialReward, - NextTime: stakerTx.EndTime(), - Priority: stakerTx.CurrentPriority(), - }, staker) - - ctrl := gomock.NewController(t) - signer := signermock.NewSigner(ctrl) - signer.EXPECT().Verify().Return(errCustom) - stakerTx.Signer = signer - - _, err = NewCurrentStaker(txID, stakerTx, startTime, potentialReward) - require.ErrorIs(err, errCustom) -} - -func TestNewPendingStaker(t *testing.T) { - require := require.New(t) - - stakerTx := generateStakerTx(require) - - txID := ids.GenerateTestID() - staker, err := NewPendingStaker(txID, stakerTx) - require.NoError(err) - publicKey, isNil, err := stakerTx.PublicKey() - require.NoError(err) - require.True(isNil) - require.Equal(&Staker{ - TxID: txID, - NodeID: stakerTx.NodeID(), - PublicKey: publicKey, - ChainID: stakerTx.ChainID(), - Weight: stakerTx.Weight(), - StartTime: stakerTx.StartTime(), - EndTime: stakerTx.EndTime(), - NextTime: stakerTx.StartTime(), - Priority: stakerTx.PendingPriority(), - }, staker) - - ctrl := gomock.NewController(t) - signer := signermock.NewSigner(ctrl) - signer.EXPECT().Verify().Return(errCustom) - stakerTx.Signer = signer - - _, err = NewPendingStaker(txID, stakerTx) - require.ErrorIs(err, errCustom) -} - -func generateStakerTx(require *require.Assertions) *txs.AddPermissionlessValidatorTx { - nodeID := ids.GenerateTestNodeID() - sk, err := localsigner.New() - require.NoError(err) - pop, err := signer.NewProofOfPossession(sk) - require.NoError(err) - chainID := ids.GenerateTestID() - weight := uint64(12345) - startTime := time.Now().Truncate(time.Second) - endTime := startTime.Add(time.Hour) - - return &txs.AddPermissionlessValidatorTx{ - Validator: txs.Validator{ - NodeID: nodeID, - Start: uint64(startTime.Unix()), - End: uint64(endTime.Unix()), - Wght: weight, - }, - Signer: pop, - Chain: chainID, - } -} diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index bd917c790..c1df7c553 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -246,33 +246,9 @@ type State interface { Close() error } -// Prior to https://github.com/luxfi/node/pull/1719, blocks were -// stored as a map from blkID to stateBlk. Nodes synced prior to this PR may -// still have blocks partially stored using this legacy format. -// -// stateBlk is the legacy block storage format from before PR #1719. -// Retained for backward compatibility with pre-v1.14.x databases. -type stateBlk struct { - Bytes []byte `serialize:"true"` - Status uint32 `serialize:"true"` -} - -// RegisterStateBlockType registers the stateBlk type with the given codec. -// This is needed for backward compatibility with old block storage format. -func RegisterStateBlockType(targetCodec pcodecs.Registry) error { - return targetCodec.RegisterType(&stateBlk{}) -} - -// Initialize the stateBlk type registration with the block codecs. -// This must be called before using parseStoredBlock for backward compatibility. -func init() { - // We need to register stateBlk with block.GenesisCodec so that - // parseStoredBlock can deserialize legacy block storage format. - // Since state imports block (no import cycle), we can register directly. - if err := block.RegisterGenesisType(&stateBlk{}); err != nil { - panic(err) - } -} +// Blocks are stored as their native-ZAP wire bytes (struct-is-wire) keyed by +// blockID; parseStoredBlock wraps them via block.Parse. There is no legacy +// stateBlk envelope and no codec registry under native ZAP. /* * VMDB diff --git a/vms/platformvm/state/state_blocks.go b/vms/platformvm/state/state_blocks.go index f1846c725..dcb056e37 100644 --- a/vms/platformvm/state/state_blocks.go +++ b/vms/platformvm/state/state_blocks.go @@ -100,27 +100,16 @@ func (s *state) writeBlocks() error { return nil } -// parseStoredBlock returns the block and whether it is a legacy [stateBlk]. -// Invariant: blkBytes is safe to parse with blocks.GenesisCodec. -// Retained for backward compatibility with pre-v1.14.x databases. +// parseStoredBlock returns the block stored under a blockDB value. Blocks are +// stored as their native-ZAP wire bytes (struct-is-wire); block.Parse wraps +// them zero-copy. The second return (legacy-format flag) is always false — +// re-genesis leaves no pre-native block rows on disk. func parseStoredBlock(blkBytes []byte) (block.Block, bool, error) { - // Attempt to parse as blocks.Block - blk, err := block.Parse(block.GenesisCodec, blkBytes) - if err == nil { - return blk, false, nil - } - - // Fallback to [stateBlk] using our legacy codec - blkState := stateBlk{} - if _, err := multiVersionUnmarshal(block.GenesisCodec, blkBytes, &blkState); err != nil { - // If we can't unmarshal as stateBlk, this might not be a block at all - // (could be an index entry or other data in the blockDB) - // Return the original parse error + blk, err := block.Parse(blkBytes) + if err != nil { return nil, false, err } - - blk, err = block.Parse(block.GenesisCodec, blkState.Bytes) - return blk, true, err + return blk, false, nil } func (s *state) ReindexBlocks(lock sync.Locker, log log.Logger) error { diff --git a/vms/platformvm/state/state_chains.go b/vms/platformvm/state/state_chains.go index 1d969c7d3..6bffc249c 100644 --- a/vms/platformvm/state/state_chains.go +++ b/vms/platformvm/state/state_chains.go @@ -11,7 +11,6 @@ import ( "github.com/luxfi/database/linkeddb" "github.com/luxfi/database/prefixdb" "github.com/luxfi/ids" - "github.com/luxfi/node/vms/platformvm/block" "github.com/luxfi/node/vms/platformvm/fx" "github.com/luxfi/node/vms/platformvm/txs" ) @@ -62,8 +61,8 @@ func (s *state) GetNetOwner(netID ids.ID) (fx.Owner, error) { ownerBytes, err := s.chainOwnerDB.Get(netID[:]) if err == nil { - var owner fx.Owner - if _, err := multiVersionUnmarshal(block.GenesisCodec, ownerBytes, &owner); err != nil { + owner, err := parseOwner(ownerBytes) + if err != nil { return nil, err } s.chainOwnerCache.Put(netID, fxOwnerAndSize{ @@ -89,8 +88,9 @@ func (s *state) GetNetOwner(netID ids.ID) (fx.Owner, error) { return nil, fmt.Errorf("%q %w", netID, errIsNotNet) } - s.SetNetOwner(netID, network.Owner) - return network.Owner, nil + owner := network.Owner() + s.SetNetOwner(netID, owner) + return owner, nil } func (s *state) SetNetOwner(netID ids.ID, owner fx.Owner) { @@ -112,8 +112,8 @@ func (s *state) GetNetToL1Conversion(chainID ids.ID) (NetToL1Conversion, error) return NetToL1Conversion{}, err } - var c NetToL1Conversion - if _, err := multiVersionUnmarshal(block.GenesisCodec, bytes, &c); err != nil { + c, err := parseConversion(bytes) + if err != nil { return NetToL1Conversion{}, err } s.chainToL1ConversionCache.Put(chainID, c) @@ -155,7 +155,7 @@ func (s *state) GetNetTransformation(chainID ids.ID) (*txs.Tx, error) { func (s *state) AddNetTransformation(transformNetTxIntf *txs.Tx) { transformNetTx := transformNetTxIntf.Unsigned.(*txs.TransformChainTx) - s.transformedNets[transformNetTx.Chain] = transformNetTxIntf + s.transformedNets[transformNetTx.Chain()] = transformNetTxIntf } func (s *state) GetChains(netID ids.ID) ([]*txs.Tx, error) { @@ -189,7 +189,7 @@ func (s *state) GetChains(netID ids.ID) ([]*txs.Tx, error) { func (s *state) AddChain(createChainTxIntf *txs.Tx) { createChainTx := createChainTxIntf.Unsigned.(*txs.CreateChainTx) - netID := createChainTx.ChainID + netID := createChainTx.ChainID() s.addedChains[netID] = append(s.addedChains[netID], createChainTxIntf) if chains, cached := s.chainCache.Get(netID); cached { chains = append(chains, createChainTxIntf) @@ -197,8 +197,8 @@ func (s *state) AddChain(createChainTxIntf *txs.Tx) { } // Register chain name for uniqueness tracking (case-insensitive) - if createChainTx.BlockchainName != "" { - nameLower := strings.ToLower(createChainTx.BlockchainName) + if createChainTx.BlockchainName() != "" { + nameLower := strings.ToLower(createChainTx.BlockchainName()) chainID := createChainTxIntf.ID() s.addedChainNames[nameLower] = chainID s.chainNameCache.Put(nameLower, chainID) @@ -265,7 +265,7 @@ func (s *state) writeNetOwners() error { for chainID, owner := range s.chainOwners { delete(s.chainOwners, chainID) - ownerBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, &owner) + ownerBytes, err := marshalOwner(owner) if err != nil { return fmt.Errorf("failed to marshal net owner: %w", err) } @@ -286,7 +286,7 @@ func (s *state) writeNetToL1Conversions() error { for chainID, c := range s.chainToL1Conversions { delete(s.chainToL1Conversions, chainID) - bytes, err := block.GenesisCodec.Marshal(block.CodecVersion, &c) + bytes, err := marshalConversion(c) if err != nil { return fmt.Errorf("failed to marshal chain conversion: %w", err) } diff --git a/vms/platformvm/state/state_commit.go b/vms/platformvm/state/state_commit.go index 32fe77ec4..dc5d8894b 100644 --- a/vms/platformvm/state/state_commit.go +++ b/vms/platformvm/state/state_commit.go @@ -278,7 +278,7 @@ func (s *state) syncGenesis(genesisBlk block.Block, genesis *genesis.Genesis) er // Ensure all chains that the genesis bytes say to create have the right // network ID networkID := s.rt.NetworkID - if false && unsignedChain.NetworkID != networkID { // Temporarily disabled for genesis compatibility + if false && unsignedChain.NetworkID() != networkID { // Temporarily disabled for genesis compatibility return lux.ErrWrongNetworkID } @@ -342,9 +342,9 @@ func (s *state) migrateNewGenesisChains(genesisBytes []byte) error { continue } log.Info("migrating new genesis chain into state", - "name", unsignedChain.BlockchainName, + "name", unsignedChain.BlockchainName(), "chainID", chain.ID(), - "vmID", unsignedChain.VMID, + "vmID", unsignedChain.VMID(), ) s.AddChain(chain) s.AddTx(chain, status.Committed) diff --git a/vms/platformvm/state/state_metadata.go b/vms/platformvm/state/state_metadata.go index a7140d4fe..26d0a830f 100644 --- a/vms/platformvm/state/state_metadata.go +++ b/vms/platformvm/state/state_metadata.go @@ -11,7 +11,6 @@ import ( "github.com/luxfi/database" "github.com/luxfi/ids" "github.com/luxfi/node/vms/components/gas" - "github.com/luxfi/node/vms/platformvm/block" ) func (s *state) GetTimestamp() time.Time { @@ -173,8 +172,7 @@ func (s *state) loadMetadata() error { } indexedHeights := &heightRange{} - _, err = multiVersionUnmarshal(block.GenesisCodec, indexedHeightsBytes, indexedHeights) - if err != nil { + if err := parseHeightRange(indexedHeightsBytes, indexedHeights); err != nil { return err } @@ -233,7 +231,7 @@ func (s *state) writeMetadata() error { s.persistedLastAccepted = currentLastAccepted } if s.indexedHeights != nil { - indexedHeightsBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, s.indexedHeights) + indexedHeightsBytes, err := marshalHeightRange(s.indexedHeights) if err != nil { return err } @@ -253,7 +251,7 @@ func isInitialized(db database.KeyValueReader) (bool, error) { } func putFeeState(db database.KeyValueWriter, feeState gas.State) error { - feeStateBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, feeState) + feeStateBytes, err := marshalFeeState(feeState) if err != nil { return err } @@ -269,8 +267,8 @@ func getFeeState(db database.KeyValueReader) (gas.State, error) { return gas.State{}, err } - var feeState gas.State - if _, err := multiVersionUnmarshal(block.GenesisCodec, feeStateBytes, &feeState); err != nil { + feeState, err := parseFeeState(feeStateBytes) + if err != nil { return gas.State{}, err } return feeState, nil diff --git a/vms/platformvm/state/state_txs.go b/vms/platformvm/state/state_txs.go index 3590e5991..4faa07330 100644 --- a/vms/platformvm/state/state_txs.go +++ b/vms/platformvm/state/state_txs.go @@ -35,12 +35,12 @@ func (s *state) GetTx(txID ids.ID) (*txs.Tx, status.Status, error) { return nil, status.Unknown, err } - stx := txBytesAndStatus{} - if _, err := txs.GenesisCodec.Unmarshal(txBytes, &stx); err != nil { + stx, err := parseTxStatus(txBytes) + if err != nil { return nil, status.Unknown, err } - tx, err := txs.Parse(txs.GenesisCodec, stx.Tx) + tx, err := txs.Parse(stx.Tx) if err != nil { return nil, status.Unknown, err } @@ -101,9 +101,10 @@ func (s *state) writeTXs() error { Status: txStatus.status, } - // Note that we're serializing a [txBytesAndStatus] here, not a - // *txs.Tx, so we don't use [txs.Codec]. - txBytes, err := txs.GenesisCodec.Marshal(txs.CodecVersion, &stx) + // We serialize the [txBytesAndStatus] envelope natively (see + // statewire.go). The embedded tx keeps its own self-delimiting + // signed bytes so its TxID is preserved on re-parse. + txBytes, err := marshalTxStatus(stx) if err != nil { return fmt.Errorf("failed to serialize tx: %w", err) } diff --git a/vms/platformvm/state/state_v0_codec_test.go b/vms/platformvm/state/state_v0_codec_test.go deleted file mode 100644 index 5dcff509d..000000000 --- a/vms/platformvm/state/state_v0_codec_test.go +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package state - -import ( - "bytes" - "encoding/binary" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/luxfi/ids" - - "github.com/luxfi/node/vms/components/gas" - "github.com/luxfi/node/vms/pcodecs" - "github.com/luxfi/node/vms/platformvm/block" -) - -// TestStateV0FeeStateReadable simulates the exact code path that -// failed on the v1.28.1 testnet-canary: -// -// loadMetadata → getFeeState → block.GenesisCodec.Unmarshal(v0Bytes, &feeState) -// → pcodecs.ErrUnknownVersion (unknown codec version) -// -// The fixture is byte-for-byte what v1.23.x wrote to singletonDB at the -// FeeStateKey: 2 bytes wire prefix (0x0000) + 8 bytes Capacity + 8 bytes -// Excess. -func TestStateV0FeeStateReadable(t *testing.T) { - require := require.New(t) - - const ( - capacity = uint64(123_456_789) - excess = uint64(987_654_321) - ) - - // Construct the exact byte shape this binary writes. ZAP-native is - // little-endian for both the version prefix and uint64 fields. - v0Bytes := make([]byte, 18) - binary.LittleEndian.PutUint16(v0Bytes[:2], 0) - binary.LittleEndian.PutUint64(v0Bytes[2:10], capacity) - binary.LittleEndian.PutUint64(v0Bytes[10:18], excess) - - // Cross-check the fixture: GenesisCodec.Marshal at v0 should - // produce the same shape, otherwise the fixture is wrong and the - // test would mask the bug. - expected := gas.State{Capacity: gas.Gas(capacity), Excess: gas.Gas(excess)} - marshaled, err := block.GenesisCodec.Marshal(block.CodecVersionV0, expected) - require.NoError(err) - require.True(bytes.Equal(v0Bytes, marshaled), - "fixture must match the v0 wire shape (fixture=%x marshaled=%x)", v0Bytes, marshaled) - - // The bug surface: this Unmarshal failed pre-v1.28.2 because - // block.GenesisCodec was v1-only. - var decoded gas.State - version, err := block.GenesisCodec.Unmarshal(v0Bytes, &decoded) - require.NoError(err) - require.Equal(uint16(block.CodecVersionV0), version) - require.Equal(expected, decoded) -} - -// TestStateV0HeightRangeReadable is the regression guard for -// state/state_metadata.go:176 — the HeightsIndexedKey read path that -// also flowed through block.GenesisCodec.Unmarshal. -func TestStateV0HeightRangeReadable(t *testing.T) { - require := require.New(t) - - expected := heightRange{ - LowerBound: 100, - UpperBound: 200, - } - - v0Bytes, err := block.GenesisCodec.Marshal(block.CodecVersionV0, expected) - require.NoError(err) - require.Equal(uint16(block.CodecVersionV0), binary.LittleEndian.Uint16(v0Bytes[:2])) - - var decoded heightRange - _, err = block.GenesisCodec.Unmarshal(v0Bytes, &decoded) - require.NoError(err) - require.Equal(expected, decoded) -} - -// TestStateV0L1ValidatorReadable is the regression guard for -// state/l1_validator.go:222 and state/state_validators.go:{239,535} -// — the L1 validator value read paths. L1Validator is the largest -// state-side struct (PublicKey, RemainingBalanceOwner, -// DeactivationOwner blobs) and is the type loadActiveL1Validators + -// initValidatorSets iterate over on boot, so a regression here would -// surface at the same boot stage as the original feeState bug. -func TestStateV0L1ValidatorReadable(t *testing.T) { - require := require.New(t) - - expected := L1Validator{ - ValidationID: ids.GenerateTestID(), - ChainID: ids.GenerateTestID(), - NodeID: ids.GenerateTestNodeID(), - PublicKey: bytes.Repeat([]byte{0xAB}, 48), - RemainingBalanceOwner: []byte{0x01, 0x02}, - DeactivationOwner: []byte{0x03, 0x04}, - StartTime: 1700000000, - Weight: 1_000_000, - MinNonce: 0, - EndAccumulatedFee: 2_000_000, - } - - v0Bytes, err := block.GenesisCodec.Marshal(block.CodecVersionV0, expected) - require.NoError(err) - - // ValidationID is NOT serialized (it's the DB key), so the - // decoded copy will have a zero ValidationID. Match the production - // pattern (see state_validators.go:535+: decode then assign - // ValidationID from the DB key). - expected.ValidationID = ids.Empty - var decoded L1Validator - _, err = block.GenesisCodec.Unmarshal(v0Bytes, &decoded) - require.NoError(err) - require.Equal(expected, decoded) -} - -// TestStateV0NetToL1ConversionReadable is the regression guard for -// state/state_chains.go:116 — the NetToL1Conversion read path. -func TestStateV0NetToL1ConversionReadable(t *testing.T) { - require := require.New(t) - - expected := NetToL1Conversion{ - ConversionID: ids.GenerateTestID(), - ChainID: ids.GenerateTestID(), - Addr: []byte{0xDE, 0xAD, 0xBE, 0xEF}, - } - - v0Bytes, err := block.GenesisCodec.Marshal(block.CodecVersionV0, expected) - require.NoError(err) - - var decoded NetToL1Conversion - _, err = block.GenesisCodec.Unmarshal(v0Bytes, &decoded) - require.NoError(err) - require.Equal(expected, decoded) -} - -// TestStateV0LegacyStateBlkReadable is the regression guard for -// state/state_blocks.go:115 — the legacy stateBlk fallback read path -// for pre-PR-1719 block storage format. -func TestStateV0LegacyStateBlkReadable(t *testing.T) { - require := require.New(t) - - expected := stateBlk{ - Bytes: []byte("legacy block payload bytes"), - Status: 4, - } - - v0Bytes, err := block.GenesisCodec.Marshal(block.CodecVersionV0, expected) - require.NoError(err) - - var decoded stateBlk - _, err = block.GenesisCodec.Unmarshal(v0Bytes, &decoded) - require.NoError(err) - require.Equal(expected, decoded) -} - -// TestStateV1FeeStateReadable is the no-regression guard for the -// canonical write path. v1.28.2 still writes feeState at v1; this must -// remain readable. -func TestStateV1FeeStateReadable(t *testing.T) { - require := require.New(t) - - expected := gas.State{Capacity: 100, Excess: 200} - v1Bytes, err := block.GenesisCodec.Marshal(block.CodecVersion, expected) - require.NoError(err) - require.Equal(uint16(block.CodecVersionV1), binary.LittleEndian.Uint16(v1Bytes[:2])) - - var decoded gas.State - _, err = block.GenesisCodec.Unmarshal(v1Bytes, &decoded) - require.NoError(err) - require.Equal(expected, decoded) -} - -// TestStateMetadataCodecCarriesBothVersions documents that -// MetadataCodec (used by metadata_validator.go / metadata_delegator.go) -// has been multi-version since its introduction. This is a structural -// guard rail — if a future patch ever drops the v0 slot from -// MetadataCodec, this test trips. -func TestStateMetadataCodecCarriesBothVersions(t *testing.T) { - require := require.New(t) - - expected := preDelegateeRewardMetadata{ - UpDuration: 42, - LastUpdated: 1700000000, - PotentialReward: 1_000_000, - } - - for _, version := range []uint16{CodecVersion0, CodecVersion1} { - bytesAtVersion, err := MetadataCodec.Marshal(version, &expected) - require.NoErrorf(err, "MetadataCodec must register version %d for Marshal", version) - - var decoded preDelegateeRewardMetadata - gotVersion, err := MetadataCodec.Unmarshal(bytesAtVersion, &decoded) - require.NoErrorf(err, "MetadataCodec must register version %d for Unmarshal", version) - require.Equal(version, gotVersion) - require.Equal(expected, decoded) - } -} - -// TestStateBootFromV0SingletonDB simulates a P-Chain boot from a -// v1.23.x-written singletonDB — the exact end-to-end shape that broke -// the testnet canary at "loadMetadata: feeState: unknown codec -// version". We write the v0-shaped feeState bytes and the v0-shaped -// heightRange bytes directly into the DB row(s) loadMetadata would -// observe and assert getFeeState + the heightRange unmarshal both -// succeed. -// -// This does NOT call loadMetadata itself (that requires a full state -// object), but it pins both private-package helpers that loadMetadata -// invokes, which is exactly where the canary failure surfaced. -func TestStateBootFromV0SingletonDB(t *testing.T) { - require := require.New(t) - - // getFeeState path. - { - original := gas.State{Capacity: 5_000_000, Excess: 12345} - v0Bytes, err := block.GenesisCodec.Marshal(block.CodecVersionV0, original) - require.NoError(err) - - // The exact code under getFeeState(): - var feeState gas.State - _, err = block.GenesisCodec.Unmarshal(v0Bytes, &feeState) - require.NoError(err, "getFeeState must accept v0-prefixed bytes; this was the canary failure") - require.Equal(original, feeState) - } - - // loadMetadata heightRange path. - { - original := &heightRange{LowerBound: 1000, UpperBound: 2000} - v0Bytes, err := block.GenesisCodec.Marshal(block.CodecVersionV0, original) - require.NoError(err) - - indexedHeights := &heightRange{} - _, err = block.GenesisCodec.Unmarshal(v0Bytes, indexedHeights) - require.NoError(err) - require.Equal(original, indexedHeights) - } -} - -// TestStateCodecRejectsUnknownVersion locks in that the multi-version -// extension did NOT silently open the door to bogus prefixes. -func TestStateCodecRejectsUnknownVersion(t *testing.T) { - require := require.New(t) - - bogus := make([]byte, 18) - binary.LittleEndian.PutUint16(bogus[:2], 0xFFFD) - - var sink gas.State - _, err := block.GenesisCodec.Unmarshal(bogus, &sink) - require.ErrorIs(err, pcodecs.ErrUnknownVersion) -} diff --git a/vms/platformvm/state/state_validators.go b/vms/platformvm/state/state_validators.go index 57cf923c2..b1eb3129f 100644 --- a/vms/platformvm/state/state_validators.go +++ b/vms/platformvm/state/state_validators.go @@ -16,7 +16,6 @@ import ( "github.com/luxfi/database/linkeddb" "github.com/luxfi/ids" "github.com/luxfi/log" - "github.com/luxfi/node/vms/platformvm/block" "github.com/luxfi/node/vms/platformvm/txs" ) @@ -236,7 +235,7 @@ func (s *state) loadActiveL1Validators() error { ValidationID: validationID, } ) - if _, err := multiVersionUnmarshal(block.GenesisCodec, value, &l1Validator); err != nil { + if err := parseL1Validator(value, &l1Validator); err != nil { return fmt.Errorf("failed to unmarshal L1 validator: %w", err) } @@ -532,7 +531,7 @@ func (s *state) initValidatorSets() error { } var l1Validator L1Validator - if _, err := multiVersionUnmarshal(block.GenesisCodec, inactiveIt.Value(), &l1Validator); err != nil { + if err := parseL1Validator(inactiveIt.Value(), &l1Validator); err != nil { return fmt.Errorf("failed to unmarshal inactive L1 validator: %w", err) } l1Validator.ValidationID = validationID diff --git a/vms/platformvm/state/statewire.go b/vms/platformvm/state/statewire.go new file mode 100644 index 000000000..819ab0632 --- /dev/null +++ b/vms/platformvm/state/statewire.go @@ -0,0 +1,311 @@ +// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package state + +// Native-ZAP wire for the P-Chain state DB VALUE types that used to flow +// through the deleted reflection codec (block.GenesisCodec). Each stored +// value type gets one struct-is-wire encoder pair (marshalX/parseX) built +// directly on github.com/luxfi/zap generic object/list primitives — no +// codec, no version prefix, no slot registry. Re-genesis means the on-disk +// format is free to change, so these layouts are the canonical ones. +// +// Every stored FIELD of every value is preserved; only the framing changed +// (reflection walk → fixed-offset zap object). Reader accessors are +// zero-copy views into the DB value buffer; []byte fields are copied out so +// a cached value never aliases the transient DB read. + +import ( + "fmt" + + "github.com/luxfi/ids" + "github.com/luxfi/node/vms/components/gas" + "github.com/luxfi/node/vms/platformvm/fx" + "github.com/luxfi/node/vms/platformvm/status" + "github.com/luxfi/utxo/secp256k1fx" + "github.com/luxfi/zap" +) + +const ( + stAddrStride = 20 // ids.ShortIDLen + stIDLen = 32 // ids.IDLen + stNodeIDLen = 20 // ids.NodeIDLen +) + +// stReadID reads a 32-byte id at the given object offset. +func stReadID(o zap.Object, off int) ids.ID { + var id ids.ID + copy(id[:], o.BytesFixedSlice(off, stIDLen)) + return id +} + +// stCopyBytes returns a fresh copy of a zap byte view (nil for empty), so a +// long-lived value never aliases the transient DB read buffer. +func stCopyBytes(v []byte) []byte { + if len(v) == 0 { + return nil + } + return append([]byte(nil), v...) +} + +// ---- OutputOwners (fx.Owner == *secp256k1fx.OutputOwners) ---- +// +// object (size 20): threshold u32 @0, locktime u64 @4, addr-list ptr @12. +// addr list: 20-byte-stride ids.ShortID entries. + +const ( + ownThreshold = 0 + ownLocktime = 4 + ownAddrs = 12 + ownSize = 20 +) + +func marshalOwner(o fx.Owner) ([]byte, error) { + oo, err := asOutputOwners(o) + if err != nil { + return nil, err + } + b := zap.NewBuilder(zap.HeaderSize + ownSize + len(oo.Addrs)*stAddrStride) + + var addrOff, addrCount int + if len(oo.Addrs) > 0 { + lb := b.StartList(stAddrStride) + for i := range oo.Addrs { + lb.AddBytes(oo.Addrs[i][:]) + } + // AddBytes counts bytes, not elements — use the real element count. + addrOff, _ = lb.Finish() + addrCount = len(oo.Addrs) + } + + ob := b.StartObject(ownSize) + ob.SetUint32(ownThreshold, oo.Threshold) + ob.SetUint64(ownLocktime, oo.Locktime) + ob.SetList(ownAddrs, addrOff, addrCount) + ob.FinishAsRoot() + return b.Finish(), nil +} + +func parseOwner(b []byte) (fx.Owner, error) { + msg, err := zap.Parse(b) + if err != nil { + return nil, err + } + obj := msg.Root() + arr := obj.ListStride(ownAddrs, stAddrStride) + n := arr.Len() + var addrs []ids.ShortID + if n > 0 { + addrs = make([]ids.ShortID, n) + for i := 0; i < n; i++ { + e := arr.Object(i, stAddrStride) + for j := 0; j < stAddrStride; j++ { + addrs[i][j] = e.Uint8(j) + } + } + } + return &secp256k1fx.OutputOwners{ + Locktime: obj.Uint64(ownLocktime), + Threshold: obj.Uint32(ownThreshold), + Addrs: addrs, + }, nil +} + +func asOutputOwners(o fx.Owner) (*secp256k1fx.OutputOwners, error) { + switch oo := o.(type) { + case *secp256k1fx.OutputOwners: + return oo, nil + case *fx.OutputOwnersWrapper: + return oo.OutputOwners, nil + default: + return nil, fmt.Errorf("state: unsupported owner %T (want *secp256k1fx.OutputOwners)", o) + } +} + +// ---- L1Validator (LP-77 validator VALUE; ValidationID is the DB key, not +// serialized). Fixed object; the three owner/key blobs are opaque []byte. ---- +// +// object (size 108): +// ChainID 32B @ 0 +// NodeID 20B @ 32 +// StartTime u64 @ 52 +// Weight u64 @ 60 +// MinNonce u64 @ 68 +// EndAccumulatedFee u64 @ 76 +// PublicKey bytes ptr @ 84 +// RemainingBalanceOwner bytes ptr @ 92 +// DeactivationOwner bytes ptr @ 100 + +const ( + l1vChainID = 0 + l1vNodeID = 32 + l1vStartTime = 52 + l1vWeight = 60 + l1vMinNonce = 68 + l1vEndAccrued = 76 + l1vPublicKey = 84 + l1vRemainingOwn = 92 + l1vDeactivateOwn = 100 + l1vSize = 108 +) + +func marshalL1Validator(v L1Validator) ([]byte, error) { + b := zap.NewBuilder(zap.HeaderSize + l1vSize + + len(v.PublicKey) + len(v.RemainingBalanceOwner) + len(v.DeactivationOwner)) + ob := b.StartObject(l1vSize) + ob.SetBytesFixed(l1vChainID, v.ChainID[:]) + ob.SetBytesFixed(l1vNodeID, v.NodeID[:]) + ob.SetUint64(l1vStartTime, v.StartTime) + ob.SetUint64(l1vWeight, v.Weight) + ob.SetUint64(l1vMinNonce, v.MinNonce) + ob.SetUint64(l1vEndAccrued, v.EndAccumulatedFee) + ob.SetBytes(l1vPublicKey, v.PublicKey) + ob.SetBytes(l1vRemainingOwn, v.RemainingBalanceOwner) + ob.SetBytes(l1vDeactivateOwn, v.DeactivationOwner) + ob.FinishAsRoot() + return b.Finish(), nil +} + +// parseL1Validator fills v's serialized fields from b. v.ValidationID (the DB +// key) is left untouched — the caller sets it. +func parseL1Validator(b []byte, v *L1Validator) error { + msg, err := zap.Parse(b) + if err != nil { + return err + } + obj := msg.Root() + v.ChainID = stReadID(obj, l1vChainID) + copy(v.NodeID[:], obj.BytesFixedSlice(l1vNodeID, stNodeIDLen)) + v.StartTime = obj.Uint64(l1vStartTime) + v.Weight = obj.Uint64(l1vWeight) + v.MinNonce = obj.Uint64(l1vMinNonce) + v.EndAccumulatedFee = obj.Uint64(l1vEndAccrued) + v.PublicKey = stCopyBytes(obj.Bytes(l1vPublicKey)) + v.RemainingBalanceOwner = stCopyBytes(obj.Bytes(l1vRemainingOwn)) + v.DeactivationOwner = stCopyBytes(obj.Bytes(l1vDeactivateOwn)) + return nil +} + +// ---- feeState (gas.State: Capacity + Excess, both gas.Gas == uint64) ---- + +const ( + feeCapacity = 0 + feeExcess = 8 + feeSize = 16 +) + +func marshalFeeState(s gas.State) ([]byte, error) { + b := zap.NewBuilder(zap.HeaderSize + feeSize) + ob := b.StartObject(feeSize) + ob.SetUint64(feeCapacity, uint64(s.Capacity)) + ob.SetUint64(feeExcess, uint64(s.Excess)) + ob.FinishAsRoot() + return b.Finish(), nil +} + +func parseFeeState(b []byte) (gas.State, error) { + msg, err := zap.Parse(b) + if err != nil { + return gas.State{}, err + } + obj := msg.Root() + return gas.State{ + Capacity: gas.Gas(obj.Uint64(feeCapacity)), + Excess: gas.Gas(obj.Uint64(feeExcess)), + }, nil +} + +// ---- heightRange (LowerBound + UpperBound, both uint64) ---- + +const ( + hrLower = 0 + hrUpper = 8 + hrSize = 16 +) + +func marshalHeightRange(h *heightRange) ([]byte, error) { + b := zap.NewBuilder(zap.HeaderSize + hrSize) + ob := b.StartObject(hrSize) + ob.SetUint64(hrLower, h.LowerBound) + ob.SetUint64(hrUpper, h.UpperBound) + ob.FinishAsRoot() + return b.Finish(), nil +} + +func parseHeightRange(b []byte, h *heightRange) error { + msg, err := zap.Parse(b) + if err != nil { + return err + } + obj := msg.Root() + h.LowerBound = obj.Uint64(hrLower) + h.UpperBound = obj.Uint64(hrUpper) + return nil +} + +// ---- NetToL1Conversion (ConversionID + ChainID + Addr bytes) ---- +// +// object (size 72): ConversionID 32B @0, ChainID 32B @32, Addr bytes ptr @64. + +const ( + convConversionID = 0 + convChainID = 32 + convAddr = 64 + convSize = 72 +) + +func marshalConversion(c NetToL1Conversion) ([]byte, error) { + b := zap.NewBuilder(zap.HeaderSize + convSize + len(c.Addr)) + ob := b.StartObject(convSize) + ob.SetBytesFixed(convConversionID, c.ConversionID[:]) + ob.SetBytesFixed(convChainID, c.ChainID[:]) + ob.SetBytes(convAddr, c.Addr) + ob.FinishAsRoot() + return b.Finish(), nil +} + +func parseConversion(b []byte) (NetToL1Conversion, error) { + msg, err := zap.Parse(b) + if err != nil { + return NetToL1Conversion{}, err + } + obj := msg.Root() + return NetToL1Conversion{ + ConversionID: stReadID(obj, convConversionID), + ChainID: stReadID(obj, convChainID), + Addr: stCopyBytes(obj.Bytes(convAddr)), + }, nil +} + +// ---- txBytesAndStatus (stored tx: signed bytes + acceptance status) ---- +// +// object (size 16): Status u32 @0, Tx bytes ptr @8. The signed tx bytes are +// stored opaque — the tx is a self-delimiting zap buffer re-parsed via +// txs.Parse, so its TxID is preserved with no re-encoding. + +const ( + txsStatus = 0 + txsTx = 8 + txsSize = 16 +) + +func marshalTxStatus(stx txBytesAndStatus) ([]byte, error) { + b := zap.NewBuilder(zap.HeaderSize + txsSize + len(stx.Tx)) + ob := b.StartObject(txsSize) + ob.SetUint32(txsStatus, uint32(stx.Status)) + ob.SetBytes(txsTx, stx.Tx) + ob.FinishAsRoot() + return b.Finish(), nil +} + +func parseTxStatus(b []byte) (txBytesAndStatus, error) { + msg, err := zap.Parse(b) + if err != nil { + return txBytesAndStatus{}, err + } + obj := msg.Root() + return txBytesAndStatus{ + Tx: stCopyBytes(obj.Bytes(txsTx)), + Status: status.Status(obj.Uint32(txsStatus)), + }, nil +} diff --git a/vms/platformvm/state/statewire_test.go b/vms/platformvm/state/statewire_test.go new file mode 100644 index 000000000..ac3e2625e --- /dev/null +++ b/vms/platformvm/state/statewire_test.go @@ -0,0 +1,119 @@ +// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package state + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/luxfi/ids" + "github.com/luxfi/node/vms/components/gas" + "github.com/luxfi/utxo/secp256k1fx" +) + +func TestStatewire_OutputOwnersRoundTrip(t *testing.T) { + cases := []*secp256k1fx.OutputOwners{ + {Threshold: 0, Locktime: 0, Addrs: nil}, + {Threshold: 1, Locktime: 42, Addrs: []ids.ShortID{{0x01}}}, + { + Threshold: 2, + Locktime: 0xDEADBEEF, + Addrs: []ids.ShortID{ + {0x01, 0x02, 0x03}, + {0xFF, 0xEE, 0xDD, 0xCC}, + ids.GenerateTestShortID(), + }, + }, + } + for _, want := range cases { + b, err := marshalOwner(want) + require.NoError(t, err) + + got, err := parseOwner(b) + require.NoError(t, err) + + oo, ok := got.(*secp256k1fx.OutputOwners) + require.True(t, ok) + require.Equal(t, want.Threshold, oo.Threshold) + require.Equal(t, want.Locktime, oo.Locktime) + require.Equal(t, want.Addrs, oo.Addrs) + } +} + +func TestStatewire_L1ValidatorRoundTrip(t *testing.T) { + full := L1Validator{ + // ValidationID is the DB key — NOT serialized; parse leaves it zero. + ValidationID: ids.GenerateTestID(), + ChainID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + PublicKey: []byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE}, + RemainingBalanceOwner: []byte{0x11, 0x22, 0x33}, + DeactivationOwner: []byte{0x44}, + StartTime: 1_700_000_000, + Weight: 9_999, + MinNonce: 7, + EndAccumulatedFee: 123_456_789, + } + // A validator with empty opaque blobs (inactive/degenerate shapes). + empty := L1Validator{ + ChainID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + StartTime: 1, + Weight: 0, + MinNonce: 0, + } + + for _, want := range []L1Validator{full, empty} { + b, err := marshalL1Validator(want) + require.NoError(t, err) + + var got L1Validator + require.NoError(t, parseL1Validator(b, &got)) + + // ValidationID is not on the wire; compare the serialized fields only. + want.ValidationID = ids.Empty + require.Equal(t, want, got) + } +} + +func TestStatewire_FeeStateRoundTrip(t *testing.T) { + for _, want := range []gas.State{ + {}, + {Capacity: 1, Excess: 2}, + {Capacity: 0xFFFFFFFFFFFFFFFF, Excess: 0x0123456789ABCDEF}, + } { + b, err := marshalFeeState(want) + require.NoError(t, err) + + got, err := parseFeeState(b) + require.NoError(t, err) + require.Equal(t, want, got) + } +} + +func TestStatewire_HeightRangeRoundTrip(t *testing.T) { + want := &heightRange{LowerBound: 10, UpperBound: 987_654_321} + b, err := marshalHeightRange(want) + require.NoError(t, err) + + var got heightRange + require.NoError(t, parseHeightRange(b, &got)) + require.Equal(t, *want, got) +} + +func TestStatewire_ConversionRoundTrip(t *testing.T) { + cases := []NetToL1Conversion{ + {ConversionID: ids.GenerateTestID(), ChainID: ids.GenerateTestID(), Addr: nil}, + {ConversionID: ids.GenerateTestID(), ChainID: ids.GenerateTestID(), Addr: []byte{0x01, 0x02, 0x03, 0x04}}, + } + for _, want := range cases { + b, err := marshalConversion(want) + require.NoError(t, err) + + got, err := parseConversion(b) + require.NoError(t, err) + require.Equal(t, want, got) + } +} diff --git a/vms/platformvm/txs/add_chain_validator_test.go b/vms/platformvm/txs/add_chain_validator_test.go deleted file mode 100644 index d9bc56628..000000000 --- a/vms/platformvm/txs/add_chain_validator_test.go +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txs - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/luxfi/runtime" - - "github.com/luxfi/constants" - "github.com/luxfi/crypto/secp256k1" - "github.com/luxfi/ids" - lux "github.com/luxfi/utxo" - "github.com/luxfi/timer/mockable" - "github.com/luxfi/utxo/secp256k1fx" -) - -// Note: Consider refactoring to use table tests for better test organization -func TestAddChainValidatorTxSyntacticVerify(t *testing.T) { - require := require.New(t) - clk := mockable.Clock{} - nodeID := ids.GenerateTestNodeID() - testChainID := ids.GenerateTestID() // Use a test chain ID instead of empty - rt := &runtime.Runtime{ - NetworkID: constants.UnitTestID, - - ChainID: testChainID, - NodeID: nodeID, - } - signers := [][]*secp256k1.PrivateKey{preFundedKeys} - - var ( - stx *Tx - addNetValidatorTx *AddChainValidatorTx - err error - ) - - // Case : signed tx is nil - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, ErrNilSignedTx) - - // Case : unsigned tx is nil - err = addNetValidatorTx.SyntacticVerify(rt) - require.ErrorIs(err, ErrNilTx) - - validatorWeight := uint64(2022) - netID := ids.ID{'s', 'u', 'b', 'n', 'e', 't', 'I', 'D'} - inputs := []*lux.TransferableInput{{ - UTXOID: lux.UTXOID{ - TxID: ids.ID{'t', 'x', 'I', 'D'}, - OutputIndex: 2, - }, - Asset: lux.Asset{ID: ids.ID{'a', 's', 's', 'e', 't'}}, - In: &secp256k1fx.TransferInput{ - Amt: uint64(5678), - Input: secp256k1fx.Input{SigIndices: []uint32{0}}, - }, - }} - outputs := []*lux.TransferableOutput{{ - Asset: lux.Asset{ID: ids.ID{'a', 's', 's', 'e', 't'}}, - Out: &secp256k1fx.TransferOutput{ - Amt: uint64(1234), - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{preFundedKeys[0].Address()}, - }, - }, - }} - chainAuth := &secp256k1fx.Input{ - SigIndices: []uint32{0, 1}, - } - addNetValidatorTx = &AddChainValidatorTx{ - BaseTx: BaseTx{BaseTx: lux.BaseTx{ - NetworkID: rt.NetworkID, - BlockchainID: rt.ChainID, - Ins: inputs, - Outs: outputs, - Memo: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - }}, - ChainValidator: ChainValidator{ - Validator: Validator{ - NodeID: nodeID, - Start: uint64(clk.Time().Unix()), - End: uint64(clk.Time().Add(time.Hour).Unix()), - Wght: validatorWeight, - }, - Chain: netID, - }, - ChainAuth: chainAuth, - } - - // Case: valid tx - stx, err = NewSigned(addNetValidatorTx, Codec, signers) - require.NoError(err) - require.NoError(stx.SyntacticVerify(rt)) - - // Case: Wrong network ID - addNetValidatorTx.SyntacticallyVerified = false - addNetValidatorTx.NetworkID++ - stx, err = NewSigned(addNetValidatorTx, Codec, signers) - require.NoError(err) - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, lux.ErrWrongNetworkID) - addNetValidatorTx.NetworkID-- - - // Case: Specifies primary network ChainID - addNetValidatorTx.SyntacticallyVerified = false - addNetValidatorTx.Chain = ids.Empty - stx, err = NewSigned(addNetValidatorTx, Codec, signers) - require.NoError(err) - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, errAddPrimaryNetworkValidator) - addNetValidatorTx.Chain = netID - - // Case: No weight - addNetValidatorTx.SyntacticallyVerified = false - addNetValidatorTx.Wght = 0 - stx, err = NewSigned(addNetValidatorTx, Codec, signers) - require.NoError(err) - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, ErrWeightTooSmall) - addNetValidatorTx.Wght = validatorWeight - - // Case: Net auth indices not unique - addNetValidatorTx.SyntacticallyVerified = false - input := addNetValidatorTx.ChainAuth.(*secp256k1fx.Input) - oldInput := *input - input.SigIndices[0] = input.SigIndices[1] - stx, err = NewSigned(addNetValidatorTx, Codec, signers) - require.NoError(err) - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, secp256k1fx.ErrInputIndicesNotSortedUnique) - *input = oldInput - - // Case: adding to Primary Network - addNetValidatorTx.SyntacticallyVerified = false - addNetValidatorTx.Chain = constants.PrimaryNetworkID - stx, err = NewSigned(addNetValidatorTx, Codec, signers) - require.NoError(err) - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, errAddPrimaryNetworkValidator) -} - -func TestAddNetValidatorMarshal(t *testing.T) { - require := require.New(t) - clk := mockable.Clock{} - nodeID := ids.GenerateTestNodeID() - testChainID := ids.GenerateTestID() // Use a test chain ID instead of empty - rt := &runtime.Runtime{ - NetworkID: constants.UnitTestID, - - ChainID: testChainID, - NodeID: nodeID, - } - signers := [][]*secp256k1.PrivateKey{preFundedKeys} - - var ( - stx *Tx - addNetValidatorTx *AddChainValidatorTx - err error - ) - - // create a valid tx - validatorWeight := uint64(2022) - netID := ids.ID{'s', 'u', 'b', 'n', 'e', 't', 'I', 'D'} - inputs := []*lux.TransferableInput{{ - UTXOID: lux.UTXOID{ - TxID: ids.ID{'t', 'x', 'I', 'D'}, - OutputIndex: 2, - }, - Asset: lux.Asset{ID: ids.ID{'a', 's', 's', 'e', 't'}}, - In: &secp256k1fx.TransferInput{ - Amt: uint64(5678), - Input: secp256k1fx.Input{SigIndices: []uint32{0}}, - }, - }} - outputs := []*lux.TransferableOutput{{ - Asset: lux.Asset{ID: ids.ID{'a', 's', 's', 'e', 't'}}, - Out: &secp256k1fx.TransferOutput{ - Amt: uint64(1234), - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{preFundedKeys[0].Address()}, - }, - }, - }} - chainAuth := &secp256k1fx.Input{ - SigIndices: []uint32{0, 1}, - } - addNetValidatorTx = &AddChainValidatorTx{ - BaseTx: BaseTx{BaseTx: lux.BaseTx{ - NetworkID: rt.NetworkID, - BlockchainID: rt.ChainID, - Ins: inputs, - Outs: outputs, - Memo: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - }}, - ChainValidator: ChainValidator{ - Validator: Validator{ - NodeID: nodeID, - Start: uint64(clk.Time().Unix()), - End: uint64(clk.Time().Add(time.Hour).Unix()), - Wght: validatorWeight, - }, - Chain: netID, - }, - ChainAuth: chainAuth, - } - - // Case: valid tx - stx, err = NewSigned(addNetValidatorTx, Codec, signers) - require.NoError(err) - require.NoError(stx.SyntacticVerify(rt)) - - txBytes, err := Codec.Marshal(CodecVersion, stx) - require.NoError(err) - - parsedTx, err := Parse(Codec, txBytes) - require.NoError(err) - - require.NoError(parsedTx.SyntacticVerify(rt)) - require.Equal(stx, parsedTx) -} - -func TestAddChainValidatorTxNotValidatorTx(t *testing.T) { - txIntf := any((*AddChainValidatorTx)(nil)) - _, ok := txIntf.(ValidatorTx) - require.False(t, ok) -} - -func TestAddChainValidatorTxNotDelegatorTx(t *testing.T) { - txIntf := any((*AddChainValidatorTx)(nil)) - _, ok := txIntf.(DelegatorTx) - require.False(t, ok) -} - -func TestAddChainValidatorTxNotPermissionlessStaker(t *testing.T) { - txIntf := any((*AddChainValidatorTx)(nil)) - _, ok := txIntf.(PermissionlessStaker) - require.False(t, ok) -} diff --git a/vms/platformvm/txs/add_delegator_test.go b/vms/platformvm/txs/add_delegator_test.go index a1133251e..b434ab483 100644 --- a/vms/platformvm/txs/add_delegator_test.go +++ b/vms/platformvm/txs/add_delegator_test.go @@ -1,223 +1,84 @@ -// 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 ( "testing" - "time" "github.com/stretchr/testify/require" - "github.com/luxfi/runtime" - "github.com/luxfi/constants" + consensustest "github.com/luxfi/consensus/test/helpers" "github.com/luxfi/crypto/secp256k1" "github.com/luxfi/ids" lux "github.com/luxfi/utxo" - "github.com/luxfi/node/vms/platformvm/stakeable" - "github.com/luxfi/timer/mockable" "github.com/luxfi/utxo/secp256k1fx" ) +// preFundedKeys is the shared set of test signing keys used across the txs +// package tests (owner addresses, credentials). var preFundedKeys = secp256k1.TestKeys() func TestAddDelegatorTxSyntacticVerify(t *testing.T) { require := require.New(t) - clk := mockable.Clock{} - utxoAssetID := ids.GenerateTestID() - nodeID := ids.GenerateTestNodeID() - testChainID := ids.GenerateTestID() // Use a test chain ID instead of empty - rt := &runtime.Runtime{ - NetworkID: constants.UnitTestID, + rt := consensustest.Runtime(t, ids.GenerateTestID()) - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - NodeID: nodeID, - } - signers := [][]*secp256k1.PrivateKey{preFundedKeys} - - var ( - stx *Tx - addDelegatorTx *AddDelegatorTx - err error - ) - - // Case : signed tx is nil - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, ErrNilSignedTx) - - // Case : unsigned tx is nil - err = addDelegatorTx.SyntacticVerify(rt) - require.ErrorIs(err, ErrNilTx) - - validatorWeight := uint64(2022) - inputs := []*lux.TransferableInput{{ - UTXOID: lux.UTXOID{ - TxID: ids.ID{'t', 'x', 'I', 'D'}, - OutputIndex: 2, - }, - Asset: lux.Asset{ID: utxoAssetID}, - In: &secp256k1fx.TransferInput{ - Amt: uint64(5678), - Input: secp256k1fx.Input{SigIndices: []uint32{0}}, - }, + owner := &secp256k1fx.OutputOwners{Threshold: 1, Addrs: []ids.ShortID{preFundedKeys[0].Address()}} + weight := uint64(2022) + validator := Validator{NodeID: ids.GenerateTestNodeID(), Start: 0, End: 3600, Wght: weight} + stakeOuts := []*lux.TransferableOutput{{ + Asset: lux.Asset{ID: rt.UTXOAssetID}, + Out: &secp256k1fx.TransferOutput{Amt: weight, OutputOwners: *owner}, }} - outputs := []*lux.TransferableOutput{{ - Asset: lux.Asset{ID: utxoAssetID}, - Out: &secp256k1fx.TransferOutput{ - Amt: uint64(1234), - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{preFundedKeys[0].Address()}, - }, - }, - }} - stakes := []*lux.TransferableOutput{{ - Asset: lux.Asset{ID: utxoAssetID}, - Out: &stakeable.LockOut{ - Locktime: uint64(clk.Time().Add(time.Second).Unix()), - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: validatorWeight, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{preFundedKeys[0].Address()}, - }, - }, - }, - }} - addDelegatorTx = &AddDelegatorTx{ - BaseTx: BaseTx{BaseTx: lux.BaseTx{ - NetworkID: rt.NetworkID, - BlockchainID: rt.ChainID, - Outs: outputs, - Ins: inputs, - Memo: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - }}, - Validator: Validator{ - NodeID: rt.NodeID, - Start: uint64(clk.Time().Unix()), - End: uint64(clk.Time().Add(time.Hour).Unix()), - Wght: validatorWeight, - }, - StakeOuts: stakes, - DelegationRewardsOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{preFundedKeys[0].Address()}, - }, + base := func(networkID uint32) *lux.BaseTx { + return &lux.BaseTx{NetworkID: networkID, BlockchainID: rt.ChainID} } - // Case: signed tx not initialized - stx = &Tx{Unsigned: addDelegatorTx} - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, errSignedTxNotInitialized) + // Case: signed tx is nil + var stx *Tx + require.ErrorIs(stx.SyntacticVerify(rt), ErrNilSignedTx) + + // Case: unsigned tx is nil + var nilTx *AddDelegatorTx + require.ErrorIs(nilTx.SyntacticVerify(rt), ErrNilTx) + + // Case: signed tx not initialized (Tx wrapper without Initialize/Sign) + u, err := NewAddDelegatorTx(base(rt.NetworkID), validator, stakeOuts, owner) + require.NoError(err) + require.ErrorIs((&Tx{Unsigned: u}).SyntacticVerify(rt), errSignedTxNotInitialized) // Case: valid tx - stx, err = NewSigned(addDelegatorTx, Codec, signers) - require.NoError(err) - require.NoError(stx.SyntacticVerify(rt)) + require.NoError(u.SyntacticVerify(rt)) - // Case: Wrong network ID - addDelegatorTx.SyntacticallyVerified = false - addDelegatorTx.NetworkID++ - stx, err = NewSigned(addDelegatorTx, Codec, signers) + // Case: wrong network ID + uWrong, err := NewAddDelegatorTx(base(rt.NetworkID+1), validator, stakeOuts, owner) require.NoError(err) - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, lux.ErrWrongNetworkID) - addDelegatorTx.NetworkID-- + require.ErrorIs(uWrong.SyntacticVerify(rt), lux.ErrWrongNetworkID) // Case: delegator weight is not equal to total stake weight - addDelegatorTx.SyntacticallyVerified = false - addDelegatorTx.Wght = 2 * validatorWeight - stx, err = NewSigned(addDelegatorTx, Codec, signers) + heavyValidator := validator + heavyValidator.Wght = 2 * weight + uMismatch, err := NewAddDelegatorTx(base(rt.NetworkID), heavyValidator, stakeOuts, owner) require.NoError(err) - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, errDelegatorWeightMismatch) - addDelegatorTx.Wght = validatorWeight + require.ErrorIs(uMismatch.SyntacticVerify(rt), errDelegatorWeightMismatch) } func TestAddDelegatorTxSyntacticVerifyNotLUX(t *testing.T) { require := require.New(t) - clk := mockable.Clock{} - nodeID := ids.GenerateTestNodeID() - testChainID := ids.GenerateTestID() // Use a test chain ID instead of empty - rt := &runtime.Runtime{ - NetworkID: constants.UnitTestID, + rt := consensustest.Runtime(t, ids.GenerateTestID()) - ChainID: testChainID, - NodeID: nodeID, - } - signers := [][]*secp256k1.PrivateKey{preFundedKeys} - - var ( - stx *Tx - addDelegatorTx *AddDelegatorTx - err error - ) - - assetID := ids.GenerateTestID() - validatorWeight := uint64(2022) - inputs := []*lux.TransferableInput{{ - UTXOID: lux.UTXOID{ - TxID: ids.ID{'t', 'x', 'I', 'D'}, - OutputIndex: 2, - }, - Asset: lux.Asset{ID: assetID}, - In: &secp256k1fx.TransferInput{ - Amt: uint64(5678), - Input: secp256k1fx.Input{SigIndices: []uint32{0}}, - }, + owner := &secp256k1fx.OutputOwners{Threshold: 1, Addrs: []ids.ShortID{preFundedKeys[0].Address()}} + weight := uint64(2022) + validator := Validator{NodeID: ids.GenerateTestNodeID(), Start: 0, End: 3600, Wght: weight} + // Stake asset is not the primary-network UTXO asset. + stakeOuts := []*lux.TransferableOutput{{ + Asset: lux.Asset{ID: ids.GenerateTestID()}, + Out: &secp256k1fx.TransferOutput{Amt: weight, OutputOwners: *owner}, }} - outputs := []*lux.TransferableOutput{{ - Asset: lux.Asset{ID: assetID}, - Out: &secp256k1fx.TransferOutput{ - Amt: uint64(1234), - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{preFundedKeys[0].Address()}, - }, - }, - }} - stakes := []*lux.TransferableOutput{{ - Asset: lux.Asset{ID: assetID}, - Out: &stakeable.LockOut{ - Locktime: uint64(clk.Time().Add(time.Second).Unix()), - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: validatorWeight, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{preFundedKeys[0].Address()}, - }, - }, - }, - }} - addDelegatorTx = &AddDelegatorTx{ - BaseTx: BaseTx{BaseTx: lux.BaseTx{ - NetworkID: rt.NetworkID, - BlockchainID: rt.ChainID, - Outs: outputs, - Ins: inputs, - Memo: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - }}, - Validator: Validator{ - NodeID: rt.NodeID, - Start: uint64(clk.Time().Unix()), - End: uint64(clk.Time().Add(time.Hour).Unix()), - Wght: validatorWeight, - }, - StakeOuts: stakes, - DelegationRewardsOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{preFundedKeys[0].Address()}, - }, - } - stx, err = NewSigned(addDelegatorTx, Codec, signers) + u, err := NewAddDelegatorTx(&lux.BaseTx{NetworkID: rt.NetworkID, BlockchainID: rt.ChainID}, validator, stakeOuts, owner) require.NoError(err) - - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, errStakeMustBeLUX) + require.ErrorIs(u.SyntacticVerify(rt), errStakeMustBeLUX) } func TestAddDelegatorTxNotValidatorTx(t *testing.T) { diff --git a/vms/platformvm/txs/add_permissionless_delegator_tx_test.go b/vms/platformvm/txs/add_permissionless_delegator_tx_test.go deleted file mode 100644 index 38a76ab46..000000000 --- a/vms/platformvm/txs/add_permissionless_delegator_tx_test.go +++ /dev/null @@ -1,1428 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txs - -import ( - "github.com/go-json-experiment/json" - "github.com/go-json-experiment/json/jsontext" - "errors" - "math" - "testing" - - "github.com/luxfi/mock/gomock" - "github.com/stretchr/testify/require" - - "github.com/luxfi/runtime" - "github.com/luxfi/constants" - "github.com/luxfi/ids" - lux "github.com/luxfi/utxo" - luxmock "github.com/luxfi/utxo/luxmock" - "github.com/luxfi/node/vms/platformvm/fx/fxmock" - "github.com/luxfi/node/vms/platformvm/stakeable" - "github.com/luxfi/utils" - "github.com/luxfi/utxo/secp256k1fx" - "github.com/luxfi/vm/types" - - safemath "github.com/luxfi/math" -) - -var errCustom = errors.New("custom error") - -func TestAddPermissionlessPrimaryDelegatorSerialization(t *testing.T) { - require := require.New(t) - - // Use empty chain ID for serialization test to match expected bytes - testChainID := ids.Empty - - addr := ids.ShortID{ - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - } - - utxoAssetID, err := ids.FromString("d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG") - require.NoError(err) - - customAssetID := ids.ID{ - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - } - - txID := ids.ID{ - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - } - nodeID := ids.BuildTestNodeID([]byte{ - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0x11, 0x22, 0x33, 0x44, - }) - - simpleAddPrimaryTx := &AddPermissionlessDelegatorTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: testChainID, - Outs: []*lux.TransferableOutput{}, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 2 * constants.KiloLux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{1}, - }, - }, - }, - }, - Memo: types.JSONByteSlice{}, - }, - }, - Validator: Validator{ - NodeID: nodeID, - Start: 12345, - End: 12345 + 200*24*60*60, - Wght: 2 * constants.KiloLux, - }, - Chain: constants.PrimaryNetworkID, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 2 * constants.KiloLux, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - }, - DelegationRewardsOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - } - lux.SortTransferableOutputs(simpleAddPrimaryTx.Outs) - lux.SortTransferableOutputs(simpleAddPrimaryTx.StakeOuts) - utils.Sort(simpleAddPrimaryTx.Ins) - rt := &runtime.Runtime{ - NetworkID: 1, - - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - } - require.NoError(simpleAddPrimaryTx.SyntacticVerify(rt)) - - expectedUnsignedSimpleAddPrimaryTxBytes := []byte{ - 0x01, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, - 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, - 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x05, 0x00, 0x00, 0x00, 0x00, 0x94, 0x35, 0x77, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x22, - 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, - 0x33, 0x44, 0x39, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0xdc, 0x07, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x94, 0x35, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x51, 0xc2, - 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, - 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x07, 0x00, - 0x00, 0x00, 0x00, 0x94, 0x35, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, - 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x0b, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, - 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, - } - var unsignedSimpleAddPrimaryTx UnsignedTx = simpleAddPrimaryTx - unsignedSimpleAddPrimaryTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleAddPrimaryTx) - require.NoError(err) - require.Equal(expectedUnsignedSimpleAddPrimaryTxBytes, unsignedSimpleAddPrimaryTxBytes) - - complexAddPrimaryTx := &AddPermissionlessDelegatorTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: constants.PlatformChainID, - Outs: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 87654321, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 12345678, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 876543210, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 0xffffffffffffffff, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - }, - }, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.MegaLux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2, 5}, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 2, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &stakeable.LockIn{ - Locktime: 876543210, - TransferableIn: &secp256k1fx.TransferInput{ - Amt: 0xefffffffffffffff, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 3, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 0x1000000000000000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{}, - }, - }, - }, - }, - Memo: types.JSONByteSlice("😅\nwell that's\x01\x23\x45!"), - }, - }, - Validator: Validator{ - NodeID: nodeID, - Start: 12345, - End: 12345 + 200*24*60*60, - Wght: 5 * constants.KiloLux, - }, - Chain: constants.PrimaryNetworkID, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 2 * constants.KiloLux, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 987654321, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 3 * constants.KiloLux, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 87654321, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - }, - }, - }, - }, - DelegationRewardsOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - } - lux.SortTransferableOutputs(complexAddPrimaryTx.Outs) - lux.SortTransferableOutputs(complexAddPrimaryTx.StakeOuts) - utils.Sort(complexAddPrimaryTx.Ins) - rt = &runtime.Runtime{ - NetworkID: 1, - - ChainID: constants.PlatformChainID, - UTXOAssetID: utxoAssetID, - } - require.NoError(complexAddPrimaryTx.SyntacticVerify(rt)) - - expectedUnsignedComplexAddPrimaryTxBytes := []byte{ - 0x01, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x03, 0x00, 0x00, 0x00, 0x51, 0xc2, - 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, - 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x16, 0x00, - 0x00, 0x00, 0xb1, 0x7f, 0x39, 0x05, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x61, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, - 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, - 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, - 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x16, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x03, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, - 0x01, 0xff, 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, - 0xc9, 0x6b, 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x05, 0x00, 0x00, 0x00, 0x00, 0x10, - 0xa5, 0xd4, 0xe8, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x02, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x15, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x03, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x9f, - 0x98, 0x85, 0x0a, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x27, 0x73, 0x01, 0x23, - 0x45, 0x21, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, - 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x39, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0xdc, - 0x07, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, - 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, - 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, - 0x24, 0x3b, 0x16, 0x00, 0x00, 0x00, 0xb1, 0x68, 0xde, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, - 0x00, 0x00, 0x00, 0x5e, 0xd0, 0xb2, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x7f, 0x39, 0x05, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, - 0x01, 0xff, 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, - 0xc9, 0x6b, 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x07, 0x00, 0x00, 0x00, 0x00, 0x94, - 0x35, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - } - var unsignedComplexAddPrimaryTx UnsignedTx = complexAddPrimaryTx - unsignedComplexAddPrimaryTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexAddPrimaryTx) - require.NoError(err) - require.Equal(expectedUnsignedComplexAddPrimaryTxBytes, unsignedComplexAddPrimaryTxBytes) - - // Remove aliaser as BCLookup field doesn't exist in runtime.Runtime - // This functionality is now handled differently - - rt2 := &runtime.Runtime{ - NetworkID: constants.MainnetID, // Must match tx.NetworkID for "P-lux1..." address encoding - - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - } - unsignedComplexAddPrimaryTx.InitRuntime(rt2) - - unsignedComplexAddPrimaryTxJSONBytes, err := json.Marshal(unsignedComplexAddPrimaryTx, jsontext.WithIndent("\t")) - require.NoError(err) - require.JSONEq(`{ - "networkID": 1, - "blockchainID": "11111111111111111111111111111111P", - "outputs": [ - { - "assetID": "d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "locktime": 87654321, - "output": { - "addresses": [], - "amount": 1, - "locktime": 12345678, - "threshold": 0 - } - } - }, - { - "assetID": "d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "addresses": [ - "7EKFm18KvWqcxMCNgpBSN51pJnEr1cVUb" - ], - "amount": 1, - "locktime": 0, - "threshold": 1 - } - }, - { - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "locktime": 876543210, - "output": { - "addresses": [ - "7EKFm18KvWqcxMCNgpBSN51pJnEr1cVUb" - ], - "amount": 18446744073709551615, - "locktime": 0, - "threshold": 1 - } - } - } - ], - "inputs": [ - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 1, - "assetID": "d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "amount": 1000000000000, - "signatureIndices": [ - 2, - 5 - ] - } - }, - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 2, - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "locktime": 876543210, - "input": { - "amount": 17293822569102704639, - "signatureIndices": [ - 0 - ] - } - } - }, - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 3, - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "amount": 1152921504606846976, - "signatureIndices": [] - } - } - ], - "memo": "0xf09f98850a77656c6c2074686174277301234521", - "validator": { - "nodeID": "NodeID-2ZbTY9GatRTrfinAoYiYLcf6CvrPAUYgo", - "start": 12345, - "end": 17292345, - "weight": 5000000000 - }, - "chainID": "11111111111111111111111111111111LpoYY", - "stake": [ - { - "assetID": "d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "locktime": 987654321, - "output": { - "addresses": [], - "amount": 3000000000, - "locktime": 87654321, - "threshold": 0 - } - } - }, - { - "assetID": "d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "addresses": [ - "7EKFm18KvWqcxMCNgpBSN51pJnEr1cVUb" - ], - "amount": 2000000000, - "locktime": 0, - "threshold": 1 - } - } - ], - "rewardsOwner": { - "addresses": [], - "locktime": 0, - "threshold": 0 - } -}`, string(unsignedComplexAddPrimaryTxJSONBytes)) -} - -func TestAddPermissionlessNetDelegatorSerialization(t *testing.T) { - require := require.New(t) - // Use empty chain ID for serialization test to match expected bytes - testChainID := ids.Empty - - addr := ids.ShortID{ - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - } - - utxoAssetID, err := ids.FromString("d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG") - require.NoError(err) - - customAssetID := ids.ID{ - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - } - - txID := ids.ID{ - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - } - nodeID := ids.BuildTestNodeID([]byte{ - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0x11, 0x22, 0x33, 0x44, - }) - netID := ids.ID{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, - } - - simpleAddNetTx := &AddPermissionlessDelegatorTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: constants.PlatformChainID, - Outs: []*lux.TransferableOutput{}, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.MilliLux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{1}, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 2, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 1, - Input: secp256k1fx.Input{ - SigIndices: []uint32{1}, - }, - }, - }, - }, - Memo: types.JSONByteSlice{}, - }, - }, - Validator: Validator{ - NodeID: nodeID, - Start: 12345, - End: 12346, - Wght: 1, - }, - Chain: netID, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - }, - DelegationRewardsOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - } - lux.SortTransferableOutputs(simpleAddNetTx.Outs) - lux.SortTransferableOutputs(simpleAddNetTx.StakeOuts) - utils.Sort(simpleAddNetTx.Ins) - rt := &runtime.Runtime{ - NetworkID: constants.UnitTestID, - - ChainID: ids.GenerateTestID(), - } - rt = &runtime.Runtime{ - NetworkID: 1, - - ChainID: constants.PlatformChainID, - UTXOAssetID: utxoAssetID, - } - require.NoError(simpleAddNetTx.SyntacticVerify(rt)) - - expectedUnsignedSimpleAddNetTxBytes := []byte{ - 0x01, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, - 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, - 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x05, 0x00, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x02, 0x00, 0x00, 0x00, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x05, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, - 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x39, 0x30, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3a, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, - 0x17, 0x18, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, - 0x37, 0x38, 0x01, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, - 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, - 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, - } - var unsignedSimpleAddNetTx UnsignedTx = simpleAddNetTx - unsignedSimpleAddNetTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleAddNetTx) - require.NoError(err) - require.Equal(expectedUnsignedSimpleAddNetTxBytes, unsignedSimpleAddNetTxBytes) - - complexAddNetTx := &AddPermissionlessDelegatorTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: constants.PlatformChainID, - Outs: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 87654321, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 12345678, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 876543210, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 0xfffffffffffffff0, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - }, - }, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.MegaLux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2, 5}, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 2, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &stakeable.LockIn{ - Locktime: 876543210, - TransferableIn: &secp256k1fx.TransferInput{ - Amt: 0xefffffffffffffff, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 3, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 0x1000000000000000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{}, - }, - }, - }, - }, - Memo: types.JSONByteSlice("😅\nwell that's\x01\x23\x45!"), - }, - }, - Validator: Validator{ - NodeID: nodeID, - Start: 12345, - End: 12345 + 1, - Wght: 9, - }, - Chain: netID, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 2, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 987654321, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 7, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 87654321, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - }, - }, - }, - }, - DelegationRewardsOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - } - lux.SortTransferableOutputs(complexAddNetTx.Outs) - lux.SortTransferableOutputs(complexAddNetTx.StakeOuts) - utils.Sort(complexAddNetTx.Ins) - rt = &runtime.Runtime{ - NetworkID: 1, - - ChainID: constants.PlatformChainID, - UTXOAssetID: utxoAssetID, - } - require.NoError(complexAddNetTx.SyntacticVerify(rt)) - - expectedUnsignedComplexAddNetTxBytes := []byte{ - 0x01, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x03, 0x00, 0x00, 0x00, 0x51, 0xc2, - 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, - 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x16, 0x00, - 0x00, 0x00, 0xb1, 0x7f, 0x39, 0x05, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x61, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, - 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, - 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, - 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x16, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x03, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, - 0x01, 0xff, 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, - 0xc9, 0x6b, 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x05, 0x00, 0x00, 0x00, 0x00, 0x10, - 0xa5, 0xd4, 0xe8, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x02, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x15, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x03, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x9f, - 0x98, 0x85, 0x0a, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x27, 0x73, 0x01, 0x23, - 0x45, 0x21, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, - 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x39, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x30, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, - 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x21, 0x22, - 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x02, 0x00, - 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x16, 0x00, 0x00, 0x00, 0xb1, 0x68, 0xde, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x7f, 0x39, 0x05, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - } - var unsignedComplexAddNetTx UnsignedTx = complexAddNetTx - unsignedComplexAddNetTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexAddNetTx) - require.NoError(err) - require.Equal(expectedUnsignedComplexAddNetTxBytes, unsignedComplexAddNetTxBytes) - - // Remove aliaser as BCLookup field doesn't exist in runtime.Runtime - // This functionality is now handled differently - - rt3 := &runtime.Runtime{ - NetworkID: constants.MainnetID, // Must match tx.NetworkID for "P-lux1..." address encoding - - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - } - unsignedComplexAddNetTx.InitRuntime(rt3) - - unsignedComplexAddNetTxJSONBytes, err := json.Marshal(unsignedComplexAddNetTx, jsontext.WithIndent("\t")) - require.NoError(err) - require.JSONEq(`{ - "networkID": 1, - "blockchainID": "11111111111111111111111111111111P", - "outputs": [ - { - "assetID": "d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "locktime": 87654321, - "output": { - "addresses": [], - "amount": 1, - "locktime": 12345678, - "threshold": 0 - } - } - }, - { - "assetID": "d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "addresses": [ - "7EKFm18KvWqcxMCNgpBSN51pJnEr1cVUb" - ], - "amount": 1, - "locktime": 0, - "threshold": 1 - } - }, - { - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "locktime": 876543210, - "output": { - "addresses": [ - "7EKFm18KvWqcxMCNgpBSN51pJnEr1cVUb" - ], - "amount": 18446744073709551600, - "locktime": 0, - "threshold": 1 - } - } - } - ], - "inputs": [ - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 1, - "assetID": "d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "amount": 1000000000000, - "signatureIndices": [ - 2, - 5 - ] - } - }, - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 2, - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "locktime": 876543210, - "input": { - "amount": 17293822569102704639, - "signatureIndices": [ - 0 - ] - } - } - }, - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 3, - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "amount": 1152921504606846976, - "signatureIndices": [] - } - } - ], - "memo": "0xf09f98850a77656c6c2074686174277301234521", - "validator": { - "nodeID": "NodeID-2ZbTY9GatRTrfinAoYiYLcf6CvrPAUYgo", - "start": 12345, - "end": 12346, - "weight": 9 - }, - "chainID": "SkB92YpWm4UpburLz9tEKZw2i67H3FF6YkjaU4BkFUDTG9Xm", - "stake": [ - { - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "locktime": 987654321, - "output": { - "addresses": [], - "amount": 7, - "locktime": 87654321, - "threshold": 0 - } - } - }, - { - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "addresses": [ - "7EKFm18KvWqcxMCNgpBSN51pJnEr1cVUb" - ], - "amount": 2, - "locktime": 0, - "threshold": 1 - } - } - ], - "rewardsOwner": { - "addresses": [], - "locktime": 0, - "threshold": 0 - } -}`, string(unsignedComplexAddNetTxJSONBytes)) -} - -func TestAddPermissionlessDelegatorTxSyntacticVerify(t *testing.T) { - type test struct { - name string - txFunc func(*gomock.Controller) *AddPermissionlessDelegatorTx - err error - } - - var ( - networkID = uint32(1337) - chainID = ids.GenerateTestID() - ) - - _ = &runtime.Runtime{ - NetworkID: constants.UnitTestID, - - ChainID: chainID, - } - - // A BaseTx that already passed syntactic verification. - verifiedBaseTx := BaseTx{ - SyntacticallyVerified: true, - } - - // A BaseTx that passes syntactic verification. - validBaseTx := BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: networkID, - BlockchainID: chainID, - }, - } - - // A BaseTx that fails syntactic verification. - invalidBaseTx := BaseTx{} - - tests := []test{ - { - name: "nil tx", - txFunc: func(*gomock.Controller) *AddPermissionlessDelegatorTx { - return nil - }, - err: ErrNilTx, - }, - { - name: "already verified", - txFunc: func(*gomock.Controller) *AddPermissionlessDelegatorTx { - return &AddPermissionlessDelegatorTx{ - BaseTx: verifiedBaseTx, - } - }, - err: nil, - }, - { - name: "no provided stake", - txFunc: func(*gomock.Controller) *AddPermissionlessDelegatorTx { - return &AddPermissionlessDelegatorTx{ - BaseTx: validBaseTx, - StakeOuts: nil, - } - }, - err: errNoStake, - }, - { - name: "invalid BaseTx", - txFunc: func(*gomock.Controller) *AddPermissionlessDelegatorTx { - return &AddPermissionlessDelegatorTx{ - BaseTx: invalidBaseTx, - Validator: Validator{ - NodeID: ids.GenerateTestNodeID(), - }, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: ids.GenerateTestID(), - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - }, - } - }, - err: lux.ErrWrongNetworkID, - }, - { - name: "invalid rewards owner", - txFunc: func(ctrl *gomock.Controller) *AddPermissionlessDelegatorTx { - rewardsOwner := fxmock.NewOwner(ctrl) - rewardsOwner.EXPECT().Verify().Return(errCustom) - return &AddPermissionlessDelegatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - Wght: 1, - }, - Chain: ids.GenerateTestID(), - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: ids.GenerateTestID(), - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - }, - DelegationRewardsOwner: rewardsOwner, - } - }, - err: errCustom, - }, - { - name: "invalid stake output", - txFunc: func(ctrl *gomock.Controller) *AddPermissionlessDelegatorTx { - rewardsOwner := fxmock.NewOwner(ctrl) - rewardsOwner.EXPECT().Verify().Return(nil).AnyTimes() - - stakeOut := luxmock.NewMockTransferableOut(ctrl) - stakeOut.EXPECT().Verify().Return(errCustom) - return &AddPermissionlessDelegatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - Wght: 1, - }, - Chain: ids.GenerateTestID(), - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: ids.GenerateTestID(), - }, - Out: stakeOut, - }, - }, - DelegationRewardsOwner: rewardsOwner, - } - }, - err: errCustom, - }, - { - name: "multiple staked assets", - txFunc: func(ctrl *gomock.Controller) *AddPermissionlessDelegatorTx { - rewardsOwner := fxmock.NewOwner(ctrl) - rewardsOwner.EXPECT().Verify().Return(nil).AnyTimes() - return &AddPermissionlessDelegatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - Wght: 1, - }, - Chain: ids.GenerateTestID(), - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: ids.GenerateTestID(), - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - { - Asset: lux.Asset{ - ID: ids.GenerateTestID(), - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - }, - DelegationRewardsOwner: rewardsOwner, - } - }, - err: errMultipleStakedAssets, - }, - { - name: "stake not sorted", - txFunc: func(ctrl *gomock.Controller) *AddPermissionlessDelegatorTx { - rewardsOwner := fxmock.NewOwner(ctrl) - rewardsOwner.EXPECT().Verify().Return(nil).AnyTimes() - assetID := ids.GenerateTestID() - return &AddPermissionlessDelegatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - Wght: 1, - }, - Chain: ids.GenerateTestID(), - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 2, - }, - }, - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - }, - DelegationRewardsOwner: rewardsOwner, - } - }, - err: errOutputsNotSorted, - }, - { - name: "stake overflow", - txFunc: func(ctrl *gomock.Controller) *AddPermissionlessDelegatorTx { - rewardsOwner := fxmock.NewOwner(ctrl) - rewardsOwner.EXPECT().Verify().Return(nil).AnyTimes() - assetID := ids.GenerateTestID() - return &AddPermissionlessDelegatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - NodeID: ids.GenerateTestNodeID(), - Wght: 1, - }, - Chain: ids.GenerateTestID(), - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: math.MaxUint64, - }, - }, - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 2, - }, - }, - }, - DelegationRewardsOwner: rewardsOwner, - } - }, - err: safemath.ErrOverflow, - }, - { - name: "weight mismatch", - txFunc: func(ctrl *gomock.Controller) *AddPermissionlessDelegatorTx { - rewardsOwner := fxmock.NewOwner(ctrl) - rewardsOwner.EXPECT().Verify().Return(nil).AnyTimes() - assetID := ids.GenerateTestID() - return &AddPermissionlessDelegatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - Wght: 1, - }, - Chain: ids.GenerateTestID(), - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - }, - DelegationRewardsOwner: rewardsOwner, - } - }, - err: errDelegatorWeightMismatch, - }, - { - name: "valid net validator", - txFunc: func(ctrl *gomock.Controller) *AddPermissionlessDelegatorTx { - rewardsOwner := fxmock.NewOwner(ctrl) - rewardsOwner.EXPECT().Verify().Return(nil).AnyTimes() - assetID := ids.GenerateTestID() - return &AddPermissionlessDelegatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - Wght: 2, - }, - Chain: ids.GenerateTestID(), - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - }, - DelegationRewardsOwner: rewardsOwner, - } - }, - err: nil, - }, - { - name: "valid primary network validator", - txFunc: func(ctrl *gomock.Controller) *AddPermissionlessDelegatorTx { - rewardsOwner := fxmock.NewOwner(ctrl) - rewardsOwner.EXPECT().Verify().Return(nil).AnyTimes() - assetID := ids.GenerateTestID() - return &AddPermissionlessDelegatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - Wght: 2, - }, - Chain: constants.PrimaryNetworkID, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - }, - DelegationRewardsOwner: rewardsOwner, - } - }, - err: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - - tx := tt.txFunc(ctrl) - testCtx := &runtime.Runtime{ - NetworkID: networkID, - - ChainID: chainID, - } - err := tx.SyntacticVerify(testCtx) - require.ErrorIs(t, err, tt.err) - }) - } -} - -func TestAddPermissionlessDelegatorTxNotValidatorTx(t *testing.T) { - txIntf := any((*AddPermissionlessDelegatorTx)(nil)) - _, ok := txIntf.(ValidatorTx) - require.False(t, ok) -} diff --git a/vms/platformvm/txs/add_permissionless_validator_tx_test.go b/vms/platformvm/txs/add_permissionless_validator_tx_test.go index a7af1a8ae..0e516cbca 100644 --- a/vms/platformvm/txs/add_permissionless_validator_tx_test.go +++ b/vms/platformvm/txs/add_permissionless_validator_tx_test.go @@ -1,1573 +1,231 @@ -// 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 ( - "encoding/hex" "math" "testing" - "github.com/luxfi/mock/gomock" "github.com/stretchr/testify/require" - "github.com/luxfi/runtime" "github.com/luxfi/constants" "github.com/luxfi/crypto/bls/signer/localsigner" "github.com/luxfi/ids" - lux "github.com/luxfi/utxo" - luxmock "github.com/luxfi/utxo/luxmock" - "github.com/luxfi/node/vms/platformvm/fx/fxmock" + safemath "github.com/luxfi/math" + "github.com/luxfi/utxo/secp256k1fx" + + consensustest "github.com/luxfi/consensus/test/helpers" "github.com/luxfi/node/vms/platformvm/reward" "github.com/luxfi/node/vms/platformvm/signer" - "github.com/luxfi/node/vms/platformvm/stakeable" - "github.com/luxfi/utils" - "github.com/luxfi/utxo/secp256k1fx" - "github.com/luxfi/vm/types" - - safemath "github.com/luxfi/math" + lux "github.com/luxfi/utxo" ) -func TestAddPermissionlessPrimaryValidator(t *testing.T) { +// TestAddPermissionlessValidatorTx_RoundTrip exercises the struct-is-wire path +// for both a chain (non-primary, empty signer) and a primary-network validator +// (real BLS proof-of-possession): the delta fields round-trip through Parse. +// This replaces the deleted linearcodec golden-byte + JSON serialization tests. +func TestAddPermissionlessValidatorTx_RoundTrip(t *testing.T) { require := require.New(t) - addr := ids.ShortID{ - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, + assetID := ids.GenerateTestID() + stakeOuts := []*lux.TransferableOutput{ + {Asset: lux.Asset{ID: assetID}, Out: &secp256k1fx.TransferOutput{ + Amt: 3, + OutputOwners: secp256k1fx.OutputOwners{Threshold: 1, Addrs: []ids.ShortID{ids.GenerateTestShortID()}}, + }}, } + valOwner := &secp256k1fx.OutputOwners{Threshold: 1, Addrs: []ids.ShortID{ids.GenerateTestShortID()}} + delOwner := &secp256k1fx.OutputOwners{Threshold: 1, Addrs: []ids.ShortID{ids.GenerateTestShortID()}} + vdr := Validator{NodeID: ids.GenerateTestNodeID(), Start: 1, End: 100, Wght: 3} - skBytes, err := hex.DecodeString("6668fecd4595b81e4d568398c820bbf3f073cb222902279fa55ebb84764ed2e3") + // Chain (non-primary) validator: empty signer off the primary network. + netTx, err := NewAddPermissionlessValidatorTx(spendBase(), vdr, ids.GenerateTestID(), &signer.Empty{}, stakeOuts, valOwner, delOwner, 100) require.NoError(err) + gotNet := roundTrip(t, netTx).(*AddPermissionlessValidatorTx) + require.Equal(vdr, gotNet.Validator()) + require.Equal(netTx.Chain(), gotNet.Chain()) + require.Equal(stakeOuts, gotNet.StakeOuts()) + require.Equal(valOwner, gotNet.ValidatorRewardsOwner()) + require.Equal(delOwner, gotNet.DelegatorRewardsOwner()) + require.EqualValues(100, gotNet.DelegationShares()) + require.Equal(&signer.Empty{}, gotNet.Signer()) - sk, err := localsigner.FromBytes(skBytes) + // Primary-network validator: real BLS proof-of-possession round-trips by + // its wire bytes (the parsed PoP has no cached publicKey until Verify). + sk, err := localsigner.New() require.NoError(err) pop, err := signer.NewProofOfPossession(sk) require.NoError(err) - - utxoAssetID, err := ids.FromString("d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG") + primTx, err := NewAddPermissionlessValidatorTx(spendBase(), vdr, constants.PrimaryNetworkID, pop, stakeOuts, valOwner, delOwner, 100) require.NoError(err) - - customAssetID := ids.ID{ - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - } - - txID := ids.ID{ - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - } - nodeID := ids.BuildTestNodeID([]byte{ - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0x11, 0x22, 0x33, 0x44, - }) - - simpleAddPrimaryTx := &AddPermissionlessValidatorTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: constants.PlatformChainID, - Outs: []*lux.TransferableOutput{}, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 2 * constants.KiloLux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{1}, - }, - }, - }, - }, - Memo: types.JSONByteSlice{}, - }, - }, - Validator: Validator{ - NodeID: nodeID, - Start: 12345, - End: 12345 + 200*24*60*60, - Wght: 2 * constants.KiloLux, - }, - Chain: constants.PrimaryNetworkID, - Signer: pop, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 2 * constants.KiloLux, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - }, - ValidatorRewardsOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - DelegatorRewardsOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - DelegationShares: reward.PercentDenominator, - } - lux.SortTransferableOutputs(simpleAddPrimaryTx.Outs) - lux.SortTransferableOutputs(simpleAddPrimaryTx.StakeOuts) - utils.Sort(simpleAddPrimaryTx.Ins) - rt := &runtime.Runtime{ - NetworkID: constants.MainnetID, - - ChainID: constants.PlatformChainID, - UTXOAssetID: utxoAssetID, - } - require.NoError(simpleAddPrimaryTx.SyntacticVerify(rt)) - - expectedUnsignedSimpleAddPrimaryTxBytes := []byte{ - // Codec version - 0x00, 0x01, - // AddPermissionlessValidatorTx type ID (post codec-collapse: 29) - 0x00, 0x00, 0x00, 0x1d, - // Mainnet network ID - 0x00, 0x00, 0x00, 0x01, - // P-chain blockchain ID (31 zeros + 'P') - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, - // Number of immediate outputs - 0x00, 0x00, 0x00, 0x00, - // Number of inputs - 0x00, 0x00, 0x00, 0x01, - // inputs[0] - // TxID - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - // Tx output index - 0x00, 0x00, 0x00, 0x01, - // Mainnet LUX asset ID - 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, - 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, - 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, - 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, - // secp256k1fx transfer input type ID - 0x00, 0x00, 0x00, 0x05, - // Amount = 2k LUX - 0x00, 0x00, 0x00, 0x00, 0x77, 0x35, 0x94, 0x00, - // Number of input signature indices - 0x00, 0x00, 0x00, 0x01, - // signature index - 0x00, 0x00, 0x00, 0x01, - // memo length - 0x00, 0x00, 0x00, 0x00, - // NodeID - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0x11, 0x22, 0x33, 0x44, - // Start time - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x39, - // End time - 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0xdc, 0x39, - // Stake weight - 0x00, 0x00, 0x00, 0x00, 0x77, 0x35, 0x94, 0x00, - // Primary network netID - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // BLS PoP type ID (post codec-collapse: 32) - 0x00, 0x00, 0x00, 0x20, - // BLS compressed public key - 0xaf, 0xf4, 0xac, 0xb4, 0xc5, 0x43, 0x9b, 0x5d, - 0x42, 0x6c, 0xad, 0xf9, 0xe9, 0x46, 0xd3, 0xa4, - 0x52, 0xf7, 0xde, 0x34, 0x14, 0xd1, 0xad, 0x27, - 0x33, 0x61, 0x33, 0x21, 0x1d, 0x8b, 0x90, 0xcf, - 0x49, 0xfb, 0x97, 0xee, 0xbc, 0xde, 0xee, 0xf7, - 0x14, 0xdc, 0x20, 0xf5, 0x4e, 0xd0, 0xd4, 0xd1, - // BLS compressed signature length - 0x8c, 0xfd, 0x79, 0x09, 0xd1, 0x53, 0xb9, 0x60, - 0x4b, 0x62, 0xb1, 0x43, 0xba, 0x36, 0x20, 0x7b, - 0xb7, 0xe6, 0x48, 0x67, 0x42, 0x44, 0x80, 0x20, - 0x2a, 0x67, 0xdc, 0x68, 0x76, 0x83, 0x46, 0xd9, - 0x5c, 0x90, 0x98, 0x3c, 0x2d, 0x27, 0x9c, 0x64, - 0xc4, 0x3c, 0x51, 0x13, 0x6b, 0x2a, 0x05, 0xe0, - 0x16, 0x02, 0xd5, 0x2a, 0xa6, 0x37, 0x6f, 0xda, - 0x17, 0xfa, 0x6e, 0x2a, 0x18, 0xa0, 0x83, 0xe4, - 0x9d, 0x9c, 0x45, 0x0e, 0xab, 0x7b, 0x89, 0xb1, - 0xd5, 0x55, 0x5d, 0xa5, 0xc4, 0x89, 0x87, 0x2e, - 0x02, 0xb7, 0xe5, 0x22, 0x7b, 0x77, 0x55, 0x0a, - 0xf1, 0x33, 0x0e, 0x5a, 0x71, 0xf8, 0xc3, 0x68, - // Number of locked outputs - 0x00, 0x00, 0x00, 0x01, - // Mainnet LUX asset ID - 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, - 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, - 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, - 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, - // secp256k1fx transferable output type ID - 0x00, 0x00, 0x00, 0x07, - // amount - 0x00, 0x00, 0x00, 0x00, 0x77, 0x35, 0x94, 0x00, - // locktime - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // threshold - 0x00, 0x00, 0x00, 0x01, - // number of addresses - 0x00, 0x00, 0x00, 0x01, - // addresses[0] - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - // secp256k1fx owner type ID - 0x00, 0x00, 0x00, 0x0b, - // locktime - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // threshold - 0x00, 0x00, 0x00, 0x01, - // number of addresses - 0x00, 0x00, 0x00, 0x01, - // addresses[0] - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - // secp256k1fx owner type ID - 0x00, 0x00, 0x00, 0x0b, - // locktime - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // threshold - 0x00, 0x00, 0x00, 0x01, - // number of addresses - 0x00, 0x00, 0x00, 0x01, - // addresses[0] - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - // delegation shares - 0x00, 0x0f, 0x42, 0x40, - } - var unsignedSimpleAddPrimaryTx UnsignedTx = simpleAddPrimaryTx - unsignedSimpleAddPrimaryTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleAddPrimaryTx) - require.NoError(err) - // BLS signatures include randomness, so we can't compare exact bytes - // Just verify it serializes without error and has reasonable length - require.Greater(len(unsignedSimpleAddPrimaryTxBytes), 100) - _ = expectedUnsignedSimpleAddPrimaryTxBytes - - complexAddPrimaryTx := &AddPermissionlessValidatorTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: constants.PlatformChainID, - Outs: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 87654321, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 12345678, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 876543210, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 0xffffffffffffffff, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - }, - }, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.MegaLux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2, 5}, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 2, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &stakeable.LockIn{ - Locktime: 876543210, - TransferableIn: &secp256k1fx.TransferInput{ - Amt: 0xefffffffffffffff, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 3, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 0x1000000000000000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{}, - }, - }, - }, - }, - Memo: types.JSONByteSlice("😅\nwell that's\x01\x23\x45!"), - }, - }, - Validator: Validator{ - NodeID: nodeID, - Start: 12345, - End: 12345 + 200*24*60*60, - Wght: 5 * constants.KiloLux, - }, - Chain: constants.PrimaryNetworkID, - Signer: pop, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 2 * constants.KiloLux, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 987654321, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 3 * constants.KiloLux, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 87654321, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - }, - }, - }, - }, - ValidatorRewardsOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - DelegatorRewardsOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - DelegationShares: reward.PercentDenominator, - } - lux.SortTransferableOutputs(complexAddPrimaryTx.Outs) - lux.SortTransferableOutputs(complexAddPrimaryTx.StakeOuts) - utils.Sort(complexAddPrimaryTx.Ins) - rt = &runtime.Runtime{ - NetworkID: constants.MainnetID, - - ChainID: constants.PlatformChainID, - UTXOAssetID: utxoAssetID, - } - require.NoError(complexAddPrimaryTx.SyntacticVerify(rt)) - - _ = []byte{ // expectedUnsignedComplexAddPrimaryTxBytes - not used since we skip exact byte comparison - // Codec version - 0x00, 0x01, - // AddPermissionlessValidatorTx type ID (post codec-collapse: 29) - 0x00, 0x00, 0x00, 0x1d, - // Mainnet network ID - 0x00, 0x00, 0x00, 0x01, - // P-chain blockchain ID (31 zeros + 'P') - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, - // Number of immediate outputs - 0x00, 0x00, 0x00, 0x03, - // outputs[0] - // Mainnet LUX asset ID - 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, - 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, - 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, - 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, - // secp256k1fx transfer output type ID - 0x00, 0x00, 0x00, 0x07, - // amount - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - // locktime - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // threshold - 0x00, 0x00, 0x00, 0x01, - // number of addresses - 0x00, 0x00, 0x00, 0x01, - // addresses[0] - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - // outputs[1] - // Mainnet LUX asset ID - 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, - 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, - 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, - 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, - // stakeable locked output type ID - 0x00, 0x00, 0x00, 0x16, - // locktime - 0x00, 0x00, 0x00, 0x00, 0x05, 0x39, 0x7f, 0xb1, - // secp256k1fx transfer output type ID - 0x00, 0x00, 0x00, 0x07, - // amount - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - // locktime - 0x00, 0x00, 0x00, 0x00, 0x00, 0xbc, 0x61, 0x4e, - // threshold - 0x00, 0x00, 0x00, 0x00, - // number of addresses - 0x00, 0x00, 0x00, 0x00, - // outputs[2] - // custom asset ID - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - // stakeable locked output type ID - 0x00, 0x00, 0x00, 0x16, - // locktime - 0x00, 0x00, 0x00, 0x00, 0x34, 0x3e, 0xfc, 0xea, - // secp256k1fx transfer output type ID - 0x00, 0x00, 0x00, 0x07, - // amount - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - // locktime - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // threshold - 0x00, 0x00, 0x00, 0x01, - // number of addresses - 0x00, 0x00, 0x00, 0x01, - // address[0] - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - // number of inputs - 0x00, 0x00, 0x00, 0x03, - // inputs[0] - // TxID - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - // Tx output index - 0x00, 0x00, 0x00, 0x01, - // Mainnet LUX asset ID - 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, - 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, - 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, - 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, - // secp256k1fx transfer input type ID - 0x00, 0x00, 0x00, 0x05, - // amount - 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x10, 0x00, - // number of signature indices - 0x00, 0x00, 0x00, 0x02, - // first signature index - 0x00, 0x00, 0x00, 0x02, - // second signature index - 0x00, 0x00, 0x00, 0x05, - // inputs[1] - // TxID - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - // Tx output index - 0x00, 0x00, 0x00, 0x02, - // custom asset ID - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - // stakeable locked input type ID - 0x00, 0x00, 0x00, 0x15, - // locktime - 0x00, 0x00, 0x00, 0x00, 0x34, 0x3e, 0xfc, 0xea, - // secp256k1fx transfer input type ID - 0x00, 0x00, 0x00, 0x05, - // amount - 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - // number of signature indices - 0x00, 0x00, 0x00, 0x01, - // signature index - 0x00, 0x00, 0x00, 0x00, - // inputs[2] - // TxID - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - // Tx output index - 0x00, 0x00, 0x00, 0x03, - // custom asset ID - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - // secp256k1 transfer input type ID - 0x00, 0x00, 0x00, 0x05, - // amount - 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // number of signature indices - 0x00, 0x00, 0x00, 0x00, - // memo length - 0x00, 0x00, 0x00, 0x14, - // memo - 0xf0, 0x9f, 0x98, 0x85, 0x0a, 0x77, 0x65, 0x6c, - 0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x27, 0x73, - 0x01, 0x23, 0x45, 0x21, - // nodeID - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0x11, 0x22, 0x33, 0x44, - // Start time - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x39, - // End time - 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0xdc, 0x39, - // Stake weight - 0x00, 0x00, 0x00, 0x01, 0x2a, 0x05, 0xf2, 0x00, - // Primary Network net ID - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // BLS PoP type ID (post codec-collapse: 32) - 0x00, 0x00, 0x00, 0x20, - // BLS compressed public key - 0xaf, 0xf4, 0xac, 0xb4, 0xc5, 0x43, 0x9b, 0x5d, - 0x42, 0x6c, 0xad, 0xf9, 0xe9, 0x46, 0xd3, 0xa4, - 0x52, 0xf7, 0xde, 0x34, 0x14, 0xd1, 0xad, 0x27, - 0x33, 0x61, 0x33, 0x21, 0x1d, 0x8b, 0x90, 0xcf, - 0x49, 0xfb, 0x97, 0xee, 0xbc, 0xde, 0xee, 0xf7, - 0x14, 0xdc, 0x20, 0xf5, 0x4e, 0xd0, 0xd4, 0xd1, - // BLS compressed signature - 0x8c, 0xfd, 0x79, 0x09, 0xd1, 0x53, 0xb9, 0x60, - 0x4b, 0x62, 0xb1, 0x43, 0xba, 0x36, 0x20, 0x7b, - 0xb7, 0xe6, 0x48, 0x67, 0x42, 0x44, 0x80, 0x20, - 0x2a, 0x67, 0xdc, 0x68, 0x76, 0x83, 0x46, 0xd9, - 0x5c, 0x90, 0x98, 0x3c, 0x2d, 0x27, 0x9c, 0x64, - 0xc4, 0x3c, 0x51, 0x13, 0x6b, 0x2a, 0x05, 0xe0, - 0x16, 0x02, 0xd5, 0x2a, 0xa6, 0x37, 0x6f, 0xda, - 0x17, 0xfa, 0x6e, 0x2a, 0x18, 0xa0, 0x83, 0xe4, - 0x9d, 0x9c, 0x45, 0x0e, 0xab, 0x7b, 0x89, 0xb1, - 0xd5, 0x55, 0x5d, 0xa5, 0xc4, 0x89, 0x87, 0x2e, - 0x02, 0xb7, 0xe5, 0x22, 0x7b, 0x77, 0x55, 0x0a, - 0xf1, 0x33, 0x0e, 0x5a, 0x71, 0xf8, 0xc3, 0x68, - // number of locked outputs - 0x00, 0x00, 0x00, 0x02, - // Mainnet LUX asset ID - 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, - 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, - 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, - 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, - // secp256k1 transfer output type ID - 0x00, 0x00, 0x00, 0x07, - // amount - 0x00, 0x00, 0x00, 0x00, 0x77, 0x35, 0x94, 0x00, - // locktime - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // threshold - 0x00, 0x00, 0x00, 0x01, - // number of addresses - 0x00, 0x00, 0x00, 0x01, - // addresses[0] - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - // Mainnet LUX asset ID - 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, - 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, - 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, - 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, - // stakeable locked output type ID - 0x00, 0x00, 0x00, 0x16, - // locktime - 0x00, 0x00, 0x00, 0x00, 0x3a, 0xde, 0x68, 0xb1, - // secp256k1 transfer output type ID - 0x00, 0x00, 0x00, 0x07, - // amount - 0x00, 0x00, 0x00, 0x00, 0xb2, 0xd0, 0x5e, 0x00, - // locktime - 0x00, 0x00, 0x00, 0x00, 0x05, 0x39, 0x7f, 0xb1, - // threshold - 0x00, 0x00, 0x00, 0x00, - // number of addresses - 0x00, 0x00, 0x00, 0x00, - // secp256k1 owner type ID - 0x00, 0x00, 0x00, 0x0b, - // locktime - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // threshold - 0x00, 0x00, 0x00, 0x01, - // number of addresses - 0x00, 0x00, 0x00, 0x01, - // addresses[0] - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - // secp256k1 owner type ID - 0x00, 0x00, 0x00, 0x0b, - // locktime - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // threshold - 0x00, 0x00, 0x00, 0x00, - // number of addresses - 0x00, 0x00, 0x00, 0x00, - // delegation shares - 0x00, 0x0f, 0x42, 0x40, - } - var unsignedComplexAddPrimaryTx UnsignedTx = complexAddPrimaryTx - unsignedComplexAddPrimaryTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexAddPrimaryTx) - require.NoError(err) - // BLS signatures include randomness, so we can't compare exact bytes - // Just verify that serialization works and produces a reasonable size - require.Greater(len(unsignedComplexAddPrimaryTxBytes), 100) -} - -func TestAddPermissionlessNetValidator(t *testing.T) { - require := require.New(t) - - addr := ids.ShortID{ - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - } - - utxoAssetID, err := ids.FromString("d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG") - require.NoError(err) - - customAssetID := ids.ID{ - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - } - - txID := ids.ID{ - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - } - nodeID := ids.BuildTestNodeID([]byte{ - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0x11, 0x22, 0x33, 0x44, - }) - netID := ids.ID{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, - } - - simpleAddNetTx := &AddPermissionlessValidatorTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: constants.PlatformChainID, - Outs: []*lux.TransferableOutput{}, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.MilliLux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{1}, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 2, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 1, - Input: secp256k1fx.Input{ - SigIndices: []uint32{1}, - }, - }, - }, - }, - Memo: types.JSONByteSlice{}, - }, - }, - Validator: Validator{ - NodeID: nodeID, - Start: 12345, - End: 12346, - Wght: 1, - }, - Chain: netID, - Signer: &signer.Empty{}, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - }, - ValidatorRewardsOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - DelegatorRewardsOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - DelegationShares: reward.PercentDenominator, - } - lux.SortTransferableOutputs(simpleAddNetTx.Outs) - lux.SortTransferableOutputs(simpleAddNetTx.StakeOuts) - utils.Sort(simpleAddNetTx.Ins) - rt := &runtime.Runtime{ - NetworkID: constants.MainnetID, - - ChainID: constants.PlatformChainID, - UTXOAssetID: utxoAssetID, - } - require.NoError(simpleAddNetTx.SyntacticVerify(rt)) - - expectedUnsignedSimpleAddNetTxBytes := []byte{ - 0x01, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, - 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, - 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x05, 0x00, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x02, 0x00, 0x00, 0x00, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x05, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, - 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x39, 0x30, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3a, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, - 0x17, 0x18, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, - 0x37, 0x38, 0x1f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x40, 0x42, 0x0f, 0x00, - } - var unsignedSimpleAddNetTx UnsignedTx = simpleAddNetTx - unsignedSimpleAddNetTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleAddNetTx) - require.NoError(err) - require.Equal(expectedUnsignedSimpleAddNetTxBytes, unsignedSimpleAddNetTxBytes) - - complexAddNetTx := &AddPermissionlessValidatorTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: constants.PlatformChainID, - Outs: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 87654321, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 12345678, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 876543210, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 0xfffffffffffffff0, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - }, - }, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.MegaLux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2, 5}, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 2, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &stakeable.LockIn{ - Locktime: 876543210, - TransferableIn: &secp256k1fx.TransferInput{ - Amt: 0xefffffffffffffff, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 3, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 0x1000000000000000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{}, - }, - }, - }, - }, - Memo: types.JSONByteSlice("😅\nwell that's\x01\x23\x45!"), - }, - }, - Validator: Validator{ - NodeID: nodeID, - Start: 12345, - End: 12345 + 1, - Wght: 9, - }, - Chain: netID, - Signer: &signer.Empty{}, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 2, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 987654321, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 7, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 87654321, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - }, - }, - }, - }, - ValidatorRewardsOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - DelegatorRewardsOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - DelegationShares: reward.PercentDenominator, - } - lux.SortTransferableOutputs(complexAddNetTx.Outs) - lux.SortTransferableOutputs(complexAddNetTx.StakeOuts) - utils.Sort(complexAddNetTx.Ins) - rt2 := &runtime.Runtime{ - NetworkID: constants.MainnetID, // Must match tx.NetworkID for "P-lux1..." address encoding - - ChainID: constants.PlatformChainID, - UTXOAssetID: utxoAssetID, - } - require.NoError(complexAddNetTx.SyntacticVerify(rt2)) - - expectedUnsignedComplexAddNetTxBytes := []byte{ - 0x01, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x03, 0x00, 0x00, 0x00, 0x51, 0xc2, - 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, - 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x16, 0x00, - 0x00, 0x00, 0xb1, 0x7f, 0x39, 0x05, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x61, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, - 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, - 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, - 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x16, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x03, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, - 0x01, 0xff, 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, - 0xc9, 0x6b, 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x05, 0x00, 0x00, 0x00, 0x00, 0x10, - 0xa5, 0xd4, 0xe8, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x02, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x15, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x03, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x9f, - 0x98, 0x85, 0x0a, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x27, 0x73, 0x01, 0x23, - 0x45, 0x21, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, - 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x39, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x30, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, - 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x21, 0x22, - 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x1f, 0x00, - 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x16, 0x00, 0x00, 0x00, 0xb1, 0x68, 0xde, 0x3a, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x7f, - 0x39, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x07, 0x00, - 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, - 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x0b, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, - 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x42, 0x0f, 0x00, - } - var unsignedComplexAddNetTx UnsignedTx = complexAddNetTx - unsignedComplexAddNetTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexAddNetTx) - require.NoError(err) - require.Equal(expectedUnsignedComplexAddNetTxBytes, unsignedComplexAddNetTxBytes) + gotPrim := roundTrip(t, primTx).(*AddPermissionlessValidatorTx) + require.Equal(constants.PrimaryNetworkID, gotPrim.Chain()) + gotSig, ok := gotPrim.Signer().(*signer.ProofOfPossession) + require.True(ok) + require.Equal(pop.PublicKey, gotSig.PublicKey) + require.Equal(pop.ProofOfPossession, gotSig.ProofOfPossession) } func TestAddPermissionlessValidatorTxSyntacticVerify(t *testing.T) { - type test struct { - name string - txFunc func(*gomock.Controller) *AddPermissionlessValidatorTx - err error + rt := consensustest.Runtime(t, ids.GenerateTestID()) + + // Local builders — struct-is-wire has no post-hoc field mutation, so every + // (in)valid case is expressed by passing values THROUGH the constructor. + validBase := func() *lux.BaseTx { + return &lux.BaseTx{NetworkID: rt.NetworkID, BlockchainID: rt.ChainID} + } + invalidBase := func() *lux.BaseTx { + return &lux.BaseTx{NetworkID: 0, BlockchainID: rt.ChainID} // wrong networkID + } + goodOwner := func() *secp256k1fx.OutputOwners { + return &secp256k1fx.OutputOwners{Threshold: 1, Addrs: []ids.ShortID{ids.GenerateTestShortID()}} + } + out := func(assetID ids.ID, amt uint64) *lux.TransferableOutput { + return &lux.TransferableOutput{Asset: lux.Asset{ID: assetID}, Out: &secp256k1fx.TransferOutput{Amt: amt}} } - var ( - networkID = uint32(1337) - chainID = ids.GenerateTestID() - ) - - rt := &runtime.Runtime{ - NetworkID: networkID, - - ChainID: chainID, - } - - // A BaseTx that already passed syntactic verification. - verifiedBaseTx := BaseTx{ - SyntacticallyVerified: true, - } - - // A BaseTx that passes syntactic verification. - validBaseTx := BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: networkID, - BlockchainID: chainID, - }, - } - - blsSK, err := localsigner.New() + // Real BLS PoP for the "valid primary network validator" case. + sk, err := localsigner.New() + require.NoError(t, err) + pop, err := signer.NewProofOfPossession(sk) require.NoError(t, err) - blsPOP, err := signer.NewProofOfPossession(blsSK) - require.NoError(t, err) - - // A BaseTx that fails syntactic verification. - invalidBaseTx := BaseTx{} - - tests := []test{ + tests := []struct { + name string + build func(t *testing.T) *AddPermissionlessValidatorTx + err error + }{ { - name: "nil tx", - txFunc: func(*gomock.Controller) *AddPermissionlessValidatorTx { - return nil - }, - err: ErrNilTx, - }, - { - name: "already verified", - txFunc: func(*gomock.Controller) *AddPermissionlessValidatorTx { - return &AddPermissionlessValidatorTx{ - BaseTx: verifiedBaseTx, - } - }, - err: nil, + name: "nil tx", + build: func(*testing.T) *AddPermissionlessValidatorTx { return nil }, + err: ErrNilTx, }, { name: "empty nodeID", - txFunc: func(*gomock.Controller) *AddPermissionlessValidatorTx { - return &AddPermissionlessValidatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - NodeID: ids.EmptyNodeID, - }, - } + build: func(t *testing.T) *AddPermissionlessValidatorTx { + tx, err := NewAddPermissionlessValidatorTx(validBase(), Validator{NodeID: ids.EmptyNodeID}, ids.GenerateTestID(), &signer.Empty{}, nil, goodOwner(), goodOwner(), 0) + require.NoError(t, err) + return tx }, err: errEmptyNodeID, }, { name: "no provided stake", - txFunc: func(*gomock.Controller) *AddPermissionlessValidatorTx { - return &AddPermissionlessValidatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - NodeID: ids.GenerateTestNodeID(), - }, - StakeOuts: nil, - } + build: func(t *testing.T) *AddPermissionlessValidatorTx { + tx, err := NewAddPermissionlessValidatorTx(validBase(), Validator{NodeID: ids.GenerateTestNodeID()}, ids.GenerateTestID(), &signer.Empty{}, nil, goodOwner(), goodOwner(), 0) + require.NoError(t, err) + return tx }, err: errNoStake, }, { name: "too many shares", - txFunc: func(*gomock.Controller) *AddPermissionlessValidatorTx { - return &AddPermissionlessValidatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - NodeID: ids.GenerateTestNodeID(), - }, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: ids.GenerateTestID(), - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - }, - DelegationShares: reward.PercentDenominator + 1, - } + build: func(t *testing.T) *AddPermissionlessValidatorTx { + tx, err := NewAddPermissionlessValidatorTx(validBase(), Validator{NodeID: ids.GenerateTestNodeID()}, ids.GenerateTestID(), &signer.Empty{}, []*lux.TransferableOutput{out(ids.GenerateTestID(), 1)}, goodOwner(), goodOwner(), reward.PercentDenominator+1) + require.NoError(t, err) + return tx }, err: errTooManyShares, }, { name: "invalid BaseTx", - txFunc: func(*gomock.Controller) *AddPermissionlessValidatorTx { - return &AddPermissionlessValidatorTx{ - BaseTx: invalidBaseTx, - Validator: Validator{ - NodeID: ids.GenerateTestNodeID(), - }, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: ids.GenerateTestID(), - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - }, - DelegationShares: reward.PercentDenominator, - } + build: func(t *testing.T) *AddPermissionlessValidatorTx { + tx, err := NewAddPermissionlessValidatorTx(invalidBase(), Validator{NodeID: ids.GenerateTestNodeID()}, ids.GenerateTestID(), &signer.Empty{}, []*lux.TransferableOutput{out(ids.GenerateTestID(), 1)}, goodOwner(), goodOwner(), reward.PercentDenominator) + require.NoError(t, err) + return tx }, err: lux.ErrWrongNetworkID, }, { + // Was: fxmock owner returns errCustom. Reproduced with a real + // unspendable owner (Threshold > #Addrs) → ErrOutputUnspendable. name: "invalid rewards owner", - txFunc: func(ctrl *gomock.Controller) *AddPermissionlessValidatorTx { - rewardsOwner := fxmock.NewOwner(ctrl) - rewardsOwner.EXPECT().Verify().Return(errCustom) - return &AddPermissionlessValidatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - NodeID: ids.GenerateTestNodeID(), - Wght: 1, - }, - Chain: ids.GenerateTestID(), - Signer: &signer.Empty{}, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: ids.GenerateTestID(), - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - }, - ValidatorRewardsOwner: rewardsOwner, - DelegatorRewardsOwner: rewardsOwner, - DelegationShares: reward.PercentDenominator, - } + build: func(t *testing.T) *AddPermissionlessValidatorTx { + badOwner := &secp256k1fx.OutputOwners{Threshold: 1} + tx, err := NewAddPermissionlessValidatorTx(validBase(), Validator{NodeID: ids.GenerateTestNodeID(), Wght: 1}, ids.GenerateTestID(), &signer.Empty{}, []*lux.TransferableOutput{out(ids.GenerateTestID(), 1)}, badOwner, badOwner, reward.PercentDenominator) + require.NoError(t, err) + return tx }, - err: errCustom, + err: secp256k1fx.ErrOutputUnspendable, }, { name: "wrong signer", - txFunc: func(ctrl *gomock.Controller) *AddPermissionlessValidatorTx { - rewardsOwner := fxmock.NewOwner(ctrl) - rewardsOwner.EXPECT().Verify().Return(nil).AnyTimes() - return &AddPermissionlessValidatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - NodeID: ids.GenerateTestNodeID(), - Wght: 1, - }, - Chain: constants.PrimaryNetworkID, - Signer: &signer.Empty{}, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: ids.GenerateTestID(), - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - }, - ValidatorRewardsOwner: rewardsOwner, - DelegatorRewardsOwner: rewardsOwner, - DelegationShares: reward.PercentDenominator, - } + build: func(t *testing.T) *AddPermissionlessValidatorTx { + tx, err := NewAddPermissionlessValidatorTx(validBase(), Validator{NodeID: ids.GenerateTestNodeID(), Wght: 1}, constants.PrimaryNetworkID, &signer.Empty{}, []*lux.TransferableOutput{out(ids.GenerateTestID(), 1)}, goodOwner(), goodOwner(), reward.PercentDenominator) + require.NoError(t, err) + return tx }, err: errInvalidSigner, }, { + // Was: luxmock stake-out Verify errCustom. Reproduced with a real + // unspendable stake output → ErrOutputUnspendable. name: "invalid stake output", - txFunc: func(ctrl *gomock.Controller) *AddPermissionlessValidatorTx { - rewardsOwner := fxmock.NewOwner(ctrl) - rewardsOwner.EXPECT().Verify().Return(nil).AnyTimes() - - stakeOut := luxmock.NewMockTransferableOut(ctrl) - stakeOut.EXPECT().Verify().Return(errCustom) - return &AddPermissionlessValidatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - NodeID: ids.GenerateTestNodeID(), - Wght: 1, - }, - Chain: ids.GenerateTestID(), - Signer: &signer.Empty{}, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: ids.GenerateTestID(), - }, - Out: stakeOut, - }, - }, - ValidatorRewardsOwner: rewardsOwner, - DelegatorRewardsOwner: rewardsOwner, - DelegationShares: reward.PercentDenominator, - } + build: func(t *testing.T) *AddPermissionlessValidatorTx { + badOut := &lux.TransferableOutput{Asset: lux.Asset{ID: ids.GenerateTestID()}, Out: &secp256k1fx.TransferOutput{Amt: 1, OutputOwners: secp256k1fx.OutputOwners{Threshold: 1}}} + tx, err := NewAddPermissionlessValidatorTx(validBase(), Validator{NodeID: ids.GenerateTestNodeID(), Wght: 1}, ids.GenerateTestID(), &signer.Empty{}, []*lux.TransferableOutput{badOut}, goodOwner(), goodOwner(), reward.PercentDenominator) + require.NoError(t, err) + return tx }, - err: errCustom, + err: secp256k1fx.ErrOutputUnspendable, }, { name: "stake overflow", - txFunc: func(ctrl *gomock.Controller) *AddPermissionlessValidatorTx { - rewardsOwner := fxmock.NewOwner(ctrl) - rewardsOwner.EXPECT().Verify().Return(nil).AnyTimes() + build: func(t *testing.T) *AddPermissionlessValidatorTx { assetID := ids.GenerateTestID() - return &AddPermissionlessValidatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - NodeID: ids.GenerateTestNodeID(), - Wght: 1, - }, - Chain: ids.GenerateTestID(), - Signer: &signer.Empty{}, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: math.MaxUint64, - }, - }, - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 2, - }, - }, - }, - ValidatorRewardsOwner: rewardsOwner, - DelegatorRewardsOwner: rewardsOwner, - DelegationShares: reward.PercentDenominator, - } + tx, err := NewAddPermissionlessValidatorTx(validBase(), Validator{NodeID: ids.GenerateTestNodeID(), Wght: 1}, ids.GenerateTestID(), &signer.Empty{}, []*lux.TransferableOutput{out(assetID, math.MaxUint64), out(assetID, 2)}, goodOwner(), goodOwner(), reward.PercentDenominator) + require.NoError(t, err) + return tx }, err: safemath.ErrOverflow, }, { name: "multiple staked assets", - txFunc: func(ctrl *gomock.Controller) *AddPermissionlessValidatorTx { - rewardsOwner := fxmock.NewOwner(ctrl) - rewardsOwner.EXPECT().Verify().Return(nil).AnyTimes() - return &AddPermissionlessValidatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - NodeID: ids.GenerateTestNodeID(), - Wght: 1, - }, - Chain: ids.GenerateTestID(), - Signer: &signer.Empty{}, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: ids.GenerateTestID(), - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - { - Asset: lux.Asset{ - ID: ids.GenerateTestID(), - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - }, - ValidatorRewardsOwner: rewardsOwner, - DelegatorRewardsOwner: rewardsOwner, - DelegationShares: reward.PercentDenominator, - } + build: func(t *testing.T) *AddPermissionlessValidatorTx { + tx, err := NewAddPermissionlessValidatorTx(validBase(), Validator{NodeID: ids.GenerateTestNodeID(), Wght: 1}, ids.GenerateTestID(), &signer.Empty{}, []*lux.TransferableOutput{out(ids.GenerateTestID(), 1), out(ids.GenerateTestID(), 1)}, goodOwner(), goodOwner(), reward.PercentDenominator) + require.NoError(t, err) + return tx }, err: errMultipleStakedAssets, }, { name: "stake not sorted", - txFunc: func(ctrl *gomock.Controller) *AddPermissionlessValidatorTx { - rewardsOwner := fxmock.NewOwner(ctrl) - rewardsOwner.EXPECT().Verify().Return(nil).AnyTimes() + build: func(t *testing.T) *AddPermissionlessValidatorTx { assetID := ids.GenerateTestID() - return &AddPermissionlessValidatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - NodeID: ids.GenerateTestNodeID(), - Wght: 1, - }, - Chain: ids.GenerateTestID(), - Signer: &signer.Empty{}, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 2, - }, - }, - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - }, - ValidatorRewardsOwner: rewardsOwner, - DelegatorRewardsOwner: rewardsOwner, - DelegationShares: reward.PercentDenominator, - } + tx, err := NewAddPermissionlessValidatorTx(validBase(), Validator{NodeID: ids.GenerateTestNodeID(), Wght: 1}, ids.GenerateTestID(), &signer.Empty{}, []*lux.TransferableOutput{out(assetID, 2), out(assetID, 1)}, goodOwner(), goodOwner(), reward.PercentDenominator) + require.NoError(t, err) + return tx }, err: errOutputsNotSorted, }, { name: "weight mismatch", - txFunc: func(ctrl *gomock.Controller) *AddPermissionlessValidatorTx { - rewardsOwner := fxmock.NewOwner(ctrl) - rewardsOwner.EXPECT().Verify().Return(nil).AnyTimes() + build: func(t *testing.T) *AddPermissionlessValidatorTx { assetID := ids.GenerateTestID() - return &AddPermissionlessValidatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - NodeID: ids.GenerateTestNodeID(), - Wght: 1, - }, - Chain: ids.GenerateTestID(), - Signer: &signer.Empty{}, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - }, - ValidatorRewardsOwner: rewardsOwner, - DelegatorRewardsOwner: rewardsOwner, - DelegationShares: reward.PercentDenominator, - } + tx, err := NewAddPermissionlessValidatorTx(validBase(), Validator{NodeID: ids.GenerateTestNodeID(), Wght: 1}, ids.GenerateTestID(), &signer.Empty{}, []*lux.TransferableOutput{out(assetID, 1), out(assetID, 1)}, goodOwner(), goodOwner(), reward.PercentDenominator) + require.NoError(t, err) + return tx }, err: errValidatorWeightMismatch, }, { name: "valid net validator", - txFunc: func(ctrl *gomock.Controller) *AddPermissionlessValidatorTx { - rewardsOwner := fxmock.NewOwner(ctrl) - rewardsOwner.EXPECT().Verify().Return(nil).AnyTimes() + build: func(t *testing.T) *AddPermissionlessValidatorTx { assetID := ids.GenerateTestID() - return &AddPermissionlessValidatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - NodeID: ids.GenerateTestNodeID(), - Wght: 2, - }, - Chain: ids.GenerateTestID(), - Signer: &signer.Empty{}, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - }, - ValidatorRewardsOwner: rewardsOwner, - DelegatorRewardsOwner: rewardsOwner, - DelegationShares: reward.PercentDenominator, - } + tx, err := NewAddPermissionlessValidatorTx(validBase(), Validator{NodeID: ids.GenerateTestNodeID(), Wght: 2}, ids.GenerateTestID(), &signer.Empty{}, []*lux.TransferableOutput{out(assetID, 1), out(assetID, 1)}, goodOwner(), goodOwner(), reward.PercentDenominator) + require.NoError(t, err) + return tx }, err: nil, }, { name: "valid primary network validator", - txFunc: func(ctrl *gomock.Controller) *AddPermissionlessValidatorTx { - rewardsOwner := fxmock.NewOwner(ctrl) - rewardsOwner.EXPECT().Verify().Return(nil).AnyTimes() + build: func(t *testing.T) *AddPermissionlessValidatorTx { assetID := ids.GenerateTestID() - return &AddPermissionlessValidatorTx{ - BaseTx: validBaseTx, - Validator: Validator{ - NodeID: ids.GenerateTestNodeID(), - Wght: 2, - }, - Chain: constants.PrimaryNetworkID, - Signer: blsPOP, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - { - Asset: lux.Asset{ - ID: assetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 1, - }, - }, - }, - ValidatorRewardsOwner: rewardsOwner, - DelegatorRewardsOwner: rewardsOwner, - DelegationShares: reward.PercentDenominator, - } + tx, err := NewAddPermissionlessValidatorTx(validBase(), Validator{NodeID: ids.GenerateTestNodeID(), Wght: 2}, constants.PrimaryNetworkID, pop, []*lux.TransferableOutput{out(assetID, 1), out(assetID, 1)}, goodOwner(), goodOwner(), reward.PercentDenominator) + require.NoError(t, err) + return tx }, err: nil, }, @@ -1575,11 +233,7 @@ func TestAddPermissionlessValidatorTxSyntacticVerify(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - - tx := tt.txFunc(ctrl) - err := tx.SyntacticVerify(rt) - require.ErrorIs(t, err, tt.err) + require.ErrorIs(t, tt.build(t).SyntacticVerify(rt), tt.err) }) } } diff --git a/vms/platformvm/txs/add_validator_test.go b/vms/platformvm/txs/add_validator_test.go deleted file mode 100644 index 0949f2518..000000000 --- a/vms/platformvm/txs/add_validator_test.go +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txs - -import ( - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/luxfi/runtime" - "github.com/luxfi/constants" - "github.com/luxfi/crypto/secp256k1" - "github.com/luxfi/ids" - lux "github.com/luxfi/utxo" - "github.com/luxfi/node/vms/platformvm/reward" - "github.com/luxfi/node/vms/platformvm/stakeable" - "github.com/luxfi/timer/mockable" - "github.com/luxfi/utxo/secp256k1fx" -) - -func TestAddValidatorTxSyntacticVerify(t *testing.T) { - require := require.New(t) - clk := mockable.Clock{} - utxoAssetID := ids.GenerateTestID() - nodeID := ids.GenerateTestNodeID() - testChainID := ids.GenerateTestID() // Use a test chain ID instead of empty - rt := &runtime.Runtime{ - NetworkID: constants.UnitTestID, - - ChainID: ids.GenerateTestID(), - } - rt = &runtime.Runtime{ - - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - NodeID: nodeID, - } - signers := [][]*secp256k1.PrivateKey{preFundedKeys} - - var ( - stx *Tx - addValidatorTx *AddValidatorTx - err error - ) - - // Case : signed tx is nil - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, ErrNilSignedTx) - - // Case : unsigned tx is nil - err = addValidatorTx.SyntacticVerify(rt) - require.ErrorIs(err, ErrNilTx) - - validatorWeight := uint64(2022) - rewardAddress := preFundedKeys[0].Address() - inputs := []*lux.TransferableInput{{ - UTXOID: lux.UTXOID{ - TxID: ids.ID{'t', 'x', 'I', 'D'}, - OutputIndex: 2, - }, - Asset: lux.Asset{ID: utxoAssetID}, - In: &secp256k1fx.TransferInput{ - Amt: uint64(5678), - Input: secp256k1fx.Input{SigIndices: []uint32{0}}, - }, - }} - outputs := []*lux.TransferableOutput{{ - Asset: lux.Asset{ID: utxoAssetID}, - Out: &secp256k1fx.TransferOutput{ - Amt: uint64(1234), - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{preFundedKeys[0].Address()}, - }, - }, - }} - stakes := []*lux.TransferableOutput{{ - Asset: lux.Asset{ID: utxoAssetID}, - Out: &stakeable.LockOut{ - Locktime: uint64(clk.Time().Add(time.Second).Unix()), - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: validatorWeight, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{preFundedKeys[0].Address()}, - }, - }, - }, - }} - addValidatorTx = &AddValidatorTx{ - BaseTx: BaseTx{BaseTx: lux.BaseTx{ - NetworkID: rt.NetworkID, - BlockchainID: rt.ChainID, - Ins: inputs, - Outs: outputs, - }}, - Validator: Validator{ - NodeID: rt.NodeID, - Start: uint64(clk.Time().Unix()), - End: uint64(clk.Time().Add(time.Hour).Unix()), - Wght: validatorWeight, - }, - StakeOuts: stakes, - RewardsOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{rewardAddress}, - }, - DelegationShares: reward.PercentDenominator, - } - - // Case: valid tx - stx, err = NewSigned(addValidatorTx, Codec, signers) - require.NoError(err) - require.NoError(stx.SyntacticVerify(rt)) - - // Case: Wrong network ID - addValidatorTx.SyntacticallyVerified = false - addValidatorTx.NetworkID++ - stx, err = NewSigned(addValidatorTx, Codec, signers) - require.NoError(err) - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, lux.ErrWrongNetworkID) - addValidatorTx.NetworkID-- - - // Case: Stake owner has no addresses - addValidatorTx.SyntacticallyVerified = false - addValidatorTx.StakeOuts[0]. - Out.(*stakeable.LockOut). - TransferableOut.(*secp256k1fx.TransferOutput). - Addrs = nil - stx, err = NewSigned(addValidatorTx, Codec, signers) - require.NoError(err) - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, secp256k1fx.ErrOutputUnspendable) - addValidatorTx.StakeOuts = stakes - - // Case: Rewards owner has no addresses - addValidatorTx.SyntacticallyVerified = false - addValidatorTx.RewardsOwner.(*secp256k1fx.OutputOwners).Addrs = nil - stx, err = NewSigned(addValidatorTx, Codec, signers) - require.NoError(err) - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, secp256k1fx.ErrOutputUnspendable) - addValidatorTx.RewardsOwner.(*secp256k1fx.OutputOwners).Addrs = []ids.ShortID{rewardAddress} - - // Case: Too many shares - addValidatorTx.SyntacticallyVerified = false - addValidatorTx.DelegationShares++ // 1 more than max amount - stx, err = NewSigned(addValidatorTx, Codec, signers) - require.NoError(err) - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, errTooManyShares) - addValidatorTx.DelegationShares-- -} - -func TestAddValidatorTxSyntacticVerifyNotLUX(t *testing.T) { - require := require.New(t) - clk := mockable.Clock{} - utxoAssetID := ids.GenerateTestID() - nodeID := ids.GenerateTestNodeID() - testChainID := ids.GenerateTestID() // Use a test chain ID instead of empty - rt := &runtime.Runtime{ - NetworkID: constants.UnitTestID, - - ChainID: ids.GenerateTestID(), - } - rt = &runtime.Runtime{ - - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - NodeID: nodeID, - } - signers := [][]*secp256k1.PrivateKey{preFundedKeys} - - var ( - stx *Tx - addValidatorTx *AddValidatorTx - err error - ) - - assetID := ids.GenerateTestID() - validatorWeight := uint64(2022) - rewardAddress := preFundedKeys[0].Address() - inputs := []*lux.TransferableInput{{ - UTXOID: lux.UTXOID{ - TxID: ids.ID{'t', 'x', 'I', 'D'}, - OutputIndex: 2, - }, - Asset: lux.Asset{ID: assetID}, - In: &secp256k1fx.TransferInput{ - Amt: uint64(5678), - Input: secp256k1fx.Input{SigIndices: []uint32{0}}, - }, - }} - outputs := []*lux.TransferableOutput{{ - Asset: lux.Asset{ID: assetID}, - Out: &secp256k1fx.TransferOutput{ - Amt: uint64(1234), - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{preFundedKeys[0].Address()}, - }, - }, - }} - stakes := []*lux.TransferableOutput{{ - Asset: lux.Asset{ID: assetID}, - Out: &stakeable.LockOut{ - Locktime: uint64(clk.Time().Add(time.Second).Unix()), - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: validatorWeight, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{preFundedKeys[0].Address()}, - }, - }, - }, - }} - addValidatorTx = &AddValidatorTx{ - BaseTx: BaseTx{BaseTx: lux.BaseTx{ - NetworkID: rt.NetworkID, - BlockchainID: rt.ChainID, - Ins: inputs, - Outs: outputs, - }}, - Validator: Validator{ - NodeID: rt.NodeID, - Start: uint64(clk.Time().Unix()), - End: uint64(clk.Time().Add(time.Hour).Unix()), - Wght: validatorWeight, - }, - StakeOuts: stakes, - RewardsOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{rewardAddress}, - }, - DelegationShares: reward.PercentDenominator, - } - - stx, err = NewSigned(addValidatorTx, Codec, signers) - require.NoError(err) - - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, errStakeMustBeLUX) -} - -func TestAddValidatorTxNotDelegatorTx(t *testing.T) { - txIntf := any((*AddValidatorTx)(nil)) - _, ok := txIntf.(DelegatorTx) - require.False(t, ok) -} - -// TestAddValidatorTxSyntacticVerify_ArbitraryPrimaryNetworkIDs asserts -// that AddValidatorTx — the canonical post-LP-018 add-validator-to-a- -// network tx — accepts arbitrary primary networkIDs, not just the Lux -// primary values (1/2/3/1337). A sovereign L1 IS a primary network at -// its own networkID; downstream consumers may operate primary networks -// at any uint32 they choose. This test pins that contract: the tx body -// has no per-chain "Chain" field, so SyntacticVerify must succeed -// against any valid primary networkID. -func TestAddValidatorTxSyntacticVerify_ArbitraryPrimaryNetworkIDs(t *testing.T) { - // Lux primaries (1/2/3/1337) plus four arbitrary synthetic IDs - // covering low / mid / high uint32 ranges, to demonstrate the tx - // is networkID-agnostic without baking any downstream value into - // this upstream test. - primaryNetworkIDs := []uint32{ - 1, // Lux mainnet - 2, // Lux testnet - 3, // Lux local - 1337, // Lux dev/localnet - 9001, - 123456, - 7_777_777, - 4_000_000_000, - } - - for _, networkID := range primaryNetworkIDs { - networkID := networkID - t.Run(formatPrimaryNetworkID(networkID), func(t *testing.T) { - require := require.New(t) - - clk := mockable.Clock{} - utxoAssetID := ids.GenerateTestID() - nodeID := ids.GenerateTestNodeID() - testChainID := ids.GenerateTestID() - rt := &runtime.Runtime{ - NetworkID: networkID, - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - NodeID: nodeID, - } - signers := [][]*secp256k1.PrivateKey{preFundedKeys} - - validatorWeight := uint64(2026) - rewardAddress := preFundedKeys[0].Address() - inputs := []*lux.TransferableInput{{ - UTXOID: lux.UTXOID{ - TxID: ids.ID{'l', 'p', '0', '1', '8'}, - OutputIndex: 0, - }, - Asset: lux.Asset{ID: utxoAssetID}, - In: &secp256k1fx.TransferInput{ - Amt: uint64(5678), - Input: secp256k1fx.Input{SigIndices: []uint32{0}}, - }, - }} - outputs := []*lux.TransferableOutput{{ - Asset: lux.Asset{ID: utxoAssetID}, - Out: &secp256k1fx.TransferOutput{ - Amt: uint64(1234), - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{preFundedKeys[0].Address()}, - }, - }, - }} - stakes := []*lux.TransferableOutput{{ - Asset: lux.Asset{ID: utxoAssetID}, - Out: &stakeable.LockOut{ - Locktime: uint64(clk.Time().Add(time.Second).Unix()), - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: validatorWeight, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{preFundedKeys[0].Address()}, - }, - }, - }, - }} - addValidatorTx := &AddValidatorTx{ - BaseTx: BaseTx{BaseTx: lux.BaseTx{ - NetworkID: rt.NetworkID, - BlockchainID: rt.ChainID, - Ins: inputs, - Outs: outputs, - }}, - Validator: Validator{ - NodeID: rt.NodeID, - Start: uint64(clk.Time().Unix()), - End: uint64(clk.Time().Add(time.Hour).Unix()), - Wght: validatorWeight, - }, - StakeOuts: stakes, - RewardsOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{rewardAddress}, - }, - DelegationShares: reward.PercentDenominator, - } - - stx, err := NewSigned(addValidatorTx, Codec, signers) - require.NoError(err) - require.NoError(stx.SyntacticVerify(rt)) - }) - } -} - -func formatPrimaryNetworkID(id uint32) string { - switch id { - case 1: - return "lux-mainnet-1" - case 2: - return "lux-testnet-2" - case 3: - return "lux-local-3" - case 1337: - return "lux-dev-1337" - default: - return fmt.Sprintf("primary-networkID-%d", id) - } -} diff --git a/vms/platformvm/txs/base_tx_test.go b/vms/platformvm/txs/base_tx_test.go deleted file mode 100644 index a9fe4deed..000000000 --- a/vms/platformvm/txs/base_tx_test.go +++ /dev/null @@ -1,331 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txs - -import ( - "github.com/luxfi/runtime" - - "github.com/go-json-experiment/json" - "github.com/go-json-experiment/json/jsontext" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/luxfi/constants" - "github.com/luxfi/ids" - lux "github.com/luxfi/utxo" - "github.com/luxfi/node/vms/platformvm/stakeable" - "github.com/luxfi/utils" - "github.com/luxfi/utxo/secp256k1fx" - "github.com/luxfi/vm/types" -) - -func TestBaseTxSerialization(t *testing.T) { - require := require.New(t) - - addr := ids.ShortID{ - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - } - - utxoAssetID, err := ids.FromString("d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG") - require.NoError(err) - - customAssetID := ids.ID{ - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - } - - txID := ids.ID{ - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - } - - simpleBaseTx := &BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: ids.Empty, // Use empty for serialization test - Outs: []*lux.TransferableOutput{}, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.MilliLux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{5}, - }, - }, - }, - }, - Memo: types.JSONByteSlice{}, - }, - } - testChainID := ids.Empty // Use empty for serialization test - rt := &runtime.Runtime{ - NetworkID: constants.MainnetID, // Must match tx.NetworkID - - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - } - require.NoError(simpleBaseTx.SyntacticVerify(rt)) - - expectedUnsignedSimpleBaseTxBytes := []byte{ - 0x01, 0x00, 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, - 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, - 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x05, 0x00, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - } - var unsignedSimpleBaseTx UnsignedTx = simpleBaseTx - unsignedSimpleBaseTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleBaseTx) - require.NoError(err) - require.Equal(expectedUnsignedSimpleBaseTxBytes, unsignedSimpleBaseTxBytes) - - complexBaseTx := &BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: ids.Empty, // Use empty for serialization test - Outs: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 87654321, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 12345678, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 876543210, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 0xffffffffffffffff, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - }, - }, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.Lux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2, 5}, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 2, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &stakeable.LockIn{ - Locktime: 876543210, - TransferableIn: &secp256k1fx.TransferInput{ - Amt: 0xefffffffffffffff, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 3, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 0x1000000000000000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{}, - }, - }, - }, - }, - Memo: types.JSONByteSlice("😅\nwell that's\x01\x23\x45!"), - }, - } - lux.SortTransferableOutputs(complexBaseTx.Outs) - utils.Sort(complexBaseTx.Ins) - rt2 := &runtime.Runtime{ - NetworkID: constants.MainnetID, // Must match tx.NetworkID - - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - } - require.NoError(complexBaseTx.SyntacticVerify(rt2)) - - expectedUnsignedComplexBaseTxBytes := []byte{ - 0x01, 0x00, 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x51, 0xc2, - 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, - 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x16, 0x00, - 0x00, 0x00, 0xb1, 0x7f, 0x39, 0x05, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x61, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x16, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x03, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, - 0x01, 0xff, 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, - 0xc9, 0x6b, 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x05, 0x00, 0x00, 0x00, 0x40, 0x42, - 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x02, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x15, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x03, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x9f, - 0x98, 0x85, 0x0a, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x27, 0x73, 0x01, 0x23, - 0x45, 0x21, - } - var unsignedComplexBaseTx UnsignedTx = complexBaseTx - unsignedComplexBaseTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexBaseTx) - require.NoError(err) - require.Equal(expectedUnsignedComplexBaseTxBytes, unsignedComplexBaseTxBytes) - - // Remove aliaser as BCLookup field doesn't exist in runtime.Runtime - // This functionality is now handled differently - - rt3 := &runtime.Runtime{ - NetworkID: constants.MainnetID, // Must match tx.NetworkID - - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - } - unsignedComplexBaseTx.InitRuntime(rt3) - - unsignedComplexBaseTxJSONBytes, err := json.Marshal(unsignedComplexBaseTx, jsontext.WithIndent("\t")) - require.NoError(err) - require.JSONEq(`{ - "networkID": 1, - "blockchainID": "11111111111111111111111111111111LpoYY", - "outputs": [ - { - "assetID": "d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "locktime": 87654321, - "output": { - "addresses": [], - "amount": 1, - "locktime": 12345678, - "threshold": 0 - } - } - }, - { - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "locktime": 876543210, - "output": { - "addresses": [ - "7EKFm18KvWqcxMCNgpBSN51pJnEr1cVUb" - ], - "amount": 18446744073709551615, - "locktime": 0, - "threshold": 1 - } - } - } - ], - "inputs": [ - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 1, - "assetID": "d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "amount": 1000000, - "signatureIndices": [ - 2, - 5 - ] - } - }, - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 2, - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "locktime": 876543210, - "input": { - "amount": 17293822569102704639, - "signatureIndices": [ - 0 - ] - } - } - }, - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 3, - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "amount": 1152921504606846976, - "signatureIndices": [] - } - } - ], - "memo": "0xf09f98850a77656c6c2074686174277301234521" -}`, string(unsignedComplexBaseTxJSONBytes)) -} diff --git a/vms/platformvm/txs/bench/alloc_bench_test.go b/vms/platformvm/txs/bench/alloc_bench_test.go deleted file mode 100644 index 5afce0065..000000000 --- a/vms/platformvm/txs/bench/alloc_bench_test.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package bench - -import ( - "runtime" - "testing" - "time" - - "github.com/luxfi/node/vms/platformvm/txs" - "github.com/luxfi/proto/zap_native" -) - -// allocLoopSeconds: 5 seconds keeps a full bench run under 10 min on -// CI. The honest report quotes both the 5s baseline AND a manual 60s -// reproduction command. -const allocLoopSeconds = 5 - -// BenchmarkAllocPressureLegacy: 5-second parse-loop over the legacy -// codec, AddPermissionlessValidatorTx fixture. Reports MB/s allocated, -// mallocs, NumGC. -func BenchmarkAllocPressureLegacy(b *testing.B) { - if testing.Short() { - b.Skip("alloc pressure bench skipped under -short") - } - tx := NewAddPermissionlessValidatorTxFixture() - signedBytes := MustMarshalSignedTx(tx) - - var before, after runtime.MemStats - runtime.GC() - runtime.ReadMemStats(&before) - - deadline := time.Now().Add(allocLoopSeconds * time.Second) - parses := 0 - for time.Now().Before(deadline) { - _, err := txs.Parse(txs.Codec, signedBytes) - if err != nil { - b.Fatal(err) - } - parses++ - } - - runtime.ReadMemStats(&after) - b.ReportMetric(float64(parses)/float64(allocLoopSeconds), "parses/s") - b.ReportMetric(float64(after.TotalAlloc-before.TotalAlloc)/1024/1024, "MB_alloc/run") - b.ReportMetric(float64(after.Mallocs-before.Mallocs), "mallocs/run") - b.ReportMetric(float64(after.NumGC-before.NumGC), "GC/run") - if parses > 0 { - b.ReportMetric(float64(after.TotalAlloc-before.TotalAlloc)/float64(parses), "B/parse") - } -} - -// BenchmarkAllocPressureNativeAdvanceTime: 5-second parse-loop over -// the native-ZAP AdvanceTimeTx path. Direct apples-to-apples vs -// BenchmarkAllocPressureLegacy only if you compare the AdvanceTimeTx- -// only fixture; comparing against the AddPermissionlessValidator -// fixture is unfair to legacy. To get the apples-to-apples number, -// use BenchmarkAllocPressureLegacyAdvanceTime below. -func BenchmarkAllocPressureNativeAdvanceTime(b *testing.B) { - if testing.Short() { - b.Skip("alloc pressure bench skipped under -short") - } - tx := zap_native.NewAdvanceTimeTx(1782604800) - zapBytes := tx.Bytes() - - var before, after runtime.MemStats - runtime.GC() - runtime.ReadMemStats(&before) - - deadline := time.Now().Add(allocLoopSeconds * time.Second) - parses := 0 - for time.Now().Before(deadline) { - _, err := zap_native.WrapAdvanceTimeTx(zapBytes) - if err != nil { - b.Fatal(err) - } - parses++ - } - - runtime.ReadMemStats(&after) - b.ReportMetric(float64(parses)/float64(allocLoopSeconds), "parses/s") - b.ReportMetric(float64(after.TotalAlloc-before.TotalAlloc)/1024/1024, "MB_alloc/run") - b.ReportMetric(float64(after.Mallocs-before.Mallocs), "mallocs/run") - b.ReportMetric(float64(after.NumGC-before.NumGC), "GC/run") - if parses > 0 { - b.ReportMetric(float64(after.TotalAlloc-before.TotalAlloc)/float64(parses), "B/parse") - } -} - -// BenchmarkAllocPressureLegacyAdvanceTime is the apples-to-apples -// counterpart of BenchmarkAllocPressureNativeAdvanceTime: a 5-second -// parse loop on the SAME AdvanceTimeTx payload via the legacy codec. -// The ratio of (parses/s, mallocs/run, GC/run) between this and -// BenchmarkAllocPressureNativeAdvanceTime is the direct GC-pressure -// lift number. -func BenchmarkAllocPressureLegacyAdvanceTime(b *testing.B) { - if testing.Short() { - b.Skip("alloc pressure bench skipped under -short") - } - tx := NewAdvanceTimeTxFixture() - signedBytes := MustMarshalSignedTx(tx) - - var before, after runtime.MemStats - runtime.GC() - runtime.ReadMemStats(&before) - - deadline := time.Now().Add(allocLoopSeconds * time.Second) - parses := 0 - for time.Now().Before(deadline) { - _, err := txs.Parse(txs.Codec, signedBytes) - if err != nil { - b.Fatal(err) - } - parses++ - } - - runtime.ReadMemStats(&after) - b.ReportMetric(float64(parses)/float64(allocLoopSeconds), "parses/s") - b.ReportMetric(float64(after.TotalAlloc-before.TotalAlloc)/1024/1024, "MB_alloc/run") - b.ReportMetric(float64(after.Mallocs-before.Mallocs), "mallocs/run") - b.ReportMetric(float64(after.NumGC-before.NumGC), "GC/run") - if parses > 0 { - b.ReportMetric(float64(after.TotalAlloc-before.TotalAlloc)/float64(parses), "B/parse") - } -} diff --git a/vms/platformvm/txs/bench/build_bench_test.go b/vms/platformvm/txs/bench/build_bench_test.go deleted file mode 100644 index 5e1b79b7e..000000000 --- a/vms/platformvm/txs/bench/build_bench_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package bench - -import ( - "testing" - - "github.com/luxfi/node/vms/platformvm/txs" - "github.com/luxfi/proto/zap_native" -) - -// BenchmarkBuildLegacy measures fields→bytes via the legacy -// linearcodec Marshal path for each fixture. Building includes the -// reflection-cache lookup, packer alloc, and per-field walk. -func BenchmarkBuildLegacy(b *testing.B) { - fixtures := FixtureMap() - names := sortedFixtureNames(fixtures) - for _, name := range names { - unsigned := fixtures[name] - signedTx := &txs.Tx{Unsigned: unsigned} - b.Run(name, func(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - out, err := txs.Codec.Marshal(txs.CodecVersion, signedTx) - if err != nil { - b.Fatal(err) - } - if len(out) == 0 { - b.Fatal("empty marshal") - } - b.SetBytes(int64(len(out))) - } - }) - } -} - -// BenchmarkBuildNativeAdvanceTime measures the AdvanceTimeTx build -// path through the native-ZAP builder Blue landed. Directly comparable -// to BenchmarkBuildLegacy/AdvanceTimeTx — same single uint64 field. -// -// Native: zap_native.NewAdvanceTimeTx writes the time at a fixed -// offset, no reflection, no codec dispatch. -func BenchmarkBuildNativeAdvanceTime(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - tx := zap_native.NewAdvanceTimeTx(uint64(i)) - out := tx.Bytes() - if len(out) == 0 { - b.Fatal("empty build") - } - } -} - -// sortedFixtureNames returns the fixture names in deterministic order. -func sortedFixtureNames(m map[string]txs.UnsignedTx) []string { - names := make([]string, 0, len(m)) - for k := range m { - names = append(names, k) - } - for i := 1; i < len(names); i++ { - for j := i; j > 0 && names[j-1] > names[j]; j-- { - names[j-1], names[j] = names[j], names[j-1] - } - } - return names -} diff --git a/vms/platformvm/txs/bench/field_bench_test.go b/vms/platformvm/txs/bench/field_bench_test.go deleted file mode 100644 index 80297e70c..000000000 --- a/vms/platformvm/txs/bench/field_bench_test.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package bench - -import ( - "testing" - - "github.com/luxfi/node/vms/platformvm/txs" - "github.com/luxfi/proto/zap_native" -) - -// BenchmarkFieldAccessLegacy reads the Time field of a parsed legacy -// AdvanceTimeTx. After parse, the field is a direct Go struct deref — -// the lowest theoretical bound a runtime can hit. -func BenchmarkFieldAccessLegacy(b *testing.B) { - tx := NewAdvanceTimeTxFixture() - signedBytes := MustMarshalSignedTx(tx) - parsed, err := txs.Parse(txs.Codec, signedBytes) - if err != nil { - b.Fatal(err) - } - unsigned := parsed.Unsigned.(*txs.AdvanceTimeTx) - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - _ = unsigned.Time - } -} - -// BenchmarkFieldAccessNative reads the Time field of a wrapped -// native-ZAP AdvanceTimeTx via offset arithmetic. Should be -// comparable to a struct deref — a single 8-byte little-endian load. -func BenchmarkFieldAccessNative(b *testing.B) { - tx := zap_native.NewAdvanceTimeTx(1782604800) - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - _ = tx.Time() - } -} - -// BenchmarkFieldAccess1MBatch reads the Time field 1M times for each -// codec to amortize benchmark frame overhead. The per-read cost in -// the per-op bench is below 2 ns and at that scale the testing -// framework's per-iteration overhead matters; batching escapes it. -func BenchmarkFieldAccess1MBatch(b *testing.B) { - legacyTx := NewAdvanceTimeTxFixture() - legacyBytes := MustMarshalSignedTx(legacyTx) - legacyParsed, err := txs.Parse(txs.Codec, legacyBytes) - if err != nil { - b.Fatal(err) - } - legacyUnsigned := legacyParsed.Unsigned.(*txs.AdvanceTimeTx) - zapTx := zap_native.NewAdvanceTimeTx(1782604800) - - b.Run("legacy_1M_reads", func(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - var acc uint64 - for j := 0; j < 1_000_000; j++ { - acc ^= legacyUnsigned.Time - } - if acc == 0xdeadbeefdeadbeef { - b.Fatal("impossible") - } - } - }) - b.Run("zap_1M_reads", func(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - var acc uint64 - for j := 0; j < 1_000_000; j++ { - acc ^= zapTx.Time() - } - if acc == 0xdeadbeefdeadbeef { - b.Fatal("impossible") - } - } - }) -} diff --git a/vms/platformvm/txs/bench/fixtures.go b/vms/platformvm/txs/bench/fixtures.go index b912d8ae1..5e31847b8 100644 --- a/vms/platformvm/txs/bench/fixtures.go +++ b/vms/platformvm/txs/bench/fixtures.go @@ -18,6 +18,7 @@ import ( "github.com/luxfi/crypto/bls" "github.com/luxfi/ids" "github.com/luxfi/node/vms/platformvm/reward" + "github.com/luxfi/node/vms/platformvm/security" "github.com/luxfi/node/vms/platformvm/signer" "github.com/luxfi/node/vms/platformvm/stakeable" "github.com/luxfi/node/vms/platformvm/txs" @@ -122,9 +123,12 @@ func buildBaseTxFields(numIns, numOuts int) lux.BaseTx { // payload and the unit by which all other fixtures are bounded from // below. func NewBaseTxFixture() *txs.BaseTx { - return &txs.BaseTx{ - BaseTx: buildBaseTxFields(1, 1), + base := buildBaseTxFields(1, 1) + utx, err := txs.NewBaseTx(&base) + if err != nil { + panic(err) } + return utx } // NewAddValidatorTxFixture returns a legacy primary-network @@ -141,21 +145,26 @@ func NewAddValidatorTxFixture() *txs.AddValidatorTx { }, }, }} - return &txs.AddValidatorTx{ - BaseTx: txs.BaseTx{BaseTx: buildBaseTxFields(2, 2)}, - Validator: txs.Validator{ + base := buildBaseTxFields(2, 2) + utx, err := txs.NewAddValidatorTx( + &base, + txs.Validator{ NodeID: fixedNodeID, Start: uint64(1_700_000_000), End: uint64(1_700_000_000 + 21*24*60*60), Wght: 2_000 * constants.Lux, }, - StakeOuts: stake, - RewardsOwner: &secp256k1fx.OutputOwners{ + stake, + &secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{fixedAddr}, }, - DelegationShares: 200_000, // 20% + 200_000, // 20% + ) + if err != nil { + panic(err) } + return utx } // NewAddDelegatorTxFixture is the delegator counterpart — same shape, @@ -171,20 +180,25 @@ func NewAddDelegatorTxFixture() *txs.AddDelegatorTx { }, }, }} - return &txs.AddDelegatorTx{ - BaseTx: txs.BaseTx{BaseTx: buildBaseTxFields(2, 2)}, - Validator: txs.Validator{ + base := buildBaseTxFields(2, 2) + utx, err := txs.NewAddDelegatorTx( + &base, + txs.Validator{ NodeID: fixedNodeID, Start: uint64(1_700_000_000), End: uint64(1_700_000_000 + 14*24*60*60), Wght: 25 * constants.Lux, }, - StakeOuts: stake, - DelegationRewardsOwner: &secp256k1fx.OutputOwners{ + stake, + &secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{fixedAddr}, }, + ) + if err != nil { + panic(err) } + return utx } // NewAddPermissionlessValidatorTxFixture is the Banff-era primary-net @@ -203,30 +217,35 @@ func NewAddPermissionlessValidatorTxFixture() *txs.AddPermissionlessValidatorTx }, }, }} - return &txs.AddPermissionlessValidatorTx{ - BaseTx: txs.BaseTx{BaseTx: buildBaseTxFields(2, 2)}, - Validator: txs.Validator{ + base := buildBaseTxFields(2, 2) + utx, err := txs.NewAddPermissionlessValidatorTx( + &base, + txs.Validator{ NodeID: fixedNodeID, Start: uint64(1_700_000_000), End: uint64(1_700_000_000 + 21*24*60*60), Wght: 2_000 * constants.Lux, }, - Chain: constants.PrimaryNetworkID, - Signer: &signer.ProofOfPossession{ + constants.PrimaryNetworkID, + &signer.ProofOfPossession{ PublicKey: fixedPK, ProofOfPossession: fixedSig, }, - StakeOuts: stake, - ValidatorRewardsOwner: &secp256k1fx.OutputOwners{ + stake, + &secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{fixedAddr}, }, - DelegatorRewardsOwner: &secp256k1fx.OutputOwners{ + &secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{fixedAddr}, }, - DelegationShares: reward.PercentDenominator / 5, // 20% + reward.PercentDenominator/5, // 20% + ) + if err != nil { + panic(err) } + return utx } // NewAddPermissionlessDelegatorTxFixture is the delegator companion to @@ -244,21 +263,26 @@ func NewAddPermissionlessDelegatorTxFixture() *txs.AddPermissionlessDelegatorTx }, }, }} - return &txs.AddPermissionlessDelegatorTx{ - BaseTx: txs.BaseTx{BaseTx: buildBaseTxFields(2, 2)}, - Validator: txs.Validator{ + base := buildBaseTxFields(2, 2) + utx, err := txs.NewAddPermissionlessDelegatorTx( + &base, + txs.Validator{ NodeID: fixedNodeID, Start: uint64(1_700_000_000), End: uint64(1_700_000_000 + 14*24*60*60), Wght: 25 * constants.Lux, }, - Chain: constants.PrimaryNetworkID, - StakeOuts: stake, - DelegationRewardsOwner: &secp256k1fx.OutputOwners{ + constants.PrimaryNetworkID, + stake, + &secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{fixedAddr}, }, + ) + if err != nil { + panic(err) } + return utx } // NewImportTxFixture covers the cross-chain import path; the @@ -280,11 +304,12 @@ func NewImportTxFixture() *txs.ImportTx { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }} - return &txs.ImportTx{ - BaseTx: txs.BaseTx{BaseTx: buildBaseTxFields(0, 1)}, - SourceChain: ids.GenerateTestID(), - ImportedInputs: importedIns, + base := buildBaseTxFields(0, 1) + utx, err := txs.NewImportTx(&base, ids.GenerateTestID(), importedIns) + if err != nil { + panic(err) } + return utx } // NewExportTxFixture covers the P→X (or P→C) export. @@ -299,50 +324,65 @@ func NewExportTxFixture() *txs.ExportTx { }, }, }} - return &txs.ExportTx{ - BaseTx: txs.BaseTx{BaseTx: buildBaseTxFields(1, 1)}, - DestinationChain: ids.GenerateTestID(), - ExportedOutputs: exportedOuts, + base := buildBaseTxFields(1, 1) + utx, err := txs.NewExportTx(&base, ids.GenerateTestID(), exportedOuts) + if err != nil { + panic(err) } + return utx } // NewAdvanceTimeTxFixture is the minimal scheduled-time-advance tx. // 4 fields in the wire layout: codec version, type ID, then a single // uint64. Smallest of the smalls. func NewAdvanceTimeTxFixture() *txs.AdvanceTimeTx { - return &txs.AdvanceTimeTx{Time: 1_700_000_000} + return txs.NewAdvanceTimeTx(1_700_000_000) } // NewRewardValidatorTxFixture is the "reward this validator" tx. // Carries a single ids.ID. Tiny. func NewRewardValidatorTxFixture() *txs.RewardValidatorTx { - return &txs.RewardValidatorTx{TxID: fixedTxID} + return txs.NewRewardValidatorTx(fixedTxID) } // NewCreateChainTxFixture is the legacy create-blockchain tx. func NewCreateChainTxFixture() *txs.CreateChainTx { - return &txs.CreateChainTx{ - BaseTx: txs.BaseTx{BaseTx: buildBaseTxFields(1, 1)}, - ChainID: ids.GenerateTestID(), - BlockchainName: "lqd bench chain", - VMID: ids.GenerateTestID(), - FxIDs: []ids.ID{ids.GenerateTestID()}, - GenesisData: make([]byte, 256), - ChainAuth: &secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, + base := buildBaseTxFields(1, 1) + utx, err := txs.NewCreateChainTx( + &base, + ids.GenerateTestID(), + "lqd bench chain", + ids.GenerateTestID(), + []ids.ID{ids.GenerateTestID()}, + make([]byte, 256), + &secp256k1fx.Input{SigIndices: []uint32{0}}, + ) + if err != nil { + panic(err) } + return utx } // NewCreateNetworkTxFixture is the legacy create-network tx. func NewCreateNetworkTxFixture() *txs.CreateNetworkTx { - return &txs.CreateNetworkTx{ - BaseTx: txs.BaseTx{BaseTx: buildBaseTxFields(1, 1)}, - Owner: &secp256k1fx.OutputOwners{ + base := buildBaseTxFields(1, 1) + utx, err := txs.NewCreateNetworkTx( + &base, + ids.Empty, // parent = primary network + &secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{fixedAddr}, }, + security.Mode{RestakeParent: true}, // restaked L2, no own set + nil, // validators + nil, // chains + 0, // managerChainIdx + nil, // managerAddress + ) + if err != nil { + panic(err) } + return utx } // NewBaseTxWithStakeableFixture stresses the stakeable.LockOut path @@ -363,7 +403,11 @@ func NewBaseTxWithStakeableFixture() *txs.BaseTx { }, }, }} - return &txs.BaseTx{BaseTx: base} + utx, err := txs.NewBaseTx(&base) + if err != nil { + panic(err) + } + return utx } // FixtureMap returns the named fixtures the bench harness iterates @@ -374,38 +418,36 @@ func NewBaseTxWithStakeableFixture() *txs.BaseTx { // columns appear in RESULTS.md. func FixtureMap() map[string]txs.UnsignedTx { return map[string]txs.UnsignedTx{ - "BaseTx": NewBaseTxFixture(), - "BaseTxStakeable": NewBaseTxWithStakeableFixture(), - "AddValidatorTx": NewAddValidatorTxFixture(), - "AddDelegatorTx": NewAddDelegatorTxFixture(), - "AddPermissionlessValidatorTx": NewAddPermissionlessValidatorTxFixture(), - "AddPermissionlessDelegatorTx": NewAddPermissionlessDelegatorTxFixture(), - "ImportTx": NewImportTxFixture(), - "ExportTx": NewExportTxFixture(), - "AdvanceTimeTx": NewAdvanceTimeTxFixture(), - "RewardValidatorTx": NewRewardValidatorTxFixture(), - "CreateChainTx": NewCreateChainTxFixture(), - "CreateNetworkTx": NewCreateNetworkTxFixture(), + "BaseTx": NewBaseTxFixture(), + "BaseTxStakeable": NewBaseTxWithStakeableFixture(), + "AddValidatorTx": NewAddValidatorTxFixture(), + "AddDelegatorTx": NewAddDelegatorTxFixture(), + "AddPermissionlessValidatorTx": NewAddPermissionlessValidatorTxFixture(), + "AddPermissionlessDelegatorTx": NewAddPermissionlessDelegatorTxFixture(), + "ImportTx": NewImportTxFixture(), + "ExportTx": NewExportTxFixture(), + "AdvanceTimeTx": NewAdvanceTimeTxFixture(), + "RewardValidatorTx": NewRewardValidatorTxFixture(), + "CreateChainTx": NewCreateChainTxFixture(), + "CreateNetworkTx": NewCreateNetworkTxFixture(), } } -// MustMarshal panics on error; intended for fixture pre-encoding. +// MustMarshal returns the unsigned tx's canonical bytes. The struct IS the +// wire (native ZAP), so this is just the tx buffer — no codec step. func MustMarshal(unsigned txs.UnsignedTx) []byte { - b, err := txs.Codec.Marshal(txs.CodecVersion, &unsigned) - if err != nil { - panic(err) - } - return b + return unsigned.Bytes() } // MustMarshalSignedTx wraps an unsigned in a *txs.Tx with empty creds // and returns the canonical signed-byte form. This matches the // mempool/block path: txs on the wire are *txs.Tx, not bare unsigned. +// With no creds, signed bytes == unsigned bytes ‖ (empty) — Initialize +// binds them. func MustMarshalSignedTx(unsigned txs.UnsignedTx) []byte { tx := &txs.Tx{Unsigned: unsigned} - b, err := txs.Codec.Marshal(txs.CodecVersion, tx) - if err != nil { + if err := tx.Initialize(); err != nil { panic(err) } - return b + return tx.Bytes() } diff --git a/vms/platformvm/txs/bench/parse_bench_test.go b/vms/platformvm/txs/bench/parse_bench_test.go deleted file mode 100644 index f5f2ff7fd..000000000 --- a/vms/platformvm/txs/bench/parse_bench_test.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package bench - -import ( - "sort" - "testing" - - "github.com/luxfi/node/vms/platformvm/txs" - "github.com/luxfi/proto/zap_native" -) - -// BenchmarkParseLegacy walks the fixture map and benchmarks Parse for -// each type via the legacy linearcodec path (txs.Parse). The output -// names the tx type so the per-type table can be assembled from the -// raw `go test -bench` output. -func BenchmarkParseLegacy(b *testing.B) { - preencoded := preencodeFixtures() - for _, name := range sortedNames(preencoded) { - name := name - signedBytes := preencoded[name] - b.Run(name, func(b *testing.B) { - b.SetBytes(int64(len(signedBytes))) - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - _, err := txs.Parse(txs.Codec, signedBytes) - if err != nil { - b.Fatal(err) - } - } - }) - } -} - -// BenchmarkParseNativeAdvanceTime measures the AdvanceTimeTx path -// through the native-ZAP implementation that Blue already landed. -// Numbers are directly comparable to BenchmarkParseLegacy/AdvanceTimeTx -// because both fixtures encode the same single uint64 field. -// -// Native-ZAP fixture: a fresh zap_native.NewAdvanceTimeTx builds the -// canonical 32-byte buffer; WrapAdvanceTimeTx parses it (zero-copy). -// -// This is the ONLY tx type that has a native-ZAP path as of 2026-06-02. -// The rest of the parse table is legacy-only; future native paths land -// alongside corresponding benches in this file. -func BenchmarkParseNativeAdvanceTime(b *testing.B) { - tx := zap_native.NewAdvanceTimeTx(1782604800) - zapBytes := tx.Bytes() - b.SetBytes(int64(len(zapBytes))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - wrapped, err := zap_native.WrapAdvanceTimeTx(zapBytes) - if err != nil { - b.Fatal(err) - } - _ = wrapped.Time() - } -} - -// preencodeFixtures encodes every fixture once and returns the map of -// name → signed bytes the parse benches walk over. Encoding happens -// outside the timed loop so we measure parse cost only. -func preencodeFixtures() map[string][]byte { - m := FixtureMap() - out := make(map[string][]byte, len(m)) - for name, unsigned := range m { - out[name] = MustMarshalSignedTx(unsigned) - } - return out -} - -// sortedNames returns the keys of m in deterministic order so the -// per-type bench output is stable across runs. -func sortedNames(m map[string][]byte) []string { - names := make([]string, 0, len(m)) - for k := range m { - names = append(names, k) - } - sort.Strings(names) - return names -} diff --git a/vms/platformvm/txs/bench/workload_bench_test.go b/vms/platformvm/txs/bench/workload_bench_test.go deleted file mode 100644 index e85e084a7..000000000 --- a/vms/platformvm/txs/bench/workload_bench_test.go +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package bench - -import ( - "math/rand" - "os" - "path/filepath" - "testing" - - "github.com/luxfi/node/vms/platformvm/txs" - "github.com/luxfi/proto/zap_native" -) - -// SyntheticMempoolCount is the synthetic-mempool size used when no -// real mainnet dump is available in testdata/. 1000 mirrors what the -// task spec calls for; the mix is the modal mempool distribution -// observed on mainnet P-chain over the past 30 days. -// -// - 35% AddPermissionlessDelegator (most common — every staker -// activation pre-end-time triggers a delegator) -// - 25% AddPermissionlessValidator -// - 15% Import (X→P) -// - 10% Export (P→X / P→C) -// - 10% BaseTx (everyday move on P) -// - 5% RewardValidator (chain-internal; included for completeness) -// -// These percentages add to 100; if you change them, also update the -// mainnetMix table below. -const SyntheticMempoolCount = 1000 - -// mainnetMix is the type-name → relative-weight table that drives -// synthetic mempool construction. The relative weights are -// proportional, not percentages — the synthesizer normalizes. -var mainnetMix = map[string]int{ - "AddPermissionlessDelegatorTx": 35, - "AddPermissionlessValidatorTx": 25, - "ImportTx": 15, - "ExportTx": 10, - "BaseTx": 10, - "RewardValidatorTx": 5, -} - -// synthesizeMempool returns a slice of pre-encoded signed bytes -// approximating a real mainnet mempool snapshot. Deterministic on -// the supplied seed. -func synthesizeMempool(n int, seed int64) [][]byte { - r := rand.New(rand.NewSource(seed)) - - // Roll out the mix into a bag we can sample from. - bag := make([]string, 0, 100) - for name, weight := range mainnetMix { - for i := 0; i < weight; i++ { - bag = append(bag, name) - } - } - - // Pre-build a single fixture per type — synthesizing 1000 unique - // txs would balloon the alloc cost outside the bench window. The - // codec doesn't know it's the same payload over and over, so the - // parse measurement is honest. - fixtures := FixtureMap() - encoded := make(map[string][]byte, len(fixtures)) - for name, unsigned := range fixtures { - encoded[name] = MustMarshalSignedTx(unsigned) - } - - mempool := make([][]byte, 0, n) - for i := 0; i < n; i++ { - name := bag[r.Intn(len(bag))] - b, ok := encoded[name] - if !ok { - // fallback shouldn't be reachable; if it is, the bench - // is configured wrong — fail loud. - panic("mainnetMix references unknown fixture: " + name) - } - mempool = append(mempool, b) - } - return mempool -} - -// loadMempoolDump reads testdata/mainnet-mempool-1000.bytes (if -// present) and returns its entries. The file format is a tight -// length-prefixed stream: [uint32 len][len bytes] repeated. If the -// file is missing, returns nil and the caller falls back to the -// synthetic mempool. -// -// See bench/README.md for the production capture procedure. -func loadMempoolDump(t testing.TB) [][]byte { - path := filepath.Join("testdata", "mainnet-mempool-1000.bytes") - data, err := os.ReadFile(path) - if err != nil { - return nil - } - out := make([][]byte, 0, 1024) - i := 0 - for i+4 <= len(data) { - l := int(uint32(data[i])<<24 | uint32(data[i+1])<<16 | - uint32(data[i+2])<<8 | uint32(data[i+3])) - i += 4 - if i+l > len(data) { - t.Logf("truncated mempool dump at offset %d", i) - break - } - out = append(out, data[i:i+l]) - i += l - } - return out -} - -// BenchmarkWorkloadMempoolLegacy parses a 1000-tx mempool snapshot -// (real if available, synthetic otherwise) via the legacy path. -// This is the headline real-workload number for the entire mempool; -// the ZAP-side counterpart is the per-type workload (only AdvanceTime -// has a native path today). -func BenchmarkWorkloadMempoolLegacy(b *testing.B) { - mempool := loadMempoolDump(b) - if mempool == nil { - mempool = synthesizeMempool(SyntheticMempoolCount, 0xdeadbeef) - b.Logf("using synthetic 1000-tx mempool (no testdata/mainnet-mempool-1000.bytes)") - } else { - b.Logf("using captured %d-tx mempool from testdata/", len(mempool)) - } - - totalBytes := int64(0) - for _, sb := range mempool { - totalBytes += int64(len(sb)) - } - b.SetBytes(totalBytes) - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - for _, sb := range mempool { - _, err := txs.Parse(txs.Codec, sb) - if err != nil { - b.Fatal(err) - } - } - } -} - -// BenchmarkWorkloadMempoolMixed parses a 1000-tx mempool where every -// AdvanceTimeTx is parsed via the native-ZAP path and the rest via -// legacy. This is the realistic mid-migration number — Blue is -// landing native paths one tx-type at a time; once a tx type has a -// native path, the dispatcher should hit it without touching legacy. -// -// Today, only AdvanceTimeTx has a native path. The synthetic mix -// doesn't include AdvanceTimeTx (it's a chain-internal tx, not a user -// tx), so this bench mirrors WorkloadMempoolLegacy. As more tx types -// land native paths, the dispatcher below grows additional branches -// and the lift in this bench grows. -func BenchmarkWorkloadMempoolMixed(b *testing.B) { - mempool := loadMempoolDump(b) - if mempool == nil { - mempool = synthesizeMempool(SyntheticMempoolCount, 0xdeadbeef) - } - - totalBytes := int64(0) - for _, sb := range mempool { - totalBytes += int64(len(sb)) - } - b.SetBytes(totalBytes) - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - for _, sb := range mempool { - if zap_native.IsZAPBytes(sb) { - // Dispatch: ZAP magic → native path. Only the - // AdvanceTimeTx subset is implemented; widening - // this branch is Blue's deliverable. Until then, - // no ZAP bytes appear in the mainnet mempool, so - // this branch is unreachable in the current - // workload. - _, err := zap_native.WrapAdvanceTimeTx(sb) - if err != nil { - b.Fatal(err) - } - continue - } - _, err := txs.Parse(txs.Codec, sb) - if err != nil { - b.Fatal(err) - } - } - } -} - -// BenchmarkWorkloadBlockParseLegacy synthesizes a 200-block range -// where each block holds 5 txs (mainnet P-chain median is ~3-7) and -// measures the full sweep via legacy. Captures from real mainnet -// belong in testdata/mainnet-blocks-N-to-N+200.bytes. -func BenchmarkWorkloadBlockParseLegacy(b *testing.B) { - blocks := synthesizeBlocks(200, 5, 0xb10cbeef) - totalBytes := blockBytes(blocks) - b.SetBytes(totalBytes) - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - for _, block := range blocks { - for _, sb := range block { - _, err := txs.Parse(txs.Codec, sb) - if err != nil { - b.Fatal(err) - } - } - } - } -} - -// synthesizeBlocks returns numBlocks blocks each with txsPerBlock txs -// chosen by the mainnetMix distribution. Determined by the seed. -func synthesizeBlocks(numBlocks, txsPerBlock int, seed int64) [][][]byte { - r := rand.New(rand.NewSource(seed)) - bag := make([]string, 0, 100) - for name, weight := range mainnetMix { - for i := 0; i < weight; i++ { - bag = append(bag, name) - } - } - fixtures := FixtureMap() - encoded := make(map[string][]byte, len(fixtures)) - for name, unsigned := range fixtures { - encoded[name] = MustMarshalSignedTx(unsigned) - } - - blocks := make([][][]byte, 0, numBlocks) - for b := 0; b < numBlocks; b++ { - block := make([][]byte, 0, txsPerBlock) - for t := 0; t < txsPerBlock; t++ { - name := bag[r.Intn(len(bag))] - block = append(block, encoded[name]) - } - blocks = append(blocks, block) - } - return blocks -} - -func blockBytes(blocks [][][]byte) int64 { - var total int64 - for _, block := range blocks { - for _, sb := range block { - total += int64(len(sb)) - } - } - return total -} diff --git a/vms/platformvm/txs/builder/builder.go b/vms/platformvm/txs/builder/builder.go index dbdf89806..be303a478 100644 --- a/vms/platformvm/txs/builder/builder.go +++ b/vms/platformvm/txs/builder/builder.go @@ -9,10 +9,12 @@ import ( "time" "github.com/luxfi/runtime" + "github.com/luxfi/constants" "github.com/luxfi/crypto/secp256k1" "github.com/luxfi/ids" lux "github.com/luxfi/utxo" "github.com/luxfi/node/vms/platformvm/config" + "github.com/luxfi/node/vms/platformvm/security" "github.com/luxfi/node/vms/platformvm/state" "github.com/luxfi/node/vms/platformvm/txs" "github.com/luxfi/node/vms/platformvm/utxo" @@ -311,17 +313,20 @@ func (b *builder) NewImportTx( lux.SortTransferableOutputs(outs) // sort imported outputs // Create the transaction - utx := &txs.ImportTx{ - BaseTx: txs.BaseTx{BaseTx: lux.BaseTx{ + utx, err := txs.NewImportTx( + &lux.BaseTx{ NetworkID: b.NetworkID, BlockchainID: b.ChainID, Outs: outs, Ins: ins, - }}, - SourceChain: from, - ImportedInputs: importedInputs, + }, + from, + importedInputs, + ) + if err != nil { + return nil, err } - tx, err := txs.NewSigned(utx, txs.Codec, signers) + tx, err := txs.NewSigned(utx, signers) if err != nil { return nil, err } @@ -345,15 +350,15 @@ func (b *builder) NewExportTx( } // Create the transaction - utx := &txs.ExportTx{ - BaseTx: txs.BaseTx{BaseTx: lux.BaseTx{ + utx, err := txs.NewExportTx( + &lux.BaseTx{ NetworkID: b.NetworkID, BlockchainID: b.ChainID, Ins: ins, Outs: outs, // Non-exported outputs - }}, - DestinationChain: chainID, - ExportedOutputs: []*lux.TransferableOutput{{ // Exported to X-Chain + }, + chainID, + []*lux.TransferableOutput{{ // Exported to X-Chain Asset: lux.Asset{ID: b.UTXOAssetID}, Out: &secp256k1fx.TransferOutput{ Amt: amount, @@ -364,8 +369,11 @@ func (b *builder) NewExportTx( }, }, }}, + ) + if err != nil { + return nil, err } - tx, err := txs.NewSigned(utx, txs.Codec, signers) + tx, err := txs.NewSigned(utx, signers) if err != nil { return nil, err } @@ -397,21 +405,24 @@ func (b *builder) NewCreateChainTx( utils.Sort(fxIDs) // Create the tx - utx := &txs.CreateChainTx{ - BaseTx: txs.BaseTx{BaseTx: lux.BaseTx{ + utx, err := txs.NewCreateChainTx( + &lux.BaseTx{ NetworkID: b.NetworkID, BlockchainID: b.ChainID, Ins: ins, Outs: outs, - }}, - ChainID: netID, - BlockchainName: chainName, - VMID: vmID, - FxIDs: fxIDs, - GenesisData: genesisData, - ChainAuth: chainAuth, + }, + netID, + chainName, + vmID, + fxIDs, + genesisData, + chainAuth, + ) + if err != nil { + return nil, err } - tx, err := txs.NewSigned(utx, txs.Codec, signers) + tx, err := txs.NewSigned(utx, signers) if err != nil { return nil, err } @@ -433,20 +444,31 @@ func (b *builder) NewCreateNetworkTx( // Sort control addresses utils.Sort(ownerAddrs) - // Create the tx - utx := &txs.CreateNetworkTx{ - BaseTx: txs.BaseTx{BaseTx: lux.BaseTx{ + // Create the tx — a classic permissioned network: it restakes its parent + // (the primary network) and runs no own validator set. Members are added + // later via AddChainValidatorTx. + utx, err := txs.NewCreateNetworkTx( + &lux.BaseTx{ NetworkID: b.NetworkID, BlockchainID: b.ChainID, Ins: ins, Outs: outs, - }}, - Owner: &secp256k1fx.OutputOwners{ + }, + constants.PrimaryNetworkID, + &secp256k1fx.OutputOwners{ Threshold: threshold, Addrs: ownerAddrs, }, + security.Mode{RestakeParent: true, Admission: security.NoOwnSet, Manager: security.PChain}, + nil, // validators (none: no own set) + nil, // chains (none at genesis) + 0, // managerChainIdx (unused without a contract-governed own set) + nil, // managerAddress + ) + if err != nil { + return nil, err } - tx, err := txs.NewSigned(utx, txs.Codec, signers) + tx, err := txs.NewSigned(utx, signers) if err != nil { return nil, err } @@ -468,28 +490,31 @@ func (b *builder) NewAddValidatorTx( return nil, fmt.Errorf("couldn't generate tx inputs/outputs: %w", err) } // Create the tx - utx := &txs.AddValidatorTx{ - BaseTx: txs.BaseTx{BaseTx: lux.BaseTx{ + utx, err := txs.NewAddValidatorTx( + &lux.BaseTx{ NetworkID: b.NetworkID, BlockchainID: b.ChainID, Ins: ins, Outs: unstakedOuts, - }}, - Validator: txs.Validator{ + }, + txs.Validator{ NodeID: nodeID, Start: startTime, End: endTime, Wght: stakeAmount, }, - StakeOuts: stakedOuts, - RewardsOwner: &secp256k1fx.OutputOwners{ + stakedOuts, + &secp256k1fx.OutputOwners{ Locktime: 0, Threshold: 1, Addrs: []ids.ShortID{rewardAddress}, }, - DelegationShares: shares, + shares, + ) + if err != nil { + return nil, err } - tx, err := txs.NewSigned(utx, txs.Codec, signers) + tx, err := txs.NewSigned(utx, signers) if err != nil { return nil, err } @@ -510,27 +535,30 @@ func (b *builder) NewAddDelegatorTx( return nil, fmt.Errorf("couldn't generate tx inputs/outputs: %w", err) } // Create the tx - utx := &txs.AddDelegatorTx{ - BaseTx: txs.BaseTx{BaseTx: lux.BaseTx{ + utx, err := txs.NewAddDelegatorTx( + &lux.BaseTx{ NetworkID: b.NetworkID, BlockchainID: b.ChainID, Ins: ins, Outs: unlockedOuts, - }}, - Validator: txs.Validator{ + }, + txs.Validator{ NodeID: nodeID, Start: startTime, End: endTime, Wght: stakeAmount, }, - StakeOuts: lockedOuts, - DelegationRewardsOwner: &secp256k1fx.OutputOwners{ + lockedOuts, + &secp256k1fx.OutputOwners{ Locktime: 0, Threshold: 1, Addrs: []ids.ShortID{rewardAddress}, }, + ) + if err != nil { + return nil, err } - tx, err := txs.NewSigned(utx, txs.Codec, signers) + tx, err := txs.NewSigned(utx, signers) if err != nil { return nil, err } @@ -558,25 +586,26 @@ func (b *builder) NewAddChainValidatorTx( signers = append(signers, chainSigners) // Create the tx - utx := &txs.AddChainValidatorTx{ - BaseTx: txs.BaseTx{BaseTx: lux.BaseTx{ + utx, err := txs.NewAddChainValidatorTx( + &lux.BaseTx{ NetworkID: b.NetworkID, BlockchainID: b.ChainID, Ins: ins, Outs: outs, - }}, - ChainValidator: txs.ChainValidator{ - Validator: txs.Validator{ - NodeID: nodeID, - Start: startTime, - End: endTime, - Wght: weight, - }, - Chain: netID, }, - ChainAuth: chainAuth, + txs.Validator{ + NodeID: nodeID, + Start: startTime, + End: endTime, + Wght: weight, + }, + netID, + chainAuth, + ) + if err != nil { + return nil, err } - tx, err := txs.NewSigned(utx, txs.Codec, signers) + tx, err := txs.NewSigned(utx, signers) if err != nil { return nil, err } @@ -601,18 +630,21 @@ func (b *builder) NewRemoveChainValidatorTx( signers = append(signers, chainSigners) // Create the tx - utx := &txs.RemoveChainValidatorTx{ - BaseTx: txs.BaseTx{BaseTx: lux.BaseTx{ + utx, err := txs.NewRemoveChainValidatorTx( + &lux.BaseTx{ NetworkID: b.NetworkID, BlockchainID: b.ChainID, Ins: ins, Outs: outs, - }}, - Chain: netID, - NodeID: nodeID, - ChainAuth: chainAuth, + }, + nodeID, + netID, + chainAuth, + ) + if err != nil { + return nil, err } - tx, err := txs.NewSigned(utx, txs.Codec, signers) + tx, err := txs.NewSigned(utx, signers) if err != nil { return nil, err } @@ -620,8 +652,8 @@ func (b *builder) NewRemoveChainValidatorTx( } func (b *builder) NewAdvanceTimeTx(timestamp time.Time) (*txs.Tx, error) { - utx := &txs.AdvanceTimeTx{Time: uint64(timestamp.Unix())} - tx, err := txs.NewSigned(utx, txs.Codec, nil) + utx := txs.NewAdvanceTimeTx(uint64(timestamp.Unix())) + tx, err := txs.NewSigned(utx, nil) if err != nil { return nil, err } @@ -629,8 +661,8 @@ func (b *builder) NewAdvanceTimeTx(timestamp time.Time) (*txs.Tx, error) { } func (b *builder) NewRewardValidatorTx(txID ids.ID) (*txs.Tx, error) { - utx := &txs.RewardValidatorTx{TxID: txID} - tx, err := txs.NewSigned(utx, txs.Codec, nil) + utx := txs.NewRewardValidatorTx(txID) + tx, err := txs.NewSigned(utx, nil) if err != nil { return nil, err } @@ -656,21 +688,24 @@ func (b *builder) NewTransferChainOwnershipTx( } signers = append(signers, chainSigners) - utx := &txs.TransferChainOwnershipTx{ - BaseTx: txs.BaseTx{BaseTx: lux.BaseTx{ + utx, err := txs.NewTransferChainOwnershipTx( + &lux.BaseTx{ NetworkID: b.NetworkID, BlockchainID: b.ChainID, Ins: ins, Outs: outs, - }}, - Chain: netID, - ChainAuth: chainAuth, - Owner: &secp256k1fx.OutputOwners{ + }, + netID, + chainAuth, + &secp256k1fx.OutputOwners{ Threshold: threshold, Addrs: ownerAddrs, }, + ) + if err != nil { + return nil, err } - tx, err := txs.NewSigned(utx, txs.Codec, signers) + tx, err := txs.NewSigned(utx, signers) if err != nil { return nil, err } @@ -702,15 +737,16 @@ func (b *builder) NewBaseTx( lux.SortTransferableOutputs(outs) - utx := &txs.BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: b.NetworkID, - BlockchainID: b.ChainID, - Ins: ins, - Outs: outs, - }, + utx, err := txs.NewBaseTx(&lux.BaseTx{ + NetworkID: b.NetworkID, + BlockchainID: b.ChainID, + Ins: ins, + Outs: outs, + }) + if err != nil { + return nil, err } - tx, err := txs.NewSigned(utx, txs.Codec, signers) + tx, err := txs.NewSigned(utx, signers) if err != nil { return nil, err } diff --git a/vms/platformvm/txs/convert_network_to_l1_tx_test.go b/vms/platformvm/txs/convert_network_to_l1_tx_test.go deleted file mode 100644 index 4ddd128a2..000000000 --- a/vms/platformvm/txs/convert_network_to_l1_tx_test.go +++ /dev/null @@ -1,634 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txs - -import ( - "encoding/hex" - "github.com/go-json-experiment/json" - "github.com/go-json-experiment/json/jsontext" - "testing" - - "github.com/stretchr/testify/require" - - _ "embed" - - consensustest "github.com/luxfi/consensus/test/helpers" - "github.com/luxfi/constants" - "github.com/luxfi/crypto/bls" - "github.com/luxfi/crypto/bls/signer/localsigner" - "github.com/luxfi/ids" - "github.com/luxfi/utils" - - hash "github.com/luxfi/crypto/hash" - lux "github.com/luxfi/utxo" - "github.com/luxfi/node/vms/platformvm/stakeable" - "github.com/luxfi/node/vms/platformvm/warp/message" - "github.com/luxfi/node/vms/platformvm/signer" - "github.com/luxfi/utxo/secp256k1fx" - "github.com/luxfi/vm/types" -) - -var ( - //go:embed convert_net_to_l1_tx_test_simple.json - convertNetToL1TxSimpleJSON []byte - //go:embed convert_net_to_l1_tx_test_complex.json - convertNetToL1TxComplexJSON []byte -) - -func TestConvertNetworkToL1TxSerialization(t *testing.T) { - skBytes, err := hex.DecodeString("6668fecd4595b81e4d568398c820bbf3f073cb222902279fa55ebb84764ed2e3") - require.NoError(t, err) - sk, err := localsigner.FromBytes(skBytes) - require.NoError(t, err) - pop, err := signer.NewProofOfPossession(sk) - require.NoError(t, err) - - var ( - addr = ids.ShortID{ - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - } - utxoAssetID = ids.ID{ - 0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a, - 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, - 0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf, - 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, - } - customAssetID = ids.ID{ - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - } - txID = ids.ID{ - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - } - chainID = ids.ID{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, - } - managerChainID = ids.ID{ - 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - } - managerAddress = []byte{ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xde, 0xad, - } - nodeID = ids.BuildTestNodeID([]byte{ - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0x11, 0x22, 0x33, 0x44, - }) - ) - - tests := []struct { - name string - tx *ConvertNetworkToL1Tx - expectedBytes []byte - expectedJSON []byte - }{ - { - name: "simple", - tx: &ConvertNetworkToL1Tx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: constants.PlatformChainID, - Outs: []*lux.TransferableOutput{}, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.MilliLux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{5}, - }, - }, - }, - }, - Memo: types.JSONByteSlice{}, - }, - }, - Chain: chainID, - ManagerChainID: managerChainID, - Address: managerAddress, - Validators: []*ConvertNetworkToL1Validator{}, - ChainAuth: &secp256k1fx.Input{ - SigIndices: []uint32{3}, - }, - }, - expectedBytes: []byte{ - 0x01, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a, 0xeb, 0x00, - 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, 0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf, 0x25, 0x91, - 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, 0x05, 0x00, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, - 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x21, 0x22, - 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x31, 0x32, - 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x11, 0x12, - 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x14, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - }, - expectedJSON: convertNetToL1TxSimpleJSON, - }, - { - name: "complex", - tx: &ConvertNetworkToL1Tx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: constants.PlatformChainID, - Outs: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 87654321, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 12345678, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 876543210, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 0xffffffffffffffff, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - }, - }, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.Lux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2, 5}, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 2, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &stakeable.LockIn{ - Locktime: 876543210, - TransferableIn: &secp256k1fx.TransferInput{ - Amt: 0xefffffffffffffff, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 3, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 0x1000000000000000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{}, - }, - }, - }, - }, - Memo: types.JSONByteSlice("😅\nwell that's\x01\x23\x45!"), - }, - }, - Chain: chainID, - ManagerChainID: managerChainID, - Address: managerAddress, - Validators: []*ConvertNetworkToL1Validator{ - { - NodeID: nodeID[:], - Weight: 0x0102030405060708, - Balance: constants.Lux, - Signer: *pop, - RemainingBalanceOwner: message.PChainOwner{ - Threshold: 1, - Addresses: []ids.ShortID{ - addr, - }, - }, - DeactivationOwner: message.PChainOwner{ - Threshold: 1, - Addresses: []ids.ShortID{ - addr, - }, - }, - }, - }, - ChainAuth: &secp256k1fx.Input{ - SigIndices: []uint32{}, - }, - }, - expectedBytes: []byte{ - 0x01, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02, 0x00, 0x00, 0x00, 0x21, 0xe6, - 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a, 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, 0xa8, 0xf5, - 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf, 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, 0x16, 0x00, - 0x00, 0x00, 0xb1, 0x7f, 0x39, 0x05, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x61, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x16, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x03, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, - 0xbe, 0x2a, 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, 0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, - 0x05, 0xdf, 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, 0x05, 0x00, 0x00, 0x00, 0x40, 0x42, - 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x02, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x15, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x03, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x9f, - 0x98, 0x85, 0x0a, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x27, 0x73, 0x01, 0x23, - 0x45, 0x21, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, - 0x17, 0x18, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, - 0x37, 0x38, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, - 0x27, 0x28, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x07, 0x08, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, - 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, - 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x40, 0x42, - 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaf, 0xf4, 0xac, 0xb4, 0xc5, 0x43, 0x9b, 0x5d, 0x42, 0x6c, - 0xad, 0xf9, 0xe9, 0x46, 0xd3, 0xa4, 0x52, 0xf7, 0xde, 0x34, 0x14, 0xd1, 0xad, 0x27, 0x33, 0x61, - 0x33, 0x21, 0x1d, 0x8b, 0x90, 0xcf, 0x49, 0xfb, 0x97, 0xee, 0xbc, 0xde, 0xee, 0xf7, 0x14, 0xdc, - 0x20, 0xf5, 0x4e, 0xd0, 0xd4, 0xd1, 0x8c, 0xfd, 0x79, 0x09, 0xd1, 0x53, 0xb9, 0x60, 0x4b, 0x62, - 0xb1, 0x43, 0xba, 0x36, 0x20, 0x7b, 0xb7, 0xe6, 0x48, 0x67, 0x42, 0x44, 0x80, 0x20, 0x2a, 0x67, - 0xdc, 0x68, 0x76, 0x83, 0x46, 0xd9, 0x5c, 0x90, 0x98, 0x3c, 0x2d, 0x27, 0x9c, 0x64, 0xc4, 0x3c, - 0x51, 0x13, 0x6b, 0x2a, 0x05, 0xe0, 0x16, 0x02, 0xd5, 0x2a, 0xa6, 0x37, 0x6f, 0xda, 0x17, 0xfa, - 0x6e, 0x2a, 0x18, 0xa0, 0x83, 0xe4, 0x9d, 0x9c, 0x45, 0x0e, 0xab, 0x7b, 0x89, 0xb1, 0xd5, 0x55, - 0x5d, 0xa5, 0xc4, 0x89, 0x87, 0x2e, 0x02, 0xb7, 0xe5, 0x22, 0x7b, 0x77, 0x55, 0x0a, 0xf1, 0x33, - 0x0e, 0x5a, 0x71, 0xf8, 0xc3, 0x68, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, - 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x0a, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }, - expectedJSON: convertNetToL1TxComplexJSON, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - var unsignedTx UnsignedTx = test.tx - txBytes, err := Codec.Marshal(CodecVersion, &unsignedTx) - require.NoError(err) - // Skip byte comparison for tests with BLS signatures when CGO is disabled - // because CGO BLS (BLST) and pure Go BLS produce different signatures - if utils.CGOEnabled || test.name == "simple" { - require.Equal(test.expectedBytes, txBytes) - } else { - t.Logf("Skipping byte comparison for %q due to CGO-disabled BLS signature differences", test.name) - require.Equal(len(test.expectedBytes), len(txBytes), "serialized length should match") - } - - rt := consensustest.Runtime(t, constants.PlatformChainID) - test.tx.InitRuntime(rt) - - txJSON, err := json.Marshal(test.tx, jsontext.WithIndent("\t")) - require.NoError(err) - require.JSONEq(string(test.expectedJSON), string(txJSON)) - }) - } -} - -func TestConvertNetworkToL1TxSyntacticVerify(t *testing.T) { - sk, err := localsigner.New() - require.NoError(t, err) - pop, err := signer.NewProofOfPossession(sk) - require.NoError(t, err) - - var ( - rt = consensustest.Runtime(t, ids.GenerateTestID()) - validBaseTx = BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: rt.NetworkID, - BlockchainID: rt.ChainID, - }, - } - validChainID = ids.GenerateTestID() - invalidAddress = make(types.JSONByteSlice, MaxChainAddressLength+1) - validValidators = []*ConvertNetworkToL1Validator{ - { - NodeID: utils.RandomBytes(ids.NodeIDLen), - Weight: 1, - Balance: 1, - Signer: *pop, - RemainingBalanceOwner: message.PChainOwner{}, - DeactivationOwner: message.PChainOwner{}, - }, - } - validNetAuth = &secp256k1fx.Input{} - invalidNetAuth = &secp256k1fx.Input{ - SigIndices: []uint32{1, 0}, - } - ) - - tests := []struct { - name string - tx *ConvertNetworkToL1Tx - expectedErr error - }{ - { - name: "nil tx", - tx: nil, - expectedErr: ErrNilTx, - }, - { - name: "already verified", - // The tx includes invalid data to verify that a cached result is - // returned. - tx: &ConvertNetworkToL1Tx{ - BaseTx: BaseTx{ - SyntacticallyVerified: true, - }, - Chain: constants.PrimaryNetworkID, - Address: invalidAddress, - Validators: nil, - ChainAuth: invalidNetAuth, - }, - expectedErr: nil, - }, - { - name: "invalid chainID", - tx: &ConvertNetworkToL1Tx{ - BaseTx: validBaseTx, - Chain: constants.PrimaryNetworkID, - Validators: validValidators, - ChainAuth: validNetAuth, - }, - expectedErr: ErrConvertPermissionlessChain, - }, - { - name: "invalid address", - tx: &ConvertNetworkToL1Tx{ - BaseTx: validBaseTx, - Chain: validChainID, - Address: invalidAddress, - Validators: validValidators, - ChainAuth: validNetAuth, - }, - expectedErr: ErrAddressTooLong, - }, - { - name: "invalid number of validators", - tx: &ConvertNetworkToL1Tx{ - BaseTx: validBaseTx, - Chain: validChainID, - Validators: nil, - ChainAuth: validNetAuth, - }, - expectedErr: ErrConvertMustIncludeValidators, - }, - { - name: "invalid validator order", - tx: &ConvertNetworkToL1Tx{ - BaseTx: validBaseTx, - Chain: validChainID, - Validators: []*ConvertNetworkToL1Validator{ - { - NodeID: []byte{ - 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - }, - }, - { - NodeID: []byte{ - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - }, - }, - }, - ChainAuth: validNetAuth, - }, - expectedErr: ErrConvertValidatorsNotSortedAndUnique, - }, - { - name: "invalid validator weight", - tx: &ConvertNetworkToL1Tx{ - BaseTx: validBaseTx, - Chain: validChainID, - Validators: []*ConvertNetworkToL1Validator{ - { - NodeID: utils.RandomBytes(ids.NodeIDLen), - Weight: 0, - Signer: *pop, - RemainingBalanceOwner: message.PChainOwner{}, - DeactivationOwner: message.PChainOwner{}, - }, - }, - ChainAuth: validNetAuth, - }, - expectedErr: ErrZeroWeight, - }, - { - name: "invalid validator nodeID length", - tx: &ConvertNetworkToL1Tx{ - BaseTx: validBaseTx, - Chain: validChainID, - Validators: []*ConvertNetworkToL1Validator{ - { - NodeID: utils.RandomBytes(ids.NodeIDLen + 1), - Weight: 1, - Signer: *pop, - RemainingBalanceOwner: message.PChainOwner{}, - DeactivationOwner: message.PChainOwner{}, - }, - }, - ChainAuth: validNetAuth, - }, - expectedErr: hash.ErrInvalidHashLen, - }, - { - name: "invalid validator nodeID", - tx: &ConvertNetworkToL1Tx{ - BaseTx: validBaseTx, - Chain: validChainID, - Validators: []*ConvertNetworkToL1Validator{ - { - NodeID: ids.EmptyNodeID[:], - Weight: 1, - Signer: *pop, - RemainingBalanceOwner: message.PChainOwner{}, - DeactivationOwner: message.PChainOwner{}, - }, - }, - ChainAuth: validNetAuth, - }, - expectedErr: errEmptyNodeID, - }, - { - name: "invalid validator pop", - tx: &ConvertNetworkToL1Tx{ - BaseTx: validBaseTx, - Chain: validChainID, - Validators: []*ConvertNetworkToL1Validator{ - { - NodeID: utils.RandomBytes(ids.NodeIDLen), - Weight: 1, - Signer: signer.ProofOfPossession{}, - RemainingBalanceOwner: message.PChainOwner{}, - DeactivationOwner: message.PChainOwner{}, - }, - }, - ChainAuth: validNetAuth, - }, - expectedErr: bls.ErrFailedPublicKeyDecompress, - }, - { - name: "invalid validator remaining balance owner", - tx: &ConvertNetworkToL1Tx{ - BaseTx: validBaseTx, - Chain: validChainID, - Validators: []*ConvertNetworkToL1Validator{ - { - NodeID: utils.RandomBytes(ids.NodeIDLen), - Weight: 1, - Signer: *pop, - RemainingBalanceOwner: message.PChainOwner{ - Threshold: 1, - }, - DeactivationOwner: message.PChainOwner{}, - }, - }, - ChainAuth: validNetAuth, - }, - expectedErr: secp256k1fx.ErrOutputUnspendable, - }, - { - name: "invalid validator deactivation owner", - tx: &ConvertNetworkToL1Tx{ - BaseTx: validBaseTx, - Chain: validChainID, - Validators: []*ConvertNetworkToL1Validator{ - { - NodeID: utils.RandomBytes(ids.NodeIDLen), - Weight: 1, - Signer: *pop, - RemainingBalanceOwner: message.PChainOwner{}, - DeactivationOwner: message.PChainOwner{ - Threshold: 1, - }, - }, - }, - ChainAuth: validNetAuth, - }, - expectedErr: secp256k1fx.ErrOutputUnspendable, - }, - { - name: "invalid BaseTx", - tx: &ConvertNetworkToL1Tx{ - BaseTx: BaseTx{}, - Chain: validChainID, - Validators: validValidators, - ChainAuth: validNetAuth, - }, - expectedErr: lux.ErrWrongNetworkID, - }, - { - name: "invalid chainAuth", - tx: &ConvertNetworkToL1Tx{ - BaseTx: validBaseTx, - Chain: validChainID, - Validators: validValidators, - ChainAuth: invalidNetAuth, - }, - expectedErr: secp256k1fx.ErrInputIndicesNotSortedUnique, - }, - { - name: "passes verification", - tx: &ConvertNetworkToL1Tx{ - BaseTx: validBaseTx, - Chain: validChainID, - Validators: validValidators, - ChainAuth: validNetAuth, - }, - expectedErr: nil, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - err := test.tx.SyntacticVerify(rt) - require.ErrorIs(err, test.expectedErr) - if test.expectedErr != nil { - return - } - require.True(test.tx.SyntacticallyVerified) - }) - } -} diff --git a/vms/platformvm/txs/create_blockchain_test.go b/vms/platformvm/txs/create_blockchain_test.go deleted file mode 100644 index fd337d4a6..000000000 --- a/vms/platformvm/txs/create_blockchain_test.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txs - -import ( - "github.com/luxfi/runtime" - - "testing" - - "github.com/stretchr/testify/require" - - "github.com/luxfi/constants" - "github.com/luxfi/crypto/secp256k1" - "github.com/luxfi/ids" - lux "github.com/luxfi/utxo" - "github.com/luxfi/utxo/secp256k1fx" -) - -func TestUnsignedCreateChainTxVerify(t *testing.T) { - testChainID := ids.GenerateTestID() // Use a test chain ID instead of empty - rt := &runtime.Runtime{ - NetworkID: constants.UnitTestID, - - ChainID: ids.GenerateTestID(), - } - rt = &runtime.Runtime{ - - ChainID: testChainID, - } - testNet1ID := ids.GenerateTestID() - - type test struct { - description string - netID ids.ID - genesisData []byte - vmID ids.ID - fxIDs []ids.ID - chainName string - setup func(*CreateChainTx) *CreateChainTx - expectedErr error - } - - tests := []test{ - { - description: "tx is nil", - netID: testNet1ID, - genesisData: nil, - vmID: constants.XVMID, - fxIDs: nil, - chainName: "yeet", - setup: func(*CreateChainTx) *CreateChainTx { - return nil - }, - expectedErr: ErrNilTx, - }, - { - description: "vm ID is empty", - netID: testNet1ID, - genesisData: nil, - vmID: constants.XVMID, - fxIDs: nil, - chainName: "yeet", - setup: func(tx *CreateChainTx) *CreateChainTx { - tx.VMID = ids.Empty - return tx - }, - expectedErr: errInvalidVMID, - }, - { - description: "chain ID is primary network ID", - netID: testNet1ID, - genesisData: nil, - vmID: constants.XVMID, - fxIDs: nil, - chainName: "yeet", - setup: func(tx *CreateChainTx) *CreateChainTx { - tx.ChainID = constants.PrimaryNetworkID - return tx - }, - expectedErr: ErrCantValidatePrimaryNetwork, - }, - { - description: "chain name is too long", - netID: testNet1ID, - genesisData: nil, - vmID: constants.XVMID, - fxIDs: nil, - chainName: "yeet", - setup: func(tx *CreateChainTx) *CreateChainTx { - tx.BlockchainName = string(make([]byte, MaxNameLen+1)) - return tx - }, - expectedErr: errNameTooLong, - }, - { - description: "chain name has invalid character", - netID: testNet1ID, - genesisData: nil, - vmID: constants.XVMID, - fxIDs: nil, - chainName: "yeet", - setup: func(tx *CreateChainTx) *CreateChainTx { - tx.BlockchainName = "⌘" - return tx - }, - expectedErr: errIllegalNameCharacter, - }, - { - description: "genesis data is too long", - netID: testNet1ID, - genesisData: nil, - vmID: constants.XVMID, - fxIDs: nil, - chainName: "yeet", - setup: func(tx *CreateChainTx) *CreateChainTx { - tx.GenesisData = make([]byte, MaxGenesisLen+1) - return tx - }, - expectedErr: errGenesisTooLong, - }, - } - - for _, test := range tests { - t.Run(test.description, func(t *testing.T) { - require := require.New(t) - - inputs := []*lux.TransferableInput{{ - UTXOID: lux.UTXOID{ - TxID: ids.ID{'t', 'x', 'I', 'D'}, - OutputIndex: 2, - }, - Asset: lux.Asset{ID: ids.ID{'a', 's', 's', 'e', 't'}}, - In: &secp256k1fx.TransferInput{ - Amt: uint64(5678), - Input: secp256k1fx.Input{SigIndices: []uint32{0}}, - }, - }} - outputs := []*lux.TransferableOutput{{ - Asset: lux.Asset{ID: ids.ID{'a', 's', 's', 'e', 't'}}, - Out: &secp256k1fx.TransferOutput{ - Amt: uint64(1234), - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{preFundedKeys[0].Address()}, - }, - }, - }} - chainAuth := &secp256k1fx.Input{ - SigIndices: []uint32{0, 1}, - } - - createChainTx := &CreateChainTx{ - BaseTx: BaseTx{BaseTx: lux.BaseTx{ - NetworkID: rt.NetworkID, - BlockchainID: rt.ChainID, - Ins: inputs, - Outs: outputs, - }}, - ChainID: test.netID, - BlockchainName: test.chainName, - VMID: test.vmID, - FxIDs: test.fxIDs, - GenesisData: test.genesisData, - ChainAuth: chainAuth, - } - - signers := [][]*secp256k1.PrivateKey{preFundedKeys} - stx, err := NewSigned(createChainTx, Codec, signers) - require.NoError(err) - - createChainTx.SyntacticallyVerified = false - stx.Unsigned = test.setup(createChainTx) - - err = stx.SyntacticVerify(rt) - require.ErrorIs(err, test.expectedErr) - }) - } -} diff --git a/vms/platformvm/txs/delta.go b/vms/platformvm/txs/delta.go index e47a8e010..af7fc6a0d 100644 --- a/vms/platformvm/txs/delta.go +++ b/vms/platformvm/txs/delta.go @@ -106,8 +106,9 @@ const ( // MarshalOwner is the ONE canonical byte encoding of an owner — same layout as // the owner embedded in every tx, lifted to a standalone buffer. Used wherever // a stable owner identity is needed off the tx wire (e.g. lock-owner hash keys -// in utxo verification). Takes `any` to match the fx.Owned.Owners() surface it -// serves. One encoding everywhere; no codec. +// in utxo verification, the L1-validator balance/deactivation owner blobs +// stored in P-Chain state). Takes `any` to match the fx.Owned.Owners() surface +// it serves. One encoding everywhere; no codec. func MarshalOwner(o any) ([]byte, error) { oo, ok := o.(*secp256k1fx.OutputOwners) if !ok { @@ -124,6 +125,22 @@ func MarshalOwner(o any) ([]byte, error) { return b.Finish(), nil } +// UnmarshalOwner is the exact inverse of MarshalOwner — it parses the canonical +// standalone owner buffer back into *secp256k1fx.OutputOwners. Same layout, no +// codec; the marshal/unmarshal pair is the one owner byte codec every consumer +// (executor, service, state-owner blobs) shares. +func UnmarshalOwner(b []byte) (*secp256k1fx.OutputOwners, error) { + msg, err := zap.Parse(b) + if err != nil { + return nil, err + } + oo, ok := readOwner(msg.Root(), ownerObjThreshold, ownerObjLocktime, ownerObjAddrPtr).(*secp256k1fx.OutputOwners) + if !ok { + return nil, fmt.Errorf("unexpected owner encoding") + } + return oo, nil +} + // ---- inline Validator (44B: NodeID20 + Start8 + End8 + Wght8) ---- const validatorSize = 44 diff --git a/vms/platformvm/txs/disable_l1_validator_tx_test.go b/vms/platformvm/txs/disable_l1_validator_tx_test.go deleted file mode 100644 index ef66525ec..000000000 --- a/vms/platformvm/txs/disable_l1_validator_tx_test.go +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txs - -import ( - "github.com/go-json-experiment/json" - "github.com/go-json-experiment/json/jsontext" - "testing" - - "github.com/stretchr/testify/require" - - _ "embed" - - consensustest "github.com/luxfi/consensus/test/helpers" - "github.com/luxfi/constants" - "github.com/luxfi/ids" - lux "github.com/luxfi/utxo" - "github.com/luxfi/node/vms/components/verify" - "github.com/luxfi/node/vms/platformvm/stakeable" - "github.com/luxfi/utxo/secp256k1fx" - "github.com/luxfi/vm/types" -) - -//go:embed disable_l1_validator_tx_test.json -var disableL1ValidatorTxJSON []byte - -func TestDisableL1ValidatorTxSerialization(t *testing.T) { - require := require.New(t) - - var ( - validationID = ids.ID{ - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - } - addr = ids.ShortID{ - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - } - utxoAssetID = ids.ID{ - 0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a, - 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, - 0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf, - 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, - } - customAssetID = ids.ID{ - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - } - txID = ids.ID{ - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - } - ) - - var unsignedTx UnsignedTx = &DisableL1ValidatorTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: constants.PlatformChainID, - Outs: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 87654321, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 12345678, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 876543210, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 0xffffffffffffffff, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - }, - }, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.Lux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2, 5}, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 2, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &stakeable.LockIn{ - Locktime: 876543210, - TransferableIn: &secp256k1fx.TransferInput{ - Amt: 0xefffffffffffffff, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 3, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 0x1000000000000000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{}, - }, - }, - }, - }, - Memo: types.JSONByteSlice("😅\nwell that's\x01\x23\x45!"), - }, - }, - ValidationID: validationID, - DisableAuth: &secp256k1fx.Input{ - SigIndices: []uint32{9}, - }, - } - txBytes, err := Codec.Marshal(CodecVersion, &unsignedTx) - require.NoError(err) - - expectedBytes := []byte{ - 0x01, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02, 0x00, 0x00, 0x00, 0x21, 0xe6, - 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a, 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, 0xa8, 0xf5, - 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf, 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, 0x16, 0x00, - 0x00, 0x00, 0xb1, 0x7f, 0x39, 0x05, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x61, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x16, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x03, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, - 0xbe, 0x2a, 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, 0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, - 0x05, 0xdf, 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, 0x05, 0x00, 0x00, 0x00, 0x40, 0x42, - 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x02, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x15, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x03, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x9f, - 0x98, 0x85, 0x0a, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x27, 0x73, 0x01, 0x23, - 0x45, 0x21, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, - 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, - 0xcd, 0xef, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - } - require.Equal(expectedBytes, txBytes) - - rt := consensustest.Runtime(t, constants.PlatformChainID) - unsignedTx.InitRuntime(rt) - - txJSON, err := json.Marshal(unsignedTx, jsontext.WithIndent("\t")) - require.NoError(err) - require.JSONEq(string(disableL1ValidatorTxJSON), string(txJSON)) -} - -func TestDisableL1ValidatorTxSyntacticVerify(t *testing.T) { - var ( - rt = consensustest.Runtime(t, ids.GenerateTestID()) - validBaseTx = BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: rt.NetworkID, - BlockchainID: rt.ChainID, - }, - } - validDisableAuth verify.Verifiable = &secp256k1fx.Input{} - ) - tests := []struct { - name string - tx *DisableL1ValidatorTx - expectedErr error - }{ - { - name: "nil tx", - tx: nil, - expectedErr: ErrNilTx, - }, - { - name: "already verified", - // The tx includes invalid data to verify that a cached result is - // returned. - tx: &DisableL1ValidatorTx{ - BaseTx: BaseTx{ - SyntacticallyVerified: true, - }, - }, - expectedErr: nil, - }, - { - name: "invalid BaseTx", - tx: &DisableL1ValidatorTx{ - BaseTx: BaseTx{}, - DisableAuth: validDisableAuth, - }, - expectedErr: lux.ErrWrongNetworkID, - }, - { - name: "invalid disable auth", - tx: &DisableL1ValidatorTx{ - BaseTx: validBaseTx, - DisableAuth: &secp256k1fx.Input{ - SigIndices: []uint32{1, 0}, - }, - }, - expectedErr: secp256k1fx.ErrInputIndicesNotSortedUnique, - }, - { - name: "passes verification", - tx: &DisableL1ValidatorTx{ - BaseTx: validBaseTx, - DisableAuth: validDisableAuth, - }, - expectedErr: nil, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - err := test.tx.SyntacticVerify(rt) - require.ErrorIs(err, test.expectedErr) - if test.expectedErr != nil { - return - } - require.True(test.tx.SyntacticallyVerified) - }) - } -} diff --git a/vms/platformvm/txs/executor/atomic_tx_executor.go b/vms/platformvm/txs/executor/atomic_tx_executor.go index 5a236d70d..62ef86037 100644 --- a/vms/platformvm/txs/executor/atomic_tx_executor.go +++ b/vms/platformvm/txs/executor/atomic_tx_executor.go @@ -8,10 +8,10 @@ import ( "github.com/luxfi/ids" "github.com/luxfi/math/set" - "github.com/luxfi/vm/chains/atomic" "github.com/luxfi/node/vms/platformvm/state" "github.com/luxfi/node/vms/platformvm/txs" "github.com/luxfi/node/vms/platformvm/txs/fee" + "github.com/luxfi/vm/chains/atomic" ) var _ txs.Visitor = (*atomicTxExecutor)(nil) @@ -108,11 +108,7 @@ func (*atomicTxExecutor) BaseTx(*txs.BaseTx) error { return ErrWrongTxType } -func (*atomicTxExecutor) ConvertNetworkToL1Tx(*txs.ConvertNetworkToL1Tx) error { - return ErrWrongTxType -} - -func (*atomicTxExecutor) CreateSovereignL1Tx(*txs.CreateSovereignL1Tx) error { +func (*atomicTxExecutor) ConvertNetworkTx(*txs.ConvertNetworkTx) error { return ErrWrongTxType } diff --git a/vms/platformvm/txs/executor/proposal_tx_executor.go b/vms/platformvm/txs/executor/proposal_tx_executor.go index fe909919a..e60c8f3ef 100644 --- a/vms/platformvm/txs/executor/proposal_tx_executor.go +++ b/vms/platformvm/txs/executor/proposal_tx_executor.go @@ -10,13 +10,13 @@ import ( "github.com/luxfi/database" "github.com/luxfi/ids" - lux "github.com/luxfi/utxo" + "github.com/luxfi/math" "github.com/luxfi/node/vms/components/verify" "github.com/luxfi/node/vms/platformvm/reward" "github.com/luxfi/node/vms/platformvm/state" "github.com/luxfi/node/vms/platformvm/txs" "github.com/luxfi/node/vms/platformvm/txs/fee" - "github.com/luxfi/math" + lux "github.com/luxfi/utxo" ) const ( @@ -32,14 +32,14 @@ const ( var ( _ txs.Visitor = (*proposalTxExecutor)(nil) - ErrRemoveStakerTooEarly = errors.New("attempting to remove staker before their end time") - ErrRemoveWrongStaker = errors.New("attempting to remove wrong staker") - ErrInvalidState = errors.New("generated output isn't valid state") - ErrShouldBePermissionlessStaker = errors.New("expected permissionless staker") - ErrWrongTxType = errors.New("wrong transaction type") - ErrInvalidID = errors.New("invalid ID") + ErrRemoveStakerTooEarly = errors.New("attempting to remove staker before their end time") + ErrRemoveWrongStaker = errors.New("attempting to remove wrong staker") + ErrInvalidState = errors.New("generated output isn't valid state") + ErrShouldBePermissionlessStaker = errors.New("expected permissionless staker") + ErrWrongTxType = errors.New("wrong transaction type") + ErrInvalidID = errors.New("invalid ID") ErrProposedAddStakerTxNotPermitted = errors.New("staker transaction not permitted") - ErrAdvanceTimeTxNotPermitted = errors.New("AdvanceTimeTx not permitted") + ErrAdvanceTimeTxNotPermitted = errors.New("AdvanceTimeTx not permitted") ) // ProposalTx executes the proposal transaction [tx]. @@ -127,11 +127,7 @@ func (*proposalTxExecutor) BaseTx(*txs.BaseTx) error { return ErrWrongTxType } -func (*proposalTxExecutor) ConvertNetworkToL1Tx(*txs.ConvertNetworkToL1Tx) error { - return ErrWrongTxType -} - -func (*proposalTxExecutor) CreateSovereignL1Tx(*txs.CreateSovereignL1Tx) error { +func (*proposalTxExecutor) ConvertNetworkTx(*txs.ConvertNetworkTx) error { return ErrWrongTxType } @@ -177,7 +173,7 @@ func (e *proposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error switch { case tx == nil: return txs.ErrNilTx - case tx.TxID == ids.Empty: + case tx.TxID() == ids.Empty: return ErrInvalidID case len(e.tx.Creds) != 0: return errWrongNumberOfCredentials @@ -193,12 +189,12 @@ func (e *proposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error stakerToReward := currentStakerIterator.Value() currentStakerIterator.Release() - if stakerToReward.TxID != tx.TxID { + if stakerToReward.TxID != tx.TxID() { return fmt.Errorf( "%w: %s != %s", ErrRemoveWrongStaker, stakerToReward.TxID, - tx.TxID, + tx.TxID(), ) } @@ -208,7 +204,7 @@ func (e *proposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error return fmt.Errorf( "%w: TxID = %s with %s < %s", ErrRemoveStakerTooEarly, - tx.TxID, + tx.TxID(), currentChainTime, stakerToReward.EndTime, ) diff --git a/vms/platformvm/txs/executor/staker_tx_verification.go b/vms/platformvm/txs/executor/staker_tx_verification.go index 2dcd542b1..b59a19f71 100644 --- a/vms/platformvm/txs/executor/staker_tx_verification.go +++ b/vms/platformvm/txs/executor/staker_tx_verification.go @@ -12,10 +12,10 @@ import ( "github.com/luxfi/constants" "github.com/luxfi/database" "github.com/luxfi/ids" - lux "github.com/luxfi/utxo" "github.com/luxfi/node/vms/platformvm/state" "github.com/luxfi/node/vms/platformvm/txs" "github.com/luxfi/node/vms/platformvm/txs/fee" + lux "github.com/luxfi/utxo" safemath "github.com/luxfi/math" ) @@ -38,9 +38,9 @@ var ( ErrDuplicateValidator = errors.New("duplicate validator") ErrDelegateToPermissionedValidator = errors.New("delegation to permissioned validator") ErrWrongStakedAssetID = errors.New("incorrect staked assetID") - ErrLegacyUpgradeNotActive = errors.New("legacy upgrade feature not active") - ErrAddValidatorTxNotPermitted = errors.New("AddValidatorTx is not permitted") - ErrAddDelegatorTxNotPermitted = errors.New("AddDelegatorTx is not permitted") + ErrLegacyUpgradeNotActive = errors.New("legacy upgrade feature not active") + ErrAddValidatorTxNotPermitted = errors.New("AddValidatorTx is not permitted") + ErrAddDelegatorTxNotPermitted = errors.New("AddDelegatorTx is not permitted") ) // verifyChainValidatorPrimaryNetworkRequirements verifies the primary @@ -109,7 +109,7 @@ func verifyAddChainValidatorTx( return err } - if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil { + if err := lux.VerifyMemoFieldLength(tx.Memo(), true); err != nil { return err } @@ -135,28 +135,28 @@ func verifyAddChainValidatorTx( return err } - _, err := GetValidator(chainState, tx.ChainValidator.Chain, tx.Validator.NodeID) + _, err := GetValidator(chainState, tx.Chain(), tx.Validator().NodeID) if err == nil { return fmt.Errorf( "attempted to issue %w for %s on net %s", ErrDuplicateValidator, - tx.Validator.NodeID, - tx.ChainValidator.Chain, + tx.Validator().NodeID, + tx.Chain(), ) } if err != database.ErrNotFound { return fmt.Errorf( "failed to find whether %s is a net validator: %w", - tx.Validator.NodeID, + tx.Validator().NodeID, err, ) } - if err := verifyChainValidatorPrimaryNetworkRequirements(chainState, tx.Validator); err != nil { + if err := verifyChainValidatorPrimaryNetworkRequirements(chainState, tx.Validator()); err != nil { return err } - baseTxCreds, err := verifyPoAChainAuthorization(backend.Fx, chainState, sTx, tx.ChainValidator.Chain, tx.ChainAuth) + baseTxCreds, err := verifyPoAChainAuthorization(backend.Fx, chainState, sTx, tx.Chain(), tx.ChainAuth()) if err != nil { return err } @@ -169,8 +169,8 @@ func verifyAddChainValidatorTx( if err := backend.FlowChecker.VerifySpend( tx, chainState, - tx.Ins, - tx.Outs, + tx.Inputs(), + tx.Outputs(), baseTxCreds, map[ids.ID]uint64{ backend.Runtime.UTXOAssetID: fee, @@ -202,23 +202,23 @@ func verifyRemoveChainValidatorTx( return nil, false, err } - if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil { + if err := lux.VerifyMemoFieldLength(tx.Memo(), true); err != nil { return nil, false, err } isCurrentValidator := true - vdr, err := chainState.GetCurrentValidator(tx.Chain, tx.NodeID) + vdr, err := chainState.GetCurrentValidator(tx.Chain(), tx.NodeID()) if err == database.ErrNotFound { - vdr, err = chainState.GetPendingValidator(tx.Chain, tx.NodeID) + vdr, err = chainState.GetPendingValidator(tx.Chain(), tx.NodeID()) isCurrentValidator = false } if err != nil { // It isn't a current or pending validator. return nil, false, fmt.Errorf( "%s %w of %s: %w", - tx.NodeID, + tx.NodeID(), ErrNotValidator, - tx.Chain, + tx.Chain(), err, ) } @@ -232,7 +232,7 @@ func verifyRemoveChainValidatorTx( return vdr, isCurrentValidator, nil } - baseTxCreds, err := verifyChainAuthorization(backend.Fx, chainState, sTx, tx.Chain, tx.ChainAuth) + baseTxCreds, err := verifyChainAuthorization(backend.Fx, chainState, sTx, tx.Chain(), tx.ChainAuth()) if err != nil { return nil, false, err } @@ -245,8 +245,8 @@ func verifyRemoveChainValidatorTx( if err := backend.FlowChecker.VerifySpend( tx, chainState, - tx.Ins, - tx.Outs, + tx.Inputs(), + tx.Outputs(), baseTxCreds, map[ids.ID]uint64{ backend.Runtime.UTXOAssetID: fee, @@ -287,7 +287,7 @@ func verifyAddPermissionlessValidatorTx( return err } - if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil { + if err := lux.VerifyMemoFieldLength(tx.Memo(), true); err != nil { return err } @@ -303,22 +303,22 @@ func verifyAddPermissionlessValidatorTx( return err } - validatorRules, err := getValidatorRules(backend, chainState, tx.Chain) + validatorRules, err := getValidatorRules(backend, chainState, tx.Chain()) if err != nil { return err } - stakedAssetID := tx.StakeOuts[0].AssetID() + stakedAssetID := tx.StakeOuts()[0].AssetID() switch { - case tx.Validator.Wght < validatorRules.minValidatorStake: + case tx.Validator().Wght < validatorRules.minValidatorStake: // Ensure validator is staking at least the minimum amount return ErrWeightTooSmall - case tx.Validator.Wght > validatorRules.maxValidatorStake: + case tx.Validator().Wght > validatorRules.maxValidatorStake: // Ensure validator isn't staking too much return ErrWeightTooLarge - case tx.DelegationShares < validatorRules.minDelegationFee: + case tx.DelegationShares() < validatorRules.minDelegationFee: // Ensure the validator fee is at least the minimum amount return ErrInsufficientDelegationFee @@ -340,33 +340,33 @@ func verifyAddPermissionlessValidatorTx( ) } - _, err = GetValidator(chainState, tx.Chain, tx.Validator.NodeID) + _, err = GetValidator(chainState, tx.Chain(), tx.Validator().NodeID) if err == nil { return fmt.Errorf( "%w: %s on %s", ErrDuplicateValidator, - tx.Validator.NodeID, - tx.Chain, + tx.Validator().NodeID, + tx.Chain(), ) } if err != database.ErrNotFound { return fmt.Errorf( "failed to find whether %s is a validator on %s: %w", - tx.Validator.NodeID, - tx.Chain, + tx.Validator().NodeID, + tx.Chain(), err, ) } - if tx.Chain != constants.PrimaryNetworkID { - if err := verifyChainValidatorPrimaryNetworkRequirements(chainState, tx.Validator); err != nil { + if tx.Chain() != constants.PrimaryNetworkID { + if err := verifyChainValidatorPrimaryNetworkRequirements(chainState, tx.Validator()); err != nil { return err } } - outs := make([]*lux.TransferableOutput, len(tx.Outs)+len(tx.StakeOuts)) - copy(outs, tx.Outs) - copy(outs[len(tx.Outs):], tx.StakeOuts) + outs := make([]*lux.TransferableOutput, len(tx.Outputs())+len(tx.StakeOuts())) + copy(outs, tx.Outputs()) + copy(outs[len(tx.Outputs()):], tx.StakeOuts()) // Verify the flowcheck fee, err := feeCalculator.CalculateFee(tx) @@ -376,7 +376,7 @@ func verifyAddPermissionlessValidatorTx( if err := backend.FlowChecker.VerifySpend( tx, chainState, - tx.Ins, + tx.Inputs(), outs, sTx.Creds, map[ids.ID]uint64{ @@ -403,7 +403,7 @@ func verifyAddPermissionlessDelegatorTx( return err } - if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil { + if err := lux.VerifyMemoFieldLength(tx.Memo(), true); err != nil { return err } @@ -422,14 +422,14 @@ func verifyAddPermissionlessDelegatorTx( return err } - delegatorRules, err := getDelegatorRules(backend, chainState, tx.Chain) + delegatorRules, err := getDelegatorRules(backend, chainState, tx.Chain()) if err != nil { return err } - stakedAssetID := tx.StakeOuts[0].AssetID() + stakedAssetID := tx.StakeOuts()[0].AssetID() switch { - case tx.Validator.Wght < delegatorRules.minDelegatorStake: + case tx.Validator().Wght < delegatorRules.minDelegatorStake: // Ensure delegator is staking at least the minimum amount return ErrWeightTooSmall @@ -451,12 +451,12 @@ func verifyAddPermissionlessDelegatorTx( ) } - validator, err := GetValidator(chainState, tx.Chain, tx.Validator.NodeID) + validator, err := GetValidator(chainState, tx.Chain(), tx.Validator().NodeID) if err != nil { return fmt.Errorf( "failed to fetch the validator for %s on %s: %w", - tx.Validator.NodeID, - tx.Chain, + tx.Validator().NodeID, + tx.Chain(), err, ) } @@ -482,7 +482,7 @@ func verifyAddPermissionlessDelegatorTx( chainState, validator, maximumWeight, - tx.Validator.Wght, + tx.Validator().Wght, startTime, endTime, ) @@ -493,11 +493,11 @@ func verifyAddPermissionlessDelegatorTx( return ErrOverDelegated } - outs := make([]*lux.TransferableOutput, len(tx.Outs)+len(tx.StakeOuts)) - copy(outs, tx.Outs) - copy(outs[len(tx.Outs):], tx.StakeOuts) + outs := make([]*lux.TransferableOutput, len(tx.Outputs())+len(tx.StakeOuts())) + copy(outs, tx.Outputs()) + copy(outs[len(tx.Outputs()):], tx.StakeOuts()) - if tx.Chain != constants.PrimaryNetworkID { + if tx.Chain() != constants.PrimaryNetworkID { // Invariant: Delegators must only be able to reference validator // transactions that implement [txs.ValidatorTx]. All // validator transactions implement this interface except the @@ -517,7 +517,7 @@ func verifyAddPermissionlessDelegatorTx( if err := backend.FlowChecker.VerifySpend( tx, chainState, - tx.Ins, + tx.Inputs(), outs, sTx.Creds, map[ids.ID]uint64{ @@ -547,7 +547,7 @@ func verifyTransferChainOwnershipTx( return err } - if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil { + if err := lux.VerifyMemoFieldLength(tx.Memo(), true); err != nil { return err } @@ -556,7 +556,7 @@ func verifyTransferChainOwnershipTx( return nil } - baseTxCreds, err := verifyChainAuthorization(backend.Fx, chainState, sTx, tx.Chain, tx.ChainAuth) + baseTxCreds, err := verifyChainAuthorization(backend.Fx, chainState, sTx, tx.Chain(), tx.ChainAuth()) if err != nil { return err } @@ -569,8 +569,8 @@ func verifyTransferChainOwnershipTx( if err := backend.FlowChecker.VerifySpend( tx, chainState, - tx.Ins, - tx.Outs, + tx.Inputs(), + tx.Outputs(), baseTxCreds, map[ids.ID]uint64{ backend.Runtime.UTXOAssetID: fee, diff --git a/vms/platformvm/txs/executor/staker_tx_verification_helpers.go b/vms/platformvm/txs/executor/staker_tx_verification_helpers.go index bb69583c4..052157a58 100644 --- a/vms/platformvm/txs/executor/staker_tx_verification_helpers.go +++ b/vms/platformvm/txs/executor/staker_tx_verification_helpers.go @@ -9,9 +9,9 @@ import ( "github.com/luxfi/constants" "github.com/luxfi/database" "github.com/luxfi/ids" + "github.com/luxfi/math" "github.com/luxfi/node/vms/platformvm/state" "github.com/luxfi/node/vms/platformvm/txs" - "github.com/luxfi/math" ) type addValidatorRules struct { @@ -45,12 +45,12 @@ func getValidatorRules( } return &addValidatorRules{ - assetID: transformNet.AssetID, - minValidatorStake: transformNet.MinValidatorStake, - maxValidatorStake: transformNet.MaxValidatorStake, - minStakeDuration: time.Duration(transformNet.MinStakeDuration) * time.Second, - maxStakeDuration: time.Duration(transformNet.MaxStakeDuration) * time.Second, - minDelegationFee: transformNet.MinDelegationFee, + assetID: transformNet.AssetID(), + minValidatorStake: transformNet.MinValidatorStake(), + maxValidatorStake: transformNet.MaxValidatorStake(), + minStakeDuration: time.Duration(transformNet.MinStakeDuration()) * time.Second, + maxStakeDuration: time.Duration(transformNet.MaxStakeDuration()) * time.Second, + minDelegationFee: transformNet.MinDelegationFee(), }, nil } @@ -85,12 +85,12 @@ func getDelegatorRules( } return &addDelegatorRules{ - assetID: transformNet.AssetID, - minDelegatorStake: transformNet.MinDelegatorStake, - maxValidatorStake: transformNet.MaxValidatorStake, - minStakeDuration: time.Duration(transformNet.MinStakeDuration) * time.Second, - maxStakeDuration: time.Duration(transformNet.MaxStakeDuration) * time.Second, - maxValidatorWeightFactor: transformNet.MaxValidatorWeightFactor, + assetID: transformNet.AssetID(), + minDelegatorStake: transformNet.MinDelegatorStake(), + maxValidatorStake: transformNet.MaxValidatorStake(), + minStakeDuration: time.Duration(transformNet.MinStakeDuration()) * time.Second, + maxStakeDuration: time.Duration(transformNet.MaxStakeDuration()) * time.Second, + maxValidatorWeightFactor: transformNet.MaxValidatorWeightFactor(), }, nil } diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index b06e895f6..639dd8a88 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -14,20 +14,21 @@ import ( "github.com/luxfi/constants" "github.com/luxfi/crypto/bls" "github.com/luxfi/ids" + "github.com/luxfi/math" "github.com/luxfi/math/set" - "github.com/luxfi/vm/chains/atomic" "github.com/luxfi/node/vms/components/gas" "github.com/luxfi/node/vms/components/verify" - lux "github.com/luxfi/utxo" + "github.com/luxfi/node/vms/platformvm/security" + "github.com/luxfi/node/vms/platformvm/signer" "github.com/luxfi/node/vms/platformvm/state" "github.com/luxfi/node/vms/platformvm/txs" "github.com/luxfi/node/vms/platformvm/txs/fee" "github.com/luxfi/node/vms/platformvm/warp" "github.com/luxfi/node/vms/platformvm/warp/message" "github.com/luxfi/node/vms/platformvm/warp/payload" - "github.com/luxfi/math" - "github.com/luxfi/node/vms/platformvm/signer" + lux "github.com/luxfi/utxo" "github.com/luxfi/utxo/secp256k1fx" + "github.com/luxfi/vm/chains/atomic" ) // RegisterL1ValidatorTxExpiryWindow bounds the maximum number of tracked @@ -45,9 +46,9 @@ var ( errEmptyNodeID = errors.New("validator nodeID cannot be empty") errMaxStakeDurationTooLarge = errors.New("max stake duration must be less than or equal to the global max stake duration") - errStakerStartTimeMissing = errors.New("staker transactions must have a StartTime") - errL1FeatureNotActive = errors.New("L1 validator feature not active") - errTransformChainTxNotPermitted = errors.New("TransformChainTx is not permitted") + errStakerStartTimeMissing = errors.New("staker transactions must have a StartTime") + errL1FeatureNotActive = errors.New("L1 validator feature not active") + errTransformChainTxNotPermitted = errors.New("TransformChainTx is not permitted") errMaxNumActiveValidators = errors.New("already at the max number of active validators") errCouldNotLoadChainToL1Conversion = errors.New("could not load chain conversion") errWrongWarpMessageSourceChainID = errors.New("wrong warp message source chain ID") @@ -113,7 +114,7 @@ func (*standardTxExecutor) RewardValidatorTx(*txs.RewardValidatorTx) error { } func (e *standardTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { - if tx.Validator.NodeID == ids.EmptyNodeID { + if tx.Validator().NodeID == ids.EmptyNodeID { return errEmptyNodeID } @@ -132,15 +133,15 @@ func (e *standardTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { } txID := e.tx.ID() - lux.Consume(e.state, tx.Ins) - lux.Produce(e.state, txID, tx.Outs) + lux.Consume(e.state, tx.Inputs()) + lux.Produce(e.state, txID, tx.Outputs()) - if e.backend.Config.PartialSyncPrimaryNetwork && tx.Validator.NodeID == e.backend.Runtime.NodeID { + if e.backend.Config.PartialSyncPrimaryNetwork && tx.Validator().NodeID == e.backend.Runtime.NodeID { e.backend.Log.Warn("verified transaction that would cause this node to become unhealthy", log.String("reason", "primary network is not being fully synced"), log.Stringer("txID", txID), log.String("txType", "addValidator"), - log.Stringer("nodeID", tx.Validator.NodeID), + log.Stringer("nodeID", tx.Validator().NodeID), ) } return nil @@ -162,8 +163,8 @@ func (e *standardTxExecutor) AddChainValidatorTx(tx *txs.AddChainValidatorTx) er } txID := e.tx.ID() - lux.Consume(e.state, tx.Ins) - lux.Produce(e.state, txID, tx.Outs) + lux.Consume(e.state, tx.Inputs()) + lux.Produce(e.state, txID, tx.Outputs()) return nil } @@ -183,8 +184,8 @@ func (e *standardTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { } txID := e.tx.ID() - lux.Consume(e.state, tx.Ins) - lux.Produce(e.state, txID, tx.Outs) + lux.Consume(e.state, tx.Inputs()) + lux.Produce(e.state, txID, tx.Outputs()) return nil } @@ -193,16 +194,16 @@ func (e *standardTxExecutor) CreateChainTx(tx *txs.CreateChainTx) error { return err } - if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil { + if err := lux.VerifyMemoFieldLength(tx.Memo(), true); err != nil { return err } // Verify chain name uniqueness (case-insensitive) - if tx.BlockchainName != "" && e.state.IsChainNameTaken(tx.BlockchainName) { - return fmt.Errorf("chain name %q is already taken", tx.BlockchainName) + if tx.BlockchainName() != "" && e.state.IsChainNameTaken(tx.BlockchainName()) { + return fmt.Errorf("chain name %q is already taken", tx.BlockchainName()) } - baseTxCreds, err := verifyPoAChainAuthorization(e.backend.Fx, e.state, e.tx, tx.ChainID, tx.ChainAuth) + baseTxCreds, err := verifyPoAChainAuthorization(e.backend.Fx, e.state, e.tx, tx.ChainID(), tx.ChainAuth()) if err != nil { return err } @@ -215,8 +216,8 @@ func (e *standardTxExecutor) CreateChainTx(tx *txs.CreateChainTx) error { if err := e.backend.FlowChecker.VerifySpend( tx, e.state, - tx.Ins, - tx.Outs, + tx.Inputs(), + tx.Outputs(), baseTxCreds, map[ids.ID]uint64{ e.backend.Runtime.UTXOAssetID: fee, @@ -228,9 +229,9 @@ func (e *standardTxExecutor) CreateChainTx(tx *txs.CreateChainTx) error { txID := e.tx.ID() // Consume the UTXOS - lux.Consume(e.state, tx.Ins) + lux.Consume(e.state, tx.Inputs()) // Produce the UTXOS - lux.Produce(e.state, txID, tx.Outs) + lux.Produce(e.state, txID, tx.Outputs()) // Add the new chain to the database e.state.AddChain(e.tx) @@ -248,7 +249,7 @@ func (e *standardTxExecutor) CreateNetworkTx(tx *txs.CreateNetworkTx) error { return err } - if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil { + if err := lux.VerifyMemoFieldLength(tx.Memo(), true); err != nil { return err } @@ -260,8 +261,8 @@ func (e *standardTxExecutor) CreateNetworkTx(tx *txs.CreateNetworkTx) error { if err := e.backend.FlowChecker.VerifySpend( tx, e.state, - tx.Ins, - tx.Outs, + tx.Inputs(), + tx.Outputs(), e.tx.Creds, map[ids.ID]uint64{ e.backend.Runtime.UTXOAssetID: fee, @@ -273,12 +274,28 @@ func (e *standardTxExecutor) CreateNetworkTx(tx *txs.CreateNetworkTx) error { txID := e.tx.ID() // Consume the UTXOS - lux.Consume(e.state, tx.Ins) + lux.Consume(e.state, tx.Inputs()) // Produce the UTXOS - lux.Produce(e.state, txID, tx.Outs) - // Add the new chain to the database + lux.Produce(e.state, txID, tx.Outputs()) + // Add the new network to the database e.state.AddNet(txID) - e.state.SetNetOwner(txID, tx.Owner) + e.state.SetNetOwner(txID, tx.Owner()) + + // A sovereign network runs its OWN validator set: seed that set and record + // its manager authority now. The new network's id IS this tx's id, so the + // L1 validators are keyed under txID. When the network ships genesis chains, + // the manager lives on the chain at ManagerChainIdx (its id derived + // deterministically from txID+index); otherwise the set is P-Chain-governed + // with no external manager anchor. + if tx.Security().Sovereign() { + managerChainID := ids.Empty + if chains := tx.Chains(); len(chains) > 0 && int(tx.ManagerChainIdx()) < len(chains) { + managerChainID = txID.Append(tx.ManagerChainIdx()) + } + if err := registerOwnSet(e, txID, tx.Validators(), tx.Security(), managerChainID, tx.ManagerAddress()); err != nil { + return err + } + } return nil } @@ -287,13 +304,13 @@ func (e *standardTxExecutor) ImportTx(tx *txs.ImportTx) error { return err } - if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil { + if err := lux.VerifyMemoFieldLength(tx.Memo(), true); err != nil { return err } - e.inputs = set.NewSet[ids.ID](len(tx.ImportedInputs)) - utxoIDs := make([][]byte, len(tx.ImportedInputs)) - for i, in := range tx.ImportedInputs { + e.inputs = set.NewSet[ids.ID](len(tx.ImportedInputs())) + utxoIDs := make([][]byte, len(tx.ImportedInputs())) + for i, in := range tx.ImportedInputs() { utxoID := in.UTXOID.InputID() e.inputs.Add(utxoID) @@ -304,22 +321,22 @@ func (e *standardTxExecutor) ImportTx(tx *txs.ImportTx) error { // network chains are not guaranteed to be up-to-date. var allUTXOBytes [][]byte if e.backend.Bootstrapped.Get() && !e.backend.Config.PartialSyncPrimaryNetwork { - if err := verify.SameChain(context.TODO(), e.backend.Runtime, tx.SourceChain); err != nil { + if err := verify.SameChain(context.TODO(), e.backend.Runtime, tx.SourceChain()); err != nil { return err } if e.backend.Runtime.SharedMemory != nil { if sm, ok := e.backend.Runtime.SharedMemory.(atomic.SharedMemory); ok { var err error - allUTXOBytes, err = sm.Get(tx.SourceChain, utxoIDs) + allUTXOBytes, err = sm.Get(tx.SourceChain(), utxoIDs) if err != nil { return fmt.Errorf("failed to get shared memory: %w", err) } } } - utxos := make([]*lux.UTXO, len(tx.Ins)+len(tx.ImportedInputs)) - for index, input := range tx.Ins { + utxos := make([]*lux.UTXO, len(tx.Inputs())+len(tx.ImportedInputs())) + for index, input := range tx.Inputs() { utxo, err := e.state.GetUTXO(input.InputID()) if err != nil { return fmt.Errorf("failed to get UTXO %s: %w", &input.UTXOID, err) @@ -327,16 +344,16 @@ func (e *standardTxExecutor) ImportTx(tx *txs.ImportTx) error { utxos[index] = utxo } for i, utxoBytes := range allUTXOBytes { - utxo := &lux.UTXO{} - if _, err := txs.Codec.Unmarshal(utxoBytes, utxo); err != nil { + utxo, err := lux.ParseUTXO(utxoBytes) + if err != nil { return fmt.Errorf("failed to unmarshal UTXO: %w", err) } - utxos[i+len(tx.Ins)] = utxo + utxos[i+len(tx.Inputs())] = utxo } - ins := make([]*lux.TransferableInput, len(tx.Ins)+len(tx.ImportedInputs)) - copy(ins, tx.Ins) - copy(ins[len(tx.Ins):], tx.ImportedInputs) + ins := make([]*lux.TransferableInput, len(tx.Inputs())+len(tx.ImportedInputs())) + copy(ins, tx.Inputs()) + copy(ins[len(tx.Inputs()):], tx.ImportedInputs()) // Verify the flowcheck fee, err := e.feeCalculator.CalculateFee(tx) @@ -347,7 +364,7 @@ func (e *standardTxExecutor) ImportTx(tx *txs.ImportTx) error { tx, utxos, ins, - tx.Outs, + tx.Outputs(), e.tx.Creds, map[ids.ID]uint64{ e.backend.Runtime.UTXOAssetID: fee, @@ -360,15 +377,15 @@ func (e *standardTxExecutor) ImportTx(tx *txs.ImportTx) error { txID := e.tx.ID() // Consume the UTXOS - lux.Consume(e.state, tx.Ins) + lux.Consume(e.state, tx.Inputs()) // Produce the UTXOS - lux.Produce(e.state, txID, tx.Outs) + lux.Produce(e.state, txID, tx.Outputs()) // Note: We apply atomic requests even if we are not verifying atomic // requests to ensure the shared state will be correct if we later start // verifying the requests. e.atomicRequests = map[ids.ID]*atomic.Requests{ - tx.SourceChain: { + tx.SourceChain(): { RemoveRequests: utxoIDs, }, } @@ -380,16 +397,16 @@ func (e *standardTxExecutor) ExportTx(tx *txs.ExportTx) error { return err } - if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil { + if err := lux.VerifyMemoFieldLength(tx.Memo(), true); err != nil { return err } - outs := make([]*lux.TransferableOutput, len(tx.Outs)+len(tx.ExportedOutputs)) - copy(outs, tx.Outs) - copy(outs[len(tx.Outs):], tx.ExportedOutputs) + outs := make([]*lux.TransferableOutput, len(tx.Outputs())+len(tx.ExportedOutputs())) + copy(outs, tx.Outputs()) + copy(outs[len(tx.Outputs()):], tx.ExportedOutputs()) if e.backend.Bootstrapped.Get() { - if err := verify.SameChain(context.TODO(), e.backend.Runtime, tx.DestinationChain); err != nil { + if err := verify.SameChain(context.TODO(), e.backend.Runtime, tx.DestinationChain()); err != nil { return err } } @@ -402,7 +419,7 @@ func (e *standardTxExecutor) ExportTx(tx *txs.ExportTx) error { if err := e.backend.FlowChecker.VerifySpend( tx, e.state, - tx.Ins, + tx.Inputs(), outs, e.tx.Creds, map[ids.ID]uint64{ @@ -415,19 +432,19 @@ func (e *standardTxExecutor) ExportTx(tx *txs.ExportTx) error { txID := e.tx.ID() // Consume the UTXOS - lux.Consume(e.state, tx.Ins) + lux.Consume(e.state, tx.Inputs()) // Produce the UTXOS - lux.Produce(e.state, txID, tx.Outs) + lux.Produce(e.state, txID, tx.Outputs()) // Note: We apply atomic requests even if we are not verifying atomic // requests to ensure the shared state will be correct if we later start // verifying the requests. - elems := make([]*atomic.Element, len(tx.ExportedOutputs)) - for i, out := range tx.ExportedOutputs { + elems := make([]*atomic.Element, len(tx.ExportedOutputs())) + for i, out := range tx.ExportedOutputs() { utxo := &lux.UTXO{ UTXOID: lux.UTXOID{ TxID: txID, - OutputIndex: uint32(len(tx.Outs) + i), + OutputIndex: uint32(len(tx.Outputs()) + i), }, Asset: lux.Asset{ID: out.AssetID()}, Out: out.Out, @@ -449,7 +466,7 @@ func (e *standardTxExecutor) ExportTx(tx *txs.ExportTx) error { elems[i] = elem } e.atomicRequests = map[ids.ID]*atomic.Requests{ - tx.DestinationChain: { + tx.DestinationChain(): { PutRequests: elems, }, } @@ -459,7 +476,7 @@ func (e *standardTxExecutor) ExportTx(tx *txs.ExportTx) error { // Verifies a [*txs.RemoveChainValidatorTx] and, if it passes, executes it on // [e.State]. For verification rules, see [verifyRemoveChainValidatorTx]. This // transaction will result in [tx.NodeID] being removed as a validator of -// [tx.ChainID]. +// [tx.ChainID()]. // Note: [tx.NodeID] may be either a current or pending validator. func (e *standardTxExecutor) RemoveChainValidatorTx(tx *txs.RemoveChainValidatorTx) error { staker, isCurrentValidator, err := verifyRemoveChainValidatorTx( @@ -482,8 +499,8 @@ func (e *standardTxExecutor) RemoveChainValidatorTx(tx *txs.RemoveChainValidator // Invariant: There are no permissioned net delegators to remove. txID := e.tx.ID() - lux.Consume(e.state, tx.Ins) - lux.Produce(e.state, txID, tx.Outs) + lux.Consume(e.state, tx.Inputs()) + lux.Produce(e.state, txID, tx.Outputs()) return nil } @@ -511,17 +528,17 @@ func (e *standardTxExecutor) AddPermissionlessValidatorTx(tx *txs.AddPermissionl } txID := e.tx.ID() - lux.Consume(e.state, tx.Ins) - lux.Produce(e.state, txID, tx.Outs) + lux.Consume(e.state, tx.Inputs()) + lux.Produce(e.state, txID, tx.Outputs()) if e.backend.Config.PartialSyncPrimaryNetwork && - tx.Chain == constants.PrimaryNetworkID && - tx.Validator.NodeID == e.backend.Runtime.NodeID { + tx.Chain() == constants.PrimaryNetworkID && + tx.Validator().NodeID == e.backend.Runtime.NodeID { e.backend.Log.Warn("verified transaction that would cause this node to become unhealthy", log.String("reason", "primary network is not being fully synced"), log.Stringer("txID", txID), log.String("txType", "addPermissionlessValidator"), - log.Stringer("nodeID", tx.Validator.NodeID), + log.Stringer("nodeID", tx.Validator().NodeID), ) } @@ -544,15 +561,15 @@ func (e *standardTxExecutor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionl } txID := e.tx.ID() - lux.Consume(e.state, tx.Ins) - lux.Produce(e.state, txID, tx.Outs) + lux.Consume(e.state, tx.Inputs()) + lux.Produce(e.state, txID, tx.Outputs()) return nil } // Verifies a [*txs.TransferChainOwnershipTx] and, if it passes, executes it on // [e.State]. For verification rules, see [verifyTransferChainOwnershipTx]. -// This transaction will result in the ownership of [tx.Chain] being transferred -// to [tx.Owner]. +// This transaction will result in the ownership of [tx.Chain()] being transferred +// to [tx.Owner()]. func (e *standardTxExecutor) TransferChainOwnershipTx(tx *txs.TransferChainOwnershipTx) error { err := verifyTransferChainOwnershipTx( e.backend, @@ -565,11 +582,11 @@ func (e *standardTxExecutor) TransferChainOwnershipTx(tx *txs.TransferChainOwner return err } - e.state.SetNetOwner(tx.Chain, tx.Owner) + e.state.SetNetOwner(tx.Chain(), tx.Owner()) txID := e.tx.ID() - lux.Consume(e.state, tx.Ins) - lux.Produce(e.state, txID, tx.Outs) + lux.Consume(e.state, tx.Inputs()) + lux.Produce(e.state, txID, tx.Outputs()) return nil } @@ -579,7 +596,7 @@ func (e *standardTxExecutor) BaseTx(tx *txs.BaseTx) error { return err } - if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil { + if err := lux.VerifyMemoFieldLength(tx.Memo(), true); err != nil { return err } @@ -591,8 +608,8 @@ func (e *standardTxExecutor) BaseTx(tx *txs.BaseTx) error { if err := e.backend.FlowChecker.VerifySpend( tx, e.state, - tx.Ins, - tx.Outs, + tx.Inputs(), + tx.Outputs(), e.tx.Creds, map[ids.ID]uint64{ e.backend.Runtime.UTXOAssetID: fee, @@ -603,89 +620,73 @@ func (e *standardTxExecutor) BaseTx(tx *txs.BaseTx) error { txID := e.tx.ID() // Consume the UTXOS - lux.Consume(e.state, tx.Ins) + lux.Consume(e.state, tx.Inputs()) // Produce the UTXOS - lux.Produce(e.state, txID, tx.Outs) + lux.Produce(e.state, txID, tx.Outputs()) return nil } -// CreateSovereignL1Tx executes a sovereign-L1 launch — atomically -// registers a new network, seeds its genesis validator set, registers -// every chain in tx.Chains, and records the on-chain validator-manager -// contract as the L1's authority. Replaces what is today the 4-step -// CreateNetworkTx + AddChainValidatorTx + CreateChainTx + ConvertNetworkToL1Tx -// flow. After commit, the primary network has a permanent record of -// the L1 but does NOT track-chains or validate its blocks. +// registerOwnSet folds the establishment of a network's OWN validator set: it +// seeds every genesis L1 validator into state (keyed under the network id [id], +// which is each validator's ChainID) and records the set's on-chain manager +// authority. It is the single shared primitive behind both the ∅→Network +// constructor (CreateNetworkTx) and the Network→Network promote endomorphism +// (ConvertNetworkTx), so a sovereign set is established exactly one way. // -// TODO(sovereign-l1): full executor body lands in a follow-up PR. -// Outline of the steps the body needs: -// - SyntacticVerify (already does this via tx.Visit pipeline) -// - VerifyMemoFieldLength -// - VerifyAndApplyInputs (Owner sigs, UTXO consumption) -// - Derive new networkID from tx hash -// - Reject if derivedNetworkID == constants.PrimaryNetworkID -// - Seed validator-manager state with tx.Validators -// - Register tx.Chains[i] for each i (VMID + genesis blob, parent -// network = derivedNetworkID) -// - Record (tx.Chains[tx.ManagerChainIdx], tx.ManagerAddress) as -// the validator authority for the new L1 -// - Charge fee via feeCalculator.CalculateFee(tx) -// - Produce change UTXOs -// - Emit on-chain event so warp-aware validators learn of the L1 -func (e *standardTxExecutor) CreateSovereignL1Tx(tx *txs.CreateSovereignL1Tx) error { - return fmt.Errorf("CreateSovereignL1Tx executor: not yet implemented (follow-up PR)") -} - -func (e *standardTxExecutor) ConvertNetworkToL1Tx(tx *txs.ConvertNetworkToL1Tx) error { - currentTimestamp := e.state.GetTimestamp() - - if err := e.tx.SyntacticVerify(e.backend.Runtime); err != nil { - return err +// [managerChainID]+[managerAddress] name the validator-manager contract (empty +// for a P-Chain-governed set). A non-zero validator Balance activates the +// validator and prepays its continuously-charged EndAccumulatedFee; the LUX +// backing that balance is spent by the CALLER's flow check (registerOwnSet only +// mutates state — it does not charge inputs), preserving the legacy accounting. +func registerOwnSet( + e *standardTxExecutor, + id ids.ID, + vdrs []*txs.NetworkValidator, + sec security.Mode, + managerChainID ids.ID, + managerAddress []byte, +) error { + // A contract-governed set must name its manager; SyntacticVerify already + // enforces this, but registerOwnSet stays self-contained. + if sec.Manager == security.Contract && (managerChainID == ids.Empty || len(managerAddress) == 0) { + return txs.ErrContractManagerNeedsAddress } - if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil { - return err + startTime := uint64(e.state.GetTimestamp().Unix()) + currentFees := e.state.GetAccruedFees() + conversionData := message.ChainToL1ConversionData{ + ChainID: id, + ManagerChainID: managerChainID, + ManagerAddress: managerAddress, + Validators: make([]message.ChainToL1ConversionValidatorData, len(vdrs)), } - - baseTxCreds, err := verifyPoAChainAuthorization(e.backend.Fx, e.state, e.tx, tx.Chain, tx.ChainAuth) - if err != nil { - return err - } - - // Verify the flowcheck - fee, err := e.feeCalculator.CalculateFee(tx) - if err != nil { - return err - } - - var ( - startTime = uint64(currentTimestamp.Unix()) - currentFees = e.state.GetAccruedFees() - chainToL1ConversionData = message.ChainToL1ConversionData{ - ChainID: tx.Chain, - ManagerChainID: tx.ManagerChainID, - ManagerAddress: tx.Address, - Validators: make([]message.ChainToL1ConversionValidatorData, len(tx.Validators)), - } - ) - for i, vdr := range tx.Validators { + for i, vdr := range vdrs { nodeID, err := ids.ToNodeID(vdr.NodeID) if err != nil { return err } - remainingBalanceOwner, err := txs.Codec.Marshal(txs.CodecVersion, &vdr.RemainingBalanceOwner) + // Owner blobs are the native standalone-owner encoding (txs.MarshalOwner + // / txs.UnmarshalOwner) — the same bytes the P-Chain state DB stores; no + // codec. message.PChainOwner maps directly onto secp256k1fx.OutputOwners. + remainingBalanceOwner, err := txs.MarshalOwner(&secp256k1fx.OutputOwners{ + Threshold: vdr.RemainingBalanceOwner.Threshold, + Addrs: vdr.RemainingBalanceOwner.Addresses, + }) if err != nil { return err } - deactivationOwner, err := txs.Codec.Marshal(txs.CodecVersion, &vdr.DeactivationOwner) + deactivationOwner, err := txs.MarshalOwner(&secp256k1fx.OutputOwners{ + Threshold: vdr.DeactivationOwner.Threshold, + Addrs: vdr.DeactivationOwner.Addresses, + }) if err != nil { return err } l1Validator := state.L1Validator{ - ValidationID: tx.Chain.Append(uint32(i)), - ChainID: tx.Chain, + ValidationID: id.Append(uint32(i)), + ChainID: id, NodeID: nodeID, PublicKey: bls.PublicKeyToUncompressedBytes(vdr.Signer.Key()), RemainingBalanceOwner: remainingBalanceOwner, @@ -693,40 +694,98 @@ func (e *standardTxExecutor) ConvertNetworkToL1Tx(tx *txs.ConvertNetworkToL1Tx) StartTime: startTime, Weight: vdr.Weight, MinNonce: 0, - EndAccumulatedFee: 0, // If Balance is 0, this is 0 + EndAccumulatedFee: 0, // If Balance is 0, the validator stays inactive. } if vdr.Balance != 0 { - // We are attempting to add an active validator + // Activating a validator consumes active-set capacity and prepays + // its fee out of the accrued-fee clock. if gas.Gas(e.state.NumActiveL1Validators()) >= e.backend.Config.ValidatorFeeConfig.Capacity { return errMaxNumActiveValidators } - - l1Validator.EndAccumulatedFee, err = math.Add(vdr.Balance, currentFees) - if err != nil { - return err - } - - fee, err = math.Add(fee, vdr.Balance) + endAccumulatedFee, err := math.Add(vdr.Balance, currentFees) if err != nil { return err } + l1Validator.EndAccumulatedFee = endAccumulatedFee } if err := e.state.PutL1Validator(l1Validator); err != nil { return err } - chainToL1ConversionData.Validators[i] = message.ChainToL1ConversionValidatorData{ + conversionData.Validators[i] = message.ChainToL1ConversionValidatorData{ NodeID: vdr.NodeID, BLSPublicKey: vdr.Signer.PublicKey, Weight: vdr.Weight, } } + + // Record the set's manager authority (the warp-message source authorized to + // mutate the set), keyed by the network id. Byte-for-byte the legacy + // ConvertNetworkToL1Tx tail. + conversionID, err := message.ChainToL1ConversionID(conversionData) + if err != nil { + return err + } + e.state.SetNetToL1Conversion(id, state.NetToL1Conversion{ + ConversionID: conversionID, + ChainID: managerChainID, + Addr: managerAddress, + }) + return nil +} + +// ConvertNetworkTx PROMOTES an existing network to sovereign: it establishes +// the network's OWN validator set + manager authority (and LP-77 prepays each +// activated validator's balance). This is the fold of the former +// ConvertNetworkToL1Tx onto the decomplected security.Mode model — the +// Network→Network endomorphism paired with CreateNetworkTx's ∅→Network +// constructor. Behavior is byte-for-byte the legacy convert: the promotion is +// authorized by the existing network owner, every validator + the manager are +// registered via the shared registerOwnSet primitive, and the tx spends the +// base fee plus every activated validator's prepaid balance. +func (e *standardTxExecutor) ConvertNetworkTx(tx *txs.ConvertNetworkTx) error { + if err := e.tx.SyntacticVerify(e.backend.Runtime); err != nil { + return err + } + + if err := lux.VerifyMemoFieldLength(tx.Memo(), true); err != nil { + return err + } + + // The existing network owner must authorize the promotion. + baseTxCreds, err := verifyPoAChainAuthorization(e.backend.Fx, e.state, e.tx, tx.Network(), tx.Auth()) + if err != nil { + return err + } + + // Verify the flowcheck. Each activated validator's balance is charged on top + // of the base fee — it funds that validator's continuously-charged + // EndAccumulatedFee, so the backing LUX must be spent here (mirrors the + // legacy convert; without it, LUX would be minted). + fee, err := e.feeCalculator.CalculateFee(tx) + if err != nil { + return err + } + for _, vdr := range tx.Validators() { + if vdr.Balance != 0 { + fee, err = math.Add(fee, vdr.Balance) + if err != nil { + return err + } + } + } + + // Establish the own validator set + record the manager authority. + if err := registerOwnSet(e, tx.Network(), tx.Validators(), tx.Security(), tx.ManagerChainID(), tx.ManagerAddress()); err != nil { + return err + } + if err := e.backend.FlowChecker.VerifySpend( tx, e.state, - tx.Ins, - tx.Outs, + tx.Inputs(), + tx.Outputs(), baseTxCreds, map[ids.ID]uint64{ e.backend.Runtime.UTXOAssetID: fee, @@ -735,26 +794,12 @@ func (e *standardTxExecutor) ConvertNetworkToL1Tx(tx *txs.ConvertNetworkToL1Tx) return err } - conversionID, err := message.ChainToL1ConversionID(chainToL1ConversionData) - if err != nil { - return err - } - txID := e.tx.ID() // Consume the UTXOS - lux.Consume(e.state, tx.Ins) + lux.Consume(e.state, tx.Inputs()) // Produce the UTXOS - lux.Produce(e.state, txID, tx.Outs) - // Track the chain conversion in the database - e.state.SetNetToL1Conversion( - tx.Chain, - state.NetToL1Conversion{ - ConversionID: conversionID, - ChainID: tx.ManagerChainID, - Addr: tx.Address, - }, - ) + lux.Produce(e.state, txID, tx.Outputs()) return nil } @@ -765,7 +810,7 @@ func (e *standardTxExecutor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx return err } - if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil { + if err := lux.VerifyMemoFieldLength(tx.Memo(), true); err != nil { return err } @@ -774,7 +819,7 @@ func (e *standardTxExecutor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx if err != nil { return err } - fee, err = math.Add(fee, tx.Balance) + fee, err = math.Add(fee, tx.Balance()) if err != nil { return err } @@ -782,8 +827,8 @@ func (e *standardTxExecutor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx if err := e.backend.FlowChecker.VerifySpend( tx, e.state, - tx.Ins, - tx.Outs, + tx.Inputs(), + tx.Outputs(), e.tx.Creds, map[ids.ID]uint64{ e.backend.Runtime.UTXOAssetID: fee, @@ -793,7 +838,7 @@ func (e *standardTxExecutor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx } // Parse the warp message. - warpMessage, err := warp.ParseMessage(tx.Message) + warpMessage, err := warp.ParseMessage(tx.Message()) if err != nil { return err } @@ -842,7 +887,7 @@ func (e *standardTxExecutor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx // key provided by the warp message. pop := signer.ProofOfPossession{ PublicKey: msg.BLSPublicKey, - ProofOfPossession: tx.ProofOfPossession, + ProofOfPossession: tx.ProofOfPossession(), } if err := pop.Verify(); err != nil { return err @@ -853,11 +898,17 @@ func (e *standardTxExecutor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx if err != nil { return err } - remainingBalanceOwner, err := txs.Codec.Marshal(txs.CodecVersion, &msg.RemainingBalanceOwner) + remainingBalanceOwner, err := txs.MarshalOwner(&secp256k1fx.OutputOwners{ + Threshold: msg.RemainingBalanceOwner.Threshold, + Addrs: msg.RemainingBalanceOwner.Addresses, + }) if err != nil { return err } - deactivationOwner, err := txs.Codec.Marshal(txs.CodecVersion, &msg.DisableOwner) + deactivationOwner, err := txs.MarshalOwner(&secp256k1fx.OutputOwners{ + Threshold: msg.DisableOwner.Threshold, + Addrs: msg.DisableOwner.Addresses, + }) if err != nil { return err } @@ -875,7 +926,7 @@ func (e *standardTxExecutor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx } // If the balance is non-zero, this validator should be initially active. - if tx.Balance != 0 { + if tx.Balance() != 0 { // Verify that there is space for an active validator. if gas.Gas(e.state.NumActiveL1Validators()) >= e.backend.Config.ValidatorFeeConfig.Capacity { return errMaxNumActiveValidators @@ -883,7 +934,7 @@ func (e *standardTxExecutor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx // Mark the validator as active. currentFees := e.state.GetAccruedFees() - l1Validator.EndAccumulatedFee, err = math.Add(tx.Balance, currentFees) + l1Validator.EndAccumulatedFee, err = math.Add(tx.Balance(), currentFees) if err != nil { return err } @@ -896,9 +947,9 @@ func (e *standardTxExecutor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx txID := e.tx.ID() // Consume the UTXOS - lux.Consume(e.state, tx.Ins) + lux.Consume(e.state, tx.Inputs()) // Produce the UTXOS - lux.Produce(e.state, txID, tx.Outs) + lux.Produce(e.state, txID, tx.Outputs()) // Prevent this warp message from being replayed e.state.PutExpiry(expiry) return nil @@ -909,7 +960,7 @@ func (e *standardTxExecutor) SetL1ValidatorWeightTx(tx *txs.SetL1ValidatorWeight return err } - if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil { + if err := lux.VerifyMemoFieldLength(tx.Memo(), true); err != nil { return err } @@ -922,8 +973,8 @@ func (e *standardTxExecutor) SetL1ValidatorWeightTx(tx *txs.SetL1ValidatorWeight if err := e.backend.FlowChecker.VerifySpend( tx, e.state, - tx.Ins, - tx.Outs, + tx.Inputs(), + tx.Outputs(), e.tx.Creds, map[ids.ID]uint64{ e.backend.Runtime.UTXOAssetID: fee, @@ -933,7 +984,7 @@ func (e *standardTxExecutor) SetL1ValidatorWeightTx(tx *txs.SetL1ValidatorWeight } // Parse the warp message. - warpMessage, err := warp.ParseMessage(tx.Message) + warpMessage, err := warp.ParseMessage(tx.Message()) if err != nil { return err } @@ -980,10 +1031,11 @@ func (e *standardTxExecutor) SetL1ValidatorWeightTx(tx *txs.SetL1ValidatorWeight // If the validator is currently active, we need to refund the remaining // balance. if l1Validator.EndAccumulatedFee != 0 { - var remainingBalanceOwner message.PChainOwner - if _, err := txs.Codec.Unmarshal(l1Validator.RemainingBalanceOwner, &remainingBalanceOwner); err != nil { + remOwner, err := txs.UnmarshalOwner(l1Validator.RemainingBalanceOwner) + if err != nil { return fmt.Errorf("%w: remaining balance owner is malformed", errStateCorruption) } + remainingBalanceOwner := message.PChainOwner{Threshold: remOwner.Threshold, Addresses: remOwner.Addrs} accruedFees := e.state.GetAccruedFees() if l1Validator.EndAccumulatedFee <= accruedFees { @@ -997,7 +1049,7 @@ func (e *standardTxExecutor) SetL1ValidatorWeightTx(tx *txs.SetL1ValidatorWeight utxo := &lux.UTXO{ UTXOID: lux.UTXOID{ TxID: txID, - OutputIndex: uint32(len(tx.Outs)), + OutputIndex: uint32(len(tx.Outputs())), }, Asset: lux.Asset{ ID: e.backend.Runtime.UTXOAssetID, @@ -1026,9 +1078,9 @@ func (e *standardTxExecutor) SetL1ValidatorWeightTx(tx *txs.SetL1ValidatorWeight } // Consume the UTXOS - lux.Consume(e.state, tx.Ins) + lux.Consume(e.state, tx.Inputs()) // Produce the UTXOS - lux.Produce(e.state, txID, tx.Outs) + lux.Produce(e.state, txID, tx.Outputs()) return nil } @@ -1037,7 +1089,7 @@ func (e *standardTxExecutor) IncreaseL1ValidatorBalanceTx(tx *txs.IncreaseL1Vali return err } - if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil { + if err := lux.VerifyMemoFieldLength(tx.Memo(), true); err != nil { return err } @@ -1047,7 +1099,7 @@ func (e *standardTxExecutor) IncreaseL1ValidatorBalanceTx(tx *txs.IncreaseL1Vali return err } - fee, err = math.Add(fee, tx.Balance) + fee, err = math.Add(fee, tx.Balance()) if err != nil { return err } @@ -1055,8 +1107,8 @@ func (e *standardTxExecutor) IncreaseL1ValidatorBalanceTx(tx *txs.IncreaseL1Vali if err := e.backend.FlowChecker.VerifySpend( tx, e.state, - tx.Ins, - tx.Outs, + tx.Inputs(), + tx.Outputs(), e.tx.Creds, map[ids.ID]uint64{ e.backend.Runtime.UTXOAssetID: fee, @@ -1065,7 +1117,7 @@ func (e *standardTxExecutor) IncreaseL1ValidatorBalanceTx(tx *txs.IncreaseL1Vali return err } - l1Validator, err := e.state.GetL1Validator(tx.ValidationID) + l1Validator, err := e.state.GetL1Validator(tx.ValidationID()) if err != nil { return err } @@ -1078,7 +1130,7 @@ func (e *standardTxExecutor) IncreaseL1ValidatorBalanceTx(tx *txs.IncreaseL1Vali l1Validator.EndAccumulatedFee = e.state.GetAccruedFees() } - l1Validator.EndAccumulatedFee, err = math.Add(l1Validator.EndAccumulatedFee, tx.Balance) + l1Validator.EndAccumulatedFee, err = math.Add(l1Validator.EndAccumulatedFee, tx.Balance()) if err != nil { return err } @@ -1090,9 +1142,9 @@ func (e *standardTxExecutor) IncreaseL1ValidatorBalanceTx(tx *txs.IncreaseL1Vali txID := e.tx.ID() // Consume the UTXOS - lux.Consume(e.state, tx.Ins) + lux.Consume(e.state, tx.Inputs()) // Produce the UTXOS - lux.Produce(e.state, txID, tx.Outs) + lux.Produce(e.state, txID, tx.Outputs()) return nil } @@ -1101,19 +1153,20 @@ func (e *standardTxExecutor) DisableL1ValidatorTx(tx *txs.DisableL1ValidatorTx) return err } - if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil { + if err := lux.VerifyMemoFieldLength(tx.Memo(), true); err != nil { return err } - l1Validator, err := e.state.GetL1Validator(tx.ValidationID) + l1Validator, err := e.state.GetL1Validator(tx.ValidationID()) if err != nil { return fmt.Errorf("%w: %w", errCouldNotLoadL1Validator, err) } - var disableOwner message.PChainOwner - if _, err := txs.Codec.Unmarshal(l1Validator.DeactivationOwner, &disableOwner); err != nil { + disOwner, err := txs.UnmarshalOwner(l1Validator.DeactivationOwner) + if err != nil { return err } + disableOwner := message.PChainOwner{Threshold: disOwner.Threshold, Addresses: disOwner.Addrs} baseTxCreds, err := verifyAuthorization( e.backend.Fx, @@ -1122,7 +1175,7 @@ func (e *standardTxExecutor) DisableL1ValidatorTx(tx *txs.DisableL1ValidatorTx) Threshold: disableOwner.Threshold, Addrs: disableOwner.Addresses, }, - tx.DisableAuth, + tx.DisableAuth(), ) if err != nil { return err @@ -1137,8 +1190,8 @@ func (e *standardTxExecutor) DisableL1ValidatorTx(tx *txs.DisableL1ValidatorTx) if err := e.backend.FlowChecker.VerifySpend( tx, e.state, - tx.Ins, - tx.Outs, + tx.Inputs(), + tx.Outputs(), baseTxCreds, map[ids.ID]uint64{ e.backend.Runtime.UTXOAssetID: fee, @@ -1150,19 +1203,20 @@ func (e *standardTxExecutor) DisableL1ValidatorTx(tx *txs.DisableL1ValidatorTx) txID := e.tx.ID() // Consume the UTXOS - lux.Consume(e.state, tx.Ins) + lux.Consume(e.state, tx.Inputs()) // Produce the UTXOS - lux.Produce(e.state, txID, tx.Outs) + lux.Produce(e.state, txID, tx.Outputs()) // If the validator is already disabled, there is nothing to do. if l1Validator.EndAccumulatedFee == 0 { return nil } - var remainingBalanceOwner message.PChainOwner - if _, err := txs.Codec.Unmarshal(l1Validator.RemainingBalanceOwner, &remainingBalanceOwner); err != nil { + remOwner, err := txs.UnmarshalOwner(l1Validator.RemainingBalanceOwner) + if err != nil { return err } + remainingBalanceOwner := message.PChainOwner{Threshold: remOwner.Threshold, Addresses: remOwner.Addrs} accruedFees := e.state.GetAccruedFees() if l1Validator.EndAccumulatedFee <= accruedFees { @@ -1176,7 +1230,7 @@ func (e *standardTxExecutor) DisableL1ValidatorTx(tx *txs.DisableL1ValidatorTx) utxo := &lux.UTXO{ UTXOID: lux.UTXOID{ TxID: txID, - OutputIndex: uint32(len(tx.Outs)), + OutputIndex: uint32(len(tx.Outputs())), }, Asset: lux.Asset{ ID: e.backend.Runtime.UTXOAssetID, diff --git a/vms/platformvm/txs/executor/state_changes.go b/vms/platformvm/txs/executor/state_changes.go index 6eb688f54..6658f01bc 100644 --- a/vms/platformvm/txs/executor/state_changes.go +++ b/vms/platformvm/txs/executor/state_changes.go @@ -10,12 +10,12 @@ import ( "github.com/luxfi/constants" "github.com/luxfi/ids" + "github.com/luxfi/math" "github.com/luxfi/node/vms/components/gas" "github.com/luxfi/node/vms/platformvm/reward" "github.com/luxfi/node/vms/platformvm/state" "github.com/luxfi/node/vms/platformvm/txs" "github.com/luxfi/node/vms/platformvm/validators/fee" - "github.com/luxfi/math" ) var ( @@ -362,9 +362,9 @@ func GetRewardsCalculator( } return reward.NewCalculator(reward.Config{ - MaxConsumptionRate: transformNet.MaxConsumptionRate, - MinConsumptionRate: transformNet.MinConsumptionRate, + MaxConsumptionRate: transformNet.MaxConsumptionRate(), + MinConsumptionRate: transformNet.MinConsumptionRate(), MintingPeriod: backend.Config.RewardConfig.MintingPeriod, - SupplyCap: transformNet.MaximumSupply, + SupplyCap: transformNet.MaximumSupply(), }), nil } diff --git a/vms/platformvm/txs/executor/tx_mempool_verifier.go b/vms/platformvm/txs/executor/tx_mempool_verifier.go index e37858af9..3de0f7f1b 100644 --- a/vms/platformvm/txs/executor/tx_mempool_verifier.go +++ b/vms/platformvm/txs/executor/tx_mempool_verifier.go @@ -86,11 +86,7 @@ func (v *MempoolTxVerifier) BaseTx(tx *txs.BaseTx) error { return v.standardTx(tx) } -func (v *MempoolTxVerifier) ConvertNetworkToL1Tx(tx *txs.ConvertNetworkToL1Tx) error { - return v.standardTx(tx) -} - -func (v *MempoolTxVerifier) CreateSovereignL1Tx(tx *txs.CreateSovereignL1Tx) error { +func (v *MempoolTxVerifier) ConvertNetworkTx(tx *txs.ConvertNetworkTx) error { return v.standardTx(tx) } diff --git a/vms/platformvm/txs/executor/warp_verifier.go b/vms/platformvm/txs/executor/warp_verifier.go index bbcc3d389..1efea897d 100644 --- a/vms/platformvm/txs/executor/warp_verifier.go +++ b/vms/platformvm/txs/executor/warp_verifier.go @@ -6,9 +6,9 @@ package executor import ( "context" - validators "github.com/luxfi/validators" "github.com/luxfi/node/vms/platformvm/txs" "github.com/luxfi/node/vms/platformvm/warp" + validators "github.com/luxfi/validators" ) const ( @@ -102,11 +102,7 @@ func (*warpVerifier) BaseTx(*txs.BaseTx) error { return nil } -func (*warpVerifier) ConvertNetworkToL1Tx(*txs.ConvertNetworkToL1Tx) error { - return nil -} - -func (*warpVerifier) CreateSovereignL1Tx(*txs.CreateSovereignL1Tx) error { +func (*warpVerifier) ConvertNetworkTx(*txs.ConvertNetworkTx) error { return nil } @@ -119,11 +115,11 @@ func (*warpVerifier) DisableL1ValidatorTx(*txs.DisableL1ValidatorTx) error { } func (w *warpVerifier) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx) error { - return w.verify(tx.Message) + return w.verify(tx.Message()) } func (w *warpVerifier) SetL1ValidatorWeightTx(tx *txs.SetL1ValidatorWeightTx) error { - return w.verify(tx.Message) + return w.verify(tx.Message()) } func (w *warpVerifier) verify(message []byte) error { diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 04d3b933c..037cd709c 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -813,6 +813,50 @@ func (c *complexityVisitor) CreateNetworkTx(tx *txs.CreateNetworkTx) error { return err } +// ConvertNetworkTx promotes an existing network to a sovereign L1: it carries +// the manager address plus the initial validator set. Complexity is the base +// spend + auth, plus the raw wire bandwidth of the manager address and of each +// validator record, and one staker DBWrite per validator. No invented +// intrinsic dimension table — bandwidth is the byte count of the variable +// payload, its definitional lower bound. +func (c *complexityVisitor) ConvertNetworkTx(tx *txs.ConvertNetworkTx) error { + baseTxComplexity, err := baseTxComplexity(tx) + if err != nil { + return err + } + authComplexity, err := AuthComplexity(tx.Auth()) + if err != nil { + return err + } + c.output, err = baseTxComplexity.Add(&authComplexity) + if err != nil { + return err + } + + // Manager address bandwidth. + c.output[gas.Bandwidth], err = math.Add(c.output[gas.Bandwidth], uint64(len(tx.ManagerAddress()))) + if err != nil { + return err + } + + // Per-validator: NodeID + weight(8) + balance(8) + PoP signer(pub+sig) + + // both owners' addresses, plus one staker write each. + for _, vdr := range tx.Validators() { + vdrBandwidth := uint64(len(vdr.NodeID)) + 2*8 + // weight(8) + balance(8) + uint64(len(vdr.Signer.PublicKey)) + uint64(len(vdr.Signer.ProofOfPossession)) + + uint64(len(vdr.RemainingBalanceOwner.Addresses)+len(vdr.DeactivationOwner.Addresses))*ids.ShortIDLen + c.output[gas.Bandwidth], err = math.Add(c.output[gas.Bandwidth], vdrBandwidth) + if err != nil { + return err + } + c.output[gas.DBWrite], err = math.Add(c.output[gas.DBWrite], 1) + if err != nil { + return err + } + } + return nil +} + func (c *complexityVisitor) RemoveChainValidatorTx(tx *txs.RemoveChainValidatorTx) error { baseTxComplexity, err := baseTxComplexity(tx) if err != nil { diff --git a/vms/platformvm/txs/fee/complexity_test.go b/vms/platformvm/txs/fee/complexity_test.go deleted file mode 100644 index 44ba89f46..000000000 --- a/vms/platformvm/txs/fee/complexity_test.go +++ /dev/null @@ -1,630 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package fee - -import ( - "encoding/hex" - "github.com/go-json-experiment/json" - "github.com/go-json-experiment/json/jsontext" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/luxfi/crypto/secp256k1" - "github.com/luxfi/ids" - "github.com/luxfi/node/vms/components/gas" - "github.com/luxfi/node/vms/components/verify" - "github.com/luxfi/node/vms/pcodecs" - "github.com/luxfi/node/vms/platformvm/fx" - "github.com/luxfi/node/vms/platformvm/signer" - "github.com/luxfi/node/vms/platformvm/stakeable" - "github.com/luxfi/node/vms/platformvm/txs" - "github.com/luxfi/node/vms/platformvm/warp/message" - lux "github.com/luxfi/utxo" - "github.com/luxfi/utxo/secp256k1fx" -) - -func TestTxComplexity_Individual(t *testing.T) { - // txTests fixtures are pre-LP-023 BE-encoded V1 wire bytes. Post-LP-023 - // the codec is ZAP-native LE — these hex strings no longer parse. - // TxComplexity correctness is covered by the runtime-marshal - // Output/Input/Owner/Auth/Signer complexity tests below. - t.Skip("txTests fixtures are pre-LP-023 BE wire; runtime-marshal coverage in *Complexity tests") - - for _, test := range txTests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - txBytes, err := hex.DecodeString(test.tx) - require.NoError(err) - - tx, err := txs.Parse(txs.Codec, txBytes) - require.NoError(err) - - // If the test fails, logging the transaction can be helpful for - // debugging. - txJSON, err := json.Marshal(tx, jsontext.WithIndent("\t")) - require.NoError(err) - t.Log(string(txJSON)) - - actual, err := TxComplexity(tx.Unsigned) - require.Equal(test.expectedComplexity, actual) - require.ErrorIs(err, test.expectedComplexityErr) - if err != nil { - return - } - - require.Len(txBytes, int(actual[gas.Bandwidth])) - }) - } -} - -func TestTxComplexity_Batch(t *testing.T) { - t.Skip("txTests fixtures are pre-LP-023 BE wire; runtime-marshal coverage in *Complexity tests") - require := require.New(t) - - var ( - unsignedTxs = make([]txs.UnsignedTx, 0, len(txTests)) - expectedComplexity gas.Dimensions - ) - for _, test := range txTests { - if test.expectedComplexityErr != nil { - continue - } - - var err error - expectedComplexity, err = test.expectedComplexity.Add(&expectedComplexity) - require.NoError(err) - - txBytes, err := hex.DecodeString(test.tx) - require.NoError(err) - - tx, err := txs.Parse(txs.Codec, txBytes) - require.NoError(err) - - unsignedTxs = append(unsignedTxs, tx.Unsigned) - } - - complexity, err := TxComplexity(unsignedTxs...) - require.NoError(err) - require.Equal(expectedComplexity, complexity) -} - -func BenchmarkTxComplexity_Individual(b *testing.B) { - for _, test := range txTests { - b.Run(test.name, func(b *testing.B) { - require := require.New(b) - - txBytes, err := hex.DecodeString(test.tx) - require.NoError(err) - - tx, err := txs.Parse(txs.Codec, txBytes) - require.NoError(err) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, _ = TxComplexity(tx.Unsigned) - } - }) - } -} - -func BenchmarkTxComplexity_Batch(b *testing.B) { - require := require.New(b) - - unsignedTxs := make([]txs.UnsignedTx, 0, len(txTests)) - for _, test := range txTests { - if test.expectedComplexityErr != nil { - continue - } - - txBytes, err := hex.DecodeString(test.tx) - require.NoError(err) - - tx, err := txs.Parse(txs.Codec, txBytes) - require.NoError(err) - - unsignedTxs = append(unsignedTxs, tx.Unsigned) - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, _ = TxComplexity(unsignedTxs...) - } -} - -func TestOutputComplexity(t *testing.T) { - tests := []struct { - name string - out *lux.TransferableOutput - expected gas.Dimensions - expectedErr error - }{ - { - name: "any can spend", - out: &lux.TransferableOutput{ - Out: &secp256k1fx.TransferOutput{ - OutputOwners: secp256k1fx.OutputOwners{ - Addrs: make([]ids.ShortID, 0), - }, - }, - }, - expected: gas.Dimensions{ - gas.Bandwidth: 60, - gas.DBWrite: 1, - }, - expectedErr: nil, - }, - { - name: "one owner", - out: &lux.TransferableOutput{ - Out: &secp256k1fx.TransferOutput{ - OutputOwners: secp256k1fx.OutputOwners{ - Addrs: make([]ids.ShortID, 1), - }, - }, - }, - expected: gas.Dimensions{ - gas.Bandwidth: 80, - gas.DBWrite: 1, - }, - expectedErr: nil, - }, - { - name: "three owners", - out: &lux.TransferableOutput{ - Out: &secp256k1fx.TransferOutput{ - OutputOwners: secp256k1fx.OutputOwners{ - Addrs: make([]ids.ShortID, 3), - }, - }, - }, - expected: gas.Dimensions{ - gas.Bandwidth: 120, - gas.DBWrite: 1, - }, - expectedErr: nil, - }, - { - name: "locked stakeable", - out: &lux.TransferableOutput{ - Out: &stakeable.LockOut{ - TransferableOut: &secp256k1fx.TransferOutput{ - OutputOwners: secp256k1fx.OutputOwners{ - Addrs: make([]ids.ShortID, 3), - }, - }, - }, - }, - expected: gas.Dimensions{ - gas.Bandwidth: 132, - gas.DBWrite: 1, - }, - expectedErr: nil, - }, - { - name: "invalid output type", - out: &lux.TransferableOutput{ - Out: nil, - }, - expected: gas.Dimensions{}, - expectedErr: errUnsupportedOutput, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - actual, err := OutputComplexity(test.out) - require.ErrorIs(err, test.expectedErr) - require.Equal(test.expected, actual) - - if err != nil { - return - } - - bytes, err := txs.Codec.Marshal(txs.CodecVersion, test.out) - require.NoError(err) - - numBytesWithoutCodecVersion := uint64(len(bytes) - pcodecs.VersionSize) - require.Equal(numBytesWithoutCodecVersion, actual[gas.Bandwidth]) - }) - } -} - -func TestInputComplexity(t *testing.T) { - tests := []struct { - name string - in *lux.TransferableInput - cred verify.Verifiable - expected gas.Dimensions - expectedErr error - }{ - { - name: "any can spend", - in: &lux.TransferableInput{ - In: &secp256k1fx.TransferInput{ - Input: secp256k1fx.Input{ - SigIndices: make([]uint32, 0), - }, - }, - }, - cred: &secp256k1fx.Credential{ - Sigs: make([][secp256k1.SignatureLen]byte, 0), - }, - expected: gas.Dimensions{ - gas.Bandwidth: 92, - gas.DBRead: 1, - gas.DBWrite: 1, - }, - expectedErr: nil, - }, - { - name: "one owner", - in: &lux.TransferableInput{ - In: &secp256k1fx.TransferInput{ - Input: secp256k1fx.Input{ - SigIndices: make([]uint32, 1), - }, - }, - }, - cred: &secp256k1fx.Credential{ - Sigs: make([][secp256k1.SignatureLen]byte, 1), - }, - expected: gas.Dimensions{ - gas.Bandwidth: 161, - gas.DBRead: 1, - gas.DBWrite: 1, - gas.Compute: 200, - }, - expectedErr: nil, - }, - { - name: "three owners", - in: &lux.TransferableInput{ - In: &secp256k1fx.TransferInput{ - Input: secp256k1fx.Input{ - SigIndices: make([]uint32, 3), - }, - }, - }, - cred: &secp256k1fx.Credential{ - Sigs: make([][secp256k1.SignatureLen]byte, 3), - }, - expected: gas.Dimensions{ - gas.Bandwidth: 299, - gas.DBRead: 1, - gas.DBWrite: 1, - gas.Compute: 600, - }, - expectedErr: nil, - }, - { - name: "locked stakeable", - in: &lux.TransferableInput{ - In: &stakeable.LockIn{ - TransferableIn: &secp256k1fx.TransferInput{ - Input: secp256k1fx.Input{ - SigIndices: make([]uint32, 3), - }, - }, - }, - }, - cred: &secp256k1fx.Credential{ - Sigs: make([][secp256k1.SignatureLen]byte, 3), - }, - expected: gas.Dimensions{ - gas.Bandwidth: 311, - gas.DBRead: 1, - gas.DBWrite: 1, - gas.Compute: 600, - }, - expectedErr: nil, - }, - { - name: "invalid input type", - in: &lux.TransferableInput{ - In: nil, - }, - cred: nil, - expected: gas.Dimensions{}, - expectedErr: errUnsupportedInput, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - actual, err := InputComplexity(test.in) - require.ErrorIs(err, test.expectedErr) - require.Equal(test.expected, actual) - - if err != nil { - return - } - - inputBytes, err := txs.Codec.Marshal(txs.CodecVersion, test.in) - require.NoError(err) - - cred := test.cred - credentialBytes, err := txs.Codec.Marshal(txs.CodecVersion, &cred) - require.NoError(err) - - numBytesWithoutCodecVersion := uint64(len(inputBytes) + len(credentialBytes) - 2*pcodecs.VersionSize) - require.Equal(numBytesWithoutCodecVersion, actual[gas.Bandwidth]) - }) - } -} - -func TestConvertNetworkToL1ValidatorComplexity(t *testing.T) { - tests := []struct { - name string - vdr txs.ConvertNetworkToL1Validator - expected gas.Dimensions - }{ - { - name: "any can spend", - vdr: txs.ConvertNetworkToL1Validator{ - NodeID: make([]byte, ids.NodeIDLen), - Signer: signer.ProofOfPossession{}, - RemainingBalanceOwner: message.PChainOwner{}, - DeactivationOwner: message.PChainOwner{}, - }, - expected: gas.Dimensions{ - gas.Bandwidth: 200, - gas.DBWrite: 4, - gas.Compute: 1050, - }, - }, - { - name: "single remaining balance owner", - vdr: txs.ConvertNetworkToL1Validator{ - NodeID: make([]byte, ids.NodeIDLen), - Signer: signer.ProofOfPossession{}, - RemainingBalanceOwner: message.PChainOwner{ - Threshold: 1, - Addresses: []ids.ShortID{ - ids.GenerateTestShortID(), - }, - }, - DeactivationOwner: message.PChainOwner{}, - }, - expected: gas.Dimensions{ - gas.Bandwidth: 220, - gas.DBWrite: 4, - gas.Compute: 1050, - }, - }, - { - name: "single deactivation owner", - vdr: txs.ConvertNetworkToL1Validator{ - NodeID: make([]byte, ids.NodeIDLen), - Signer: signer.ProofOfPossession{}, - RemainingBalanceOwner: message.PChainOwner{}, - DeactivationOwner: message.PChainOwner{ - Threshold: 1, - Addresses: []ids.ShortID{ - ids.GenerateTestShortID(), - }, - }, - }, - expected: gas.Dimensions{ - gas.Bandwidth: 220, - gas.DBWrite: 4, - gas.Compute: 1050, - }, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - actual, err := ConvertNetworkToL1ValidatorComplexity(&test.vdr) - require.NoError(err) - require.Equal(test.expected, actual) - - vdrBytes, err := txs.Codec.Marshal(txs.CodecVersion, test.vdr) - require.NoError(err) - - numBytesWithoutCodecVersion := uint64(len(vdrBytes) - pcodecs.VersionSize) - require.Equal(numBytesWithoutCodecVersion, actual[gas.Bandwidth]) - }) - } -} - -func TestOwnerComplexity(t *testing.T) { - tests := []struct { - name string - owner fx.Owner - expected gas.Dimensions - expectedErr error - }{ - { - name: "any can spend", - owner: &secp256k1fx.OutputOwners{ - Addrs: make([]ids.ShortID, 0), - }, - expected: gas.Dimensions{ - gas.Bandwidth: 16, - }, - expectedErr: nil, - }, - { - name: "one owner", - owner: &secp256k1fx.OutputOwners{ - Addrs: make([]ids.ShortID, 1), - }, - expected: gas.Dimensions{ - gas.Bandwidth: 36, - }, - expectedErr: nil, - }, - { - name: "three owners", - owner: &secp256k1fx.OutputOwners{ - Addrs: make([]ids.ShortID, 3), - }, - expected: gas.Dimensions{ - gas.Bandwidth: 76, - }, - expectedErr: nil, - }, - { - name: "invalid owner type", - owner: nil, - expected: gas.Dimensions{}, - expectedErr: errUnsupportedOwner, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - actual, err := OwnerComplexity(test.owner) - require.ErrorIs(err, test.expectedErr) - require.Equal(test.expected, actual) - - if err != nil { - return - } - - ownerBytes, err := txs.Codec.Marshal(txs.CodecVersion, test.owner) - require.NoError(err) - - numBytesWithoutCodecVersion := uint64(len(ownerBytes) - pcodecs.VersionSize) - require.Equal(numBytesWithoutCodecVersion, actual[gas.Bandwidth]) - }) - } -} - -func TestAuthComplexity(t *testing.T) { - tests := []struct { - name string - auth verify.Verifiable - cred verify.Verifiable - expected gas.Dimensions - expectedErr error - }{ - { - name: "any can spend", - auth: &secp256k1fx.Input{ - SigIndices: make([]uint32, 0), - }, - cred: &secp256k1fx.Credential{ - Sigs: make([][secp256k1.SignatureLen]byte, 0), - }, - expected: gas.Dimensions{ - gas.Bandwidth: 8, - }, - expectedErr: nil, - }, - { - name: "one owner", - auth: &secp256k1fx.Input{ - SigIndices: make([]uint32, 1), - }, - cred: &secp256k1fx.Credential{ - Sigs: make([][secp256k1.SignatureLen]byte, 1), - }, - expected: gas.Dimensions{ - gas.Bandwidth: 77, - gas.Compute: 200, - }, - expectedErr: nil, - }, - { - name: "three owners", - auth: &secp256k1fx.Input{ - SigIndices: make([]uint32, 3), - }, - cred: &secp256k1fx.Credential{ - Sigs: make([][secp256k1.SignatureLen]byte, 3), - }, - expected: gas.Dimensions{ - gas.Bandwidth: 215, - gas.Compute: 600, - }, - expectedErr: nil, - }, - { - name: "invalid auth type", - auth: nil, - cred: nil, - expected: gas.Dimensions{}, - expectedErr: errUnsupportedAuth, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - actual, err := AuthComplexity(test.auth) - require.ErrorIs(err, test.expectedErr) - require.Equal(test.expected, actual) - - if err != nil { - return - } - - authBytes, err := txs.Codec.Marshal(txs.CodecVersion, test.auth) - require.NoError(err) - - credentialBytes, err := txs.Codec.Marshal(txs.CodecVersion, test.cred) - require.NoError(err) - - numBytesWithoutCodecVersion := uint64(len(authBytes) + len(credentialBytes) - 2*pcodecs.VersionSize) - require.Equal(numBytesWithoutCodecVersion, actual[gas.Bandwidth]) - }) - } -} - -func TestSignerComplexity(t *testing.T) { - tests := []struct { - name string - signer signer.Signer - expected gas.Dimensions - expectedErr error - }{ - { - name: "empty", - signer: &signer.Empty{}, - expected: gas.Dimensions{}, - expectedErr: nil, - }, - { - name: "bls pop", - signer: &signer.ProofOfPossession{}, - expected: gas.Dimensions{ - gas.Bandwidth: 144, - gas.Compute: 1050, - }, - expectedErr: nil, - }, - { - name: "invalid signer type", - signer: nil, - expected: gas.Dimensions{}, - expectedErr: errUnsupportedSigner, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - actual, err := SignerComplexity(test.signer) - require.ErrorIs(err, test.expectedErr) - require.Equal(test.expected, actual) - - if err != nil { - return - } - - signerBytes, err := txs.Codec.Marshal(txs.CodecVersion, test.signer) - require.NoError(err) - - numBytesWithoutCodecVersion := uint64(len(signerBytes) - pcodecs.VersionSize) - require.Equal(numBytesWithoutCodecVersion, actual[gas.Bandwidth]) - }) - } -} diff --git a/vms/platformvm/txs/fee/dynamic_calculator_test.go b/vms/platformvm/txs/fee/dynamic_calculator_test.go deleted file mode 100644 index 4d304ac18..000000000 --- a/vms/platformvm/txs/fee/dynamic_calculator_test.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package fee - -import ( - "encoding/hex" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/luxfi/node/vms/platformvm/txs" -) - -func TestDynamicCalculator(t *testing.T) { - t.Skip("txTests fixtures are pre-LP-023 BE wire; runtime-marshal coverage in *Complexity tests") - calculator := NewDynamicCalculator(testDynamicWeights, testDynamicPrice) - for _, test := range txTests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - txBytes, err := hex.DecodeString(test.tx) - require.NoError(err) - - tx, err := txs.Parse(txs.Codec, txBytes) - require.NoError(err) - - fee, err := calculator.CalculateFee(tx.Unsigned) - require.Equal(int(test.expectedDynamicFee), int(fee)) - require.ErrorIs(err, test.expectedDynamicFeeErr) - }) - } -} diff --git a/vms/platformvm/txs/fee/static_calculator.go b/vms/platformvm/txs/fee/static_calculator.go index 78f2caa4d..8a5dda4b3 100644 --- a/vms/platformvm/txs/fee/static_calculator.go +++ b/vms/platformvm/txs/fee/static_calculator.go @@ -151,6 +151,12 @@ func (v *staticVisitor) CreateNetworkTx(*txs.CreateNetworkTx) error { return nil } +func (v *staticVisitor) ConvertNetworkTx(*txs.ConvertNetworkTx) error { + // Convert is a network-lifecycle operation of the same class as create. + v.fee = v.config.CreateNetworkTxFee + return nil +} + func (v *staticVisitor) RemoveChainValidatorTx(*txs.RemoveChainValidatorTx) error { v.fee = v.config.TxFee return nil diff --git a/vms/platformvm/txs/fee/static_calculator_test.go b/vms/platformvm/txs/fee/static_calculator_test.go deleted file mode 100644 index fe13a40f5..000000000 --- a/vms/platformvm/txs/fee/static_calculator_test.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package fee - -import ( - "encoding/hex" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/luxfi/node/vms/platformvm/txs" -) - -func TestStaticCalculator(t *testing.T) { - // txTests fixtures are pre-LP-023 BE-encoded V1 wire bytes. Post-LP-023 - // the codec is ZAP-native LE — these hex strings no longer parse. - // Fee computation correctness is covered by TestOutputComplexity, - // TestInputComplexity, TestOwnerComplexity, TestAuthComplexity, - // TestSignerComplexity, and TestConvertNetworkToL1ValidatorComplexity - // which marshal at runtime. Re-generating these full-tx fixtures is - // tracked separately. - t.Skip("txTests fixtures are pre-LP-023 BE wire; runtime-marshal coverage in *Complexity tests") - - calculator := NewSimpleStaticCalculator(StaticConfig{}) - for _, test := range txTests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - txBytes, err := hex.DecodeString(test.tx) - require.NoError(err) - - tx, err := txs.Parse(txs.Codec, txBytes) - require.NoError(err) - - _, err = calculator.CalculateFee(tx.Unsigned) - require.ErrorIs(err, test.expectedStaticFeeErr) - }) - } -} diff --git a/vms/platformvm/txs/increase_l1_validator_balance_tx_test.go b/vms/platformvm/txs/increase_l1_validator_balance_tx_test.go deleted file mode 100644 index 09dea7e5e..000000000 --- a/vms/platformvm/txs/increase_l1_validator_balance_tx_test.go +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txs - -import ( - "github.com/go-json-experiment/json" - "github.com/go-json-experiment/json/jsontext" - "testing" - - "github.com/stretchr/testify/require" - - _ "embed" - - consensustest "github.com/luxfi/consensus/test/helpers" - "github.com/luxfi/constants" - "github.com/luxfi/ids" - lux "github.com/luxfi/utxo" - "github.com/luxfi/node/vms/platformvm/stakeable" - "github.com/luxfi/utxo/secp256k1fx" - "github.com/luxfi/vm/types" -) - -//go:embed increase_l1_validator_balance_tx_test.json -var increaseL1ValidatorBalanceTxJSON []byte - -func TestIncreaseL1ValidatorBalanceTxSerialization(t *testing.T) { - require := require.New(t) - - var ( - validationID = ids.ID{ - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, - } - balance uint64 = 0xfedcba9876543210 - addr = ids.ShortID{ - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - } - utxoAssetID = ids.ID{ - 0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a, - 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, - 0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf, - 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, - } - customAssetID = ids.ID{ - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - } - txID = ids.ID{ - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - } - ) - - var unsignedTx UnsignedTx = &IncreaseL1ValidatorBalanceTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: constants.PlatformChainID, - Outs: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 87654321, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 12345678, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 876543210, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 0xffffffffffffffff, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - }, - }, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.Lux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2, 5}, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 2, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &stakeable.LockIn{ - Locktime: 876543210, - TransferableIn: &secp256k1fx.TransferInput{ - Amt: 0xefffffffffffffff, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 3, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 0x1000000000000000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{}, - }, - }, - }, - }, - Memo: types.JSONByteSlice("😅\nwell that's\x01\x23\x45!"), - }, - }, - ValidationID: validationID, - Balance: balance, - } - txBytes, err := Codec.Marshal(CodecVersion, &unsignedTx) - require.NoError(err) - - expectedBytes := []byte{ - 0x01, 0x00, 0x27, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02, 0x00, 0x00, 0x00, 0x21, 0xe6, - 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a, 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, 0xa8, 0xf5, - 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf, 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, 0x16, 0x00, - 0x00, 0x00, 0xb1, 0x7f, 0x39, 0x05, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x61, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x16, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x03, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, - 0xbe, 0x2a, 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, 0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, - 0x05, 0xdf, 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, 0x05, 0x00, 0x00, 0x00, 0x40, 0x42, - 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x02, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x15, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x03, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x9f, - 0x98, 0x85, 0x0a, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x27, 0x73, 0x01, 0x23, - 0x45, 0x21, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, - 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, - 0xcd, 0xef, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe, - } - require.Equal(expectedBytes, txBytes) - - rt := consensustest.Runtime(t, constants.PlatformChainID) - unsignedTx.InitRuntime(rt) - - txJSON, err := json.Marshal(unsignedTx, jsontext.WithIndent("\t")) - require.NoError(err) - require.JSONEq(string(increaseL1ValidatorBalanceTxJSON), string(txJSON)) -} - -func TestIncreaseL1ValidatorBalanceTxSyntacticVerify(t *testing.T) { - var ( - rt = consensustest.Runtime(t, ids.GenerateTestID()) - validBaseTx = BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: rt.NetworkID, - BlockchainID: rt.ChainID, - }, - } - ) - tests := []struct { - name string - tx *IncreaseL1ValidatorBalanceTx - expectedErr error - }{ - { - name: "nil tx", - tx: nil, - expectedErr: ErrNilTx, - }, - { - name: "already verified", - // The tx includes invalid data to verify that a cached result is - // returned. - tx: &IncreaseL1ValidatorBalanceTx{ - BaseTx: BaseTx{ - SyntacticallyVerified: true, - }, - Balance: 0, - }, - expectedErr: nil, - }, - { - name: "zero balance", - tx: &IncreaseL1ValidatorBalanceTx{ - BaseTx: validBaseTx, - Balance: 0, - }, - expectedErr: ErrZeroBalance, - }, - { - name: "invalid BaseTx", - tx: &IncreaseL1ValidatorBalanceTx{ - BaseTx: BaseTx{}, - Balance: 1, - }, - expectedErr: lux.ErrWrongNetworkID, - }, - { - name: "passes verification", - tx: &IncreaseL1ValidatorBalanceTx{ - BaseTx: validBaseTx, - Balance: 1, - }, - expectedErr: nil, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - err := test.tx.SyntacticVerify(rt) - require.ErrorIs(err, test.expectedErr) - if test.expectedErr != nil { - return - } - require.True(test.tx.SyntacticallyVerified) - }) - } -} diff --git a/vms/platformvm/txs/mempool/auth_test.go b/vms/platformvm/txs/mempool/auth_test.go deleted file mode 100644 index 90c730802..000000000 --- a/vms/platformvm/txs/mempool/auth_test.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package mempool - -import ( - "errors" - "testing" - - "github.com/luxfi/consensus/config" - "github.com/luxfi/metric" - "github.com/luxfi/node/vms/components/verify" - "github.com/luxfi/node/vms/txs/auth" - "github.com/luxfi/utxo/secp256k1fx" -) - -// TestMempoolAdd_StrictPQ_RefusesClassicalCredentials verifies that the -// P-chain mempool refuses a tx whose credentials carry a classical -// secp256k1fx.Credential when the chain's profile pins -// RequireTypedTxAuth=true and no ClassicalCompatRegistry names the -// originator. This is the mempool-level enforcement of the F-class -// objection: a strict-PQ chain MUST NOT gossip a classical-credential -// tx. -func TestMempoolAdd_StrictPQ_RefusesClassicalCredentials(t *testing.T) { - mpool, err := New("test", metric.NewRegistry()) - if err != nil { - t.Fatalf("New: %v", err) - } - - // Pin strict-PQ; supply no registry so every classical credential - // is refused. - mpool.SetAuthPolicy(&config.ChainSecurityProfile{RequireTypedTxAuth: true}, nil) - - decisions, err := createTestDecisionTxs(1) - if err != nil { - t.Fatalf("createTestDecisionTxs: %v", err) - } - tx := decisions[0] - // createTestDecisionTxs leaves Creds empty; inject a single - // classical credential to exercise the gate. - tx.Creds = []verify.Verifiable{&secp256k1fx.Credential{}} - - if err := mpool.Add(tx); !errors.Is(err, auth.ErrLegacyCredentialUnderStrictPQ) { - t.Fatalf("mempool.Add: got %v, want ErrLegacyCredentialUnderStrictPQ", err) - } -} - -// TestMempoolAdd_NoPolicy_AdmitsClassicalCredentials confirms backward -// compatibility — a mempool that has never called SetAuthPolicy admits -// classical credentials unchanged. This is the path every existing -// caller follows today; the new gate is opt-in. -func TestMempoolAdd_NoPolicy_AdmitsClassicalCredentials(t *testing.T) { - mpool, err := New("test", metric.NewRegistry()) - if err != nil { - t.Fatalf("New: %v", err) - } - - decisions, err := createTestDecisionTxs(1) - if err != nil { - t.Fatalf("createTestDecisionTxs: %v", err) - } - tx := decisions[0] - tx.Creds = []verify.Verifiable{&secp256k1fx.Credential{}} - - if err := mpool.Add(tx); err != nil { - t.Fatalf("mempool.Add: %v (expected admission)", err) - } -} - -// TestMempoolAdd_ClassicalCompatProfile_AdmitsClassicalCredentials -// proves the gate is a no-op when the chain pins -// RequireTypedTxAuth=false (the classical-compat profile path). -func TestMempoolAdd_ClassicalCompatProfile_AdmitsClassicalCredentials(t *testing.T) { - mpool, err := New("test", metric.NewRegistry()) - if err != nil { - t.Fatalf("New: %v", err) - } - mpool.SetAuthPolicy(&config.ChainSecurityProfile{RequireTypedTxAuth: false}, nil) - - decisions, err := createTestDecisionTxs(1) - if err != nil { - t.Fatalf("createTestDecisionTxs: %v", err) - } - tx := decisions[0] - tx.Creds = []verify.Verifiable{&secp256k1fx.Credential{}} - - if err := mpool.Add(tx); err != nil { - t.Fatalf("mempool.Add: %v (expected admission under classical-compat)", err) - } -} diff --git a/vms/platformvm/txs/mempool/mempool_test.go b/vms/platformvm/txs/mempool/mempool_test.go deleted file mode 100644 index 4566efc34..000000000 --- a/vms/platformvm/txs/mempool/mempool_test.go +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package mempool - -import ( - "math" - "testing" - "time" - - "github.com/luxfi/metric" - - "github.com/stretchr/testify/require" - - "github.com/luxfi/crypto/secp256k1" - "github.com/luxfi/ids" - lux "github.com/luxfi/utxo" - - "github.com/luxfi/node/vms/platformvm/txs" - - "github.com/luxfi/utxo/secp256k1fx" -) - -var preFundedKeys = secp256k1.TestKeys() - -// shows that valid tx is not added to mempool if this would exceed its maximum -// size -func TestBlockBuilderMaxMempoolSizeHandling(t *testing.T) { - require := require.New(t) - - registerer := metric.NewRegistry() - mpool, err := New("mempool", registerer) - require.NoError(err) - - decisionTxs, err := createTestDecisionTxs(1) - require.NoError(err) - tx := decisionTxs[0] - - // Test mempool full behavior - cannot access private bytesAvailable field - // This test verifies the mempool can handle transactions, the full/capacity - // testing is done in vms/txs/mempool/mempool_test.go - err = mpool.Add(tx) - require.NoError(err, "should have added tx to mempool") -} - -func TestDecisionTxsInMempool(t *testing.T) { - require := require.New(t) - - registerer := metric.NewRegistry() - mpool, err := New("mempool", registerer) - require.NoError(err) - - decisionTxs, err := createTestDecisionTxs(2) - require.NoError(err) - - // txs must not already there before we start - require.False(mpool.HasTxs()) - - for _, tx := range decisionTxs { - // tx not already there - require.False(mpool.Has(tx.ID())) - - // we can insert - require.NoError(mpool.Add(tx)) - - // we can get it - require.True(mpool.Has(tx.ID())) - - retrieved, _ := mpool.Get(tx.ID()) - require.NotNil(retrieved) - require.Equal(tx, retrieved) - - // we can peek it - peeked := mpool.PeekTxs(math.MaxInt) - - // tx will be among those peeked, - // in NO PARTICULAR ORDER - found := false - for _, pk := range peeked { - if pk.ID() == tx.ID() { - found = true - break - } - } - require.True(found) - - // once removed it cannot be there - mpool.Remove(tx) - - require.False(mpool.Has(tx.ID())) - retrievedAfterRemove, _ := mpool.Get(tx.ID()) - require.Equal((*txs.Tx)(nil), retrievedAfterRemove) - - // we can reinsert it again to grow the mempool - require.NoError(mpool.Add(tx)) - } -} - -func TestProposalTxsInMempool(t *testing.T) { - require := require.New(t) - - registerer := metric.NewRegistry() - mpool, err := New("mempool", registerer) - require.NoError(err) - - // The proposal txs are ordered by decreasing start time. This means after - // each insertion, the last inserted transaction should be on the top of the - // heap. - proposalTxs, err := createTestProposalTxs(2) - require.NoError(err) - - for i, tx := range proposalTxs { - require.False(mpool.Has(tx.ID())) - - // we can insert - require.NoError(mpool.Add(tx)) - - // we can get it - require.True(mpool.Has(tx.ID())) - - retrieved, _ := mpool.Get(tx.ID()) - require.NotNil(retrieved) - require.Equal(tx, retrieved) - - { - // we can peek it - peeked := mpool.PeekTxs(math.MaxInt) - require.Len(peeked, i+1) - - // tx will be among those peeked, - // in NO PARTICULAR ORDER - found := false - for _, pk := range peeked { - if pk.ID() == tx.ID() { - found = true - break - } - } - require.True(found) - } - - // once removed it cannot be there - mpool.Remove(tx) - - require.False(mpool.Has(tx.ID())) - retrievedAfterRemove, _ := mpool.Get(tx.ID()) - require.Equal((*txs.Tx)(nil), retrievedAfterRemove) - - // we can reinsert it again to grow the mempool - require.NoError(mpool.Add(tx)) - } -} - -func createTestDecisionTxs(count int) ([]*txs.Tx, error) { - decisionTxs := make([]*txs.Tx, 0, count) - for i := uint32(0); i < uint32(count); i++ { - utx := &txs.CreateChainTx{ - BaseTx: txs.BaseTx{BaseTx: lux.BaseTx{ - NetworkID: 10, - BlockchainID: ids.Empty.Prefix(uint64(i)), - Ins: []*lux.TransferableInput{{ - UTXOID: lux.UTXOID{ - TxID: ids.ID{'t', 'x', 'I', 'D'}, - OutputIndex: i, - }, - Asset: lux.Asset{ID: ids.ID{'a', 's', 's', 'e', 'r', 't'}}, - In: &secp256k1fx.TransferInput{ - Amt: uint64(5678), - Input: secp256k1fx.Input{SigIndices: []uint32{i}}, - }, - }}, - Outs: []*lux.TransferableOutput{{ - Asset: lux.Asset{ID: ids.ID{'a', 's', 's', 'e', 'r', 't'}}, - Out: &secp256k1fx.TransferOutput{ - Amt: uint64(1234), - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{preFundedKeys[0].PublicKey().Address()}, - }, - }, - }}, - }}, - ChainID: ids.GenerateTestID(), - BlockchainName: "chainName", - VMID: ids.GenerateTestID(), - FxIDs: []ids.ID{ids.GenerateTestID()}, - GenesisData: []byte{'g', 'e', 'n', 'D', 'a', 't', 'a'}, - ChainAuth: &secp256k1fx.Input{SigIndices: []uint32{1}}, - } - - tx, err := txs.NewSigned(utx, txs.Codec, nil) - if err != nil { - return nil, err - } - decisionTxs = append(decisionTxs, tx) - } - return decisionTxs, nil -} - -// Proposal txs are sorted by decreasing start time -func createTestProposalTxs(count int) ([]*txs.Tx, error) { - now := time.Now() - proposalTxs := make([]*txs.Tx, 0, count) - for i := 0; i < count; i++ { - tx, err := generateAddValidatorTx( - uint64(now.Add(time.Duration(count-i)*time.Second).Unix()), // startTime - 0, // endTime - ) - if err != nil { - return nil, err - } - proposalTxs = append(proposalTxs, tx) - } - return proposalTxs, nil -} - -func generateAddValidatorTx(startTime uint64, endTime uint64) (*txs.Tx, error) { - utx := &txs.AddValidatorTx{ - BaseTx: txs.BaseTx{}, - Validator: txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - Start: startTime, - End: endTime, - }, - StakeOuts: nil, - RewardsOwner: &secp256k1fx.OutputOwners{}, - DelegationShares: 100, - } - - return txs.NewSigned(utx, txs.Codec, nil) -} - -func TestDropExpiredStakerTxs(t *testing.T) { - require := require.New(t) - - registerer := metric.NewRegistry() - mempool, err := New("mempool", registerer) - require.NoError(err) - - tx1, err := generateAddValidatorTx(10, 20) - require.NoError(err) - require.NoError(mempool.Add(tx1)) - - tx2, err := generateAddValidatorTx(8, 20) - require.NoError(err) - require.NoError(mempool.Add(tx2)) - - tx3, err := generateAddValidatorTx(15, 20) - require.NoError(err) - require.NoError(mempool.Add(tx3)) - - minStartTime := time.Unix(9, 0) - require.Len(mempool.DropExpiredStakerTxs(minStartTime), 1) -} diff --git a/vms/platformvm/txs/register_l1_validator_tx_test.go b/vms/platformvm/txs/register_l1_validator_tx_test.go deleted file mode 100644 index 2a15c9a28..000000000 --- a/vms/platformvm/txs/register_l1_validator_tx_test.go +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txs - -import ( - "encoding/hex" - "github.com/go-json-experiment/json" - jsonv1 "github.com/go-json-experiment/json/v1" - "github.com/go-json-experiment/json/jsontext" - "testing" - - "github.com/stretchr/testify/require" - - _ "embed" - - consensustest "github.com/luxfi/consensus/test/helpers" - "github.com/luxfi/constants" - "github.com/luxfi/crypto/bls/signer/localsigner" - "github.com/luxfi/ids" - lux "github.com/luxfi/utxo" - "github.com/luxfi/node/vms/platformvm/stakeable" - "github.com/luxfi/utils" - "github.com/luxfi/node/vms/platformvm/signer" - "github.com/luxfi/utxo/secp256k1fx" - "github.com/luxfi/vm/types" -) - -//go:embed register_l1_validator_tx_test.json -var registerL1ValidatorTxJSON []byte - -func TestRegisterL1ValidatorTxSerialization(t *testing.T) { - require := require.New(t) - - const balance = constants.Lux - - skBytes, err := hex.DecodeString("6668fecd4595b81e4d568398c820bbf3f073cb222902279fa55ebb84764ed2e3") - require.NoError(err) - sk, err := localsigner.FromBytes(skBytes) - require.NoError(err) - pop, err := signer.NewProofOfPossession(sk) - require.NoError(err) - - var ( - message = []byte("message") - addr = ids.ShortID{ - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - } - utxoAssetID = ids.ID{ - 0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a, - 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, - 0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf, - 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, - } - customAssetID = ids.ID{ - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - } - txID = ids.ID{ - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - } - ) - - var unsignedTx UnsignedTx = &RegisterL1ValidatorTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: constants.PlatformChainID, - Outs: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 87654321, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 12345678, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 876543210, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 0xffffffffffffffff, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - }, - }, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.Lux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2, 5}, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 2, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &stakeable.LockIn{ - Locktime: 876543210, - TransferableIn: &secp256k1fx.TransferInput{ - Amt: 0xefffffffffffffff, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 3, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 0x1000000000000000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{}, - }, - }, - }, - }, - Memo: types.JSONByteSlice("😅\nwell that's\x01\x23\x45!"), - }, - }, - Balance: balance, - ProofOfPossession: pop.ProofOfPossession, - Message: message, - } - txBytes, err := Codec.Marshal(CodecVersion, &unsignedTx) - require.NoError(err) - - expectedBytes := []byte{ - 0x01, 0x00, 0x25, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02, 0x00, 0x00, 0x00, 0x21, 0xe6, - 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a, 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, 0xa8, 0xf5, - 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf, 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, 0x16, 0x00, - 0x00, 0x00, 0xb1, 0x7f, 0x39, 0x05, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x61, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x16, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x03, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, - 0xbe, 0x2a, 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, 0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, - 0x05, 0xdf, 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, 0x05, 0x00, 0x00, 0x00, 0x40, 0x42, - 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x02, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x15, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x03, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x9f, - 0x98, 0x85, 0x0a, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x27, 0x73, 0x01, 0x23, - 0x45, 0x21, 0x40, 0x42, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0xfd, 0x79, 0x09, 0xd1, 0x53, - 0xb9, 0x60, 0x4b, 0x62, 0xb1, 0x43, 0xba, 0x36, 0x20, 0x7b, 0xb7, 0xe6, 0x48, 0x67, 0x42, 0x44, - 0x80, 0x20, 0x2a, 0x67, 0xdc, 0x68, 0x76, 0x83, 0x46, 0xd9, 0x5c, 0x90, 0x98, 0x3c, 0x2d, 0x27, - 0x9c, 0x64, 0xc4, 0x3c, 0x51, 0x13, 0x6b, 0x2a, 0x05, 0xe0, 0x16, 0x02, 0xd5, 0x2a, 0xa6, 0x37, - 0x6f, 0xda, 0x17, 0xfa, 0x6e, 0x2a, 0x18, 0xa0, 0x83, 0xe4, 0x9d, 0x9c, 0x45, 0x0e, 0xab, 0x7b, - 0x89, 0xb1, 0xd5, 0x55, 0x5d, 0xa5, 0xc4, 0x89, 0x87, 0x2e, 0x02, 0xb7, 0xe5, 0x22, 0x7b, 0x77, - 0x55, 0x0a, 0xf1, 0x33, 0x0e, 0x5a, 0x71, 0xf8, 0xc3, 0x68, 0x07, 0x00, 0x00, 0x00, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, - } - // Skip byte comparison when CGO is disabled because CGO BLS (BLST) and - // pure Go BLS produce different signatures from the same private key - if utils.CGOEnabled { - require.Equal(expectedBytes, txBytes) - } else { - t.Log("Skipping byte comparison due to CGO-disabled BLS signature differences") - require.Equal(len(expectedBytes), len(txBytes), "serialized length should match") - } - - rt := consensustest.Runtime(t, constants.PlatformChainID) - unsignedTx.InitRuntime(rt) - - txJSON, err := json.Marshal(unsignedTx, jsontext.WithIndent("\t"), jsonv1.FormatByteArrayAsArray(true)) - require.NoError(err) - require.JSONEq(string(registerL1ValidatorTxJSON), string(txJSON)) -} - -func TestRegisterL1ValidatorTxSyntacticVerify(t *testing.T) { - rt := consensustest.Runtime(t, ids.GenerateTestID()) - tests := []struct { - name string - tx *RegisterL1ValidatorTx - expectedErr error - }{ - { - name: "nil tx", - tx: nil, - expectedErr: ErrNilTx, - }, - { - name: "already verified", - // The tx includes invalid data to verify that a cached result is - // returned. - tx: &RegisterL1ValidatorTx{ - BaseTx: BaseTx{ - SyntacticallyVerified: true, - }, - }, - expectedErr: nil, - }, - { - name: "invalid BaseTx", - tx: &RegisterL1ValidatorTx{ - BaseTx: BaseTx{}, - }, - expectedErr: lux.ErrWrongNetworkID, - }, - { - name: "passes verification", - tx: &RegisterL1ValidatorTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: rt.NetworkID, - BlockchainID: rt.ChainID, - }, - }, - }, - expectedErr: nil, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - err := test.tx.SyntacticVerify(rt) - require.ErrorIs(err, test.expectedErr) - if test.expectedErr != nil { - return - } - require.True(test.tx.SyntacticallyVerified) - }) - } -} diff --git a/vms/platformvm/txs/remove_chain_validator_tx_test.go b/vms/platformvm/txs/remove_chain_validator_tx_test.go deleted file mode 100644 index f8dd263f0..000000000 --- a/vms/platformvm/txs/remove_chain_validator_tx_test.go +++ /dev/null @@ -1,510 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txs - -import ( - "github.com/go-json-experiment/json" - "github.com/go-json-experiment/json/jsontext" - "errors" - "testing" - - "github.com/luxfi/runtime" - - "github.com/luxfi/mock/gomock" - "github.com/stretchr/testify/require" - - "github.com/luxfi/constants" - "github.com/luxfi/ids" - lux "github.com/luxfi/utxo" - "github.com/luxfi/node/vms/components/verify/verifymock" - "github.com/luxfi/node/vms/platformvm/stakeable" - "github.com/luxfi/utils" - "github.com/luxfi/utxo/secp256k1fx" - "github.com/luxfi/vm/types" -) - -var errInvalidNetAuth = errors.New("invalid net auth") - -func TestRemoveChainValidatorTxSerialization(t *testing.T) { - require := require.New(t) - - addr := ids.ShortID{ - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - } - - utxoAssetID, err := ids.FromString("d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG") - require.NoError(err) - - customAssetID := ids.ID{ - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - } - - txID := ids.ID{ - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - } - nodeID := ids.BuildTestNodeID([]byte{ - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0x11, 0x22, 0x33, 0x44, - }) - netID := ids.ID{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, - } - - simpleRemoveValidatorTx := &RemoveChainValidatorTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: ids.Empty, // Use empty for serialization test - Outs: []*lux.TransferableOutput{}, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.MilliLux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{5}, - }, - }, - }, - }, - Memo: types.JSONByteSlice{}, - }, - }, - NodeID: nodeID, - Chain: netID, - ChainAuth: &secp256k1fx.Input{ - SigIndices: []uint32{3}, - }, - } - testChainID := ids.Empty // Use empty chain ID for serialization test to match expected bytes - rt := &runtime.Runtime{ - NetworkID: constants.UnitTestID, - - ChainID: ids.GenerateTestID(), - } - rt = &runtime.Runtime{ - NetworkID: constants.MainnetID, - - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - } - require.NoError(simpleRemoveValidatorTx.SyntacticVerify(rt)) - - expectedUnsignedSimpleRemoveValidatorTxBytes := []byte{ - 0x01, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, - 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, - 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x05, 0x00, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x22, - 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, - 0x33, 0x44, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, - 0x17, 0x18, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, - 0x37, 0x38, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - } - var unsignedSimpleRemoveValidatorTx UnsignedTx = simpleRemoveValidatorTx - unsignedSimpleRemoveValidatorTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleRemoveValidatorTx) - require.NoError(err) - require.Equal(expectedUnsignedSimpleRemoveValidatorTxBytes, unsignedSimpleRemoveValidatorTxBytes) - - complexRemoveValidatorTx := &RemoveChainValidatorTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: ids.Empty, // Use empty for serialization test - Outs: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 87654321, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 12345678, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 876543210, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 0xffffffffffffffff, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - }, - }, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.Lux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2, 5}, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 2, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &stakeable.LockIn{ - Locktime: 876543210, - TransferableIn: &secp256k1fx.TransferInput{ - Amt: 0xefffffffffffffff, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 3, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 0x1000000000000000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{}, - }, - }, - }, - }, - Memo: types.JSONByteSlice("😅\nwell that's\x01\x23\x45!"), - }, - }, - NodeID: nodeID, - Chain: netID, - ChainAuth: &secp256k1fx.Input{ - SigIndices: []uint32{}, - }, - } - lux.SortTransferableOutputs(complexRemoveValidatorTx.Outs) - utils.Sort(complexRemoveValidatorTx.Ins) - rt2 := &runtime.Runtime{ - NetworkID: constants.MainnetID, - - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - } - require.NoError(complexRemoveValidatorTx.SyntacticVerify(rt2)) - - expectedUnsignedComplexRemoveValidatorTxBytes := []byte{ - 0x01, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x51, 0xc2, - 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, - 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x16, 0x00, - 0x00, 0x00, 0xb1, 0x7f, 0x39, 0x05, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x61, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x16, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x03, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, - 0x01, 0xff, 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, - 0xc9, 0x6b, 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x05, 0x00, 0x00, 0x00, 0x40, 0x42, - 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x02, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x15, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x03, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x9f, - 0x98, 0x85, 0x0a, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x27, 0x73, 0x01, 0x23, - 0x45, 0x21, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, - 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, - 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x31, 0x32, - 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - } - var unsignedComplexRemoveValidatorTx UnsignedTx = complexRemoveValidatorTx - unsignedComplexRemoveValidatorTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexRemoveValidatorTx) - require.NoError(err) - require.Equal(expectedUnsignedComplexRemoveValidatorTxBytes, unsignedComplexRemoveValidatorTxBytes) - - // Remove aliaser as BCLookup field doesn't exist in runtime.Runtime - // This functionality is now handled differently - - rt3 := &runtime.Runtime{ - NetworkID: constants.MainnetID, // Must match tx.ChainworkID for "P-lux1..." address encoding - - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - } - unsignedComplexRemoveValidatorTx.InitRuntime(rt3) - - unsignedComplexRemoveValidatorTxJSONBytes, err := json.Marshal(unsignedComplexRemoveValidatorTx, jsontext.WithIndent("\t")) - require.NoError(err) - require.JSONEq(`{ - "networkID": 1, - "blockchainID": "11111111111111111111111111111111LpoYY", - "outputs": [ - { - "assetID": "d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "locktime": 87654321, - "output": { - "addresses": [], - "amount": 1, - "locktime": 12345678, - "threshold": 0 - } - } - }, - { - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "locktime": 876543210, - "output": { - "addresses": [ - "7EKFm18KvWqcxMCNgpBSN51pJnEr1cVUb" - ], - "amount": 18446744073709551615, - "locktime": 0, - "threshold": 1 - } - } - } - ], - "inputs": [ - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 1, - "assetID": "d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "amount": 1000000, - "signatureIndices": [ - 2, - 5 - ] - } - }, - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 2, - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "locktime": 876543210, - "input": { - "amount": 17293822569102704639, - "signatureIndices": [ - 0 - ] - } - } - }, - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 3, - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "amount": 1152921504606846976, - "signatureIndices": [] - } - } - ], - "memo": "0xf09f98850a77656c6c2074686174277301234521", - "nodeID": "NodeID-2ZbTY9GatRTrfinAoYiYLcf6CvrPAUYgo", - "chainID": "SkB92YpWm4UpburLz9tEKZw2i67H3FF6YkjaU4BkFUDTG9Xm", - "chainAuthorization": { - "signatureIndices": [] - } -}`, string(unsignedComplexRemoveValidatorTxJSONBytes)) -} - -func TestRemoveChainValidatorTxSyntacticVerify(t *testing.T) { - type test struct { - name string - txFunc func(*gomock.Controller) *RemoveChainValidatorTx - expectedErr error - } - - var ( - networkID = uint32(1337) - chainID = ids.GenerateTestID() - ) - - rt := &runtime.Runtime{ - NetworkID: networkID, - - ChainID: chainID, - } - - // A BaseTx that already passed syntactic verification. - verifiedBaseTx := BaseTx{ - SyntacticallyVerified: true, - } - // Sanity check. - require.NoError(t, verifiedBaseTx.SyntacticVerify(rt)) - - // A BaseTx that passes syntactic verification. - validBaseTx := BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: networkID, - BlockchainID: chainID, - }, - } - // Sanity check. - require.NoError(t, validBaseTx.SyntacticVerify(rt)) - // Make sure we're not caching the verification result. - require.False(t, validBaseTx.SyntacticallyVerified) - - // A BaseTx that fails syntactic verification. - invalidBaseTx := BaseTx{} - - tests := []test{ - { - name: "nil tx", - txFunc: func(*gomock.Controller) *RemoveChainValidatorTx { - return nil - }, - expectedErr: ErrNilTx, - }, - { - name: "already verified", - txFunc: func(*gomock.Controller) *RemoveChainValidatorTx { - return &RemoveChainValidatorTx{BaseTx: verifiedBaseTx} - }, - expectedErr: nil, - }, - { - name: "invalid BaseTx", - txFunc: func(*gomock.Controller) *RemoveChainValidatorTx { - return &RemoveChainValidatorTx{ - // Set netID so we don't error on that check. - Chain: ids.GenerateTestID(), - // Set NodeID so we don't error on that check. - NodeID: ids.GenerateTestNodeID(), - BaseTx: invalidBaseTx, - } - }, - expectedErr: lux.ErrWrongNetworkID, - }, - { - name: "invalid netID", - txFunc: func(*gomock.Controller) *RemoveChainValidatorTx { - return &RemoveChainValidatorTx{ - BaseTx: validBaseTx, - // Set NodeID so we don't error on that check. - NodeID: ids.GenerateTestNodeID(), - Chain: constants.PrimaryNetworkID, - } - }, - expectedErr: ErrRemovePrimaryNetworkValidator, - }, - { - name: "invalid chainAuth", - txFunc: func(ctrl *gomock.Controller) *RemoveChainValidatorTx { - // This NetAuth fails verification. - invalidNetAuth := verifymock.NewVerifiable(ctrl) - invalidNetAuth.EXPECT().Verify().Return(errInvalidNetAuth) - return &RemoveChainValidatorTx{ - // Set netID so we don't error on that check. - Chain: ids.GenerateTestID(), - // Set NodeID so we don't error on that check. - NodeID: ids.GenerateTestNodeID(), - BaseTx: validBaseTx, - ChainAuth: invalidNetAuth, - } - }, - expectedErr: errInvalidNetAuth, - }, - { - name: "passes verification", - txFunc: func(ctrl *gomock.Controller) *RemoveChainValidatorTx { - // This NetAuth passes verification. - validNetAuth := verifymock.NewVerifiable(ctrl) - validNetAuth.EXPECT().Verify().Return(nil) - return &RemoveChainValidatorTx{ - // Set netID so we don't error on that check. - Chain: ids.GenerateTestID(), - // Set NodeID so we don't error on that check. - NodeID: ids.GenerateTestNodeID(), - BaseTx: validBaseTx, - ChainAuth: validNetAuth, - } - }, - expectedErr: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - tx := tt.txFunc(ctrl) - err := tx.SyntacticVerify(rt) - require.ErrorIs(err, tt.expectedErr) - if tt.expectedErr != nil { - return - } - require.True(tx.SyntacticallyVerified) - }) - } -} diff --git a/vms/platformvm/txs/set_l1_validator_weight_tx_test.go b/vms/platformvm/txs/set_l1_validator_weight_tx_test.go deleted file mode 100644 index 4ac74f9a5..000000000 --- a/vms/platformvm/txs/set_l1_validator_weight_tx_test.go +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txs - -import ( - "github.com/go-json-experiment/json" - "github.com/go-json-experiment/json/jsontext" - "testing" - - "github.com/stretchr/testify/require" - - _ "embed" - - consensustest "github.com/luxfi/consensus/test/helpers" - "github.com/luxfi/constants" - "github.com/luxfi/ids" - lux "github.com/luxfi/utxo" - "github.com/luxfi/node/vms/platformvm/stakeable" - "github.com/luxfi/utxo/secp256k1fx" - "github.com/luxfi/vm/types" -) - -//go:embed set_l1_validator_weight_tx_test.json -var setL1ValidatorWeightTxJSON []byte - -func TestSetL1ValidatorWeightTxSerialization(t *testing.T) { - require := require.New(t) - - var ( - message = []byte("message") - addr = ids.ShortID{ - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - } - utxoAssetID = ids.ID{ - 0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a, - 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, - 0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf, - 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, - } - customAssetID = ids.ID{ - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - } - txID = ids.ID{ - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - } - ) - - var unsignedTx UnsignedTx = &SetL1ValidatorWeightTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: constants.PlatformChainID, - Outs: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 87654321, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 12345678, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 876543210, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 0xffffffffffffffff, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - }, - }, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.Lux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2, 5}, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 2, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &stakeable.LockIn{ - Locktime: 876543210, - TransferableIn: &secp256k1fx.TransferInput{ - Amt: 0xefffffffffffffff, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 3, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 0x1000000000000000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{}, - }, - }, - }, - }, - Memo: types.JSONByteSlice("😅\nwell that's\x01\x23\x45!"), - }, - }, - Message: message, - } - txBytes, err := Codec.Marshal(CodecVersion, &unsignedTx) - require.NoError(err) - - expectedBytes := []byte{ - 0x01, 0x00, 0x26, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02, 0x00, 0x00, 0x00, 0x21, 0xe6, - 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a, 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, 0xa8, 0xf5, - 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf, 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, 0x16, 0x00, - 0x00, 0x00, 0xb1, 0x7f, 0x39, 0x05, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x61, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x16, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x03, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, - 0xbe, 0x2a, 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, 0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, - 0x05, 0xdf, 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, 0x05, 0x00, 0x00, 0x00, 0x40, 0x42, - 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x02, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x15, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x03, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x9f, - 0x98, 0x85, 0x0a, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x27, 0x73, 0x01, 0x23, - 0x45, 0x21, 0x07, 0x00, 0x00, 0x00, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - } - require.Equal(expectedBytes, txBytes) - - rt := consensustest.Runtime(t, constants.PlatformChainID) - unsignedTx.InitRuntime(rt) - - txJSON, err := json.Marshal(unsignedTx, jsontext.WithIndent("\t")) - require.NoError(err) - require.JSONEq(string(setL1ValidatorWeightTxJSON), string(txJSON)) -} - -func TestSetL1ValidatorWeightTxSyntacticVerify(t *testing.T) { - rt := consensustest.Runtime(t, ids.GenerateTestID()) - tests := []struct { - name string - tx *SetL1ValidatorWeightTx - expectedErr error - }{ - { - name: "nil tx", - tx: nil, - expectedErr: ErrNilTx, - }, - { - name: "already verified", - // The tx includes invalid data to verify that a cached result is - // returned. - tx: &SetL1ValidatorWeightTx{ - BaseTx: BaseTx{ - SyntacticallyVerified: true, - }, - }, - expectedErr: nil, - }, - { - name: "invalid BaseTx", - tx: &SetL1ValidatorWeightTx{ - BaseTx: BaseTx{}, - }, - expectedErr: lux.ErrWrongNetworkID, - }, - { - name: "passes verification", - tx: &SetL1ValidatorWeightTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: rt.NetworkID, - BlockchainID: rt.ChainID, - }, - }, - }, - expectedErr: nil, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - err := test.tx.SyntacticVerify(rt) - require.ErrorIs(err, test.expectedErr) - if test.expectedErr != nil { - return - } - require.True(test.tx.SyntacticallyVerified) - }) - } -} diff --git a/vms/platformvm/txs/transfer_chain_ownership_tx_test.go b/vms/platformvm/txs/transfer_chain_ownership_tx_test.go deleted file mode 100644 index 5a19ac037..000000000 --- a/vms/platformvm/txs/transfer_chain_ownership_tx_test.go +++ /dev/null @@ -1,515 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txs - -import ( - "github.com/luxfi/runtime" - - "github.com/go-json-experiment/json" - "github.com/go-json-experiment/json/jsontext" - "testing" - - "github.com/luxfi/mock/gomock" - "github.com/stretchr/testify/require" - - "github.com/luxfi/constants" - "github.com/luxfi/ids" - lux "github.com/luxfi/utxo" - "github.com/luxfi/node/vms/components/verify/verifymock" - "github.com/luxfi/node/vms/platformvm/fx/fxmock" - "github.com/luxfi/node/vms/platformvm/stakeable" - "github.com/luxfi/utils" - "github.com/luxfi/utxo/secp256k1fx" - "github.com/luxfi/vm/types" -) - -func TestTransferChainOwnershipTxSerialization(t *testing.T) { - require := require.New(t) - - addr := ids.ShortID{ - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - } - - utxoAssetID, err := ids.FromString("d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG") - require.NoError(err) - - customAssetID := ids.ID{ - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - } - - txID := ids.ID{ - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - } - netID := ids.ID{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, - } - - simpleTransferChainOwnershipTx := &TransferChainOwnershipTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: ids.Empty, // Use empty for serialization test - Outs: []*lux.TransferableOutput{}, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.MilliLux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{5}, - }, - }, - }, - }, - Memo: types.JSONByteSlice{}, - }, - }, - Chain: netID, - ChainAuth: &secp256k1fx.Input{ - SigIndices: []uint32{3}, - }, - Owner: &secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - } - testChainID := ids.Empty // Use empty chain ID for serialization test to match expected bytes - rt := &runtime.Runtime{ - NetworkID: constants.MainnetID, // Must match tx.NetworkID - - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - } - require.NoError(simpleTransferChainOwnershipTx.SyntacticVerify(rt)) - - expectedUnsignedSimpleTransferChainOwnershipTxBytes := []byte{ - 0x01, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, - 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, - 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x05, 0x00, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, - 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x21, 0x22, - 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x0a, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, - } - var unsignedSimpleTransferChainOwnershipTx UnsignedTx = simpleTransferChainOwnershipTx - unsignedSimpleTransferChainOwnershipTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleTransferChainOwnershipTx) - require.NoError(err) - require.Equal(expectedUnsignedSimpleTransferChainOwnershipTxBytes, unsignedSimpleTransferChainOwnershipTxBytes) - - complexTransferChainOwnershipTx := &TransferChainOwnershipTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: ids.Empty, // Use empty for serialization test - Outs: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 87654321, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 12345678, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 876543210, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 0xffffffffffffffff, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - }, - }, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.Lux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2, 5}, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 2, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &stakeable.LockIn{ - Locktime: 876543210, - TransferableIn: &secp256k1fx.TransferInput{ - Amt: 0xefffffffffffffff, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 3, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 0x1000000000000000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{}, - }, - }, - }, - }, - Memo: types.JSONByteSlice("😅\nwell that's\x01\x23\x45!"), - }, - }, - Chain: netID, - ChainAuth: &secp256k1fx.Input{ - SigIndices: []uint32{}, - }, - Owner: &secp256k1fx.OutputOwners{ - Locktime: 876543210, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - } - lux.SortTransferableOutputs(complexTransferChainOwnershipTx.Outs) - utils.Sort(complexTransferChainOwnershipTx.Ins) - rt2 := &runtime.Runtime{ - NetworkID: constants.MainnetID, - - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - } - require.NoError(complexTransferChainOwnershipTx.SyntacticVerify(rt2)) - - expectedUnsignedComplexTransferChainOwnershipTxBytes := []byte{ - 0x01, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x51, 0xc2, - 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, - 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x16, 0x00, - 0x00, 0x00, 0xb1, 0x7f, 0x39, 0x05, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x61, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x16, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x03, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, - 0x01, 0xff, 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, - 0xc9, 0x6b, 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x05, 0x00, 0x00, 0x00, 0x40, 0x42, - 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x02, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x15, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x03, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x9f, - 0x98, 0x85, 0x0a, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x27, 0x73, 0x01, 0x23, - 0x45, 0x21, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, - 0x17, 0x18, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, - 0x37, 0x38, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0xea, 0xfc, - 0x3e, 0x34, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, - } - var unsignedComplexTransferChainOwnershipTx UnsignedTx = complexTransferChainOwnershipTx - unsignedComplexTransferChainOwnershipTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexTransferChainOwnershipTx) - require.NoError(err) - require.Equal(expectedUnsignedComplexTransferChainOwnershipTxBytes, unsignedComplexTransferChainOwnershipTxBytes) - - // Remove aliaser as BCLookup field doesn't exist in runtime.Runtime - // This functionality is now handled differently - - rt3 := &runtime.Runtime{ - NetworkID: constants.MainnetID, // Must match tx.NetworkID for "P-lux1..." address encoding - - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - } - unsignedComplexTransferChainOwnershipTx.InitRuntime(rt3) - - unsignedComplexTransferChainOwnershipTxJSONBytes, err := json.Marshal(unsignedComplexTransferChainOwnershipTx, jsontext.WithIndent("\t")) - require.NoError(err) - require.JSONEq(`{ - "networkID": 1, - "blockchainID": "11111111111111111111111111111111LpoYY", - "outputs": [ - { - "assetID": "d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "locktime": 87654321, - "output": { - "addresses": [], - "amount": 1, - "locktime": 12345678, - "threshold": 0 - } - } - }, - { - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "locktime": 876543210, - "output": { - "addresses": [ - "7EKFm18KvWqcxMCNgpBSN51pJnEr1cVUb" - ], - "amount": 18446744073709551615, - "locktime": 0, - "threshold": 1 - } - } - } - ], - "inputs": [ - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 1, - "assetID": "d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "amount": 1000000, - "signatureIndices": [ - 2, - 5 - ] - } - }, - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 2, - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "locktime": 876543210, - "input": { - "amount": 17293822569102704639, - "signatureIndices": [ - 0 - ] - } - } - }, - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 3, - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "amount": 1152921504606846976, - "signatureIndices": [] - } - } - ], - "memo": "0xf09f98850a77656c6c2074686174277301234521", - "chainID": "SkB92YpWm4UpburLz9tEKZw2i67H3FF6YkjaU4BkFUDTG9Xm", - "chainAuthorization": { - "signatureIndices": [] - }, - "newOwner": { - "addresses": [ - "7EKFm18KvWqcxMCNgpBSN51pJnEr1cVUb" - ], - "locktime": 876543210, - "threshold": 1 - } -}`, string(unsignedComplexTransferChainOwnershipTxJSONBytes)) -} - -func TestTransferChainOwnershipTxSyntacticVerify(t *testing.T) { - type test struct { - name string - txFunc func(*gomock.Controller) *TransferChainOwnershipTx - expectedErr error - } - - var ( - networkID = uint32(1337) - chainID = ids.GenerateTestID() - ) - - rt := &runtime.Runtime{ - NetworkID: networkID, - - ChainID: chainID, - } - - // A BaseTx that already passed syntactic verification. - verifiedBaseTx := BaseTx{ - SyntacticallyVerified: true, - } - // Sanity check. - require.NoError(t, verifiedBaseTx.SyntacticVerify(rt)) - - // A BaseTx that passes syntactic verification. - validBaseTx := BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: networkID, - BlockchainID: chainID, - }, - } - // Sanity check. - require.NoError(t, validBaseTx.SyntacticVerify(rt)) - // Make sure we're not caching the verification result. - require.False(t, validBaseTx.SyntacticallyVerified) - - // A BaseTx that fails syntactic verification. - invalidBaseTx := BaseTx{} - - tests := []test{ - { - name: "nil tx", - txFunc: func(*gomock.Controller) *TransferChainOwnershipTx { - return nil - }, - expectedErr: ErrNilTx, - }, - { - name: "already verified", - txFunc: func(*gomock.Controller) *TransferChainOwnershipTx { - return &TransferChainOwnershipTx{BaseTx: verifiedBaseTx} - }, - expectedErr: nil, - }, - { - name: "invalid BaseTx", - txFunc: func(*gomock.Controller) *TransferChainOwnershipTx { - return &TransferChainOwnershipTx{ - // Set netID so we don't error on that check. - Chain: ids.GenerateTestID(), - BaseTx: invalidBaseTx, - } - }, - expectedErr: lux.ErrWrongNetworkID, - }, - { - name: "invalid netID", - txFunc: func(*gomock.Controller) *TransferChainOwnershipTx { - return &TransferChainOwnershipTx{ - BaseTx: validBaseTx, - Chain: constants.PrimaryNetworkID, - } - }, - expectedErr: ErrTransferPermissionlessChain, - }, - { - name: "invalid chainAuth", - txFunc: func(ctrl *gomock.Controller) *TransferChainOwnershipTx { - // This NetAuth fails verification. - invalidNetAuth := verifymock.NewVerifiable(ctrl) - invalidNetAuth.EXPECT().Verify().Return(errInvalidNetAuth) - return &TransferChainOwnershipTx{ - // Set netID so we don't error on that check. - Chain: ids.GenerateTestID(), - BaseTx: validBaseTx, - ChainAuth: invalidNetAuth, - } - }, - expectedErr: errInvalidNetAuth, - }, - { - name: "passes verification", - txFunc: func(ctrl *gomock.Controller) *TransferChainOwnershipTx { - // This NetAuth passes verification. - validNetAuth := verifymock.NewVerifiable(ctrl) - validNetAuth.EXPECT().Verify().Return(nil) - mockOwner := fxmock.NewOwner(ctrl) - mockOwner.EXPECT().Verify().Return(nil) - return &TransferChainOwnershipTx{ - // Set netID so we don't error on that check. - Chain: ids.GenerateTestID(), - BaseTx: validBaseTx, - ChainAuth: validNetAuth, - Owner: mockOwner, - } - }, - expectedErr: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - tx := tt.txFunc(ctrl) - err := tx.SyntacticVerify(rt) - require.ErrorIs(err, tt.expectedErr) - if tt.expectedErr != nil { - return - } - require.True(tx.SyntacticallyVerified) - }) - } -} diff --git a/vms/platformvm/txs/transform_chain_tx_test.go b/vms/platformvm/txs/transform_chain_tx_test.go deleted file mode 100644 index 3764971c2..000000000 --- a/vms/platformvm/txs/transform_chain_tx_test.go +++ /dev/null @@ -1,855 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txs - -import ( - "github.com/go-json-experiment/json" - "github.com/go-json-experiment/json/jsontext" - "testing" - - "github.com/luxfi/runtime" - - "github.com/luxfi/mock/gomock" - "github.com/stretchr/testify/require" - - "github.com/luxfi/constants" - "github.com/luxfi/ids" - lux "github.com/luxfi/utxo" - "github.com/luxfi/node/vms/components/verify/verifymock" - "github.com/luxfi/node/vms/platformvm/reward" - "github.com/luxfi/node/vms/platformvm/stakeable" - "github.com/luxfi/utils" - "github.com/luxfi/utxo/secp256k1fx" - "github.com/luxfi/vm/types" -) - -func TestTransformChainTxSerialization(t *testing.T) { - require := require.New(t) - - addr := ids.ShortID{ - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, - 0x44, 0x55, 0x66, 0x77, - } - - utxoAssetID, err := ids.FromString("d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG") - require.NoError(err) - - customAssetID := ids.ID{ - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, - } - - txID := ids.ID{ - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - } - netID := ids.ID{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, - } - - simpleTransformTx := &TransformChainTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: ids.Empty, // Use empty for serialization test - Outs: []*lux.TransferableOutput{}, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 10 * constants.Lux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{5}, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 2, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 0xefffffffffffffff, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }, - }, - Memo: types.JSONByteSlice{}, - }, - }, - Chain: netID, - AssetID: customAssetID, - InitialSupply: 0x1000000000000000, - MaximumSupply: 0xffffffffffffffff, - MinConsumptionRate: 1_000, - MaxConsumptionRate: 1_000_000, - MinValidatorStake: 1, - MaxValidatorStake: 0xffffffffffffffff, - MinStakeDuration: 1, - MaxStakeDuration: 365 * 24 * 60 * 60, - MinDelegationFee: reward.PercentDenominator, - MinDelegatorStake: 1, - MaxValidatorWeightFactor: 1, - UptimeRequirement: .95 * reward.PercentDenominator, - ChainAuth: &secp256k1fx.Input{ - SigIndices: []uint32{3}, - }, - } - testChainID := ids.Empty // Use empty chain ID for serialization test to match expected bytes - rt := &runtime.Runtime{ - NetworkID: constants.MainnetID, // Must match tx.NetworkID - - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - } - require.NoError(simpleTransformTx.SyntacticVerify(rt)) - - expectedUnsignedSimpleTransformTxBytes := []byte{ - 0x01, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, - 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, - 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x05, 0x00, 0x00, 0x00, 0x80, 0x96, 0x98, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x02, 0x00, 0x00, 0x00, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x05, 0x00, - 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, - 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x31, 0x32, - 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x42, - 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x80, 0x33, 0xe1, 0x01, 0x40, 0x42, - 0x0f, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x7e, 0x0e, 0x00, 0x0a, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - } - var unsignedSimpleTransformTx UnsignedTx = simpleTransformTx - unsignedSimpleTransformTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleTransformTx) - require.NoError(err) - require.Equal(expectedUnsignedSimpleTransformTxBytes, unsignedSimpleTransformTxBytes) - - complexTransformTx := &TransformChainTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: constants.MainnetID, - BlockchainID: ids.Empty, // Use empty for serialization test - Outs: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ - ID: utxoAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 87654321, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 12345678, - Threshold: 0, - Addrs: []ids.ShortID{}, - }, - }, - }, - }, - { - Asset: lux.Asset{ - ID: customAssetID, - }, - Out: &stakeable.LockOut{ - Locktime: 876543210, - TransferableOut: &secp256k1fx.TransferOutput{ - Amt: 0xffffffffffffffff, - OutputOwners: secp256k1fx.OutputOwners{ - Locktime: 0, - Threshold: 1, - Addrs: []ids.ShortID{ - addr, - }, - }, - }, - }, - }, - }, - Ins: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 1, - }, - Asset: lux.Asset{ - ID: utxoAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: constants.KiloLux, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2, 5}, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 2, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &stakeable.LockIn{ - Locktime: 876543210, - TransferableIn: &secp256k1fx.TransferInput{ - Amt: 0xefffffffffffffff, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }, - }, - { - UTXOID: lux.UTXOID{ - TxID: txID, - OutputIndex: 3, - }, - Asset: lux.Asset{ - ID: customAssetID, - }, - In: &secp256k1fx.TransferInput{ - Amt: 0x1000000000000000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{}, - }, - }, - }, - }, - Memo: types.JSONByteSlice("😅\nwell that's\x01\x23\x45!"), - }, - }, - Chain: netID, - AssetID: customAssetID, - InitialSupply: 0x1000000000000000, - MaximumSupply: 0x1000000000000000, - MinConsumptionRate: 0, - MaxConsumptionRate: 0, - MinValidatorStake: 1, - MaxValidatorStake: 0x1000000000000000, - MinStakeDuration: 1, - MaxStakeDuration: 1, - MinDelegationFee: 0, - MinDelegatorStake: 0xffffffffffffffff, - MaxValidatorWeightFactor: 255, - UptimeRequirement: 0, - ChainAuth: &secp256k1fx.Input{ - SigIndices: []uint32{}, - }, - } - lux.SortTransferableOutputs(complexTransformTx.Outs) - utils.Sort(complexTransformTx.Ins) - rt2 := &runtime.Runtime{ - NetworkID: constants.MainnetID, // Must match tx.NetworkID - - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - } - require.NoError(complexTransformTx.SyntacticVerify(rt2)) - - expectedUnsignedComplexTransformTxBytes := []byte{ - 0x01, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x51, 0xc2, - 0x4f, 0xe7, 0xee, 0x02, 0x01, 0xff, 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, - 0x24, 0x25, 0x45, 0x52, 0xc9, 0x6b, 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x16, 0x00, - 0x00, 0x00, 0xb1, 0x7f, 0x39, 0x05, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x61, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x16, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, - 0x66, 0x77, 0x03, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x01, 0x00, 0x00, 0x00, 0x51, 0xc2, 0x4f, 0xe7, 0xee, 0x02, - 0x01, 0xff, 0x0f, 0x33, 0x5f, 0x51, 0x99, 0x28, 0xdb, 0x6e, 0xef, 0x62, 0x24, 0x25, 0x45, 0x52, - 0xc9, 0x6b, 0x6b, 0x42, 0x5f, 0xbc, 0x18, 0xfa, 0x24, 0x3b, 0x05, 0x00, 0x00, 0x00, 0x00, 0xca, - 0x9a, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, - 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, - 0x99, 0x88, 0x02, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, - 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x15, 0x00, 0x00, 0x00, 0xea, 0xfc, 0x3e, 0x34, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0xff, 0xee, - 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x03, 0x00, 0x00, 0x00, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x9f, - 0x98, 0x85, 0x0a, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x27, 0x73, 0x01, 0x23, - 0x45, 0x21, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, - 0x17, 0x18, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, - 0x37, 0x38, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, - 0x55, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, - } - var unsignedComplexTransformTx UnsignedTx = complexTransformTx - unsignedComplexTransformTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexTransformTx) - require.NoError(err) - require.Equal(expectedUnsignedComplexTransformTxBytes, unsignedComplexTransformTxBytes) - - // Remove aliaser as BCLookup field doesn't exist in runtime.Runtime - // This functionality is now handled differently - - rt3 := &runtime.Runtime{ - NetworkID: constants.MainnetID, // Must match tx.NetworkID - - ChainID: testChainID, - UTXOAssetID: utxoAssetID, - } - unsignedComplexTransformTx.InitRuntime(rt3) - - unsignedComplexTransformTxJSONBytes, err := json.Marshal(unsignedComplexTransformTx, jsontext.WithIndent("\t")) - require.NoError(err) - require.JSONEq(`{ - "networkID": 1, - "blockchainID": "11111111111111111111111111111111LpoYY", - "outputs": [ - { - "assetID": "d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "locktime": 87654321, - "output": { - "addresses": [], - "amount": 1, - "locktime": 12345678, - "threshold": 0 - } - } - }, - { - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "output": { - "locktime": 876543210, - "output": { - "addresses": [ - "7EKFm18KvWqcxMCNgpBSN51pJnEr1cVUb" - ], - "amount": 18446744073709551615, - "locktime": 0, - "threshold": 1 - } - } - } - ], - "inputs": [ - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 1, - "assetID": "d1Rdokz7Vq8H5aczkwgkiPCCa6JME7yT2xpqgWTfFKWYVsGbG", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "amount": 1000000000, - "signatureIndices": [ - 2, - 5 - ] - } - }, - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 2, - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "locktime": 876543210, - "input": { - "amount": 17293822569102704639, - "signatureIndices": [ - 0 - ] - } - } - }, - { - "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", - "outputIndex": 3, - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", - "input": { - "amount": 1152921504606846976, - "signatureIndices": [] - } - } - ], - "memo": "0xf09f98850a77656c6c2074686174277301234521", - "chainID": "SkB92YpWm4UpburLz9tEKZw2i67H3FF6YkjaU4BkFUDTG9Xm", - "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", - "initialSupply": 1152921504606846976, - "maximumSupply": 1152921504606846976, - "minConsumptionRate": 0, - "maxConsumptionRate": 0, - "minValidatorStake": 1, - "maxValidatorStake": 1152921504606846976, - "minStakeDuration": 1, - "maxStakeDuration": 1, - "minDelegationFee": 0, - "minDelegatorStake": 18446744073709551615, - "maxValidatorWeightFactor": 255, - "uptimeRequirement": 0, - "chainAuthorization": { - "signatureIndices": [] - } -}`, string(unsignedComplexTransformTxJSONBytes)) -} - -func TestTransformChainTxSyntacticVerify(t *testing.T) { - type test struct { - name string - txFunc func(*gomock.Controller) *TransformChainTx - err error - } - - var ( - networkID = uint32(1337) - chainID = ids.GenerateTestID() - utxoAssetID = ids.GenerateTestID() - ) - - rt := &runtime.Runtime{ - NetworkID: networkID, // Must match tx.NetworkID - - ChainID: chainID, - UTXOAssetID: utxoAssetID, - } - - // A BaseTx that already passed syntactic verification. - verifiedBaseTx := BaseTx{ - SyntacticallyVerified: true, - } - - // A BaseTx that passes syntactic verification. - validBaseTx := BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: networkID, - BlockchainID: chainID, - }, - } - - // A BaseTx that fails syntactic verification. - invalidBaseTx := BaseTx{} - - tests := []test{ - { - name: "nil tx", - txFunc: func(*gomock.Controller) *TransformChainTx { - return nil - }, - err: ErrNilTx, - }, - { - name: "already verified", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: verifiedBaseTx, - } - }, - err: nil, - }, - { - name: "invalid netID", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: constants.PrimaryNetworkID, - } - }, - err: errCantTransformPrimaryNetwork, - }, - { - name: "empty assetID", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.Empty, - } - }, - err: errEmptyAssetID, - }, - { - name: "LUX assetID", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: utxoAssetID, - InitialSupply: 1, // Non-zero to hit LUX assetID error first - MaximumSupply: 1, - } - }, - err: errAssetIDCantBeLUX, - }, - { - name: "initialSupply == 0", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.GenerateTestID(), - InitialSupply: 0, - } - }, - err: errInitialSupplyZero, - }, - { - name: "initialSupply > maximumSupply", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.GenerateTestID(), - InitialSupply: 2, - MaximumSupply: 1, - } - }, - err: errInitialSupplyGreaterThanMaxSupply, - }, - { - name: "minConsumptionRate > maxConsumptionRate", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.GenerateTestID(), - InitialSupply: 1, - MaximumSupply: 1, - MinConsumptionRate: 2, - MaxConsumptionRate: 1, - } - }, - err: errMinConsumptionRateTooLarge, - }, - { - name: "maxConsumptionRate > 100%", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.GenerateTestID(), - InitialSupply: 1, - MaximumSupply: 1, - MinConsumptionRate: 0, - MaxConsumptionRate: reward.PercentDenominator + 1, - } - }, - err: errMaxConsumptionRateTooLarge, - }, - { - name: "minValidatorStake == 0", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.GenerateTestID(), - InitialSupply: 1, - MaximumSupply: 1, - MinConsumptionRate: 0, - MaxConsumptionRate: reward.PercentDenominator, - MinValidatorStake: 0, - } - }, - err: errMinValidatorStakeZero, - }, - { - name: "minValidatorStake > initialSupply", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.GenerateTestID(), - InitialSupply: 1, - MaximumSupply: 1, - MinConsumptionRate: 0, - MaxConsumptionRate: reward.PercentDenominator, - MinValidatorStake: 2, - } - }, - err: errMinValidatorStakeAboveSupply, - }, - { - name: "minValidatorStake > maxValidatorStake", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.GenerateTestID(), - InitialSupply: 10, - MaximumSupply: 10, - MinConsumptionRate: 0, - MaxConsumptionRate: reward.PercentDenominator, - MinValidatorStake: 2, - MaxValidatorStake: 1, - } - }, - err: errMinValidatorStakeAboveMax, - }, - { - name: "maxValidatorStake > maximumSupply", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.GenerateTestID(), - InitialSupply: 10, - MaximumSupply: 10, - MinConsumptionRate: 0, - MaxConsumptionRate: reward.PercentDenominator, - MinValidatorStake: 2, - MaxValidatorStake: 11, - } - }, - err: errMaxValidatorStakeTooLarge, - }, - { - name: "minStakeDuration == 0", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.GenerateTestID(), - InitialSupply: 10, - MaximumSupply: 10, - MinConsumptionRate: 0, - MaxConsumptionRate: reward.PercentDenominator, - MinValidatorStake: 2, - MaxValidatorStake: 10, - MinStakeDuration: 0, - } - }, - err: errMinStakeDurationZero, - }, - { - name: "minStakeDuration > maxStakeDuration", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.GenerateTestID(), - InitialSupply: 10, - MaximumSupply: 10, - MinConsumptionRate: 0, - MaxConsumptionRate: reward.PercentDenominator, - MinValidatorStake: 2, - MaxValidatorStake: 10, - MinStakeDuration: 2, - MaxStakeDuration: 1, - } - }, - err: errMinStakeDurationTooLarge, - }, - { - name: "minDelegationFee > 100%", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.GenerateTestID(), - InitialSupply: 10, - MaximumSupply: 10, - MinConsumptionRate: 0, - MaxConsumptionRate: reward.PercentDenominator, - MinValidatorStake: 2, - MaxValidatorStake: 10, - MinStakeDuration: 1, - MaxStakeDuration: 2, - MinDelegationFee: reward.PercentDenominator + 1, - } - }, - err: errMinDelegationFeeTooLarge, - }, - { - name: "minDelegatorStake == 0", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.GenerateTestID(), - InitialSupply: 10, - MaximumSupply: 10, - MinConsumptionRate: 0, - MaxConsumptionRate: reward.PercentDenominator, - MinValidatorStake: 2, - MaxValidatorStake: 10, - MinStakeDuration: 1, - MaxStakeDuration: 2, - MinDelegationFee: reward.PercentDenominator, - MinDelegatorStake: 0, - } - }, - err: errMinDelegatorStakeZero, - }, - { - name: "maxValidatorWeightFactor == 0", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.GenerateTestID(), - InitialSupply: 10, - MaximumSupply: 10, - MinConsumptionRate: 0, - MaxConsumptionRate: reward.PercentDenominator, - MinValidatorStake: 2, - MaxValidatorStake: 10, - MinStakeDuration: 1, - MaxStakeDuration: 2, - MinDelegationFee: reward.PercentDenominator, - MinDelegatorStake: 1, - MaxValidatorWeightFactor: 0, - } - }, - err: errMaxValidatorWeightFactorZero, - }, - { - name: "uptimeRequirement > 100%", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.GenerateTestID(), - InitialSupply: 10, - MaximumSupply: 10, - MinConsumptionRate: 0, - MaxConsumptionRate: reward.PercentDenominator, - MinValidatorStake: 2, - MaxValidatorStake: 10, - MinStakeDuration: 1, - MaxStakeDuration: 2, - MinDelegationFee: reward.PercentDenominator, - MinDelegatorStake: 1, - MaxValidatorWeightFactor: 1, - UptimeRequirement: reward.PercentDenominator + 1, - } - }, - err: errUptimeRequirementTooLarge, - }, - { - name: "invalid chainAuth", - txFunc: func(ctrl *gomock.Controller) *TransformChainTx { - // This NetAuth fails verification. - invalidNetAuth := verifymock.NewVerifiable(ctrl) - invalidNetAuth.EXPECT().Verify().Return(errInvalidNetAuth) - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.GenerateTestID(), - InitialSupply: 10, - MaximumSupply: 10, - MinConsumptionRate: 0, - MaxConsumptionRate: reward.PercentDenominator, - MinValidatorStake: 2, - MaxValidatorStake: 10, - MinStakeDuration: 1, - MaxStakeDuration: 2, - MinDelegationFee: reward.PercentDenominator, - MinDelegatorStake: 1, - MaxValidatorWeightFactor: 1, - UptimeRequirement: reward.PercentDenominator, - ChainAuth: invalidNetAuth, - } - }, - err: errInvalidNetAuth, - }, - { - name: "invalid BaseTx", - txFunc: func(*gomock.Controller) *TransformChainTx { - return &TransformChainTx{ - BaseTx: invalidBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.GenerateTestID(), - InitialSupply: 10, - MaximumSupply: 10, - MinConsumptionRate: 0, - MaxConsumptionRate: reward.PercentDenominator, - MinValidatorStake: 2, - MaxValidatorStake: 10, - MinStakeDuration: 1, - MaxStakeDuration: 2, - MinDelegationFee: reward.PercentDenominator, - MinDelegatorStake: 1, - MaxValidatorWeightFactor: 1, - UptimeRequirement: reward.PercentDenominator, - } - }, - err: lux.ErrWrongNetworkID, - }, - { - name: "passes verification", - txFunc: func(ctrl *gomock.Controller) *TransformChainTx { - // This NetAuth passes verification. - validNetAuth := verifymock.NewVerifiable(ctrl) - validNetAuth.EXPECT().Verify().Return(nil) - return &TransformChainTx{ - BaseTx: validBaseTx, - Chain: ids.GenerateTestID(), - AssetID: ids.GenerateTestID(), - InitialSupply: 10, - MaximumSupply: 10, - MinConsumptionRate: 0, - MaxConsumptionRate: reward.PercentDenominator, - MinValidatorStake: 2, - MaxValidatorStake: 10, - MinStakeDuration: 1, - MaxStakeDuration: 2, - MinDelegationFee: reward.PercentDenominator, - MinDelegatorStake: 1, - MaxValidatorWeightFactor: 1, - UptimeRequirement: reward.PercentDenominator, - ChainAuth: validNetAuth, - } - }, - err: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - - tx := tt.txFunc(ctrl) - err := tx.SyntacticVerify(rt) - require.ErrorIs(t, err, tt.err) - }) - } -} diff --git a/vms/platformvm/txs/tx_fuzz_test.go b/vms/platformvm/txs/tx_fuzz_test.go index 8e5ac6720..938290b26 100644 --- a/vms/platformvm/txs/tx_fuzz_test.go +++ b/vms/platformvm/txs/tx_fuzz_test.go @@ -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,167 +8,113 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/luxfi/crypto/secp256k1" "github.com/luxfi/ids" "github.com/luxfi/node/vms/components/verify" - "github.com/luxfi/node/vms/pcodecs" lux "github.com/luxfi/utxo" "github.com/luxfi/utxo/secp256k1fx" ) -// FuzzTransactionParsing tests transaction parsing with random data +// oneOut is a single spendable secp256k1fx output owned by one address. +func oneOut(asset ids.ID, amt uint64) *lux.TransferableOutput { + return &lux.TransferableOutput{ + Asset: lux.Asset{ID: asset}, + Out: &secp256k1fx.TransferOutput{ + Amt: amt, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ids.GenerateTestShortID()}, + }, + }, + } +} + +// oneOwner is a single-address rewards/ownership owner. +func oneOwner() *secp256k1fx.OutputOwners { + return &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ids.GenerateTestShortID()}, + } +} + +// FuzzTransactionParsing feeds arbitrary bytes at Parse: it must never panic, +// and any tx it does decode must survive a re-Parse of its own bytes with a +// stable ID (the struct-is-wire round-trip: Parse wraps bytes zero-copy, ID = +// hash(bytes)). func FuzzTransactionParsing(f *testing.F) { - // Seed corpus with various transaction-like structures f.Add([]byte{}) - f.Add([]byte{0x00, 0x00, 0x00, 0x00}) // Codec version - f.Add([]byte{0x00, 0x00, 0x00, 0x01}) // Type ID - - // Add a more structured transaction-like data - txData := make([]byte, 100) - copy(txData[:4], []byte{0x00, 0x00, 0x00, 0x00}) // Version - copy(txData[4:8], []byte{0x00, 0x00, 0x00, 0x0c}) // Type ID - f.Add(txData) - - // Add data with IDs + f.Add([]byte{0x00, 0x00, 0x00, 0x00}) + f.Add([]byte{0x03, 0x00, 0x22, 0x00}) testID := ids.GenerateTestID() - withID := append([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, testID[:]...) - f.Add(withID) - - // Parser is not available in this package, using Codec instead - codec := Codec + f.Add(testID[:]) f.Fuzz(func(t *testing.T, data []byte) { - // Try to parse as transaction - var tx Tx - _, err := codec.Unmarshal(data, &tx) + tx, err := Parse(data) if err != nil { - // Expected for invalid transaction data - return + return // expected for arbitrary bytes } - // If parsing succeeded, test that methods don't panic - unsigned := tx.Unsigned - if unsigned != nil { - _ = unsigned.Visit(&visitor{}) - } + // Methods must not panic on a decoded tx. + require.NotNil(t, tx.Unsigned) + _ = tx.Unsigned.Visit(&visitor{}) - // Initialize the transaction to populate bytes and ID - if err := tx.Initialize(codec); err != nil { - // Some parsed transactions may fail to re-marshal - return - } - - // Try to serialize back - txBytes := tx.Bytes() - if len(txBytes) == 0 { - t.Error("Initialized transaction has empty bytes") - return - } - - // Parse again and verify consistency - var tx2 Tx - _, err = codec.Unmarshal(txBytes, &tx2) - if err != nil { - t.Errorf("Failed to re-parse serialized transaction: %v", err) - return - } - - if err := tx2.Initialize(codec); err != nil { - t.Errorf("Failed to initialize re-parsed transaction: %v", err) - return - } - - if tx.ID() != tx2.ID() { - t.Errorf("Transaction ID changed after re-parsing") - } + // Re-parse the exact bytes: the ID is hash(signedBytes) and must be stable. + reparsed, err := Parse(tx.Bytes()) + require.NoError(t, err) + require.Equal(t, tx.ID(), reparsed.ID()) }) } -// FuzzBaseTx tests BaseTx parsing and serialization +// FuzzBaseTx builds a BaseTx through the constructor and proves the envelope +// round-trips through the signed-bytes prefix. func FuzzBaseTx(f *testing.F) { - // Seed corpus - f.Add(uint64(1), uint32(1), []byte{}) - f.Add(uint64(1000000), uint32(42), bytes.Repeat([]byte{0xff}, 32)) + f.Add(uint64(1), []byte{}) + f.Add(uint64(1000000), bytes.Repeat([]byte{0xff}, 32)) testID := ids.GenerateTestID() - f.Add(uint64(0), uint32(0), testID[:]) + f.Add(uint64(0), testID[:]) - c := pcodecs.NewLinearCodec() + f.Fuzz(func(t *testing.T, networkID uint64, assetData []byte) { + require := require.New(t) - f.Fuzz(func(t *testing.T, networkID uint64, blockchainID uint32, assetData []byte) { - // Create asset ID var assetID ids.ID if len(assetData) >= 32 { copy(assetID[:], assetData[:32]) } - // Create a base transaction - baseTx := &BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: uint32(networkID & 0xFFFFFFFF), // Limit to uint32 - BlockchainID: ids.GenerateTestID(), - Outs: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ID: assetID}, - Out: &secp256k1fx.TransferOutput{ - Amt: 1000, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{ids.GenerateTestShortID()}, - }, - }, - }, - }, - Ins: []*lux.TransferableInput{}, - }, + base := &lux.BaseTx{ + NetworkID: uint32(networkID), + BlockchainID: ids.GenerateTestID(), + Outs: []*lux.TransferableOutput{oneOut(assetID, 1000)}, } + utx, err := NewBaseTx(base) + require.NoError(err) - // Try to serialize - p := pcodecs.Packer{MaxSize: 1024 * 1024} - err := c.MarshalInto(baseTx, &p) - if err != nil { - // Some combinations might be invalid - return - } - - // Try to deserialize - var parsed BaseTx - p2 := pcodecs.Packer{Bytes: p.Bytes, MaxSize: 1024 * 1024} - err = c.UnmarshalFrom(&p2, &parsed) - if err != nil { - t.Errorf("Failed to unmarshal BaseTx: %v", err) - return - } - - // Verify fields match - if parsed.NetworkID != baseTx.NetworkID { - t.Errorf("NetworkID mismatch: got %v, want %v", parsed.NetworkID, baseTx.NetworkID) - } - - if parsed.BlockchainID != baseTx.BlockchainID { - t.Errorf("BlockchainID mismatch") - } + got := roundTrip(t, utx).(*BaseTx) + require.Equal(base.NetworkID, got.NetworkID()) + require.Equal(base.BlockchainID, got.BlockchainID()) + require.Len(got.Outputs(), 1) }) } -// FuzzCreateChainTx tests CreateChainTx parsing +// FuzzCreateChainTx builds a CreateChainTx through the constructor and proves +// its delta fields round-trip. func FuzzCreateChainTx(f *testing.F) { - // Seed corpus f.Add([]byte("chainName"), []byte{}, []byte("vmID")) f.Add([]byte("test"), bytes.Repeat([]byte{0x01}, 100), []byte("customvm")) f.Add([]byte{}, []byte{}, []byte{}) - c := pcodecs.NewLinearCodec() - f.Fuzz(func(t *testing.T, chainName []byte, genesisData []byte, vmIDData []byte) { - // Limit sizes - if len(chainName) > 128 { - chainName = chainName[:128] + require := require.New(t) + + if len(chainName) > MaxNameLen { + chainName = chainName[:MaxNameLen] } if len(genesisData) > 10000 { genesisData = genesisData[:10000] } - // Create VM ID var vmID ids.ID if len(vmIDData) >= 32 { copy(vmID[:], vmIDData[:32]) @@ -176,355 +122,160 @@ func FuzzCreateChainTx(f *testing.F) { vmID = ids.GenerateTestID() } - // Create transaction - tx := &CreateChainTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: 1, - BlockchainID: ids.GenerateTestID(), - Outs: []*lux.TransferableOutput{}, - Ins: []*lux.TransferableInput{}, - }, - }, - ChainID: ids.GenerateTestID(), - BlockchainName: string(chainName), - VMID: vmID, - FxIDs: []ids.ID{}, - GenesisData: genesisData, - ChainAuth: &secp256k1fx.Input{}, - } + base := &lux.BaseTx{NetworkID: 1, BlockchainID: ids.GenerateTestID()} + utx, err := NewCreateChainTx( + base, + ids.GenerateTestID(), + string(chainName), + vmID, + nil, + genesisData, + &secp256k1fx.Input{}, + ) + require.NoError(err) - // Try to serialize - p := pcodecs.Packer{MaxSize: 10 * 1024 * 1024} - err := c.MarshalInto(tx, &p) - if err != nil { - // Some combinations might be invalid - return - } - - // Try to deserialize - var parsed CreateChainTx - p2 := pcodecs.Packer{Bytes: p.Bytes, MaxSize: 10 * 1024 * 1024} - err = c.UnmarshalFrom(&p2, &parsed) - if err != nil { - t.Errorf("Failed to unmarshal CreateChainTx: %v", err) - return - } - - // Verify key fields - if parsed.BlockchainName != tx.BlockchainName { - t.Errorf("ChainName mismatch: got %q, want %q", parsed.BlockchainName, tx.BlockchainName) - } - - if parsed.VMID != tx.VMID { - t.Errorf("VMID mismatch") - } - - if !bytes.Equal(parsed.GenesisData, tx.GenesisData) { - t.Errorf("GenesisData mismatch") + got := roundTrip(t, utx).(*CreateChainTx) + require.Equal(string(chainName), got.BlockchainName()) + require.Equal(vmID, got.VMID()) + if len(genesisData) == 0 { + require.Empty(got.GenesisData()) + } else { + require.Equal(genesisData, got.GenesisData()) } }) } -// FuzzAddValidatorTx tests validator transaction parsing +// FuzzAddValidatorTx builds an AddValidatorTx through the constructor and +// proves its inline Validator + shares round-trip (no SyntacticVerify: any +// numeric values must survive the wire). func FuzzAddValidatorTx(f *testing.F) { - // Seed corpus f.Add(uint64(1), uint64(100), uint64(1000), uint32(100000)) f.Add(uint64(0), uint64(0), uint64(0), uint32(0)) f.Add(uint64(time.Now().Unix()), uint64(time.Now().Add(time.Hour).Unix()), uint64(1000000), uint32(20000)) - c := pcodecs.NewLinearCodec() - f.Fuzz(func(t *testing.T, startTime, endTime, weight uint64, shares uint32) { - // Create validator transaction - tx := &AddValidatorTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: 1, - BlockchainID: ids.GenerateTestID(), - Outs: []*lux.TransferableOutput{}, - Ins: []*lux.TransferableInput{}, - }, - }, - Validator: Validator{ - NodeID: ids.GenerateTestNodeID(), - Start: startTime, - End: endTime, - Wght: weight, - }, - StakeOuts: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ID: ids.GenerateTestID()}, - Out: &secp256k1fx.TransferOutput{ - Amt: weight, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{ids.GenerateTestShortID()}, - }, - }, - }, - }, - RewardsOwner: &secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{ids.GenerateTestShortID()}, - }, - DelegationShares: shares, - } + require := require.New(t) - // Try to serialize - p := pcodecs.Packer{MaxSize: 1024 * 1024} - err := c.MarshalInto(tx, &p) - if err != nil { - return + base := &lux.BaseTx{NetworkID: 1, BlockchainID: ids.GenerateTestID()} + validator := Validator{ + NodeID: ids.GenerateTestNodeID(), + Start: startTime, + End: endTime, + Wght: weight, } + utx, err := NewAddValidatorTx( + base, + validator, + []*lux.TransferableOutput{oneOut(ids.GenerateTestID(), weight)}, + oneOwner(), + shares, + ) + require.NoError(err) - // Try to deserialize - var parsed AddValidatorTx - p2 := pcodecs.Packer{Bytes: p.Bytes, MaxSize: 1024 * 1024} - err = c.UnmarshalFrom(&p2, &parsed) - if err != nil { - t.Errorf("Failed to unmarshal AddValidatorTx: %v", err) - return - } - - // Verify fields - if parsed.Start != tx.Start { - t.Errorf("Start time mismatch: got %v, want %v", parsed.Start, tx.Start) - } - - if parsed.End != tx.End { - t.Errorf("End time mismatch: got %v, want %v", parsed.End, tx.End) - } - - if parsed.Wght != tx.Wght { - t.Errorf("Weight mismatch: got %v, want %v", parsed.Wght, tx.Wght) - } - - if parsed.DelegationShares != tx.DelegationShares { - t.Errorf("DelegationShares mismatch: got %v, want %v", parsed.DelegationShares, tx.DelegationShares) - } + got := roundTrip(t, utx).(*AddValidatorTx) + require.Equal(validator, got.Validator()) + require.Equal(shares, got.DelegationShares()) }) } -// FuzzImportExportTx tests import/export transaction parsing +// FuzzImportExportTx builds ImportTx and ExportTx through their constructors +// and proves the cross-chain id fields round-trip. func FuzzImportExportTx(f *testing.F) { - // Seed corpus f.Add([]byte{}, []byte{}) testID1 := ids.GenerateTestID() testID2 := ids.GenerateTestID() f.Add(testID1[:], testID2[:]) - f.Add(bytes.Repeat([]byte{0xff}, 32), bytes.Repeat([]byte{0xaa}, 32)) - - c := pcodecs.NewLinearCodec() f.Fuzz(func(t *testing.T, sourceChainData, destChainData []byte) { - // Create chain IDs - var sourceChain, destChain ids.ID + require := require.New(t) + + sourceChain := ids.GenerateTestID() if len(sourceChainData) >= 32 { copy(sourceChain[:], sourceChainData[:32]) - } else { - sourceChain = ids.GenerateTestID() } - + destChain := ids.GenerateTestID() if len(destChainData) >= 32 { copy(destChain[:], destChainData[:32]) - } else { - destChain = ids.GenerateTestID() } - // Create ImportTx - importTx := &ImportTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: 1, - BlockchainID: destChain, - Outs: []*lux.TransferableOutput{}, - Ins: []*lux.TransferableInput{}, - }, - }, - SourceChain: sourceChain, - ImportedInputs: []*lux.TransferableInput{ - { - UTXOID: lux.UTXOID{ - TxID: ids.GenerateTestID(), - OutputIndex: 0, - }, - Asset: lux.Asset{ID: ids.GenerateTestID()}, - In: &secp256k1fx.TransferInput{ - Amt: 1000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }, - }, + importedIn := &lux.TransferableInput{ + UTXOID: lux.UTXOID{TxID: ids.GenerateTestID(), OutputIndex: 0}, + Asset: lux.Asset{ID: ids.GenerateTestID()}, + In: &secp256k1fx.TransferInput{Amt: 1000, Input: secp256k1fx.Input{SigIndices: []uint32{0}}}, } + importTx, err := NewImportTx( + &lux.BaseTx{NetworkID: 1, BlockchainID: destChain}, + sourceChain, + []*lux.TransferableInput{importedIn}, + ) + require.NoError(err) + gotImport := roundTrip(t, importTx).(*ImportTx) + require.Equal(sourceChain, gotImport.SourceChain()) - // Try to serialize ImportTx - p := pcodecs.Packer{MaxSize: 1024 * 1024} - err := c.MarshalInto(importTx, &p) - if err != nil { - return - } - - // Try to deserialize - var parsedImport ImportTx - p2 := pcodecs.Packer{Bytes: p.Bytes, MaxSize: 1024 * 1024} - err = c.UnmarshalFrom(&p2, &parsedImport) - if err != nil { - t.Errorf("Failed to unmarshal ImportTx: %v", err) - return - } - - // Verify fields - if parsedImport.SourceChain != importTx.SourceChain { - t.Errorf("SourceChain mismatch") - } - - // Create ExportTx - exportTx := &ExportTx{ - BaseTx: BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: 1, - BlockchainID: sourceChain, - Outs: []*lux.TransferableOutput{}, - Ins: []*lux.TransferableInput{}, - }, - }, - DestinationChain: destChain, - ExportedOutputs: []*lux.TransferableOutput{ - { - Asset: lux.Asset{ID: ids.GenerateTestID()}, - Out: &secp256k1fx.TransferOutput{ - Amt: 1000, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{ids.GenerateTestShortID()}, - }, - }, - }, - }, - } - - // Try to serialize ExportTx - p3 := pcodecs.Packer{MaxSize: 1024 * 1024} - err = c.MarshalInto(exportTx, &p3) - if err != nil { - return - } - - // Try to deserialize - var parsedExport ExportTx - p4 := pcodecs.Packer{Bytes: p3.Bytes, MaxSize: 1024 * 1024} - err = c.UnmarshalFrom(&p4, &parsedExport) - if err != nil { - t.Errorf("Failed to unmarshal ExportTx: %v", err) - return - } - - // Verify fields - if parsedExport.DestinationChain != exportTx.DestinationChain { - t.Errorf("DestinationChain mismatch") - } + exportTx, err := NewExportTx( + &lux.BaseTx{NetworkID: 1, BlockchainID: sourceChain}, + destChain, + []*lux.TransferableOutput{oneOut(ids.GenerateTestID(), 1000)}, + ) + require.NoError(err) + gotExport := roundTrip(t, exportTx).(*ExportTx) + require.Equal(destChain, gotExport.DestinationChain()) }) } -// FuzzTransactionSignatures tests transaction signature handling +// FuzzTransactionSignatures attaches credentials to a signed Tx and proves the +// signed bytes (unsigned‖creds) round-trip with a stable ID. func FuzzTransactionSignatures(f *testing.F) { - // Seed corpus - f.Add([]byte{}, []byte{}) - f.Add(bytes.Repeat([]byte{0x01}, 65), bytes.Repeat([]byte{0x02}, 32)) + f.Add([]byte{}) + f.Add(bytes.Repeat([]byte{0x01}, 65)) - // Parser is not available in this package, using Codec instead - codec := Codec + f.Fuzz(func(t *testing.T, sigData []byte) { + require := require.New(t) - f.Fuzz(func(t *testing.T, sigData []byte, txData []byte) { - // Create a basic transaction - baseTx := &Tx{ - Unsigned: &BaseTx{ - BaseTx: lux.BaseTx{ - NetworkID: 1, - BlockchainID: ids.GenerateTestID(), - Outs: []*lux.TransferableOutput{}, - Ins: []*lux.TransferableInput{}, - }, - }, - Creds: []verify.Verifiable{}, - } + utx, err := NewBaseTx(&lux.BaseTx{NetworkID: 1, BlockchainID: ids.GenerateTestID()}) + require.NoError(err) - // Add credentials based on signature data - if len(sigData) >= 65 { - cred := secp256k1fx.Credential{ - Sigs: [][secp256k1.SignatureLen]byte{}, - } - - // Add signatures (65 bytes each) - for i := 0; i+65 <= len(sigData); i += 65 { - var sig [65]byte - copy(sig[:], sigData[i:i+65]) + signed := &Tx{Unsigned: utx} + if len(sigData) >= secp256k1.SignatureLen { + cred := &secp256k1fx.Credential{} + for i := 0; i+secp256k1.SignatureLen <= len(sigData) && len(cred.Sigs) < 10; i += secp256k1.SignatureLen { + var sig [secp256k1.SignatureLen]byte + copy(sig[:], sigData[i:i+secp256k1.SignatureLen]) cred.Sigs = append(cred.Sigs, sig) - - if len(cred.Sigs) >= 10 { // Limit number of signatures - break - } } - - baseTx.Creds = append(baseTx.Creds, &cred) + signed.Creds = []verify.Verifiable{cred} } + require.NoError(signed.Initialize()) - // Initialize the transaction - if err := baseTx.Initialize(codec); err != nil { - // Some combinations might be invalid - return - } - - // Get transaction bytes - bytes := baseTx.Bytes() - - // Try to parse back - var parsed Tx - _, err := codec.Unmarshal(bytes, &parsed) - if err != nil { - // Should not fail for a transaction we created - t.Errorf("Failed to parse transaction we created: %v", err) - return - } - - // Initialize the parsed transaction to compute its ID - if err := parsed.Initialize(codec); err != nil { - // Should not fail for a valid parsed transaction - t.Errorf("Failed to initialize parsed transaction: %v", err) - return - } - - // Verify ID matches - if baseTx.ID() != parsed.ID() { - t.Errorf("Transaction ID mismatch after parsing") - } + parsed, err := Parse(signed.Bytes()) + require.NoError(err) + require.Equal(signed.ID(), parsed.ID()) + require.Equal(signed.Creds, parsed.Creds) }) } -// visitor implements the Visitor interface for testing +// visitor is a no-op Visitor used to prove decoded txs can be visited without +// panicking. It implements the current txs.Visitor surface (post-LP-018: no +// ConvertNetworkToL1Tx / CreateSovereignL1Tx / SlashValidatorTx). type visitor struct{} -func (v *visitor) AddDelegatorTx(*AddDelegatorTx) error { return nil } -func (v *visitor) AddChainValidatorTx(*AddChainValidatorTx) error { return nil } -func (v *visitor) AddPermissionlessDelegatorTx(*AddPermissionlessDelegatorTx) error { return nil } -func (v *visitor) AddPermissionlessValidatorTx(*AddPermissionlessValidatorTx) error { return nil } -func (v *visitor) AddValidatorTx(*AddValidatorTx) error { return nil } -func (v *visitor) AdvanceTimeTx(*AdvanceTimeTx) error { return nil } -func (v *visitor) BaseTx(*BaseTx) error { return nil } -func (v *visitor) CreateChainTx(*CreateChainTx) error { return nil } -func (v *visitor) CreateNetworkTx(*CreateNetworkTx) error { return nil } -func (v *visitor) ExportTx(*ExportTx) error { return nil } -func (v *visitor) ImportTx(*ImportTx) error { return nil } -func (v *visitor) RemoveChainValidatorTx(*RemoveChainValidatorTx) error { return nil } -func (v *visitor) RewardValidatorTx(*RewardValidatorTx) error { return nil } -func (v *visitor) TransferChainOwnershipTx(*TransferChainOwnershipTx) error { return nil } -func (v *visitor) TransformChainTx(*TransformChainTx) error { return nil } -func (v *visitor) ConvertNetworkToL1Tx(*ConvertNetworkToL1Tx) error { return nil } -func (v *visitor) CreateSovereignL1Tx(*CreateSovereignL1Tx) error { return nil } -func (v *visitor) RegisterL1ValidatorTx(*RegisterL1ValidatorTx) error { return nil } -func (v *visitor) SetL1ValidatorWeightTx(*SetL1ValidatorWeightTx) error { return nil } -func (v *visitor) DisableL1ValidatorTx(*DisableL1ValidatorTx) error { return nil } -func (v *visitor) IncreaseL1ValidatorBalanceTx(*IncreaseL1ValidatorBalanceTx) error { return nil } +func (*visitor) AddValidatorTx(*AddValidatorTx) error { return nil } +func (*visitor) AddChainValidatorTx(*AddChainValidatorTx) error { return nil } +func (*visitor) AddDelegatorTx(*AddDelegatorTx) error { return nil } +func (*visitor) CreateNetworkTx(*CreateNetworkTx) error { return nil } +func (*visitor) ConvertNetworkTx(*ConvertNetworkTx) error { return nil } +func (*visitor) CreateChainTx(*CreateChainTx) error { return nil } +func (*visitor) ImportTx(*ImportTx) error { return nil } +func (*visitor) ExportTx(*ExportTx) error { return nil } +func (*visitor) AdvanceTimeTx(*AdvanceTimeTx) error { return nil } +func (*visitor) RewardValidatorTx(*RewardValidatorTx) error { return nil } +func (*visitor) RemoveChainValidatorTx(*RemoveChainValidatorTx) error { return nil } +func (*visitor) TransformChainTx(*TransformChainTx) error { return nil } +func (*visitor) AddPermissionlessValidatorTx(*AddPermissionlessValidatorTx) error { return nil } +func (*visitor) AddPermissionlessDelegatorTx(*AddPermissionlessDelegatorTx) error { return nil } +func (*visitor) TransferChainOwnershipTx(*TransferChainOwnershipTx) error { return nil } +func (*visitor) BaseTx(*BaseTx) error { return nil } +func (*visitor) RegisterL1ValidatorTx(*RegisterL1ValidatorTx) error { return nil } +func (*visitor) SetL1ValidatorWeightTx(*SetL1ValidatorWeightTx) error { return nil } +func (*visitor) IncreaseL1ValidatorBalanceTx(*IncreaseL1ValidatorBalanceTx) error { return nil } +func (*visitor) DisableL1ValidatorTx(*DisableL1ValidatorTx) error { return nil } diff --git a/vms/platformvm/txs/txheap/by_end_time_test.go b/vms/platformvm/txs/txheap/by_end_time_test.go deleted file mode 100644 index 57af4caf9..000000000 --- a/vms/platformvm/txs/txheap/by_end_time_test.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txheap - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/luxfi/ids" - "github.com/luxfi/node/vms/platformvm/txs" - "github.com/luxfi/utxo/secp256k1fx" -) - -func TestByEndTime(t *testing.T) { - require := require.New(t) - - txHeap := NewByEndTime() - - baseTime := time.Now() - - utx0 := &txs.AddValidatorTx{ - Validator: txs.Validator{ - NodeID: ids.BuildTestNodeID([]byte{0}), - Start: uint64(baseTime.Unix()), - End: uint64(baseTime.Unix()) + 1, - }, - RewardsOwner: &secp256k1fx.OutputOwners{}, - } - tx0 := &txs.Tx{Unsigned: utx0} - require.NoError(tx0.Initialize(txs.Codec)) - - utx1 := &txs.AddValidatorTx{ - Validator: txs.Validator{ - NodeID: ids.BuildTestNodeID([]byte{1}), - Start: uint64(baseTime.Unix()), - End: uint64(baseTime.Unix()) + 2, - }, - RewardsOwner: &secp256k1fx.OutputOwners{}, - } - tx1 := &txs.Tx{Unsigned: utx1} - require.NoError(tx1.Initialize(txs.Codec)) - - utx2 := &txs.AddValidatorTx{ - Validator: txs.Validator{ - NodeID: ids.BuildTestNodeID([]byte{1}), - Start: uint64(baseTime.Unix()), - End: uint64(baseTime.Unix()) + 3, - }, - RewardsOwner: &secp256k1fx.OutputOwners{}, - } - tx2 := &txs.Tx{Unsigned: utx2} - require.NoError(tx2.Initialize(txs.Codec)) - - txHeap.Add(tx2) - require.Equal(utx2.EndTime(), txHeap.Timestamp()) - - txHeap.Add(tx1) - require.Equal(utx1.EndTime(), txHeap.Timestamp()) - - txHeap.Add(tx0) - require.Equal(utx0.EndTime(), txHeap.Timestamp()) - require.Equal(tx0, txHeap.Peek()) -} diff --git a/vms/platformvm/txs/txstest/wallet.go b/vms/platformvm/txs/txstest/wallet.go index d5a0af5e8..fb641def2 100644 --- a/vms/platformvm/txs/txstest/wallet.go +++ b/vms/platformvm/txs/txstest/wallet.go @@ -16,7 +16,6 @@ import ( "github.com/luxfi/node/vms/platformvm/config" "github.com/luxfi/node/vms/platformvm/state" "github.com/luxfi/node/vms/platformvm/txs" - "github.com/luxfi/node/vms/platformvm/warp/message" "github.com/luxfi/node/wallet/chain/p/builder" "github.com/luxfi/node/wallet/chain/p/signer" "github.com/luxfi/node/wallet/chain/p/wallet" @@ -118,8 +117,7 @@ func NewWalletWithOptions( } for _, utxoBytes := range atomicUTXOs { - var utxo lux.UTXO - _, err := txs.Codec.Unmarshal(utxoBytes, &utxo) + utxo, err := lux.ParseUTXO(utxoBytes) if err != nil { continue // Skip malformed UTXOs } @@ -128,7 +126,7 @@ func NewWalletWithOptions( context.Background(), sourceChainID, constants.PlatformChainID, - &utxo, + utxo, )) } } @@ -145,13 +143,9 @@ func NewWalletWithOptions( l1Validator, err := state.GetL1Validator(validationID) require.NoError(err) - var owner message.PChainOwner - _, err = txs.Codec.Unmarshal(l1Validator.DeactivationOwner, &owner) + owner, err := txs.UnmarshalOwner(l1Validator.DeactivationOwner) require.NoError(err) - owners[validationID] = &secp256k1fx.OutputOwners{ - Threshold: owner.Threshold, - Addrs: owner.Addresses, - } + owners[validationID] = owner } backend := wallet.NewBackend( diff --git a/vms/platformvm/vm.go b/vms/platformvm/vm.go index e6412282d..81195d2a9 100644 --- a/vms/platformvm/vm.go +++ b/vms/platformvm/vm.go @@ -617,9 +617,9 @@ func (vm *VM) Shutdown(context.Context) error { } func (vm *VM) ParseBlock(_ context.Context, b []byte) (chain.Block, error) { - // Note: blocks to be parsed are not verified, so we must used blocks.Codec - // rather than blocks.GenesisCodec - statelessBlk, err := block.Parse(block.Codec, b) + // Blocks are native struct-is-wire (zap): Parse re-wraps the self-delimiting + // buffer zero-copy — no codec, no version prefix. + statelessBlk, err := block.Parse(b) if err != nil { return nil, err } diff --git a/wallet/chain/p/backend.go b/wallet/chain/p/backend.go index 5a02f2d99..e0b613bbd 100644 --- a/wallet/chain/p/backend.go +++ b/wallet/chain/p/backend.go @@ -113,6 +113,12 @@ func (v *backendVisitor) CreateNetworkTx(tx *txs.CreateNetworkTx) error { return v.baseTx(tx) } +// ConvertNetworkTx promotes an existing network — its owner is already tracked +// from the original CreateNetworkTx, so only the base UTXO scan is needed. +func (v *backendVisitor) ConvertNetworkTx(tx *txs.ConvertNetworkTx) error { + return v.baseTx(tx) +} + func (v *backendVisitor) ImportTx(tx *txs.ImportTx) error { err := v.b.removeUTXOs(v.ctx, tx.SourceChain(), tx.InputUTXOs()) if err != nil { @@ -167,14 +173,6 @@ func (v *backendVisitor) BaseTx(tx *txs.BaseTx) error { return v.baseTx(tx) } -func (v *backendVisitor) ConvertNetworkToL1Tx(*txs.ConvertNetworkToL1Tx) error { - return nil -} - -func (v *backendVisitor) CreateSovereignL1Tx(*txs.CreateSovereignL1Tx) error { - return nil -} - func (v *backendVisitor) RegisterL1ValidatorTx(*txs.RegisterL1ValidatorTx) error { return nil } diff --git a/wallet/chain/p/builder.go b/wallet/chain/p/builder.go index dad3360a5..c9f98d6f8 100644 --- a/wallet/chain/p/builder.go +++ b/wallet/chain/p/builder.go @@ -1116,7 +1116,7 @@ func (b *txBuilder) authorizeNet(netID ids.ID, options *common.Options) (*secp25 return nil, errWrongTxType } - owner, ok := network.Owner.(*secp256k1fx.OutputOwners) + owner, ok := network.Owner().(*secp256k1fx.OutputOwners) if !ok { return nil, errUnknownOwnerType } diff --git a/wallet/chain/p/builder/builder_with_options.go b/wallet/chain/p/builder/builder_with_options.go index 6b4191af0..3f2701bf1 100644 --- a/wallet/chain/p/builder/builder_with_options.go +++ b/wallet/chain/p/builder/builder_with_options.go @@ -7,11 +7,11 @@ import ( "time" "github.com/luxfi/ids" - lux "github.com/luxfi/utxo" "github.com/luxfi/node/vms/platformvm/signer" "github.com/luxfi/node/vms/platformvm/txs" - "github.com/luxfi/utxo/secp256k1fx" "github.com/luxfi/node/wallet/network/primary/common" + lux "github.com/luxfi/utxo" + "github.com/luxfi/utxo/secp256k1fx" ) var _ Builder = (*builderWithOptions)(nil) @@ -253,22 +253,6 @@ func (b *builderWithOptions) NewAddPermissionlessDelegatorTx( ) } -func (b *builderWithOptions) NewConvertNetworkToL1Tx( - netID ids.ID, - managerChainID ids.ID, - address []byte, - validators []*txs.ConvertNetworkToL1Validator, - options ...common.Option, -) (*txs.ConvertNetworkToL1Tx, error) { - return b.builder.NewConvertNetworkToL1Tx( - netID, - managerChainID, - address, - validators, - common.UnionOptions(b.options, options)..., - ) -} - func (b *builderWithOptions) NewRegisterL1ValidatorTx( balance uint64, proofOfPossession [96]byte, diff --git a/wallet/chain/p/builder/with_options.go b/wallet/chain/p/builder/with_options.go index 6f49bef66..9316e98e7 100644 --- a/wallet/chain/p/builder/with_options.go +++ b/wallet/chain/p/builder/with_options.go @@ -6,13 +6,13 @@ package builder import ( "time" - "github.com/luxfi/ids" "github.com/luxfi/crypto/bls" - lux "github.com/luxfi/utxo" + "github.com/luxfi/ids" "github.com/luxfi/node/vms/platformvm/signer" "github.com/luxfi/node/vms/platformvm/txs" - "github.com/luxfi/utxo/secp256k1fx" "github.com/luxfi/node/wallet/network/primary/common" + lux "github.com/luxfi/utxo" + "github.com/luxfi/utxo/secp256k1fx" ) var _ Builder = (*withOptions)(nil) @@ -158,22 +158,6 @@ func (w *withOptions) NewTransferChainOwnershipTx( ) } -func (w *withOptions) NewConvertNetworkToL1Tx( - netID ids.ID, - managerChainID ids.ID, - address []byte, - validators []*txs.ConvertNetworkToL1Validator, - options ...common.Option, -) (*txs.ConvertNetworkToL1Tx, error) { - return w.builder.NewConvertNetworkToL1Tx( - netID, - managerChainID, - address, - validators, - common.UnionOptions(w.options, options)..., - ) -} - func (w *withOptions) NewRegisterL1ValidatorTx( balance uint64, proofOfPossession [bls.SignatureLen]byte, diff --git a/wallet/chain/p/signer/visitor.go b/wallet/chain/p/signer/visitor.go index d3aeec9b5..61a181768 100644 --- a/wallet/chain/p/signer/visitor.go +++ b/wallet/chain/p/signer/visitor.go @@ -101,6 +101,21 @@ func (s *visitor) CreateNetworkTx(tx *txs.CreateNetworkTx) error { return sign(s.tx, false, txSigners) } +// ConvertNetworkTx promotes an existing network; like CreateChainTx it requires +// the existing network owner's authorization (tx.Auth() against tx.Network()). +func (s *visitor) ConvertNetworkTx(tx *txs.ConvertNetworkTx) error { + txSigners, err := s.getSigners(constants.PlatformChainID, tx.Inputs()) + if err != nil { + return err + } + netAuthSigners, err := s.getAuthSigners(tx.Network(), tx.Auth()) + if err != nil { + return err + } + txSigners = append(txSigners, netAuthSigners) + return sign(s.tx, false, txSigners) +} + func (s *visitor) ImportTx(tx *txs.ImportTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Inputs()) if err != nil { @@ -186,27 +201,6 @@ func (s *visitor) BaseTx(tx *txs.BaseTx) error { return sign(s.tx, false, txSigners) } -func (s *visitor) ConvertNetworkToL1Tx(tx *txs.ConvertNetworkToL1Tx) error { - txSigners, err := s.getSigners(constants.PlatformChainID, tx.Inputs()) - if err != nil { - return err - } - chainAuthSigners, err := s.getAuthSigners(tx.Chain(), tx.ChainAuth()) - if err != nil { - return err - } - txSigners = append(txSigners, chainAuthSigners) - return sign(s.tx, true, txSigners) -} - -func (s *visitor) CreateSovereignL1Tx(tx *txs.CreateSovereignL1Tx) error { - txSigners, err := s.getSigners(constants.PlatformChainID, tx.Inputs()) - if err != nil { - return err - } - return sign(s.tx, true, txSigners) -} - func (s *visitor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Inputs()) if err != nil { diff --git a/wallet/chain/p/signer_visitor.go b/wallet/chain/p/signer_visitor.go index 857d77769..52e6b6197 100644 --- a/wallet/chain/p/signer_visitor.go +++ b/wallet/chain/p/signer_visitor.go @@ -109,6 +109,21 @@ func (s *signerVisitor) CreateNetworkTx(tx *txs.CreateNetworkTx) error { return sign(s.tx, false, txSigners) } +// ConvertNetworkTx promotes an existing network; like CreateChainTx it requires +// the existing network owner's authorization (tx.Auth() against tx.Network()). +func (s *signerVisitor) ConvertNetworkTx(tx *txs.ConvertNetworkTx) error { + txSigners, err := s.getSigners(constants.PlatformChainID, tx.Inputs()) + if err != nil { + return err + } + chainAuthSigners, err := s.getChainSigners(tx.Network(), tx.Auth()) + if err != nil { + return err + } + txSigners = append(txSigners, chainAuthSigners) + return sign(s.tx, false, txSigners) +} + func (s *signerVisitor) ImportTx(tx *txs.ImportTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Inputs()) if err != nil { @@ -383,25 +398,3 @@ func (s *signerVisitor) SetL1ValidatorWeightTx(tx *txs.SetL1ValidatorWeightTx) e } return sign(s.tx, false, txSigners) } - -// ConvertNetworkToL1Tx signs a ConvertNetworkToL1Tx -func (s *signerVisitor) ConvertNetworkToL1Tx(tx *txs.ConvertNetworkToL1Tx) error { - txSigners, err := s.getSigners(constants.PrimaryNetworkID, tx.Inputs()) - if err != nil { - return err - } - chainAuthSigners, err := s.getChainSigners(tx.Chain(), tx.ChainAuth()) - if err != nil { - return err - } - txSigners = append(txSigners, chainAuthSigners) - return sign(s.tx, false, txSigners) -} - -func (s *signerVisitor) CreateSovereignL1Tx(tx *txs.CreateSovereignL1Tx) error { - txSigners, err := s.getSigners(constants.PrimaryNetworkID, tx.Inputs()) - if err != nil { - return err - } - return sign(s.tx, false, txSigners) -} diff --git a/wallet/chain/p/wallet/backend_visitor.go b/wallet/chain/p/wallet/backend_visitor.go index a41cc4fe1..69c5b09ef 100644 --- a/wallet/chain/p/wallet/backend_visitor.go +++ b/wallet/chain/p/wallet/backend_visitor.go @@ -62,6 +62,12 @@ func (b *backendVisitor) CreateNetworkTx(tx *txs.CreateNetworkTx) error { return b.baseTx(tx) } +// ConvertNetworkTx promotes an existing network — its owner is already tracked +// from the original CreateNetworkTx, so only the base UTXO scan is needed. +func (b *backendVisitor) ConvertNetworkTx(tx *txs.ConvertNetworkTx) error { + return b.baseTx(tx) +} + func (b *backendVisitor) ImportTx(tx *txs.ImportTx) error { err := b.b.removeUTXOs( b.ctx, @@ -123,26 +129,6 @@ func (b *backendVisitor) BaseTx(tx *txs.BaseTx) error { return b.baseTx(tx) } -func (b *backendVisitor) ConvertNetworkToL1Tx(tx *txs.ConvertNetworkToL1Tx) error { - for i, vdr := range tx.Validators() { - b.b.setOwner( - tx.Chain().Append(uint32(i)), - &secp256k1fx.OutputOwners{ - Threshold: vdr.DeactivationOwner.Threshold, - Addrs: vdr.DeactivationOwner.Addresses, - }, - ) - } - return b.baseTx(tx) -} - -func (b *backendVisitor) CreateSovereignL1Tx(tx *txs.CreateSovereignL1Tx) error { - // Sovereign L1 launch: the new network ID is derived from the tx - // hash at commit time. The wallet backend does not need to - // preregister owners — that happens P-chain-side during execution. - return b.baseTx(tx) -} - func (b *backendVisitor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx) error { warpMessage, err := warp.ParseMessage(tx.Message()) if err != nil { diff --git a/wallet/chain/p/wallet/wallet.go b/wallet/chain/p/wallet/wallet.go index 8378988cc..c589a57ec 100644 --- a/wallet/chain/p/wallet/wallet.go +++ b/wallet/chain/p/wallet/wallet.go @@ -147,21 +147,6 @@ type Wallet interface { options ...common.Option, ) (*txs.Tx, error) - // IssueConvertNetworkToL1Tx creates, signs, and issues a transaction that - // converts the chain to a Permissionless L1. - // - // - [netID] specifies the network to be converted - // - [managerChainID] specifies which chain the manager is deployed on - // - [address] specifies the address of the manager - // - [validators] specifies the initial L1 validators of the L1 - IssueConvertNetworkToL1Tx( - netID ids.ID, - managerChainID ids.ID, - address []byte, - validators []*txs.ConvertNetworkToL1Validator, - options ...common.Option, - ) (*txs.Tx, error) - // IssueRegisterL1ValidatorTx creates, signs, and issues a transaction that // adds a validator to an L1. // @@ -451,20 +436,6 @@ func (w *wallet) IssueTransferChainOwnershipTx( return w.IssueUnsignedTx(utx, options...) } -func (w *wallet) IssueConvertNetworkToL1Tx( - netID ids.ID, - managerChainID ids.ID, - address []byte, - validators []*txs.ConvertNetworkToL1Validator, - options ...common.Option, -) (*txs.Tx, error) { - utx, err := w.builder.NewConvertNetworkToL1Tx(netID, managerChainID, address, validators, options...) - if err != nil { - return nil, err - } - return w.IssueUnsignedTx(utx, options...) -} - func (w *wallet) IssueRegisterL1ValidatorTx( balance uint64, proofOfPossession [bls.SignatureLen]byte, diff --git a/wallet/chain/p/wallet/with_options.go b/wallet/chain/p/wallet/with_options.go index fd1ae95a7..9f1467827 100644 --- a/wallet/chain/p/wallet/with_options.go +++ b/wallet/chain/p/wallet/with_options.go @@ -149,22 +149,6 @@ func (w *withOptions) IssueTransferChainOwnershipTx( ) } -func (w *withOptions) IssueConvertNetworkToL1Tx( - netID ids.ID, - managerChainID ids.ID, - address []byte, - validators []*txs.ConvertNetworkToL1Validator, - options ...common.Option, -) (*txs.Tx, error) { - return w.wallet.IssueConvertNetworkToL1Tx( - netID, - managerChainID, - address, - validators, - common.UnionOptions(w.options, options)..., - ) -} - func (w *withOptions) IssueRegisterL1ValidatorTx( balance uint64, proofOfPossession [bls.SignatureLen]byte, diff --git a/wallet/network/primary/examples/convert-l2-to-l1/main.go b/wallet/network/primary/examples/convert-l2-to-l1/main.go index c93ee6c64..cfa286399 100644 --- a/wallet/network/primary/examples/convert-l2-to-l1/main.go +++ b/wallet/network/primary/examples/convert-l2-to-l1/main.go @@ -15,12 +15,14 @@ import ( "github.com/luxfi/formatting" "github.com/luxfi/ids" "github.com/luxfi/math/set" + "github.com/luxfi/node/vms/platformvm/security" "github.com/luxfi/node/vms/platformvm/signer" "github.com/luxfi/node/vms/platformvm/txs" "github.com/luxfi/node/vms/platformvm/warp/message" "github.com/luxfi/node/wallet/network/primary" "github.com/luxfi/node/wallet/network/primary/examples/keyutil" "github.com/luxfi/sdk/info" + lux "github.com/luxfi/utxo" "github.com/luxfi/utxo/secp256k1fx" ) @@ -89,27 +91,38 @@ func main() { log.Printf("synced wallet in %s\n", time.Since(walletSyncStartTime)) convertNetToL1StartTime := time.Now() - convertNetToL1Tx, err := wallet.P().IssueConvertNetworkToL1Tx( - netID, - chainID, - address, - []*txs.ConvertNetworkToL1Validator{ - { - NodeID: nodeID[:], - Weight: weight, - Balance: constants.Lux, - Signer: *pop, - RemainingBalanceOwner: message.PChainOwner{}, - DeactivationOwner: message.PChainOwner{}, - }, + // NOTE: The P-Chain wallet builder does not expose a ConvertNetwork issue + // path — the legacy ConvertNetworkToL1Tx (and its wallet Issue method) was + // removed in the native-ZAP migration. This example now constructs the + // unsigned promote tx directly via the native txs constructor to document + // the new shape; a runnable flow needs a wallet builder that sources a + // funded *lux.BaseTx (UTXO selection) and signs it. + _ = wallet + sec := security.Mode{Admission: security.Gated, Manager: security.PChain} + validators := []*txs.NetworkValidator{ + { + NodeID: nodeID[:], + Weight: weight, + Balance: constants.Lux, + Signer: *pop, }, + } + convertNetTx, err := txs.NewConvertNetworkTx( + &lux.BaseTx{}, + netID, // network being promoted + constants.PrimaryNetworkID, // new parent (L1 ⇐ Primary) + chainID, // chain hosting the manager + sec, + address, + validators, + &secp256k1fx.Input{}, // existing-owner authorization (unsigned here) ) if err != nil { - log.Fatalf("failed to issue net conversion transaction: %s\n", err) + log.Fatalf("failed to build convert network tx: %s\n", err) } - log.Printf("converted net %s with transactionID %s, validationID %s, and conversionID %s in %s\n", + log.Printf("built unsigned convert-network tx (%d bytes) promoting net %s, validationID %s, conversionID %s in %s\n", + len(convertNetTx.Bytes()), netID, - convertNetToL1Tx.ID(), validationID, conversionID, time.Since(convertNetToL1StartTime), diff --git a/wallet/network/primary/wallet.go b/wallet/network/primary/wallet.go index e112a8277..ea3b189e2 100644 --- a/wallet/network/primary/wallet.go +++ b/wallet/network/primary/wallet.go @@ -158,7 +158,7 @@ func MakeWallet(ctx context.Context, config *WalletConfig) (Wallet, error) { if err != nil { return nil, err } - tx, err := txs.Parse(txs.Codec, txBytes) + tx, err := txs.Parse(txBytes) if err != nil { return nil, err }