Files
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

193 lines
6.1 KiB
Go

// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package identityvm
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/luxfi/chains/fee"
"github.com/luxfi/database/memdb"
"github.com/luxfi/ids"
"github.com/luxfi/log"
"github.com/luxfi/runtime"
vmcore "github.com/luxfi/vm"
)
func TestBlockAccessors(t *testing.T) {
h := newHarness(t)
b := &Block{
ParentID_: ids.ID{2},
BlockHeight: 5,
BlockTimestamp: 1_700_000_000,
vm: h.VM,
}
require.Equal(t, ids.ID{2}, b.Parent())
require.Equal(t, ids.ID{2}, b.ParentID())
require.EqualValues(t, 5, b.Height())
require.Equal(t, time.Unix(1_700_000_000, 0), b.Timestamp())
require.Zero(t, b.records())
require.NotNil(t, h.FeePolicy())
require.NoError(t, h.gateUserTx(fee.MinTxFeeFloor))
require.Error(t, h.gateUserTx(0))
}
// A credential lapses when its expiry passes, whatever the revocation set says.
func TestACredentialLapses(t *testing.T) {
h := newHarness(t)
issuer, subject := newParty(t), newParty(t)
issuerRecord := h.issuer(t, issuer, "registry")
subjectRecord := h.identity(t, subject, nil)
h.accept(t, &Change{Issuer: issuerRecord}, &Change{Identity: subjectRecord})
brief := h.credential(t, issuer, issuerRecord.ID, subjectRecord.ID, time.Now().Add(40*time.Millisecond))
h.accept(t, &Change{Credential: brief})
require.NoError(t, h.Verify(brief.ID))
time.Sleep(60 * time.Millisecond)
require.ErrorIs(t, h.Verify(brief.ID), errCredentialExpired)
_, status, err := h.Credential(brief.ID)
require.NoError(t, err)
require.Equal(t, CredentialExpired, status)
}
// Claims a credential cannot carry are refused, and a claims document that
// cannot be encoded is not a document.
func TestClaimsAreBounded(t *testing.T) {
h := newHarnessWith(t, &Config{MaxClaims: 2})
issuer, subject := newParty(t), newParty(t)
issuerRecord := h.issuer(t, issuer, "registry")
subjectRecord := h.identity(t, subject, nil)
h.accept(t, &Change{Issuer: issuerRecord}, &Change{Identity: subjectRecord})
cred := &Credential{
Issuer: issuerRecord.ID,
Subject: subjectRecord.ID,
IssuanceDate: time.Unix(0, 1).UTC(),
ExpirationDate: time.Now().Add(time.Hour),
Claims: map[string]interface{}{"a": "1", "b": "2", "c": "3"},
}
cred.Signature = issuer.sign(t, cred.signable(), h.bind)
cred.ID = tag("identityvm/credential", cred.signable())
require.ErrorIs(t, h.Submit(&Change{Credential: cred}), errTooManyClaims)
// A claims map json cannot encode encodes as nothing, and the credential
// the chain records is the one whose id was taken over those bytes.
require.Nil(t, marshalClaims(map[string]interface{}{"chan": make(chan int)}))
// The Proof artifact reports what it cannot describe.
_, _, cred2 := h.enrolled(t)
held, _, err := h.Credential(cred2.ID)
require.NoError(t, err)
held.Claims = map[string]interface{}{"chan": make(chan int)}
_, err = h.Proof(cred2.ID, nil)
require.Error(t, err)
}
// A revoker the chain has no key for cannot revoke, even naming the right id.
func TestARevokerNeedsAKeyOnTheChain(t *testing.T) {
h := newHarnessWith(t, &Config{AllowSelfIssue: true})
// A self-issued credential whose issuer is an identity, then the identity
// removed from the caches: the chain has the credential and not the key.
p := newParty(t)
record := h.identity(t, p, nil)
h.accept(t, &Change{Identity: record})
cred := h.credential(t, p, record.ID, record.ID, time.Now().Add(time.Hour))
h.accept(t, &Change{Credential: cred})
h.chain.Lock()
delete(h.identities, record.ID)
h.chain.Unlock()
require.ErrorIs(t, h.Submit(&Change{Revocation: h.revocation(t, p, cred.ID, record.ID)}), errUnknownIdentity)
var pending view
pending.vm = h.VM
_, ok := pending.key(ids.GenerateTestID())
require.False(t, ok)
}
// A block introducing the same issuer or the same credential twice claims one
// name twice, whichever list it is in.
func TestABlockCannotClaimOneNameTwice(t *testing.T) {
ctx := context.Background()
h := newHarness(t)
issuer := newParty(t)
issuerRecord := h.issuer(t, issuer, "registry")
subject := newParty(t)
subjectRecord := h.identity(t, subject, nil)
cred := h.credential(t, issuer, issuerRecord.ID, subjectRecord.ID, time.Now().Add(time.Hour))
tip, height := h.chain.Tip()
twice := &Block{
ParentID_: tip,
BlockHeight: height + 1,
BlockTimestamp: time.Now().Unix(),
Identities: []*Identity{subjectRecord},
Issuers: []*Issuer{issuerRecord, issuerRecord},
vm: h.VM,
}
require.ErrorIs(t, twice.Verify(ctx), errExists)
twice.Issuers = []*Issuer{issuerRecord}
twice.Credentials = []*Credential{cred, cred}
twice.bytes, twice.ID_ = nil, ids.Empty
require.ErrorIs(t, twice.Verify(ctx), errExists)
}
// Assembly drops one change of each kind that it cannot build, leaving the
// block it can.
func TestAssemblyDropsEachKind(t *testing.T) {
ctx := context.Background()
h := newHarness(t)
good := h.identity(t, newParty(t), nil)
require.NoError(t, h.Submit(&Change{Identity: good}))
// Reaching past the door: a peer's gossip, or a door that stops checking.
for _, bad := range []*Change{
{Issuer: &Issuer{ID: ids.GenerateTestID(), PublicKey: []byte("x")}},
{Credential: &Credential{ID: ids.GenerateTestID()}},
{Revocation: &Revocation{CredentialID: ids.GenerateTestID()}},
} {
require.NoError(t, h.pending.Add(bad))
}
built, err := h.BuildBlock(ctx)
require.NoError(t, err)
blk := built.(*Block)
require.Len(t, blk.Identities, 1)
require.Empty(t, blk.Issuers)
require.Empty(t, blk.Credentials)
require.Empty(t, blk.Revocations)
require.NoError(t, blk.Verify(ctx))
}
// A chain that cannot read its own tip does not know where it is.
func TestOpenRefusesAnUnreadableTip(t *testing.T) {
db := memdb.New()
require.NoError(t, db.Put([]byte("chain/tip"), []byte("not an id")))
genesis, err := json.Marshal(&Genesis{Timestamp: 1})
require.NoError(t, err)
err = (&VM{}).Initialize(context.Background(), vmcore.Init{
Runtime: &runtime.Runtime{ChainID: ids.ID{8}, Log: log.NoLog{}},
DB: db,
Genesis: genesis,
Log: log.NoLog{},
})
require.ErrorContains(t, err, "tip")
}