mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
consensus parity: X-Chain finalizes via the linear 2/3-stake cert (drop undriven DAG path)
X-Chain was on an undriven, unconfigured DAG engine (test-ID/height-0 vertices, a handler that dropped every inbound consensus message) — no certificate finality, no parity with C/D/P. It now runs the same linear consensuschain path as every other chain: deterministic 2/3-stake BFT certificate finality (AcceptWithCert), the BFT overlap-bound floor, and the epoch-pinned validator set. vms/xvm/vm.go: conform *VM to block.ChainVM / consensuschain.BlockBuilder (Connected/Shutdown/WaitForEvent/HealthCheck), remove GetEngine() and the dead DAG methods, add the compile-time asserts. X already had a real linear builder (embedded blockbuilder.Builder, wired in Linearize) — nothing faked. vms/xvm/health.go: HealthCheck -> chain.HealthResult. vms/xvm/tx.go: deleted (dead dag.Tx wrapper). chains/manager.go: linearize DAG-native VMs (X-Chain) into linear block mode before SetState, wired to the REAL toEngine the cert runtime reads from. Interface-gated — only VMs implementing Linearize are affected; C/D/P/Q untouched. With no production VM exposing GetEngine, the undriven DAG dispatch is dead. vms/dexvm/dexvm.go, vms/types/fee/policy.go: doc corrections (dexvm is plugin/optional/NFT-gated, linear cert finality; X-Chain UTXO fee is a deliberate exception to the account-model FlatPolicy floor). Full node builds; vms/xvm + chains tests green. Parity holds by construction for C/D/X/Q/Z: one cert path, one finality authority.
This commit is contained in:
@@ -1185,6 +1185,28 @@ func (m *manager) buildChain(chainParams ChainParameters, sb nets.Net) (*chainIn
|
||||
}
|
||||
}
|
||||
|
||||
// X-Chain (and any DAG-native VM) must be linearized into linear block mode
|
||||
// BEFORE it goes Ready, so its embedded block builder is wired to the real
|
||||
// toEngine channel the cert runtime reads from. Interface-gated: only VMs that
|
||||
// implement Linearize (X-Chain) take this path; C/D/P/Q are untouched. This is
|
||||
// what lets X-Chain finalize through the same 2/3-stake cert path as every other
|
||||
// chain instead of the (now-removed) undriven DAG engine.
|
||||
if linearVM, ok := vmTyped.(interface {
|
||||
Linearize(context.Context, ids.ID, chan<- vm.Message) error
|
||||
}); ok {
|
||||
m.Log.Info("linearizing DAG-native VM into linear block mode",
|
||||
log.Stringer("chainID", chainParams.ID))
|
||||
linCtx, linCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
if err := linearVM.Linearize(linCtx, ids.Empty, toEngine); err != nil {
|
||||
linCancel()
|
||||
m.Log.Error("failed to linearize VM",
|
||||
log.Stringer("chainID", chainParams.ID),
|
||||
log.Err(err))
|
||||
return nil, fmt.Errorf("failed to linearize VM into linear block mode: %w", err)
|
||||
}
|
||||
linCancel()
|
||||
}
|
||||
|
||||
// Transition VM to normal operation after initialization
|
||||
// For genesis-based networks with pre-configured validators, this is required
|
||||
// to make the VM APIs available immediately
|
||||
|
||||
+6
-4
@@ -9,10 +9,12 @@
|
||||
// New code should import the canonical path:
|
||||
// "github.com/luxfi/chains/dexvm"
|
||||
//
|
||||
// This package is a thin, unconditional backward-compatibility alias. The
|
||||
// underlying chains/dexvm is the pure-Go stateless atomic proxy (zero private
|
||||
// deps), so — like every other genesis VM — it is linked into every build with
|
||||
// no build tag.
|
||||
// This package is a thin backward-compatibility alias. The underlying
|
||||
// chains/dexvm is the pure-Go stateless atomic proxy (zero private deps).
|
||||
// Unlike the always-on genesis VMs, dexvm is registered in OptionalVMs and is
|
||||
// NFT-gated (see node/vms.go:118, RequiredNFT "dex-operator"): a node only
|
||||
// tracks/validates the D-Chain when the network has configured that operator
|
||||
// collection, so it is plugin-loaded on demand, not linked unconditionally.
|
||||
package dexvm
|
||||
|
||||
import (
|
||||
|
||||
@@ -43,6 +43,13 @@ import (
|
||||
// bound when reviewing/upgrading per-VM policies; not enforced inside
|
||||
// Validate (a VM is free to charge MORE), but every VM choosing less
|
||||
// will be flagged by the migration checklist.
|
||||
//
|
||||
// Known intentional exception: the X-Chain (xvm) prices transactions through
|
||||
// its own UTXO fee subsystem (xvm/config.go TxFee, default 1000 nLUX), NOT the
|
||||
// FlatPolicy here, and is deliberately outside this floor — its high-throughput
|
||||
// UTXO economics are set independently of the account-model FlatPolicy floor.
|
||||
// This is by design, not a migration miss; do not "fix" it by raising xvm
|
||||
// TxFee to MinTxFeeFloor.
|
||||
const MinTxFeeFloor uint64 = 1_000_000
|
||||
|
||||
// Sentinel errors returned by Policy implementations.
|
||||
|
||||
+16
-3
@@ -3,8 +3,21 @@
|
||||
|
||||
package xvm
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
|
||||
func (*VM) HealthCheck(context.Context) (interface{}, error) {
|
||||
return nil, nil
|
||||
"github.com/luxfi/node/version"
|
||||
chain "github.com/luxfi/vm/chain"
|
||||
)
|
||||
|
||||
// HealthCheck reports the VM's health to the consensus engine. It returns a
|
||||
// chain.HealthResult (= block.HealthCheckResult) so *VM satisfies the linear
|
||||
// chain.ChainVM interface used by the certificate path.
|
||||
func (vm *VM) HealthCheck(context.Context) (chain.HealthResult, error) {
|
||||
return chain.HealthResult{
|
||||
Healthy: vm.onShutdownCtx == nil || vm.onShutdownCtx.Err() == nil,
|
||||
Details: map[string]string{
|
||||
"version": version.Current.String(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
-161
@@ -1,161 +0,0 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package xvm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/log"
|
||||
|
||||
"github.com/luxfi/consensus/core/choices"
|
||||
"github.com/luxfi/consensus/engine/dag"
|
||||
"github.com/luxfi/database"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/math/set"
|
||||
"github.com/luxfi/node/vms/xvm/txs"
|
||||
"github.com/luxfi/node/vms/xvm/txs/executor"
|
||||
)
|
||||
|
||||
var (
|
||||
_ dag.Tx = (*Tx)(nil)
|
||||
|
||||
errTxNotProcessing = errors.New("transaction is not processing")
|
||||
errUnexpectedReject = errors.New("attempting to reject transaction")
|
||||
)
|
||||
|
||||
type Tx struct {
|
||||
vm *VM
|
||||
tx *txs.Tx
|
||||
}
|
||||
|
||||
func (tx *Tx) ID() ids.ID {
|
||||
return tx.tx.ID()
|
||||
}
|
||||
|
||||
// Height returns the height of this transaction (not used in XVM)
|
||||
func (tx *Tx) Height() uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Parent returns the parent ID (not used in XVM DAG)
|
||||
func (tx *Tx) Parent() ids.ID {
|
||||
return ids.Empty
|
||||
}
|
||||
|
||||
// ParentIDs returns the IDs of the parent transactions (inputs)
|
||||
func (tx *Tx) ParentIDs() []ids.ID {
|
||||
// Return the transaction IDs this transaction depends on
|
||||
parents := []ids.ID{}
|
||||
for _, in := range tx.tx.Unsigned.InputUTXOs() {
|
||||
if in.Symbolic() {
|
||||
continue
|
||||
}
|
||||
txID, _ := in.InputSource()
|
||||
parents = append(parents, txID)
|
||||
}
|
||||
return parents
|
||||
}
|
||||
|
||||
func (tx *Tx) Accept(ctx context.Context) error {
|
||||
if s := tx.Status(); s != choices.Processing {
|
||||
return fmt.Errorf("%w: %s", errTxNotProcessing, s)
|
||||
}
|
||||
|
||||
tx.vm.onAccept(tx.tx)
|
||||
|
||||
executor := &executor.Executor{
|
||||
Codec: tx.vm.txBackend.Codec,
|
||||
State: tx.vm.state,
|
||||
Tx: tx.tx,
|
||||
Inputs: set.NewSet[ids.ID](0), // Initialize empty set for imported inputs
|
||||
}
|
||||
err := tx.tx.Unsigned.Visit(executor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error staging accepted state changes: %w", err)
|
||||
}
|
||||
|
||||
tx.vm.state.AddTx(tx.tx)
|
||||
|
||||
commitBatch, err := tx.vm.state.CommitBatch()
|
||||
if err != nil {
|
||||
txID := tx.tx.ID()
|
||||
return fmt.Errorf("couldn't create commitBatch while processing tx %s: %w", txID, err)
|
||||
}
|
||||
|
||||
defer tx.vm.state.Abort()
|
||||
// Convert the atomicRequests to interface{} type for SharedMemory
|
||||
requests := make(map[ids.ID]interface{}, len(executor.AtomicRequests))
|
||||
for chainID, reqs := range executor.AtomicRequests {
|
||||
requests[chainID] = reqs
|
||||
}
|
||||
err = tx.vm.SharedMemory.Apply(
|
||||
requests,
|
||||
commitBatch,
|
||||
)
|
||||
if err != nil {
|
||||
txID := tx.tx.ID()
|
||||
return fmt.Errorf("error committing accepted state changes while processing tx %s: %w", txID, err)
|
||||
}
|
||||
|
||||
return tx.vm.metrics.MarkTxAccepted(tx.tx)
|
||||
}
|
||||
|
||||
func (*Tx) Reject(ctx context.Context) error {
|
||||
return errUnexpectedReject
|
||||
}
|
||||
|
||||
func (tx *Tx) Status() choices.Status {
|
||||
txID := tx.tx.ID()
|
||||
_, err := tx.vm.state.GetTx(txID)
|
||||
switch err {
|
||||
case nil:
|
||||
return choices.Accepted
|
||||
case database.ErrNotFound:
|
||||
return choices.Processing
|
||||
default:
|
||||
tx.vm.log.Error("failed looking up tx status",
|
||||
log.Stringer("txID", txID),
|
||||
log.String("error", err.Error()),
|
||||
)
|
||||
return choices.Processing
|
||||
}
|
||||
}
|
||||
|
||||
func (tx *Tx) MissingDependencies() (set.Set[ids.ID], error) {
|
||||
txIDs := make(set.Set[ids.ID])
|
||||
for _, in := range tx.tx.Unsigned.InputUTXOs() {
|
||||
if in.Symbolic() {
|
||||
continue
|
||||
}
|
||||
txID, _ := in.InputSource()
|
||||
|
||||
_, err := tx.vm.state.GetTx(txID)
|
||||
switch err {
|
||||
case nil:
|
||||
// Tx was already accepted
|
||||
case database.ErrNotFound:
|
||||
txIDs.Add(txID)
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return txIDs, nil
|
||||
}
|
||||
|
||||
func (tx *Tx) Bytes() []byte {
|
||||
return tx.tx.Bytes()
|
||||
}
|
||||
|
||||
func (tx *Tx) Verify(ctx context.Context) error {
|
||||
if s := tx.Status(); s != choices.Processing {
|
||||
return fmt.Errorf("%w: %s", errTxNotProcessing, s)
|
||||
}
|
||||
return tx.tx.Unsigned.Visit(&executor.SemanticVerifier{
|
||||
Backend: tx.vm.txBackend,
|
||||
State: tx.vm.state,
|
||||
Tx: tx.tx,
|
||||
})
|
||||
}
|
||||
+30
-92
@@ -19,8 +19,7 @@ import (
|
||||
|
||||
"github.com/luxfi/address"
|
||||
consensusconfig "github.com/luxfi/consensus/config"
|
||||
"github.com/luxfi/consensus/engine/dag"
|
||||
dagvertex "github.com/luxfi/consensus/engine/dag/vertex"
|
||||
consensuschain "github.com/luxfi/consensus/engine/chain"
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/container/linked"
|
||||
"github.com/luxfi/database"
|
||||
@@ -66,6 +65,17 @@ var (
|
||||
errIncompatibleFx = errors.New("incompatible feature extension")
|
||||
errUnknownFx = errors.New("unknown feature extension")
|
||||
errGenesisAssetMustHaveState = errors.New("genesis asset must have non-empty state")
|
||||
|
||||
// Compile-time check that *VM satisfies chain.ChainVM (= block.ChainVM)
|
||||
// AND the consensus engine's BlockBuilder. Together these prove X-Chain
|
||||
// takes the LINEAR ⅔-stake cert path in the chain manager (buildChain →
|
||||
// consensuschain.NewRuntime), not the DAG path. BuildBlock is promoted
|
||||
// from the embedded blockbuilder.Builder (the real linear builder: parent
|
||||
// = preferred, height = parent+1, real timestamp). Do NOT add a
|
||||
// GetEngine() dag.Engine method: that routes the manager's type switch
|
||||
// back to createDAG and bypasses the certificate.
|
||||
_ chain.ChainVM = (*VM)(nil)
|
||||
_ consensuschain.BlockBuilder = (*VM)(nil)
|
||||
)
|
||||
|
||||
// BCLookup provides blockchain alias lookup
|
||||
@@ -115,7 +125,7 @@ type VM struct {
|
||||
|
||||
registerer metrics.Registerer
|
||||
|
||||
connectedPeers map[ids.NodeID]*version.Application
|
||||
connectedPeers map[ids.NodeID]*consensusversion.Application
|
||||
|
||||
parser block.Parser
|
||||
|
||||
@@ -170,21 +180,14 @@ type VM struct {
|
||||
classicalCompatRegistry auth.ClassicalCompatRegistry
|
||||
}
|
||||
|
||||
func (vm *VM) Connected(ctx context.Context, nodeID ids.NodeID, version *version.Application) error {
|
||||
func (vm *VM) Connected(ctx context.Context, nodeID ids.NodeID, nodeVersion *consensusversion.Application) error {
|
||||
// If the chain isn't linearized yet, we must track the peers externally
|
||||
// until the network is initialized.
|
||||
if vm.network == nil {
|
||||
vm.connectedPeers[nodeID] = version
|
||||
vm.connectedPeers[nodeID] = nodeVersion
|
||||
return nil
|
||||
}
|
||||
// Convert to consensus version type
|
||||
consensusVer := &consensusversion.Application{
|
||||
Name: version.Name,
|
||||
Major: version.Major,
|
||||
Minor: version.Minor,
|
||||
Patch: version.Patch,
|
||||
}
|
||||
return vm.network.Connected(ctx, nodeID, consensusVer)
|
||||
return vm.network.Connected(ctx, nodeID, nodeVersion)
|
||||
}
|
||||
|
||||
func (vm *VM) Disconnected(ctx context.Context, nodeID ids.NodeID) error {
|
||||
@@ -281,7 +284,7 @@ func (vm *VM) initialize(
|
||||
// Get metrics from a global registry or create new one
|
||||
vm.registerer = metric.NewRegistry()
|
||||
|
||||
vm.connectedPeers = make(map[ids.NodeID]*version.Application)
|
||||
vm.connectedPeers = make(map[ids.NodeID]*consensusversion.Application)
|
||||
|
||||
// Initialize metrics as soon as possible
|
||||
vm.metrics, err = xvmmetrics.New(vm.registerer)
|
||||
@@ -421,7 +424,7 @@ func (vm *VM) SetState(_ context.Context, stateNum uint32) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) Shutdown() error {
|
||||
func (vm *VM) Shutdown(context.Context) error {
|
||||
if vm.state == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -618,15 +621,8 @@ func (vm *VM) Linearize(ctx context.Context, stopVertexID ids.ID, toEngine chan<
|
||||
}
|
||||
|
||||
// Notify the network of our current peers
|
||||
for nodeID, version := range vm.connectedPeers {
|
||||
// Convert to consensus version type
|
||||
consensusVer := &consensusversion.Application{
|
||||
Name: version.Name,
|
||||
Major: version.Major,
|
||||
Minor: version.Minor,
|
||||
Patch: version.Patch,
|
||||
}
|
||||
if err := vm.network.Connected(ctx, nodeID, consensusVer); err != nil {
|
||||
for nodeID, nodeVersion := range vm.connectedPeers {
|
||||
if err := vm.network.Connected(ctx, nodeID, nodeVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -658,26 +654,6 @@ func (vm *VM) Linearize(ctx context.Context, stopVertexID ids.ID, toEngine chan<
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *VM) ParseTx(_ context.Context, bytes []byte) (dag.Tx, error) {
|
||||
tx, err := vm.parser.ParseTx(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = tx.Unsigned.Visit(&txexecutor.SyntacticVerifier{
|
||||
Backend: vm.txBackend,
|
||||
Tx: tx,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Tx{
|
||||
vm: vm,
|
||||
tx: tx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
/*
|
||||
******************************************************************************
|
||||
********************************** JSON API **********************************
|
||||
@@ -895,19 +871,22 @@ func (vm *VM) onAccept(tx *txs.Tx) {
|
||||
vm.walletService.decided(txID)
|
||||
}
|
||||
|
||||
// WaitForEvent implements the engine.VM interface
|
||||
func (vm *VM) WaitForEvent(ctx context.Context) (interface{}, error) {
|
||||
// WaitForEvent blocks until the VM has work for the consensus engine (a
|
||||
// pending-tx event) or ctx is cancelled. It returns a vmcore.Message
|
||||
// (= block.Message) so *VM satisfies the linear chain.ChainVM interface used by
|
||||
// the certificate path.
|
||||
func (vm *VM) WaitForEvent(ctx context.Context) (vmcore.Message, error) {
|
||||
if vm.toEngine == nil {
|
||||
// Before linearization, no events to wait for
|
||||
// Before linearization, no events to wait for.
|
||||
<-ctx.Done()
|
||||
return vmcore.PendingTxs, ctx.Err()
|
||||
return vmcore.Message{}, ctx.Err()
|
||||
}
|
||||
|
||||
select {
|
||||
case msgType := <-vm.toEngine:
|
||||
return msgType, nil
|
||||
case msg := <-vm.toEngine:
|
||||
return msg, nil
|
||||
case <-ctx.Done():
|
||||
return vmcore.PendingTxs, ctx.Err()
|
||||
return vmcore.Message{}, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -917,47 +896,6 @@ func (vm *VM) NewHTTPHandler(ctx context.Context) (http.Handler, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// BuildVertex builds a new vertex - required for LinearizableVMWithEngine
|
||||
func (vm *VM) BuildVertex(ctx context.Context) (dagvertex.Vertex, error) {
|
||||
// XVM doesn't use vertices, it uses blocks
|
||||
return nil, errors.New("XVM does not support vertex building")
|
||||
}
|
||||
|
||||
// GetVertex gets a vertex by ID - required for LinearizableVMWithEngine
|
||||
func (vm *VM) GetVertex(ctx context.Context, vtxID ids.ID) (dagvertex.Vertex, error) {
|
||||
// XVM doesn't use vertices, it uses blocks
|
||||
return nil, errors.New("XVM does not support vertex operations")
|
||||
}
|
||||
|
||||
// ParseVertex parses vertex bytes - required for LinearizableVMWithEngine
|
||||
func (vm *VM) ParseVertex(ctx context.Context, vtxBytes []byte) (dagvertex.Vertex, error) {
|
||||
// XVM doesn't use vertices, it uses blocks
|
||||
return nil, errors.New("XVM does not support vertex parsing")
|
||||
}
|
||||
|
||||
// GetEngine returns the consensus engine - required for LinearizableVMWithEngine
|
||||
func (vm *VM) GetEngine() dag.Engine {
|
||||
// XVM doesn't have a separate engine, return a new DAG engine
|
||||
return dag.New()
|
||||
}
|
||||
|
||||
// SetEngine sets the consensus engine - required for LinearizableVMWithEngine
|
||||
func (vm *VM) SetEngine(engine interface{}) {
|
||||
// XVM doesn't use a separate engine
|
||||
}
|
||||
|
||||
// GetTx returns a transaction by ID - required for LinearizableVMWithEngine
|
||||
func (vm *VM) GetTx(ctx context.Context, txID ids.ID) (dag.Transaction, error) {
|
||||
tx, err := vm.state.GetTx(txID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Tx{
|
||||
vm: vm,
|
||||
tx: tx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// noOpHandler is a simple no-op implementation of warp.Handler
|
||||
type noOpHandler struct{}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ func TestXVMInitialize_WiresSecurityProfileIntoMempool(t *testing.T) {
|
||||
Sender: &noOpSender{},
|
||||
},
|
||||
))
|
||||
t.Cleanup(func() { _ = vmImpl.Shutdown() })
|
||||
t.Cleanup(func() { _ = vmImpl.Shutdown(context.Background()) })
|
||||
|
||||
// Linearize so the mempool builder is constructed and SetAuthPolicy
|
||||
// has fired with the strict-PQ profile.
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/luxfi/vm"
|
||||
"github.com/luxfi/runtime"
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/crypto/secp256k1"
|
||||
"github.com/luxfi/database"
|
||||
"github.com/luxfi/database/memdb"
|
||||
"github.com/luxfi/ids"
|
||||
@@ -365,107 +364,6 @@ func TestVMFormat(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTxAcceptAfterParseTx(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
env := setup(t, &envConfig{
|
||||
fork: upgrade.Default,
|
||||
notLinearized: true,
|
||||
})
|
||||
defer env.vm.Lock.Unlock()
|
||||
|
||||
var (
|
||||
key = keys[0]
|
||||
kc = secp256k1fx.NewKeychain(key)
|
||||
)
|
||||
|
||||
firstTx, err := env.txBuilder.BaseTx(
|
||||
[]*lux.TransferableOutput{{
|
||||
Asset: lux.Asset{ID: env.genesisTx.ID()},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: startBalance - env.vm.TxFee,
|
||||
OutputOwners: secp256k1fx.OutputOwners{
|
||||
Threshold: 1,
|
||||
Addrs: []ids.ShortID{key.PublicKey().Address()},
|
||||
},
|
||||
},
|
||||
}},
|
||||
nil, // memo
|
||||
kc,
|
||||
key.Address(),
|
||||
)
|
||||
require.NoError(err)
|
||||
|
||||
// Find the output index that holds the requested startBalance-TxFee
|
||||
// amount; firstTx may have a separate change output, and the canonical
|
||||
// output sort (ZAP-native wire envelope bytes per LP-023) determines
|
||||
// which index that lands on.
|
||||
requestedAmt := startBalance - env.vm.TxFee
|
||||
firstBaseTx := firstTx.Unsigned.(*xvmtxs.BaseTx)
|
||||
var requestedIdx uint32
|
||||
found := false
|
||||
for i, out := range firstBaseTx.Outs {
|
||||
o, ok := out.Out.(*secp256k1fx.TransferOutput)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if o.Amt == requestedAmt {
|
||||
requestedIdx = uint32(i)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
require.True(found, "firstTx must produce an output with the requested amount")
|
||||
|
||||
// let secondTx spend firstTx outputs
|
||||
secondTx := &xvmtxs.Tx{Unsigned: &xvmtxs.BaseTx{
|
||||
BaseTx: lux.BaseTx{
|
||||
NetworkID: constants.UnitTestID,
|
||||
BlockchainID: env.vm.XChainID,
|
||||
Ins: []*lux.TransferableInput{{
|
||||
UTXOID: lux.UTXOID{
|
||||
TxID: firstTx.ID(),
|
||||
OutputIndex: requestedIdx,
|
||||
},
|
||||
Asset: lux.Asset{ID: env.genesisTx.ID()},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
Amt: requestedAmt,
|
||||
Input: secp256k1fx.Input{
|
||||
SigIndices: []uint32{
|
||||
0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}}
|
||||
require.NoError(secondTx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}}))
|
||||
|
||||
parsedFirstTx, err := env.vm.ParseTx(context.Background(), firstTx.Bytes())
|
||||
require.NoError(err)
|
||||
|
||||
require.NoError(parsedFirstTx.Verify(context.Background()))
|
||||
require.NoError(parsedFirstTx.Accept(context.Background()))
|
||||
|
||||
// Update the preferred block (normally done by consensus engine)
|
||||
require.NoError(env.vm.SetPreference(context.Background(), parsedFirstTx.ID()))
|
||||
|
||||
parsedSecondTx, err := env.vm.ParseTx(context.Background(), secondTx.Bytes())
|
||||
require.NoError(err)
|
||||
|
||||
require.NoError(parsedSecondTx.Verify(context.Background()))
|
||||
require.NoError(parsedSecondTx.Accept(context.Background()))
|
||||
|
||||
// Update the preferred block (normally done by consensus engine)
|
||||
require.NoError(env.vm.SetPreference(context.Background(), parsedSecondTx.ID()))
|
||||
|
||||
_, err = env.vm.state.GetTx(firstTx.ID())
|
||||
require.NoError(err)
|
||||
|
||||
_, err = env.vm.state.GetTx(secondTx.ID())
|
||||
require.NoError(err)
|
||||
}
|
||||
|
||||
// Test issuing an import transaction.
|
||||
func TestIssueImportTx(t *testing.T) {
|
||||
require := require.New(t)
|
||||
@@ -544,70 +442,6 @@ func TestIssueImportTx(t *testing.T) {
|
||||
env.vm.Lock.Unlock() // Final unlock (no defer in this test)
|
||||
}
|
||||
|
||||
// Test force accepting an import transaction.
|
||||
func TestForceAcceptImportTx(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
env := setup(t, &envConfig{
|
||||
fork: upgrade.Default,
|
||||
notLinearized: true,
|
||||
})
|
||||
defer env.vm.Lock.Unlock()
|
||||
|
||||
genesisTx := getCreateTxFromGenesisTest(t, env.genesisBytes, "LUX")
|
||||
luxID := genesisTx.ID()
|
||||
|
||||
key := keys[0]
|
||||
utxoID := lux.UTXOID{
|
||||
TxID: ids.ID{
|
||||
0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee,
|
||||
0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec,
|
||||
0x0b, 0x2b, 0x4b, 0x6b, 0x8a, 0xaa, 0xca, 0xea,
|
||||
0x09, 0x29, 0x49, 0x69, 0x88, 0xa8, 0xc8, 0xe8,
|
||||
},
|
||||
}
|
||||
|
||||
txAssetID := lux.Asset{ID: luxID}
|
||||
tx := &xvmtxs.Tx{Unsigned: &xvmtxs.ImportTx{
|
||||
BaseTx: xvmtxs.BaseTx{BaseTx: lux.BaseTx{
|
||||
NetworkID: constants.UnitTestID,
|
||||
BlockchainID: env.vm.XChainID,
|
||||
Outs: []*lux.TransferableOutput{{
|
||||
Asset: txAssetID,
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 10,
|
||||
OutputOwners: secp256k1fx.OutputOwners{
|
||||
Threshold: 1,
|
||||
Addrs: []ids.ShortID{keys[0].PublicKey().Address()},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
SourceChain: constants.PlatformChainID,
|
||||
ImportedIns: []*lux.TransferableInput{{
|
||||
UTXOID: utxoID,
|
||||
Asset: txAssetID,
|
||||
In: &secp256k1fx.TransferInput{
|
||||
Amt: 1010,
|
||||
Input: secp256k1fx.Input{
|
||||
SigIndices: []uint32{0},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}}
|
||||
require.NoError(tx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}}))
|
||||
|
||||
parsedTx, err := env.vm.ParseTx(context.Background(), tx.Bytes())
|
||||
require.NoError(err)
|
||||
|
||||
require.NoError(parsedTx.Verify(context.Background()))
|
||||
require.NoError(parsedTx.Accept(context.Background()))
|
||||
|
||||
id := utxoID.InputID()
|
||||
_, err = env.vm.SharedMemory.Get(constants.PlatformChainID, [][]byte{id[:]})
|
||||
require.ErrorIs(err, database.ErrNotFound)
|
||||
}
|
||||
|
||||
func TestImportTxNotState(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
|
||||
@@ -318,7 +318,7 @@ func setup(t testing.TB, config *envConfig) *testEnv {
|
||||
// This ensures PushGossip and PullGossip goroutines are properly terminated
|
||||
t.Cleanup(func() {
|
||||
// Shutdown the VM to cancel onShutdownCtx and stop gossip goroutines
|
||||
_ = vmImpl.Shutdown()
|
||||
_ = vmImpl.Shutdown(context.Background())
|
||||
})
|
||||
|
||||
// Linearize the DAG to initialize the network
|
||||
|
||||
Reference in New Issue
Block a user