Files
zeekay 538b88d7b9 conformance: the X-chain's semantic pass, judged against something
The differential compared five fields of every X vector and the sixth,
`exec`, of none of them. Both evaluators reported the semantic pass as not
evaluated, on the grounds that judging it needs a funded UTXO set — so the
runner had one voice on that field, listed it under NOT COMPARED, and was
right to. A field one implementation answers is not a comparison.

It does not need a funded set. The P-chain's vectors have always been
judged on a chain that holds NOTHING, and the reason is written down in
`execp.go`: a funded state would have to be built three times, once per
language, and the differential would then be measuring three state builders
instead of three chains. What survives an empty chain is the verdict CLASS,
and that is the shape a fork has — a chain that refuses a kind by name
answers UNSUPPORTED where a chain that looks the input up answers LEDGER.

So the X-chain is judged the same way. Go runs `SemanticVerifier` and then
`Executor` over a fresh diff on an empty `xvm/state`, through the node's own
types; Rust runs `verify_semantic` and then `execute` over an empty `Store`.
Both are given an empty shared area as well, so an import refuses for having
nothing to import rather than for having nowhere to look. Both answer
LEDGER on all five, computed on each side from the bytes.

That the field is now really compared was checked by breaking it: a Rust
evaluator that skips the semantic pass and applies the transaction anyway
answers OK, and the runner fails with five disagreements against Go and
five against the recorded corpus. Under the old harness that same chain —
one that never asks whether a transaction is allowed — passed green.

The Go backend was doubled: the syntactic verifier built one and the new
semantic path would have built another. There is one now, `xBackend()`, so
a vector cannot be judged under one wiring and executed under another.

C++ still declines, and the runner still says so.
2026-09-04 22:37:46 -07:00

129 lines
3.6 KiB
Go

// SPDX-License-Identifier: BSD-3-Clause-Eco
// Naming what came off the wire.
//
// The three implementations have to agree about WHICH transaction a buffer is
// before they can agree about anything else, so the kind is a compared field.
// The names below are the wire's names — the same nineteen the kind byte 2..20
// selects in Go, Rust and C++ — with no language's spelling in them.
package main
import (
"github.com/luxfi/node/vms/platformvm/block"
"github.com/luxfi/node/vms/platformvm/txs"
xblock "github.com/luxfi/node/vms/xvm/block"
"github.com/luxfi/node/vms/xvm/fxs"
xtxs "github.com/luxfi/node/vms/xvm/txs"
"github.com/luxfi/node/vms/xvm/txs/executor"
"github.com/luxfi/utxo/nftfx"
"github.com/luxfi/utxo/propertyfx"
"github.com/luxfi/utxo/secp256k1fx"
xwire "github.com/luxfi/utxo/wire"
)
func pKindName(u txs.UnsignedTx) string {
switch u.(type) {
case *txs.RewardValidatorTx:
return "RewardValidator"
case *txs.BaseTx:
return "Base"
case *txs.ImportTx:
return "Import"
case *txs.ExportTx:
return "Export"
case *txs.CreateNetworkTx:
return "CreateNetwork"
case *txs.CreateChainTx:
return "CreateChain"
case *txs.TransferChainOwnershipTx:
return "TransferChainOwnership"
case *txs.RemoveChainValidatorTx:
return "RemoveChainValidator"
case *txs.TransformChainTx:
return "TransformChain"
case *txs.AddValidatorTx:
return "AddValidator"
case *txs.AddChainValidatorTx:
return "AddChainValidator"
case *txs.AddDelegatorTx:
return "AddDelegator"
case *txs.AddPermissionlessValidatorTx:
return "AddPermissionlessValidator"
case *txs.AddPermissionlessDelegatorTx:
return "AddPermissionlessDelegator"
case *txs.RegisterL1ValidatorTx:
return "RegisterL1Validator"
case *txs.SetL1ValidatorWeightTx:
return "SetL1ValidatorWeight"
case *txs.IncreaseL1ValidatorBalanceTx:
return "IncreaseL1ValidatorBalance"
case *txs.DisableL1ValidatorTx:
return "DisableL1Validator"
case *txs.ConvertNetworkTx:
return "ConvertNetwork"
default:
return "unknown"
}
}
func pBlockKindName(b block.Block) string {
switch b.(type) {
case *block.StandardBlock:
return "StandardBlock"
case *block.ProposalBlock:
return "ProposalBlock"
case *block.CommitBlock:
return "CommitBlock"
case *block.AbortBlock:
return "AbortBlock"
default:
return "unknown"
}
}
func xKindName(u xtxs.UnsignedTx) string {
switch u.(type) {
case *xtxs.BaseTx:
return "Base"
case *xtxs.CreateAssetTx:
return "CreateAsset"
case *xtxs.OperationTx:
return "Operation"
case *xtxs.ImportTx:
return "Import"
case *xtxs.ExportTx:
return "Export"
default:
return "unknown"
}
}
// xFxIndex registers the secp256k1 family at position 0, which is where the
// corpus's CreateAssetTx says its initial state lives.
func xFxIndex() *xtxs.FxIndex {
fi := xtxs.NewFxIndex()
fi.Set(xwire.TypeKindSecp256k1, 0)
return fi
}
// xParser is the X-chain's own block parser, over the fxs the X-chain runs.
// Parsing a block is fx-dependent, so this is the node's parser rather than a
// second reading of the same bytes.
var xParser = func() xblock.Parser {
p, err := xblock.NewParser([]fxs.Fx{&secp256k1fx.Fx{}, &nftfx.Fx{}, &propertyfx.Fx{}})
if err != nil {
panic(err)
}
return p
}()
// xSyntactic is the X-chain's own syntactic verifier, given the one chain
// identity the corpus is built for. It is the node's type, not a copy of it.
//
// The backend is `xBackend()`, the same one the semantic pass and the executor
// read, so a vector cannot be judged under one wiring and executed under
// another.
func xSyntacticVerifier(tx *xtxs.Tx) *executor.SyntacticVerifier {
return &executor.SyntacticVerifier{Backend: xBackend(), Tx: tx}
}