Files
zeekayandHanzo Dev 104bf5f954 Go 1.27.1, and the code it can say more simply
Modules declare go 1.27.1 and the build images follow. go fix applied the
modernizers; omitzero is not applied, since it changes what the JSON says.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-16 19:39:55 -07:00

258 lines
11 KiB
Go

// Copyright (C) 2026, Hanzo AI Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// RED-1 adversarial suite (fourth pass): multi-job conservation under shared
// operators + slashing, economic edges (reward=0, stake floored to zero), and
// large-N bounds.
package aiquorum
import (
"math/big"
"testing"
"github.com/holiman/uint256"
"github.com/luxfi/geth/common"
"github.com/stretchr/testify/require"
)
// ===========================================================================
// RED-L: GLOBAL conservation across TWO overlapping jobs that share operators,
// where withholders in each job are slashed. The slash in job 1 reduces an
// operator's stake; that same operator may be a winner in job 2. Assert that
// after BOTH settle and EVERYONE withdraws, the escrow account is exactly zero
// and the universe wei is conserved — i.e. interleaved slashing+payout across
// jobs neither mints nor strands wei.
// ===========================================================================
func TestRedL_MultiJobConservation(t *testing.T) {
db := newMockStateDB()
lg := newLedger(db)
// 8 operators with generous stake so two slashes never floor anyone to zero.
ops := registerN(t, db, lg, spec1, 8, tokens(5))
fund(db, requester, tokens(1000))
r2 := reqAddr(2)
fund(db, r2, tokens(1000))
// BurnAddress is in the universe: the non-refundable request fee MOVES there
// (value is not destroyed), so the grand total stays invariant across jobs.
accts := append([]common.Address{}, ops...)
accts = append(accts, requester, r2, ContractAddress, BurnAddress)
universe := totalWei(db, accts)
embed := common.HexToHash("0x0e3b")
// Job 1 by `requester`: N=5, threshold=3. 3 winners, 2 withholders.
j1, err := RequestInference(db, lg, requester, spec1, prompt1, 5, 3, tokens(1), 10)
require.NoError(t, err)
sel1 := selOf(db, j1, 5)
ans1 := common.HexToHash("0xa1")
for i := range 5 {
nce := common.BigToHash(big.NewInt(int64(i)))
require.NoError(t, CommitResponse(db, j1, sel1[i], ComputeCommit(j1, spec1, prompt1, ans1, embed, sel1[i], nce), 12))
}
for i := range 3 { // only 3 reveal
nce := common.BigToHash(big.NewInt(int64(i)))
require.NoError(t, RevealResponse(db, j1, sel1[i], ans1, embed, nce, revealBlkAt))
}
// Job 2 by `r2`: N=5, threshold=3, different prompt -> different selection. All reveal.
prompt2 := common.HexToHash("0x33")
j2, err := RequestInference(db, lg, r2, spec1, prompt2, 5, 3, tokens(2), 10)
require.NoError(t, err)
sel2 := selOf(db, j2, 5)
ans2 := common.HexToHash("0xb2")
for i := range 5 {
nce := common.BigToHash(big.NewInt(int64(100 + i)))
require.NoError(t, CommitResponse(db, j2, sel2[i], ComputeCommitP(j2, prompt2, ans2, embed, sel2[i], nce), 12))
require.NoError(t, RevealResponseP(t, db, j2, sel2[i], ans2, embed, nce, revealBlkAt))
}
_, err = Settle(db, lg, j1, settleBlkAt)
require.NoError(t, err)
_, err = Settle(db, lg, j2, settleBlkAt)
require.NoError(t, err)
require.Equal(t, universe, totalWei(db, accts), "two settles mint/burn nothing")
// Everyone withdraws rewards, then stake.
for _, a := range append([]common.Address{requester, r2}, ops...) {
if !GetCredit(db, a).IsZero() {
_, err := WithdrawRewards(db, lg, a)
require.NoError(t, err)
}
}
for _, op := range ops {
require.NoError(t, DeregisterOperator(db, op, 1000))
_, err := WithdrawStake(db, lg, op, 1000+UnbondCooldownBlocks)
require.NoError(t, err)
}
require.Zero(t, db.GetBalance(ContractAddress).Uint64(), "escrow fully drained across two jobs")
require.Equal(t, universe, totalWei(db, accts), "universe conserved across two interleaved jobs")
}
// selOf enumerates a job's selected set.
func selOf(db *mockStateDB, jobID common.Hash, n uint32) []common.Address {
out := make([]common.Address, n)
for i := range n {
out[i] = SelectedAt(db, jobID, i)
}
return out
}
// ComputeCommitP / RevealResponseP: commit/reveal helpers parameterized by a
// custom prompt (job 2 uses prompt2). spec is spec1 throughout.
func ComputeCommitP(jobID, prompt, out, embed common.Hash, op common.Address, nonce common.Hash) common.Hash {
return ComputeCommit(jobID, spec1, prompt, out, embed, op, nonce)
}
func RevealResponseP(t *testing.T, db StateDB, jobID common.Hash, op common.Address, out, embed, nonce common.Hash, block uint64) error {
t.Helper()
return RevealResponse(db, jobID, op, out, embed, nonce, block)
}
// ===========================================================================
// RED-M: economic edges.
// (1) reward = 0 — is a zero-reward job allowed, and does it conserve? A
// zero-reward job escrows 0 wei; winners are paid 0 + slash share.
// (2) An operator whose stake is EXACTLY MinStake, slashed by SlashPerOperator,
// drops below MinStake but stake stays >= 0 (floored). With stake < slash
// (e.g. stake = 0.05 token, slash = 0.1) only the remaining stake is taken
// (floor at zero) — assert no underflow / no negative stake / pool counts
// only what was actually taken.
// ===========================================================================
func TestRedM_EconomicEdges(t *testing.T) {
// (1) reward = 0.
db := newMockStateDB()
lg := newLedger(db)
ops := registerN(t, db, lg, spec1, 7, tokens(2)) // N=5 + RED-A margin 2
fund(db, requester, tokens(10))
j, err := RequestInference(db, lg, requester, spec1, prompt1, 5, 3, uint256.NewInt(0), 10)
require.NoError(t, err, "zero-reward job should be permitted (escrows 0)")
sel := selOf(db, j, 5)
ans := common.HexToHash("0xa")
embed := common.HexToHash("0xe")
for i := range 5 {
nce := common.BigToHash(big.NewInt(int64(i)))
require.NoError(t, CommitResponse(db, j, sel[i], ComputeCommit(j, spec1, prompt1, ans, embed, sel[i], nce), 12))
}
for i := range 3 { // 3 reveal, 2 withhold -> slashed; slash pool funds winner shares
nce := common.BigToHash(big.NewInt(int64(i)))
require.NoError(t, RevealResponse(db, j, sel[i], ans, embed, nce, revealBlkAt))
}
res, err := Settle(db, lg, j, settleBlkAt)
require.NoError(t, err)
require.Equal(t, JobSettled, res.Status)
// Each of the 3 winners gets reward(0)+share of 2*Slash; conservation: total
// credit == 0 (escrow) + 2*Slash.
total := new(uint256.Int)
for _, op := range ops {
total.Add(total, GetCredit(db, op))
}
total.Add(total, GetCredit(db, requester))
require.Equal(t, new(uint256.Int).Mul(SlashPerOperator, uint256.NewInt(2)).Uint64(), total.Uint64(),
"zero-reward settle: total credit == slash pool exactly")
// (2) stake below slash amount -> floored at remaining, no underflow.
db2 := newMockStateDB()
lg2 := newLedger(db2)
// Register 5 strong + 1 thin operator (pool of 6 clears N=4's margin of 2).
// The thin operator bonds exactly MinStake; its stake is eroded below the slash
// later to exercise the flooring path.
for i := range 5 {
fund(db2, opAddr(i), tokens(5))
require.NoError(t, RegisterOperator(db2, lg2, opAddr(i), tokens(3), spec1, common.Hash{}))
}
thin := opAddr(5)
fund(db2, thin, tokens(5))
require.NoError(t, RegisterOperator(db2, lg2, thin, MinStake, spec1, common.Hash{}))
fund(db2, requester, tokens(100))
// Job A: thin commits+withholds -> slashed by 0.1 (MinStake=1.0 -> 0.9 left).
// The beacon draws 4 of 6; find a request block where thin is in the draw
// (deterministic test setup — selection is a pure function of the job_id, which
// depends on the request block; this is not production grinding, just choosing a
// scenario where the target operator participates).
var jA common.Hash
var blkA uint64
for b := uint64(10); b < 200; b++ {
jid, err := RequestInference(db2, lg2, requester, spec1, prompt1, 4, 3, tokens(1), b)
require.NoError(t, err)
if IsSelected(db2, jid, thin) {
jA, blkA = jid, b
break
}
}
require.NotEqual(t, common.Hash{}, jA, "no request block selected the thin operator in 190 tries")
require.True(t, IsSelected(db2, jA, thin))
commitBlkA := blkA + 2
revealBlkA := blkA + CommitBlocks + 1
settleBlkA := blkA + CommitBlocks + RevealBlocks + 1
selA := selOf(db2, jA, 4)
ansA := common.HexToHash("0xaa")
for i := range 4 {
nce := common.BigToHash(big.NewInt(int64(i)))
require.NoError(t, CommitResponse(db2, jA, selA[i], ComputeCommit(jA, spec1, prompt1, ansA, embed, selA[i], nce), commitBlkA))
if selA[i] != thin {
require.NoError(t, RevealResponse(db2, jA, selA[i], ansA, embed, nce, revealBlkA))
}
}
_, err = Settle(db2, lg2, jA, settleBlkA)
require.NoError(t, err)
_, _, thinStake, _, _ := GetOperator(db2, thin)
require.Equal(t, new(uint256.Int).Sub(MinStake, SlashPerOperator).Uint64(), thinStake.Uint64())
// Now drain thin's remaining stake to BELOW the slash so the next slash floors.
// thin is below MinStake now, so it's ineligible for new jobs; directly verify
// slashStake floors at remaining via a synthetic check: set stake to 0.05 token.
writeStake(db2, thin, new(uint256.Int).Div(SlashPerOperator, uint256.NewInt(2))) // 0.05 token
stakeBefore := readStake(db2, thin)
require.True(t, stakeBefore.Lt(SlashPerOperator))
// Slash it directly via slashStake on a crafted job where thin committed+withheld.
// Build a tiny job structure in-state: select thin at index 0, mark commit, no reveal.
jc := common.HexToHash("0xc0c0")
db2.SetState(ContractAddress, slotHashIdx(nsSelList, jc, 0), common.BytesToHash(common.LeftPadBytes(thin.Bytes(), 32)))
db2.SetState(ContractAddress, slotHashAddr(nsCommit, jc, thin), common.HexToHash("0x1"))
pool, err := slashStake(db2, jc, jobRecord{N: 1}, common.Hash{}, false)
require.NoError(t, err)
require.Equal(t, stakeBefore.Uint64(), pool.Uint64(), "slash floored at remaining stake (no underflow, no over-slash)")
require.Zero(t, readStake(db2, thin).Uint64(), "stake floored to exactly zero")
}
// ===========================================================================
// RED-N: large-N bounds. N = maxN (256): selection, full commit/reveal, and
// settle must run without panic and produce a correct settlement. Confirms the
// O(N) tally/slash loops are bounded by maxN (gas-bounded per the dispatcher's
// per-N surcharge) and there is no overflow in the share/escrow math at scale.
// ===========================================================================
func TestRedN_MaxNSettleBounded(t *testing.T) {
db := newMockStateDB()
lg := newLedger(db)
n := uint32(maxN)
// Pool must clear N + requiredMargin(N) = 256 + 128 = 384 to admit the request.
M := int(n + requiredMargin(n))
registerN(t, db, lg, spec1, M, tokens(2))
fund(db, requester, tokens(1_000_000))
threshold := n/2 + 1
j, err := RequestInference(db, lg, requester, spec1, prompt1, n, threshold, tokens(1), 10)
require.NoError(t, err)
sel := selOf(db, j, n)
ans := common.HexToHash("0xa")
embed := common.HexToHash("0xe")
// All commit+reveal the same answer -> unanimous quorum of size N.
for i := range n {
nce := common.BigToHash(big.NewInt(int64(i)))
require.NoError(t, CommitResponse(db, j, sel[i], ComputeCommit(j, spec1, prompt1, ans, embed, sel[i], nce), 12))
require.NoError(t, RevealResponse(db, j, sel[i], ans, embed, nce, revealBlkAt))
}
res, err := Settle(db, lg, j, settleBlkAt)
require.NoError(t, err)
require.Equal(t, JobSettled, res.Status)
require.Equal(t, n, res.WinnerCount)
require.Equal(t, ans, res.CanonicalHash)
// Paid exactly N*reward (no slash since all revealed), bounded, no overflow.
require.Equal(t, new(uint256.Int).Mul(tokens(1), uint256.NewInt(uint64(n))).Uint64(), res.Paid.Uint64())
}