From c11446ed449a3c42d0e29dec11cbbb84c04671b3 Mon Sep 17 00:00:00 2001 From: JianGuo Date: Wed, 3 Apr 2024 14:38:27 +0800 Subject: [PATCH] Add cdk rpc (#163) * update * update * update * Add cdk rpc api (#164) * update * update * update * update * update --- dataavailability/dataavailability.go | 110 ++++---- .../datacommittee/datacommittee.go | 20 +- dataavailability/interfaces.go | 41 ++- etherman/etherman_xlayer.go | 238 +++++++++--------- jsonrpc/client/zkevm.go | 36 +++ jsonrpc/endpoints_zkevm.go | 36 +++ jsonrpc/endpoints_zkevm.openrpc.json | 35 ++- jsonrpc/mocks/mock_state_xlayer.go | 32 ++- jsonrpc/types/interfaces.go | 1 + jsonrpc/types/types.go | 17 ++ state/interfaces.go | 1 + state/mocks/mock_storage_xlayer.go | 4 + state/pgstatestorage/batch_xlayer.go | 24 -- state/pgstatestorage/pgstatestorage_xlayer.go | 55 ++++ 14 files changed, 439 insertions(+), 211 deletions(-) delete mode 100644 state/pgstatestorage/batch_xlayer.go create mode 100644 state/pgstatestorage/pgstatestorage_xlayer.go diff --git a/dataavailability/dataavailability.go b/dataavailability/dataavailability.go index 1ca09a39ef..846cbb19c7 100644 --- a/dataavailability/dataavailability.go +++ b/dataavailability/dataavailability.go @@ -7,12 +7,11 @@ import ( "github.com/0xPolygonHermez/zkevm-node/etherman/types" "github.com/0xPolygonHermez/zkevm-node/log" - "github.com/0xPolygonHermez/zkevm-node/state" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) -const unexpectedHashTemplate = "missmatch on transaction data for batch num %d. Expected hash %s, actual hash: %s" +const unexpectedHashTemplate = "mismatch on transaction data for batch num %d. Expected hash %s, actual hash: %s" // DataAvailability implements an abstract data availability integration type DataAvailability struct { @@ -60,57 +59,82 @@ func (d *DataAvailability) PostSequence(ctx context.Context, sequences []types.S // 1. From local DB // 2. From Sequencer // 3. From DA backend -func (d *DataAvailability) GetBatchL2Data(batchNum uint64, expectedTransactionsHash common.Hash) ([]byte, error) { - found := true - transactionsData, err := d.state.GetBatchL2DataByNumber(d.ctx, batchNum, nil) - if err != nil { - if err == state.ErrNotFound { - found = false +func (d *DataAvailability) GetBatchL2Data(batchNums []uint64, batchHashes []common.Hash, dataAvailabilityMessage []byte) ([][]byte, error) { + if len(batchNums) != len(batchHashes) { + return nil, fmt.Errorf("invalid L2 batch data retrieval arguments, %d != %d", len(batchNums), len(batchHashes)) + } + + data, err := d.localData(batchNums, batchHashes) + if err == nil { + return data, nil + } + + if !d.isTrustedSequencer { + log.Infof("Try to get data from trusted sequencer for batches %v", batchNums) + data, err = d.trustedSequencerData(batchNums, batchHashes) + if err != nil { + log.Warnf("trusted sequencer failed to return data for batches %v: %s", batchNums, err.Error()) } else { - return nil, fmt.Errorf("failed to get batch data from state for batch num %d: %w", batchNum, err) + return data, nil } } - actualTransactionsHash := crypto.Keccak256Hash(transactionsData) - if !found || expectedTransactionsHash != actualTransactionsHash { - if found { - log.Warnf(unexpectedHashTemplate, batchNum, expectedTransactionsHash, actualTransactionsHash) - } + log.Infof("Try to get data from DA backend for batches %v", batchNums) + return d.backend.GetSequence(d.ctx, batchHashes, dataAvailabilityMessage) +} - if !d.isTrustedSequencer { - log.Info("trying to get data from trusted sequencer") - data, err := d.getDataFromTrustedSequencer(batchNum, expectedTransactionsHash) - if err != nil { - log.Warn("failed to get data from trusted sequencer: %w", err) - } else { - return data, nil - } +// localData retrieves batches from local database and returns an error unless all are found +func (d *DataAvailability) localData(numbers []uint64, hashes []common.Hash) ([][]byte, error) { + data, err := d.state.GetBatchL2DataByNumbers(d.ctx, numbers, nil) + if err != nil { + return nil, err + } + var batches [][]byte + for i := 0; i < len(numbers); i++ { + batchNumber := numbers[i] + expectedHash := hashes[i] + batchData, ok := data[batchNumber] + if !ok { + return nil, fmt.Errorf("missing batch %v", batchNumber) } - - log.Info("trying to get data from the data availability backend") - data, err := d.backend.GetBatchL2Data(batchNum, expectedTransactionsHash) - if err != nil { - log.Error("failed to get data from the data availability backend: %w", err) - if d.isTrustedSequencer { - return nil, fmt.Errorf("data not found on the local DB nor on any data committee member") - } else { - return nil, fmt.Errorf("data not found on the local DB, nor from the trusted sequencer nor on any data committee member") - } + actualHash := crypto.Keccak256Hash(batchData) + if actualHash != expectedHash { + err = fmt.Errorf(unexpectedHashTemplate, batchNumber, expectedHash, actualHash) + log.Warnf("wrong local data for hash: %s", err.Error()) + return nil, err + } else { + batches = append(batches, batchData) } - return data, nil } - return transactionsData, nil + return batches, nil } -func (d *DataAvailability) getDataFromTrustedSequencer(batchNum uint64, expectedTransactionsHash common.Hash) ([]byte, error) { - b, err := d.zkEVMClient.BatchByNumber(d.ctx, new(big.Int).SetUint64(batchNum)) +// trustedSequencerData retrieved batch data from the trusted sequencer and returns an error unless all are found +func (d *DataAvailability) trustedSequencerData(batchNums []uint64, expectedHashes []common.Hash) ([][]byte, error) { + if len(batchNums) != len(expectedHashes) { + return nil, fmt.Errorf("invalid arguments, len of batch numbers does not equal length of expected hashes: %d != %d", + len(batchNums), len(expectedHashes)) + } + var nums []*big.Int + for _, n := range batchNums { + nums = append(nums, new(big.Int).SetUint64(n)) + } + batchData, err := d.zkEVMClient.BatchesByNumbers(d.ctx, nums) if err != nil { - return nil, fmt.Errorf("failed to get batch num %d from trusted sequencer: %w", batchNum, err) + return nil, err } - actualTransactionsHash := crypto.Keccak256Hash(b.BatchL2Data) - if expectedTransactionsHash != actualTransactionsHash { - return nil, fmt.Errorf( - unexpectedHashTemplate, batchNum, expectedTransactionsHash, actualTransactionsHash, - ) + if len(batchData) != len(batchNums) { + return nil, fmt.Errorf("missing batch data, expected %d, got %d", len(batchNums), len(batchData)) + } + var result [][]byte + for i := 0; i < len(batchNums); i++ { + number := batchNums[i] + batch := batchData[i] + expectedTransactionsHash := expectedHashes[i] + actualTransactionsHash := crypto.Keccak256Hash(batch.BatchL2Data) + if expectedTransactionsHash != actualTransactionsHash { + return nil, fmt.Errorf(unexpectedHashTemplate, number, expectedTransactionsHash, actualTransactionsHash) + } + result = append(result, batch.BatchL2Data) } - return b.BatchL2Data, nil + return result, nil } diff --git a/dataavailability/datacommittee/datacommittee.go b/dataavailability/datacommittee/datacommittee.go index 13c946fb6c..4adc74fd65 100644 --- a/dataavailability/datacommittee/datacommittee.go +++ b/dataavailability/datacommittee/datacommittee.go @@ -20,7 +20,7 @@ import ( "golang.org/x/net/context" ) -const unexpectedHashTemplate = "missmatch on transaction data for batch num %d. Expected hash %s, actual hash: %s" +const unexpectedHashTemplate = "missmatch on transaction data. Expected hash %s, actual hash: %s" // DataCommitteeMember represents a member of the Data Committee type DataCommitteeMember struct { @@ -87,8 +87,22 @@ func (d *DataCommitteeBackend) Init() error { return nil } +// GetSequence gets backend data one hash at a time. This should be optimized on the DAC side to get them all at once. +func (d *DataCommitteeBackend) GetSequence(ctx context.Context, hashes []common.Hash, dataAvailabilityMessage []byte) ([][]byte, error) { + // TODO: optimize this on the DAC side by implementing a multi batch retrieve api + var batchData [][]byte + for _, h := range hashes { + data, err := d.GetBatchL2Data(h) + if err != nil { + return nil, err + } + batchData = append(batchData, data) + } + return batchData, nil +} + // GetBatchL2Data returns the data from the DAC. It checks that it matches with the expected hash -func (d *DataCommitteeBackend) GetBatchL2Data(batchNum uint64, hash common.Hash) ([]byte, error) { +func (d *DataCommitteeBackend) GetBatchL2Data(hash common.Hash) ([]byte, error) { intialMember := d.selectedCommitteeMember found := false for !found && intialMember != -1 { @@ -110,7 +124,7 @@ func (d *DataCommitteeBackend) GetBatchL2Data(batchNum uint64, hash common.Hash) actualTransactionsHash := crypto.Keccak256Hash(data) if actualTransactionsHash != hash { unexpectedHash := fmt.Errorf( - unexpectedHashTemplate, batchNum, hash, actualTransactionsHash, + unexpectedHashTemplate, hash, actualTransactionsHash, ) log.Warnf( "error getting data from DAC node %s at %s: %s", diff --git a/dataavailability/interfaces.go b/dataavailability/interfaces.go index 441829fafb..d45b41b358 100644 --- a/dataavailability/interfaces.go +++ b/dataavailability/interfaces.go @@ -10,34 +10,49 @@ import ( "github.com/jackc/pgx/v4" ) +// DABackender is an interface for components that store and retrieve batch data +type DABackender interface { + SequenceRetriever + SequenceSender + // Init initializes the DABackend + Init() error +} + +// SequenceSender is used to send provided sequence of batches +type SequenceSender interface { + // PostSequence sends the sequence data to the data availability backend, and returns the dataAvailabilityMessage + // as expected by the contract + PostSequence(ctx context.Context, batchesData [][]byte) ([]byte, error) +} + +// SequenceRetriever is used to retrieve batch data +type SequenceRetriever interface { + // GetSequence retrieves the sequence data from the data availability backend + GetSequence(ctx context.Context, batchHashes []common.Hash, dataAvailabilityMessage []byte) ([][]byte, error) +} + +// === Internal interfaces === + type stateInterface interface { GetBatchL2DataByNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) ([]byte, error) + GetBatchL2DataByNumbers(ctx context.Context, batchNumbers []uint64, dbTx pgx.Tx) (map[uint64][]byte, error) GetBatchByNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*state.Batch, error) } // BatchDataProvider is used to retrieve batch data type BatchDataProvider interface { // GetBatchL2Data retrieve the data of a batch from the DA backend. The returned data must be the pre-image of the hash - GetBatchL2Data(batchNum uint64, hash common.Hash) ([]byte, error) + GetBatchL2Data(batchNum []uint64, batchHashes []common.Hash, dataAvailabilityMessage []byte) ([][]byte, error) } -// SequenceSender is used to send provided sequence of batches -type SequenceSender interface { - // PostSequence sends the sequence data to the data availability backend, and returns the dataAvailabilityMessage - // as expected by the contract - PostSequence(ctx context.Context, batchesData [][]byte) ([]byte, error) -} - -// DABackender is the interface needed to implement in order to -// integrate a DA service -type DABackender interface { +// DataManager is an interface for components that send and retrieve batch data +type DataManager interface { BatchDataProvider SequenceSender - // Init initializes the DABackend - Init() error } // ZKEVMClientTrustedBatchesGetter contains the methods required to interact with zkEVM-RPC type ZKEVMClientTrustedBatchesGetter interface { BatchByNumber(ctx context.Context, number *big.Int) (*types.Batch, error) + BatchesByNumbers(ctx context.Context, numbers []*big.Int) ([]*types.BatchData, error) } diff --git a/etherman/etherman_xlayer.go b/etherman/etherman_xlayer.go index 3d67886841..0714f68310 100644 --- a/etherman/etherman_xlayer.go +++ b/etherman/etherman_xlayer.go @@ -1,6 +1,7 @@ package etherman import ( + "bytes" "context" "crypto/ecdsa" "encoding/json" @@ -107,6 +108,10 @@ var ( methodIDSequenceBatchesEtrog = []byte{0xec, 0xef, 0x3f, 0x99} // nolint:unused // 0xecef3f99 // methodIDSequenceBatchesElderberry: MethodID for sequenceBatches in Elderberry methodIDSequenceBatchesElderberry = []byte{0xde, 0xf5, 0x7e, 0x54} // nolint:unused // 0xdef57e54 sequenceBatches((bytes,bytes32,uint64,bytes32)[],uint64,uint64,address) + // methodIDSequenceBatchesValidiumEtrog: MethodID for sequenceBatchesValidium in Etrog + methodIDSequenceBatchesValidiumEtrog = []byte{0x2d, 0x72, 0xc2, 0x48} // 0x2d72c248 sequenceBatchesValidium((bytes32,bytes32,uint64,bytes32)[],address,bytes) + // methodIDSequenceBatchesValidiumElderberry: MethodID for sequenceBatchesValidium in Elderberry + methodIDSequenceBatchesValidiumElderberry = []byte{0xdb, 0x5b, 0x0e, 0xd7} // 0xdb5b0ed7 sequenceBatchesValidium((bytes32,bytes32,uint64,bytes32)[],uint64,uint64,address,bytes) // ErrNotFound is used when the object is not found ErrNotFound = errors.New("not found") @@ -246,6 +251,7 @@ func NewClient(cfg Config, l1Config L1Config) (*Client, error) { } pol, err := pol.NewPol(l1Config.PolAddr, ethClient) if err != nil { + log.Errorf("error creating NewPol client (%s). Error: %w", l1Config.PolAddr.String(), err) return nil, err } dapAddr, err := zkevm.DataAvailabilityProtocol(&bind.CallOpts{Pending: false}) @@ -286,8 +292,8 @@ func NewClient(cfg Config, l1Config L1Config) (*Client, error) { RollupManager: rollupManager, Pol: pol, GlobalExitRootManager: globalExitRoot, - OldGlobalExitRootManager: oldGlobalExitRoot, DAProtocol: dap, + OldGlobalExitRootManager: oldGlobalExitRoot, SCAddresses: scAddresses, RollupID: rollupID, GasProviders: externalGasProviders{ @@ -357,6 +363,11 @@ func (etherMan *Client) GetForks(ctx context.Context, genBlockNumber uint64, las log.Debug("Getting forkIDs from blockNumber: ", genBlockNumber) start := time.Now() var logs []types.Log + + if lastL1BlockSynced < genBlockNumber { + lastL1BlockSynced = genBlockNumber + } + log.Debug("Using ForkIDChunkSize: ", etherMan.cfg.ForkIDChunkSize) for i := genBlockNumber; i <= lastL1BlockSynced; i = i + etherMan.cfg.ForkIDChunkSize + 1 { final := i + etherMan.cfg.ForkIDChunkSize @@ -1222,22 +1233,23 @@ func (etherMan *Client) sequencedBatchesEvent(ctx context.Context, vLog types.Lo var sequences []SequencedBatch if sb.NumBatch != 1 { - //methodId := tx.Data()[:4] - //log.Debugf("MethodId: %s", common.Bytes2Hex(methodId)) - //if bytes.Equal(methodId, methodIDSequenceBatchesEtrog) { - //sequences, err = decodeSequencesEtrog(tx.Data(), sb.NumBatch, msg.From, vLog.TxHash, msg.Nonce, sb.L1InfoRoot, etherMan.da) - //if err != nil { - // return fmt.Errorf("error decoding the sequences (etrog): %v", err) - //} - //} else if bytes.Equal(methodId, methodIDSequenceBatchesElderberry) { - sequences, err = decodeSequencesElderberry(tx.Data(), sb.NumBatch, msg.From, vLog.TxHash, msg.Nonce, sb.L1InfoRoot, etherMan.da) - - if err != nil { - return fmt.Errorf("error decoding the sequences (elderberry): %v", err) + methodId := tx.Data()[:4] + log.Debugf("MethodId: %s", common.Bytes2Hex(methodId)) + if bytes.Equal(methodId, methodIDSequenceBatchesEtrog) || + bytes.Equal(methodId, methodIDSequenceBatchesValidiumEtrog) { + sequences, err = decodeSequencesEtrog(tx.Data(), sb.NumBatch, msg.From, vLog.TxHash, msg.Nonce, sb.L1InfoRoot, etherMan.da) + if err != nil { + return fmt.Errorf("error decoding the sequences (etrog): %v", err) + } + } else if bytes.Equal(methodId, methodIDSequenceBatchesElderberry) || + bytes.Equal(methodId, methodIDSequenceBatchesValidiumElderberry) { + sequences, err = decodeSequencesElderberry(tx.Data(), sb.NumBatch, msg.From, vLog.TxHash, msg.Nonce, sb.L1InfoRoot, etherMan.da) + if err != nil { + return fmt.Errorf("error decoding the sequences (elderberry): %v", err) + } + } else { + return fmt.Errorf("error decoding the sequences: methodId %s unknown", common.Bytes2Hex(methodId)) } - //} else { - // return fmt.Errorf("error decoding the sequences: methodId %s unknown", common.Bytes2Hex(methodId)) - //} } else { log.Info("initial transaction sequence...") sequences = append(sequences, SequencedBatch{ @@ -1325,98 +1337,11 @@ func decodeSequencesElderberry(txData []byte, lastBatchNumber uint64, sequencer return nil, err } - // Recover Method from signature and ABI - method, err := smcAbi.MethodById(txData[:4]) - if err != nil { - return nil, err - } - - // Unpack method inputs - data, err := method.Inputs.Unpack(txData[4:]) - if err != nil { - return nil, err - } - var sequences []polygonzkevm.PolygonRollupBaseEtrogBatchData - bytedata, err := json.Marshal(data[0]) - if err != nil { - return nil, err - } - switch method.Name { - case "rollup": // TODO: put correct value - err = json.Unmarshal(bytedata, &sequences) - if err != nil { - return nil, err - } - maxSequenceTimestamp := data[1].(uint64) - initSequencedBatchNumber := data[2].(uint64) - coinbase := (data[3]).(common.Address) - sequencedBatches := make([]SequencedBatch, len(sequences)) - - for i, seq := range sequences { - elderberry := SequencedBatchElderberryData{ - MaxSequenceTimestamp: maxSequenceTimestamp, - InitSequencedBatchNumber: initSequencedBatchNumber, - } - bn := lastBatchNumber - uint64(len(sequences)-(i+1)) - s := seq - sequencedBatches[i] = SequencedBatch{ - BatchNumber: bn, - L1InfoRoot: &l1InfoRoot, - SequencerAddr: sequencer, - TxHash: txHash, - Nonce: nonce, - Coinbase: coinbase, - PolygonRollupBaseEtrogBatchData: &s, - SequencedBatchElderberryData: &elderberry, - } - } - - return sequencedBatches, nil - case "sequenceBatchesValidium": - var sequencesValidium []polygonzkevm.PolygonValidiumEtrogValidiumBatchData - err = json.Unmarshal(bytedata, &sequencesValidium) - if err != nil { - return nil, err - } - maxSequenceTimestamp := data[1].(uint64) - initSequencedBatchNumber := data[2].(uint64) - coinbase := (data[3]).(common.Address) - sequencedBatches := make([]SequencedBatch, len(sequencesValidium)) - for i, seq := range sequencesValidium { - elderberry := SequencedBatchElderberryData{ - MaxSequenceTimestamp: maxSequenceTimestamp, - InitSequencedBatchNumber: initSequencedBatchNumber, - } - bn := lastBatchNumber - uint64(len(sequencesValidium)-(i+1)) - batchL2Data, err := da.GetBatchL2Data(bn, sequencesValidium[i].TransactionsHash) - if err != nil { - return nil, err - } - s := polygonzkevm.PolygonRollupBaseEtrogBatchData{ - Transactions: batchL2Data, - ForcedGlobalExitRoot: seq.ForcedGlobalExitRoot, - ForcedTimestamp: seq.ForcedTimestamp, - ForcedBlockHashL1: seq.ForcedBlockHashL1, - } - sequencedBatches[i] = SequencedBatch{ - BatchNumber: bn, - L1InfoRoot: &l1InfoRoot, - SequencerAddr: sequencer, - TxHash: txHash, - Nonce: nonce, - Coinbase: coinbase, - PolygonRollupBaseEtrogBatchData: &s, - SequencedBatchElderberryData: &elderberry, - } - } - - return sequencedBatches, nil - default: - return nil, fmt.Errorf("unexpected method called in sequence batches transaction: %s", method.RawName) - } + return decodeSequencedBatches(smcAbi, txData, state.FORKID_ELDERBERRY, lastBatchNumber, sequencer, txHash, nonce, l1InfoRoot, da) } -func decodeSequencesEtrog(txData []byte, lastBatchNumber uint64, sequencer common.Address, txHash common.Hash, nonce uint64, l1InfoRoot common.Hash, da dataavailability.BatchDataProvider) ([]SequencedBatch, error) { // nolint:unused +func decodeSequencesEtrog(txData []byte, lastBatchNumber uint64, sequencer common.Address, txHash common.Hash, nonce uint64, l1InfoRoot common.Hash, + da dataavailability.BatchDataProvider) ([]SequencedBatch, error) { // Extract coded txs. // Load contract ABI smcAbi, err := abi.JSON(strings.NewReader(polygonzkevm.PolygonvalidiumXlayerABI)) @@ -1424,6 +1349,13 @@ func decodeSequencesEtrog(txData []byte, lastBatchNumber uint64, sequencer commo return nil, err } + return decodeSequencedBatches(smcAbi, txData, state.FORKID_ETROG, lastBatchNumber, sequencer, txHash, nonce, l1InfoRoot, da) +} + +// decodeSequencedBatches decodes provided data, based on the funcName, whether it is rollup or validium data and returns sequenced batches +func decodeSequencedBatches(smcAbi abi.ABI, txData []byte, forkID uint64, lastBatchNumber uint64, + sequencer common.Address, txHash common.Hash, nonce uint64, l1InfoRoot common.Hash, + da dataavailability.BatchDataProvider) ([]SequencedBatch, error) { // Recover Method from signature and ABI method, err := smcAbi.MethodById(txData[:4]) if err != nil { @@ -1439,19 +1371,37 @@ func decodeSequencesEtrog(txData []byte, lastBatchNumber uint64, sequencer commo if err != nil { return nil, err } - var sequences []polygonzkevm.PolygonRollupBaseEtrogBatchData + + var ( + maxSequenceTimestamp uint64 + initSequencedBatchNumber uint64 + coinbase common.Address + dataAvailabilityMsg []byte + ) + switch method.Name { - case "rollup": // TODO: put correct value - err = json.Unmarshal(bytedata, &sequences) + case "sequenceBatches": + var sequences []polygonzkevm.PolygonRollupBaseEtrogBatchData + err := json.Unmarshal(bytedata, &sequences) if err != nil { return nil, err } - coinbase := (data[1]).(common.Address) + + switch forkID { + case state.FORKID_ETROG: + coinbase = data[1].(common.Address) + + case state.FORKID_ELDERBERRY: + maxSequenceTimestamp = data[1].(uint64) + initSequencedBatchNumber = data[2].(uint64) + coinbase = data[3].(common.Address) + } + sequencedBatches := make([]SequencedBatch, len(sequences)) for i, seq := range sequences { bn := lastBatchNumber - uint64(len(sequences)-(i+1)) s := seq - sequencedBatches[i] = SequencedBatch{ + batch := SequencedBatch{ BatchNumber: bn, L1InfoRoot: &l1InfoRoot, SequencerAddr: sequencer, @@ -1460,30 +1410,59 @@ func decodeSequencesEtrog(txData []byte, lastBatchNumber uint64, sequencer commo Coinbase: coinbase, PolygonRollupBaseEtrogBatchData: &s, } + if forkID >= state.FORKID_ELDERBERRY { + batch.SequencedBatchElderberryData = &SequencedBatchElderberryData{ + MaxSequenceTimestamp: maxSequenceTimestamp, + InitSequencedBatchNumber: initSequencedBatchNumber, + } + } + sequencedBatches[i] = batch } return sequencedBatches, nil case "sequenceBatchesValidium": var sequencesValidium []polygonzkevm.PolygonValidiumEtrogValidiumBatchData - err = json.Unmarshal(bytedata, &sequencesValidium) + err := json.Unmarshal(bytedata, &sequencesValidium) if err != nil { return nil, err } - coinbase := (data[1]).(common.Address) + + switch forkID { + case state.FORKID_ETROG: + coinbase = data[1].(common.Address) + dataAvailabilityMsg = data[2].([]byte) + + case state.FORKID_ELDERBERRY: + maxSequenceTimestamp = data[1].(uint64) + initSequencedBatchNumber = data[2].(uint64) + coinbase = data[3].(common.Address) + dataAvailabilityMsg = data[4].([]byte) + } + sequencedBatches := make([]SequencedBatch, len(sequencesValidium)) - for i, seq := range sequencesValidium { + + var ( + batchNums []uint64 + hashes []common.Hash + ) + + for i, validiumData := range sequencesValidium { bn := lastBatchNumber - uint64(len(sequencesValidium)-(i+1)) - batchL2Data, err := da.GetBatchL2Data(bn, sequencesValidium[i].TransactionsHash) - if err != nil { - return nil, err - } + batchNums = append(batchNums, bn) + hashes = append(hashes, validiumData.TransactionsHash) + } + batchL2Data, err := da.GetBatchL2Data(batchNums, hashes, dataAvailabilityMsg) + if err != nil { + return nil, err + } + for i, bn := range batchNums { s := polygonzkevm.PolygonRollupBaseEtrogBatchData{ - Transactions: batchL2Data, - ForcedGlobalExitRoot: seq.ForcedGlobalExitRoot, - ForcedTimestamp: seq.ForcedTimestamp, - ForcedBlockHashL1: seq.ForcedBlockHashL1, + Transactions: batchL2Data[i], + ForcedGlobalExitRoot: sequencesValidium[i].ForcedGlobalExitRoot, + ForcedTimestamp: sequencesValidium[i].ForcedTimestamp, + ForcedBlockHashL1: sequencesValidium[i].ForcedBlockHashL1, } - sequencedBatches[i] = SequencedBatch{ + batch := SequencedBatch{ BatchNumber: bn, L1InfoRoot: &l1InfoRoot, SequencerAddr: sequencer, @@ -1492,12 +1471,19 @@ func decodeSequencesEtrog(txData []byte, lastBatchNumber uint64, sequencer commo Coinbase: coinbase, PolygonRollupBaseEtrogBatchData: &s, } + if forkID >= state.FORKID_ELDERBERRY { + elderberry := &SequencedBatchElderberryData{ + MaxSequenceTimestamp: maxSequenceTimestamp, + InitSequencedBatchNumber: initSequencedBatchNumber, + } + batch.SequencedBatchElderberryData = elderberry + } + sequencedBatches[i] = batch } - return sequencedBatches, nil - default: - return nil, fmt.Errorf("unexpected method called in sequence batches transaction: %s", method.RawName) } + + return nil, fmt.Errorf("unexpected method called in sequence batches transaction: %s", method.RawName) } func decodeSequencesPreEtrog(txData []byte, lastBatchNumber uint64, sequencer common.Address, txHash common.Hash, nonce uint64) ([]SequencedBatch, error) { diff --git a/jsonrpc/client/zkevm.go b/jsonrpc/client/zkevm.go index 7bd6be3332..c794da4e0c 100644 --- a/jsonrpc/client/zkevm.go +++ b/jsonrpc/client/zkevm.go @@ -58,6 +58,42 @@ func (c *Client) BatchByNumber(ctx context.Context, number *big.Int) (*types.Bat return result, nil } +// BatchesByNumbers returns batches from the current canonical chain by batch numbers. If the list is empty, the last +// known batch is returned as a list. +func (c *Client) BatchesByNumbers(_ context.Context, numbers []*big.Int) ([]*types.BatchData, error) { + var list []types.BatchNumber + for _, n := range numbers { + list = append(list, types.BatchNumber(n.Int64())) + } + if len(list) == 0 { + list = append(list, types.LatestBatchNumber) + } + + var batchNumbers []string + for _, n := range list { + batchNumbers = append(batchNumbers, n.StringOrHex()) + } + + foo := make(map[string][]string, 0) + foo["numbers"] = batchNumbers // nolint: gosec + response, err := JSONRPCCall(c.url, "zkevm_getBatchDataByNumbers", foo) + if err != nil { + return nil, err + } + + if response.Error != nil { + return nil, response.Error.RPCError() + } + + var result *types.BatchDataResult + err = json.Unmarshal(response.Result, &result) + if err != nil { + return nil, err + } + + return result.Data, nil +} + // ExitRootsByGER returns the exit roots accordingly to the provided Global Exit Root func (c *Client) ExitRootsByGER(ctx context.Context, globalExitRoot common.Hash) (*types.ExitRoots, error) { response, err := JSONRPCCall(c.url, "zkevm_getExitRootsByGER", globalExitRoot.String()) diff --git a/jsonrpc/endpoints_zkevm.go b/jsonrpc/endpoints_zkevm.go index f4c6020ba8..7c3e17a555 100644 --- a/jsonrpc/endpoints_zkevm.go +++ b/jsonrpc/endpoints_zkevm.go @@ -204,6 +204,42 @@ func (z *ZKEVMEndpoints) GetBatchByNumber(batchNumber types.BatchNumber, fullTx }) } +// GetBatchDataByNumbers returns the batch data for batches by numbers +func (z *ZKEVMEndpoints) GetBatchDataByNumbers(filter types.BatchFilter) (interface{}, types.Error) { + return z.txMan.NewDbTxScope(z.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, types.Error) { + var batchNumbers []uint64 + for _, bn := range filter.Numbers { + n, rpcErr := bn.GetNumericBatchNumber(ctx, z.state, z.etherman, dbTx) + if rpcErr != nil { + return nil, rpcErr + } + batchNumbers = append(batchNumbers, n) + } + + batchesData, err := z.state.GetBatchL2DataByNumbers(ctx, batchNumbers, dbTx) + if errors.Is(err, state.ErrNotFound) { + return nil, nil + } else if err != nil { + return RPCErrorResponse(types.DefaultErrorCode, + fmt.Sprintf("couldn't load batch data from state by numbers %v", filter.Numbers), err, true) + } + + var ret []*types.BatchData + for _, n := range batchNumbers { + data := &types.BatchData{Number: types.ArgUint64(n)} + if b, ok := batchesData[n]; ok { + data.BatchL2Data = b + data.Empty = false + } else { + data.Empty = true + } + ret = append(ret, data) + } + + return types.BatchDataResult{Data: ret}, nil + }) +} + // GetFullBlockByNumber returns information about a block by block number func (z *ZKEVMEndpoints) GetFullBlockByNumber(number types.BlockNumber, fullTx bool) (interface{}, types.Error) { return z.txMan.NewDbTxScope(z.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, types.Error) { diff --git a/jsonrpc/endpoints_zkevm.openrpc.json b/jsonrpc/endpoints_zkevm.openrpc.json index d795e0f1cb..5875d2fc76 100644 --- a/jsonrpc/endpoints_zkevm.openrpc.json +++ b/jsonrpc/endpoints_zkevm.openrpc.json @@ -166,6 +166,18 @@ } ] }, + { + "name": "zkevm_getBatchDataByNumbers", + "summary": "Gets batch data for a given list of batch numbers", + "params": [ + { + "$ref": "#/components/contentDescriptors/BatchFilter" + } + ], + "result": { + "$ref": "#/components/contentDescriptors/BatchDataResult" + } + }, { "name": "zkevm_getBatchByNumber", "summary": "Gets a batch for a given number", @@ -512,6 +524,27 @@ "$ref": "#/components/schemas/Batch" } }, + "BatchFilter": { + "name": "filter", + "description": "batch filter", + "schema": { + "$ref": "#/components/schemas/BatchFilter" + } + }, + "BatchData": { + "name": "batchData", + "description": "batch data", + "schema": { + "$ref": "#/components/schemas/BatchData" + } + }, + "BatchDataResult": { + "name": "batchDataResult", + "description": "batch data result", + "schema": { + "$ref": "#/components/schemas/BatchDataResult" + } + }, "Block": { "name": "block", "summary": "A block", @@ -1440,4 +1473,4 @@ } } } -} \ No newline at end of file +} diff --git a/jsonrpc/mocks/mock_state_xlayer.go b/jsonrpc/mocks/mock_state_xlayer.go index dc6bbbd27d..e01d802c7e 100644 --- a/jsonrpc/mocks/mock_state_xlayer.go +++ b/jsonrpc/mocks/mock_state_xlayer.go @@ -55,4 +55,34 @@ func (_m *StateMock) GetSafeL2BlockNumber(ctx context.Context, l1SafeBlockNumber } return r0, r1 -} \ No newline at end of file +} + +// GetBatchL2DataByNumbers provides a mock function with given fields: ctx, batchNumbers, dbTx +func (_m *StateMock) GetBatchL2DataByNumbers(ctx context.Context, batchNumbers []uint64, dbTx pgx.Tx) (map[uint64][]byte, error) { + ret := _m.Called(ctx, batchNumbers, dbTx) + + if len(ret) == 0 { + panic("no return value specified for GetBatchL2DataByNumbers") + } + + var r0 map[uint64][]byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []uint64, pgx.Tx) (map[uint64][]byte, error)); ok { + return rf(ctx, batchNumbers, dbTx) + } + if rf, ok := ret.Get(0).(func(context.Context, []uint64, pgx.Tx) map[uint64][]byte); ok { + r0 = rf(ctx, batchNumbers, dbTx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[uint64][]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []uint64, pgx.Tx) error); ok { + r1 = rf(ctx, batchNumbers, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/jsonrpc/types/interfaces.go b/jsonrpc/types/interfaces.go index 7df5442512..028faaa2d1 100644 --- a/jsonrpc/types/interfaces.go +++ b/jsonrpc/types/interfaces.go @@ -66,6 +66,7 @@ type StateInterface interface { GetLastVerifiedBatch(ctx context.Context, dbTx pgx.Tx) (*state.VerifiedBatch, error) GetLastBatchNumber(ctx context.Context, dbTx pgx.Tx) (uint64, error) GetBatchByNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*state.Batch, error) + GetBatchL2DataByNumbers(ctx context.Context, batchNumbers []uint64, dbTx pgx.Tx) (map[uint64][]byte, error) GetTransactionsByBatchNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (txs []types.Transaction, effectivePercentages []uint8, err error) GetVirtualBatch(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*state.VirtualBatch, error) GetVerifiedBatch(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*state.VerifiedBatch, error) diff --git a/jsonrpc/types/types.go b/jsonrpc/types/types.go index b9c902cc1a..1c7bfe5b0b 100644 --- a/jsonrpc/types/types.go +++ b/jsonrpc/types/types.go @@ -446,6 +446,23 @@ func NewBatch(ctx context.Context, st StateInterface, batch *state.Batch, virtua return res, nil } +// BatchFilter is a list of batch numbers to retrieve +type BatchFilter struct { + Numbers []BatchNumber `json:"numbers"` +} + +// BatchData is an abbreviated structure that only contains the number and L2 batch data +type BatchData struct { + Number ArgUint64 `json:"number"` + BatchL2Data ArgBytes `json:"batchL2Data,omitempty"` + Empty bool `json:"empty"` +} + +// BatchDataResult is a list of BatchData for a BatchFilter +type BatchDataResult struct { + Data []*BatchData `json:"data"` +} + // TransactionOrHash for union type of transaction and types.Hash type TransactionOrHash struct { Hash *common.Hash diff --git a/state/interfaces.go b/state/interfaces.go index d37b531403..a1ad71f3ff 100644 --- a/state/interfaces.go +++ b/state/interfaces.go @@ -161,4 +161,5 @@ type storage interface { GetLastL2BlockByBatchNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*L2Block, error) // GetBatchL2DataByNumber is XLayer method GetBatchL2DataByNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) ([]byte, error) + GetBatchL2DataByNumbers(ctx context.Context, batchNumbers []uint64, dbTx pgx.Tx) (map[uint64][]byte, error) } diff --git a/state/mocks/mock_storage_xlayer.go b/state/mocks/mock_storage_xlayer.go index 8a1bf01161..99a912c025 100644 --- a/state/mocks/mock_storage_xlayer.go +++ b/state/mocks/mock_storage_xlayer.go @@ -11,3 +11,7 @@ import ( func (_m *StorageMock) GetBatchL2DataByNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) ([]byte, error){ return nil, nil } + +func (_m *StorageMock) GetBatchL2DataByNumbers(ctx context.Context, batchNumbers []uint64, dbTx pgx.Tx) (map[uint64][]byte, error){ + return nil, nil +} \ No newline at end of file diff --git a/state/pgstatestorage/batch_xlayer.go b/state/pgstatestorage/batch_xlayer.go deleted file mode 100644 index 94dc2b640a..0000000000 --- a/state/pgstatestorage/batch_xlayer.go +++ /dev/null @@ -1,24 +0,0 @@ -package pgstatestorage - -import ( - "context" - "errors" - - "github.com/0xPolygonHermez/zkevm-node/state" - "github.com/jackc/pgx/v4" -) - -// GetBatchL2DataByNumber returns the batch L2 data of the given batch number. -func (p *PostgresStorage) GetBatchL2DataByNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) ([]byte, error) { - getBatchL2DataByBatchNumber := "SELECT raw_txs_data FROM state.batch WHERE batch_num = $1" - q := p.getExecQuerier(dbTx) - var batchL2Data []byte - err := q.QueryRow(ctx, getBatchL2DataByBatchNumber, batchNumber).Scan(&batchL2Data) - - if errors.Is(err, pgx.ErrNoRows) { - return nil, state.ErrNotFound - } else if err != nil { - return nil, err - } - return batchL2Data, nil -} diff --git a/state/pgstatestorage/pgstatestorage_xlayer.go b/state/pgstatestorage/pgstatestorage_xlayer.go new file mode 100644 index 0000000000..5301415948 --- /dev/null +++ b/state/pgstatestorage/pgstatestorage_xlayer.go @@ -0,0 +1,55 @@ +package pgstatestorage + +import ( + "context" + "errors" + + "github.com/0xPolygonHermez/zkevm-node/state" + "github.com/jackc/pgx/v4" +) + +// GetBatchL2DataByNumber returns the batch L2 data of the given batch number. +func (p *PostgresStorage) GetBatchL2DataByNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) ([]byte, error) { + batchData, err := p.GetBatchL2DataByNumbers(ctx, []uint64{batchNumber}, dbTx) + if err != nil { + return nil, err + } + data, ok := batchData[batchNumber] + if !ok { + return nil, state.ErrNotFound + } + return data, nil +} + +// GetBatchL2DataByNumbers returns the batch L2 data of the given batch numbers. The data is a union of state.batch and state.forced_batch tables. +func (p *PostgresStorage) GetBatchL2DataByNumbers(ctx context.Context, batchNumbers []uint64, dbTx pgx.Tx) (map[uint64][]byte, error) { + const getBatchL2DataByBatchNumber = ` + SELECT batch_num, raw_txs_data FROM state.batch WHERE batch_num = ANY($1) + UNION + SELECT forced_batch_num, convert_from(decode(raw_txs_data, 'hex'), 'UTF8')::bytea FROM state.forced_batch WHERE forced_batch_num = ANY($2) +` + q := p.getExecQuerier(dbTx) + rows, err := q.Query(ctx, getBatchL2DataByBatchNumber, batchNumbers, batchNumbers) + if errors.Is(err, pgx.ErrNoRows) { + return nil, state.ErrNotFound + } else if err != nil { + return nil, err + } + defer rows.Close() + batchL2DataMap := make(map[uint64][]byte) + for rows.Next() { + var ( + batchNum uint64 + batchL2Data []byte + ) + err := rows.Scan(&batchNum, &batchL2Data) + if err != nil { + return nil, err + } + batchL2DataMap[batchNum] = batchL2Data + } + if len(batchL2DataMap) == 0 { + return nil, state.ErrNotFound + } + return batchL2DataMap, nil +}