mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
aivm: compare the recorded receipt_root unconditionally
verifyImported exists so every validator reaches byte-identical engine state from
the same committed intents, and its comparison was written
`wantRoot != (common.Hash{}) && got != wantRoot`. The zero-hash exemption made the
guarantee opt-out: a proposer recorded intents, wrote an all-zero receipt_root,
and every follower applied those intents and returned nil without ever checking
its resulting engine state matched the proposer's. Proven: a block whose
receipt_root was zeroed while the engine's real root was non-zero verified with
no error.
Nothing honest relied on the exemption. A proposer stamps the engine's own root
(vm.go:622), and before anything settles the root is legitimately zero on both
sides — so plain equality admits every honest block and rejects only a genuine
divergence.
Test: TestVerifyRejectsBlockClaimingZeroReceiptRoot.
Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
+8
-1
@@ -126,7 +126,14 @@ func (vm *VM) verifyImported(height uint64, recorded []CIntent, wantRoot common.
|
||||
return err
|
||||
}
|
||||
}
|
||||
if got := vm.quorum.ReceiptRoot(vm.qstate); wantRoot != (common.Hash{}) && got != wantRoot {
|
||||
// The recorded root must equal the one these imports actually produce, with no
|
||||
// exemption for the zero hash. Skipping the comparison when the block claims
|
||||
// zero made the determinism check opt-out: a proposer wrote a zero receipt_root
|
||||
// and followers applied its intents without ever checking that their engine
|
||||
// state agreed. An honest proposer stamps the engine's own root (vm.go:622), and
|
||||
// a block with no folded receipts carries zero on both sides, so an equality
|
||||
// check admits every honest case and only rejects a genuine divergence.
|
||||
if got := vm.quorum.ReceiptRoot(vm.qstate); got != wantRoot {
|
||||
return ErrReceiptRootMismatch
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package aivm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/luxfi/geth/common"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestVerifyRejectsBlockClaimingZeroReceiptRoot proves the receipt-root
|
||||
// comparison has no exemption for the zero hash.
|
||||
//
|
||||
// verifyImported exists so every validator reaches byte-identical engine state
|
||||
// from the same committed inputs. Exempting the zero hash made that guarantee
|
||||
// opt-out: a proposer recorded intents, wrote a zero receipt_root, and followers
|
||||
// applied the intents without ever checking their engine state agreed with the
|
||||
// proposer's.
|
||||
func TestVerifyRejectsBlockClaimingZeroReceiptRoot(t *testing.T) {
|
||||
require := require.New(t)
|
||||
v, reqr, _ := newAIVMForConsensus(t)
|
||||
e, st, lg := v.QuorumEngine()
|
||||
|
||||
reward := uint256.NewInt(1_000_000_000_000_000_000)
|
||||
|
||||
// Drive one task to settlement so the engine has folded a receipt and the
|
||||
// receipt_root is non-zero. Until something settles the root is legitimately
|
||||
// zero on both sides, which is why the exemption looked harmless.
|
||||
settled := mkIntent(e, reqr, testN, testThr, uint256.NewInt(1), reward)
|
||||
taskID, err := e.ImportCommittedIntent(st, lg, acceptAll, settled, 100)
|
||||
require.NoError(err)
|
||||
selected, _ := e.SelectOperators(st, taskID, modelSpec, testN)
|
||||
out := h(0x42)
|
||||
for _, op := range selected {
|
||||
require.NoError(e.CommitResponse(st, taskID, op, opCommit(taskID, op, out, h(0x01), h(0x02)), 101))
|
||||
require.NoError(e.RevealResponse(st, taskID, op, out, h(0x01), h(0x02), 131))
|
||||
}
|
||||
_, err = e.Settle(st, lg, taskID, 161)
|
||||
require.NoError(err)
|
||||
require.NoError(v.commitEngine())
|
||||
|
||||
root := e.ReceiptRoot(st)
|
||||
require.NotEqual(common.Hash{}, root, "a settled receipt must move the root")
|
||||
|
||||
// An honest proposer stamps the engine's own root and verifies.
|
||||
v.EnqueueCommittedIntent(mkIntent(e, reqr, testN, testThr, uint256.NewInt(2), reward))
|
||||
blk, err := v.BuildBlock(context.Background())
|
||||
require.NoError(err)
|
||||
honest := blk.(*Block)
|
||||
require.Len(honest.ImportedIntents, 1)
|
||||
require.Equal(root, honest.ReceiptRoot)
|
||||
require.NoError(honest.Verify(context.Background()))
|
||||
|
||||
// The same block claiming a zero root must be refused, not waved through.
|
||||
tampered := *honest
|
||||
tampered.ReceiptRoot = common.Hash{}
|
||||
require.ErrorIs(tampered.Verify(context.Background()), ErrReceiptRootMismatch)
|
||||
}
|
||||
Reference in New Issue
Block a user