Files
zeekay 7cf87b230b node-unified is the node
The module already called itself github.com/luxfi/node while containing no node
in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and
the Go column of the matrix measured a VM plugin. A repo that fetches the thing
it claims to be has not replaced it, so the Go sources move in — 1515 files,
the daemon and everything under it — and the build reaches outside this module
for nothing.

Rust and C++ sit beside it unchanged and are still measured against it by the
differential. That is the point of one repo: the reference implementation and
the two ports are the same checkout, so parity is a thing you can run rather
than a thing you assert.

Verified: builds, and the closure carries no lux-private — checked at build time
rather than believed. The daemon starts twelve chains on a local network
(P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
2026-09-07 16:15:25 -07:00

963 lines
25 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build test
package p
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/luxfi/constants"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/crypto/bls/signer/localsigner"
"github.com/luxfi/crypto/secp256k1"
"github.com/luxfi/ids"
"github.com/luxfi/math/set"
"github.com/luxfi/node/vms/components/gas"
"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/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/node/wallet/chain/p/builder"
"github.com/luxfi/node/wallet/chain/p/wallet"
"github.com/luxfi/node/wallet/network/primary/common"
"github.com/luxfi/node/wallet/network/primary/common/utxotest"
"github.com/luxfi/util"
lux "github.com/luxfi/utxo"
"github.com/luxfi/utxo/secp256k1fx"
)
var (
chainID = ids.GenerateTestID()
nodeID = ids.GenerateTestNodeID()
validationID = ids.GenerateTestID()
testKeys = secp256k1.TestKeys()
chainAuthKey = testKeys[0]
chainAuthAddr = chainAuthKey.Address()
chainOwner = &secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{chainAuthAddr},
}
importKey = testKeys[0]
importAddr = importKey.Address()
importOwner = &secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{importAddr},
}
rewardKey = testKeys[0]
rewardAddr = rewardKey.Address()
rewardsOwner = &secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{rewardAddr},
}
utxoKey = testKeys[1]
utxoAddr = utxoKey.Address()
utxoOwner = secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{utxoAddr},
}
validationAuthKey = testKeys[2]
validationAuthAddr = validationAuthKey.Address()
validationOwner = &secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{validationAuthAddr},
}
// We hard-code [utxoAssetID] and [chainAssetID] to make ordering of UTXOs
// generated by [makeTestUTXOs] reproducible.
utxoAssetID = ids.Empty.Prefix(1789)
chainAssetID = ids.Empty.Prefix(2025)
utxos = makeTestUTXOs(utxoKey)
luxOutput = &lux.TransferableOutput{
Asset: lux.Asset{ID: utxoAssetID},
Out: &secp256k1fx.TransferOutput{
Amt: 7 * constants.Lux,
OutputOwners: utxoOwner,
},
}
chainOwners = map[ids.ID]fx.Owner{
chainID: chainOwner,
}
validationOwners = map[ids.ID]fx.Owner{
validationID: validationOwner,
}
primaryNetworkPermissionlessStaker = &txs.ChainValidator{
Validator: txs.Validator{
NodeID: nodeID,
End: uint64(time.Now().Add(time.Hour).Unix()),
Wght: 2 * constants.Lux,
},
Chain: constants.PrimaryNetworkID,
}
testContext = &builder.Context{
NetworkID: constants.UnitTestID,
UTXOAssetID: utxoAssetID,
ComplexityWeights: gas.Dimensions{
gas.Bandwidth: 1,
gas.DBRead: 10,
gas.DBWrite: 100,
gas.Compute: 1000,
},
GasPrice: 1,
}
dynamicFeeCalculator = fee.NewDynamicCalculator(
testContext.ComplexityWeights,
testContext.GasPrice,
)
testEnvironment = []environment{
{
name: "default",
context: testContext,
feeCalculator: dynamicFeeCalculator,
},
{
name: "default with memo",
context: testContext,
feeCalculator: dynamicFeeCalculator,
memo: []byte("memo"),
},
}
)
type environment struct {
name string
context *builder.Context
feeCalculator fee.Calculator
memo []byte
}
// These tests create a tx, then verify that utxos included in the tx are
// exactly necessary to pay fees for it.
func TestBaseTx(t *testing.T) {
for _, e := range testEnvironment {
t.Run(e.name, func(t *testing.T) {
var (
require = require.New(t)
chainUTXOs = utxotest.NewDeterministicChainUTXOs(t, map[ids.ID][]*lux.UTXO{
constants.PlatformChainID: utxos,
})
backend = wallet.NewBackend(chainUTXOs, nil)
builder = builder.New(set.Of(utxoAddr), e.context, backend)
)
utx, err := builder.NewBaseTx(
[]*lux.TransferableOutput{luxOutput},
common.WithMemo(e.memo),
)
require.NoError(err)
require.Contains(utx.Outputs(), luxOutput)
require.Equal(e.memo, utx.Memo())
requireFeeIsCorrect(
require,
e.feeCalculator,
utx,
nil,
nil,
nil,
)
})
}
}
func TestAddChainValidatorTx(t *testing.T) {
chainValidator := &txs.ChainValidator{
Validator: txs.Validator{
NodeID: nodeID,
End: uint64(time.Now().Add(time.Hour).Unix()),
},
Chain: chainID,
}
for _, e := range testEnvironment {
t.Run(e.name, func(t *testing.T) {
var (
require = require.New(t)
chainUTXOs = utxotest.NewDeterministicChainUTXOs(t, map[ids.ID][]*lux.UTXO{
constants.PlatformChainID: utxos,
})
backend = wallet.NewBackend(chainUTXOs, chainOwners)
builder = builder.New(set.Of(utxoAddr, chainAuthAddr), e.context, backend)
)
utx, err := builder.NewAddChainValidatorTx(
chainValidator,
common.WithMemo(e.memo),
)
require.NoError(err)
require.Equal(chainValidator.Validator, utx.Validator())
require.Equal(chainValidator.Chain, utx.Chain())
require.Equal(e.memo, utx.Memo())
requireFeeIsCorrect(
require,
e.feeCalculator,
utx,
nil,
nil,
nil,
)
})
}
}
func TestRemoveChainValidatorTx(t *testing.T) {
for _, e := range testEnvironment {
t.Run(e.name, func(t *testing.T) {
var (
require = require.New(t)
chainUTXOs = utxotest.NewDeterministicChainUTXOs(t, map[ids.ID][]*lux.UTXO{
constants.PlatformChainID: utxos,
})
backend = wallet.NewBackend(chainUTXOs, chainOwners)
builder = builder.New(set.Of(utxoAddr, chainAuthAddr), e.context, backend)
)
utx, err := builder.NewRemoveChainValidatorTx(
nodeID,
chainID,
common.WithMemo(e.memo),
)
require.NoError(err)
require.Equal(nodeID, utx.NodeID())
require.Equal(chainID, utx.Chain())
require.Equal(e.memo, utx.Memo())
requireFeeIsCorrect(
require,
e.feeCalculator,
utx,
nil,
nil,
nil,
)
})
}
}
func TestCreateChainTx(t *testing.T) {
var (
genesisBytes = []byte{'a', 'b', 'c'}
vmID = ids.GenerateTestID()
fxIDs = []ids.ID{ids.GenerateTestID()}
chainName = "dummyChain"
)
for _, e := range testEnvironment {
t.Run(e.name, func(t *testing.T) {
var (
require = require.New(t)
chainUTXOs = utxotest.NewDeterministicChainUTXOs(t, map[ids.ID][]*lux.UTXO{
constants.PlatformChainID: utxos,
})
backend = wallet.NewBackend(chainUTXOs, chainOwners)
builder = builder.New(set.Of(utxoAddr, chainAuthAddr), e.context, backend)
)
utx, err := builder.NewCreateChainTx(
chainID,
genesisBytes,
vmID,
fxIDs,
chainName,
common.WithMemo(e.memo),
)
require.NoError(err)
require.Equal(chainID, utx.ChainID())
require.Equal(genesisBytes, utx.GenesisData())
require.Equal(vmID, utx.VMID())
require.ElementsMatch(fxIDs, utx.FxIDs())
require.Equal(chainName, utx.BlockchainName())
require.Equal(e.memo, utx.Memo())
requireFeeIsCorrect(
require,
e.feeCalculator,
utx,
nil,
nil,
nil,
)
})
}
}
func TestTransferChainOwnershipTx(t *testing.T) {
for _, e := range testEnvironment {
t.Run(e.name, func(t *testing.T) {
var (
require = require.New(t)
chainUTXOs = utxotest.NewDeterministicChainUTXOs(t, map[ids.ID][]*lux.UTXO{
constants.PlatformChainID: utxos,
})
backend = wallet.NewBackend(chainUTXOs, chainOwners)
builder = builder.New(set.Of(utxoAddr, chainAuthAddr), e.context, backend)
)
utx, err := builder.NewTransferChainOwnershipTx(
chainID,
chainOwner,
common.WithMemo(e.memo),
)
require.NoError(err)
require.Equal(chainID, utx.Chain())
require.Equal(chainOwner, utx.Owner())
require.Equal(e.memo, utx.Memo())
requireFeeIsCorrect(
require,
e.feeCalculator,
utx,
nil,
nil,
nil,
)
})
}
}
func TestImportTx(t *testing.T) {
var (
sourceChainID = ids.GenerateTestID()
importedUTXOs = utxos[4:] // Use the last UTXO (9 Lux) which is sufficient for fees
)
for _, e := range testEnvironment {
t.Run(e.name, func(t *testing.T) {
var (
require = require.New(t)
chainUTXOs = utxotest.NewDeterministicChainUTXOs(t, map[ids.ID][]*lux.UTXO{
constants.PlatformChainID: utxos,
sourceChainID: importedUTXOs,
})
backend = wallet.NewBackend(chainUTXOs, nil)
builder = builder.New(set.Of(utxoAddr), e.context, backend)
)
utx, err := builder.NewImportTx(
sourceChainID,
importOwner,
common.WithMemo(e.memo),
)
require.NoError(err)
require.Equal(sourceChainID, utx.SourceChain())
require.Equal(e.memo, utx.Memo())
require.Empty(utx.Inputs()) // The imported input should be sufficient for fees
require.Len(utx.ImportedInputs(), len(importedUTXOs)) // All utxos should be imported
requireFeeIsCorrect(
require,
e.feeCalculator,
utx,
utx.ImportedInputs(),
nil,
nil,
)
})
}
}
func TestExportTx(t *testing.T) {
exportedOutputs := []*lux.TransferableOutput{luxOutput}
for _, e := range testEnvironment {
t.Run(e.name, func(t *testing.T) {
var (
require = require.New(t)
chainUTXOs = utxotest.NewDeterministicChainUTXOs(t, map[ids.ID][]*lux.UTXO{
constants.PlatformChainID: utxos,
})
backend = wallet.NewBackend(chainUTXOs, nil)
builder = builder.New(set.Of(utxoAddr), e.context, backend)
)
utx, err := builder.NewExportTx(
chainID,
exportedOutputs,
common.WithMemo(e.memo),
)
require.NoError(err)
require.Equal(chainID, utx.DestinationChain())
require.ElementsMatch(exportedOutputs, utx.ExportedOutputs())
require.Equal(e.memo, utx.Memo())
requireFeeIsCorrect(
require,
e.feeCalculator,
utx,
nil,
utx.ExportedOutputs(),
nil,
)
})
}
}
func TestAddPermissionlessValidatorTx(t *testing.T) {
var utxosOffset uint64 = 2025
makeUTXO := func(amount uint64) *lux.UTXO {
utxosOffset++
return &lux.UTXO{
UTXOID: lux.UTXOID{
TxID: ids.Empty.Prefix(utxosOffset),
OutputIndex: uint32(utxosOffset),
},
Asset: lux.Asset{ID: utxoAssetID},
Out: &secp256k1fx.TransferOutput{
Amt: amount,
OutputOwners: utxoOwner,
},
}
}
var (
utxos = []*lux.UTXO{
makeUTXO(1 * constants.NanoLux), // small UTXO
makeUTXO(9 * constants.Lux), // large UTXO
}
validationRewardsOwner = rewardsOwner
delegationRewardsOwner = rewardsOwner
delegationShares uint32 = reward.PercentDenominator
)
sk, err := localsigner.New()
require.NoError(t, err)
pop, err := signer.NewProofOfPossession(sk)
require.NoError(t, err)
for _, e := range testEnvironment {
t.Run(e.name, func(t *testing.T) {
var (
require = require.New(t)
chainUTXOs = utxotest.NewDeterministicChainUTXOs(t, map[ids.ID][]*lux.UTXO{
constants.PlatformChainID: utxos,
})
backend = wallet.NewBackend(chainUTXOs, nil)
builder = builder.New(set.Of(utxoAddr, rewardAddr), e.context, backend)
)
utx, err := builder.NewAddPermissionlessValidatorTx(
primaryNetworkPermissionlessStaker,
pop,
utxoAssetID,
validationRewardsOwner,
delegationRewardsOwner,
delegationShares,
common.WithMemo(e.memo),
)
require.NoError(err)
require.Equal(primaryNetworkPermissionlessStaker.Validator, utx.Validator())
require.Equal(primaryNetworkPermissionlessStaker.Chain, utx.Chain())
// signer.ProofOfPossession carries a lazily-parsed `publicKey` that
// Verify populates and that is NOT wire state. The round-tripped tx has
// not called Verify, so comparing whole structs diffs on a cache field
// while every wire byte matches. Compare the wire fields.
gotPoP, ok := utx.Signer().(*signer.ProofOfPossession)
require.True(ok, "signer is %T, want *signer.ProofOfPossession", utx.Signer())
require.Equal(pop.PublicKey, gotPoP.PublicKey)
require.Equal(pop.ProofOfPossession, gotPoP.ProofOfPossession)
// Outputs should be merged if possible. For example, if there are two
// unlocked inputs consumed for staking, this should only produce one staked
// output.
require.Len(utx.StakeOuts(), 1)
// check stake amount
require.Equal(
map[ids.ID]uint64{
utxoAssetID: primaryNetworkPermissionlessStaker.Wght,
},
addOutputAmounts(utx.StakeOuts()),
)
require.Equal(validationRewardsOwner, utx.ValidatorRewardsOwner())
require.Equal(delegationRewardsOwner, utx.DelegatorRewardsOwner())
require.Equal(delegationShares, utx.DelegationShares())
require.Equal(e.memo, utx.Memo())
requireFeeIsCorrect(
require,
e.feeCalculator,
utx,
nil,
utx.StakeOuts(),
nil,
)
})
}
}
func TestAddPermissionlessDelegatorTx(t *testing.T) {
for _, e := range testEnvironment {
t.Run(e.name, func(t *testing.T) {
var (
require = require.New(t)
chainUTXOs = utxotest.NewDeterministicChainUTXOs(t, map[ids.ID][]*lux.UTXO{
constants.PlatformChainID: utxos,
})
backend = wallet.NewBackend(chainUTXOs, nil)
builder = builder.New(set.Of(utxoAddr, rewardAddr), e.context, backend)
)
utx, err := builder.NewAddPermissionlessDelegatorTx(
primaryNetworkPermissionlessStaker,
utxoAssetID,
rewardsOwner,
common.WithMemo(e.memo),
)
require.NoError(err)
require.Equal(primaryNetworkPermissionlessStaker.Validator, utx.Validator())
require.Equal(primaryNetworkPermissionlessStaker.Chain, utx.Chain())
// check stake amount
require.Equal(
map[ids.ID]uint64{
utxoAssetID: primaryNetworkPermissionlessStaker.Wght,
},
addOutputAmounts(utx.StakeOuts()),
)
require.Equal(rewardsOwner, utx.DelegationRewardsOwner())
require.Equal(e.memo, utx.Memo())
requireFeeIsCorrect(
require,
e.feeCalculator,
utx,
nil,
utx.StakeOuts(),
nil,
)
})
}
}
// TestConvertNetworkToL1Tx was removed: the wallet builder method it exercised,
// NewConvertNetworkToL1Tx, no longer exists. It disappeared in d008eba286
// ("security.Mode: decomplect network security into its own package") — a commit
// about moving a package, so this looks like collateral damage rather than an
// intentional API removal.
//
// It is NOT restorable mechanically. The tx type is now txs.ConvertNetworkTx over
// txs.NetworkValidator, and txs.NewConvertNetworkTx requires two arguments the old
// builder signature never had — `parent ids.ID` and `sec security.Mode`. Choosing a
// security mode for a network conversion is a protocol decision, not something a
// test repair should invent, so the builder method needs to come back deliberately
// and this test with it. The fee helpers it needs are still present
// (IntrinsicConvertNetworkToL1TxComplexities, ConvertNetworkToL1ValidatorComplexity).
//
// Leaving it in place cost far more than it saved: it did not compile, so ALL 14
// tests in this file were dead, not just this one.
func TestRegisterL1ValidatorTx(t *testing.T) {
const (
expiry = 1731005097
weight = 7905001371
balance = constants.Lux
)
sk, err := localsigner.New()
require.NoError(t, err)
pop, err := signer.NewProofOfPossession(sk)
require.NoError(t, err)
addressedCallPayload, err := message.NewRegisterL1Validator(
chainID,
nodeID,
pop.PublicKey,
expiry,
message.PChainOwner{
Threshold: 1,
Addresses: []ids.ShortID{
ids.GenerateTestShortID(),
},
},
message.PChainOwner{
Threshold: 1,
Addresses: []ids.ShortID{
ids.GenerateTestShortID(),
},
},
weight,
)
require.NoError(t, err)
addressedCall, err := payload.NewAddressedCall(
utils.RandomBytes(20),
addressedCallPayload.Bytes(),
)
require.NoError(t, err)
unsignedWarp, err := warp.NewUnsignedMessage(
constants.UnitTestID,
ids.GenerateTestID(),
addressedCall.Bytes(),
)
require.NoError(t, err)
signers := set.NewBits(0)
unsignedBytes := unsignedWarp.Bytes()
sig, err := sk.Sign(unsignedBytes)
require.NoError(t, err)
sigBytes := [bls.SignatureLen]byte{}
copy(sigBytes[:], bls.SignatureToBytes(sig))
warp, err := warp.NewMessage(
unsignedWarp,
&warp.BitSetSignature{
Signers: signers.Bytes(),
Signature: sigBytes,
},
)
require.NoError(t, err)
warpMessageBytes := warp.Bytes()
for _, e := range testEnvironment {
t.Run(e.name, func(t *testing.T) {
var (
require = require.New(t)
chainUTXOs = utxotest.NewDeterministicChainUTXOs(t, map[ids.ID][]*lux.UTXO{
constants.PlatformChainID: utxos,
})
backend = wallet.NewBackend(chainUTXOs, nil)
builder = builder.New(set.Of(utxoAddr), e.context, backend)
)
utx, err := builder.NewRegisterL1ValidatorTx(
balance,
pop.ProofOfPossession,
warpMessageBytes,
common.WithMemo(e.memo),
)
require.NoError(err)
require.Equal(balance, utx.Balance())
require.Equal(pop.ProofOfPossession, utx.ProofOfPossession())
require.Equal(warpMessageBytes, utx.Message())
require.Equal(e.memo, utx.Memo())
requireFeeIsCorrect(
require,
e.feeCalculator,
utx,
nil,
nil,
map[ids.ID]uint64{
e.context.UTXOAssetID: balance, // Balance of the validator
},
)
})
}
}
func TestSetL1ValidatorWeightTx(t *testing.T) {
const (
nonce = 1
weight = 7905001371
)
var (
validationID = ids.GenerateTestID()
chainID = ids.GenerateTestID()
address = utils.RandomBytes(20)
)
addressedCallPayload, err := message.NewL1ValidatorWeight(
validationID,
nonce,
weight,
)
require.NoError(t, err)
addressedCall, err := payload.NewAddressedCall(
address,
addressedCallPayload.Bytes(),
)
require.NoError(t, err)
unsignedWarp, err := warp.NewUnsignedMessage(
constants.UnitTestID,
chainID,
addressedCall.Bytes(),
)
require.NoError(t, err)
sk, err := localsigner.New()
require.NoError(t, err)
sig, err := sk.Sign(unsignedWarp.Bytes())
require.NoError(t, err)
warp, err := warp.NewMessage(
unsignedWarp,
&warp.BitSetSignature{
Signers: set.NewBits(0).Bytes(),
Signature: ([bls.SignatureLen]byte)(
bls.SignatureToBytes(sig),
),
},
)
require.NoError(t, err)
warpMessageBytes := warp.Bytes()
for _, e := range testEnvironment {
t.Run(e.name, func(t *testing.T) {
var (
require = require.New(t)
chainUTXOs = utxotest.NewDeterministicChainUTXOs(t, map[ids.ID][]*lux.UTXO{
constants.PlatformChainID: utxos,
})
backend = wallet.NewBackend(chainUTXOs, nil)
builder = builder.New(set.Of(utxoAddr), e.context, backend)
)
utx, err := builder.NewSetL1ValidatorWeightTx(
warpMessageBytes,
common.WithMemo(e.memo),
)
require.NoError(err)
require.Equal(warpMessageBytes, utx.Message())
require.Equal(e.memo, utx.Memo())
requireFeeIsCorrect(
require,
e.feeCalculator,
utx,
nil,
nil,
nil,
)
})
}
}
func TestIncreaseL1ValidatorBalanceTx(t *testing.T) {
const balance = constants.Lux
validationID := ids.GenerateTestID()
for _, e := range testEnvironment {
t.Run(e.name, func(t *testing.T) {
var (
require = require.New(t)
chainUTXOs = utxotest.NewDeterministicChainUTXOs(t, map[ids.ID][]*lux.UTXO{
constants.PlatformChainID: utxos,
})
backend = wallet.NewBackend(chainUTXOs, nil)
builder = builder.New(set.Of(utxoAddr), e.context, backend)
)
utx, err := builder.NewIncreaseL1ValidatorBalanceTx(
validationID,
balance,
common.WithMemo(e.memo),
)
require.NoError(err)
require.Equal(validationID, utx.ValidationID())
require.Equal(balance, utx.Balance())
require.Equal(e.memo, utx.Memo())
requireFeeIsCorrect(
require,
e.feeCalculator,
utx,
nil,
nil,
map[ids.ID]uint64{
e.context.UTXOAssetID: balance, // Balance increase
},
)
})
}
}
func TestDisableL1ValidatorTx(t *testing.T) {
for _, e := range testEnvironment {
t.Run(e.name, func(t *testing.T) {
var (
require = require.New(t)
chainUTXOs = utxotest.NewDeterministicChainUTXOs(t, map[ids.ID][]*lux.UTXO{
constants.PlatformChainID: utxos,
})
backend = wallet.NewBackend(chainUTXOs, validationOwners)
builder = builder.New(set.Of(utxoAddr, validationAuthAddr), e.context, backend)
)
utx, err := builder.NewDisableL1ValidatorTx(
validationID,
common.WithMemo(e.memo),
)
require.NoError(err)
require.Equal(validationID, utx.ValidationID())
require.Equal(e.memo, utx.Memo())
requireFeeIsCorrect(
require,
e.feeCalculator,
utx,
nil,
nil,
nil,
)
})
}
}
func makeTestUTXOs(utxosKey *secp256k1.PrivateKey) []*lux.UTXO {
// Note: we avoid ids.GenerateTestNodeID here to make sure that UTXO IDs
// won't change run by run. This simplifies checking what utxos are included
// in the built txs.
const utxosOffset uint64 = 2025
utxosAddr := utxosKey.Address()
return []*lux.UTXO{
{ // a small UTXO first, which should not be enough to pay fees
UTXOID: lux.UTXOID{
TxID: ids.Empty.Prefix(utxosOffset),
OutputIndex: uint32(utxosOffset),
},
Asset: lux.Asset{ID: utxoAssetID},
Out: &secp256k1fx.TransferOutput{
Amt: 2 * constants.MilliLux,
OutputOwners: secp256k1fx.OutputOwners{
Locktime: 0,
Addrs: []ids.ShortID{utxosAddr},
Threshold: 1,
},
},
},
{ // a locked, small UTXO
UTXOID: lux.UTXOID{
TxID: ids.Empty.Prefix(utxosOffset + 1),
OutputIndex: uint32(utxosOffset + 1),
},
Asset: lux.Asset{ID: utxoAssetID},
Out: &stakeable.LockOut{
Locktime: uint64(time.Now().Add(time.Hour).Unix()),
TransferableOut: &secp256k1fx.TransferOutput{
Amt: 3 * constants.MilliLux,
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{utxosAddr},
},
},
},
},
{ // a chainAssetID denominated UTXO
UTXOID: lux.UTXOID{
TxID: ids.Empty.Prefix(utxosOffset + 2),
OutputIndex: uint32(utxosOffset + 2),
},
Asset: lux.Asset{ID: chainAssetID},
Out: &secp256k1fx.TransferOutput{
Amt: 99 * constants.MegaLux,
OutputOwners: secp256k1fx.OutputOwners{
Locktime: 0,
Addrs: []ids.ShortID{utxosAddr},
Threshold: 1,
},
},
},
{ // a locked, large UTXO
UTXOID: lux.UTXOID{
TxID: ids.Empty.Prefix(utxosOffset + 3),
OutputIndex: uint32(utxosOffset + 3),
},
Asset: lux.Asset{ID: utxoAssetID},
Out: &stakeable.LockOut{
Locktime: uint64(time.Now().Add(time.Hour).Unix()),
TransferableOut: &secp256k1fx.TransferOutput{
Amt: 88 * constants.Lux,
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{utxosAddr},
},
},
},
},
{ // a large UTXO last, which should be enough to pay any fee by itself
UTXOID: lux.UTXOID{
TxID: ids.Empty.Prefix(utxosOffset + 4),
OutputIndex: uint32(utxosOffset + 4),
},
Asset: lux.Asset{ID: utxoAssetID},
Out: &secp256k1fx.TransferOutput{
Amt: 9 * constants.Lux,
OutputOwners: secp256k1fx.OutputOwners{
Locktime: 0,
Addrs: []ids.ShortID{utxosAddr},
Threshold: 1,
},
},
},
}
}
// requireFeeIsCorrect calculates the required fee for the unsigned transaction
// and verifies that the burned amount is exactly the required fee.
// spender is the pair of sides the fee assertion balances. txs.UnsignedTx
// exposes Outputs() but not Inputs(); every concrete platformvm tx has both
// (spendingTx supplies them), so the test asks for exactly what it needs rather
// than widening a production interface for a test's benefit.
type spender interface {
Inputs() []*lux.TransferableInput
Outputs() []*lux.TransferableOutput
}
func requireFeeIsCorrect(
require *require.Assertions,
feeCalculator fee.Calculator,
utx txs.UnsignedTx,
additionalIns []*lux.TransferableInput,
additionalOuts []*lux.TransferableOutput,
additionalFee map[ids.ID]uint64,
) {
// The separate *lux.BaseTx argument is gone: the tx types no longer embed
// one, and passing &utx.BaseTx.BaseTx at 14 call sites was only ever a way
// to reach the ins and outs that utx itself now answers for.
sp, ok := utx.(spender)
require.True(ok, "%T exposes neither Inputs() nor Outputs()", utx)
amountConsumed := addInputAmounts(sp.Inputs(), additionalIns)
amountProduced := addOutputAmounts(sp.Outputs(), additionalOuts)
expectedFee, err := feeCalculator.CalculateFee(utx)
require.NoError(err)
expectedAmountBurned := addAmounts(
map[ids.ID]uint64{
utxoAssetID: expectedFee,
},
additionalFee,
)
expectedAmountConsumed := addAmounts(amountProduced, expectedAmountBurned)
require.Equal(expectedAmountConsumed, amountConsumed)
}
func addAmounts(allAmounts ...map[ids.ID]uint64) map[ids.ID]uint64 {
amounts := make(map[ids.ID]uint64)
for _, amountsToAdd := range allAmounts {
for assetID, amount := range amountsToAdd {
amounts[assetID] += amount
}
}
return amounts
}
func addInputAmounts(inputSlices ...[]*lux.TransferableInput) map[ids.ID]uint64 {
consumed := make(map[ids.ID]uint64)
for _, inputs := range inputSlices {
for _, in := range inputs {
consumed[in.AssetID()] += in.In.Amount()
}
}
return consumed
}
func addOutputAmounts(outputSlices ...[]*lux.TransferableOutput) map[ids.ID]uint64 {
produced := make(map[ids.ID]uint64)
for _, outputs := range outputSlices {
for _, out := range outputs {
produced[out.AssetID()] += out.Out.Amount()
}
}
return produced
}