diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index dc08d3861d..db9ce1044c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -35,8 +35,8 @@ jobs: uses: actions/cache@v2 with: path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-a-${{ github.sha }} - restore-keys: ${{ runner.os }}-buildx-a- + key: ${{ runner.os }}-buildx-b-${{ github.sha }} + restore-keys: ${{ runner.os }}-buildx-b- - name: Build nitro-node docker uses: docker/build-push-action@v2 diff --git a/arbnode/api.go b/arbnode/api.go index 4e951ba8de..322d8e85b1 100644 --- a/arbnode/api.go +++ b/arbnode/api.go @@ -40,10 +40,7 @@ func (a *BlockValidatorAPI) RevalidateBlock(ctx context.Context, blockNum rpc.Bl } moduleRoot = moduleRoots[0] } - batchFetcher := func(batchSeqNum uint64, _ common.Hash) ([]byte, error) { - panic("not yet implemented") // BUGBUG - } - return a.val.ValidateBlock(ctx, header, moduleRoot, batchFetcher) + return a.val.ValidateBlock(ctx, header, moduleRoot) } func (a *BlockValidatorAPI) LatestValidatedBlock(ctx context.Context) (uint64, error) { diff --git a/arbnode/inbox_reader.go b/arbnode/inbox_reader.go index 27700ec173..5ece1fea48 100644 --- a/arbnode/inbox_reader.go +++ b/arbnode/inbox_reader.go @@ -7,7 +7,6 @@ import ( "context" "errors" "fmt" - "github.com/offchainlabs/nitro/util/headerreader" "math/big" "strings" "sync" @@ -18,6 +17,7 @@ import ( "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/util/arbmath" + "github.com/offchainlabs/nitro/util/headerreader" "github.com/offchainlabs/nitro/util/stopwaiter" ) diff --git a/arbnode/node.go b/arbnode/node.go index e643dc61a3..29f33cb320 100644 --- a/arbnode/node.go +++ b/arbnode/node.go @@ -7,10 +7,6 @@ import ( "context" "errors" "fmt" - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/accounts/keystore" - "github.com/offchainlabs/nitro/cmd/genericconf" - "golang.org/x/term" "math/big" "os" "path/filepath" @@ -18,6 +14,11 @@ import ( "syscall" "time" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/offchainlabs/nitro/cmd/genericconf" + "golang.org/x/term" + "github.com/offchainlabs/nitro/util/headerreader" "github.com/ethereum/go-ethereum/rpc" @@ -670,18 +671,7 @@ func createNodeImpl( if err != nil { return nil, err } - - arbos.BatchFetcher = func(batchNum uint64, batchDataHash common.Hash) (batchData []byte, err error) { - bnum := new(big.Int).SetUint64(batchNum) - batches, err := sequencerInbox.LookupBatchesInRange(context.Background(), bnum, bnum) - if err != nil { - return nil, err - } - if len(batches) <= 1 { - return nil, errors.New("expected sequencer batch not found") - } - return batches[0].GetData(ctx, l1client) - } + txStreamer.SetInboxReader(inboxReader) nitroMachineConfig := validator.DefaultNitroMachineConfig if config.Wasm.RootPath != "" { diff --git a/arbnode/transaction_streamer.go b/arbnode/transaction_streamer.go index 36dfdb6fbe..2d808138a5 100644 --- a/arbnode/transaction_streamer.go +++ b/arbnode/transaction_streamer.go @@ -54,6 +54,7 @@ type TransactionStreamer struct { coordinator *SeqCoordinator broadcastServer *broadcaster.Broadcaster validator *validator.BlockValidator + inboxReader *InboxReader } func NewTransactionStreamer(db ethdb.Database, bc *core.BlockChain, broadcastServer *broadcaster.Broadcaster) (*TransactionStreamer, error) { @@ -96,6 +97,16 @@ func (s *TransactionStreamer) SetSeqCoordinator(coordinator *SeqCoordinator) { s.coordinator = coordinator } +func (s *TransactionStreamer) SetInboxReader(inboxReader *InboxReader) { + if s.Started() { + panic("trying to set inbox reader after start") + } + if s.inboxReader != nil { + panic("trying to set inbox reader when already set") + } + s.inboxReader = inboxReader +} + func (s *TransactionStreamer) cleanupInconsistentState() error { // If it doesn't exist yet, set the message count to 0 hasMessageCount, err := s.db.Has(messageCountKey) @@ -696,6 +707,10 @@ func (s *TransactionStreamer) createBlocks(ctx context.Context) error { } }() + batchFetcher := func(batchNum uint64) ([]byte, error) { + return s.inboxReader.GetSequencerMessageBytes(ctx, batchNum) + } + for pos < msgCount { statedb, err = s.bc.StateAt(lastBlockHeader.Root) @@ -720,14 +735,18 @@ func (s *TransactionStreamer) createBlocks(ctx context.Context) error { return err } - block, receipts := arbos.ProduceBlock( + block, receipts, err := arbos.ProduceBlock( msg.Message, msg.DelayedMessagesRead, lastBlockHeader, statedb, s.bc, s.bc.Config(), + batchFetcher, ) + if err != nil { + return err + } // ProduceBlock advances one message pos++ diff --git a/arbos/block_processor.go b/arbos/block_processor.go index 3722b982e2..5d8204d0e9 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -6,11 +6,12 @@ package arbos import ( "encoding/binary" "fmt" - "github.com/offchainlabs/nitro/arbos/l1pricing" "math" "math/big" "strconv" + "github.com/offchainlabs/nitro/arbos/l1pricing" + "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbos/util" @@ -99,6 +100,8 @@ func noopSequencingHooks() *SequencingHooks { } } +type FallibleBatchFetcher func(batchNum uint64) ([]byte, error) + func ProduceBlock( message *L1IncomingMessage, delayedMessagesRead uint64, @@ -106,17 +109,30 @@ func ProduceBlock( statedb *state.StateDB, chainContext core.ChainContext, chainConfig *params.ChainConfig, -) (*types.Block, types.Receipts) { - txes, err := message.ParseL2Transactions(chainConfig.ChainID) + batchFetcher FallibleBatchFetcher, +) (*types.Block, types.Receipts, error) { + var batchFetchErr error + txes, err := message.ParseL2Transactions(chainConfig.ChainID, func(batchNum uint64) []byte { + data, err := batchFetcher(batchNum) + if err != nil { + batchFetchErr = err + return nil + } + return data + }) + if batchFetchErr != nil { + return nil, nil, batchFetchErr + } if err != nil { log.Warn("error parsing incoming message", "err", err) txes = types.Transactions{} } hooks := noopSequencingHooks() - return ProduceBlockAdvanced( + block, receipts := ProduceBlockAdvanced( message.Header, txes, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, hooks, ) + return block, receipts, nil } // A bit more flexible than ProduceBlock for use in the sequencer. diff --git a/arbos/incomingmessage.go b/arbos/incomingmessage.go index 619550bbb2..ad627f9938 100644 --- a/arbos/incomingmessage.go +++ b/arbos/incomingmessage.go @@ -16,6 +16,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbos/util" ) @@ -170,7 +171,9 @@ func ParseIncomingL1Message(rd io.Reader) (*L1IncomingMessage, error) { }, nil } -func (msg *L1IncomingMessage) ParseL2Transactions(chainId *big.Int) (types.Transactions, error) { +type InfallibleBatchFetcher func(batchNum uint64) []byte + +func (msg *L1IncomingMessage) ParseL2Transactions(chainId *big.Int, batchFetcher InfallibleBatchFetcher) (types.Transactions, error) { if len(msg.L2msg) > MaxL2MessageSize { // ignore the message if l2msg is too large return nil, errors.New("message too large") @@ -222,7 +225,7 @@ func (msg *L1IncomingMessage) ParseL2Transactions(chainId *big.Int) (types.Trans log.Debug("ignoring rollup event message") return types.Transactions{}, nil case L1MessageType_BatchPostingReport: - tx, err := parseBatchPostingReportMessage(bytes.NewReader(msg.L2msg), chainId) + tx, err := parseBatchPostingReportMessage(bytes.NewReader(msg.L2msg), chainId, batchFetcher) if err != nil { return nil, err } @@ -503,7 +506,7 @@ func parseSubmitRetryableMessage(rd io.Reader, header *L1IncomingMessageHeader, return types.NewTx(tx), err } -func parseBatchPostingReportMessage(rd io.Reader, chainId *big.Int) (*types.Transaction, error) { +func parseBatchPostingReportMessage(rd io.Reader, chainId *big.Int, batchFetcher InfallibleBatchFetcher) (*types.Transaction, error) { batchTimestamp, err := util.HashFromReader(rd) if err != nil { return nil, err @@ -512,16 +515,26 @@ func parseBatchPostingReportMessage(rd io.Reader, chainId *big.Int) (*types.Tran if err != nil { return nil, err } - batchNum, err := util.HashFromReader(rd) + batchNumHash, err := util.HashFromReader(rd) if err != nil { return nil, err } + batchNum := batchNumHash.Big().Uint64() l1BaseFee, err := util.HashFromReader(rd) if err != nil { return nil, err } - data, err := util.PackInternalTxDataBatchPostingReport(batchTimestamp, batchPosterAddr, batchNum, l1BaseFee) + batchData := batchFetcher(batchNum) + var batchDataGas uint64 + for _, b := range batchData { + if b == 0 { + batchDataGas += params.TxDataZeroGas + } else { + batchDataGas += params.TxDataNonZeroGasEIP2028 + } + } + data, err := util.PackInternalTxDataBatchPostingReport(batchTimestamp, batchPosterAddr, batchNum, batchDataGas, l1BaseFee.Big()) if err != nil { return nil, err } diff --git a/arbos/incomingmessage_test.go b/arbos/incomingmessage_test.go index f5ac4b2dd6..4aa3c86c41 100644 --- a/arbos/incomingmessage_test.go +++ b/arbos/incomingmessage_test.go @@ -34,7 +34,7 @@ func TestSerializeAndParseL1Message(t *testing.T) { if err != nil { t.Error(err) } - txes, err := newMsg.ParseL2Transactions(chainId) + txes, err := newMsg.ParseL2Transactions(chainId, nil) if err != nil { t.Error(err) } diff --git a/arbos/internal_tx.go b/arbos/internal_tx.go index e578df2816..149469034f 100644 --- a/arbos/internal_tx.go +++ b/arbos/internal_tx.go @@ -5,10 +5,10 @@ package arbos import ( "fmt" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" "math/big" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -48,9 +48,7 @@ func InternalTxStartBlock( } } -type BatchFetcherFunc func(batchNum uint64, batchDataHash common.Hash) (batchData []byte, err error) - -func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.ArbosState, evm *vm.EVM, fetcher BatchFetcherFunc) { +func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.ArbosState, evm *vm.EVM) { switch tx.SubType { case arbInternalTxStartBlock: inputs, err := util.UnpackInternalTxDataStartBlock(tx.Data) @@ -102,23 +100,11 @@ func ApplyInternalTxUpdate(tx *types.ArbitrumInternalTx, state *arbosState.Arbos panic(err) } batchTimestamp, _ := inputs[0].(*big.Int) - // ignore input[1], batchPosterAddress, which exists because we might need it in the future - batchNumberBig, _ := inputs[2].(*big.Int) - batchNumber := batchNumberBig.Uint64() - batchDataHash, _ := inputs[3].(common.Hash) + // ignore input[1], batchPosterAddress, and input[2], batchNumber, which exist because we might need them in the future + batchDataGas, _ := inputs[3].(uint64) l1BaseFeeWei, _ := inputs[4].(*big.Int) - batchData, err := fetcher(batchNumber, batchDataHash) - if err != nil { - panic(err) - } - dataGas := params.TxDataNonZeroGasEIP2028 * uint64(len(batchData)) - for _, b := range batchData { - if b == 0 { - dataGas -= params.TxDataNonZeroGasEIP2028 - params.TxDataZeroGas - } - } - weiSpent := new(big.Int).Mul(l1BaseFeeWei, new(big.Int).SetUint64(dataGas)) + weiSpent := new(big.Int).Mul(l1BaseFeeWei, new(big.Int).SetUint64(batchDataGas)) err = state.L1PricingState().UpdateForSequencerSpending(evm.StateDB, batchTimestamp.Uint64(), evm.Context.Time.Uint64(), weiSpent) if err != nil { log.Warn("L1Pricing UpdateForSequencerSpending failed", "err", err) diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index 82f9ae23a9..cd5ae602c3 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -28,8 +28,6 @@ import ( var arbosAddress = types.ArbosAddress -var BatchFetcher BatchFetcherFunc - // A TxProcessor is created and freed for every L2 transaction. // It tracks state for ArbOS, allowing it infuence in Geth's tx processing. // Public fields are accessible in precompiles. @@ -57,7 +55,6 @@ func NewTxProcessor(evm *vm.EVM, msg core.Message) *TxProcessor { TopTxType: nil, evm: evm, CurrentRetryable: nil, - // TODO: initialize batchFetcher } } @@ -122,7 +119,7 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r if p.msg.From() != arbosAddress { return false, 0, errors.New("internal tx not from arbAddress"), nil } - ApplyInternalTxUpdate(tx, p.state, evm, BatchFetcher) + ApplyInternalTxUpdate(tx, p.state, evm) return true, 0, nil, nil case *types.ArbitrumSubmitRetryableTx: defer (startTracer())() diff --git a/arbstate/geth_test.go b/arbstate/geth_test.go index 33b11d5235..74b71b7e06 100644 --- a/arbstate/geth_test.go +++ b/arbstate/geth_test.go @@ -116,7 +116,7 @@ func RunMessagesThroughAPI(t *testing.T, msgs [][]byte, statedb *state.StateDB) if err != nil { t.Error(err) } - txes, err := msg.ParseL2Transactions(chainId) + txes, err := msg.ParseL2Transactions(chainId, nil) if err != nil { t.Error(err) } diff --git a/cmd/replay/main.go b/cmd/replay/main.go index 6ffdd1966a..251f0b5402 100644 --- a/cmd/replay/main.go +++ b/cmd/replay/main.go @@ -152,12 +152,14 @@ func main() { message := readMessage(chainConfig.ArbitrumChainParams.DataAvailabilityCommittee) - arbos.BatchFetcher = func(batchSeqNum uint64, batchHash common.Hash) ([]byte, error) { - return wavmio.ResolvePreImage(batchHash), nil - } - chainContext := WavmChainContext{} - newBlock, _ = arbos.ProduceBlock(message.Message, message.DelayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig) + batchFetcher := func(batchNum uint64) ([]byte, error) { + return wavmio.ReadInboxMessage(batchNum), nil + } + newBlock, _, err = arbos.ProduceBlock(message.Message, message.DelayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, batchFetcher) + if err != nil { + panic(err) + } } else { // Initialize ArbOS with this init message and create the genesis block. diff --git a/contracts/src/precompiles/ArbosActs.sol b/contracts/src/precompiles/ArbosActs.sol index b8fedc23c8..46bf402a74 100644 --- a/contracts/src/precompiles/ArbosActs.sol +++ b/contracts/src/precompiles/ArbosActs.sol @@ -41,8 +41,8 @@ interface ArbosActs { function batchPostingReport( uint256 batchTimestamp, address batchPosterAddress, - uint256 batchNumber, - bytes32 batchDataHash, + uint64 batchNumber, + uint64 batchDataGas, uint256 l1BaseFeeWei ) external; diff --git a/precompiles/ArbosActs.go b/precompiles/ArbosActs.go index a3b0439036..1edb485add 100644 --- a/precompiles/ArbosActs.go +++ b/precompiles/ArbosActs.go @@ -15,6 +15,6 @@ func (con ArbosActs) StartBlock(c ctx, evm mech, l1BaseFee, l2BaseFeeLastBlock h return con.CallerNotArbOSError() } -func (con ArbosActs) BatchPostingReport(c ctx, evm mech, batchTimestamp huge, batchPosterAddress addr, batchNumber huge, batchDataHash hash, l1BaseFeeWei huge) error { +func (con ArbosActs) BatchPostingReport(c ctx, evm mech, batchTimestamp huge, batchPosterAddress addr, batchNumber uint64, batchDataGas uint64, l1BaseFeeWei huge) error { return con.CallerNotArbOSError() } diff --git a/system_tests/replay_fuzz/replay_fuzz.go b/system_tests/replay_fuzz/replay_fuzz.go index f296e082de..55cc3bf261 100644 --- a/system_tests/replay_fuzz/replay_fuzz.go +++ b/system_tests/replay_fuzz/replay_fuzz.go @@ -29,6 +29,7 @@ func BuildBlock( chainContext core.ChainContext, chainConfig *params.ChainConfig, inbox arbstate.InboxBackend, + seqBatch []byte, ) (*types.Block, error) { var delayedMessagesRead uint64 if lastBlockHeader != nil { @@ -45,10 +46,13 @@ func BuildBlock( delayedMessagesRead = inboxMultiplexer.DelayedMessagesRead() l1Message := message.Message - block, _ := arbos.ProduceBlock( - l1Message, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, + batchFetcher := func(uint64) ([]byte, error) { + return seqBatch, nil + } + block, _, err := arbos.ProduceBlock( + l1Message, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, batchFetcher, ) - return block, nil + return block, err } // A simple mock inbox multiplexer backend @@ -142,7 +146,7 @@ func Fuzz(input []byte) int { positionWithinMessage: 0, delayedMessages: delayedMessages, } - _, err = BuildBlock(statedb, genesis, noopChainContext{}, params.ArbitrumOneChainConfig(), inbox) + _, err = BuildBlock(statedb, genesis, noopChainContext{}, params.ArbitrumOneChainConfig(), inbox, seqBatch) if err != nil { // With the fixed header it shouldn't be possible to read a delayed message, // and no other type of error should be possible. diff --git a/system_tests/retryable_test.go b/system_tests/retryable_test.go index f15180cf03..f5d430368b 100644 --- a/system_tests/retryable_test.go +++ b/system_tests/retryable_test.go @@ -53,7 +53,7 @@ func retryableSetup(t *testing.T) ( if len(messages) != 1 { Fail(t, "expected 1 message from retryable submission, found", len(messages)) } - txs, err := messages[0].Message.ParseL2Transactions(params.ArbitrumDevTestChainConfig().ChainID) + txs, err := messages[0].Message.ParseL2Transactions(params.ArbitrumDevTestChainConfig().ChainID, nil) Require(t, err) if len(txs) != 1 { Fail(t, "expected 1 tx from retryable submission, found", len(txs)) diff --git a/validator/block_validator.go b/validator/block_validator.go index bb25f24597..594ea1a7be 100644 --- a/validator/block_validator.go +++ b/validator/block_validator.go @@ -7,7 +7,6 @@ import ( "context" "encoding/binary" "fmt" - "github.com/offchainlabs/nitro/arbos" "os" "path/filepath" "runtime" @@ -205,13 +204,13 @@ func (v *BlockValidator) readLastBlockValidatedDbInfo() error { return nil } -func (v *BlockValidator) prepareBlock(header *types.Header, prevHeader *types.Header, msg arbstate.MessageWithMetadata, validationStatus *validationStatus, batchFetcher arbos.BatchFetcherFunc) { - preimages, hasDelayedMessage, delayedMsgToRead, err := BlockDataForValidation(v.blockchain, header, prevHeader, msg, v.config.StorePreimages, batchFetcher) +func (v *BlockValidator) prepareBlock(ctx context.Context, header *types.Header, prevHeader *types.Header, msg arbstate.MessageWithMetadata, validationStatus *validationStatus) { + preimages, readBatchInfo, hasDelayedMessage, delayedMsgToRead, err := BlockDataForValidation(ctx, v.blockchain, v.inboxReader, header, prevHeader, msg, v.config.StorePreimages) if err != nil { log.Error("failed to set up validation", "err", err, "header", header, "prevHeader", prevHeader) return } - validationEntry, err := newValidationEntry(prevHeader, header, hasDelayedMessage, delayedMsgToRead, preimages) + validationEntry, err := newValidationEntry(prevHeader, header, hasDelayedMessage, delayedMsgToRead, preimages, readBatchInfo) if err != nil { log.Error("failed to create validation entry", "err", err, "header", header, "prevHeader", prevHeader) return @@ -256,10 +255,7 @@ func (v *BlockValidator) NewBlock(block *types.Block, prevHeader *types.Header, if v.nextValidationEntryBlock <= blockNum { v.nextValidationEntryBlock = blockNum + 1 } - batchFetcher := func(batchSeqNum uint64, batchHash common.Hash) ([]byte, error) { - return v.inboxReader.GetSequencerMessageBytes(context.Background(), batchSeqNum) - } - v.LaunchUntrackedThread(func() { v.prepareBlock(block.Header(), prevHeader, msg, status, batchFetcher) }) + v.LaunchUntrackedThread(func() { v.prepareBlock(context.Background(), block.Header(), prevHeader, msg, status) }) } var launchTime = time.Now().Format("2006_01_02__15_04") @@ -402,10 +398,14 @@ func (v *BlockValidator) validate(ctx context.Context, validationStatus *validat default: } }() + entry.BatchInfo = append(entry.BatchInfo, BatchInfo{ + Number: entry.StartPosition.BatchNumber, + Data: seqMsg, + }) log.Info("starting validation for block", "blockNr", entry.BlockNumber) for _, moduleRoot := range validationStatus.ModuleRoots { before := time.Now() - gsEnd, delayedMsg, err := v.executeBlock(ctx, entry, seqMsg, moduleRoot) + gsEnd, delayedMsg, err := v.executeBlock(ctx, entry, moduleRoot) duration := time.Since(before) if err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { diff --git a/validator/challenge_manager.go b/validator/challenge_manager.go index 6712174142..2b66cb2a29 100644 --- a/validator/challenge_manager.go +++ b/validator/challenge_manager.go @@ -7,9 +7,10 @@ import ( "context" "encoding/binary" "fmt" - "github.com/offchainlabs/nitro/arbstate" "math/big" + "github.com/offchainlabs/nitro/arbstate" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" @@ -416,15 +417,14 @@ func (m *ChallengeManager) createInitialMachine(ctx context.Context, blockNum in if err != nil { return err } - batchFetcher := func(batchSeqNum uint64, _ common.Hash) ([]byte, error) { - return m.inboxReader.GetSequencerMessageBytes(context.Background(), batchSeqNum) - } + var batchInfo []BatchInfo if tooFar { // Just record the part of block creation before the message is read - _, preimages, err := RecordBlockCreation(m.blockchain, blockHeader, nil, batchFetcher) + _, preimages, readBatchInfo, err := RecordBlockCreation(ctx, m.blockchain, m.inboxReader, blockHeader, nil) if err != nil { return err } + batchInfo = readBatchInfo err = SetMachinePreimageResolver(ctx, machine, preimages, nil, m.blockchain, m.das) if err != nil { return err @@ -443,7 +443,7 @@ func (m *ChallengeManager) createInitialMachine(ctx context.Context, blockNum in if nextHeader == nil { return fmt.Errorf("next block header %v after challenge point unknown", blockNum+1) } - preimages, hasDelayedMsg, delayedMsgNr, err := BlockDataForValidation(m.blockchain, nextHeader, blockHeader, message, false, batchFetcher) + preimages, readBatchInfo, hasDelayedMsg, delayedMsgNr, err := BlockDataForValidation(ctx, m.blockchain, m.inboxReader, nextHeader, blockHeader, message, false) if err != nil { return err } @@ -451,7 +451,12 @@ func (m *ChallengeManager) createInitialMachine(ctx context.Context, blockNum in if err != nil { return err } - err = SetMachinePreimageResolver(ctx, machine, preimages, batchBytes, m.blockchain, m.das) + readBatchInfo = append(readBatchInfo, BatchInfo{ + Number: startGlobalState.Batch, + Data: batchBytes, + }) + batchInfo = readBatchInfo + err = SetMachinePreimageResolver(ctx, machine, preimages, batchInfo, m.blockchain, m.das) if err != nil { return err } @@ -465,7 +470,9 @@ func (m *ChallengeManager) createInitialMachine(ctx context.Context, blockNum in return err } } - err = machine.AddSequencerInboxMessage(startGlobalState.Batch, batchBytes) + } + for _, batch := range batchInfo { + err = machine.AddSequencerInboxMessage(batch.Number, batch.Data) if err != nil { return err } diff --git a/validator/stateless_block_validator.go b/validator/stateless_block_validator.go index 18b1ab1617..af9f276221 100644 --- a/validator/stateless_block_validator.go +++ b/validator/stateless_block_validator.go @@ -4,10 +4,9 @@ package validator import ( - "bytes" "context" "fmt" - "github.com/ethereum/go-ethereum/crypto" + "github.com/offchainlabs/nitro/arbutil" "github.com/ethereum/go-ethereum/arbitrum" @@ -138,6 +137,7 @@ type validationEntry struct { StartPosition GlobalStatePosition EndPosition GlobalStatePosition Preimages map[common.Hash][]byte + BatchInfo []BatchInfo } func (v *validationEntry) start() GoGlobalState { @@ -166,6 +166,7 @@ func newValidationEntry( hasDelayed bool, delayedMsgNr uint64, preimages map[common.Hash][]byte, + batchInfo []BatchInfo, ) (*validationEntry, error) { extraInfo, err := types.DeserializeHeaderExtraInformation(header) if err != nil { @@ -185,6 +186,7 @@ func newValidationEntry( HasDelayedMsg: hasDelayed, DelayedMsgNr: delayedMsgNr, Preimages: preimages, + BatchInfo: batchInfo, }, nil } @@ -214,11 +216,16 @@ func NewStatelessBlockValidator( return validator, nil } +type BatchInfo struct { + Number uint64 + Data []byte +} + // If msg is nil, this will record block creation up to the point where message would be accessed (for a "too far" proof) -func RecordBlockCreation(blockchain *core.BlockChain, prevHeader *types.Header, msg *arbstate.MessageWithMetadata, batchFetcher arbos.BatchFetcherFunc) (common.Hash, map[common.Hash][]byte, error) { +func RecordBlockCreation(ctx context.Context, blockchain *core.BlockChain, inboxReader InboxReaderInterface, prevHeader *types.Header, msg *arbstate.MessageWithMetadata) (common.Hash, map[common.Hash][]byte, []BatchInfo, error) { recordingdb, chaincontext, recordingKV, err := arbitrum.PrepareRecording(blockchain, prevHeader) if err != nil { - return common.Hash{}, nil, err + return common.Hash{}, nil, nil, err } chainConfig := blockchain.Config() @@ -228,52 +235,51 @@ func RecordBlockCreation(blockchain *core.BlockChain, prevHeader *types.Header, if prevHeader != nil { initialArbosState, err := arbosState.OpenSystemArbosState(recordingdb, nil, true) if err != nil { - return common.Hash{}, nil, fmt.Errorf("error opening initial ArbOS state: %w", err) + return common.Hash{}, nil, nil, fmt.Errorf("error opening initial ArbOS state: %w", err) } chainId, err := initialArbosState.ChainId() if err != nil { - return common.Hash{}, nil, fmt.Errorf("error getting chain ID from initial ArbOS state: %w", err) + return common.Hash{}, nil, nil, fmt.Errorf("error getting chain ID from initial ArbOS state: %w", err) } if chainId.Cmp(chainConfig.ChainID) != 0 { - return common.Hash{}, nil, fmt.Errorf("unexpected chain ID %v in ArbOS state, expected %v", chainId, chainConfig.ChainID) + return common.Hash{}, nil, nil, fmt.Errorf("unexpected chain ID %v in ArbOS state, expected %v", chainId, chainConfig.ChainID) } } - oldBlockPreimages := make(map[[32]byte][]byte) - arbos.BatchFetcher = func(batchSeqNum uint64, batchHash common.Hash) ([]byte, error) { - batchData, err := batchFetcher(batchSeqNum, batchHash) - if err != nil { - return nil, err - } - if !bytes.Equal(crypto.Keccak256(batchData), batchHash[:]) { - return nil, errors.New("batch data mismatch") - } - oldBlockPreimages[batchHash] = batchData - return batchData, nil - } - var blockHash common.Hash + var readBatchInfo []BatchInfo if msg != nil { - block, _ := arbos.ProduceBlock( + batchFetcher := func(batchNum uint64) ([]byte, error) { + data, err := inboxReader.GetSequencerMessageBytes(ctx, batchNum) + if err != nil { + return nil, err + } + readBatchInfo = append(readBatchInfo, BatchInfo{ + Number: batchNum, + Data: data, + }) + return data, nil + } + block, _, err := arbos.ProduceBlock( msg.Message, msg.DelayedMessagesRead, prevHeader, recordingdb, chaincontext, chainConfig, + batchFetcher, ) + if err != nil { + return common.Hash{}, nil, nil, err + } blockHash = block.Hash() } preimages, err := arbitrum.PreimagesFromRecording(chaincontext, recordingKV) - for h, d := range oldBlockPreimages { - preimages[h] = d - } - - return blockHash, preimages, err + return blockHash, preimages, readBatchInfo, err } -func BlockDataForValidation(blockchain *core.BlockChain, header, prevHeader *types.Header, msg arbstate.MessageWithMetadata, producePreimages bool, batchFetcher arbos.BatchFetcherFunc) (preimages map[common.Hash][]byte, hasDelayedMessage bool, delayedMsgNr uint64, err error) { +func BlockDataForValidation(ctx context.Context, blockchain *core.BlockChain, inboxReader InboxReaderInterface, header, prevHeader *types.Header, msg arbstate.MessageWithMetadata, producePreimages bool) (preimages map[common.Hash][]byte, readBatchInfo []BatchInfo, hasDelayedMessage bool, delayedMsgNr uint64, err error) { var prevHash common.Hash if prevHeader != nil { prevHash = prevHeader.Hash() @@ -285,7 +291,7 @@ func BlockDataForValidation(blockchain *core.BlockChain, header, prevHeader *typ if prevHeader != nil && producePreimages { var blockhash common.Hash - blockhash, preimages, err = RecordBlockCreation(blockchain, prevHeader, &msg, batchFetcher) + blockhash, preimages, readBatchInfo, err = RecordBlockCreation(ctx, blockchain, inboxReader, prevHeader, &msg) if err != nil { return } @@ -305,23 +311,25 @@ func BlockDataForValidation(blockchain *core.BlockChain, header, prevHeader *typ return } -func SetMachinePreimageResolver(ctx context.Context, mach *ArbitratorMachine, preimages map[common.Hash][]byte, seqMsg []byte, bc *core.BlockChain, das arbstate.SimpleDASReader) error { +func SetMachinePreimageResolver(ctx context.Context, mach *ArbitratorMachine, preimages map[common.Hash][]byte, batchInfo []BatchInfo, bc *core.BlockChain, das arbstate.SimpleDASReader) error { recordNewPreimages := true if preimages == nil { preimages = make(map[common.Hash][]byte) recordNewPreimages = false } - if arbstate.IsDASMessageHeaderByte(seqMsg[40]) { - if das == nil { - log.Error("No DAS configured, but sequencer message found with DAS header") - if bc.Config().ArbitrumChainParams.DataAvailabilityCommittee { - return errors.New("processing data availability chain without DAS configured") - } - } else { - _, err := arbstate.RecoverPayloadFromDasBatch(ctx, seqMsg, das, preimages) - if err != nil { - return err + for _, batch := range batchInfo { + if arbstate.IsDASMessageHeaderByte(batch.Data[40]) { + if das == nil { + log.Error("No DAS configured, but sequencer message found with DAS header") + if bc.Config().ArbitrumChainParams.DataAvailabilityCommittee { + return errors.New("processing data availability chain without DAS configured") + } + } else { + _, err := arbstate.RecoverPayloadFromDasBatch(ctx, batch.Data, das, preimages) + if err != nil { + return err + } } } } @@ -354,7 +362,7 @@ func SetMachinePreimageResolver(ctx context.Context, mach *ArbitratorMachine, pr }) } -func (v *StatelessBlockValidator) executeBlock(ctx context.Context, entry *validationEntry, seqMsg []byte, moduleRoot common.Hash) (GoGlobalState, []byte, error) { +func (v *StatelessBlockValidator) executeBlock(ctx context.Context, entry *validationEntry, moduleRoot common.Hash) (GoGlobalState, []byte, error) { start := entry.StartPosition gsStart := entry.start() @@ -363,7 +371,7 @@ func (v *StatelessBlockValidator) executeBlock(ctx context.Context, entry *valid return GoGlobalState{}, nil, fmt.Errorf("unabled to get WASM machine: %w", err) } mach := basemachine.Clone() - err = SetMachinePreimageResolver(ctx, mach, entry.Preimages, seqMsg, v.blockchain, v.daService) + err = SetMachinePreimageResolver(ctx, mach, entry.Preimages, entry.BatchInfo, v.blockchain, v.daService) if err != nil { return GoGlobalState{}, nil, err } @@ -372,10 +380,12 @@ func (v *StatelessBlockValidator) executeBlock(ctx context.Context, entry *valid log.Error("error while setting global state for proving", "err", err, "gsStart", gsStart) return GoGlobalState{}, nil, errors.New("error while setting global state for proving") } - err = mach.AddSequencerInboxMessage(start.BatchNumber, seqMsg) - if err != nil { - log.Error("error while trying to add sequencer msg for proving", "err", err, "seq", start.BatchNumber, "blockNr", entry.BlockNumber) - return GoGlobalState{}, nil, errors.New("error while trying to add sequencer msg for proving") + for _, batch := range entry.BatchInfo { + err = mach.AddSequencerInboxMessage(batch.Number, batch.Data) + if err != nil { + log.Error("error while trying to add sequencer msg for proving", "err", err, "seq", start.BatchNumber, "blockNr", entry.BlockNumber) + return GoGlobalState{}, nil, errors.New("error while trying to add sequencer msg for proving") + } } var delayedMsg []byte if entry.HasDelayedMsg { @@ -410,7 +420,7 @@ func (v *StatelessBlockValidator) executeBlock(ctx context.Context, entry *valid return mach.GetGlobalState(), delayedMsg, nil } -func (v *StatelessBlockValidator) ValidateBlock(ctx context.Context, header *types.Header, moduleRoot common.Hash, batchFetcher arbos.BatchFetcherFunc) (bool, error) { +func (v *StatelessBlockValidator) ValidateBlock(ctx context.Context, header *types.Header, moduleRoot common.Hash) (bool, error) { if header == nil { return false, errors.New("header not found") } @@ -424,7 +434,7 @@ func (v *StatelessBlockValidator) ValidateBlock(ctx context.Context, header *typ if err != nil { return false, err } - preimages, hasDelayedMessage, delayedMsgToRead, err := BlockDataForValidation(v.blockchain, header, prevHeader, msg, false, batchFetcher) + preimages, readBatchInfo, hasDelayedMessage, delayedMsgToRead, err := BlockDataForValidation(ctx, v.blockchain, v.inboxReader, header, prevHeader, msg, false) if err != nil { return false, fmt.Errorf("failed to get block data to validate: %w", err) } @@ -443,7 +453,7 @@ func (v *StatelessBlockValidator) ValidateBlock(ctx context.Context, header *typ return false, fmt.Errorf("failed calculating position for validation: %w", err) } - entry, err := newValidationEntry(prevHeader, header, hasDelayedMessage, delayedMsgToRead, preimages) + entry, err := newValidationEntry(prevHeader, header, hasDelayedMessage, delayedMsgToRead, preimages, readBatchInfo) if err != nil { return false, fmt.Errorf("failed to create validation entry %w", err) } @@ -454,8 +464,12 @@ func (v *StatelessBlockValidator) ValidateBlock(ctx context.Context, header *typ if err != nil { return false, err } + entry.BatchInfo = append(entry.BatchInfo, BatchInfo{ + Number: startPos.BatchNumber, + Data: seqMsg, + }) - gsEnd, _, err := v.executeBlock(ctx, entry, seqMsg, moduleRoot) + gsEnd, _, err := v.executeBlock(ctx, entry, moduleRoot) if err != nil { return false, err }