mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
Coverage 74.4% -> 99.7%. The two statements left are boot gates that cannot fail by construction: RegisterService on a service type this package defines, and fee.Validate on the floor chain.Floor returns. The tests hold keys, because the chain now requires signatures: a party signs its own record and the harness submits it, which is the shape a client has to follow. Each defect the rewrite closed has a test named for it — an attacker's block claiming a victim's DID, a revocation naming a public id, a credential naming an issuer it cannot sign for, an allowlist that admits nobody it does not name, a signature made for another chain, a restart that must hold what the chain accepted, and a block whose records reference each other. Health and getStatus answered an error HealthCheck cannot return.
182 lines
5.1 KiB
Go
182 lines
5.1 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package identityvm
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/json"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/luxfi/crypto/mldsa"
|
|
"github.com/luxfi/database"
|
|
"github.com/luxfi/database/memdb"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/log"
|
|
"github.com/luxfi/runtime"
|
|
vmcore "github.com/luxfi/vm"
|
|
)
|
|
|
|
// party holds a key pair. Every authorization on this chain is a signature, so
|
|
// a test that wants one holds the key that makes it — which is the point: the
|
|
// service used to build the record itself from a public key the caller named.
|
|
type party struct {
|
|
priv *mldsa.PrivateKey
|
|
pub []byte
|
|
}
|
|
|
|
func newParty(t *testing.T) *party {
|
|
t.Helper()
|
|
priv, err := mldsa.GenerateKey(rand.Reader, KeyMode)
|
|
require.NoError(t, err)
|
|
return &party{priv: priv, pub: priv.PublicKey.Bytes()}
|
|
}
|
|
|
|
// sign signs a record's preimage under a chain's binding. Deterministic, so a
|
|
// fixture is the same fixture every run.
|
|
func (p *party) sign(t *testing.T, message []byte, bind [32]byte) []byte {
|
|
t.Helper()
|
|
sig, err := p.priv.SignCtxDeterministic(message, bind[:])
|
|
require.NoError(t, err)
|
|
return sig
|
|
}
|
|
|
|
// harness is a booted I-Chain and the parties acting on it.
|
|
type harness struct {
|
|
*VM
|
|
db database.Database
|
|
chainID ids.ID
|
|
network uint32
|
|
}
|
|
|
|
func newHarness(t *testing.T) *harness { return newHarnessWith(t, nil) }
|
|
|
|
func newHarnessWith(t *testing.T, config *Config) *harness {
|
|
t.Helper()
|
|
h := &harness{db: memdb.New(), chainID: ids.GenerateTestID(), network: 1}
|
|
h.VM = h.boot(t, config)
|
|
return h
|
|
}
|
|
|
|
func (h *harness) boot(t *testing.T, config *Config) *VM {
|
|
t.Helper()
|
|
|
|
vm := &VM{}
|
|
require.NoError(t, vm.Initialize(context.Background(), initFor(h, config)))
|
|
t.Cleanup(func() { _ = vm.Shutdown(context.Background()) })
|
|
return vm
|
|
}
|
|
|
|
// initFor is what the node hands a VM at boot.
|
|
func initFor(h *harness, config *Config) vmcore.Init {
|
|
genesis, _ := json.Marshal(&Genesis{Timestamp: 1607144400, Config: config})
|
|
return vmcore.Init{
|
|
Runtime: &runtime.Runtime{ChainID: h.chainID, NetworkID: h.network, Log: log.NoLog{}},
|
|
DB: h.db,
|
|
Genesis: genesis,
|
|
ToEngine: make(chan vmcore.Message, 1),
|
|
Log: log.NoLog{},
|
|
}
|
|
}
|
|
|
|
// restart boots a second VM over the same records. What the chain accepted, a
|
|
// restarted node holds.
|
|
func (h *harness) restart(t *testing.T) *VM {
|
|
t.Helper()
|
|
h.VM = h.boot(t, nil)
|
|
return h.VM
|
|
}
|
|
|
|
// identity builds a signed identity for a party.
|
|
func (h *harness) identity(t *testing.T, p *party, metadata map[string]string) *Identity {
|
|
t.Helper()
|
|
i := &Identity{
|
|
PublicKey: p.pub,
|
|
Created: time.Unix(0, 1).UTC(),
|
|
Metadata: metadata,
|
|
}
|
|
i.ID = identityID(p.pub)
|
|
i.Signature = p.sign(t, i.signable(), h.bind)
|
|
return i
|
|
}
|
|
|
|
// issuer builds a signed issuer for a party.
|
|
func (h *harness) issuer(t *testing.T, p *party, name string) *Issuer {
|
|
t.Helper()
|
|
s := &Issuer{
|
|
Name: name,
|
|
PublicKey: p.pub,
|
|
Types: []string{"VerifiableCredential"},
|
|
CreatedAt: time.Unix(0, 1).UTC(),
|
|
}
|
|
s.ID = issuerID(p.pub)
|
|
s.Signature = p.sign(t, s.signable(), h.bind)
|
|
return s
|
|
}
|
|
|
|
// credential builds a credential signed by the issuing party.
|
|
func (h *harness) credential(t *testing.T, by *party, issuer, subject ids.ID, expires time.Time) *Credential {
|
|
t.Helper()
|
|
c := &Credential{
|
|
Type: []string{"VerifiableCredential"},
|
|
Issuer: issuer,
|
|
Subject: subject,
|
|
IssuanceDate: time.Unix(0, 2).UTC(),
|
|
ExpirationDate: expires,
|
|
Claims: map[string]interface{}{"degree": "maths"},
|
|
}
|
|
c.Signature = by.sign(t, c.signable(), h.bind)
|
|
c.ID = tag("identityvm/credential", c.signable())
|
|
return c
|
|
}
|
|
|
|
// revocation builds a revocation signed by the revoking party.
|
|
func (h *harness) revocation(t *testing.T, by *party, credID, revoker ids.ID) *Revocation {
|
|
t.Helper()
|
|
r := &Revocation{
|
|
CredentialID: credID,
|
|
RevokedBy: revoker,
|
|
RevokedAt: time.Unix(0, 3).UTC(),
|
|
Reason: "superseded",
|
|
}
|
|
r.Signature = by.sign(t, r.signable(), h.bind)
|
|
return r
|
|
}
|
|
|
|
// accept submits changes, builds the block that carries them, verifies it and
|
|
// accepts it — the whole path a change takes to become state.
|
|
func (h *harness) accept(t *testing.T, changes ...*Change) *Block {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
|
|
for _, c := range changes {
|
|
require.NoError(t, h.Submit(c))
|
|
}
|
|
|
|
built, err := h.BuildBlock(ctx)
|
|
require.NoError(t, err)
|
|
blk := built.(*Block)
|
|
require.NoError(t, blk.Verify(ctx))
|
|
require.NoError(t, blk.Accept(ctx))
|
|
return blk
|
|
}
|
|
|
|
// enrolled registers an issuer, a subject, and a credential from one to the
|
|
// other — the shape most tests start from.
|
|
func (h *harness) enrolled(t *testing.T) (issuer, subject *party, cred *Credential) {
|
|
t.Helper()
|
|
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 = h.credential(t, issuer, issuerRecord.ID, subjectRecord.ID, time.Now().Add(time.Hour))
|
|
h.accept(t, &Change{Credential: cred})
|
|
return issuer, subject, cred
|
|
}
|