Files
zeekayandHanzo Dev 938b06d485 Stop requiring the node this module is imported by
proto required luxfi/node and luxfi/node required proto, with no replace
directive on either side. The cycle was not load-bearing: most of it was proto
reaching into the node for packages proto already owns — p/warp, p/txs,
p/signer, p/stakeable, p/security, x/txs — which now name themselves.

Two were real. The UTXO components moved here, where the chains that use them
already live, and the node is the only other caller, so that dependency now
runs one way. And p/config held the node's chain manager to queue a chain the
P-Chain had accepted a CreateChainTx for; it calls one method, so it declares
one method. A P-Chain that imports the node to describe QueueChainCreation is
what made two modules require each other.

Dropping the requirement dropped a registration with it: lux.ParseUTXO is a
registry filled by an init, which the node used to link in on proto's behalf.
The packages that call it now say so themselves.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-06 12:49:55 -07:00

318 lines
7.2 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package state
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/luxfi/database"
"github.com/luxfi/database/memdb"
"github.com/luxfi/database/versiondb"
"github.com/luxfi/ids"
"github.com/luxfi/metric"
"github.com/luxfi/upgrade"
lux "github.com/luxfi/utxo"
"github.com/luxfi/proto/x/block"
"github.com/luxfi/proto/x/fxs"
"github.com/luxfi/proto/x/txs"
"github.com/luxfi/utxo/secp256k1fx"
)
const trackChecksums = false
var (
parser block.Parser
populatedUTXO *lux.UTXO
populatedUTXOID ids.ID
populatedTx *txs.Tx
populatedTxID ids.ID
populatedBlk block.Block
populatedBlkHeight uint64
populatedBlkID ids.ID
)
func init() {
var err error
parser, err = block.NewParser(
[]fxs.Fx{
&secp256k1fx.Fx{},
},
)
if err != nil {
panic(err)
}
populatedUTXO = &lux.UTXO{
UTXOID: lux.UTXOID{
TxID: ids.GenerateTestID(),
},
Asset: lux.Asset{
ID: ids.GenerateTestID(),
},
Out: &secp256k1fx.TransferOutput{
Amt: 1,
},
}
populatedUTXOID = populatedUTXO.InputID()
populatedTx = &txs.Tx{Unsigned: &txs.BaseTx{BaseTx: lux.BaseTx{
BlockchainID: ids.GenerateTestID(),
}}}
err = populatedTx.Initialize()
if err != nil {
panic(err)
}
populatedTxID = populatedTx.ID()
// Native-ZAP blocks store each tx's own wire bytes, so a block's txs must
// be Initialized (bytes populated) before the block is built — the same
// contract production uses (mempool/parse hand the builder initialized txs).
blkTx := &txs.Tx{Unsigned: &txs.BaseTx{BaseTx: lux.BaseTx{
BlockchainID: ids.GenerateTestID(),
}}}
if err = blkTx.Initialize(); err != nil {
panic(err)
}
populatedBlk, err = block.NewStandardBlock(
ids.GenerateTestID(),
1,
time.Now(),
[]*txs.Tx{blkTx},
)
if err != nil {
panic(err)
}
populatedBlkHeight = populatedBlk.Height()
populatedBlkID = populatedBlk.ID()
}
type versions struct {
chains map[ids.ID]Chain
}
func (v *versions) GetState(blkID ids.ID) (Chain, bool) {
c, ok := v.chains[blkID]
return c, ok
}
func TestState(t *testing.T) {
require := require.New(t)
db := memdb.New()
vdb := versiondb.New(db)
s, err := New(vdb, parser, metric.NewNoOp().Registry(), trackChecksums)
require.NoError(err)
s.AddUTXO(populatedUTXO)
s.AddTx(populatedTx)
s.AddBlock(populatedBlk)
require.NoError(s.Commit())
s, err = New(vdb, parser, metric.NewNoOp().Registry(), trackChecksums)
require.NoError(err)
ChainUTXOTest(t, s)
ChainTxTest(t, s)
ChainBlockTest(t, s)
}
func TestDiff(t *testing.T) {
require := require.New(t)
db := memdb.New()
vdb := versiondb.New(db)
s, err := New(vdb, parser, metric.NewNoOp().Registry(), trackChecksums)
require.NoError(err)
s.AddUTXO(populatedUTXO)
s.AddTx(populatedTx)
s.AddBlock(populatedBlk)
require.NoError(s.Commit())
parentID := ids.GenerateTestID()
d, err := NewDiff(parentID, &versions{
chains: map[ids.ID]Chain{
parentID: s,
},
})
require.NoError(err)
ChainUTXOTest(t, d)
ChainTxTest(t, d)
ChainBlockTest(t, d)
}
func ChainUTXOTest(t *testing.T, c Chain) {
require := require.New(t)
fetchedUTXO, err := c.GetUTXO(populatedUTXOID)
require.NoError(err)
// Compare IDs because [fetchedUTXO] isn't initialized
require.Equal(populatedUTXO.InputID(), fetchedUTXO.InputID())
utxo := &lux.UTXO{
UTXOID: lux.UTXOID{
TxID: ids.GenerateTestID(),
},
Asset: lux.Asset{
ID: ids.GenerateTestID(),
},
Out: &secp256k1fx.TransferOutput{
Amt: 1,
},
}
utxoID := utxo.InputID()
_, err = c.GetUTXO(utxoID)
require.ErrorIs(err, database.ErrNotFound)
c.AddUTXO(utxo)
fetchedUTXO, err = c.GetUTXO(utxoID)
require.NoError(err)
require.Equal(utxo, fetchedUTXO)
c.DeleteUTXO(utxoID)
_, err = c.GetUTXO(utxoID)
require.ErrorIs(err, database.ErrNotFound)
}
func ChainTxTest(t *testing.T, c Chain) {
require := require.New(t)
fetchedTx, err := c.GetTx(populatedTxID)
require.NoError(err)
// Compare IDs because [fetchedTx] differs between nil and empty fields
require.Equal(populatedTx.ID(), fetchedTx.ID())
// Pull again for the cached path
fetchedTx, err = c.GetTx(populatedTxID)
require.NoError(err)
require.Equal(populatedTx.ID(), fetchedTx.ID())
tx := &txs.Tx{Unsigned: &txs.BaseTx{BaseTx: lux.BaseTx{
BlockchainID: ids.GenerateTestID(),
}}}
require.NoError(tx.Initialize())
txID := tx.ID()
_, err = c.GetTx(txID)
require.ErrorIs(err, database.ErrNotFound)
// Pull again for the cached path
_, err = c.GetTx(txID)
require.ErrorIs(err, database.ErrNotFound)
c.AddTx(tx)
fetchedTx, err = c.GetTx(txID)
require.NoError(err)
require.Equal(tx, fetchedTx)
}
func ChainBlockTest(t *testing.T, c Chain) {
require := require.New(t)
fetchedBlkID, err := c.GetBlockIDAtHeight(populatedBlkHeight)
require.NoError(err)
require.Equal(populatedBlkID, fetchedBlkID)
fetchedBlk, err := c.GetBlock(populatedBlkID)
require.NoError(err)
require.Equal(populatedBlk.ID(), fetchedBlk.ID())
// Pull again for the cached path
fetchedBlkID, err = c.GetBlockIDAtHeight(populatedBlkHeight)
require.NoError(err)
require.Equal(populatedBlkID, fetchedBlkID)
fetchedBlk, err = c.GetBlock(populatedBlkID)
require.NoError(err)
require.Equal(populatedBlk.ID(), fetchedBlk.ID())
inlineTx := &txs.Tx{Unsigned: &txs.BaseTx{BaseTx: lux.BaseTx{
BlockchainID: ids.GenerateTestID(),
}}}
require.NoError(inlineTx.Initialize())
blk, err := block.NewStandardBlock(
ids.GenerateTestID(),
10,
time.Now(),
[]*txs.Tx{inlineTx},
)
if err != nil {
panic(err)
}
blkID := blk.ID()
blkHeight := blk.Height()
_, err = c.GetBlockIDAtHeight(blkHeight)
require.ErrorIs(err, database.ErrNotFound)
_, err = c.GetBlock(blkID)
require.ErrorIs(err, database.ErrNotFound)
// Pull again for the cached path
_, err = c.GetBlockIDAtHeight(blkHeight)
require.ErrorIs(err, database.ErrNotFound)
_, err = c.GetBlock(blkID)
require.ErrorIs(err, database.ErrNotFound)
c.AddBlock(blk)
fetchedBlkID, err = c.GetBlockIDAtHeight(blkHeight)
require.NoError(err)
require.Equal(blkID, fetchedBlkID)
fetchedBlk, err = c.GetBlock(blkID)
require.NoError(err)
require.Equal(blk, fetchedBlk)
}
func TestInitializeChainState(t *testing.T) {
require := require.New(t)
db := memdb.New()
vdb := versiondb.New(db)
s, err := New(vdb, parser, metric.NewNoOp().Registry(), trackChecksums)
require.NoError(err)
stopVertexID := ids.GenerateTestID()
genesisTimestamp := upgrade.InitiallyActiveTime
require.NoError(s.InitializeChainState(stopVertexID, genesisTimestamp))
lastAcceptedID := s.GetLastAccepted()
genesis, err := s.GetBlock(lastAcceptedID)
require.NoError(err)
require.Equal(stopVertexID, genesis.Parent())
require.Equal(genesisTimestamp.UnixNano(), genesis.Timestamp().UnixNano())
childBlock, err := block.NewStandardBlock(
genesis.ID(),
genesis.Height()+1,
genesisTimestamp,
nil,
)
require.NoError(err)
s.AddBlock(childBlock)
s.SetLastAccepted(childBlock.ID())
require.NoError(s.Commit())
require.NoError(s.InitializeChainState(stopVertexID, genesisTimestamp))
lastAcceptedID = s.GetLastAccepted()
lastAccepted, err := s.GetBlock(lastAcceptedID)
require.NoError(err)
require.Equal(genesis.ID(), lastAccepted.Parent())
}