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.
336 lines
9.2 KiB
Go
336 lines
9.2 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package xvm
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/luxfi/database"
|
|
"github.com/luxfi/metric"
|
|
|
|
"github.com/luxfi/database/memdb"
|
|
|
|
"github.com/luxfi/database/prefixdb"
|
|
|
|
"github.com/luxfi/database/versiondb"
|
|
|
|
"github.com/luxfi/ids"
|
|
|
|
"github.com/luxfi/util"
|
|
|
|
"github.com/luxfi/constants"
|
|
|
|
"github.com/luxfi/crypto/secp256k1"
|
|
|
|
"github.com/luxfi/node/vms/xvm/txs"
|
|
|
|
"github.com/luxfi/node/vms/components/index"
|
|
|
|
lux "github.com/luxfi/utxo"
|
|
|
|
"github.com/luxfi/utxo/secp256k1fx"
|
|
)
|
|
|
|
func TestIndexTransaction_Ordered(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
env := setup(t, &envConfig{fork: activeUpgrade, indexTransactions: true})
|
|
env.vm.Lock.Unlock()
|
|
|
|
key := keys[0]
|
|
addr := key.PublicKey().Address()
|
|
txAssetID := lux.Asset{ID: env.genesisTx.ID()}
|
|
|
|
var txs []*txs.Tx
|
|
for i := 0; i < 5; i++ {
|
|
// make utxo
|
|
utxoID := lux.UTXOID{
|
|
TxID: ids.GenerateTestID(),
|
|
}
|
|
utxo := buildUTXO(utxoID, txAssetID, addr)
|
|
env.vm.state.AddUTXO(utxo)
|
|
require.NoError(env.vm.state.Commit())
|
|
|
|
// make transaction
|
|
tx := buildTX(env.consensusRuntime.ChainID, utxoID, txAssetID, addr)
|
|
require.NoError(tx.SignSECP256K1Fx([][]*secp256k1.PrivateKey{{key}}))
|
|
|
|
issueAndAccept(require, env.vm, tx)
|
|
|
|
txs = append(txs, tx)
|
|
}
|
|
|
|
// Debug: try reading directly from addressTxsIndexer
|
|
txIDs, err := env.vm.addressTxsIndexer.Read(addr[:], txAssetID.ID, 0, 5)
|
|
require.NoError(err, "Failed to read from indexer")
|
|
t.Logf("Read %d transactions from indexer for addr %s", len(txIDs), addr)
|
|
|
|
// for each tx check its indexed at right index
|
|
for i, tx := range txs {
|
|
assertIndexedTX(t, env.vm.baseDB, uint64(i), addr, txAssetID.ID, tx.ID())
|
|
}
|
|
assertLatestIdx(t, env.vm.baseDB, addr, txAssetID.ID, 5)
|
|
}
|
|
|
|
func TestIndexTransaction_MultipleTransactions(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
env := setup(t, &envConfig{fork: activeUpgrade, indexTransactions: true})
|
|
env.vm.Lock.Unlock()
|
|
|
|
addressTxMap := map[ids.ShortID]*txs.Tx{}
|
|
txAssetID := lux.Asset{ID: env.genesisTx.ID()}
|
|
|
|
for _, key := range keys {
|
|
addr := key.PublicKey().Address()
|
|
|
|
// make utxo
|
|
utxoID := lux.UTXOID{
|
|
TxID: ids.GenerateTestID(),
|
|
}
|
|
utxo := buildUTXO(utxoID, txAssetID, addr)
|
|
env.vm.state.AddUTXO(utxo)
|
|
require.NoError(env.vm.state.Commit())
|
|
|
|
// make transaction
|
|
tx := buildTX(env.consensusRuntime.ChainID, utxoID, txAssetID, addr)
|
|
require.NoError(tx.SignSECP256K1Fx([][]*secp256k1.PrivateKey{{key}}))
|
|
|
|
// issue transaction
|
|
issueAndAccept(require, env.vm, tx)
|
|
|
|
addressTxMap[addr] = tx
|
|
}
|
|
|
|
// ensure length is same as keys length
|
|
require.Len(addressTxMap, len(keys))
|
|
|
|
// for each *UniqueTx check its indexed at right index for the right address
|
|
for addr, tx := range addressTxMap {
|
|
assertIndexedTX(t, env.vm.baseDB, 0, addr, txAssetID.ID, tx.ID())
|
|
assertLatestIdx(t, env.vm.baseDB, addr, txAssetID.ID, 1)
|
|
}
|
|
}
|
|
|
|
func TestIndexTransaction_MultipleAddresses(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
env := setup(t, &envConfig{fork: activeUpgrade, indexTransactions: true})
|
|
env.vm.Lock.Unlock()
|
|
|
|
addrs := make([]ids.ShortID, len(keys))
|
|
for i, key := range keys {
|
|
addrs[i] = key.PublicKey().Address()
|
|
}
|
|
utils.Sort(addrs)
|
|
|
|
txAssetID := lux.Asset{ID: env.genesisTx.ID()}
|
|
|
|
key := keys[0]
|
|
addr := key.PublicKey().Address()
|
|
|
|
// make utxo
|
|
utxoID := lux.UTXOID{
|
|
TxID: ids.GenerateTestID(),
|
|
}
|
|
utxo := buildUTXO(utxoID, txAssetID, addr)
|
|
env.vm.state.AddUTXO(utxo)
|
|
require.NoError(env.vm.state.Commit())
|
|
|
|
// make transaction
|
|
tx := buildTX(env.consensusRuntime.ChainID, utxoID, txAssetID, addrs...)
|
|
require.NoError(tx.SignSECP256K1Fx([][]*secp256k1.PrivateKey{{key}}))
|
|
|
|
issueAndAccept(require, env.vm, tx)
|
|
|
|
assertIndexedTX(t, env.vm.baseDB, 0, addr, txAssetID.ID, tx.ID())
|
|
assertLatestIdx(t, env.vm.baseDB, addr, txAssetID.ID, 1)
|
|
}
|
|
|
|
func TestIndexer_Read(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
env := setup(t, &envConfig{fork: activeUpgrade, indexTransactions: true})
|
|
env.vm.Lock.Unlock()
|
|
|
|
// generate test address and asset IDs
|
|
assetID := ids.GenerateTestID()
|
|
addr := ids.GenerateTestShortID()
|
|
|
|
// setup some fake txs under the above generated address and asset IDs
|
|
testTxs := initTestTxIndex(t, env.vm.baseDB, addr, assetID, 25)
|
|
require.Len(testTxs, 25)
|
|
|
|
// read the pages, 5 items at a time
|
|
var (
|
|
cursor uint64
|
|
pageSize uint64 = 5
|
|
)
|
|
for cursor < 25 {
|
|
txIDs, err := env.vm.addressTxsIndexer.Read(addr[:], assetID, cursor, pageSize)
|
|
require.NoError(err)
|
|
require.Len(txIDs, 5)
|
|
require.Equal(txIDs, testTxs[cursor:cursor+pageSize])
|
|
cursor += pageSize
|
|
}
|
|
}
|
|
|
|
func TestIndexingNewInitWithIndexingEnabled(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
db := memdb.New()
|
|
|
|
// start with indexing enabled
|
|
_, err := index.NewIndexer(db, nil, "", metric.NewNoOp().Registry(), true)
|
|
require.NoError(err)
|
|
|
|
// now disable indexing with allow-incomplete set to false
|
|
_, err = index.NewNoIndexer(db, false)
|
|
require.ErrorIs(err, index.ErrCausesIncompleteIndex)
|
|
|
|
// now disable indexing with allow-incomplete set to true
|
|
_, err = index.NewNoIndexer(db, true)
|
|
require.NoError(err)
|
|
}
|
|
|
|
func TestIndexingNewInitWithIndexingDisabled(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
db := memdb.New()
|
|
|
|
// disable indexing with allow-incomplete set to false
|
|
_, err := index.NewNoIndexer(db, false)
|
|
require.NoError(err)
|
|
|
|
// It's not OK to have an incomplete index when allowIncompleteIndices is false
|
|
_, err = index.NewIndexer(db, nil, "", metric.NewNoOp().Registry(), false)
|
|
require.ErrorIs(err, index.ErrIndexingRequiredFromGenesis)
|
|
|
|
// It's OK to have an incomplete index when allowIncompleteIndices is true
|
|
_, err = index.NewIndexer(db, nil, "", metric.NewNoOp().Registry(), true)
|
|
require.NoError(err)
|
|
|
|
// It's OK to have an incomplete index when indexing currently disabled
|
|
_, err = index.NewNoIndexer(db, false)
|
|
require.NoError(err)
|
|
|
|
// It's OK to have an incomplete index when allowIncompleteIndices is true
|
|
_, err = index.NewNoIndexer(db, true)
|
|
require.NoError(err)
|
|
}
|
|
|
|
func TestIndexingAllowIncomplete(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
baseDB := memdb.New()
|
|
db := versiondb.New(baseDB)
|
|
// disabled indexer will persist idxEnabled as false
|
|
_, err := index.NewNoIndexer(db, false)
|
|
require.NoError(err)
|
|
|
|
// we initialize with indexing enabled now and allow incomplete indexing as false
|
|
_, err = index.NewIndexer(db, nil, "", metric.NewNoOp().Registry(), false)
|
|
// we should get error because:
|
|
// - indexing was disabled previously
|
|
// - node now is asked to enable indexing with allow incomplete set to false
|
|
require.ErrorIs(err, index.ErrIndexingRequiredFromGenesis)
|
|
}
|
|
|
|
func buildUTXO(utxoID lux.UTXOID, txAssetID lux.Asset, addr ids.ShortID) *lux.UTXO {
|
|
return &lux.UTXO{
|
|
UTXOID: utxoID,
|
|
Asset: txAssetID,
|
|
Out: &secp256k1fx.TransferOutput{
|
|
Amt: startBalance,
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: []ids.ShortID{addr},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func buildTX(chainID ids.ID, utxoID lux.UTXOID, txAssetID lux.Asset, address ...ids.ShortID) *txs.Tx {
|
|
return &txs.Tx{Unsigned: &txs.BaseTx{
|
|
BaseTx: lux.BaseTx{
|
|
NetworkID: constants.UnitTestID,
|
|
BlockchainID: chainID,
|
|
Ins: []*lux.TransferableInput{{
|
|
UTXOID: utxoID,
|
|
Asset: txAssetID,
|
|
In: &secp256k1fx.TransferInput{
|
|
Amt: startBalance,
|
|
Input: secp256k1fx.Input{SigIndices: []uint32{0}},
|
|
},
|
|
}},
|
|
Outs: []*lux.TransferableOutput{{
|
|
Asset: txAssetID,
|
|
Out: &secp256k1fx.TransferOutput{
|
|
Amt: startBalance - testTxFee,
|
|
OutputOwners: secp256k1fx.OutputOwners{
|
|
Threshold: 1,
|
|
Addrs: address,
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
}}
|
|
}
|
|
|
|
func assertLatestIdx(t *testing.T, db database.Database, sourceAddress ids.ShortID, assetID ids.ID, expectedIdx uint64) {
|
|
require := require.New(t)
|
|
|
|
addressDB := prefixdb.New(sourceAddress[:], db)
|
|
assetDB := prefixdb.New(assetID[:], addressDB)
|
|
|
|
expectedIdxBytes := database.PackUInt64(expectedIdx)
|
|
idxBytes, err := assetDB.Get([]byte("idx"))
|
|
require.NoError(err)
|
|
require.Equal(expectedIdxBytes, idxBytes)
|
|
}
|
|
|
|
func assertIndexedTX(t *testing.T, db database.Database, index uint64, sourceAddress ids.ShortID, assetID ids.ID, transactionID ids.ID) {
|
|
require := require.New(t)
|
|
|
|
addressDB := prefixdb.New(sourceAddress[:], db)
|
|
assetDB := prefixdb.New(assetID[:], addressDB)
|
|
|
|
idxBytes := database.PackUInt64(index)
|
|
txID, err := database.GetID(assetDB, idxBytes)
|
|
require.NoError(err)
|
|
require.Equal(transactionID, txID)
|
|
}
|
|
|
|
// Sets up test tx IDs in DB in the following structure for the indexer to pick
|
|
// them up:
|
|
//
|
|
// [address] prefix DB
|
|
// [assetID] prefix DB
|
|
// - "idx": 2
|
|
// - 0: txID1
|
|
// - 1: txID1
|
|
func initTestTxIndex(t *testing.T, db database.Database, address ids.ShortID, assetID ids.ID, txCount int) []ids.ID {
|
|
require := require.New(t)
|
|
|
|
testTxs := make([]ids.ID, txCount)
|
|
for i := 0; i < txCount; i++ {
|
|
testTxs[i] = ids.GenerateTestID()
|
|
}
|
|
|
|
addressPrefixDB := prefixdb.New(address[:], db)
|
|
assetPrefixDB := prefixdb.New(assetID[:], addressPrefixDB)
|
|
|
|
for i, txID := range testTxs {
|
|
idxBytes := database.PackUInt64(uint64(i))
|
|
txID := txID
|
|
require.NoError(assetPrefixDB.Put(idxBytes, txID[:]))
|
|
}
|
|
|
|
idxBytes := database.PackUInt64(uint64(len(testTxs)))
|
|
require.NoError(assetPrefixDB.Put([]byte("idx"), idxBytes))
|
|
return testTxs
|
|
}
|