Files
chains/keyvm/gas_test.go
T
zeekay a93f6e3dec build the VMs without the daemon in their closure
A plugin is its own binary; the daemon that loads it has no business being
linked into it. Twelve VMs reached into github.com/luxfi/node for four things,
and each one now programs against a home that is actually its own:

- fee admission (Policy, FlatPolicy, NoUserTxPolicy, Validate) joins the
  settlement half already in chains/fee. One fee package, and the nodefee alias
  that marked the split is gone.
- the cross-chain artifact types move to chains/artifacts.
- the factory the daemon registers is luxfi/vm/manager.Factory, and the version
  types are luxfi/version. Both are ordinary modules the daemon reads too, so a
  VM imports them directly rather than through anything of ours.

The GPU switch is deleted, not moved. It was a global set during daemon startup
that a separate process could never observe, so every plugin read its default;
FHEOptions.Backend is the switch that was really there.

go list -deps names no luxfi/node for aivm, bridgevm, dexvm, evm, fhevm,
graphvm, identityvm, keyvm, mpcvm, quantumvm, schain or zkvm, and the built
plugin binaries carry no such string. oraclevm and relayvm still do: they are
re-export shims over luxfi/oracle and luxfi/relay, which build their fee
policies upstream. Their tests keep naming the daemon's fee package because
that is the type their value has.
2026-09-03 21:10:40 -07:00

146 lines
5.5 KiB
Go

// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package keyvm
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/chains/fee"
)
// TestGas_PerAlgorithmDistinct proves the audit fix: ML-KEM encapsulation,
// ML-DSA-65 sign-authorize, and BLS verify are priced DIFFERENTLY, not by one
// flat floor.
func TestGas_PerAlgorithmDistinct(t *testing.T) {
feeFor := func(algo string) uint64 {
f, err := FeeFor(&Transaction{Type: TxAuthorize, Algorithm: algo})
require.NoError(t, err)
return f
}
kem := feeFor("ml-kem-768")
dsa := feeFor("ml-dsa-65")
bls := feeFor("bls-threshold")
require.NotEqual(t, kem, dsa, "ML-KEM and ML-DSA-65 must price differently")
require.NotEqual(t, dsa, bls, "ML-DSA-65 and BLS must price differently")
require.NotEqual(t, kem, bls, "ML-KEM and BLS must price differently")
// Real-cost ordering: KEM encaps < BLS verify < ML-DSA-65 threshold sign.
require.Less(t, kem, bls)
require.Less(t, bls, dsa)
}
// TestGas_AllOperationsMeetFloor proves every scheduled operation settles at or
// above the node admission floor, so settlement and admission never drift.
func TestGas_AllOperationsMeetFloor(t *testing.T) {
// Algorithm-priced operations across every supported algorithm.
for _, op := range []uint8{TxRegisterKey, TxAuthorize} {
for algo := range algoGas {
f, err := FeeFor(&Transaction{Type: op, Algorithm: algo})
require.NoError(t, err)
require.GreaterOrEqualf(t, f, fee.MinTxFeeFloor,
"op %d algo %s fee %d below floor %d", op, algo, f, fee.MinTxFeeFloor)
}
}
// Policy-only operations (algorithm-independent).
for _, op := range []uint8{TxSetPolicy, TxRevokeKey} {
f, err := FeeFor(&Transaction{Type: op})
require.NoError(t, err)
require.GreaterOrEqual(t, f, fee.MinTxFeeFloor)
}
require.GreaterOrEqual(t, MinScheduledFee(), fee.MinTxFeeFloor,
"the cheapest scheduled fee must satisfy the admission floor")
}
// TestGas_UnknownAlgorithmRejected proves an unrecognised algorithm is refused
// (fail closed), never priced at the bare base cost.
func TestGas_UnknownAlgorithmRejected(t *testing.T) {
_, err := GasFor(&Transaction{Type: TxRegisterKey, Algorithm: "rsa-2048"})
require.ErrorIs(t, err, ErrUnknownAlgorithm)
_, err = GasFor(&Transaction{Type: TxAuthorize, Algorithm: ""})
require.ErrorIs(t, err, ErrUnknownAlgorithm)
require.False(t, SupportedAlgorithm("rsa-2048"))
require.True(t, SupportedAlgorithm("ml-dsa-65"))
}
// TestGas_PolicyOpsAlgorithmIndependent proves policy operations ignore the
// algorithm field entirely.
func TestGas_PolicyOpsAlgorithmIndependent(t *testing.T) {
a, err := FeeFor(&Transaction{Type: TxSetPolicy, Algorithm: "ml-dsa-87"})
require.NoError(t, err)
b, err := FeeFor(&Transaction{Type: TxSetPolicy, Algorithm: ""})
require.NoError(t, err)
require.Equal(t, a, b, "SetPolicy must be algorithm-independent")
}
// TestGas_PayloadBytesArePriced is the regression test for unpriced bytes. The
// schedule had no length term, so a megabyte of commitments rode on chain — into
// the block AND into the key record, permanently — for the same flat fee as an
// empty payload. Price must be strictly increasing in payload length, at the
// declared rate, for every operation.
func TestGas_PayloadBytesArePriced(t *testing.T) {
for _, op := range []uint8{TxRegisterKey, TxSetPolicy, TxAuthorize, TxRevokeKey} {
algo := ""
if usesAlgorithm(op) {
algo = "ml-dsa-65"
}
empty, err := GasFor(&Transaction{Type: op, Algorithm: algo})
require.NoError(t, err)
small, err := GasFor(&Transaction{Type: op, Algorithm: algo, Payload: make([]byte, 100)})
require.NoError(t, err)
large, err := GasFor(&Transaction{Type: op, Algorithm: algo, Payload: make([]byte, 1_000_000)})
require.NoError(t, err)
require.Greaterf(t, small, empty, "op %d: 100 bytes must cost more than none", op)
require.Greaterf(t, large, small, "op %d: a megabyte must cost more than 100 bytes", op)
require.Equalf(t, empty+100*GasPerPayloadByte, small,
"op %d: bytes must be priced at the declared rate", op)
require.Equalf(t, empty+1_000_000*GasPerPayloadByte, large, "op %d", op)
}
}
// TestGas_PayloadPricingReachesThePayer proves the byte term is not an isolated
// arithmetic fact: a payer submitting a larger payload is charged more real
// balance, and the surplus is burned.
func TestGas_PayloadPricingReachesThePayer(t *testing.T) {
small, large := newTestKey(t), newTestKey(t)
const fund = uint64(100_000_000_000)
vm := newTestVM(t, map[string]uint64{small.hexAddr(): fund, large.hexAddr(): fund})
defer func() { _ = vm.Shutdown(context.Background()) }()
tiny := registerTx(t, small, "a", 100_000_000, 1)
// The same operation carrying 64 KiB of PUBLIC commitments.
bulk := make([][]byte, 64)
for i := range bulk {
bulk[i] = make([]byte, 1024)
}
payload := mustJSONRaw(RegisterKeyPayload{
Name: "b", PublicKey: []byte("PUB"), Threshold: 1, TotalShares: 1, Commitments: bulk,
})
fat := &Transaction{
Type: TxRegisterKey, Algorithm: "ml-dsa-65", Payer: large.addr,
KeyID: deriveKeyID("b"), GasLimit: 100_000_000, Nonce: 1, Payload: payload,
}
large.sign(t, fat)
acceptOne(t, vm, tiny)
acceptOne(t, vm, fat)
tinyBal, err := vm.Balance(small.addr)
require.NoError(t, err)
fatBal, err := vm.Balance(large.addr)
require.NoError(t, err)
require.Less(t, fatBal, tinyBal, "a bigger payload must cost the payer more")
burned, err := vm.Burned()
require.NoError(t, err)
require.Equal(t, (fund-tinyBal)+(fund-fatBal), burned,
"everything both payers lost was burned")
}