Commit a0e47e5a authored by protolambda's avatar protolambda Committed by GitHub

FP: add KZG point-evaluation preimage-oracle type (#9461)

* FP: add KZG point-evaluation preimage-oracle type, to process L2 oracle call with

* op-program: Integrate kzg precompile oracle

* fix PreimageOracle.sol

* store kzg precompile input preimage

* update bindings and go.mod

* fix go.mod

* resolve TODOs

* s/Hex2Bytes/FromHex

* fix typo

* ctb: set kzg preimage length to 1

* op-challenger: Load kzg point evaluations to PreimageOracle (#9497)

* op-challenger: load KZG point evaluation into oracle

Also: increase max game depth for devnet.
This is a temporary solution to avoid flakes in e2e tests that use the kzg precompile.
The current execution trace max step of 2**31 steps isn't sufficient.

---------
Co-authored-by: default avatarinphi <mlaw2501@gmail.com>
Co-authored-by: default avatarrefcell <abigger87@gmail.com>
parent b5e56690
...@@ -257,6 +257,8 @@ func Run(ctx *cli.Context) error { ...@@ -257,6 +257,8 @@ func Run(ctx *cli.Context) error {
stopAtPreimageTypeByte = preimage.Sha256KeyType stopAtPreimageTypeByte = preimage.Sha256KeyType
case "blob": case "blob":
stopAtPreimageTypeByte = preimage.BlobKeyType stopAtPreimageTypeByte = preimage.BlobKeyType
case "kzg-point-evaluation":
stopAtPreimageTypeByte = preimage.KZGPointEvaluationKeyType
case "any": case "any":
stopAtAnyPreimage = true stopAtAnyPreimage = true
case "": case "":
......
This diff is collapsed.
This diff is collapsed.
...@@ -20,23 +20,24 @@ import ( ...@@ -20,23 +20,24 @@ import (
) )
const ( const (
methodInitLPP = "initLPP" methodInitLPP = "initLPP"
methodAddLeavesLPP = "addLeavesLPP" methodAddLeavesLPP = "addLeavesLPP"
methodSqueezeLPP = "squeezeLPP" methodSqueezeLPP = "squeezeLPP"
methodLoadKeccak256PreimagePart = "loadKeccak256PreimagePart" methodLoadKeccak256PreimagePart = "loadKeccak256PreimagePart"
methodLoadSha256PreimagePart = "loadSha256PreimagePart" methodLoadSha256PreimagePart = "loadSha256PreimagePart"
methodLoadBlobPreimagePart = "loadBlobPreimagePart" methodLoadBlobPreimagePart = "loadBlobPreimagePart"
methodProposalCount = "proposalCount" methodLoadKZGPointEvaluationPreimage = "loadKZGPointEvaluationPreimage"
methodProposals = "proposals" methodProposalCount = "proposalCount"
methodProposalMetadata = "proposalMetadata" methodProposals = "proposals"
methodProposalBlocksLen = "proposalBlocksLen" methodProposalMetadata = "proposalMetadata"
methodProposalBlocks = "proposalBlocks" methodProposalBlocksLen = "proposalBlocksLen"
methodPreimagePartOk = "preimagePartOk" methodProposalBlocks = "proposalBlocks"
methodMinProposalSize = "minProposalSize" methodPreimagePartOk = "preimagePartOk"
methodChallengeFirstLPP = "challengeFirstLPP" methodMinProposalSize = "minProposalSize"
methodChallengeLPP = "challengeLPP" methodChallengeFirstLPP = "challengeFirstLPP"
methodChallengePeriod = "challengePeriod" methodChallengeLPP = "challengeLPP"
methodGetTreeRootLPP = "getTreeRootLPP" methodChallengePeriod = "challengePeriod"
methodGetTreeRootLPP = "getTreeRootLPP"
) )
var ( var (
...@@ -102,6 +103,9 @@ func (c *PreimageOracleContract) AddGlobalDataTx(data *types.PreimageOracleData) ...@@ -102,6 +103,9 @@ func (c *PreimageOracleContract) AddGlobalDataTx(data *types.PreimageOracleData)
data.BlobProof, data.BlobProof,
new(big.Int).SetUint64(uint64(data.OracleOffset))) new(big.Int).SetUint64(uint64(data.OracleOffset)))
return call.ToTxCandidate() return call.ToTxCandidate()
case preimage.KZGPointEvaluationKeyType:
call := c.contract.Call(methodLoadKZGPointEvaluationPreimage, data.GetPreimageWithoutSize())
return call.ToTxCandidate()
default: default:
return txmgr.TxCandidate{}, fmt.Errorf("%w: %v", ErrUnsupportedKeyType, keyType) return txmgr.TxCandidate{}, fmt.Errorf("%w: %v", ErrUnsupportedKeyType, keyType)
} }
......
...@@ -70,6 +70,19 @@ func TestPreimageOracleContract_AddGlobalDataTx(t *testing.T) { ...@@ -70,6 +70,19 @@ func TestPreimageOracleContract_AddGlobalDataTx(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx) stubRpc.VerifyTxCandidate(tx)
}) })
t.Run("KZGPointEvaluation", func(t *testing.T) {
stubRpc, oracle := setupPreimageOracleTest(t)
input := testutils.RandomData(rand.New(rand.NewSource(23)), 200)
data := types.NewPreimageOracleKZGPointEvaluationData(common.Hash{byte(preimage.KZGPointEvaluationKeyType), 0xcc}.Bytes(), input)
stubRpc.SetResponse(oracleAddr, methodLoadKZGPointEvaluationPreimage, batching.BlockLatest, []interface{}{
data.GetPreimageWithoutSize(),
}, nil)
tx, err := oracle.AddGlobalDataTx(data)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
})
} }
func TestPreimageOracleContract_ChallengePeriod(t *testing.T) { func TestPreimageOracleContract_ChallengePeriod(t *testing.T) {
......
...@@ -33,7 +33,7 @@ func (d *DirectPreimageUploader) UploadPreimage(ctx context.Context, claimIdx ui ...@@ -33,7 +33,7 @@ func (d *DirectPreimageUploader) UploadPreimage(ctx context.Context, claimIdx ui
if data == nil { if data == nil {
return ErrNilPreimageData return ErrNilPreimageData
} }
d.log.Info("Updating oracle data", "key", data.OracleKey) d.log.Info("Updating oracle data", "key", fmt.Sprintf("%x", data.OracleKey))
candidate, err := d.contract.UpdateOracleTx(ctx, claimIdx, data) candidate, err := d.contract.UpdateOracleTx(ctx, claimIdx, data)
if err != nil { if err != nil {
return fmt.Errorf("failed to create pre-image oracle tx: %w", err) return fmt.Errorf("failed to create pre-image oracle tx: %w", err)
......
...@@ -46,6 +46,8 @@ func (l *preimageLoader) LoadPreimage(proof *proofData) (*types.PreimageOracleDa ...@@ -46,6 +46,8 @@ func (l *preimageLoader) LoadPreimage(proof *proofData) (*types.PreimageOracleDa
switch preimage.KeyType(proof.OracleKey[0]) { switch preimage.KeyType(proof.OracleKey[0]) {
case preimage.BlobKeyType: case preimage.BlobKeyType:
return l.loadBlobPreimage(proof) return l.loadBlobPreimage(proof)
case preimage.KZGPointEvaluationKeyType:
return l.loadKZGPointEvaluationPreimage(proof)
default: default:
return types.NewPreimageOracleData(proof.OracleKey, proof.OracleValue, proof.OracleOffset), nil return types.NewPreimageOracleData(proof.OracleKey, proof.OracleValue, proof.OracleOffset), nil
} }
...@@ -96,8 +98,23 @@ func (l *preimageLoader) loadBlobPreimage(proof *proofData) (*types.PreimageOrac ...@@ -96,8 +98,23 @@ func (l *preimageLoader) loadBlobPreimage(proof *proofData) (*types.PreimageOrac
return nil, fmt.Errorf("failed to verify proof: %w", err) return nil, fmt.Errorf("failed to verify proof: %w", err)
} }
claimWithLength := make([]byte, len(claim)+lengthPrefixSize) claimWithLength := lengthPrefixed(claim[:])
binary.BigEndian.PutUint64(claimWithLength[:lengthPrefixSize], uint64(len(claim)))
copy(claimWithLength[lengthPrefixSize:], claim[:])
return types.NewPreimageOracleBlobData(proof.OracleKey, claimWithLength, proof.OracleOffset, requiredFieldElement, commitment, kzgProof[:]), nil return types.NewPreimageOracleBlobData(proof.OracleKey, claimWithLength, proof.OracleOffset, requiredFieldElement, commitment, kzgProof[:]), nil
} }
func (l *preimageLoader) loadKZGPointEvaluationPreimage(proof *proofData) (*types.PreimageOracleData, error) {
inputKey := preimage.Keccak256Key(proof.OracleKey).PreimageKey()
input, err := l.getPreimage(inputKey)
if err != nil {
return nil, fmt.Errorf("failed to get key preimage: %w", err)
}
inputWithLength := lengthPrefixed(input)
return types.NewPreimageOracleKZGPointEvaluationData(proof.OracleKey, inputWithLength), nil
}
func lengthPrefixed(data []byte) []byte {
dataWithLength := make([]byte, len(data)+lengthPrefixSize)
binary.BigEndian.PutUint64(dataWithLength[:lengthPrefixSize], uint64(len(data)))
copy(dataWithLength[lengthPrefixSize:], data)
return dataWithLength
}
...@@ -152,6 +152,31 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) { ...@@ -152,6 +152,31 @@ func TestPreimageLoader_BlobPreimage(t *testing.T) {
}) })
} }
func TestPreimageLoader_KZGPointEvaluationPreimage(t *testing.T) {
input := []byte("test input")
key := preimage.KZGPointEvaluationKey(crypto.Keccak256Hash(input)).PreimageKey()
proof := &proofData{
OracleKey: key[:],
}
t.Run("NoInputPreimage", func(t *testing.T) {
kv := kvstore.NewMemKV()
loader := newPreimageLoader(kv.Get)
_, err := loader.LoadPreimage(proof)
require.ErrorIs(t, err, kvstore.ErrNotFound)
})
t.Run("Valid", func(t *testing.T) {
kv := kvstore.NewMemKV()
loader := newPreimageLoader(kv.Get)
require.NoError(t, kv.Put(preimage.Keccak256Key(proof.OracleKey).PreimageKey(), input))
actual, err := loader.LoadPreimage(proof)
require.NoError(t, err)
inputWithLength := lengthPrefixed(input)
expected := types.NewPreimageOracleKZGPointEvaluationData(proof.OracleKey, inputWithLength)
require.Equal(t, expected, actual)
})
}
// Returns a serialized random field element in big-endian // Returns a serialized random field element in big-endian
func fieldElement(val uint64) [32]byte { func fieldElement(val uint64) [32]byte {
r := fr.NewElement(val) r := fr.NewElement(val)
......
...@@ -265,6 +265,10 @@ func FirstKeccakPreimageLoad() PreimageOpt { ...@@ -265,6 +265,10 @@ func FirstKeccakPreimageLoad() PreimageOpt {
return FirstPreimageLoadOfType("keccak") return FirstPreimageLoadOfType("keccak")
} }
func FirstKZGPointEvaluationPreimageLoad() PreimageOpt {
return FirstPreimageLoadOfType("kzg-point-evaluation")
}
func PreimageLargerThan(size int) PreimageOpt { func PreimageLargerThan(size int) PreimageOpt {
return func() preimageOpts { return func() preimageOpts {
return []string{"--stop-at-preimage-larger-than", strconv.Itoa(size)} return []string{"--stop-at-preimage-larger-than", strconv.Itoa(size)}
......
...@@ -79,6 +79,15 @@ func NewPreimageOracleBlobData(key []byte, data []byte, offset uint32, fieldInde ...@@ -79,6 +79,15 @@ func NewPreimageOracleBlobData(key []byte, data []byte, offset uint32, fieldInde
} }
} }
func NewPreimageOracleKZGPointEvaluationData(key []byte, input []byte) *PreimageOracleData {
return &PreimageOracleData{
IsLocal: false,
OracleKey: key,
oracleData: input,
OracleOffset: 0,
}
}
// StepCallData encapsulates the data needed to perform a step. // StepCallData encapsulates the data needed to perform a step.
type StepCallData struct { type StepCallData struct {
ClaimIndex uint64 ClaimIndex uint64
......
...@@ -15,6 +15,7 @@ import ( ...@@ -15,6 +15,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types" keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -558,7 +559,13 @@ func (g *OutputGameHelper) uploadPreimage(ctx context.Context, data *types.Preim ...@@ -558,7 +559,13 @@ func (g *OutputGameHelper) uploadPreimage(ctx context.Context, data *types.Preim
oracle := g.oracle(ctx) oracle := g.oracle(ctx)
boundOracle, err := bindings.NewPreimageOracle(oracle.Addr(), g.client) boundOracle, err := bindings.NewPreimageOracle(oracle.Addr(), g.client)
g.require.NoError(err) g.require.NoError(err)
tx, err := boundOracle.LoadKeccak256PreimagePart(g.opts, new(big.Int).SetUint64(uint64(data.OracleOffset)), data.GetPreimageWithoutSize()) var tx *gethtypes.Transaction
switch data.OracleKey[0] {
case byte(preimage.KZGPointEvaluationKeyType):
tx, err = boundOracle.LoadKZGPointEvaluationPreimage(g.opts, data.GetPreimageWithoutSize())
default:
tx, err = boundOracle.LoadKeccak256PreimagePart(g.opts, new(big.Int).SetUint64(uint64(data.OracleOffset)), data.GetPreimageWithoutSize())
}
g.require.NoError(err, "Failed to load preimage part") g.require.NoError(err, "Failed to load preimage part")
_, err = wait.ForReceiptOK(ctx, g.client, tx.Hash()) _, err = wait.ForReceiptOK(ctx, g.client, tx.Hash())
g.require.NoError(err) g.require.NoError(err)
......
...@@ -84,6 +84,17 @@ func ForUnsafeBlock(ctx context.Context, rollupCl *sources.RollupClient, n uint6 ...@@ -84,6 +84,17 @@ func ForUnsafeBlock(ctx context.Context, rollupCl *sources.RollupClient, n uint6
return err return err
} }
func ForSafeBlock(ctx context.Context, rollupClient *sources.RollupClient, n uint64) error {
ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
defer cancel()
_, err := AndGet(ctx, time.Second, func() (*eth.SyncStatus, error) {
return rollupClient.SyncStatus(ctx)
}, func(syncStatus *eth.SyncStatus) bool {
return syncStatus.SafeL2.Number >= n
})
return err
}
func ForNextSafeBlock(ctx context.Context, client BlockCaller) (*types.Block, error) { func ForNextSafeBlock(ctx context.Context, client BlockCaller) (*types.Block, error) {
safeBlockNumber := big.NewInt(rpc.SafeBlockNumber.Int64()) safeBlockNumber := big.NewInt(rpc.SafeBlockNumber.Int64())
current, err := client.BlockByNumber(ctx, safeBlockNumber) current, err := client.BlockByNumber(ctx, safeBlockNumber)
......
...@@ -294,6 +294,46 @@ func TestOutputCannonStepWithPreimage(t *testing.T) { ...@@ -294,6 +294,46 @@ func TestOutputCannonStepWithPreimage(t *testing.T) {
}) })
} }
func TestOutputCannonStepWithKZGPointEvaluation(t *testing.T) {
testPreimageStep := func(t *testing.T, preloadPreimage bool) {
op_e2e.InitParallel(t, op_e2e.UsesCannon)
ctx := context.Background()
sys, _ := startFaultDisputeSystem(t, withEcotone())
t.Cleanup(sys.Close)
receipt := sendKZGPointEvaluationTx(t, sys, "sequencer", sys.Cfg.Secrets.Alice)
precompileBlock := receipt.BlockNumber
t.Logf("KZG Point Evaluation block number: %d", precompileBlock)
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", precompileBlock.Uint64(), common.Hash{0x01, 0xaa})
require.NotNil(t, game)
outputRootClaim := game.DisputeLastBlock(ctx)
game.LogGameData(ctx)
game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice))
// Wait for the honest challenger to dispute the outputRootClaim. This creates a root of an execution game that we challenge by coercing
// a step at a preimage trace index.
outputRootClaim = outputRootClaim.WaitForCounterClaim(ctx)
// Now the honest challenger is positioned as the defender of the execution game
// We then move to challenge it to induce a preimage load
preimageLoadCheck := game.CreateStepPreimageLoadCheck(ctx)
game.ChallengeToPreimageLoad(ctx, outputRootClaim, sys.Cfg.Secrets.Alice, cannon.FirstKZGPointEvaluationPreimageLoad(), preimageLoadCheck, preloadPreimage)
// The above method already verified the image was uploaded and step called successfully
// So we don't waste time resolving the game - that's tested elsewhere.
}
t.Run("non-existing preimage", func(t *testing.T) {
testPreimageStep(t, false)
})
t.Run("preimage already exists", func(t *testing.T) {
testPreimageStep(t, true)
})
}
func TestOutputCannonProposedOutputRootValid(t *testing.T) { func TestOutputCannonProposedOutputRootValid(t *testing.T) {
// honestStepsFail attempts to perform both an attack and defend step using the correct trace. // honestStepsFail attempts to perform both an attack and defend step using the correct trace.
honestStepsFail := func(ctx context.Context, game *disputegame.OutputCannonGameHelper, correctTrace *disputegame.OutputHonestHelper, parentClaimIdx int64) { honestStepsFail := func(ctx context.Context, game *disputegame.OutputCannonGameHelper, correctTrace *disputegame.OutputHonestHelper, parentClaimIdx int64) {
......
package faultproofs
import (
"context"
"encoding/json"
"fmt"
"math"
"path/filepath"
"testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
)
func TestPrecompiles(t *testing.T) {
// precompile test vectors copied from go-ethereum
tests := []struct {
name string
address common.Address
input []byte
}{
{
name: "ecrecover",
address: common.BytesToAddress([]byte{0x01}),
input: common.FromHex("18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549"),
},
{
name: "sha256",
address: common.BytesToAddress([]byte{0x02}),
input: common.FromHex("68656c6c6f20776f726c64"),
},
{
name: "ripemd160",
address: common.BytesToAddress([]byte{0x03}),
input: common.FromHex("68656c6c6f20776f726c64"),
},
{
name: "bn256Pairing",
address: common.BytesToAddress([]byte{0x08}),
input: common.FromHex("1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f593034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf704bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a416782bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa"),
},
{
name: "blake2F",
address: common.BytesToAddress([]byte{0x09}),
input: common.FromHex("0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"),
},
{
name: "kzgPointEvaluation",
address: common.BytesToAddress([]byte{0x0a}),
input: common.FromHex("01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a"),
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon)
ctx := context.Background()
cfg := op_e2e.DefaultSystemConfig(t)
// We don't need a verifier - just the sequencer is enough
delete(cfg.Nodes, "verifier")
// Use a small sequencer window size to avoid test timeout while waiting for empty blocks
// But not too small to ensure that our claim and subsequent state change is published
cfg.DeployConfig.SequencerWindowSize = 16
minTs := hexutil.Uint64(0)
cfg.DeployConfig.L2GenesisDeltaTimeOffset = &minTs
cfg.DeployConfig.L2GenesisEcotoneTimeOffset = &minTs
sys, err := cfg.Start(t)
require.Nil(t, err, "Error starting up system")
defer sys.Close()
log := testlog.Logger(t, log.LevelInfo)
log.Info("genesis", "l2", sys.RollupConfig.Genesis.L2, "l1", sys.RollupConfig.Genesis.L1, "l2_time", sys.RollupConfig.Genesis.L2Time)
l1Client := sys.Clients["l1"]
l2Seq := sys.Clients["sequencer"]
rollupRPCClient, err := rpc.DialContext(context.Background(), sys.RollupNodes["sequencer"].HTTPEndpoint())
require.Nil(t, err)
rollupClient := sources.NewRollupClient(client.NewBaseRPCClient(rollupRPCClient))
aliceKey := cfg.Secrets.Alice
t.Log("Capture current L2 head as agreed starting point")
latestBlock, err := l2Seq.BlockByNumber(ctx, nil)
require.NoError(t, err)
agreedL2Output, err := rollupClient.OutputAtBlock(ctx, latestBlock.NumberU64())
require.NoError(t, err, "could not retrieve l2 agreed block")
l2Head := agreedL2Output.BlockRef.Hash
l2OutputRoot := agreedL2Output.OutputRoot
receipt := op_e2e.SendL2Tx(t, cfg, l2Seq, aliceKey, func(opts *op_e2e.TxOpts) {
opts.Gas = 1_000_000
opts.ToAddr = &test.address
opts.Nonce = 0
opts.Data = test.input
})
t.Log("Determine L2 claim")
l2ClaimBlockNumber := receipt.BlockNumber
l2Output, err := rollupClient.OutputAtBlock(ctx, l2ClaimBlockNumber.Uint64())
require.NoError(t, err, "could not get expected output")
l2Claim := l2Output.OutputRoot
t.Log("Determine L1 head that includes all batches required for L2 claim block")
require.NoError(t, wait.ForSafeBlock(ctx, rollupClient, l2ClaimBlockNumber.Uint64()))
l1HeadBlock, err := l1Client.BlockByNumber(ctx, nil)
require.NoError(t, err, "get l1 head block")
l1Head := l1HeadBlock.Hash()
inputs := cannon.LocalGameInputs{
L1Head: l1Head,
L2Head: l2Head,
L2Claim: common.Hash(l2Claim),
L2OutputRoot: common.Hash(l2OutputRoot),
L2BlockNumber: l2ClaimBlockNumber,
}
runCannon(t, ctx, sys, inputs, "sequencer")
})
}
}
func runCannon(t *testing.T, ctx context.Context, sys *op_e2e.System, inputs cannon.LocalGameInputs, l2Node string) {
l1Endpoint := sys.NodeEndpoint("l1")
l1Beacon := sys.L1BeaconEndpoint()
cannonOpts := challenger.WithCannon(t, sys.RollupCfg(), sys.L2Genesis(), sys.RollupEndpoint(l2Node), sys.NodeEndpoint(l2Node))
dir := t.TempDir()
proofsDir := filepath.Join(dir, "cannon-proofs")
cfg := config.NewConfig(common.Address{}, l1Endpoint, l1Beacon, dir)
cannonOpts(&cfg)
logger := testlog.Logger(t, log.LevelInfo).New("role", "cannon")
executor := cannon.NewExecutor(logger, metrics.NoopMetrics, &cfg, inputs)
t.Log("Running cannon")
err := executor.GenerateProof(ctx, proofsDir, math.MaxUint)
require.NoError(t, err, "failed to generate proof")
state, err := parseState(filepath.Join(proofsDir, "final.json.gz"))
require.NoError(t, err, "failed to parse state")
t.Logf("Completed in %d steps", state.Step)
}
func parseState(path string) (*mipsevm.State, error) {
file, err := ioutil.OpenDecompressed(path)
if err != nil {
return nil, fmt.Errorf("cannot open state file (%v): %w", path, err)
}
defer file.Close()
var state mipsevm.State
err = json.NewDecoder(file).Decode(&state)
if err != nil {
return nil, fmt.Errorf("invalid mipsevm state (%v): %w", path, err)
}
return &state, nil
}
package faultproofs package faultproofs
import ( import (
"crypto/ecdsa"
"testing" "testing"
batcherFlags "github.com/ethereum-optimism/optimism/op-batcher/flags" batcherFlags "github.com/ethereum-optimism/optimism/op-batcher/flags"
op_e2e "github.com/ethereum-optimism/optimism/op-e2e" op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -29,6 +32,15 @@ func withBlobBatches() faultDisputeConfigOpts { ...@@ -29,6 +32,15 @@ func withBlobBatches() faultDisputeConfigOpts {
} }
} }
func withEcotone() faultDisputeConfigOpts {
return func(cfg *op_e2e.SystemConfig) {
genesisActivation := hexutil.Uint64(0)
cfg.DeployConfig.L1CancunTimeOffset = &genesisActivation
cfg.DeployConfig.L2GenesisDeltaTimeOffset = &genesisActivation
cfg.DeployConfig.L2GenesisEcotoneTimeOffset = &genesisActivation
}
}
func startFaultDisputeSystem(t *testing.T, opts ...faultDisputeConfigOpts) (*op_e2e.System, *ethclient.Client) { func startFaultDisputeSystem(t *testing.T, opts ...faultDisputeConfigOpts) (*op_e2e.System, *ethclient.Client) {
cfg := op_e2e.DefaultSystemConfig(t) cfg := op_e2e.DefaultSystemConfig(t)
delete(cfg.Nodes, "verifier") delete(cfg.Nodes, "verifier")
...@@ -44,3 +56,12 @@ func startFaultDisputeSystem(t *testing.T, opts ...faultDisputeConfigOpts) (*op_ ...@@ -44,3 +56,12 @@ func startFaultDisputeSystem(t *testing.T, opts ...faultDisputeConfigOpts) (*op_
require.Nil(t, err, "Error starting up system") require.Nil(t, err, "Error starting up system")
return sys, sys.Clients["l1"] return sys, sys.Clients["l1"]
} }
func sendKZGPointEvaluationTx(t *testing.T, sys *op_e2e.System, l2Node string, privateKey *ecdsa.PrivateKey) *types.Receipt {
return op_e2e.SendL2Tx(t, sys.Cfg, sys.Clients[l2Node], privateKey, func(opts *op_e2e.TxOpts) {
precompile := common.BytesToAddress([]byte{0x0a})
opts.Gas = 100_000
opts.ToAddr = &precompile
opts.Data = common.FromHex("01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a")
})
}
...@@ -40,6 +40,8 @@ const ( ...@@ -40,6 +40,8 @@ const (
Sha256KeyType KeyType = 4 Sha256KeyType KeyType = 4
// BlobKeyType is for blob point pre-images. // BlobKeyType is for blob point pre-images.
BlobKeyType KeyType = 5 BlobKeyType KeyType = 5
// KZGPointEvaluationKeyType is for KZG point-evaluation pre-images.
KZGPointEvaluationKeyType KeyType = 6
) )
// LocalIndexKey is a key local to the program, indexing a special program input. // LocalIndexKey is a key local to the program, indexing a special program input.
...@@ -102,6 +104,23 @@ func (k BlobKey) TerminalString() string { ...@@ -102,6 +104,23 @@ func (k BlobKey) TerminalString() string {
return "0x" + hex.EncodeToString(k[:]) return "0x" + hex.EncodeToString(k[:])
} }
// KZGPointEvaluationKey is the hash of a KZG point-evaluation EVM call input-data
type KZGPointEvaluationKey [32]byte
func (k KZGPointEvaluationKey) PreimageKey() (out [32]byte) {
out = k
out[0] = byte(KZGPointEvaluationKeyType)
return
}
func (k KZGPointEvaluationKey) String() string {
return "0x" + hex.EncodeToString(k[:])
}
func (k KZGPointEvaluationKey) TerminalString() string {
return "0x" + hex.EncodeToString(k[:])
}
// Hint is an interface to enable any program type to function as a hint, // Hint is an interface to enable any program type to function as a hint,
// when passed to the Hinter interface, returning a string representation // when passed to the Hinter interface, returning a string representation
// of what data the host should prepare pre-images for. // of what data the host should prepare pre-images for.
......
...@@ -62,4 +62,19 @@ func TestPreimageKeyTypes(t *testing.T) { ...@@ -62,4 +62,19 @@ func TestPreimageKeyTypes(t *testing.T) {
// String encoding // String encoding
require.Equal(t, "0x00000000000000000000000000000000000000000000000000000000000000ff", actual.String()) require.Equal(t, "0x00000000000000000000000000000000000000000000000000000000000000ff", actual.String())
}) })
t.Run("KZGPointEvaluationKey", func(t *testing.T) {
fauxHash := [32]byte{}
fauxHash[31] = 0xFF
actual := KZGPointEvaluationKey(fauxHash)
// PreimageKey encoding
expected := [32]byte{}
expected[0] = byte(KZGPointEvaluationKeyType)
expected[31] = 0xFF
require.Equal(t, expected, actual.PreimageKey())
// String encoding
require.Equal(t, "0x00000000000000000000000000000000000000000000000000000000000000ff", actual.String())
})
} }
...@@ -38,6 +38,9 @@ func WithVerification(source PreimageGetter) PreimageGetter { ...@@ -38,6 +38,9 @@ func WithVerification(source PreimageGetter) PreimageGetter {
case BlobKeyType: case BlobKeyType:
// Can't verify an individual field element without having a kzg proof // Can't verify an individual field element without having a kzg proof
return data, nil return data, nil
case KZGPointEvaluationKeyType:
// Can't verify the KZG point evaluation result without having a kzg proof
return data, nil
default: default:
return nil, fmt.Errorf("%w: %v", ErrUnsupportedKeyType, key[0]) return nil, fmt.Errorf("%w: %v", ErrUnsupportedKeyType, key[0])
} }
......
...@@ -39,6 +39,12 @@ func TestWithVerification(t *testing.T) { ...@@ -39,6 +39,12 @@ func TestWithVerification(t *testing.T) {
data: []byte{4, 3, 5, 7, 3}, data: []byte{4, 3, 5, 7, 3},
expectedData: []byte{4, 3, 5, 7, 3}, expectedData: []byte{4, 3, 5, 7, 3},
}, },
{
name: "KZGPointEvaluationKey NoVerification",
key: KZGPointEvaluationKey([32]byte{1, 2, 3, 4}),
data: []byte{4, 3, 5, 7, 3},
expectedData: []byte{4, 3, 5, 7, 3},
},
{ {
name: "UnknownKey", name: "UnknownKey",
key: invalidKey([32]byte{0xaa}), key: invalidKey([32]byte{0xaa}),
......
...@@ -17,11 +17,12 @@ const cacheSize = 2000 ...@@ -17,11 +17,12 @@ const cacheSize = 2000
// CachingOracle is an implementation of Oracle that delegates to another implementation, adding caching of all results // CachingOracle is an implementation of Oracle that delegates to another implementation, adding caching of all results
type CachingOracle struct { type CachingOracle struct {
oracle Oracle oracle Oracle
blocks *simplelru.LRU[common.Hash, eth.BlockInfo] blocks *simplelru.LRU[common.Hash, eth.BlockInfo]
txs *simplelru.LRU[common.Hash, types.Transactions] txs *simplelru.LRU[common.Hash, types.Transactions]
rcpts *simplelru.LRU[common.Hash, types.Receipts] rcpts *simplelru.LRU[common.Hash, types.Receipts]
blobs *simplelru.LRU[common.Hash, *eth.Blob] blobs *simplelru.LRU[common.Hash, *eth.Blob]
ptEvals *simplelru.LRU[common.Hash, bool]
} }
func NewCachingOracle(oracle Oracle) *CachingOracle { func NewCachingOracle(oracle Oracle) *CachingOracle {
...@@ -29,12 +30,14 @@ func NewCachingOracle(oracle Oracle) *CachingOracle { ...@@ -29,12 +30,14 @@ func NewCachingOracle(oracle Oracle) *CachingOracle {
txsLRU, _ := simplelru.NewLRU[common.Hash, types.Transactions](cacheSize, nil) txsLRU, _ := simplelru.NewLRU[common.Hash, types.Transactions](cacheSize, nil)
rcptsLRU, _ := simplelru.NewLRU[common.Hash, types.Receipts](cacheSize, nil) rcptsLRU, _ := simplelru.NewLRU[common.Hash, types.Receipts](cacheSize, nil)
blobsLRU, _ := simplelru.NewLRU[common.Hash, *eth.Blob](cacheSize, nil) blobsLRU, _ := simplelru.NewLRU[common.Hash, *eth.Blob](cacheSize, nil)
ptEvals, _ := simplelru.NewLRU[common.Hash, bool](cacheSize, nil)
return &CachingOracle{ return &CachingOracle{
oracle: oracle, oracle: oracle,
blocks: blockLRU, blocks: blockLRU,
txs: txsLRU, txs: txsLRU,
rcpts: rcptsLRU, rcpts: rcptsLRU,
blobs: blobsLRU, blobs: blobsLRU,
ptEvals: ptEvals,
} }
} }
...@@ -86,3 +89,14 @@ func (o *CachingOracle) GetBlob(ref eth.L1BlockRef, blobHash eth.IndexedBlobHash ...@@ -86,3 +89,14 @@ func (o *CachingOracle) GetBlob(ref eth.L1BlockRef, blobHash eth.IndexedBlobHash
o.blobs.Add(cacheKey, blob) o.blobs.Add(cacheKey, blob)
return blob return blob
} }
func (o *CachingOracle) KZGPointEvaluation(input []byte) bool {
cacheKey := crypto.Keccak256Hash(input)
ptEval, ok := o.ptEvals.Get(cacheKey)
if ok {
return ptEval
}
ptEval = o.oracle.KZGPointEvaluation(input)
o.ptEvals.Add(cacheKey, ptEval)
return ptEval
}
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/ethereum-optimism/optimism/op-program/client/l1/test" "github.com/ethereum-optimism/optimism/op-program/client/l1/test"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/ethereum-optimism/optimism/op-service/testutils"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -91,3 +92,20 @@ func TestCachingOracle_GetBlobs(t *testing.T) { ...@@ -91,3 +92,20 @@ func TestCachingOracle_GetBlobs(t *testing.T) {
actualBlob = oracle.GetBlob(l1BlockRef, indexedBlobHash) actualBlob = oracle.GetBlob(l1BlockRef, indexedBlobHash)
require.Equal(t, &blob, actualBlob) require.Equal(t, &blob, actualBlob)
} }
func TestCachingOracle_KZGPointEvaluation(t *testing.T) {
stub := test.NewStubOracle(t)
oracle := NewCachingOracle(stub)
input := []byte{0x01, 0x02, 0x03, 0x04}
// Initial call retrieves from the stub
stub.PtEvals[crypto.Keccak256Hash(input)] = true
actualPtEval := oracle.KZGPointEvaluation(input)
require.True(t, actualPtEval)
// Later calls should retrieve from cache
delete(stub.PtEvals, crypto.Keccak256Hash(input))
actualPtEval = oracle.KZGPointEvaluation(input)
require.True(t, actualPtEval)
}
...@@ -8,10 +8,11 @@ import ( ...@@ -8,10 +8,11 @@ import (
) )
const ( const (
HintL1BlockHeader = "l1-block-header" HintL1BlockHeader = "l1-block-header"
HintL1Transactions = "l1-transactions" HintL1Transactions = "l1-transactions"
HintL1Receipts = "l1-receipts" HintL1Receipts = "l1-receipts"
HintL1Blob = "l1-blob" HintL1Blob = "l1-blob"
HintL1KZGPointEvaluation = "l1-kzg-point-evaluation"
) )
type BlockHeaderHint common.Hash type BlockHeaderHint common.Hash
...@@ -45,3 +46,11 @@ var _ preimage.Hint = BlobHint{} ...@@ -45,3 +46,11 @@ var _ preimage.Hint = BlobHint{}
func (l BlobHint) Hint() string { func (l BlobHint) Hint() string {
return HintL1Blob + " " + hexutil.Encode(l) return HintL1Blob + " " + hexutil.Encode(l)
} }
type KZGPointEvaluationHint []byte
var _ preimage.Hint = KZGPointEvaluationHint{}
func (l KZGPointEvaluationHint) Hint() string {
return HintL1KZGPointEvaluation + " " + hexutil.Encode(l)
}
...@@ -27,6 +27,9 @@ type Oracle interface { ...@@ -27,6 +27,9 @@ type Oracle interface {
// GetBlobField retrieves the field element at the given index from the blob with the given hash. // GetBlobField retrieves the field element at the given index from the blob with the given hash.
GetBlob(ref eth.L1BlockRef, blobHash eth.IndexedBlobHash) *eth.Blob GetBlob(ref eth.L1BlockRef, blobHash eth.IndexedBlobHash) *eth.Blob
// KZGPointEvaluation retriees the result of the Cancun KZG point evaluation precompile for the given input.
KZGPointEvaluation(input []byte) bool
} }
// PreimageOracle implements Oracle using by interfacing with the pure preimage.Oracle // PreimageOracle implements Oracle using by interfacing with the pure preimage.Oracle
...@@ -115,3 +118,13 @@ func (p *PreimageOracle) GetBlob(ref eth.L1BlockRef, blobHash eth.IndexedBlobHas ...@@ -115,3 +118,13 @@ func (p *PreimageOracle) GetBlob(ref eth.L1BlockRef, blobHash eth.IndexedBlobHas
return &blob return &blob
} }
func (p *PreimageOracle) KZGPointEvaluation(input []byte) bool {
p.hint.Hint(KZGPointEvaluationHint(input))
key := preimage.KZGPointEvaluationKey(crypto.Keccak256Hash(input[:]))
result := p.oracle.Get(key)
if len(result) != 1 || result[0] > 1 {
panic(fmt.Errorf("unexpected preimage oracle KZGPointEvaluation behavior, got result: %x", result))
}
return result[0] == 1
}
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
) )
type StubOracle struct { type StubOracle struct {
...@@ -22,15 +23,19 @@ type StubOracle struct { ...@@ -22,15 +23,19 @@ type StubOracle struct {
// Blobs maps indexed blob hash to l1 block ref to blob // Blobs maps indexed blob hash to l1 block ref to blob
Blobs map[eth.L1BlockRef]map[eth.IndexedBlobHash]*eth.Blob Blobs map[eth.L1BlockRef]map[eth.IndexedBlobHash]*eth.Blob
// PtEvals maps hashed input to whether the KZG point evaluation was successful
PtEvals map[common.Hash]bool
} }
func NewStubOracle(t *testing.T) *StubOracle { func NewStubOracle(t *testing.T) *StubOracle {
return &StubOracle{ return &StubOracle{
t: t, t: t,
Blocks: make(map[common.Hash]eth.BlockInfo), Blocks: make(map[common.Hash]eth.BlockInfo),
Txs: make(map[common.Hash]types.Transactions), Txs: make(map[common.Hash]types.Transactions),
Rcpts: make(map[common.Hash]types.Receipts), Rcpts: make(map[common.Hash]types.Receipts),
Blobs: make(map[eth.L1BlockRef]map[eth.IndexedBlobHash]*eth.Blob), Blobs: make(map[eth.L1BlockRef]map[eth.IndexedBlobHash]*eth.Blob),
PtEvals: make(map[common.Hash]bool),
} }
} }
...@@ -69,3 +74,11 @@ func (o StubOracle) GetBlob(ref eth.L1BlockRef, blobHash eth.IndexedBlobHash) *e ...@@ -69,3 +74,11 @@ func (o StubOracle) GetBlob(ref eth.L1BlockRef, blobHash eth.IndexedBlobHash) *e
} }
return blob return blob
} }
func (o StubOracle) KZGPointEvaluation(input []byte) bool {
result, ok := o.PtEvals[crypto.Keccak256Hash(input)]
if !ok {
o.t.Fatalf("unknown kzg point evaluation %x", input)
}
return result
}
...@@ -40,7 +40,7 @@ type OracleBackedL2Chain struct { ...@@ -40,7 +40,7 @@ type OracleBackedL2Chain struct {
var _ engineapi.EngineBackend = (*OracleBackedL2Chain)(nil) var _ engineapi.EngineBackend = (*OracleBackedL2Chain)(nil)
func NewOracleBackedL2Chain(logger log.Logger, oracle Oracle, chainCfg *params.ChainConfig, l2OutputRoot common.Hash) (*OracleBackedL2Chain, error) { func NewOracleBackedL2Chain(logger log.Logger, oracle Oracle, kzgOracle engineapi.KZGPointEvaluationOracle, chainCfg *params.ChainConfig, l2OutputRoot common.Hash) (*OracleBackedL2Chain, error) {
output := oracle.OutputByRoot(l2OutputRoot) output := oracle.OutputByRoot(l2OutputRoot)
outputV0, ok := output.(*eth.OutputV0) outputV0, ok := output.(*eth.OutputV0)
if !ok { if !ok {
...@@ -66,6 +66,9 @@ func NewOracleBackedL2Chain(logger log.Logger, oracle Oracle, chainCfg *params.C ...@@ -66,6 +66,9 @@ func NewOracleBackedL2Chain(logger log.Logger, oracle Oracle, chainCfg *params.C
oracleHead: head.Header(), oracleHead: head.Header(),
blocks: make(map[common.Hash]*types.Block), blocks: make(map[common.Hash]*types.Block),
db: NewOracleBackedDB(oracle), db: NewOracleBackedDB(oracle),
vmCfg: vm.Config{
OptimismPrecompileOverrides: precompileOverrides(kzgOracle),
},
}, nil }, nil
} }
...@@ -229,3 +232,15 @@ func (o *OracleBackedL2Chain) SetFinalized(header *types.Header) { ...@@ -229,3 +232,15 @@ func (o *OracleBackedL2Chain) SetFinalized(header *types.Header) {
func (o *OracleBackedL2Chain) SetSafe(header *types.Header) { func (o *OracleBackedL2Chain) SetSafe(header *types.Header) {
o.safe = header o.safe = header
} }
var kzgPointEvaluationPrecompileAddress = common.BytesToAddress([]byte{0xa})
func precompileOverrides(kzgOracle engineapi.KZGPointEvaluationOracle) vm.PrecompileOverrides {
return func(rules params.Rules, address common.Address) (vm.PrecompiledContract, bool) {
// NOTE: Ignoring chain rules for now. We assume that precompile behavior won't change for the foreseeable future
if address != kzgPointEvaluationPrecompileAddress {
return nil, false
}
return &engineapi.OracleKZGPointEvaluation{Oracle: kzgOracle}, true
}
}
...@@ -28,6 +28,9 @@ var fundedKey, _ = crypto.GenerateKey() ...@@ -28,6 +28,9 @@ var fundedKey, _ = crypto.GenerateKey()
var fundedAddress = crypto.PubkeyToAddress(fundedKey.PublicKey) var fundedAddress = crypto.PubkeyToAddress(fundedKey.PublicKey)
var targetAddress = common.HexToAddress("0x001122334455") var targetAddress = common.HexToAddress("0x001122334455")
// Also a valid input to the kzg point evaluation precompile
var inputData = common.FromHex("01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a")
func TestInitialState(t *testing.T) { func TestInitialState(t *testing.T) {
blocks, chain := setupOracleBackedChain(t, 5) blocks, chain := setupOracleBackedChain(t, 5)
head := blocks[5] head := blocks[5]
...@@ -186,6 +189,25 @@ func TestGetHeaderByNumber(t *testing.T) { ...@@ -186,6 +189,25 @@ func TestGetHeaderByNumber(t *testing.T) {
}) })
} }
func TestKZGPointEvaluationPrecompile(t *testing.T) {
blockCount := 3
headBlockNumber := 3
logger := testlog.Logger(t, log.LevelDebug)
chainCfg, blocks, oracle := setupOracle(t, blockCount, headBlockNumber, true)
head := blocks[headBlockNumber].Hash()
stubOutput := eth.OutputV0{BlockHash: head}
kzgOracle := new(l2test.StubKZGOracle)
kzgOracle.PtEvals = map[common.Hash]bool{
crypto.Keccak256Hash(inputData): true,
}
chain, err := NewOracleBackedL2Chain(logger, oracle, kzgOracle, chainCfg, common.Hash(eth.OutputRoot(&stubOutput)))
require.NoError(t, err)
newBlock := createKZGBlock(t, chain)
require.NoError(t, chain.InsertBlockWithoutSetHead(newBlock))
require.Equal(t, 1, kzgOracle.Calls)
}
func assertBlockDataAvailable(t *testing.T, chain *OracleBackedL2Chain, block *types.Block, blockNumber uint64) { func assertBlockDataAvailable(t *testing.T, chain *OracleBackedL2Chain, block *types.Block, blockNumber uint64) {
require.Equal(t, block, chain.GetBlockByHash(block.Hash()), "get block %v by hash", blockNumber) require.Equal(t, block, chain.GetBlockByHash(block.Hash()), "get block %v by hash", blockNumber)
require.Equal(t, block.Header(), chain.GetHeaderByHash(block.Hash()), "get header %v by hash", blockNumber) require.Equal(t, block.Header(), chain.GetHeaderByHash(block.Hash()), "get header %v by hash", blockNumber)
...@@ -200,15 +222,16 @@ func setupOracleBackedChain(t *testing.T, blockCount int) ([]*types.Block, *Orac ...@@ -200,15 +222,16 @@ func setupOracleBackedChain(t *testing.T, blockCount int) ([]*types.Block, *Orac
func setupOracleBackedChainWithLowerHead(t *testing.T, blockCount int, headBlockNumber int) ([]*types.Block, *OracleBackedL2Chain) { func setupOracleBackedChainWithLowerHead(t *testing.T, blockCount int, headBlockNumber int) ([]*types.Block, *OracleBackedL2Chain) {
logger := testlog.Logger(t, log.LevelDebug) logger := testlog.Logger(t, log.LevelDebug)
chainCfg, blocks, oracle := setupOracle(t, blockCount, headBlockNumber) chainCfg, blocks, oracle := setupOracle(t, blockCount, headBlockNumber, false)
head := blocks[headBlockNumber].Hash() head := blocks[headBlockNumber].Hash()
stubOutput := eth.OutputV0{BlockHash: head} stubOutput := eth.OutputV0{BlockHash: head}
chain, err := NewOracleBackedL2Chain(logger, oracle, chainCfg, common.Hash(eth.OutputRoot(&stubOutput))) kzgOracle := new(l2test.StubKZGOracle)
chain, err := NewOracleBackedL2Chain(logger, oracle, kzgOracle, chainCfg, common.Hash(eth.OutputRoot(&stubOutput)))
require.NoError(t, err) require.NoError(t, err)
return blocks, chain return blocks, chain
} }
func setupOracle(t *testing.T, blockCount int, headBlockNumber int) (*params.ChainConfig, []*types.Block, *l2test.StubBlockOracle) { func setupOracle(t *testing.T, blockCount int, headBlockNumber int, enableEcotone bool) (*params.ChainConfig, []*types.Block, *l2test.StubBlockOracle) {
deployConfig := &genesis.DeployConfig{ deployConfig := &genesis.DeployConfig{
L1ChainID: 900, L1ChainID: 900,
L2ChainID: 901, L2ChainID: 901,
...@@ -219,6 +242,13 @@ func setupOracle(t *testing.T, blockCount int, headBlockNumber int) (*params.Cha ...@@ -219,6 +242,13 @@ func setupOracle(t *testing.T, blockCount int, headBlockNumber int) (*params.Cha
// This is slightly weird for a chain starting post-merge but it happens so need to make sure it works // This is slightly weird for a chain starting post-merge but it happens so need to make sure it works
L2GenesisBlockDifficulty: (*hexutil.Big)(big.NewInt(100)), L2GenesisBlockDifficulty: (*hexutil.Big)(big.NewInt(100)),
} }
if enableEcotone {
ts := hexutil.Uint64(0)
deployConfig.L2GenesisRegolithTimeOffset = &ts
deployConfig.L2GenesisCanyonTimeOffset = &ts
deployConfig.L2GenesisDeltaTimeOffset = &ts
deployConfig.L2GenesisEcotoneTimeOffset = &ts
}
l1Genesis, err := genesis.NewL1Genesis(deployConfig) l1Genesis, err := genesis.NewL1Genesis(deployConfig)
require.NoError(t, err) require.NoError(t, err)
l2Genesis, err := genesis.NewL2Genesis(deployConfig, l1Genesis.ToBlock()) l2Genesis, err := genesis.NewL2Genesis(deployConfig, l1Genesis.ToBlock())
...@@ -246,7 +276,7 @@ func setupOracle(t *testing.T, blockCount int, headBlockNumber int) (*params.Cha ...@@ -246,7 +276,7 @@ func setupOracle(t *testing.T, blockCount int, headBlockNumber int) (*params.Cha
return chainCfg, blocks, oracle return chainCfg, blocks, oracle
} }
func createBlock(t *testing.T, chain *OracleBackedL2Chain) *types.Block { func createBlockWithTargetAddress(t *testing.T, chain *OracleBackedL2Chain, target *common.Address) *types.Block {
parent := chain.GetBlockByHash(chain.CurrentHeader().Hash()) parent := chain.GetBlockByHash(chain.CurrentHeader().Hash())
parentDB, err := chain.StateAt(parent.Root()) parentDB, err := chain.StateAt(parent.Root())
require.NoError(t, err) require.NoError(t, err)
...@@ -257,10 +287,11 @@ func createBlock(t *testing.T, chain *OracleBackedL2Chain) *types.Block { ...@@ -257,10 +287,11 @@ func createBlock(t *testing.T, chain *OracleBackedL2Chain) *types.Block {
rawTx := &types.DynamicFeeTx{ rawTx := &types.DynamicFeeTx{
ChainID: config.ChainID, ChainID: config.ChainID,
Nonce: nonce, Nonce: nonce,
To: &targetAddress, To: target,
GasTipCap: big.NewInt(0), GasTipCap: big.NewInt(0),
GasFeeCap: parent.BaseFee(), GasFeeCap: parent.BaseFee(),
Gas: 21_000, Gas: 210_000,
Data: inputData,
Value: big.NewInt(1), Value: big.NewInt(1),
} }
tx := types.MustSignNewTx(fundedKey, types.NewLondonSigner(config.ChainID), rawTx) tx := types.MustSignNewTx(fundedKey, types.NewLondonSigner(config.ChainID), rawTx)
...@@ -269,6 +300,14 @@ func createBlock(t *testing.T, chain *OracleBackedL2Chain) *types.Block { ...@@ -269,6 +300,14 @@ func createBlock(t *testing.T, chain *OracleBackedL2Chain) *types.Block {
return blocks[0] return blocks[0]
} }
func createBlock(t *testing.T, chain *OracleBackedL2Chain) *types.Block {
return createBlockWithTargetAddress(t, chain, &targetAddress)
}
func createKZGBlock(t *testing.T, chain *OracleBackedL2Chain) *types.Block {
return createBlockWithTargetAddress(t, chain, &kzgPointEvaluationPrecompileAddress)
}
func TestEngineAPITests(t *testing.T) { func TestEngineAPITests(t *testing.T) {
test.RunEngineAPITests(t, func(t *testing.T) engineapi.EngineBackend { test.RunEngineAPITests(t, func(t *testing.T) engineapi.EngineBackend {
_, chain := setupOracleBackedChain(t, 0) _, chain := setupOracleBackedChain(t, 0)
......
...@@ -84,7 +84,12 @@ func NewBlockProcessorFromHeader(provider BlockDataProvider, h *types.Header) (* ...@@ -84,7 +84,12 @@ func NewBlockProcessorFromHeader(provider BlockDataProvider, h *types.Header) (*
// Unfortunately this is not part of any Geth environment setup, // Unfortunately this is not part of any Geth environment setup,
// we just have to apply it, like how the Geth block-builder worker does. // we just have to apply it, like how the Geth block-builder worker does.
context := core.NewEVMBlockContext(header, provider, nil, provider.Config(), statedb) context := core.NewEVMBlockContext(header, provider, nil, provider.Config(), statedb)
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, provider.Config(), vm.Config{}) // NOTE: Unlikely to be needed for the beacon block root, but we setup any precompile overrides anyways for forwards-compatibility
var precompileOverrides vm.PrecompileOverrides
if vmConfig := provider.GetVMConfig(); vmConfig != nil && vmConfig.OptimismPrecompileOverrides != nil {
precompileOverrides = vmConfig.OptimismPrecompileOverrides
}
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, provider.Config(), vm.Config{OptimismPrecompileOverrides: precompileOverrides})
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, statedb) core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, statedb)
} }
return &BlockProcessor{ return &BlockProcessor{
......
// This file contains code of the upstream go-ethereum kzgPointEvaluation implementation.
// Modifications have been made, primarily to substitute kzg4844.VerifyProof with a preimage oracle call.
//
// Original copyright disclaimer, applicable only to this file:
// -------------------------------------------------------------------
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package engineapi
import (
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum-optimism/optimism/op-service/eth"
)
// OracleKZGPointEvaluation implements the EIP-4844 point evaluation precompile,
// using the preimage-oracle to perform the evaluation.
type OracleKZGPointEvaluation struct {
Oracle KZGPointEvaluationOracle
}
// KZGPointEvaluationOracle defines the high-level API used to retrieve the result of the KZG point evaluation precompile
type KZGPointEvaluationOracle interface {
KZGPointEvaluation(input []byte) bool
}
// RequiredGas estimates the gas required for running the point evaluation precompile.
func (b *OracleKZGPointEvaluation) RequiredGas(input []byte) uint64 {
return params.BlobTxPointEvaluationPrecompileGas
}
const (
blobVerifyInputLength = 192 // Max input length for the point evaluation precompile.
blobPrecompileReturnValue = "000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"
)
var (
errBlobVerifyInvalidInputLength = errors.New("invalid input length")
errBlobVerifyMismatchedVersion = errors.New("mismatched versioned hash")
errBlobVerifyKZGProof = errors.New("error verifying kzg proof")
)
// Run executes the point evaluation precompile.
func (b *OracleKZGPointEvaluation) Run(input []byte) ([]byte, error) {
// Modification note: the L1 precompile behavior may change, but not in incompatible ways.
// We want to enforce the subset that represents the EVM behavior activated in L2.
// Below is a copy of the Cancun behavior. L1 might expand on that at a later point.
if len(input) != blobVerifyInputLength {
return nil, errBlobVerifyInvalidInputLength
}
// versioned hash: first 32 bytes
var versionedHash common.Hash
copy(versionedHash[:], input[:])
var (
point kzg4844.Point
claim kzg4844.Claim
)
// Evaluation point: next 32 bytes
copy(point[:], input[32:])
// Expected output: next 32 bytes
copy(claim[:], input[64:])
// input kzg point: next 48 bytes
var commitment kzg4844.Commitment
copy(commitment[:], input[96:])
if eth.KZGToVersionedHash(commitment) != versionedHash {
return nil, errBlobVerifyMismatchedVersion
}
// Proof: next 48 bytes
var proof kzg4844.Proof
copy(proof[:], input[144:])
// Modification note: below replaces the kzg4844.VerifyProof call
ok := b.Oracle.KZGPointEvaluation(input)
if !ok {
return nil, fmt.Errorf("%w: invalid KZG point evaluation", errBlobVerifyKZGProof)
}
return common.FromHex(blobPrecompileReturnValue), nil
}
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
) )
...@@ -122,3 +123,18 @@ func (o *StubStateOracle) CodeByHash(hash common.Hash) []byte { ...@@ -122,3 +123,18 @@ func (o *StubStateOracle) CodeByHash(hash common.Hash) []byte {
} }
return data return data
} }
type StubKZGOracle struct {
t *testing.T
PtEvals map[common.Hash]bool
Calls int
}
func (o *StubKZGOracle) KZGPointEvaluation(input []byte) bool {
result, ok := o.PtEvals[crypto.Keccak256Hash(input)]
if !ok {
o.t.Fatalf("no value for point evaluation %v", input)
}
o.Calls++
return result
}
...@@ -64,7 +64,7 @@ func RunProgram(logger log.Logger, preimageOracle io.ReadWriter, preimageHinter ...@@ -64,7 +64,7 @@ func RunProgram(logger log.Logger, preimageOracle io.ReadWriter, preimageHinter
func runDerivation(logger log.Logger, cfg *rollup.Config, l2Cfg *params.ChainConfig, l1Head common.Hash, l2OutputRoot common.Hash, l2Claim common.Hash, l2ClaimBlockNum uint64, l1Oracle l1.Oracle, l2Oracle l2.Oracle) error { func runDerivation(logger log.Logger, cfg *rollup.Config, l2Cfg *params.ChainConfig, l1Head common.Hash, l2OutputRoot common.Hash, l2Claim common.Hash, l2ClaimBlockNum uint64, l1Oracle l1.Oracle, l2Oracle l2.Oracle) error {
l1Source := l1.NewOracleL1Client(logger, l1Oracle, l1Head) l1Source := l1.NewOracleL1Client(logger, l1Oracle, l1Head)
l1BlobsSource := l1.NewBlobFetcher(logger, l1Oracle) l1BlobsSource := l1.NewBlobFetcher(logger, l1Oracle)
engineBackend, err := l2.NewOracleBackedL2Chain(logger, l2Oracle, l2Cfg, l2OutputRoot) engineBackend, err := l2.NewOracleBackedL2Chain(logger, l2Oracle, l1Oracle /* kzg oracle */, l2Cfg, l2OutputRoot)
if err != nil { if err != nil {
return fmt.Errorf("failed to create oracle-backed L2 chain: %w", err) return fmt.Errorf("failed to create oracle-backed L2 chain: %w", err)
} }
......
...@@ -16,11 +16,17 @@ import ( ...@@ -16,11 +16,17 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
var (
kzgPointEvaluationSuccess = [1]byte{1}
kzgPointEvaluationFailure = [1]byte{0}
)
type L1Source interface { type L1Source interface {
InfoByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, error) InfoByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, error)
InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error) InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error)
...@@ -32,6 +38,10 @@ type L1BlobSource interface { ...@@ -32,6 +38,10 @@ type L1BlobSource interface {
GetBlobs(ctx context.Context, ref eth.L1BlockRef, hashes []eth.IndexedBlobHash) ([]*eth.Blob, error) GetBlobs(ctx context.Context, ref eth.L1BlockRef, hashes []eth.IndexedBlobHash) ([]*eth.Blob, error)
} }
type L1PrecompileSource interface {
KZGPointEvaluation(input []byte) ([]byte, error)
}
type L2Source interface { type L2Source interface {
InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error) InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error)
NodeByHash(ctx context.Context, hash common.Hash) ([]byte, error) NodeByHash(ctx context.Context, hash common.Hash) ([]byte, error)
...@@ -165,6 +175,22 @@ func (p *Prefetcher) prefetch(ctx context.Context, hint string) error { ...@@ -165,6 +175,22 @@ func (p *Prefetcher) prefetch(ctx context.Context, hint string) error {
} }
} }
return nil return nil
case l1.HintL1KZGPointEvaluation:
precompile := vm.PrecompiledContractsCancun[common.BytesToAddress([]byte{0x0a})]
// KZG Point Evaluation precompile also verifies hintBytes length
_, err := precompile.Run(hintBytes)
var result [1]byte
if err == nil {
result = kzgPointEvaluationSuccess
} else {
result = kzgPointEvaluationFailure
}
inputHash := crypto.Keccak256Hash(hintBytes)
// Put the input preimage so it can be loaded later
if err := p.kvStore.Put(preimage.Keccak256Key(inputHash).PreimageKey(), hintBytes); err != nil {
return err
}
return p.kvStore.Put(preimage.KZGPointEvaluationKey(inputHash).PreimageKey(), result[:])
case l2.HintL2BlockHeader, l2.HintL2Transactions: case l2.HintL2BlockHeader, l2.HintL2Transactions:
if len(hintBytes) != 32 { if len(hintBytes) != 32 {
return fmt.Errorf("invalid L2 header/tx hint: %x", hint) return fmt.Errorf("invalid L2 header/tx hint: %x", hint)
......
...@@ -231,6 +231,45 @@ func TestFetchL1Blob(t *testing.T) { ...@@ -231,6 +231,45 @@ func TestFetchL1Blob(t *testing.T) {
}) })
} }
func TestFetchKZGPointEvaluation(t *testing.T) {
runTest := func(name string, input []byte, expected bool) {
t.Run(name, func(t *testing.T) {
prefetcher, _, _, _, _ := createPrefetcher(t)
oracle := l1.NewPreimageOracle(asOracleFn(t, prefetcher), asHinter(t, prefetcher))
result := oracle.KZGPointEvaluation(input)
require.Equal(t, expected, result)
val, err := prefetcher.kvStore.Get(preimage.Keccak256Key(crypto.Keccak256Hash(input)).PreimageKey())
require.NoError(t, err)
require.EqualValues(t, input, val)
key := preimage.KZGPointEvaluationKey(crypto.Keccak256Hash(input)).PreimageKey()
val, err = prefetcher.kvStore.Get(key)
require.NoError(t, err)
if expected {
require.EqualValues(t, kzgPointEvaluationSuccess[:], val)
} else {
require.EqualValues(t, kzgPointEvaluationFailure[:], val)
}
})
}
validInput := common.FromHex("01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a")
runTest("Valid input", validInput, true)
runTest("Invalid input", []byte{0x00}, false)
t.Run("Already Known", func(t *testing.T) {
input := []byte("test input")
prefetcher, _, _, _, kv := createPrefetcher(t)
err := kv.Put(preimage.KZGPointEvaluationKey(crypto.Keccak256Hash(input)).PreimageKey(), kzgPointEvaluationSuccess[:])
require.NoError(t, err)
oracle := l1.NewPreimageOracle(asOracleFn(t, prefetcher), asHinter(t, prefetcher))
result := oracle.KZGPointEvaluation(input)
require.True(t, result)
})
}
func TestFetchL2Block(t *testing.T) { func TestFetchL2Block(t *testing.T) {
rng := rand.New(rand.NewSource(123)) rng := rand.New(rand.NewSource(123))
block, rcpts := testutils.RandomBlock(rng, 10) block, rcpts := testutils.RandomBlock(rng, 10)
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
"requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", "requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", "recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameAbsolutePrestate": "0x03c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98", "faultGameAbsolutePrestate": "0x03c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98",
"faultGameMaxDepth": 46, "faultGameMaxDepth": 50,
"faultGameMaxDuration": 2400, "faultGameMaxDuration": 2400,
"faultGameGenesisBlock": 0, "faultGameGenesisBlock": 0,
"faultGameGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", "faultGameGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
......
...@@ -301,6 +301,19 @@ ...@@ -301,6 +301,19 @@
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"type": "function" "type": "function"
}, },
{
"inputs": [
{
"internalType": "bytes",
"name": "_input",
"type": "bytes"
}
],
"name": "loadKZGPointEvaluationPreimage",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
......
...@@ -329,6 +329,45 @@ contract PreimageOracle is IPreimageOracle { ...@@ -329,6 +329,45 @@ contract PreimageOracle is IPreimageOracle {
preimageLengths[key] = 32; preimageLengths[key] = 32;
} }
/// @inheritdoc IPreimageOracle
function loadKZGPointEvaluationPreimage(bytes calldata _input) external {
// Prior to Cancun activation, the blob preimage precompile is not available.
if (block.timestamp < CANCUN_ACTIVATION) revert CancunNotActive();
bytes32 key;
bytes32 part;
assembly {
// we leave solidity slots 0x40 and 0x60 untouched, and everything after as scratch-memory.
let ptr := 0x80
// copy input into memory
calldatacopy(ptr, _input.offset, _input.length)
// compute the hash
let h := keccak256(ptr, _input.length)
// mask out prefix byte, replace with type 6 byte
key := or(and(h, not(shl(248, 0xFF))), shl(248, 0x06))
// Verify the KZG proof by calling the point evaluation precompile.
// Capture the verification result
part :=
staticcall(
gas(), // forward all gas
0x0A, // point evaluation precompile address
ptr, // input ptr
_input.length, // we may want to load differently sized point-evaluation calls in the future
0x00, // output ptr
0x00 // output size
)
// "part" will be 0 on error, and 1 on success, of the KZG Point-evaluation precompile call
// We do have to shift it to the left-most byte of the bytes32 however, since we only read that byte.
part := shl(248, part)
}
// the part offset is always 0
preimagePartOk[key][0] = true;
preimageParts[key][0] = part;
preimageLengths[key] = 1;
}
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// Large Preimage Proposals (External) // // Large Preimage Proposals (External) //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
......
...@@ -69,4 +69,8 @@ interface IPreimageOracle { ...@@ -69,4 +69,8 @@ interface IPreimageOracle {
uint256 _partOffset uint256 _partOffset
) )
external; external;
/// @notice Prepares a point evaluation precompile result to be read by the keccak256 of its input.
/// @param _input The point evaluation precompile input.
function loadKZGPointEvaluationPreimage(bytes calldata _input) external;
} }
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