Commit d27f4695 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-challenger: Separate concepts of InputData and Leaf (#9143)

A Leaf must always be 136 bytes of data and includes keccak padding. InputData does not include the keccak padding.
parent 22b5a2db
......@@ -10,7 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
......@@ -33,23 +33,12 @@ type PreimageOracleContract struct {
contract *batching.BoundContract
}
// Leaf is the keccak state matrix added to the large preimage merkle tree.
type Leaf struct {
// Input is the data absorbed for the block, exactly 136 bytes
Input [136]byte
// Index of the block in the absorption process
Index *big.Int
// StateCommitment is the hash of the internal state after absorbing the input.
StateCommitment common.Hash
}
// toPreimageOracleLeaf converts a Leaf to the contract [bindings.PreimageOracleLeaf] type.
func (l Leaf) toPreimageOracleLeaf() bindings.PreimageOracleLeaf {
commitment := ([32]byte)(l.StateCommitment.Bytes())
func toPreimageOracleLeaf(l keccakTypes.Leaf) bindings.PreimageOracleLeaf {
return bindings.PreimageOracleLeaf{
Input: l.Input[:],
Index: l.Index,
StateCommitment: commitment,
StateCommitment: l.StateCommitment,
}
}
......@@ -98,7 +87,7 @@ func (c *PreimageOracleContract) InitLargePreimage(uuid *big.Int, partOffset uin
return call.ToTxCandidate()
}
func (c *PreimageOracleContract) AddLeaves(uuid *big.Int, input []byte, commitments [][32]byte, finalize bool) (txmgr.TxCandidate, error) {
func (c *PreimageOracleContract) AddLeaves(uuid *big.Int, input []byte, commitments []common.Hash, finalize bool) (txmgr.TxCandidate, error) {
call := c.contract.Call(methodAddLeavesLPP, uuid, input, commitments, finalize)
return call.ToTxCandidate()
}
......@@ -107,9 +96,9 @@ func (c *PreimageOracleContract) Squeeze(
claimant common.Address,
uuid *big.Int,
stateMatrix *matrix.StateMatrix,
preState Leaf,
preState keccakTypes.Leaf,
preStateProof MerkleProof,
postState Leaf,
postState keccakTypes.Leaf,
postStateProof MerkleProof,
) (txmgr.TxCandidate, error) {
call := c.contract.Call(
......@@ -117,9 +106,9 @@ func (c *PreimageOracleContract) Squeeze(
claimant,
uuid,
abiEncodeStateMatrix(stateMatrix),
preState.toPreimageOracleLeaf(),
toPreimageOracleLeaf(preState),
preStateProof.toSized(),
postState.toPreimageOracleLeaf(),
toPreimageOracleLeaf(postState),
postStateProof.toSized(),
)
return call.ToTxCandidate()
......@@ -136,7 +125,7 @@ func abiEncodeStateMatrix(stateMatrix *matrix.StateMatrix) bindings.LibKeccakSta
return bindings.LibKeccakStateMatrix{State: *stateSlice}
}
func (c *PreimageOracleContract) GetActivePreimages(ctx context.Context, blockHash common.Hash) ([]gameTypes.LargePreimageMetaData, error) {
func (c *PreimageOracleContract) GetActivePreimages(ctx context.Context, blockHash common.Hash) ([]keccakTypes.LargePreimageMetaData, error) {
block := batching.BlockByHash(blockHash)
results, err := batching.ReadArray(ctx, c.multiCaller, block, c.contract.Call(methodProposalCount), func(i *big.Int) *batching.ContractCall {
return c.contract.Call(methodProposals, i)
......@@ -145,7 +134,7 @@ func (c *PreimageOracleContract) GetActivePreimages(ctx context.Context, blockHa
return nil, fmt.Errorf("failed to load claims: %w", err)
}
var idents []gameTypes.LargePreimageIdent
var idents []keccakTypes.LargePreimageIdent
for _, result := range results {
idents = append(idents, c.decodePreimageIdent(result))
}
......@@ -153,7 +142,7 @@ func (c *PreimageOracleContract) GetActivePreimages(ctx context.Context, blockHa
return c.GetProposalMetadata(ctx, block, idents...)
}
func (c *PreimageOracleContract) GetProposalMetadata(ctx context.Context, block batching.Block, idents ...gameTypes.LargePreimageIdent) ([]gameTypes.LargePreimageMetaData, error) {
func (c *PreimageOracleContract) GetProposalMetadata(ctx context.Context, block batching.Block, idents ...keccakTypes.LargePreimageIdent) ([]keccakTypes.LargePreimageMetaData, error) {
var calls []*batching.ContractCall
for _, ident := range idents {
calls = append(calls, c.contract.Call(methodProposalMetadata, ident.Claimant, ident.UUID))
......@@ -162,10 +151,10 @@ func (c *PreimageOracleContract) GetProposalMetadata(ctx context.Context, block
if err != nil {
return nil, fmt.Errorf("failed to load proposal metadata: %w", err)
}
var proposals []gameTypes.LargePreimageMetaData
var proposals []keccakTypes.LargePreimageMetaData
for i, result := range results {
meta := metadata(result.GetBytes32(0))
proposals = append(proposals, gameTypes.LargePreimageMetaData{
proposals = append(proposals, keccakTypes.LargePreimageMetaData{
LargePreimageIdent: idents[i],
Timestamp: meta.timestamp(),
PartOffset: meta.partOffset(),
......@@ -178,8 +167,8 @@ func (c *PreimageOracleContract) GetProposalMetadata(ctx context.Context, block
return proposals, nil
}
func (c *PreimageOracleContract) decodePreimageIdent(result *batching.CallResult) gameTypes.LargePreimageIdent {
return gameTypes.LargePreimageIdent{
func (c *PreimageOracleContract) decodePreimageIdent(result *batching.CallResult) keccakTypes.LargePreimageIdent {
return keccakTypes.LargePreimageIdent{
Claimant: result.GetAddress(0),
UUID: result.GetBigInt(1),
}
......
......@@ -10,7 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test"
"github.com/ethereum/go-ethereum/common"
......@@ -57,7 +57,7 @@ func TestPreimageOracleContract_AddLeaves(t *testing.T) {
uuid := big.NewInt(123)
input := []byte{0x12}
commitments := [][32]byte{{0x34}}
commitments := []common.Hash{{0x34}}
finalize := true
stubRpc.SetResponse(oracleAddr, methodAddLeavesLPP, batching.BlockLatest, []interface{}{
uuid,
......@@ -77,13 +77,13 @@ func TestPreimageOracleContract_Squeeze(t *testing.T) {
claimant := common.Address{0x12}
uuid := big.NewInt(123)
stateMatrix := matrix.NewStateMatrix()
preState := Leaf{
preState := keccakTypes.Leaf{
Input: [136]byte{0x12},
Index: big.NewInt(123),
StateCommitment: common.Hash{0x34},
}
preStateProof := MerkleProof{{0x34}}
postState := Leaf{
postState := keccakTypes.Leaf{
Input: [136]byte{0x34},
Index: big.NewInt(456),
StateCommitment: common.Hash{0x56},
......@@ -93,9 +93,9 @@ func TestPreimageOracleContract_Squeeze(t *testing.T) {
claimant,
uuid,
abiEncodeStateMatrix(stateMatrix),
preState.toPreimageOracleLeaf(),
toPreimageOracleLeaf(preState),
preStateProof.toSized(),
postState.toPreimageOracleLeaf(),
toPreimageOracleLeaf(postState),
postStateProof.toSized(),
}, nil)
......@@ -127,7 +127,7 @@ func TestGetProposalMetadata(t *testing.T) {
require.Equal(t, proposals, preimages)
// Fetching a proposal that doesn't exist should return an empty metadata object.
ident := gameTypes.LargePreimageIdent{Claimant: common.Address{0x12}, UUID: big.NewInt(123)}
ident := keccakTypes.LargePreimageIdent{Claimant: common.Address{0x12}, UUID: big.NewInt(123)}
meta := new(metadata)
stubRpc.SetResponse(
oracleAddr,
......@@ -137,10 +137,10 @@ func TestGetProposalMetadata(t *testing.T) {
[]interface{}{meta})
preimages, err = oracle.GetProposalMetadata(context.Background(), batching.BlockByHash(blockHash), ident)
require.NoError(t, err)
require.Equal(t, []gameTypes.LargePreimageMetaData{{LargePreimageIdent: ident}}, preimages)
require.Equal(t, []keccakTypes.LargePreimageMetaData{{LargePreimageIdent: ident}}, preimages)
}
func setupPreimageOracleTestWithProposals(t *testing.T, block batching.Block) (*batchingTest.AbiBasedRpc, *PreimageOracleContract, []gameTypes.LargePreimageMetaData) {
func setupPreimageOracleTestWithProposals(t *testing.T, block batching.Block) (*batchingTest.AbiBasedRpc, *PreimageOracleContract, []keccakTypes.LargePreimageMetaData) {
stubRpc, oracle := setupPreimageOracleTest(t)
stubRpc.SetResponse(
oracleAddr,
......@@ -149,8 +149,8 @@ func setupPreimageOracleTestWithProposals(t *testing.T, block batching.Block) (*
[]interface{}{},
[]interface{}{big.NewInt(3)})
preimage1 := gameTypes.LargePreimageMetaData{
LargePreimageIdent: gameTypes.LargePreimageIdent{
preimage1 := keccakTypes.LargePreimageMetaData{
LargePreimageIdent: keccakTypes.LargePreimageIdent{
Claimant: common.Address{0xaa},
UUID: big.NewInt(1111),
},
......@@ -161,8 +161,8 @@ func setupPreimageOracleTestWithProposals(t *testing.T, block batching.Block) (*
BytesProcessed: 100,
Countered: false,
}
preimage2 := gameTypes.LargePreimageMetaData{
LargePreimageIdent: gameTypes.LargePreimageIdent{
preimage2 := keccakTypes.LargePreimageMetaData{
LargePreimageIdent: keccakTypes.LargePreimageIdent{
Claimant: common.Address{0xbb},
UUID: big.NewInt(2222),
},
......@@ -173,8 +173,8 @@ func setupPreimageOracleTestWithProposals(t *testing.T, block batching.Block) (*
BytesProcessed: 200,
Countered: true,
}
preimage3 := gameTypes.LargePreimageMetaData{
LargePreimageIdent: gameTypes.LargePreimageIdent{
preimage3 := keccakTypes.LargePreimageMetaData{
LargePreimageIdent: keccakTypes.LargePreimageIdent{
Claimant: common.Address{0xcc},
UUID: big.NewInt(3333),
},
......@@ -186,7 +186,7 @@ func setupPreimageOracleTestWithProposals(t *testing.T, block batching.Block) (*
Countered: false,
}
proposals := []gameTypes.LargePreimageMetaData{preimage1, preimage2, preimage3}
proposals := []keccakTypes.LargePreimageMetaData{preimage1, preimage2, preimage3}
for i, proposal := range proposals {
stubRpc.SetResponse(
......
......@@ -11,7 +11,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
ethtypes "github.com/ethereum/go-ethereum/core/types"
......@@ -23,23 +23,13 @@ var errNotSupported = errors.New("not supported")
var _ PreimageUploader = (*LargePreimageUploader)(nil)
// MaxLeafsPerChunk is the maximum number of leafs per chunk.
const MaxLeafsPerChunk = 300
// MaxBlocksPerChunk is the maximum number of keccak blocks per chunk.
const MaxBlocksPerChunk = 300
// MaxChunkSize is the maximum size of a preimage chunk in bytes.
// Notice, the max chunk size must be a multiple of the leaf size.
// The max chunk size is roughly 0.04MB to avoid memory expansion.
const MaxChunkSize = MaxLeafsPerChunk * matrix.LeafSize
// Chunk is a contigous segment of preimage data.
type Chunk struct {
// Input is the preimage data.
Input []byte
// Commitments are the keccak commitments for each leaf in the chunk.
Commitments [][32]byte
// Finalize indicates whether the chunk is the final chunk.
Finalize bool
}
const MaxChunkSize = MaxBlocksPerChunk * keccakTypes.BlockSize
// LargePreimageUploader handles uploading large preimages by
// streaming the merkleized preimage to the PreimageOracle contract,
......@@ -56,7 +46,7 @@ func NewLargePreimageUploader(logger log.Logger, txMgr txmgr.TxManager, contract
}
func (p *LargePreimageUploader) UploadPreimage(ctx context.Context, parent uint64, data *types.PreimageOracleData) error {
chunks, err := p.splitChunks(data)
calls, err := p.splitCalls(data)
if err != nil {
return fmt.Errorf("failed to split preimage into chunks for data with oracle offset %d: %w", data.OracleOffset, err)
}
......@@ -64,7 +54,7 @@ func (p *LargePreimageUploader) UploadPreimage(ctx context.Context, parent uint6
uuid := p.newUUID(data)
// Fetch the current metadata for this preimage data, if it exists.
ident := gameTypes.LargePreimageIdent{Claimant: p.txMgr.From(), UUID: uuid}
ident := keccakTypes.LargePreimageIdent{Claimant: p.txMgr.From(), UUID: uuid}
metadata, err := p.contract.GetProposalMetadata(ctx, batching.BlockLatest, ident)
if err != nil {
return fmt.Errorf("failed to get pre-image oracle metadata: %w", err)
......@@ -81,14 +71,14 @@ func (p *LargePreimageUploader) UploadPreimage(ctx context.Context, parent uint6
// Filter out any chunks that have already been uploaded to the Preimage Oracle.
if len(metadata) > 0 {
numSkip := metadata[0].BytesProcessed / MaxChunkSize
chunks = chunks[numSkip:]
calls = calls[numSkip:]
// If the timestamp is non-zero, the preimage has been finalized.
if metadata[0].Timestamp != 0 {
chunks = chunks[len(chunks):]
calls = calls[len(calls):]
}
}
err = p.addLargePreimageLeafs(ctx, uuid, chunks)
err = p.addLargePreimageData(ctx, uuid, calls)
if err != nil {
return fmt.Errorf("failed to add leaves to large preimage with uuid: %s: %w", uuid, err)
}
......@@ -112,34 +102,22 @@ func (p *LargePreimageUploader) newUUID(data *types.PreimageOracleData) *big.Int
}
// splitChunks splits the preimage data into chunks of size [MaxChunkSize] (except the last chunk).
func (p *LargePreimageUploader) splitChunks(data *types.PreimageOracleData) ([]Chunk, error) {
func (p *LargePreimageUploader) splitCalls(data *types.PreimageOracleData) ([]keccakTypes.InputData, error) {
// Split the preimage data into chunks of size [MaxChunkSize] (except the last chunk).
stateMatrix := matrix.NewStateMatrix()
chunk := make([]byte, 0, MaxChunkSize)
chunks := []Chunk{}
commitments := make([][32]byte, 0, MaxLeafsPerChunk)
var calls []keccakTypes.InputData
in := bytes.NewReader(data.OracleData)
for i := 0; ; i++ {
// Absorb the next preimage chunk leaf and run the keccak permutation.
leaf, err := stateMatrix.AbsorbNextLeaf(in)
chunk = append(chunk, leaf...)
commitments = append(commitments, stateMatrix.StateCommitment())
// SAFETY: the last leaf will always return an [io.EOF] error from [AbsorbNextLeaf].
for {
call, err := stateMatrix.AbsorbUpTo(in, MaxChunkSize)
if errors.Is(err, io.EOF) {
chunks = append(chunks, Chunk{chunk, commitments[:], true})
calls = append(calls, call)
break
} else if err != nil {
return nil, fmt.Errorf("failed to absorb data: %w", err)
}
if err != nil {
return nil, fmt.Errorf("failed to absorb leaf: %w", err)
}
// Only create a call if the chunk is full.
if len(chunk) >= MaxChunkSize {
chunks = append(chunks, Chunk{chunk, commitments[:], false})
chunk = make([]byte, 0, MaxChunkSize)
commitments = make([][32]byte, 0, MaxLeafsPerChunk)
}
calls = append(calls, call)
}
return chunks, nil
return calls, nil
}
// initLargePreimage initializes the large preimage proposal.
......@@ -155,10 +133,10 @@ func (p *LargePreimageUploader) initLargePreimage(ctx context.Context, uuid *big
return nil
}
// addLargePreimageLeafs adds leafs to the large preimage proposal.
// addLargePreimageData adds data to the large preimage proposal.
// This method **must** be called after calling [initLargePreimage].
// SAFETY: submits transactions in a [Queue] for latency while preserving submission order.
func (p *LargePreimageUploader) addLargePreimageLeafs(ctx context.Context, uuid *big.Int, chunks []Chunk) error {
func (p *LargePreimageUploader) addLargePreimageData(ctx context.Context, uuid *big.Int, chunks []keccakTypes.InputData) error {
queue := txmgr.NewQueue[int](ctx, p.txMgr, 10)
receiptChs := make([]chan txmgr.TxReceipt[int], len(chunks))
for i, chunk := range chunks {
......
......@@ -9,7 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
......@@ -150,12 +150,12 @@ func TestLargePreimageUploader_UploadPreimage(t *testing.T) {
}
func mockPreimageOracleData() types.PreimageOracleData {
fullLeaf := make([]byte, matrix.LeafSize)
for i := 0; i < matrix.LeafSize; i++ {
fullLeaf := make([]byte, keccakTypes.BlockSize)
for i := 0; i < keccakTypes.BlockSize; i++ {
fullLeaf[i] = byte(i)
}
oracleData := make([]byte, 5*MaxLeafsPerChunk)
for i := 0; i < 5*MaxLeafsPerChunk; i++ {
oracleData := make([]byte, 5*MaxBlocksPerChunk)
for i := 0; i < 5*MaxBlocksPerChunk; i++ {
oracleData = append(oracleData, fullLeaf...)
}
// Add a single byte to the end to make sure the last leaf is not processed.
......@@ -193,7 +193,7 @@ func (s *mockPreimageOracleContract) InitLargePreimage(_ *big.Int, _ uint32, _ u
}
return txmgr.TxCandidate{}, nil
}
func (s *mockPreimageOracleContract) AddLeaves(_ *big.Int, input []byte, _ [][32]byte, _ bool) (txmgr.TxCandidate, error) {
func (s *mockPreimageOracleContract) AddLeaves(_ *big.Int, input []byte, _ []common.Hash, _ bool) (txmgr.TxCandidate, error) {
s.addCalls++
s.addData = append(s.addData, input...)
if s.addFails {
......@@ -201,14 +201,14 @@ func (s *mockPreimageOracleContract) AddLeaves(_ *big.Int, input []byte, _ [][32
}
return txmgr.TxCandidate{}, nil
}
func (s *mockPreimageOracleContract) Squeeze(_ common.Address, _ *big.Int, _ *matrix.StateMatrix, _ contracts.Leaf, _ contracts.MerkleProof, _ contracts.Leaf, _ contracts.MerkleProof) (txmgr.TxCandidate, error) {
func (s *mockPreimageOracleContract) Squeeze(_ common.Address, _ *big.Int, _ *matrix.StateMatrix, _ keccakTypes.Leaf, _ contracts.MerkleProof, _ keccakTypes.Leaf, _ contracts.MerkleProof) (txmgr.TxCandidate, error) {
return txmgr.TxCandidate{}, nil
}
func (s *mockPreimageOracleContract) GetProposalMetadata(_ context.Context, _ batching.Block, idents ...gameTypes.LargePreimageIdent) ([]gameTypes.LargePreimageMetaData, error) {
func (s *mockPreimageOracleContract) GetProposalMetadata(_ context.Context, _ batching.Block, idents ...keccakTypes.LargePreimageIdent) ([]keccakTypes.LargePreimageMetaData, error) {
if s.initialized || s.bytesProcessed > 0 {
metadata := make([]gameTypes.LargePreimageMetaData, 0)
metadata := make([]keccakTypes.LargePreimageMetaData, 0)
for _, ident := range idents {
metadata = append(metadata, gameTypes.LargePreimageMetaData{
metadata = append(metadata, keccakTypes.LargePreimageMetaData{
LargePreimageIdent: ident,
ClaimedSize: s.claimedSize,
BytesProcessed: uint32(s.bytesProcessed),
......@@ -217,5 +217,5 @@ func (s *mockPreimageOracleContract) GetProposalMetadata(_ context.Context, _ ba
}
return metadata, nil
}
return []gameTypes.LargePreimageMetaData{{LargePreimageIdent: idents[0]}}, nil
return []keccakTypes.LargePreimageMetaData{{LargePreimageIdent: idents[0]}}, nil
}
......@@ -8,7 +8,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
......@@ -25,7 +25,7 @@ type PreimageUploader interface {
// PreimageOracleContract is the interface for interacting with the PreimageOracle contract.
type PreimageOracleContract interface {
InitLargePreimage(uuid *big.Int, partOffset uint32, claimedSize uint32) (txmgr.TxCandidate, error)
AddLeaves(uuid *big.Int, input []byte, commitments [][32]byte, finalize bool) (txmgr.TxCandidate, error)
Squeeze(claimant common.Address, uuid *big.Int, stateMatrix *matrix.StateMatrix, preState contracts.Leaf, preStateProof contracts.MerkleProof, postState contracts.Leaf, postStateProof contracts.MerkleProof) (txmgr.TxCandidate, error)
GetProposalMetadata(ctx context.Context, block batching.Block, idents ...gameTypes.LargePreimageIdent) ([]gameTypes.LargePreimageMetaData, error)
AddLeaves(uuid *big.Int, input []byte, commitments []common.Hash, finalize bool) (txmgr.TxCandidate, error)
Squeeze(claimant common.Address, uuid *big.Int, stateMatrix *matrix.StateMatrix, preState keccakTypes.Leaf, preStateProof contracts.MerkleProof, postState keccakTypes.Leaf, postStateProof contracts.MerkleProof) (txmgr.TxCandidate, error)
GetProposalMetadata(ctx context.Context, block batching.Block, idents ...keccakTypes.LargePreimageIdent) ([]keccakTypes.LargePreimageMetaData, error)
}
......@@ -9,6 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
......@@ -26,7 +27,7 @@ var (
type CloseFunc func()
type Registry interface {
RegisterGameType(gameType uint8, creator scheduler.PlayerCreator, oracle types.LargePreimageOracle)
RegisterGameType(gameType uint8, creator scheduler.PlayerCreator, oracle keccakTypes.LargePreimageOracle)
}
func RegisterGameTypes(
......
......@@ -5,6 +5,7 @@ import (
"io"
"math/big"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
......@@ -15,10 +16,10 @@ type StateMatrix struct {
s *state
}
// LeafSize is the size in bytes required for leaf data.
const LeafSize = 136
var uint256Size = 32
var (
ErrInvalidMaxLen = errors.New("invalid max length to absorb")
uint256Size = 32
)
// NewStateMatrix creates a new state matrix initialized with the initial, zero keccak block.
func NewStateMatrix() *StateMatrix {
......@@ -41,13 +42,43 @@ func (d *StateMatrix) PackState() []byte {
return buf
}
// AbsorbNextLeaf reads up to [LeafSize] bytes from in and absorbs them into the state matrix.
func (d *StateMatrix) AbsorbUpTo(in io.Reader, maxLen int) (types.InputData, error) {
if maxLen < types.BlockSize || maxLen%types.BlockSize != 0 {
return types.InputData{}, ErrInvalidMaxLen
}
input := make([]byte, 0, maxLen)
commitments := make([]common.Hash, 0, maxLen/types.BlockSize)
for len(input)+types.BlockSize <= maxLen {
readData, err := d.absorbNextLeafInput(in)
if errors.Is(err, io.EOF) {
input = append(input, readData...)
commitments = append(commitments, d.StateCommitment())
return types.InputData{
Input: input,
Commitments: commitments,
Finalize: true,
}, io.EOF
} else if err != nil {
return types.InputData{}, err
}
input = append(input, readData...)
commitments = append(commitments, d.StateCommitment())
}
return types.InputData{
Input: input,
Commitments: commitments,
Finalize: false,
}, nil
}
// absorbNextLeafInput reads up to [BlockSize] bytes from in and absorbs them into the state matrix.
// If EOF is reached while reading, the state matrix is finalized and [io.EOF] is returned.
func (d *StateMatrix) AbsorbNextLeaf(in io.Reader) ([]byte, error) {
data := make([]byte, LeafSize)
func (d *StateMatrix) absorbNextLeafInput(in io.Reader) ([]byte, error) {
data := make([]byte, types.BlockSize)
read := 0
final := false
for read < LeafSize {
for read < types.BlockSize {
n, err := in.Read(data[read:])
if errors.Is(err, io.EOF) {
final = true
......@@ -57,19 +88,18 @@ func (d *StateMatrix) AbsorbNextLeaf(in io.Reader) ([]byte, error) {
}
read += n
}
leafData := data[:read]
d.AbsorbLeaf(leafData, final)
input := data[:read]
d.absorbLeafInput(input, final)
if final {
return leafData, io.EOF
return input, io.EOF
}
return leafData, nil
return input, nil
}
// AbsorbLeaf absorbs the specified data into the keccak sponge.
// If final is true, the data is padded to the required length, otherwise it must be exactly
// LeafSize bytes.
func (d *StateMatrix) AbsorbLeaf(data []byte, final bool) {
if !final && len(data) != LeafSize {
// absorbLeafInput absorbs the specified data into the keccak sponge.
// If final is true, the data is padded to the required length, otherwise it must be exactly [types.BlockSize] bytes.
func (d *StateMatrix) absorbLeafInput(data []byte, final bool) {
if !final && len(data) != types.BlockSize {
panic("sha3: Incorrect leaf data length")
}
_, _ = d.s.Write(data[:])
......
......@@ -7,8 +7,11 @@ import (
"errors"
"fmt"
"io"
"math/rand"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/testutils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
......@@ -61,13 +64,13 @@ func TestReferenceCommitments(t *testing.T) {
t.Run(fmt.Sprintf("Ref-%v", i), func(t *testing.T) {
s := NewStateMatrix()
commitments := []common.Hash{s.StateCommitment()}
for i := 0; i < len(test.Input); i += LeafSize {
end := min(i+LeafSize, len(test.Input))
s.AbsorbLeaf(test.Input[i:end], end == len(test.Input))
for i := 0; i < len(test.Input); i += types.BlockSize {
end := min(i+types.BlockSize, len(test.Input))
s.absorbLeafInput(test.Input[i:end], end == len(test.Input))
commitments = append(commitments, s.StateCommitment())
}
if len(test.Input) == 0 {
s.AbsorbLeaf(nil, true)
s.absorbLeafInput(nil, true)
commitments = append(commitments, s.StateCommitment())
}
actual := s.Hash()
......@@ -89,7 +92,7 @@ func TestReferenceCommitmentsFromReader(t *testing.T) {
commitments := []common.Hash{s.StateCommitment()}
in := bytes.NewReader(test.Input)
for {
_, err := s.AbsorbNextLeaf(in)
_, err := s.absorbNextLeafInput(in)
if errors.Is(err, io.EOF) {
commitments = append(commitments, s.StateCommitment())
break
......@@ -106,39 +109,95 @@ func TestReferenceCommitmentsFromReader(t *testing.T) {
}
}
func TestAbsorbUpTo_ReferenceCommitments(t *testing.T) {
var tests []testData
require.NoError(t, json.Unmarshal(refTests, &tests))
for i, test := range tests {
test := test
t.Run(fmt.Sprintf("Ref-%v", i), func(t *testing.T) {
s := NewStateMatrix()
commitments := []common.Hash{s.StateCommitment()}
in := bytes.NewReader(test.Input)
for {
input, err := s.AbsorbUpTo(in, types.BlockSize*3)
if errors.Is(err, io.EOF) {
commitments = append(commitments, input.Commitments...)
break
}
// Shouldn't get any error except EOF
require.NoError(t, err)
commitments = append(commitments, input.Commitments...)
}
actual := s.Hash()
expected := crypto.Keccak256Hash(test.Input)
require.Equal(t, expected, actual)
require.Equal(t, test.Commitments, commitments)
})
}
}
func TestAbsorbUpTo_LimitsDataRead(t *testing.T) {
s := NewStateMatrix()
data := testutils.RandomData(rand.New(rand.NewSource(2424)), types.BlockSize*6+20)
in := bytes.NewReader(data)
// Should fully read the first four leaves worth
inputData, err := s.AbsorbUpTo(in, types.BlockSize*4)
require.NoError(t, err)
require.Equal(t, data[0:types.BlockSize*4], inputData.Input)
require.Len(t, inputData.Commitments, 4)
require.False(t, inputData.Finalize)
// Should read the remaining data and return EOF
inputData, err = s.AbsorbUpTo(in, types.BlockSize*10)
require.ErrorIs(t, err, io.EOF)
require.Equal(t, data[types.BlockSize*4:], inputData.Input)
require.Len(t, inputData.Commitments, 3, "2 full leaves plus the final partial leaf")
require.True(t, inputData.Finalize)
}
func TestAbsorbUpTo_InvalidLengths(t *testing.T) {
s := NewStateMatrix()
lengths := []int{-types.BlockSize, -1, 0, 1, types.BlockSize - 1, types.BlockSize + 1, 2*types.BlockSize + 1}
for _, length := range lengths {
_, err := s.AbsorbUpTo(bytes.NewReader(nil), length)
require.ErrorIsf(t, err, ErrInvalidMaxLen, "Should get invalid length for length %v", length)
}
}
func TestMatrix_AbsorbNextLeaf(t *testing.T) {
fullLeaf := make([]byte, LeafSize)
for i := 0; i < LeafSize; i++ {
fullLeaf := make([]byte, types.BlockSize)
for i := 0; i < types.BlockSize; i++ {
fullLeaf[i] = byte(i)
}
tests := []struct {
name string
input []byte
leafs [][]byte
leafInputs [][]byte
errs []error
}{
{
name: "empty",
input: []byte{},
leafs: [][]byte{{}},
leafInputs: [][]byte{{}},
errs: []error{io.EOF},
},
{
name: "single",
input: fullLeaf,
leafs: [][]byte{fullLeaf},
leafInputs: [][]byte{fullLeaf},
errs: []error{io.EOF},
},
{
name: "single-overflow",
input: append(fullLeaf, byte(9)),
leafs: [][]byte{fullLeaf, {byte(9)}},
leafInputs: [][]byte{fullLeaf, {byte(9)}},
errs: []error{nil, io.EOF},
},
{
name: "double",
input: append(fullLeaf, fullLeaf...),
leafs: [][]byte{fullLeaf, fullLeaf},
leafInputs: [][]byte{fullLeaf, fullLeaf},
errs: []error{nil, io.EOF},
},
}
......@@ -148,8 +207,8 @@ func TestMatrix_AbsorbNextLeaf(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
state := NewStateMatrix()
in := bytes.NewReader(test.input)
for i, leaf := range test.leafs {
buf, err := state.AbsorbNextLeaf(in)
for i, leaf := range test.leafInputs {
buf, err := state.absorbNextLeafInput(in)
if errors.Is(err, io.EOF) {
require.Equal(t, test.errs[i], err)
break
......@@ -164,9 +223,9 @@ func TestMatrix_AbsorbNextLeaf(t *testing.T) {
func FuzzKeccak(f *testing.F) {
f.Fuzz(func(t *testing.T, number, time uint64, data []byte) {
s := NewStateMatrix()
for i := 0; i < len(data); i += LeafSize {
end := min(i+LeafSize, len(data))
s.AbsorbLeaf(data[i:end], end == len(data))
for i := 0; i < len(data); i += types.BlockSize {
end := min(i+types.BlockSize, len(data))
s.absorbLeafInput(data[i:end], end == len(data))
}
actual := s.Hash()
expected := crypto.Keccak256Hash(data)
......
......@@ -4,25 +4,25 @@ import (
"context"
"sync"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
type Verifier interface {
Verify(ctx context.Context, oracle types.LargePreimageOracle, preimage types.LargePreimageMetaData)
Verify(ctx context.Context, oracle keccakTypes.LargePreimageOracle, preimage keccakTypes.LargePreimageMetaData)
}
type LargePreimageScheduler struct {
log log.Logger
ch chan common.Hash
oracles []types.LargePreimageOracle
oracles []keccakTypes.LargePreimageOracle
verifier Verifier
cancel func()
wg sync.WaitGroup
}
func NewLargePreimageScheduler(logger log.Logger, oracles []types.LargePreimageOracle, verifier Verifier) *LargePreimageScheduler {
func NewLargePreimageScheduler(logger log.Logger, oracles []keccakTypes.LargePreimageOracle, verifier Verifier) *LargePreimageScheduler {
return &LargePreimageScheduler{
log: logger,
ch: make(chan common.Hash, 1),
......@@ -76,7 +76,7 @@ func (s *LargePreimageScheduler) verifyPreimages(ctx context.Context, blockHash
return nil
}
func (s *LargePreimageScheduler) verifyOraclePreimages(ctx context.Context, oracle types.LargePreimageOracle, blockHash common.Hash) error {
func (s *LargePreimageScheduler) verifyOraclePreimages(ctx context.Context, oracle keccakTypes.LargePreimageOracle, blockHash common.Hash) error {
preimages, err := oracle.GetActivePreimages(ctx, blockHash)
for _, preimage := range preimages {
if preimage.ShouldVerify() {
......
......@@ -7,7 +7,7 @@ import (
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
......@@ -17,32 +17,32 @@ import (
func TestScheduleNextCheck(t *testing.T) {
ctx := context.Background()
logger := testlog.Logger(t, log.LvlInfo)
preimage1 := types.LargePreimageMetaData{ // Incomplete so won't be verified
LargePreimageIdent: types.LargePreimageIdent{
preimage1 := keccakTypes.LargePreimageMetaData{ // Incomplete so won't be verified
LargePreimageIdent: keccakTypes.LargePreimageIdent{
Claimant: common.Address{0xab},
UUID: big.NewInt(111),
},
}
preimage2 := types.LargePreimageMetaData{ // Already countered so won't be verified
LargePreimageIdent: types.LargePreimageIdent{
preimage2 := keccakTypes.LargePreimageMetaData{ // Already countered so won't be verified
LargePreimageIdent: keccakTypes.LargePreimageIdent{
Claimant: common.Address{0xab},
UUID: big.NewInt(222),
},
Timestamp: 1234,
Countered: true,
}
preimage3 := types.LargePreimageMetaData{
LargePreimageIdent: types.LargePreimageIdent{
preimage3 := keccakTypes.LargePreimageMetaData{
LargePreimageIdent: keccakTypes.LargePreimageIdent{
Claimant: common.Address{0xdd},
UUID: big.NewInt(333),
},
Timestamp: 1234,
}
oracle := &stubOracle{
images: []types.LargePreimageMetaData{preimage1, preimage2, preimage3},
images: []keccakTypes.LargePreimageMetaData{preimage1, preimage2, preimage3},
}
verifier := &stubVerifier{}
scheduler := NewLargePreimageScheduler(logger, []types.LargePreimageOracle{oracle}, verifier)
scheduler := NewLargePreimageScheduler(logger, []keccakTypes.LargePreimageOracle{oracle}, verifier)
scheduler.Start(ctx)
defer scheduler.Close()
err := scheduler.Schedule(common.Hash{0xaa}, 3)
......@@ -61,14 +61,14 @@ type stubOracle struct {
m sync.Mutex
addr common.Address
getPreimagesCount int
images []types.LargePreimageMetaData
images []keccakTypes.LargePreimageMetaData
}
func (s *stubOracle) Addr() common.Address {
return s.addr
}
func (s *stubOracle) GetActivePreimages(_ context.Context, _ common.Hash) ([]types.LargePreimageMetaData, error) {
func (s *stubOracle) GetActivePreimages(_ context.Context, _ common.Hash) ([]keccakTypes.LargePreimageMetaData, error) {
s.m.Lock()
defer s.m.Unlock()
s.getPreimagesCount++
......@@ -83,19 +83,19 @@ func (s *stubOracle) GetPreimagesCount() int {
type stubVerifier struct {
m sync.Mutex
verified []types.LargePreimageMetaData
verified []keccakTypes.LargePreimageMetaData
}
func (s *stubVerifier) Verify(_ context.Context, _ types.LargePreimageOracle, image types.LargePreimageMetaData) {
func (s *stubVerifier) Verify(_ context.Context, _ keccakTypes.LargePreimageOracle, image keccakTypes.LargePreimageMetaData) {
s.m.Lock()
defer s.m.Unlock()
s.verified = append(s.verified, image)
}
func (s *stubVerifier) Verified() []types.LargePreimageMetaData {
func (s *stubVerifier) Verified() []keccakTypes.LargePreimageMetaData {
s.m.Lock()
defer s.m.Unlock()
v := make([]types.LargePreimageMetaData, len(s.verified))
v := make([]keccakTypes.LargePreimageMetaData, len(s.verified))
copy(v, s.verified)
return v
}
package types
import (
"context"
"math/big"
"github.com/ethereum/go-ethereum/common"
)
// BlockSize is the size in bytes required for leaf data.
const BlockSize = 136
// Leaf is the keccak state matrix added to the large preimage merkle tree.
type Leaf struct {
// Input is the data absorbed for the block, exactly 136 bytes
Input [BlockSize]byte
// Index of the block in the absorption process
Index *big.Int
// StateCommitment is the hash of the internal state after absorbing the input.
StateCommitment common.Hash
}
// InputData is a contiguous segment of preimage data.
type InputData struct {
// Input is the preimage data.
// When Finalize is false, len(Input) must equal len(Commitments)*BlockSize
// When Finalize is true, len(Input) must be between len(Commitments - 1)*BlockSize and len(Commitments)*BlockSize
Input []byte
// Commitments are the keccak commitments for each leaf in the chunk.
Commitments []common.Hash
// Finalize indicates whether the chunk is the final chunk.
Finalize bool
}
type LargePreimageIdent struct {
Claimant common.Address
UUID *big.Int
}
type LargePreimageMetaData struct {
LargePreimageIdent
// Timestamp is the time at which the proposal first became fully available.
// 0 when not all data is available yet
Timestamp uint64
PartOffset uint32
ClaimedSize uint32
BlocksProcessed uint32
BytesProcessed uint32
Countered bool
}
// ShouldVerify returns true if the preimage upload is complete and has not yet been countered.
// Note that the challenge period for the preimage may have expired but the image not yet been finalized.
func (m LargePreimageMetaData) ShouldVerify() bool {
return m.Timestamp > 0 && !m.Countered
}
type LargePreimageOracle interface {
Addr() common.Address
GetActivePreimages(ctx context.Context, blockHash common.Hash) ([]LargePreimageMetaData, error)
}
......@@ -3,7 +3,7 @@ package keccak
import (
"context"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum/go-ethereum/log"
)
......@@ -17,6 +17,6 @@ func NewPreimageVerifier(logger log.Logger) *PreimageVerifier {
}
}
func (v *PreimageVerifier) Verify(ctx context.Context, oracle types.LargePreimageOracle, preimage types.LargePreimageMetaData) {
func (v *PreimageVerifier) Verify(ctx context.Context, oracle keccakTypes.LargePreimageOracle, preimage keccakTypes.LargePreimageMetaData) {
// No verification currently performed.
}
......@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum/go-ethereum/common"
......@@ -16,19 +17,19 @@ var (
type GameTypeRegistry struct {
types map[uint8]scheduler.PlayerCreator
oracles map[common.Address]types.LargePreimageOracle
oracles map[common.Address]keccakTypes.LargePreimageOracle
}
func NewGameTypeRegistry() *GameTypeRegistry {
return &GameTypeRegistry{
types: make(map[uint8]scheduler.PlayerCreator),
oracles: make(map[common.Address]types.LargePreimageOracle),
oracles: make(map[common.Address]keccakTypes.LargePreimageOracle),
}
}
// RegisterGameType registers a scheduler.PlayerCreator to use for a specific game type.
// Panics if the same game type is registered multiple times, since this indicates a significant programmer error.
func (r *GameTypeRegistry) RegisterGameType(gameType uint8, creator scheduler.PlayerCreator, oracle types.LargePreimageOracle) {
func (r *GameTypeRegistry) RegisterGameType(gameType uint8, creator scheduler.PlayerCreator, oracle keccakTypes.LargePreimageOracle) {
if _, ok := r.types[gameType]; ok {
panic(fmt.Errorf("duplicate creator registered for game type: %v", gameType))
}
......@@ -49,6 +50,6 @@ func (r *GameTypeRegistry) CreatePlayer(game types.GameMetadata, dir string) (sc
return creator(game, dir)
}
func (r *GameTypeRegistry) Oracles() []types.LargePreimageOracle {
func (r *GameTypeRegistry) Oracles() []keccakTypes.LargePreimageOracle {
return maps.Values(r.oracles)
}
......@@ -4,6 +4,7 @@ import (
"context"
"testing"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler/test"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
......@@ -63,6 +64,6 @@ func (s stubPreimageOracle) Addr() common.Address {
return common.Address(s)
}
func (s stubPreimageOracle) GetActivePreimages(_ context.Context, _ common.Hash) ([]types.LargePreimageMetaData, error) {
func (s stubPreimageOracle) GetActivePreimages(_ context.Context, _ common.Hash) ([]keccakTypes.LargePreimageMetaData, error) {
return nil, nil
}
package types
import (
"context"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
)
......@@ -43,32 +41,3 @@ type GameMetadata struct {
Timestamp uint64
Proxy common.Address
}
type LargePreimageIdent struct {
Claimant common.Address
UUID *big.Int
}
type LargePreimageMetaData struct {
LargePreimageIdent
// Timestamp is the time at which the proposal first became fully available.
// 0 when not all data is available yet
Timestamp uint64
PartOffset uint32
ClaimedSize uint32
BlocksProcessed uint32
BytesProcessed uint32
Countered bool
}
// ShouldVerify returns true if the preimage upload is complete and has not yet been countered.
// Note that the challenge period for the preimage may have expired but the image not yet been finalized.
func (m LargePreimageMetaData) ShouldVerify() bool {
return m.Timestamp > 0 && !m.Countered
}
type LargePreimageOracle interface {
Addr() common.Address
GetActivePreimages(ctx context.Context, blockHash common.Hash) ([]LargePreimageMetaData, error)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment