mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
The module already called itself github.com/luxfi/node while containing no node in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and the Go column of the matrix measured a VM plugin. A repo that fetches the thing it claims to be has not replaced it, so the Go sources move in — 1515 files, the daemon and everything under it — and the build reaches outside this module for nothing. Rust and C++ sit beside it unchanged and are still measured against it by the differential. That is the point of one repo: the reference implementation and the two ports are the same checkout, so parity is a thing you can run rather than a thing you assert. Verified: builds, and the closure carries no lux-private — checked at build time rather than believed. The daemon starts twelve chains on a local network (P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
963 lines
25 KiB
Go
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
|
|
}
|