Files
node/vms/xvm/block/builder/builder_merkleroot_test.go
zeekay 7cf87b230b node-unified is the node
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.
2026-09-07 16:15:25 -07:00

152 lines
5.8 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package builder
import (
"context"
"testing"
"time"
"github.com/luxfi/log"
"github.com/luxfi/metric"
"github.com/luxfi/mock/gomock"
"github.com/stretchr/testify/require"
"github.com/luxfi/ids"
"github.com/luxfi/math/set"
"github.com/luxfi/node/vms/xvm/block"
blkexecutor "github.com/luxfi/node/vms/xvm/block/executor"
"github.com/luxfi/node/vms/xvm/block/executor/executormock"
"github.com/luxfi/node/vms/xvm/config"
"github.com/luxfi/node/vms/xvm/state/statemock"
"github.com/luxfi/node/vms/xvm/txs"
txexecutor "github.com/luxfi/node/vms/xvm/txs/executor"
"github.com/luxfi/node/vms/xvm/txs/mempool"
"github.com/luxfi/node/vms/xvm/txs/txsmock"
"github.com/luxfi/timer/mockable"
chain "github.com/luxfi/vm/chain"
)
// buildOneTxBlock drives BuildBlock with a single always-valid mocked tx over a
// mocked manager/state, parent at [parentHeight] with [parentRoot]. It returns
// the *StandardBlock the builder handed to NewBlock so the test can inspect the
// stamped Root. The builder always stamps the xvm execution_root — there is no
// activation gate — so the block's Root is the canonical execution_root over the
// post-block state.
func buildOneTxBlock(
t *testing.T,
ctrl *gomock.Controller,
parentRoot ids.ID,
parentHeight uint64,
) *block.StandardBlock {
t.Helper()
require := require.New(t)
preferredID := ids.GenerateTestID()
preferredTimestamp := time.Now()
preferredBlock := block.NewMockBlock(ctrl)
preferredBlock.EXPECT().Height().Return(parentHeight).AnyTimes()
preferredBlock.EXPECT().Timestamp().Return(preferredTimestamp).AnyTimes()
preferredBlock.EXPECT().MerkleRoot().Return(parentRoot).AnyTimes()
preferredState := statemock.NewChain(ctrl)
preferredState.EXPECT().GetLastAccepted().Return(preferredID).AnyTimes()
preferredState.EXPECT().GetTimestamp().Return(preferredTimestamp).AnyTimes()
// The execution_root projection enumerates the post-block occupied UTXO set
// through the parent chain. This unit's tx produces no UTXOs, so the parent
// (and thus the post-block) UTXO set is empty — the UTXO family folds to the
// empty root, leaving the tx family + parent root + height to make the
// stamped root non-empty.
preferredState.EXPECT().UTXOs(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
// One tx that passes semantic verification and execution and adds no inputs.
unsignedTx := txsmock.NewUnsignedTx(ctrl)
unsignedTx.EXPECT().Visit(gomock.Any()).Return(nil) // semantic verification
unsignedTx.EXPECT().Visit(gomock.Any()).DoAndReturn( // execution
func(visitor txs.Visitor) error {
require.IsType(&txexecutor.Executor{}, visitor)
ex := visitor.(*txexecutor.Executor)
if ex.Inputs == nil {
ex.Inputs = set.NewSet[ids.ID](0)
}
return nil
},
)
unsignedTx.EXPECT().SetBytes(gomock.Any()).AnyTimes()
unsignedTx.EXPECT().InputIDs().Return(nil).AnyTimes()
tx := &txs.Tx{Unsigned: unsignedTx}
// Give the tx a real, stable ID (ID = hash of signed bytes). The
// execution_root's tx family binds tx.ID(), so a deterministic ID lets the
// test recompute the expected root.
tx.SetBytes(nil, []byte{0x0A, 0x0B, 0x0C, 0x0D})
manager := executormock.NewManager(ctrl)
manager.EXPECT().Preferred().Return(preferredID)
manager.EXPECT().GetStatelessBlock(preferredID).Return(preferredBlock, nil)
// GetState(preferredID) is consulted by NewDiff and again by the
// execution_root projection (the diff fetches its parent state to enumerate
// the post-block UTXO set), so allow repeated calls.
manager.EXPECT().GetState(preferredID).Return(preferredState, true).AnyTimes()
manager.EXPECT().VerifyUniqueInputs(preferredID, gomock.Any()).Return(nil).AnyTimes()
var built *block.StandardBlock
manager.EXPECT().NewBlock(gomock.Any()).DoAndReturn(
func(b *block.StandardBlock) chain.Block {
built = b
return nil
},
)
memPool, err := mempool.New("", metric.NewRegistry())
require.NoError(err)
require.NoError(memPool.Add(tx))
// The builder serializes the block natively (ZAP struct-is-wire); there is
// no codec to inject. The test reads block.Root (stamped before serialize).
builder := New(
&txexecutor.Backend{
Ctx: context.Background(),
Runtime: testRuntime(),
Config: &config.Config{},
Log: log.NewNoOpLogger(),
},
manager,
&mockable.Clock{},
memPool,
)
_, err = builder.BuildBlock(context.Background())
require.NoError(err)
require.NotNil(built)
return built
}
// TestBuildBlockStampsExecutionRoot is the builder-side always-active proof: the
// builder always stamps the xvm execution_root computed over the post-block
// state. The stamped root must be non-empty and must equal the canonical
// BlockExecutionRoot over the same inputs — there is no empty-root path.
func TestBuildBlockStampsExecutionRoot(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
parentRoot := ids.GenerateTestID()
const parentHeight = uint64(41)
built := buildOneTxBlock(t, ctrl, parentRoot, parentHeight)
require.NotEqual(ids.Empty, built.MerkleRoot(), "builder must stamp a non-empty execution_root")
// Recompute the expected root from the canonical projection: parent root +
// the block's tx family at the built height (parentHeight + 1), over a
// post-block state whose occupied UTXO set is empty (this unit's tx produces
// none — same as the parent state the builder projected). The asset family is
// always empty (xvm's UTXO-only executor state has no asset arena).
emptyState := statemock.NewChain(ctrl)
emptyState.EXPECT().UTXOs(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
want, err := blkexecutor.BlockExecutionRoot(parentRoot, built.Txs(), emptyState, parentHeight+1)
require.NoError(err)
require.Equal(want, built.MerkleRoot())
}