diff --git a/op-challenger/game/fault/preimages/direct_test.go b/op-challenger/game/fault/preimages/direct_test.go index 3eded6b464e9..549f3f033aee 100644 --- a/op-challenger/game/fault/preimages/direct_test.go +++ b/op-challenger/game/fault/preimages/direct_test.go @@ -17,6 +17,7 @@ import ( var ( mockUpdateOracleTxError = errors.New("mock update oracle tx error") mockTxMgrSendError = errors.New("mock tx mgr send error") + mockInitLPPError = errors.New("mock init LPP error") ) func TestDirectPreimageUploader_UploadPreimage(t *testing.T) { @@ -25,7 +26,7 @@ func TestDirectPreimageUploader_UploadPreimage(t *testing.T) { contract.updateFails = true err := oracle.UploadPreimage(context.Background(), 0, &types.PreimageOracleData{}) require.ErrorIs(t, err, mockUpdateOracleTxError) - require.Equal(t, 1, contract.updates) + require.Equal(t, 1, contract.updateCalls) require.Equal(t, 0, txMgr.sends) // verify that the tx was not sent }) @@ -34,7 +35,7 @@ func TestDirectPreimageUploader_UploadPreimage(t *testing.T) { txMgr.sendFails = true err := oracle.UploadPreimage(context.Background(), 0, &types.PreimageOracleData{}) require.ErrorIs(t, err, mockTxMgrSendError) - require.Equal(t, 1, contract.updates) + require.Equal(t, 1, contract.updateCalls) require.Equal(t, 1, txMgr.sends) }) @@ -48,7 +49,7 @@ func TestDirectPreimageUploader_UploadPreimage(t *testing.T) { oracle, _, contract := newTestDirectPreimageUploader(t) err := oracle.UploadPreimage(context.Background(), 0, &types.PreimageOracleData{}) require.NoError(t, err) - require.Equal(t, 1, contract.updates) + require.Equal(t, 1, contract.updateCalls) }) } @@ -85,12 +86,12 @@ func newTestDirectPreimageUploader(t *testing.T) (*DirectPreimageUploader, *mock } type mockPreimageGameContract struct { - updates int + updateCalls int updateFails bool } func (s *mockPreimageGameContract) UpdateOracleTx(_ context.Context, _ uint64, _ *types.PreimageOracleData) (txmgr.TxCandidate, error) { - s.updates++ + s.updateCalls++ if s.updateFails { return txmgr.TxCandidate{}, mockUpdateOracleTxError } diff --git a/op-challenger/game/fault/preimages/large.go b/op-challenger/game/fault/preimages/large.go index 55687b3f617d..c560ec327f36 100644 --- a/op-challenger/game/fault/preimages/large.go +++ b/op-challenger/game/fault/preimages/large.go @@ -2,17 +2,21 @@ package preimages import ( "context" + "crypto/rand" "errors" + "fmt" + "math/big" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-service/txmgr" + ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" ) -var _ PreimageUploader = (*LargePreimageUploader)(nil) - var errNotSupported = errors.New("not supported") +var _ PreimageUploader = (*LargePreimageUploader)(nil) + // LargePreimageUploader handles uploading large preimages by // streaming the merkleized preimage to the PreimageOracle contract, // tightly packed across multiple transactions. @@ -34,7 +38,53 @@ func (p *LargePreimageUploader) UploadPreimage(ctx context.Context, parent uint6 // todo(proofs#467): split up the preimage into chunks and submit the preimages // and state commitments to the preimage oracle contract using // `PreimageOracle.addLeavesLPP` (`_finalize` = false). + + // TODO(client-pod#473): The UUID must be deterministic so the challenger can resume uploads. + uuid, err := p.newUUID() + if err != nil { + return fmt.Errorf("failed to generate UUID: %w", err) + } + err = p.initLargePreimage(ctx, uuid, data.OracleOffset, uint32(len(data.OracleData))) + if err != nil { + return fmt.Errorf("failed to initialize large preimage with uuid: %s: %w", uuid, err) + } + // todo(proofs#467): track the challenge period starting once the full preimage is posted. // todo(proofs#467): once the challenge period is over, call `squeezeLPP` on the preimage oracle contract. + return errNotSupported } + +func (p *LargePreimageUploader) newUUID() (*big.Int, error) { + max := new(big.Int) + max.Exp(big.NewInt(2), big.NewInt(130), nil).Sub(max, big.NewInt(1)) + return rand.Int(rand.Reader, max) +} + +// initLargePreimage initializes the large preimage proposal. +// This method *must* be called before adding any leaves. +func (p *LargePreimageUploader) initLargePreimage(ctx context.Context, uuid *big.Int, partOffset uint32, claimedSize uint32) error { + candidate, err := p.contract.InitLargePreimage(uuid, partOffset, claimedSize) + if err != nil { + return fmt.Errorf("failed to create pre-image oracle tx: %w", err) + } + if err := p.sendTxAndWait(ctx, candidate); err != nil { + return fmt.Errorf("failed to populate pre-image oracle: %w", err) + } + return nil +} + +// sendTxAndWait sends a transaction through the [txmgr] and waits for a receipt. +// This sets the tx GasLimit to 0, performing gas estimation online through the [txmgr]. +func (p *LargePreimageUploader) sendTxAndWait(ctx context.Context, candidate txmgr.TxCandidate) error { + receipt, err := p.txMgr.Send(ctx, candidate) + if err != nil { + return err + } + if receipt.Status == ethtypes.ReceiptStatusFailed { + p.log.Error("LargePreimageUploader tx successfully published but reverted", "tx_hash", receipt.TxHash) + } else { + p.log.Debug("LargePreimageUploader tx successfully published", "tx_hash", receipt.TxHash) + } + return nil +} diff --git a/op-challenger/game/fault/preimages/large_test.go b/op-challenger/game/fault/preimages/large_test.go index af3a3c7fd565..81b77cda3821 100644 --- a/op-challenger/game/fault/preimages/large_test.go +++ b/op-challenger/game/fault/preimages/large_test.go @@ -16,9 +16,18 @@ import ( ) func TestLargePreimageUploader_UploadPreimage(t *testing.T) { + t.Run("InitFails", func(t *testing.T) { + oracle, _, contract := newTestLargePreimageUploader(t) + contract.initFails = true + err := oracle.UploadPreimage(context.Background(), 0, &types.PreimageOracleData{}) + require.ErrorIs(t, err, mockInitLPPError) + require.Equal(t, 1, contract.initCalls) + }) + t.Run("Success", func(t *testing.T) { - oracle, _, _ := newTestLargePreimageUploader(t) + oracle, _, contract := newTestLargePreimageUploader(t) err := oracle.UploadPreimage(context.Background(), 0, &types.PreimageOracleData{}) + require.Equal(t, 1, contract.initCalls) // TODO(proofs#467): fix this to not error. See LargePreimageUploader.UploadPreimage. require.ErrorIs(t, err, errNotSupported) }) @@ -31,9 +40,16 @@ func newTestLargePreimageUploader(t *testing.T) (*LargePreimageUploader, *mockTx return NewLargePreimageUploader(logger, txMgr, contract), txMgr, contract } -type mockPreimageOracleContract struct{} +type mockPreimageOracleContract struct { + initCalls int + initFails bool +} func (s *mockPreimageOracleContract) InitLargePreimage(_ *big.Int, _ uint32, _ uint32) (txmgr.TxCandidate, error) { + s.initCalls++ + if s.initFails { + return txmgr.TxCandidate{}, mockInitLPPError + } return txmgr.TxCandidate{}, nil } func (s *mockPreimageOracleContract) AddLeaves(_ *big.Int, _ []contracts.Leaf, _ bool) ([]txmgr.TxCandidate, error) {