mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
One place, one way to define UTXO primitives. Delete the in-tree duplicate at node/vms/components/lux/ and import the standalone luxfi/utxo package with a 'lux' alias so existing call-sites (lux.UTXO, lux.TransferableInput, etc.) remain unchanged in consumers. Files changed: 167 imports rewritten, 2 directories deleted. Build still green on the whole tree except pre-existing examples/multi-network QChainMainnetID issue (unrelated). Next: rename luxfi/utxo/luxmock → utxomock for full brand-neutrality; that is a separate PR since it requires touching all consumer alias names.
220 lines
5.0 KiB
Go
220 lines
5.0 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package executor
|
|
|
|
import (
|
|
"math/rand"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/luxfi/crypto/secp256k1"
|
|
"github.com/luxfi/database/prefixdb"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/vm/chains/atomic"
|
|
"github.com/luxfi/node/upgrade/upgradetest"
|
|
lux "github.com/luxfi/utxo"
|
|
"github.com/luxfi/node/vms/platformvm/state"
|
|
"github.com/luxfi/node/vms/platformvm/txs"
|
|
"github.com/luxfi/node/wallet/chain/p/builder"
|
|
"github.com/luxfi/utxo/secp256k1fx"
|
|
)
|
|
|
|
var fundedSharedMemoryCalls byte
|
|
|
|
func TestNewImportTx(t *testing.T) {
|
|
env := newEnvironment(t, upgradetest.ApricotPhase5)
|
|
|
|
type test struct {
|
|
description string
|
|
sourceChainID ids.ID
|
|
sharedMemory atomic.SharedMemory
|
|
timestamp time.Time
|
|
expectedErr error
|
|
}
|
|
|
|
sourceKey, err := secp256k1.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
|
|
customAssetID := ids.GenerateTestID()
|
|
// generate a constant random source generator.
|
|
randSrc := rand.NewSource(0)
|
|
tests := []test{
|
|
{
|
|
description: "can't pay fee",
|
|
sourceChainID: env.rt.XChainID,
|
|
sharedMemory: fundedSharedMemory(
|
|
t,
|
|
env,
|
|
sourceKey,
|
|
env.rt.XChainID,
|
|
map[ids.ID]uint64{},
|
|
randSrc,
|
|
),
|
|
expectedErr: builder.ErrInsufficientFunds,
|
|
},
|
|
{
|
|
description: "can barely pay fee",
|
|
sourceChainID: env.rt.XChainID,
|
|
sharedMemory: fundedSharedMemory(
|
|
t,
|
|
env,
|
|
sourceKey,
|
|
env.rt.XChainID,
|
|
map[ids.ID]uint64{
|
|
env.rt.XAssetID: 1,
|
|
},
|
|
randSrc,
|
|
),
|
|
expectedErr: nil,
|
|
},
|
|
{
|
|
description: "attempting to import from C-chain",
|
|
sourceChainID: env.rt.CChainID,
|
|
sharedMemory: fundedSharedMemory(
|
|
t,
|
|
env,
|
|
sourceKey,
|
|
env.rt.CChainID,
|
|
map[ids.ID]uint64{
|
|
env.rt.XAssetID: 1,
|
|
},
|
|
randSrc,
|
|
),
|
|
timestamp: env.config.UpgradeConfig.ApricotPhase5Time,
|
|
expectedErr: nil,
|
|
},
|
|
{
|
|
description: "attempting to import non-lux from X-chain",
|
|
sourceChainID: env.rt.XChainID,
|
|
sharedMemory: fundedSharedMemory(
|
|
t,
|
|
env,
|
|
sourceKey,
|
|
env.rt.XChainID,
|
|
map[ids.ID]uint64{
|
|
customAssetID: 1,
|
|
},
|
|
randSrc,
|
|
),
|
|
timestamp: env.config.UpgradeConfig.ApricotPhase5Time,
|
|
expectedErr: nil,
|
|
},
|
|
}
|
|
|
|
to := &secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{ids.GenerateTestShortID()},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.description, func(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
env.msm.SharedMemory = tt.sharedMemory
|
|
|
|
wallet := newWallet(t, env, walletConfig{
|
|
keys: []*secp256k1.PrivateKey{sourceKey},
|
|
importSourceChainIDs: []ids.ID{tt.sourceChainID},
|
|
})
|
|
|
|
tx, err := wallet.IssueImportTx(
|
|
tt.sourceChainID,
|
|
to,
|
|
)
|
|
require.ErrorIs(err, tt.expectedErr)
|
|
if tt.expectedErr != nil {
|
|
return
|
|
}
|
|
|
|
unsignedTx := tx.Unsigned.(*txs.ImportTx)
|
|
require.NotEmpty(unsignedTx.ImportedInputs)
|
|
numInputs := len(unsignedTx.Ins) + len(unsignedTx.ImportedInputs)
|
|
require.Equal(len(tx.Creds), numInputs, "should have the same number of credentials as inputs")
|
|
|
|
totalIn := uint64(0)
|
|
for _, in := range unsignedTx.Ins {
|
|
totalIn += in.Input().Amount()
|
|
}
|
|
for _, in := range unsignedTx.ImportedInputs {
|
|
totalIn += in.Input().Amount()
|
|
}
|
|
totalOut := uint64(0)
|
|
for _, out := range unsignedTx.Outs {
|
|
totalOut += out.Out.Amount()
|
|
}
|
|
|
|
require.Equal(totalIn, totalOut)
|
|
|
|
stateDiff, err := state.NewDiff(lastAcceptedID, env)
|
|
require.NoError(err)
|
|
|
|
stateDiff.SetTimestamp(tt.timestamp)
|
|
|
|
feeCalculator := state.PickFeeCalculator(env.config, stateDiff)
|
|
_, _, _, err = StandardTx(
|
|
&env.backend,
|
|
feeCalculator,
|
|
tx,
|
|
stateDiff,
|
|
)
|
|
require.NoError(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Returns a shared memory where GetDatabase returns a database
|
|
// where [recipientKey] has a balance of [amt]
|
|
func fundedSharedMemory(
|
|
t *testing.T,
|
|
env *environment,
|
|
sourceKey *secp256k1.PrivateKey,
|
|
peerChain ids.ID,
|
|
assets map[ids.ID]uint64,
|
|
randSrc rand.Source,
|
|
) atomic.SharedMemory {
|
|
fundedSharedMemoryCalls++
|
|
m := atomic.NewMemory(prefixdb.New([]byte{fundedSharedMemoryCalls}, env.baseDB))
|
|
|
|
sm := m.NewSharedMemory(env.rt.ChainID)
|
|
peerSharedMemory := m.NewSharedMemory(peerChain)
|
|
|
|
for assetID, amt := range assets {
|
|
utxo := &lux.UTXO{
|
|
UTXOID: lux.UTXOID{
|
|
TxID: ids.GenerateTestID(),
|
|
OutputIndex: uint32(randSrc.Int63()),
|
|
},
|
|
Asset: lux.Asset{ID: assetID},
|
|
Out: &secp256k1fx.TransferOutput{
|
|
Amt: amt,
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Locktime: 0,
|
|
Addrs: []ids.ShortID{sourceKey.Address()},
|
|
Threshold: 1,
|
|
},
|
|
},
|
|
}
|
|
utxoBytes, err := txs.Codec.Marshal(txs.CodecVersion, utxo)
|
|
require.NoError(t, err)
|
|
|
|
inputID := utxo.InputID()
|
|
require.NoError(t, peerSharedMemory.Apply(map[ids.ID]*atomic.Requests{
|
|
env.rt.ChainID: {
|
|
PutRequests: []*atomic.Element{
|
|
{
|
|
Key: inputID[:],
|
|
Value: utxoBytes,
|
|
Traits: [][]byte{
|
|
sourceKey.Address().Bytes(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}))
|
|
}
|
|
|
|
return sm
|
|
}
|