diff --git a/jsonrpc/endpoints_zkevm.openrpc.json b/jsonrpc/endpoints_zkevm.openrpc.json index 5875d2fc76..21f059caba 100644 --- a/jsonrpc/endpoints_zkevm.openrpc.json +++ b/jsonrpc/endpoints_zkevm.openrpc.json @@ -177,8 +177,7 @@ "result": { "$ref": "#/components/contentDescriptors/BatchDataResult" } - }, - { + }, { "name": "zkevm_getBatchByNumber", "summary": "Gets a batch for a given number", "params": [ @@ -483,6 +482,22 @@ "$ref": "#/components/schemas/Integer" } } + }, + { + "name": "zkevm_getBatchSealTime", + "summary": "Get Batch seal time", + "params": [ + { + "$ref": "#/components/contentDescriptors/BatchNumber" + } + ], + "result": { + "name": "result", + "description": "The batch seal time", + "schema": { + "$ref": "#/components/schemas/Timestamp" + } + } } ], "components": { @@ -1470,6 +1485,11 @@ "$ref": "#/components/schemas/Integer" } } + }, + "Timestamp": { + "title": "timestamp", + "type": "string", + "description": "The unix timestamp of the batch" } } } diff --git a/jsonrpc/endpoints_zkevm_xlayer.go b/jsonrpc/endpoints_zkevm_xlayer.go new file mode 100644 index 0000000000..aa19b3cfe9 --- /dev/null +++ b/jsonrpc/endpoints_zkevm_xlayer.go @@ -0,0 +1,28 @@ +package jsonrpc + +import ( + "context" + "fmt" + + "github.com/0xPolygonHermez/zkevm-node/hex" + "github.com/0xPolygonHermez/zkevm-node/jsonrpc/types" + "github.com/jackc/pgx/v4" +) + +// GetBatchSealTime returns the seal time +func (z *ZKEVMEndpoints) GetBatchSealTime(batchNumber types.BatchNumber) (interface{}, types.Error) { + return z.txMan.NewDbTxScope(z.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, types.Error) { + var err error + batchNumber, rpcErr := batchNumber.GetNumericBatchNumber(ctx, z.state, z.etherman, dbTx) + if rpcErr != nil { + return nil, rpcErr + } + + sealTime, err := z.state.GetLastL2BlockTimeByBatchNumber(ctx, batchNumber, dbTx) + if err != nil { + return RPCErrorResponse(types.DefaultErrorCode, fmt.Sprintf("couldn't get batch number %v's seal time, error: %v", batchNumber, err), nil, false) + } + + return hex.EncodeUint64(sealTime), nil + }) +} diff --git a/jsonrpc/mocks/mock_state_xlayer.go b/jsonrpc/mocks/mock_state_xlayer.go index e01d802c7e..f06421a831 100644 --- a/jsonrpc/mocks/mock_state_xlayer.go +++ b/jsonrpc/mocks/mock_state_xlayer.go @@ -86,3 +86,26 @@ func (_m *StateMock) GetBatchL2DataByNumbers(ctx context.Context, batchNumbers [ return r0, r1 } + +func (_m *StateMock)GetLastL2BlockTimeByBatchNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (uint64, error) { + ret := _m.Called(ctx, batchNumber, dbTx) + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) (uint64, error)); ok { + return rf(ctx, batchNumber, dbTx) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) uint64); ok { + r0 = rf(ctx, batchNumber, dbTx) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, pgx.Tx) error); ok { + r1 = rf(ctx, batchNumber, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/jsonrpc/types/interfaces.go b/jsonrpc/types/interfaces.go index 54c0561c98..c09dcdc0ce 100644 --- a/jsonrpc/types/interfaces.go +++ b/jsonrpc/types/interfaces.go @@ -83,6 +83,9 @@ type StateInterface interface { GetLatestBatchGlobalExitRoot(ctx context.Context, dbTx pgx.Tx) (common.Hash, error) GetL2TxHashByTxHash(ctx context.Context, hash common.Hash, dbTx pgx.Tx) (*common.Hash, error) PreProcessUnsignedTransaction(ctx context.Context, tx *types.Transaction, sender common.Address, l2BlockNumber *uint64, dbTx pgx.Tx) (*state.ProcessBatchResponse, error) + + // GetLastL2BlockTimeByBatchNumber gets the last l2 block time in a batch by batch number X Layer handler + GetLastL2BlockTimeByBatchNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (uint64, error) } // EthermanInterface provides integration with L1 diff --git a/state/interfaces.go b/state/interfaces.go index f412a9902d..e6e08b693b 100644 --- a/state/interfaces.go +++ b/state/interfaces.go @@ -167,4 +167,5 @@ type storage interface { // 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) + GetLastL2BlockTimeByBatchNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (uint64, error) } diff --git a/state/mocks/mock_storage_xlayer.go b/state/mocks/mock_storage_xlayer.go index 99a912c025..929f096a04 100644 --- a/state/mocks/mock_storage_xlayer.go +++ b/state/mocks/mock_storage_xlayer.go @@ -8,10 +8,14 @@ import ( pgx "github.com/jackc/pgx/v4" ) -func (_m *StorageMock) GetBatchL2DataByNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) ([]byte, error){ +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){ +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 +} + +func (_m *StorageMock) GetLastL2BlockTimeByBatchNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (uint64, error) { + return 0, nil +} diff --git a/state/pgstatestorage/l2block_xlayer.go b/state/pgstatestorage/l2block_xlayer.go new file mode 100644 index 0000000000..a03a79bb01 --- /dev/null +++ b/state/pgstatestorage/l2block_xlayer.go @@ -0,0 +1,34 @@ +package pgstatestorage + +import ( + "context" + "errors" + "fmt" + + "github.com/0xPolygonHermez/zkevm-node/state" + "github.com/jackc/pgx/v4" +) + +// GetLastL2BlockTimeByBatchNumber gets the last l2 block time in a batch by batch number +func (p *PostgresStorage) GetLastL2BlockTimeByBatchNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (uint64, error) { + lastClosedBatchNumber, err := p.GetLastBatchNumber(ctx, dbTx) + if err != nil { + return 0, err + } + if batchNumber > lastClosedBatchNumber { + return 0, fmt.Errorf("%w. got %d, last batch should be %d", state.ErrUnexpectedBatch, batchNumber, lastClosedBatchNumber) + } + const query = "SELECT header FROM state.l2block b WHERE batch_num = $1 ORDER BY b.block_num DESC LIMIT 1" + + header := &state.L2Header{} + q := p.getExecQuerier(dbTx) + err = q.QueryRow(ctx, query, batchNumber).Scan(&header) + + if errors.Is(err, pgx.ErrNoRows) { + return 0, state.ErrNotFound + } else if err != nil { + return 0, err + } + + return header.Time, nil +}