diff --git a/README.md b/README.md index 754bfee0a2..b452569fd0 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,8 @@ Required services and components: There must be only one synchronizer, and it's recommended that it has exclusive access to an executor instance, although it's not necessary. This role can perfectly be run in a single instance, however, the JSON RPC and executor services can benefit from running in multiple instances, if the performance decreases due to the number of requests received +[`zkEVM RPC Custom endpoints documentation`](./docs/zkEVM-custom-endpoints.md) + ### Trusted sequencer This role can only be performed by a single entity. This is enforced in the smart contract, as the related methods of the trusted sequencer can only be performed by the owner of a particular private key. diff --git a/docs/zkEVM-custom-endpoints.md b/docs/zkEVM-custom-endpoints.md new file mode 100644 index 0000000000..ef496c1df5 --- /dev/null +++ b/docs/zkEVM-custom-endpoints.md @@ -0,0 +1,9 @@ +# zkEVM custom endpoints + +The zkEVM Node JSON RPC server works as is when compared to the official Ethereum JSON RPC, but there are some extra information that also needs to be shared when talking about a L2 Networks, in our case we have information about Batches, Proofs, L1 transactions and much more + +In order to allow users to consume this information, a custom set of endpoints were created to provide this information, they are provided under the prefix `zkevm_` + +The endpoint documentation follows the [OpenRPC Specification](https://spec.open-rpc.org/) and can be found next to the endpoints implementation as a json file, [here](../jsonrpc/endpoints_zkevm.openrpc.json) + +The spec can be easily visualized using the oficial [OpenRPC Playground](https://playground.open-rpc.org/), just copy and paste the json content into the playground area to find a friendly UI showing the methods diff --git a/hex/hex.go b/hex/hex.go index ab224ddc7f..1636f64399 100644 --- a/hex/hex.go +++ b/hex/hex.go @@ -95,10 +95,11 @@ func EncodeBig(bigint *big.Int) string { return fmt.Sprintf("%#x", bigint) } -// DecodeHexToBig converts a hex number to a big.Int value -func DecodeHexToBig(hexNum string) *big.Int { +// DecodeBig converts a hex number to a big.Int value +func DecodeBig(hexNum string) *big.Int { + str := strings.TrimPrefix(hexNum, "0x") createdNum := new(big.Int) - createdNum.SetString(hexNum, Base) + createdNum.SetString(str, Base) return createdNum } diff --git a/hex/hex_test.go b/hex/hex_test.go new file mode 100644 index 0000000000..12e6e44048 --- /dev/null +++ b/hex/hex_test.go @@ -0,0 +1,16 @@ +package hex + +import ( + "math" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEncodeDecodeBig(t *testing.T) { + b := big.NewInt(math.MaxInt64) + e := EncodeBig(b) + d := DecodeBig(e) + assert.Equal(t, b.Uint64(), d.Uint64()) +} diff --git a/jsonrpc/codec.go b/jsonrpc/codec.go index ab39db3056..7c8286cb88 100644 --- a/jsonrpc/codec.go +++ b/jsonrpc/codec.go @@ -17,6 +17,11 @@ const ( // EarliestBlockNumber represents the earliest block number EarliestBlockNumber = BlockNumber(-1) + // LatestBatchNumber represents the latest batch number + LatestBatchNumber = BatchNumber(-2) + // EarliestBatchNumber represents the earliest batch number + EarliestBatchNumber = BatchNumber(-1) + // Earliest contains the string to represent the earliest block known. Earliest = "earliest" // Latest contains the string to represent the latest block known. @@ -187,3 +192,58 @@ func (i *Index) UnmarshalJSON(buffer []byte) error { *i = Index(n) return nil } + +// BatchNumber is the number of a ethereum block +type BatchNumber int64 + +// UnmarshalJSON automatically decodes the user input for the block number, when a JSON RPC method is called +func (b *BatchNumber) UnmarshalJSON(buffer []byte) error { + num, err := stringToBatchNumber(string(buffer)) + if err != nil { + return err + } + *b = num + return nil +} + +func (b *BatchNumber) getNumericBatchNumber(ctx context.Context, s stateInterface, dbTx pgx.Tx) (uint64, rpcError) { + bValue := LatestBatchNumber + if b != nil { + bValue = *b + } + + switch bValue { + case LatestBatchNumber: + lastBatchNumber, err := s.GetLastBatchNumber(ctx, dbTx) + if err != nil { + return 0, newRPCError(defaultErrorCode, "failed to get the last batch number from state") + } + + return lastBatchNumber, nil + + case EarliestBatchNumber: + return 0, nil + + default: + if bValue < 0 { + return 0, newRPCError(invalidParamsErrorCode, "invalid batch number: %v", bValue) + } + return uint64(bValue), nil + } +} + +func stringToBatchNumber(str string) (BatchNumber, error) { + str = strings.Trim(str, "\"") + switch str { + case Earliest: + return EarliestBatchNumber, nil + case Latest, "": + return LatestBatchNumber, nil + } + + n, err := encoding.DecodeUint64orHex(&str) + if err != nil { + return 0, err + } + return BatchNumber(n), nil +} diff --git a/jsonrpc/debug.go b/jsonrpc/endpoints_debug.go similarity index 94% rename from jsonrpc/debug.go rename to jsonrpc/endpoints_debug.go index 85faa55120..44e8c9eef9 100644 --- a/jsonrpc/debug.go +++ b/jsonrpc/endpoints_debug.go @@ -8,8 +8,8 @@ import ( "github.com/jackc/pgx/v4" ) -// Debug is the debug jsonrpc endpoint -type Debug struct { +// DebugEndpoints is the debug jsonrpc endpoint +type DebugEndpoints struct { state stateInterface txMan dbTxManager } @@ -41,7 +41,7 @@ type StructLogRes struct { // TraceTransaction creates a response for debug_traceTransaction request. // See https://geth.ethereum.org/docs/rpc/ns-debug#debug_tracetransaction -func (d *Debug) TraceTransaction(hash common.Hash, cfg *traceConfig) (interface{}, rpcError) { +func (d *DebugEndpoints) TraceTransaction(hash common.Hash, cfg *traceConfig) (interface{}, rpcError) { return d.txMan.NewDbTxScope(d.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { tracer := "" if cfg != nil && cfg.Tracer != nil { diff --git a/jsonrpc/debug_test.go b/jsonrpc/endpoints_debug_test.go similarity index 100% rename from jsonrpc/debug_test.go rename to jsonrpc/endpoints_debug_test.go diff --git a/jsonrpc/eth.go b/jsonrpc/endpoints_eth.go similarity index 84% rename from jsonrpc/eth.go rename to jsonrpc/endpoints_eth.go index 7841f77d2f..bba0b05762 100644 --- a/jsonrpc/eth.go +++ b/jsonrpc/endpoints_eth.go @@ -17,8 +17,8 @@ import ( "github.com/jackc/pgx/v4" ) -// Eth contains implementations for the "eth" RPC endpoints -type Eth struct { +// EthEndpoints contains implementations for the "eth" RPC endpoints +type EthEndpoints struct { cfg Config pool jsonRPCTxPool state stateInterface @@ -26,17 +26,16 @@ type Eth struct { txMan dbTxManager } -// newEth creates an new instance of Eth -func newEth(cfg Config, p jsonRPCTxPool, s stateInterface, storage storageInterface) *Eth { - e := &Eth{cfg: cfg, pool: p, state: s, storage: storage} - +// newEthEndpoints creates an new instance of Eth +func newEthEndpoints(cfg Config, p jsonRPCTxPool, s stateInterface, storage storageInterface) *EthEndpoints { + e := &EthEndpoints{cfg: cfg, pool: p, state: s, storage: storage} s.RegisterNewL2BlockEventHandler(e.onNewL2Block) return e } // BlockNumber returns current block number -func (e *Eth) BlockNumber() (interface{}, rpcError) { +func (e *EthEndpoints) BlockNumber() (interface{}, rpcError) { return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { lastBlockNumber, err := e.state.GetLastL2BlockNumber(ctx, dbTx) if err != nil { @@ -51,7 +50,7 @@ func (e *Eth) BlockNumber() (interface{}, rpcError) { // executed contract and potential error. // Note, this function doesn't make any changes in the state/blockchain and is // useful to execute view/pure methods and retrieve values. -func (e *Eth) Call(arg *txnArgs, number *BlockNumber) (interface{}, rpcError) { +func (e *EthEndpoints) Call(arg *txnArgs, number *BlockNumber) (interface{}, rpcError) { return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { // If the caller didn't supply the gas limit in the message, then we set it to maximum possible => block gas limit if arg.Gas == nil || *arg.Gas == argUint64(0) { @@ -89,7 +88,7 @@ func (e *Eth) Call(arg *txnArgs, number *BlockNumber) (interface{}, rpcError) { } // ChainId returns the chain id of the client -func (e *Eth) ChainId() (interface{}, rpcError) { //nolint:revive +func (e *EthEndpoints) ChainId() (interface{}, rpcError) { //nolint:revive return hex.EncodeUint64(e.cfg.ChainID), nil } @@ -99,7 +98,7 @@ func (e *Eth) ChainId() (interface{}, rpcError) { //nolint:revive // Note that the estimate may be significantly more than the amount of gas actually // used by the transaction, for a variety of reasons including EVM mechanics and // node performance. -func (e *Eth) EstimateGas(arg *txnArgs, number *BlockNumber) (interface{}, rpcError) { +func (e *EthEndpoints) EstimateGas(arg *txnArgs, number *BlockNumber) (interface{}, rpcError) { return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { blockNumber, rpcErr := number.getNumericBlockNumber(ctx, e.state, dbTx) if rpcErr != nil { @@ -125,7 +124,7 @@ func (e *Eth) EstimateGas(arg *txnArgs, number *BlockNumber) (interface{}, rpcEr } // GasPrice returns the average gas price based on the last x blocks -func (e *Eth) GasPrice() (interface{}, rpcError) { +func (e *EthEndpoints) GasPrice() (interface{}, rpcError) { ctx := context.Background() gasPrice, err := e.pool.GetGasPrice(ctx) if err != nil { @@ -135,7 +134,7 @@ func (e *Eth) GasPrice() (interface{}, rpcError) { } // GetBalance returns the account's balance at the referenced block -func (e *Eth) GetBalance(address common.Address, number *BlockNumber) (interface{}, rpcError) { +func (e *EthEndpoints) GetBalance(address common.Address, number *BlockNumber) (interface{}, rpcError) { return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { blockNumber, rpcErr := number.getNumericBlockNumber(ctx, e.state, dbTx) if rpcErr != nil { @@ -154,7 +153,7 @@ func (e *Eth) GetBalance(address common.Address, number *BlockNumber) (interface } // GetBlockByHash returns information about a block by hash -func (e *Eth) GetBlockByHash(hash common.Hash, fullTx bool) (interface{}, rpcError) { +func (e *EthEndpoints) GetBlockByHash(hash common.Hash, fullTx bool) (interface{}, rpcError) { return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { block, err := e.state.GetL2BlockByHash(ctx, hash, dbTx) if errors.Is(err, state.ErrNotFound) { @@ -170,7 +169,7 @@ func (e *Eth) GetBlockByHash(hash common.Hash, fullTx bool) (interface{}, rpcErr } // GetBlockByNumber returns information about a block by block number -func (e *Eth) GetBlockByNumber(number BlockNumber, fullTx bool) (interface{}, rpcError) { +func (e *EthEndpoints) GetBlockByNumber(number BlockNumber, fullTx bool) (interface{}, rpcError) { return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { if number == PendingBlockNumber { lastBlock, err := e.state.GetLastL2Block(ctx, dbTx) @@ -207,7 +206,7 @@ func (e *Eth) GetBlockByNumber(number BlockNumber, fullTx bool) (interface{}, rp } // GetCode returns account code at given block number -func (e *Eth) GetCode(address common.Address, number *BlockNumber) (interface{}, rpcError) { +func (e *EthEndpoints) GetCode(address common.Address, number *BlockNumber) (interface{}, rpcError) { return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { var err error blockNumber, rpcErr := number.getNumericBlockNumber(ctx, e.state, dbTx) @@ -227,13 +226,13 @@ func (e *Eth) GetCode(address common.Address, number *BlockNumber) (interface{}, } // GetCompilers eth_getCompilers -func (e *Eth) GetCompilers() (interface{}, rpcError) { +func (e *EthEndpoints) GetCompilers() (interface{}, rpcError) { return []interface{}{}, nil } // GetFilterChanges polling method for a filter, which returns // an array of logs which occurred since last poll. -func (e *Eth) GetFilterChanges(filterID string) (interface{}, rpcError) { +func (e *EthEndpoints) GetFilterChanges(filterID string) (interface{}, rpcError) { filter, err := e.storage.GetFilter(filterID) if errors.Is(err, ErrNotFound) { return rpcErrorResponse(defaultErrorCode, "filter not found", err) @@ -296,9 +295,9 @@ func (e *Eth) GetFilterChanges(filterID string) (interface{}, rpcError) { } } -// GetFilterLogs returns an array of all logs mlocking filter +// GetFilterLogs returns an array of all logs matching filter // with given id. -func (e *Eth) GetFilterLogs(filterID string) (interface{}, rpcError) { +func (e *EthEndpoints) GetFilterLogs(filterID string) (interface{}, rpcError) { filter, err := e.storage.GetFilter(filterID) if errors.Is(err, ErrNotFound) { return nil, nil @@ -317,13 +316,13 @@ func (e *Eth) GetFilterLogs(filterID string) (interface{}, rpcError) { } // GetLogs returns a list of logs accordingly to the provided filter -func (e *Eth) GetLogs(filter LogFilter) (interface{}, rpcError) { +func (e *EthEndpoints) GetLogs(filter LogFilter) (interface{}, rpcError) { return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { return e.internalGetLogs(ctx, dbTx, filter) }) } -func (e *Eth) internalGetLogs(ctx context.Context, dbTx pgx.Tx, filter LogFilter) (interface{}, rpcError) { +func (e *EthEndpoints) internalGetLogs(ctx context.Context, dbTx pgx.Tx, filter LogFilter) (interface{}, rpcError) { var err error fromBlock, rpcErr := filter.FromBlock.getNumericBlockNumber(ctx, e.state, dbTx) if rpcErr != nil { @@ -349,7 +348,7 @@ func (e *Eth) internalGetLogs(ctx context.Context, dbTx pgx.Tx, filter LogFilter } // GetStorageAt gets the value stored for an specific address and position -func (e *Eth) GetStorageAt(address common.Address, position common.Hash, number *BlockNumber) (interface{}, rpcError) { +func (e *EthEndpoints) GetStorageAt(address common.Address, position common.Hash, number *BlockNumber) (interface{}, rpcError) { return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { var err error blockNumber, rpcErr := number.getNumericBlockNumber(ctx, e.state, dbTx) @@ -370,7 +369,7 @@ func (e *Eth) GetStorageAt(address common.Address, position common.Hash, number // GetTransactionByBlockHashAndIndex returns information about a transaction by // block hash and transaction index position. -func (e *Eth) GetTransactionByBlockHashAndIndex(hash common.Hash, index Index) (interface{}, rpcError) { +func (e *EthEndpoints) GetTransactionByBlockHashAndIndex(hash common.Hash, index Index) (interface{}, rpcError) { return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { tx, err := e.state.GetTransactionByL2BlockHashAndIndex(ctx, hash, uint64(index), dbTx) if errors.Is(err, state.ErrNotFound) { @@ -387,13 +386,13 @@ func (e *Eth) GetTransactionByBlockHashAndIndex(hash common.Hash, index Index) ( } txIndex := uint64(receipt.TransactionIndex) - return toRPCTransaction(tx, receipt.BlockNumber, &receipt.BlockHash, &txIndex), nil + return toRPCTransaction(*tx, receipt.BlockNumber, &receipt.BlockHash, &txIndex), nil }) } // GetTransactionByBlockNumberAndIndex returns information about a transaction by // block number and transaction index position. -func (e *Eth) GetTransactionByBlockNumberAndIndex(number *BlockNumber, index Index) (interface{}, rpcError) { +func (e *EthEndpoints) GetTransactionByBlockNumberAndIndex(number *BlockNumber, index Index) (interface{}, rpcError) { return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { var err error blockNumber, rpcErr := number.getNumericBlockNumber(ctx, e.state, dbTx) @@ -416,12 +415,12 @@ func (e *Eth) GetTransactionByBlockNumberAndIndex(number *BlockNumber, index Ind } txIndex := uint64(receipt.TransactionIndex) - return toRPCTransaction(tx, receipt.BlockNumber, &receipt.BlockHash, &txIndex), nil + return toRPCTransaction(*tx, receipt.BlockNumber, &receipt.BlockHash, &txIndex), nil }) } // GetTransactionByHash returns a transaction by his hash -func (e *Eth) GetTransactionByHash(hash common.Hash) (interface{}, rpcError) { +func (e *EthEndpoints) GetTransactionByHash(hash common.Hash) (interface{}, rpcError) { return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { // try to get tx from state tx, err := e.state.GetTransactionByHash(ctx, hash, dbTx) @@ -437,7 +436,7 @@ func (e *Eth) GetTransactionByHash(hash common.Hash) (interface{}, rpcError) { } txIndex := uint64(receipt.TransactionIndex) - return toRPCTransaction(tx, receipt.BlockNumber, &receipt.BlockHash, &txIndex), nil + return toRPCTransaction(*tx, receipt.BlockNumber, &receipt.BlockHash, &txIndex), nil } // if the tx does not exist in the state, look for it in the pool @@ -449,12 +448,12 @@ func (e *Eth) GetTransactionByHash(hash common.Hash) (interface{}, rpcError) { } tx = &poolTx.Transaction - return toRPCTransaction(tx, nil, nil, nil), nil + return toRPCTransaction(*tx, nil, nil, nil), nil }) } // GetTransactionCount returns account nonce -func (e *Eth) GetTransactionCount(address common.Address, number *BlockNumber) (interface{}, rpcError) { +func (e *EthEndpoints) GetTransactionCount(address common.Address, number *BlockNumber) (interface{}, rpcError) { return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { var pendingNonce uint64 var nonce uint64 @@ -487,8 +486,8 @@ func (e *Eth) GetTransactionCount(address common.Address, number *BlockNumber) ( } // GetBlockTransactionCountByHash returns the number of transactions in a -// block from a block mlocking the given block hash. -func (e *Eth) GetBlockTransactionCountByHash(hash common.Hash) (interface{}, rpcError) { +// block from a block matching the given block hash. +func (e *EthEndpoints) GetBlockTransactionCountByHash(hash common.Hash) (interface{}, rpcError) { return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { c, err := e.state.GetL2BlockTransactionCountByHash(ctx, hash, dbTx) if err != nil { @@ -500,8 +499,8 @@ func (e *Eth) GetBlockTransactionCountByHash(hash common.Hash) (interface{}, rpc } // GetBlockTransactionCountByNumber returns the number of transactions in a -// block from a block mlocking the given block number. -func (e *Eth) GetBlockTransactionCountByNumber(number *BlockNumber) (interface{}, rpcError) { +// block from a block matching the given block number. +func (e *EthEndpoints) GetBlockTransactionCountByNumber(number *BlockNumber) (interface{}, rpcError) { return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { if number != nil && *number == PendingBlockNumber { c, err := e.pool.CountPendingTransactions(ctx) @@ -527,7 +526,7 @@ func (e *Eth) GetBlockTransactionCountByNumber(number *BlockNumber) (interface{} } // GetTransactionReceipt returns a transaction receipt by his hash -func (e *Eth) GetTransactionReceipt(hash common.Hash) (interface{}, rpcError) { +func (e *EthEndpoints) GetTransactionReceipt(hash common.Hash) (interface{}, rpcError) { return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { tx, err := e.state.GetTransactionByHash(ctx, hash, dbTx) if errors.Is(err, state.ErrNotFound) { @@ -555,12 +554,12 @@ func (e *Eth) GetTransactionReceipt(hash common.Hash) (interface{}, rpcError) { // NewBlockFilter creates a filter in the node, to notify when // a new block arrives. To check if the state has changed, // call eth_getFilterChanges. -func (e *Eth) NewBlockFilter() (interface{}, rpcError) { +func (e *EthEndpoints) NewBlockFilter() (interface{}, rpcError) { return e.newBlockFilter(nil) } // internal -func (e *Eth) newBlockFilter(wsConn *websocket.Conn) (interface{}, rpcError) { +func (e *EthEndpoints) newBlockFilter(wsConn *websocket.Conn) (interface{}, rpcError) { id, err := e.storage.NewBlockFilter(wsConn) if err != nil { return rpcErrorResponse(defaultErrorCode, "failed to create new block filter", err) @@ -572,12 +571,12 @@ func (e *Eth) newBlockFilter(wsConn *websocket.Conn) (interface{}, rpcError) { // NewFilter creates a filter object, based on filter options, // to notify when the state changes (logs). To check if the state // has changed, call eth_getFilterChanges. -func (e *Eth) NewFilter(filter LogFilter) (interface{}, rpcError) { +func (e *EthEndpoints) NewFilter(filter LogFilter) (interface{}, rpcError) { return e.newFilter(nil, filter) } // internal -func (e *Eth) newFilter(wsConn *websocket.Conn, filter LogFilter) (interface{}, rpcError) { +func (e *EthEndpoints) newFilter(wsConn *websocket.Conn, filter LogFilter) (interface{}, rpcError) { id, err := e.storage.NewLogFilter(wsConn, filter) if errors.Is(err, ErrFilterInvalidPayload) { return rpcErrorResponse(invalidParamsErrorCode, err.Error(), nil) @@ -591,12 +590,12 @@ func (e *Eth) newFilter(wsConn *websocket.Conn, filter LogFilter) (interface{}, // NewPendingTransactionFilter creates a filter in the node, to // notify when new pending transactions arrive. To check if the // state has changed, call eth_getFilterChanges. -func (e *Eth) NewPendingTransactionFilter() (interface{}, rpcError) { +func (e *EthEndpoints) NewPendingTransactionFilter() (interface{}, rpcError) { return e.newPendingTransactionFilter(nil) } // internal -func (e *Eth) newPendingTransactionFilter(wsConn *websocket.Conn) (interface{}, rpcError) { +func (e *EthEndpoints) newPendingTransactionFilter(wsConn *websocket.Conn) (interface{}, rpcError) { return nil, newRPCError(defaultErrorCode, "not supported yet") // id, err := e.storage.NewPendingTransactionFilter(wsConn) // if err != nil { @@ -609,7 +608,7 @@ func (e *Eth) newPendingTransactionFilter(wsConn *websocket.Conn) (interface{}, // SendRawTransaction has two different ways to handle new transactions: // - for Sequencer nodes it tries to add the tx to the pool // - for Non-Sequencer nodes it relays the Tx to the Sequencer node -func (e *Eth) SendRawTransaction(input string) (interface{}, rpcError) { +func (e *EthEndpoints) SendRawTransaction(input string) (interface{}, rpcError) { if e.cfg.SequencerNodeURI != "" { return e.relayTxToSequencerNode(input) } else { @@ -617,7 +616,7 @@ func (e *Eth) SendRawTransaction(input string) (interface{}, rpcError) { } } -func (e *Eth) relayTxToSequencerNode(input string) (interface{}, rpcError) { +func (e *EthEndpoints) relayTxToSequencerNode(input string) (interface{}, rpcError) { res, err := JSONRPCCall(e.cfg.SequencerNodeURI, "eth_sendRawTransaction", input) if err != nil { return rpcErrorResponse(defaultErrorCode, "failed to relay tx to the sequencer node", err) @@ -632,7 +631,7 @@ func (e *Eth) relayTxToSequencerNode(input string) (interface{}, rpcError) { return txHash, nil } -func (e *Eth) tryToAddTxToPool(input string) (interface{}, rpcError) { +func (e *EthEndpoints) tryToAddTxToPool(input string) (interface{}, rpcError) { tx, err := hexToTx(input) if err != nil { return rpcErrorResponse(invalidParamsErrorCode, "invalid tx input", err) @@ -648,7 +647,7 @@ func (e *Eth) tryToAddTxToPool(input string) (interface{}, rpcError) { } // UninstallFilter uninstalls a filter with given id. -func (e *Eth) UninstallFilter(filterID string) (interface{}, rpcError) { +func (e *EthEndpoints) UninstallFilter(filterID string) (interface{}, rpcError) { err := e.storage.UninstallFilter(filterID) if errors.Is(err, ErrNotFound) { return false, nil @@ -661,7 +660,7 @@ func (e *Eth) UninstallFilter(filterID string) (interface{}, rpcError) { // Syncing returns an object with data about the sync status or false. // https://eth.wiki/json-rpc/API#eth_syncing -func (e *Eth) Syncing() (interface{}, rpcError) { +func (e *EthEndpoints) Syncing() (interface{}, rpcError) { return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { syncInfo, err := e.state.GetSyncingInfo(ctx, dbTx) if err != nil { @@ -686,30 +685,30 @@ func (e *Eth) Syncing() (interface{}, rpcError) { // GetUncleByBlockHashAndIndex returns information about a uncle of a // block by hash and uncle index position -func (e *Eth) GetUncleByBlockHashAndIndex() (interface{}, rpcError) { +func (e *EthEndpoints) GetUncleByBlockHashAndIndex() (interface{}, rpcError) { return nil, nil } // GetUncleByBlockNumberAndIndex returns information about a uncle of a // block by number and uncle index position -func (e *Eth) GetUncleByBlockNumberAndIndex() (interface{}, rpcError) { +func (e *EthEndpoints) GetUncleByBlockNumberAndIndex() (interface{}, rpcError) { return nil, nil } // GetUncleCountByBlockHash returns the number of uncles in a block -// mlocking the given block hash -func (e *Eth) GetUncleCountByBlockHash() (interface{}, rpcError) { +// matching the given block hash +func (e *EthEndpoints) GetUncleCountByBlockHash() (interface{}, rpcError) { return "0x0", nil } // GetUncleCountByBlockNumber returns the number of uncles in a block -// mlocking the given block number -func (e *Eth) GetUncleCountByBlockNumber() (interface{}, rpcError) { +// matching the given block number +func (e *EthEndpoints) GetUncleCountByBlockNumber() (interface{}, rpcError) { return "0x0", nil } // ProtocolVersion returns the protocol version. -func (e *Eth) ProtocolVersion() (interface{}, rpcError) { +func (e *EthEndpoints) ProtocolVersion() (interface{}, rpcError) { return "0x0", nil } @@ -728,7 +727,7 @@ func hexToTx(str string) (*types.Transaction, error) { return tx, nil } -func (e *Eth) getBlockHeader(ctx context.Context, number BlockNumber, dbTx pgx.Tx) (*types.Header, error) { +func (e *EthEndpoints) getBlockHeader(ctx context.Context, number BlockNumber, dbTx pgx.Tx) (*types.Header, error) { switch number { case LatestBlockNumber: block, err := e.state.GetLastL2Block(ctx, dbTx) @@ -765,7 +764,7 @@ func (e *Eth) getBlockHeader(ctx context.Context, number BlockNumber, dbTx pgx.T } } -func (e *Eth) updateFilterLastPoll(filterID string) rpcError { +func (e *EthEndpoints) updateFilterLastPoll(filterID string) rpcError { err := e.storage.UpdateFilterLastPoll(filterID) if err != nil && !errors.Is(err, ErrNotFound) { return newRPCError(defaultErrorCode, "failed to update last time the filter changes were requested") @@ -777,7 +776,7 @@ func (e *Eth) updateFilterLastPoll(filterID string) rpcError { // The node will return a subscription id. // For each event that matches the subscription a notification with relevant // data is sent together with the subscription id. -func (e *Eth) Subscribe(wsConn *websocket.Conn, name string, logFilter *LogFilter) (interface{}, rpcError) { +func (e *EthEndpoints) Subscribe(wsConn *websocket.Conn, name string, logFilter *LogFilter) (interface{}, rpcError) { switch name { case "newHeads": return e.newBlockFilter(wsConn) @@ -797,18 +796,18 @@ func (e *Eth) Subscribe(wsConn *websocket.Conn, name string, logFilter *LogFilte } // Unsubscribe uninstalls the filter based on the provided filterID -func (e *Eth) Unsubscribe(wsConn *websocket.Conn, filterID string) (interface{}, rpcError) { +func (e *EthEndpoints) Unsubscribe(wsConn *websocket.Conn, filterID string) (interface{}, rpcError) { return e.UninstallFilter(filterID) } // uninstallFilterByWSConn uninstalls the filters connected to the // provided web socket connection -func (e *Eth) uninstallFilterByWSConn(wsConn *websocket.Conn) error { +func (e *EthEndpoints) uninstallFilterByWSConn(wsConn *websocket.Conn) error { return e.storage.UninstallFilterByWSConn(wsConn) } // onNewL2Block is triggered when the state triggers the event for a new l2 block -func (e *Eth) onNewL2Block(event state.NewL2BlockEvent) { +func (e *EthEndpoints) onNewL2Block(event state.NewL2BlockEvent) { blockFilters, err := e.storage.GetAllBlockFiltersWithWSConn() if err != nil { log.Errorf("failed to get all block filters with web sockets connections: %v", err) @@ -837,7 +836,7 @@ func (e *Eth) onNewL2Block(event state.NewL2BlockEvent) { } } -func (e *Eth) sendSubscriptionResponse(filter *Filter, data interface{}) { +func (e *EthEndpoints) sendSubscriptionResponse(filter *Filter, data interface{}) { const errMessage = "Unable to write WS message to filter %v, %s" result, err := json.Marshal(data) if err != nil { diff --git a/jsonrpc/eth_test.go b/jsonrpc/endpoints_eth_test.go similarity index 100% rename from jsonrpc/eth_test.go rename to jsonrpc/endpoints_eth_test.go diff --git a/jsonrpc/net.go b/jsonrpc/endpoints_net.go similarity index 57% rename from jsonrpc/net.go rename to jsonrpc/endpoints_net.go index 4b6db0b90e..f1f5f33ef6 100644 --- a/jsonrpc/net.go +++ b/jsonrpc/endpoints_net.go @@ -6,12 +6,12 @@ import ( "github.com/0xPolygonHermez/zkevm-node/encoding" ) -// Net contains implementations for the "net" RPC endpoints -type Net struct { +// NetEndpoints contains implementations for the "net" RPC endpoints +type NetEndpoints struct { cfg Config } // Version returns the current network id -func (n *Net) Version() (interface{}, rpcError) { +func (n *NetEndpoints) Version() (interface{}, rpcError) { return strconv.FormatUint(n.cfg.ChainID, encoding.Base10), nil } diff --git a/jsonrpc/txpool.go b/jsonrpc/endpoints_txpool.go similarity index 88% rename from jsonrpc/txpool.go rename to jsonrpc/endpoints_txpool.go index a87875dd4f..a5fd0b81e8 100644 --- a/jsonrpc/txpool.go +++ b/jsonrpc/endpoints_txpool.go @@ -4,8 +4,8 @@ import ( "github.com/ethereum/go-ethereum/common" ) -// TxPool is the txpool jsonrpc endpoint -type TxPool struct{} +// TxPoolEndpoints is the txpool jsonrpc endpoint +type TxPoolEndpoints struct{} type contentResponse struct { Pending map[common.Address]map[uint64]*txPoolTransaction `json:"pending"` @@ -28,7 +28,7 @@ type txPoolTransaction struct { // Content creates a response for txpool_content request. // See https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_content. -func (t *TxPool) Content() (interface{}, rpcError) { +func (e *TxPoolEndpoints) Content() (interface{}, rpcError) { resp := contentResponse{ Pending: make(map[common.Address]map[uint64]*txPoolTransaction), Queued: make(map[common.Address]map[uint64]*txPoolTransaction), diff --git a/jsonrpc/web3.go b/jsonrpc/endpoints_web3.go similarity index 57% rename from jsonrpc/web3.go rename to jsonrpc/endpoints_web3.go index 5867679569..64f1a64a03 100644 --- a/jsonrpc/web3.go +++ b/jsonrpc/endpoints_web3.go @@ -6,17 +6,17 @@ import ( "github.com/iden3/go-iden3-crypto/keccak256" ) -// Web3 contains implementations for the "web3" RPC endpoints -type Web3 struct { +// Web3Endpoints contains implementations for the "web3" RPC endpoints +type Web3Endpoints struct { } // ClientVersion returns the client version. -func (w *Web3) ClientVersion() (interface{}, rpcError) { +func (e *Web3Endpoints) ClientVersion() (interface{}, rpcError) { return "Polygon Hermez zkEVM/v2.0.0", nil } // Sha3 returns the keccak256 hash of the given data. -func (w *Web3) Sha3(data argBig) (interface{}, rpcError) { +func (e *Web3Endpoints) Sha3(data argBig) (interface{}, rpcError) { b := (*big.Int)(&data) return argBytes(keccak256.Hash(b.Bytes())), nil } diff --git a/jsonrpc/web3_test.go b/jsonrpc/endpoints_web3_test.go similarity index 100% rename from jsonrpc/web3_test.go rename to jsonrpc/endpoints_web3_test.go diff --git a/jsonrpc/endpoints_zkevm.go b/jsonrpc/endpoints_zkevm.go new file mode 100644 index 0000000000..f95a98091a --- /dev/null +++ b/jsonrpc/endpoints_zkevm.go @@ -0,0 +1,165 @@ +package jsonrpc + +import ( + "context" + "errors" + "fmt" + + "github.com/0xPolygonHermez/zkevm-node/hex" + "github.com/0xPolygonHermez/zkevm-node/log" + "github.com/0xPolygonHermez/zkevm-node/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/jackc/pgx/v4" +) + +// ZKEVMEndpoints contains implementations for the "zkevm" RPC endpoints +type ZKEVMEndpoints struct { + config Config + state stateInterface + txMan dbTxManager +} + +// ConsolidatedBlockNumber returns current block number for consolidated blocks +func (z *ZKEVMEndpoints) ConsolidatedBlockNumber() (interface{}, rpcError) { + return z.txMan.NewDbTxScope(z.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { + lastBlockNumber, err := z.state.GetLastConsolidatedL2BlockNumber(ctx, dbTx) + if err != nil { + const errorMessage = "failed to get last consolidated block number from state" + log.Errorf("%v:%v", errorMessage, err) + return nil, newRPCError(defaultErrorCode, errorMessage) + } + + return hex.EncodeUint64(lastBlockNumber), nil + }) +} + +// IsBlockConsolidated returns the consolidation status of a provided block number +func (z *ZKEVMEndpoints) IsBlockConsolidated(blockNumber argUint64) (interface{}, rpcError) { + return z.txMan.NewDbTxScope(z.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { + IsL2BlockConsolidated, err := z.state.IsL2BlockConsolidated(ctx, uint64(blockNumber), dbTx) + if err != nil { + const errorMessage = "failed to check if the block is consolidated" + log.Errorf("%v: %v", errorMessage, err) + return nil, newRPCError(defaultErrorCode, errorMessage) + } + + return IsL2BlockConsolidated, nil + }) +} + +// IsBlockVirtualized returns the virtualization status of a provided block number +func (z *ZKEVMEndpoints) IsBlockVirtualized(blockNumber argUint64) (interface{}, rpcError) { + return z.txMan.NewDbTxScope(z.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { + IsL2BlockVirtualized, err := z.state.IsL2BlockVirtualized(ctx, uint64(blockNumber), dbTx) + if err != nil { + const errorMessage = "failed to check if the block is virtualized" + log.Errorf("%v: %v", errorMessage, err) + return nil, newRPCError(defaultErrorCode, errorMessage) + } + + return IsL2BlockVirtualized, nil + }) +} + +// BatchNumberByBlockNumber returns the batch number from which the passed block number is created +func (z *ZKEVMEndpoints) BatchNumberByBlockNumber(blockNumber argUint64) (interface{}, rpcError) { + return z.txMan.NewDbTxScope(z.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { + batchNum, err := z.state.BatchNumberByL2BlockNumber(ctx, uint64(blockNumber), dbTx) + if err != nil { + const errorMessage = "failed to get batch number from block number" + log.Errorf("%v: %v", errorMessage, err.Error()) + return nil, newRPCError(defaultErrorCode, errorMessage) + } + + return batchNum, nil + }) +} + +// BatchNumber returns the latest virtualized batch number +func (z *ZKEVMEndpoints) BatchNumber() (interface{}, rpcError) { + return z.txMan.NewDbTxScope(z.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { + lastBatchNumber, err := z.state.GetLastBatchNumber(ctx, dbTx) + if err != nil { + return "0x0", newRPCError(defaultErrorCode, "failed to get the last batch number from state") + } + + return hex.EncodeUint64(lastBatchNumber), nil + }) +} + +// VirtualBatchNumber returns the latest virtualized batch number +func (z *ZKEVMEndpoints) VirtualBatchNumber() (interface{}, rpcError) { + return z.txMan.NewDbTxScope(z.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { + lastBatchNumber, err := z.state.GetLastVirtualBatchNum(ctx, dbTx) + if err != nil { + return "0x0", newRPCError(defaultErrorCode, "failed to get the last virtual batch number from state") + } + + return hex.EncodeUint64(lastBatchNumber), nil + }) +} + +// VerifiedBatchNumber returns the latest verified batch number +func (z *ZKEVMEndpoints) VerifiedBatchNumber() (interface{}, rpcError) { + return z.txMan.NewDbTxScope(z.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { + lastBatch, err := z.state.GetLastVerifiedBatch(ctx, dbTx) + if err != nil { + return "0x0", newRPCError(defaultErrorCode, "failed to get the last verified batch number from state") + } + + return hex.EncodeUint64(lastBatch.BatchNumber), nil + }) +} + +// GetBatchByNumber returns information about a batch by batch number +func (z *ZKEVMEndpoints) GetBatchByNumber(batchNumber BatchNumber, fullTx bool) (interface{}, rpcError) { + return z.txMan.NewDbTxScope(z.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { + var err error + batchNumber, rpcErr := batchNumber.getNumericBatchNumber(ctx, z.state, dbTx) + if rpcErr != nil { + return nil, rpcErr + } + + batch, err := z.state.GetBatchByNumber(ctx, batchNumber, dbTx) + if errors.Is(err, state.ErrNotFound) { + return nil, nil + } else if err != nil { + return rpcErrorResponse(defaultErrorCode, fmt.Sprintf("couldn't load batch from state by number %v", batchNumber), err) + } + + txs, err := z.state.GetTransactionsByBatchNumber(ctx, batchNumber, dbTx) + if !errors.Is(err, state.ErrNotFound) && err != nil { + return rpcErrorResponse(defaultErrorCode, fmt.Sprintf("couldn't load batch txs from state by number %v", batchNumber), err) + } + + receipts := make([]types.Receipt, 0, len(txs)) + for _, tx := range txs { + receipt, err := z.state.GetTransactionReceipt(ctx, tx.Hash(), dbTx) + if err != nil { + return rpcErrorResponse(defaultErrorCode, fmt.Sprintf("couldn't load receipt for tx %v", tx.Hash().String()), err) + } + receipts = append(receipts, *receipt) + } + + virtualBatch, err := z.state.GetVirtualBatch(ctx, batchNumber, dbTx) + if err != nil && !errors.Is(err, state.ErrNotFound) { + return rpcErrorResponse(defaultErrorCode, fmt.Sprintf("couldn't load virtual batch from state by number %v", batchNumber), err) + } + + verifiedBatch, err := z.state.GetVerifiedBatch(ctx, batchNumber, dbTx) + if err != nil && !errors.Is(err, state.ErrNotFound) { + return rpcErrorResponse(defaultErrorCode, fmt.Sprintf("couldn't load virtual batch from state by number %v", batchNumber), err) + } + + batch.Transactions = txs + rpcBatch := l2BatchToRPCBatch(batch, virtualBatch, verifiedBatch, receipts, fullTx) + + return rpcBatch, nil + }) +} + +// GetBroadcastURI returns the IP:PORT of the broadcast service provided +// by the Trusted Sequencer JSON RPC server +func (z *ZKEVMEndpoints) GetBroadcastURI() (interface{}, rpcError) { + return z.config.BroadcastURI, nil +} diff --git a/jsonrpc/endpoints_zkevm.openrpc.json b/jsonrpc/endpoints_zkevm.openrpc.json new file mode 100644 index 0000000000..7143b65281 --- /dev/null +++ b/jsonrpc/endpoints_zkevm.openrpc.json @@ -0,0 +1,502 @@ +{ + "openrpc": "1.0.0-rc1", + "info": { + "title": "zkEVM Endpoints", + "version": "2.0.0" + }, + "methods": [ + { + "name": "zkevm_consolidatedBlockNumber", + "summary": "Returns the latest block number that is connected to the latest batch verified.", + "params": [], + "result": { + "name": "result", + "schema": { + "$ref": "#/components/contentDescriptors/BlockNumber" + } + }, + "examples": [ + { + "name": "example", + "description": "", + "params": [], + "result": { + "name": "exampleResult", + "description": "", + "value": "0x1" + } + } + ] + }, + { + "name": "zkevm_isBlockVirtualized", + "summary": "Returns true if the provided block number is already connected to a batch that was already virtualized, otherwise false.", + "params": [ + { + "name": "blockNumber", + "schema": { + "$ref": "#/components/contentDescriptors/BlockNumber" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "boolean" + } + }, + "examples": [ + { + "name": "example", + "description": "", + "params": [], + "result": { + "name": "exampleResult", + "description": "", + "value": true + } + } + ] + }, + { + "name": "zkevm_isBlockConsolidated", + "summary": "Returns true if the provided block number is already connected to a batch that was already verified, otherwise false.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockNumber" + } + ], + "result": { + "name": "result", + "schema": { + "type": "boolean" + } + }, + "examples": [ + { + "name": "example", + "description": "", + "params": [], + "result": { + "name": "exampleResult", + "description": "", + "value": true + } + } + ] + }, + { + "name": "zkevm_getBroadcastURI", + "summary": "Returns the configured Broadcast URL of the Trusted Sequencer.", + "params": [], + "result": { + "name": "result", + "schema": { + "type": "string" + } + }, + "examples": [ + { + "name": "example", + "description": "", + "params": [], + "result": { + "name": "exampleResult", + "description": "", + "value": "https://broadcast:1111" + } + } + ] + }, + { + "name": "zkevm_batchNumber", + "summary": "Returns the latest batch number.", + "params": [], + "result": { + "name": "result", + "schema": { + "$ref": "#/components/contentDescriptors/BatchNumber" + } + }, + "examples": [ + { + "name": "example", + "description": "", + "params": [], + "result": { + "name": "exampleResult", + "description": "", + "value": "0x1" + } + } + ] + }, + { + "name": "zkevm_virtualBatchNumber", + "summary": "Returns the latest virtual batch number.", + "params": [], + "result": { + "name": "result", + "schema": { + "$ref": "#/components/contentDescriptors/BatchNumber" + } + }, + "examples": [ + { + "name": "example", + "description": "", + "params": [], + "result": { + "name": "exampleResult", + "description": "", + "value": "0x1" + } + } + ] + }, + { + "name": "zkevm_verifiedBatchNumber", + "summary": "Returns the latest verified batch number.", + "params": [], + "result": { + "name": "result", + "schema": { + "$ref": "#/components/contentDescriptors/BatchNumber" + } + }, + "examples": [ + { + "name": "example", + "description": "", + "params": [], + "result": { + "name": "exampleResult", + "description": "", + "value": "0x1" + } + } + ] + }, + { + "name": "zkevm_batchNumberByBlockNumber", + "summary": "Returns the batch number of the batch connected to the block.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockNumber" + } + ], + "result": { + "name": "result", + "schema": { + "$ref": "#/components/contentDescriptors/BatchNumber" + } + }, + "examples": [ + { + "name": "example", + "description": "", + "params": [], + "result": { + "name": "exampleResult", + "description": "", + "value": "0x1" + } + } + ] + }, + { + "name": "zkevm_getBatchByNumber", + "summary": "", + "params": [ + { + "$ref": "#/components/contentDescriptors/BatchNumberOrTag" + } + ], + "result": { + "$ref": "#/components/contentDescriptors/Batch" + } + } + ], + "components": { + "contentDescriptors": { + "BlockNumber": { + "name": "blockNumber", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockNumber" + } + }, + "BatchNumber": { + "name": "batchNumber", + "required": true, + "schema": { + "$ref": "#/components/schemas/BatchNumber" + } + }, + "BatchNumberOrTag": { + "name": "batchNumberOrTag", + "required": true, + "schema": { + "title": "batchNumberOrTag", + "oneOf": [ + { + "$ref": "#/components/schemas/BatchNumber" + }, + { + "$ref": "#/components/schemas/BatchNumberTag" + } + ] + } + }, + "Batch": { + "name": "batch", + "description": "batch", + "required": true, + "schema": { + "$ref": "#/components/schemas/Batch" + } + } + }, + "schemas": { + "Null": { + "title": "null", + "type": "null", + "description": "Null" + }, + "BatchNumberTag": { + "title": "batchNumberTag", + "type": "string", + "description": "The optional batch height description", + "enum": [ + "earliest", + "latest" + ] + }, + "Integer": { + "title": "integer", + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "Hex representation of the integer" + }, + "Keccak": { + "title": "keccak", + "type": "string", + "description": "Hex representation of a Keccak 256 hash", + "pattern": "^0x[a-fA-F\\d]{64}$" + }, + "Address": { + "title": "address", + "type": "string", + "pattern": "^0x[a-fA-F\\d]{40}$" + }, + "BlockNumber": { + "title": "blockNumber", + "type": "string", + "description": "The hex representation of the block's height", + "$ref": "#/components/schemas/Integer" + }, + "BatchNumber": { + "title": "batchNumber", + "type": "string", + "description": "The hex representation of the batch's height", + "$ref": "#/components/schemas/Integer" + }, + "TransactionHash": { + "title": "transactionHash", + "type": "string", + "description": "Keccak 256 Hash of the RLP encoding of a transaction", + "$ref": "#/components/schemas/Keccak" + }, + "Nonce": { + "title": "nonce", + "description": "A number only to be used once", + "$ref": "#/components/schemas/Integer" + }, + "From": { + "title": "From", + "description": "The sender of the transaction", + "$ref": "#/components/schemas/Address" + }, + "BlockNumberOrNull": { + "title": "blockNumberOrNull", + "description": "The block number or null when its the pending block", + "oneOf": [ + { + "$ref": "#/components/schemas/BlockNumber" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + }, + "IntegerOrNull": { + "title": "integerOrNull", + "oneOf": [ + { + "$ref": "#/components/schemas/Integer" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + }, + "KeccakOrPending": { + "title": "keccakOrPending", + "oneOf": [ + { + "$ref": "#/components/schemas/Keccak" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + }, + "To": { + "title": "To", + "description": "Destination address of the transaction. Null if it was a contract create.", + "oneOf": [ + { + "$ref": "#/components/schemas/Address" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + }, + "BlockHashOrNull": { + "title": "blockHashOrNull", + "description": "The block hash or null when its the pending block", + "$ref": "#/components/schemas/KeccakOrPending" + }, + "TransactionIndex": { + "title": "transactionIndex", + "description": "The index of the transaction. null when its pending", + "$ref": "#/components/schemas/IntegerOrNull" + }, + "Batch": { + "title": "Batch", + "type": "object", + "readOnly": true, + "properties": { + "number": { + "$ref": "#/components/schemas/BlockNumber" + }, + "transactions": { + "title": "transactionsOrHashes", + "description": "Array of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter", + "type": "array", + "items": { + "title": "transactionOrTransactionHash", + "oneOf": [ + { + "$ref": "#/components/schemas/Transaction" + }, + { + "$ref": "#/components/schemas/TransactionHash" + } + ] + } + }, + "globalExitRoot": { + "$ref": "#/components/schemas/Keccak" + }, + "accInputHash": { + "$ref": "#/components/schemas/Keccak" + }, + "timestamp": { + "$ref": "#/components/schemas/Integer" + }, + "sendSequencesTxHash": { + "$ref": "#/components/schemas/TransactionHash" + }, + "verifyBatchTxHash": { + "$ref": "#/components/schemas/TransactionHash" + }, + "stateRoot": { + "$ref": "#/components/schemas/Keccak" + }, + "coinbase": { + "$ref": "#/components/schemas/Address" + } + } + }, + "Transaction": { + "title": "transaction", + "type": "object", + "required": [ + "gas", + "gasPrice", + "nonce" + ], + "properties": { + "blockHash": { + "$ref": "#/components/schemas/BlockHashOrNull" + }, + "blockNumber": { + "$ref": "#/components/schemas/BlockNumberOrNull" + }, + "from": { + "$ref": "#/components/schemas/From" + }, + "gas": { + "title": "transactionGas", + "type": "string", + "description": "The gas limit provided by the sender in Wei" + }, + "gasPrice": { + "title": "transactionGasPrice", + "type": "string", + "description": "The gas price willing to be paid by the sender in Wei" + }, + "hash": { + "$ref": "#/components/schemas/TransactionHash" + }, + "input": { + "title": "transactionInput", + "type": "string", + "description": "The data field sent with the transaction" + }, + "nonce": { + "title": "transactionNonce", + "description": "The total number of prior transactions made by the sender", + "$ref": "#/components/schemas/Nonce" + }, + "to": { + "$ref": "#/components/schemas/To" + }, + "transactionIndex": { + "$ref": "#/components/schemas/TransactionIndex" + }, + "value": { + "title": "transactionValue", + "description": "Value of Ether being transferred in Wei", + "$ref": "#/components/schemas/Keccak" + }, + "v": { + "title": "transactionSigV", + "type": "string", + "description": "ECDSA recovery id" + }, + "r": { + "title": "transactionSigR", + "type": "string", + "description": "ECDSA signature r" + }, + "s": { + "title": "transactionSigS", + "type": "string", + "description": "ECDSA signature s" + } + } + }, + "Transactions": { + "title": "transactions", + "description": "An array of transactions", + "type": "array", + "items": { + "$ref": "#/components/schemas/Transaction" + } + } + } + } +} \ No newline at end of file diff --git a/jsonrpc/endpoints_zkevm_test.go b/jsonrpc/endpoints_zkevm_test.go new file mode 100644 index 0000000000..5d85a6c92f --- /dev/null +++ b/jsonrpc/endpoints_zkevm_test.go @@ -0,0 +1,1086 @@ +package jsonrpc + +import ( + "context" + "encoding/json" + "errors" + "math/big" + "strings" + "testing" + "time" + + "github.com/0xPolygonHermez/zkevm-node/hex" + "github.com/0xPolygonHermez/zkevm-node/state" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestConsolidatedBlockNumber(t *testing.T) { + s, m, _ := newSequencerMockedServer(t) + defer s.Stop() + + type testCase struct { + Name string + ExpectedResult *uint64 + ExpectedError rpcError + SetupMocks func(m *mocks) + } + + testCases := []testCase{ + { + Name: "Get consolidated block number successfully", + ExpectedResult: ptrUint64(10), + SetupMocks: func(m *mocks) { + m.DbTx. + On("Commit", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("GetLastConsolidatedL2BlockNumber", context.Background(), m.DbTx). + Return(uint64(10), nil). + Once() + }, + }, + { + Name: "failed to get consolidated block number", + ExpectedResult: nil, + ExpectedError: newRPCError(defaultErrorCode, "failed to get last consolidated block number from state"), + SetupMocks: func(m *mocks) { + m.DbTx. + On("Rollback", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("GetLastConsolidatedL2BlockNumber", context.Background(), m.DbTx). + Return(uint64(0), errors.New("failed to get last consolidated block number")). + Once() + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + tc := testCase + tc.SetupMocks(m) + + res, err := s.JSONRPCCall("zkevm_consolidatedBlockNumber") + require.NoError(t, err) + + if res.Result != nil { + var result argUint64 + err = json.Unmarshal(res.Result, &result) + require.NoError(t, err) + assert.Equal(t, *tc.ExpectedResult, uint64(result)) + } + + if res.Error != nil || tc.ExpectedError != nil { + assert.Equal(t, tc.ExpectedError.ErrorCode(), res.Error.Code) + assert.Equal(t, tc.ExpectedError.Error(), res.Error.Message) + } + }) + } +} + +func TestIsBlockConsolidated(t *testing.T) { + s, m, _ := newSequencerMockedServer(t) + defer s.Stop() + + type testCase struct { + Name string + ExpectedResult bool + ExpectedError rpcError + SetupMocks func(m *mocks) + } + + testCases := []testCase{ + { + Name: "Query status of block number successfully", + ExpectedResult: true, + SetupMocks: func(m *mocks) { + m.DbTx. + On("Commit", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("IsL2BlockConsolidated", context.Background(), uint64(1), m.DbTx). + Return(true, nil). + Once() + }, + }, + { + Name: "Failed to query the consolidation status", + ExpectedResult: false, + ExpectedError: newRPCError(defaultErrorCode, "failed to check if the block is consolidated"), + SetupMocks: func(m *mocks) { + m.DbTx. + On("Rollback", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("IsL2BlockConsolidated", context.Background(), uint64(1), m.DbTx). + Return(false, errors.New("failed to check if the block is consolidated")). + Once() + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + tc := testCase + tc.SetupMocks(m) + + res, err := s.JSONRPCCall("zkevm_isBlockConsolidated", "0x1") + require.NoError(t, err) + + if res.Result != nil { + var result bool + err = json.Unmarshal(res.Result, &result) + require.NoError(t, err) + assert.Equal(t, tc.ExpectedResult, result) + } + + if res.Error != nil || tc.ExpectedError != nil { + assert.Equal(t, tc.ExpectedError.ErrorCode(), res.Error.Code) + assert.Equal(t, tc.ExpectedError.Error(), res.Error.Message) + } + }) + } +} + +func TestIsBlockVirtualized(t *testing.T) { + s, m, _ := newSequencerMockedServer(t) + defer s.Stop() + + type testCase struct { + Name string + ExpectedResult bool + ExpectedError rpcError + SetupMocks func(m *mocks) + } + + testCases := []testCase{ + { + Name: "Query status of block number successfully", + ExpectedResult: true, + SetupMocks: func(m *mocks) { + m.DbTx. + On("Commit", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("IsL2BlockVirtualized", context.Background(), uint64(1), m.DbTx). + Return(true, nil). + Once() + }, + }, + { + Name: "Failed to query the virtualization status", + ExpectedResult: false, + ExpectedError: newRPCError(defaultErrorCode, "failed to check if the block is virtualized"), + SetupMocks: func(m *mocks) { + m.DbTx. + On("Rollback", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("IsL2BlockVirtualized", context.Background(), uint64(1), m.DbTx). + Return(false, errors.New("failed to check if the block is virtualized")). + Once() + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + tc := testCase + tc.SetupMocks(m) + + res, err := s.JSONRPCCall("zkevm_isBlockVirtualized", "0x1") + require.NoError(t, err) + + if res.Result != nil { + var result bool + err = json.Unmarshal(res.Result, &result) + require.NoError(t, err) + assert.Equal(t, tc.ExpectedResult, result) + } + + if res.Error != nil || tc.ExpectedError != nil { + assert.Equal(t, tc.ExpectedError.ErrorCode(), res.Error.Code) + assert.Equal(t, tc.ExpectedError.Error(), res.Error.Message) + } + }) + } +} + +func TestBatchNumberByBlockNumber(t *testing.T) { + s, m, _ := newSequencerMockedServer(t) + defer s.Stop() + blockNumber := uint64(1) + batchNumber := uint64(1) + + type testCase struct { + Name string + ExpectedResult uint64 + ExpectedError rpcError + SetupMocks func(m *mocks) + } + + testCases := []testCase{ + { + Name: "Query status of batch number of l2 block by its number successfully", + ExpectedResult: batchNumber, + SetupMocks: func(m *mocks) { + m.DbTx. + On("Commit", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("BatchNumberByL2BlockNumber", context.Background(), blockNumber, m.DbTx). + Return(batchNumber, nil). + Once() + }, + }, + { + Name: "Failed to query the consolidation status", + ExpectedResult: uint64(0), + ExpectedError: newRPCError(defaultErrorCode, "failed to get batch number from block number"), + SetupMocks: func(m *mocks) { + m.DbTx. + On("Rollback", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("BatchNumberByL2BlockNumber", context.Background(), blockNumber, m.DbTx). + Return(uint64(0), errors.New("failed to get batch number of l2 batchNum")). + Once() + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + tc := testCase + tc.SetupMocks(m) + + res, err := s.JSONRPCCall("zkevm_batchNumberByBlockNumber", hex.EncodeUint64(blockNumber)) + require.NoError(t, err) + + if res.Result != nil { + var result uint64 + err = json.Unmarshal(res.Result, &result) + require.NoError(t, err) + assert.Equal(t, tc.ExpectedResult, result) + } + + if res.Error != nil || tc.ExpectedError != nil { + assert.Equal(t, tc.ExpectedError.ErrorCode(), res.Error.Code) + assert.Equal(t, tc.ExpectedError.Error(), res.Error.Message) + } + }) + } +} + +func TestBatchNumber(t *testing.T) { + s, m, _ := newSequencerMockedServer(t) + defer s.Stop() + + type testCase struct { + Name string + ExpectedResult uint64 + ExpectedError rpcError + SetupMocks func(m *mocks) + } + + testCases := []testCase{ + { + Name: "get batch number successfully", + ExpectedError: nil, + ExpectedResult: 10, + SetupMocks: func(m *mocks) { + m.DbTx. + On("Commit", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("GetLastBatchNumber", context.Background(), m.DbTx). + Return(uint64(10), nil). + Once() + }, + }, + { + Name: "failed to get batch number", + ExpectedError: newRPCError(defaultErrorCode, "failed to get the last batch number from state"), + ExpectedResult: 0, + SetupMocks: func(m *mocks) { + m.DbTx. + On("Rollback", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("GetLastBatchNumber", context.Background(), m.DbTx). + Return(uint64(0), errors.New("failed to get last batch number")). + Once() + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + tc := testCase + tc.SetupMocks(m) + + res, err := s.JSONRPCCall("zkevm_batchNumber") + require.NoError(t, err) + + if res.Result != nil { + var result argUint64 + err = json.Unmarshal(res.Result, &result) + require.NoError(t, err) + assert.Equal(t, tc.ExpectedResult, uint64(result)) + } + + if res.Error != nil || tc.ExpectedError != nil { + assert.Equal(t, tc.ExpectedError.ErrorCode(), res.Error.Code) + assert.Equal(t, tc.ExpectedError.Error(), res.Error.Message) + } + }) + } +} + +func TestVirtualBatchNumber(t *testing.T) { + s, m, _ := newSequencerMockedServer(t) + defer s.Stop() + + type testCase struct { + Name string + ExpectedResult uint64 + ExpectedError rpcError + SetupMocks func(m *mocks) + } + + testCases := []testCase{ + { + Name: "get virtual batch number successfully", + ExpectedError: nil, + ExpectedResult: 10, + SetupMocks: func(m *mocks) { + m.DbTx. + On("Commit", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("GetLastVirtualBatchNum", context.Background(), m.DbTx). + Return(uint64(10), nil). + Once() + }, + }, + { + Name: "failed to get virtual batch number", + ExpectedError: newRPCError(defaultErrorCode, "failed to get the last virtual batch number from state"), + ExpectedResult: 0, + SetupMocks: func(m *mocks) { + m.DbTx. + On("Rollback", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("GetLastVirtualBatchNum", context.Background(), m.DbTx). + Return(uint64(0), errors.New("failed to get last batch number")). + Once() + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + tc := testCase + tc.SetupMocks(m) + + res, err := s.JSONRPCCall("zkevm_virtualBatchNumber") + require.NoError(t, err) + + if res.Result != nil { + var result argUint64 + err = json.Unmarshal(res.Result, &result) + require.NoError(t, err) + assert.Equal(t, tc.ExpectedResult, uint64(result)) + } + + if res.Error != nil || tc.ExpectedError != nil { + assert.Equal(t, tc.ExpectedError.ErrorCode(), res.Error.Code) + assert.Equal(t, tc.ExpectedError.Error(), res.Error.Message) + } + }) + } +} + +func TestVerifiedBatchNumber(t *testing.T) { + s, m, _ := newSequencerMockedServer(t) + defer s.Stop() + + type testCase struct { + Name string + ExpectedResult uint64 + ExpectedError rpcError + SetupMocks func(m *mocks) + } + + testCases := []testCase{ + { + Name: "get verified batch number successfully", + ExpectedError: nil, + ExpectedResult: 10, + SetupMocks: func(m *mocks) { + m.DbTx. + On("Commit", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("GetLastVerifiedBatch", context.Background(), m.DbTx). + Return(&state.VerifiedBatch{BatchNumber: uint64(10)}, nil). + Once() + }, + }, + { + Name: "failed to get verified batch number", + ExpectedError: newRPCError(defaultErrorCode, "failed to get the last verified batch number from state"), + ExpectedResult: 0, + SetupMocks: func(m *mocks) { + m.DbTx. + On("Rollback", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("GetLastVerifiedBatch", context.Background(), m.DbTx). + Return(nil, errors.New("failed to get last batch number")). + Once() + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + tc := testCase + tc.SetupMocks(m) + + res, err := s.JSONRPCCall("zkevm_verifiedBatchNumber") + require.NoError(t, err) + + if res.Result != nil { + var result argUint64 + err = json.Unmarshal(res.Result, &result) + require.NoError(t, err) + assert.Equal(t, tc.ExpectedResult, uint64(result)) + } + + if res.Error != nil || tc.ExpectedError != nil { + assert.Equal(t, tc.ExpectedError.ErrorCode(), res.Error.Code) + assert.Equal(t, tc.ExpectedError.Error(), res.Error.Message) + } + }) + } +} + +func TestGetBatchByNumber(t *testing.T) { + type testCase struct { + Name string + Number string + WithTxDetail bool + ExpectedResult *rpcBatch + ExpectedError rpcError + SetupMocks func(*mockedServer, *mocks, *testCase) + } + + testCases := []testCase{ + { + Name: "Batch not found", + Number: "0x123", + ExpectedResult: nil, + ExpectedError: nil, + SetupMocks: func(s *mockedServer, m *mocks, tc *testCase) { + m.DbTx. + On("Commit", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("GetBatchByNumber", context.Background(), hex.DecodeBig(tc.Number).Uint64(), m.DbTx). + Return(nil, state.ErrNotFound) + }, + }, + { + Name: "get specific batch successfully with tx detail", + Number: "0x345", + WithTxDetail: true, + ExpectedResult: &rpcBatch{ + Number: 1, + Coinbase: common.HexToAddress("0x1"), + StateRoot: common.HexToHash("0x2"), + AccInputHash: common.HexToHash("0x3"), + GlobalExitRoot: common.HexToHash("0x4"), + Timestamp: 1, + SendSequencesTxHash: ptrHash(common.HexToHash("0x10")), + VerifyBatchTxHash: ptrHash(common.HexToHash("0x20")), + }, + ExpectedError: nil, + SetupMocks: func(s *mockedServer, m *mocks, tc *testCase) { + m.DbTx. + On("Commit", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + batch := &state.Batch{ + BatchNumber: 1, + Coinbase: common.HexToAddress("0x1"), + StateRoot: common.HexToHash("0x2"), + AccInputHash: common.HexToHash("0x3"), + GlobalExitRoot: common.HexToHash("0x4"), + Timestamp: time.Unix(1, 0), + } + + m.State. + On("GetBatchByNumber", context.Background(), hex.DecodeBig(tc.Number).Uint64(), m.DbTx). + Return(batch, nil). + Once() + + virtualBatch := &state.VirtualBatch{ + TxHash: common.HexToHash("0x10"), + } + + m.State. + On("GetVirtualBatch", context.Background(), hex.DecodeBig(tc.Number).Uint64(), m.DbTx). + Return(virtualBatch, nil). + Once() + + verifiedBatch := &state.VerifiedBatch{ + TxHash: common.HexToHash("0x20"), + } + + m.State. + On("GetVerifiedBatch", context.Background(), hex.DecodeBig(tc.Number).Uint64(), m.DbTx). + Return(verifiedBatch, nil). + Once() + + txs := []*types.Transaction{ + signTx(types.NewTransaction(1001, common.HexToAddress("0x1000"), big.NewInt(1000), 1001, big.NewInt(1002), []byte("1003")), s.Config.ChainID), + signTx(types.NewTransaction(1002, common.HexToAddress("0x1000"), big.NewInt(1000), 1001, big.NewInt(1002), []byte("1003")), s.Config.ChainID), + } + + batchTxs := make([]types.Transaction, 0, len(txs)) + + tc.ExpectedResult.Transactions = []rpcTransactionOrHash{} + + for i, tx := range txs { + blockNumber := big.NewInt(int64(i)) + blockHash := common.HexToHash(hex.EncodeUint64(uint64(i))) + receipt := types.NewReceipt([]byte{}, false, uint64(0)) + receipt.TxHash = tx.Hash() + receipt.TransactionIndex = uint(i) + receipt.BlockNumber = blockNumber + receipt.BlockHash = blockHash + m.State. + On("GetTransactionReceipt", context.Background(), tx.Hash(), m.DbTx). + Return(receipt, nil). + Once() + + from, _ := state.GetSender(*tx) + V, R, S := tx.RawSignatureValues() + + tc.ExpectedResult.Transactions = append(tc.ExpectedResult.Transactions, + rpcTransaction{ + Nonce: argUint64(tx.Nonce()), + GasPrice: argBig(*tx.GasPrice()), + Gas: argUint64(tx.Gas()), + To: tx.To(), + Value: argBig(*tx.Value()), + Input: tx.Data(), + Hash: tx.Hash(), + From: from, + BlockNumber: ptrArgUint64FromUint64(blockNumber.Uint64()), + BlockHash: ptrHash(receipt.BlockHash), + TxIndex: ptrArgUint64FromUint(receipt.TransactionIndex), + ChainID: argBig(*tx.ChainId()), + Type: argUint64(tx.Type()), + V: argBig(*V), + R: argBig(*R), + S: argBig(*S), + }, + ) + + batchTxs = append(batchTxs, *tx) + } + m.State. + On("GetTransactionsByBatchNumber", context.Background(), hex.DecodeBig(tc.Number).Uint64(), m.DbTx). + Return(batchTxs, nil). + Once() + }, + }, + { + Name: "get specific batch successfully without tx detail", + Number: "0x345", + WithTxDetail: false, + ExpectedResult: &rpcBatch{ + Number: 1, + Coinbase: common.HexToAddress("0x1"), + StateRoot: common.HexToHash("0x2"), + AccInputHash: common.HexToHash("0x3"), + GlobalExitRoot: common.HexToHash("0x4"), + Timestamp: 1, + SendSequencesTxHash: ptrHash(common.HexToHash("0x10")), + VerifyBatchTxHash: ptrHash(common.HexToHash("0x20")), + }, + ExpectedError: nil, + SetupMocks: func(s *mockedServer, m *mocks, tc *testCase) { + m.DbTx. + On("Commit", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + batch := &state.Batch{ + BatchNumber: 1, + Coinbase: common.HexToAddress("0x1"), + StateRoot: common.HexToHash("0x2"), + AccInputHash: common.HexToHash("0x3"), + GlobalExitRoot: common.HexToHash("0x4"), + Timestamp: time.Unix(1, 0), + } + + m.State. + On("GetBatchByNumber", context.Background(), hex.DecodeBig(tc.Number).Uint64(), m.DbTx). + Return(batch, nil). + Once() + + virtualBatch := &state.VirtualBatch{ + TxHash: common.HexToHash("0x10"), + } + + m.State. + On("GetVirtualBatch", context.Background(), hex.DecodeBig(tc.Number).Uint64(), m.DbTx). + Return(virtualBatch, nil). + Once() + + verifiedBatch := &state.VerifiedBatch{ + TxHash: common.HexToHash("0x20"), + } + + m.State. + On("GetVerifiedBatch", context.Background(), hex.DecodeBig(tc.Number).Uint64(), m.DbTx). + Return(verifiedBatch, nil). + Once() + + txs := []*types.Transaction{ + signTx(types.NewTransaction(1001, common.HexToAddress("0x1000"), big.NewInt(1000), 1001, big.NewInt(1002), []byte("1003")), s.Config.ChainID), + signTx(types.NewTransaction(1002, common.HexToAddress("0x1000"), big.NewInt(1000), 1001, big.NewInt(1002), []byte("1003")), s.Config.ChainID), + } + + batchTxs := make([]types.Transaction, 0, len(txs)) + + tc.ExpectedResult.Transactions = []rpcTransactionOrHash{} + + for i, tx := range txs { + blockNumber := big.NewInt(int64(i)) + blockHash := common.HexToHash(hex.EncodeUint64(uint64(i))) + receipt := types.NewReceipt([]byte{}, false, uint64(0)) + receipt.TxHash = tx.Hash() + receipt.TransactionIndex = uint(i) + receipt.BlockNumber = blockNumber + receipt.BlockHash = blockHash + m.State. + On("GetTransactionReceipt", context.Background(), tx.Hash(), m.DbTx). + Return(receipt, nil). + Once() + + from, _ := state.GetSender(*tx) + V, R, S := tx.RawSignatureValues() + + tc.ExpectedResult.Transactions = append(tc.ExpectedResult.Transactions, + rpcTransaction{ + Nonce: argUint64(tx.Nonce()), + GasPrice: argBig(*tx.GasPrice()), + Gas: argUint64(tx.Gas()), + To: tx.To(), + Value: argBig(*tx.Value()), + Input: tx.Data(), + Hash: tx.Hash(), + From: from, + BlockNumber: ptrArgUint64FromUint64(blockNumber.Uint64()), + BlockHash: ptrHash(receipt.BlockHash), + TxIndex: ptrArgUint64FromUint(receipt.TransactionIndex), + ChainID: argBig(*tx.ChainId()), + Type: argUint64(tx.Type()), + V: argBig(*V), + R: argBig(*R), + S: argBig(*S), + }, + ) + + batchTxs = append(batchTxs, *tx) + } + m.State. + On("GetTransactionsByBatchNumber", context.Background(), hex.DecodeBig(tc.Number).Uint64(), m.DbTx). + Return(batchTxs, nil). + Once() + }, + }, + { + Name: "get latest batch successfully", + Number: "latest", + WithTxDetail: true, + ExpectedResult: &rpcBatch{ + Number: 1, + Coinbase: common.HexToAddress("0x1"), + StateRoot: common.HexToHash("0x2"), + AccInputHash: common.HexToHash("0x3"), + GlobalExitRoot: common.HexToHash("0x4"), + Timestamp: 1, + SendSequencesTxHash: ptrHash(common.HexToHash("0x10")), + VerifyBatchTxHash: ptrHash(common.HexToHash("0x20")), + }, + ExpectedError: nil, + SetupMocks: func(s *mockedServer, m *mocks, tc *testCase) { + m.DbTx. + On("Commit", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("GetLastBatchNumber", context.Background(), m.DbTx). + Return(uint64(tc.ExpectedResult.Number), nil). + Once() + + batch := &state.Batch{ + BatchNumber: 1, + Coinbase: common.HexToAddress("0x1"), + StateRoot: common.HexToHash("0x2"), + AccInputHash: common.HexToHash("0x3"), + GlobalExitRoot: common.HexToHash("0x4"), + Timestamp: time.Unix(1, 0), + } + + m.State. + On("GetBatchByNumber", context.Background(), uint64(tc.ExpectedResult.Number), m.DbTx). + Return(batch, nil). + Once() + + virtualBatch := &state.VirtualBatch{ + TxHash: common.HexToHash("0x10"), + } + + m.State. + On("GetVirtualBatch", context.Background(), uint64(tc.ExpectedResult.Number), m.DbTx). + Return(virtualBatch, nil). + Once() + + verifiedBatch := &state.VerifiedBatch{ + TxHash: common.HexToHash("0x20"), + } + + m.State. + On("GetVerifiedBatch", context.Background(), uint64(tc.ExpectedResult.Number), m.DbTx). + Return(verifiedBatch, nil). + Once() + + txs := []*types.Transaction{ + signTx(types.NewTransaction(1001, common.HexToAddress("0x1000"), big.NewInt(1000), 1001, big.NewInt(1002), []byte("1003")), s.Config.ChainID), + signTx(types.NewTransaction(1002, common.HexToAddress("0x1000"), big.NewInt(1000), 1001, big.NewInt(1002), []byte("1003")), s.Config.ChainID), + } + + batchTxs := make([]types.Transaction, 0, len(txs)) + + tc.ExpectedResult.Transactions = []rpcTransactionOrHash{} + + for i, tx := range txs { + blockNumber := big.NewInt(int64(i)) + blockHash := common.HexToHash(hex.EncodeUint64(uint64(i))) + receipt := types.NewReceipt([]byte{}, false, uint64(0)) + receipt.TxHash = tx.Hash() + receipt.TransactionIndex = uint(i) + receipt.BlockNumber = blockNumber + receipt.BlockHash = blockHash + m.State. + On("GetTransactionReceipt", context.Background(), tx.Hash(), m.DbTx). + Return(receipt, nil). + Once() + + from, _ := state.GetSender(*tx) + V, R, S := tx.RawSignatureValues() + + tc.ExpectedResult.Transactions = append(tc.ExpectedResult.Transactions, + rpcTransaction{ + Nonce: argUint64(tx.Nonce()), + GasPrice: argBig(*tx.GasPrice()), + Gas: argUint64(tx.Gas()), + To: tx.To(), + Value: argBig(*tx.Value()), + Input: tx.Data(), + Hash: tx.Hash(), + From: from, + BlockNumber: ptrArgUint64FromUint64(blockNumber.Uint64()), + BlockHash: ptrHash(receipt.BlockHash), + TxIndex: ptrArgUint64FromUint(receipt.TransactionIndex), + ChainID: argBig(*tx.ChainId()), + Type: argUint64(tx.Type()), + V: argBig(*V), + R: argBig(*R), + S: argBig(*S), + }, + ) + + batchTxs = append(batchTxs, *tx) + } + m.State. + On("GetTransactionsByBatchNumber", context.Background(), uint64(tc.ExpectedResult.Number), m.DbTx). + Return(batchTxs, nil). + Once() + }, + }, + { + Name: "get latest batch fails to compute batch number", + Number: "latest", + ExpectedResult: nil, + ExpectedError: newRPCError(defaultErrorCode, "failed to get the last batch number from state"), + SetupMocks: func(s *mockedServer, m *mocks, tc *testCase) { + m.DbTx. + On("Rollback", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("GetLastBatchNumber", context.Background(), m.DbTx). + Return(uint64(0), errors.New("failed to get last batch number")). + Once() + }, + }, + { + Name: "get latest batch fails to load batch by number", + Number: "latest", + ExpectedResult: nil, + ExpectedError: newRPCError(defaultErrorCode, "couldn't load batch from state by number 1"), + SetupMocks: func(s *mockedServer, m *mocks, tc *testCase) { + m.DbTx. + On("Rollback", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + m.State. + On("GetLastBatchNumber", context.Background(), m.DbTx). + Return(uint64(1), nil). + Once() + + m.State. + On("GetBatchByNumber", context.Background(), uint64(1), m.DbTx). + Return(nil, errors.New("failed to load batch by number")). + Once() + }, + }, + } + + s, m, _ := newSequencerMockedServer(t) + defer s.Stop() + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + tc := testCase + testCase.SetupMocks(s, m, &tc) + + res, err := s.JSONRPCCall("zkevm_getBatchByNumber", tc.Number, tc.WithTxDetail) + require.NoError(t, err) + assert.Equal(t, float64(1), res.ID) + assert.Equal(t, "2.0", res.JSONRPC) + + if res.Result != nil { + var result interface{} + err = json.Unmarshal(res.Result, &result) + require.NoError(t, err) + + if result != nil || testCase.ExpectedResult != nil { + var batch map[string]interface{} + err = json.Unmarshal(res.Result, &batch) + require.NoError(t, err) + assert.Equal(t, tc.ExpectedResult.Number.Hex(), batch["number"].(string)) + assert.Equal(t, tc.ExpectedResult.Coinbase.String(), batch["coinbase"].(string)) + assert.Equal(t, tc.ExpectedResult.StateRoot.String(), batch["stateRoot"].(string)) + assert.Equal(t, tc.ExpectedResult.GlobalExitRoot.String(), batch["globalExitRoot"].(string)) + assert.Equal(t, tc.ExpectedResult.AccInputHash.String(), batch["accInputHash"].(string)) + assert.Equal(t, tc.ExpectedResult.Timestamp.Hex(), batch["timestamp"].(string)) + assert.Equal(t, tc.ExpectedResult.SendSequencesTxHash.String(), batch["sendSequencesTxHash"].(string)) + assert.Equal(t, tc.ExpectedResult.VerifyBatchTxHash.String(), batch["verifyBatchTxHash"].(string)) + batchTxs := batch["transactions"].([]interface{}) + for i, tx := range tc.ExpectedResult.Transactions { + switch batchTxOrHash := batchTxs[i].(type) { + case string: + assert.Equal(t, tx.getHash().String(), batchTxOrHash) + case map[string]interface{}: + tx := tx.(rpcTransaction) + assert.Equal(t, tx.Nonce.Hex(), batchTxOrHash["nonce"].(string)) + assert.Equal(t, tx.GasPrice.Hex(), batchTxOrHash["gasPrice"].(string)) + assert.Equal(t, tx.Gas.Hex(), batchTxOrHash["gas"].(string)) + assert.Equal(t, tx.To.String(), batchTxOrHash["to"].(string)) + assert.Equal(t, tx.Value.Hex(), batchTxOrHash["value"].(string)) + assert.Equal(t, tx.Input.Hex(), batchTxOrHash["input"].(string)) + assert.Equal(t, tx.V.Hex(), batchTxOrHash["v"].(string)) + assert.Equal(t, tx.R.Hex(), batchTxOrHash["r"].(string)) + assert.Equal(t, tx.S.Hex(), batchTxOrHash["s"].(string)) + assert.Equal(t, tx.Hash.String(), batchTxOrHash["hash"].(string)) + assert.Equal(t, strings.ToLower(tx.From.String()), strings.ToLower(batchTxOrHash["from"].(string))) + assert.Equal(t, tx.BlockHash.String(), batchTxOrHash["blockHash"].(string)) + assert.Equal(t, tx.BlockNumber.Hex(), batchTxOrHash["blockNumber"].(string)) + assert.Equal(t, tx.TxIndex.Hex(), batchTxOrHash["transactionIndex"].(string)) + assert.Equal(t, tx.ChainID.Hex(), batchTxOrHash["chainId"].(string)) + assert.Equal(t, tx.Type.Hex(), batchTxOrHash["type"].(string)) + } + } + } + } + + if res.Error != nil || testCase.ExpectedError != nil { + assert.Equal(t, testCase.ExpectedError.ErrorCode(), res.Error.Code) + assert.Equal(t, testCase.ExpectedError.Error(), res.Error.Message) + } + }) + } +} + +func ptrUint64(n uint64) *uint64 { + return &n +} + +func ptrArgUint64FromUint(n uint) *argUint64 { + tmp := argUint64(n) + return &tmp +} + +func ptrArgUint64FromUint64(n uint64) *argUint64 { + tmp := argUint64(n) + return &tmp +} + +func ptrHash(h common.Hash) *common.Hash { + return &h +} + +func signTx(tx *types.Transaction, chainID uint64) *types.Transaction { + privateKey, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(0).SetUint64(chainID)) + signedTx, _ := auth.Signer(auth.From, tx) + return signedTx +} diff --git a/jsonrpc/handler.go b/jsonrpc/handler.go index 159cefa265..71786f7da9 100644 --- a/jsonrpc/handler.go +++ b/jsonrpc/handler.go @@ -172,7 +172,7 @@ func (h *Handler) RemoveFilterByWsConn(wsConn *websocket.Conn) { log.Errorf("failed to get ETH endpoint interface") } - ethEndpoints := ethEndpointsInterface.(*Eth) + ethEndpoints := ethEndpointsInterface.(*EthEndpoints) if ethEndpoints == nil { log.Errorf("failed to get ETH endpoint instance") return diff --git a/jsonrpc/interfaces.go b/jsonrpc/interfaces.go index cfac7597ae..9f49ff2121 100644 --- a/jsonrpc/interfaces.go +++ b/jsonrpc/interfaces.go @@ -35,7 +35,7 @@ type stateInterface interface { GetCode(ctx context.Context, address common.Address, blockNumber uint64, dbTx pgx.Tx) ([]byte, error) GetL2BlockByHash(ctx context.Context, hash common.Hash, dbTx pgx.Tx) (*types.Block, error) GetL2BlockByNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*types.Block, error) - GetBatchNumberOfL2Block(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (uint64, error) + BatchNumberByL2BlockNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (uint64, error) GetL2BlockHashesSince(ctx context.Context, since time.Time, dbTx pgx.Tx) ([]common.Hash, error) GetL2BlockHeaderByNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*types.Header, error) GetL2BlockTransactionCountByHash(ctx context.Context, hash common.Hash, dbTx pgx.Tx) (uint64, error) @@ -52,10 +52,17 @@ type stateInterface interface { GetTransactionByL2BlockHashAndIndex(ctx context.Context, blockHash common.Hash, index uint64, dbTx pgx.Tx) (*types.Transaction, error) GetTransactionByL2BlockNumberAndIndex(ctx context.Context, blockNumber uint64, index uint64, dbTx pgx.Tx) (*types.Transaction, error) GetTransactionReceipt(ctx context.Context, transactionHash common.Hash, dbTx pgx.Tx) (*types.Receipt, error) - IsL2BlockConsolidated(ctx context.Context, blockNumber int, dbTx pgx.Tx) (bool, error) - IsL2BlockVirtualized(ctx context.Context, blockNumber int, dbTx pgx.Tx) (bool, error) + IsL2BlockConsolidated(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (bool, error) + IsL2BlockVirtualized(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (bool, error) ProcessUnsignedTransaction(ctx context.Context, tx *types.Transaction, senderAddress common.Address, l2BlockNumber *uint64, noZKEVMCounters bool, dbTx pgx.Tx) *runtime.ExecutionResult RegisterNewL2BlockEventHandler(h state.NewL2BlockEventHandler) + GetLastVirtualBatchNum(ctx context.Context, dbTx pgx.Tx) (uint64, error) + 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) + GetTransactionsByBatchNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (txs []types.Transaction, 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) } type storageInterface interface { diff --git a/jsonrpc/mock_state_test.go b/jsonrpc/mock_state_test.go index bce5b4b518..679959c7ca 100644 --- a/jsonrpc/mock_state_test.go +++ b/jsonrpc/mock_state_test.go @@ -26,6 +26,27 @@ type stateMock struct { mock.Mock } +// BatchNumberByL2BlockNumber provides a mock function with given fields: ctx, blockNumber, dbTx +func (_m *stateMock) BatchNumberByL2BlockNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (uint64, error) { + ret := _m.Called(ctx, blockNumber, dbTx) + + var r0 uint64 + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) uint64); ok { + r0 = rf(ctx, blockNumber, dbTx) + } else { + r0 = ret.Get(0).(uint64) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, uint64, pgx.Tx) error); ok { + r1 = rf(ctx, blockNumber, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // BeginStateTransaction provides a mock function with given fields: ctx func (_m *stateMock) BeginStateTransaction(ctx context.Context) (pgx.Tx, error) { ret := _m.Called(ctx) @@ -116,20 +137,22 @@ func (_m *stateMock) GetBalance(ctx context.Context, address common.Address, blo return r0, r1 } -// GetBatchNumberOfL2Block provides a mock function with given fields: ctx, blockNumber, dbTx -func (_m *stateMock) GetBatchNumberOfL2Block(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (uint64, error) { - ret := _m.Called(ctx, blockNumber, dbTx) +// GetBatchByNumber provides a mock function with given fields: ctx, batchNumber, dbTx +func (_m *stateMock) GetBatchByNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*state.Batch, error) { + ret := _m.Called(ctx, batchNumber, dbTx) - var r0 uint64 - if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) uint64); ok { - r0 = rf(ctx, blockNumber, dbTx) + var r0 *state.Batch + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) *state.Batch); ok { + r0 = rf(ctx, batchNumber, dbTx) } else { - r0 = ret.Get(0).(uint64) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*state.Batch) + } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, uint64, pgx.Tx) error); ok { - r1 = rf(ctx, blockNumber, dbTx) + r1 = rf(ctx, batchNumber, dbTx) } else { r1 = ret.Error(1) } @@ -294,6 +317,27 @@ func (_m *stateMock) GetL2BlockTransactionCountByNumber(ctx context.Context, blo return r0, r1 } +// GetLastBatchNumber provides a mock function with given fields: ctx, dbTx +func (_m *stateMock) GetLastBatchNumber(ctx context.Context, dbTx pgx.Tx) (uint64, error) { + ret := _m.Called(ctx, dbTx) + + var r0 uint64 + if rf, ok := ret.Get(0).(func(context.Context, pgx.Tx) uint64); ok { + r0 = rf(ctx, dbTx) + } else { + r0 = ret.Get(0).(uint64) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, pgx.Tx) error); ok { + r1 = rf(ctx, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetLastConsolidatedL2BlockNumber provides a mock function with given fields: ctx, dbTx func (_m *stateMock) GetLastConsolidatedL2BlockNumber(ctx context.Context, dbTx pgx.Tx) (uint64, error) { ret := _m.Called(ctx, dbTx) @@ -382,6 +426,50 @@ func (_m *stateMock) GetLastL2BlockNumber(ctx context.Context, dbTx pgx.Tx) (uin return r0, r1 } +// GetLastVerifiedBatch provides a mock function with given fields: ctx, dbTx +func (_m *stateMock) GetLastVerifiedBatch(ctx context.Context, dbTx pgx.Tx) (*state.VerifiedBatch, error) { + ret := _m.Called(ctx, dbTx) + + var r0 *state.VerifiedBatch + if rf, ok := ret.Get(0).(func(context.Context, pgx.Tx) *state.VerifiedBatch); ok { + r0 = rf(ctx, dbTx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*state.VerifiedBatch) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, pgx.Tx) error); ok { + r1 = rf(ctx, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetLastVirtualBatchNum provides a mock function with given fields: ctx, dbTx +func (_m *stateMock) GetLastVirtualBatchNum(ctx context.Context, dbTx pgx.Tx) (uint64, error) { + ret := _m.Called(ctx, dbTx) + + var r0 uint64 + if rf, ok := ret.Get(0).(func(context.Context, pgx.Tx) uint64); ok { + r0 = rf(ctx, dbTx) + } else { + r0 = ret.Get(0).(uint64) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, pgx.Tx) error); ok { + r1 = rf(ctx, dbTx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetLogs provides a mock function with given fields: ctx, fromBlock, toBlock, addresses, topics, blockHash, since, dbTx func (_m *stateMock) GetLogs(ctx context.Context, fromBlock uint64, toBlock uint64, addresses []common.Address, topics [][]common.Hash, blockHash *common.Hash, since *time.Time, dbTx pgx.Tx) ([]*types.Log, error) { ret := _m.Called(ctx, fromBlock, toBlock, addresses, topics, blockHash, since, dbTx) @@ -562,19 +650,88 @@ func (_m *stateMock) GetTransactionReceipt(ctx context.Context, transactionHash return r0, r1 } +// GetTransactionsByBatchNumber provides a mock function with given fields: ctx, batchNumber, dbTx +func (_m *stateMock) GetTransactionsByBatchNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) ([]types.Transaction, error) { + ret := _m.Called(ctx, batchNumber, dbTx) + + var r0 []types.Transaction + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) []types.Transaction); ok { + r0 = rf(ctx, batchNumber, dbTx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.Transaction) + } + } + + var r1 error + 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 +} + +// GetVerifiedBatch provides a mock function with given fields: ctx, batchNumber, dbTx +func (_m *stateMock) GetVerifiedBatch(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*state.VerifiedBatch, error) { + ret := _m.Called(ctx, batchNumber, dbTx) + + var r0 *state.VerifiedBatch + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) *state.VerifiedBatch); ok { + r0 = rf(ctx, batchNumber, dbTx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*state.VerifiedBatch) + } + } + + var r1 error + 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 +} + +// GetVirtualBatch provides a mock function with given fields: ctx, batchNumber, dbTx +func (_m *stateMock) GetVirtualBatch(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*state.VirtualBatch, error) { + ret := _m.Called(ctx, batchNumber, dbTx) + + var r0 *state.VirtualBatch + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) *state.VirtualBatch); ok { + r0 = rf(ctx, batchNumber, dbTx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*state.VirtualBatch) + } + } + + var r1 error + 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 +} + // IsL2BlockConsolidated provides a mock function with given fields: ctx, blockNumber, dbTx -func (_m *stateMock) IsL2BlockConsolidated(ctx context.Context, blockNumber int, dbTx pgx.Tx) (bool, error) { +func (_m *stateMock) IsL2BlockConsolidated(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (bool, error) { ret := _m.Called(ctx, blockNumber, dbTx) var r0 bool - if rf, ok := ret.Get(0).(func(context.Context, int, pgx.Tx) bool); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) bool); ok { r0 = rf(ctx, blockNumber, dbTx) } else { r0 = ret.Get(0).(bool) } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, int, pgx.Tx) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, uint64, pgx.Tx) error); ok { r1 = rf(ctx, blockNumber, dbTx) } else { r1 = ret.Error(1) @@ -584,18 +741,18 @@ func (_m *stateMock) IsL2BlockConsolidated(ctx context.Context, blockNumber int, } // IsL2BlockVirtualized provides a mock function with given fields: ctx, blockNumber, dbTx -func (_m *stateMock) IsL2BlockVirtualized(ctx context.Context, blockNumber int, dbTx pgx.Tx) (bool, error) { +func (_m *stateMock) IsL2BlockVirtualized(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (bool, error) { ret := _m.Called(ctx, blockNumber, dbTx) var r0 bool - if rf, ok := ret.Get(0).(func(context.Context, int, pgx.Tx) bool); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint64, pgx.Tx) bool); ok { r0 = rf(ctx, blockNumber, dbTx) } else { r0 = ret.Get(0).(bool) } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, int, pgx.Tx) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, uint64, pgx.Tx) error); ok { r1 = rf(ctx, blockNumber, dbTx) } else { r1 = ret.Error(1) diff --git a/jsonrpc/server.go b/jsonrpc/server.go index bff13c1f45..eb819a2cf2 100644 --- a/jsonrpc/server.go +++ b/jsonrpc/server.go @@ -55,32 +55,32 @@ func NewServer( handler := newJSONRpcHandler() if _, ok := apis[APIEth]; ok { - ethEndpoints := newEth(cfg, p, s, storage) + ethEndpoints := newEthEndpoints(cfg, p, s, storage) handler.registerService(APIEth, ethEndpoints) } if _, ok := apis[APINet]; ok { - netEndpoints := &Net{cfg: cfg} + netEndpoints := &NetEndpoints{cfg: cfg} handler.registerService(APINet, netEndpoints) } if _, ok := apis[APIZKEVM]; ok { - hezEndpoints := &ZKEVM{state: s, config: cfg} - handler.registerService(APIZKEVM, hezEndpoints) + zkEVMEndpoints := &ZKEVMEndpoints{state: s, config: cfg} + handler.registerService(APIZKEVM, zkEVMEndpoints) } if _, ok := apis[APITxPool]; ok { - txPoolEndpoints := &TxPool{} + txPoolEndpoints := &TxPoolEndpoints{} handler.registerService(APITxPool, txPoolEndpoints) } if _, ok := apis[APIDebug]; ok { - debugEndpoints := &Debug{state: s} + debugEndpoints := &DebugEndpoints{state: s} handler.registerService(APIDebug, debugEndpoints) } if _, ok := apis[APIWeb3]; ok { - web3Endpoints := &Web3{} + web3Endpoints := &Web3Endpoints{} handler.registerService(APIWeb3, web3Endpoints) } diff --git a/jsonrpc/server_test.go b/jsonrpc/server_test.go index 3b4ec3f529..d957a472f5 100644 --- a/jsonrpc/server_test.go +++ b/jsonrpc/server_test.go @@ -90,7 +90,7 @@ func newMockedServer(t *testing.T, cfg Config) (*mockedServer, *mocks, *ethclien func getDefaultConfig() Config { cfg := Config{ Host: host, - Port: 8123, + Port: 9123, MaxRequestsPerIPAndSecond: maxRequestsPerIPAndSecond, DefaultSenderAddress: "0x1111111111111111111111111111111111111111", MaxCumulativeGasUsed: 300000, @@ -106,7 +106,7 @@ func newSequencerMockedServer(t *testing.T) (*mockedServer, *mocks, *ethclient.C func newNonSequencerMockedServer(t *testing.T, sequencerNodeURI string) (*mockedServer, *mocks, *ethclient.Client) { cfg := getDefaultConfig() - cfg.Port = 8124 + cfg.Port = 9124 cfg.SequencerNodeURI = sequencerNodeURI return newMockedServer(t, cfg) } diff --git a/jsonrpc/types.go b/jsonrpc/types.go index dfbdc06f73..f1b511dcf3 100644 --- a/jsonrpc/types.go +++ b/jsonrpc/types.go @@ -18,7 +18,7 @@ type argUint64 uint64 // MarshalText marshals into text func (b argUint64) MarshalText() ([]byte, error) { - buf := make([]byte, 2, encoding.Base10) //nolint:gomnd + buf := make([]byte, 2) //nolint:gomnd copy(buf, `0x`) buf = strconv.AppendUint(buf, uint64(b), hex.Base) return buf, nil @@ -35,6 +35,12 @@ func (b *argUint64) UnmarshalText(input []byte) error { return nil } +// Hex() returns a hexadecimal representation +func (b argUint64) Hex() string { + bb, _ := b.MarshalText() + return string(bb) +} + type argBytes []byte // MarshalText marshals into text @@ -54,6 +60,12 @@ func (b *argBytes) UnmarshalText(input []byte) error { return nil } +// Hex() returns a hexadecimal representation +func (b argBytes) Hex() string { + bb, _ := b.MarshalText() + return string(bb) +} + func argBytesPtr(b []byte) *argBytes { bb := argBytes(b) @@ -81,6 +93,12 @@ func (a argBig) MarshalText() ([]byte, error) { return []byte("0x" + b.Text(hex.Base)), nil } +// Hex() returns a hexadecimal representation +func (b argBig) Hex() string { + bb, _ := b.MarshalText() + return string(bb) +} + func decodeToHex(b []byte) ([]byte, error) { str := string(b) str = strings.TrimPrefix(str, "0x") @@ -221,7 +239,7 @@ func l2BlockToRPCBlock(b *types.Block, fullTx bool) *rpcBlock { if fullTx { blockHash := b.Hash() txIndex := uint64(idx) - tx := toRPCTransaction(txn, b.Number(), &blockHash, &txIndex) + tx := toRPCTransaction(*txn, b.Number(), &blockHash, &txIndex) res.Transactions = append( res.Transactions, tx, @@ -241,6 +259,55 @@ func l2BlockToRPCBlock(b *types.Block, fullTx bool) *rpcBlock { return res } +type rpcBatch struct { + Number argUint64 `json:"number"` + Coinbase common.Address `json:"coinbase"` + StateRoot common.Hash `json:"stateRoot"` + GlobalExitRoot common.Hash `json:"globalExitRoot"` + AccInputHash common.Hash `json:"accInputHash"` + Timestamp argUint64 `json:"timestamp"` + SendSequencesTxHash *common.Hash `json:"sendSequencesTxHash"` + VerifyBatchTxHash *common.Hash `json:"verifyBatchTxHash"` + Transactions []rpcTransactionOrHash `json:"transactions"` +} + +func l2BatchToRPCBatch(batch *state.Batch, virtualBatch *state.VirtualBatch, verifiedBatch *state.VerifiedBatch, receipts []types.Receipt, fullTx bool) *rpcBatch { + res := &rpcBatch{ + Number: argUint64(batch.BatchNumber), + GlobalExitRoot: batch.GlobalExitRoot, + AccInputHash: batch.AccInputHash, + Timestamp: argUint64(batch.Timestamp.Unix()), + StateRoot: batch.StateRoot, + Coinbase: batch.Coinbase, + } + + if virtualBatch != nil { + res.SendSequencesTxHash = &virtualBatch.TxHash + } + + if verifiedBatch != nil { + res.VerifyBatchTxHash = &verifiedBatch.TxHash + } + + receiptsMap := make(map[common.Hash]types.Receipt, len(receipts)) + for _, receipt := range receipts { + receiptsMap[receipt.TxHash] = receipt + } + + for _, tx := range batch.Transactions { + if fullTx { + receipt := receiptsMap[tx.Hash()] + txIndex := uint64(receipt.TransactionIndex) + rpcTx := toRPCTransaction(tx, receipt.BlockNumber, &receipt.BlockHash, &txIndex) + res.Transactions = append(res.Transactions, rpcTx) + } else { + res.Transactions = append(res.Transactions, transactionHash(tx.Hash())) + } + } + + return res +} + // For union type of transaction and types.Hash type rpcTransactionOrHash interface { getHash() common.Hash @@ -277,14 +344,14 @@ func (h transactionHash) MarshalText() ([]byte, error) { } func toRPCTransaction( - t *types.Transaction, + t types.Transaction, blockNumber *big.Int, blockHash *common.Hash, txIndex *uint64, ) *rpcTransaction { v, r, s := t.RawSignatureValues() - from, _ := state.GetSender(*t) + from, _ := state.GetSender(t) res := &rpcTransaction{ Nonce: argUint64(t.Nonce()), diff --git a/jsonrpc/zkevm.go b/jsonrpc/zkevm.go deleted file mode 100644 index a8380e3002..0000000000 --- a/jsonrpc/zkevm.go +++ /dev/null @@ -1,78 +0,0 @@ -package jsonrpc - -import ( - "context" - - "github.com/0xPolygonHermez/zkevm-node/hex" - "github.com/0xPolygonHermez/zkevm-node/log" - "github.com/jackc/pgx/v4" -) - -// ZKEVM contains implementations for the "zkevm" RPC endpoints -type ZKEVM struct { - config Config - state stateInterface - txMan dbTxManager -} - -// ConsolidatedBlockNumber returns current block number for consolidated blocks -func (h *ZKEVM) ConsolidatedBlockNumber() (interface{}, rpcError) { - return h.txMan.NewDbTxScope(h.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { - lastBlockNumber, err := h.state.GetLastConsolidatedL2BlockNumber(ctx, dbTx) - if err != nil { - const errorMessage = "failed to get last consolidated block number from state" - log.Errorf("%v:%v", errorMessage, err) - return nil, newRPCError(defaultErrorCode, errorMessage) - } - - return hex.EncodeUint64(lastBlockNumber), nil - }) -} - -// IsL2BlockConsolidated returns the consolidation status of a provided block number -func (h *ZKEVM) IsL2BlockConsolidated(blockNumber int) (interface{}, rpcError) { - return h.txMan.NewDbTxScope(h.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { - IsL2BlockConsolidated, err := h.state.IsL2BlockConsolidated(ctx, blockNumber, dbTx) - if err != nil { - const errorMessage = "failed to check if the block is consolidated" - log.Errorf("%v:%v", errorMessage, err) - return nil, newRPCError(defaultErrorCode, errorMessage) - } - - return IsL2BlockConsolidated, nil - }) -} - -// IsL2BlockVirtualized returns the virtualisation status of a provided block number -func (h *ZKEVM) IsL2BlockVirtualized(blockNumber int) (interface{}, rpcError) { - return h.txMan.NewDbTxScope(h.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { - IsL2BlockVirtualized, err := h.state.IsL2BlockVirtualized(ctx, blockNumber, dbTx) - if err != nil { - const errorMessage = "failed to check if the block is virtualized" - log.Errorf("%v:%v", errorMessage, err) - return nil, newRPCError(defaultErrorCode, errorMessage) - } - - return IsL2BlockVirtualized, nil - }) -} - -// BatchNumberOfL2Block returns the batch number from which the passed block number is created -func (h *ZKEVM) BatchNumberOfL2Block(blockNumber uint64) (interface{}, rpcError) { - return h.txMan.NewDbTxScope(h.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) { - batchNum, err := h.state.GetBatchNumberOfL2Block(ctx, blockNumber, dbTx) - if err != nil { - const errorMessage = "failed to get batch number of l2 batchNum" - log.Errorf("%v:%v", errorMessage, err) - return nil, newRPCError(defaultErrorCode, errorMessage) - } - - return batchNum, nil - }) -} - -// GetBroadcastURI returns the IP:PORT of the broadcast service provided -// by the Trusted Sequencer JSON RPC server -func (h *ZKEVM) GetBroadcastURI() (interface{}, rpcError) { - return h.config.BroadcastURI, nil -} diff --git a/jsonrpc/zkevm_test.go b/jsonrpc/zkevm_test.go deleted file mode 100644 index 6713a2b44e..0000000000 --- a/jsonrpc/zkevm_test.go +++ /dev/null @@ -1,329 +0,0 @@ -package jsonrpc - -import ( - "context" - "encoding/json" - "errors" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestConsolidatedBlockNumber(t *testing.T) { - s, m, _ := newSequencerMockedServer(t) - defer s.Stop() - - type testCase struct { - Name string - ExpectedResult *uint64 - ExpectedError rpcError - SetupMocks func(m *mocks) - } - - testCases := []testCase{ - { - Name: "Get consolidated block number successfully", - ExpectedResult: ptrUint64(10), - SetupMocks: func(m *mocks) { - m.DbTx. - On("Commit", context.Background()). - Return(nil). - Once() - - m.State. - On("BeginStateTransaction", context.Background()). - Return(m.DbTx, nil). - Once() - - m.State. - On("GetLastConsolidatedL2BlockNumber", context.Background(), m.DbTx). - Return(uint64(10), nil). - Once() - }, - }, - { - Name: "failed to get consolidated block number", - ExpectedResult: nil, - ExpectedError: newRPCError(defaultErrorCode, "failed to get last consolidated block number from state"), - SetupMocks: func(m *mocks) { - m.DbTx. - On("Rollback", context.Background()). - Return(nil). - Once() - - m.State. - On("BeginStateTransaction", context.Background()). - Return(m.DbTx, nil). - Once() - - m.State. - On("GetLastConsolidatedL2BlockNumber", context.Background(), m.DbTx). - Return(uint64(0), errors.New("failed to get last consolidated block number")). - Once() - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.Name, func(t *testing.T) { - tc := testCase - tc.SetupMocks(m) - - res, err := s.JSONRPCCall("zkevm_consolidatedBlockNumber") - require.NoError(t, err) - - if res.Result != nil { - var result argUint64 - err = json.Unmarshal(res.Result, &result) - require.NoError(t, err) - assert.Equal(t, *tc.ExpectedResult, uint64(result)) - } - - if res.Error != nil || tc.ExpectedError != nil { - assert.Equal(t, tc.ExpectedError.ErrorCode(), res.Error.Code) - assert.Equal(t, tc.ExpectedError.Error(), res.Error.Message) - } - }) - } -} - -func TestIsL2BlockConsolidated(t *testing.T) { - s, m, _ := newSequencerMockedServer(t) - defer s.Stop() - - type testCase struct { - Name string - ExpectedResult bool - ExpectedError rpcError - SetupMocks func(m *mocks) - } - - testCases := []testCase{ - { - Name: "Query status of block number successfully", - ExpectedResult: true, - SetupMocks: func(m *mocks) { - m.DbTx. - On("Commit", context.Background()). - Return(nil). - Once() - - m.State. - On("BeginStateTransaction", context.Background()). - Return(m.DbTx, nil). - Once() - - m.State. - On("IsL2BlockConsolidated", context.Background(), 1, m.DbTx). - Return(true, nil). - Once() - }, - }, - { - Name: "Failed to query the consolidation status", - ExpectedResult: false, - ExpectedError: newRPCError(defaultErrorCode, "failed to check if the block is consolidated"), - SetupMocks: func(m *mocks) { - m.DbTx. - On("Rollback", context.Background()). - Return(nil). - Once() - - m.State. - On("BeginStateTransaction", context.Background()). - Return(m.DbTx, nil). - Once() - - m.State. - On("IsL2BlockConsolidated", context.Background(), 1, m.DbTx). - Return(true, errors.New("failed to check if the block is consolidated")). - Once() - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.Name, func(t *testing.T) { - tc := testCase - tc.SetupMocks(m) - - res, err := s.JSONRPCCall("zkevm_isL2BlockConsolidated", 1) - require.NoError(t, err) - - if res.Result != nil { - var result bool - err = json.Unmarshal(res.Result, &result) - require.NoError(t, err) - assert.Equal(t, tc.ExpectedResult, result) - } - - if res.Error != nil || tc.ExpectedError != nil { - assert.Equal(t, tc.ExpectedError.ErrorCode(), res.Error.Code) - assert.Equal(t, tc.ExpectedError.Error(), res.Error.Message) - } - }) - } -} - -func TestIsL2BlockVirtualized(t *testing.T) { - s, m, _ := newSequencerMockedServer(t) - defer s.Stop() - - type testCase struct { - Name string - ExpectedResult bool - ExpectedError rpcError - SetupMocks func(m *mocks) - } - - testCases := []testCase{ - { - Name: "Query status of block number successfully", - ExpectedResult: true, - SetupMocks: func(m *mocks) { - m.DbTx. - On("Commit", context.Background()). - Return(nil). - Once() - - m.State. - On("BeginStateTransaction", context.Background()). - Return(m.DbTx, nil). - Once() - - m.State. - On("IsL2BlockVirtualized", context.Background(), 1, m.DbTx). - Return(true, nil). - Once() - }, - }, - { - Name: "Failed to query the virtualization status", - ExpectedResult: false, - ExpectedError: newRPCError(defaultErrorCode, "failed to check if the block is virtualized"), - SetupMocks: func(m *mocks) { - m.DbTx. - On("Rollback", context.Background()). - Return(nil). - Once() - - m.State. - On("BeginStateTransaction", context.Background()). - Return(m.DbTx, nil). - Once() - - m.State. - On("IsL2BlockVirtualized", context.Background(), 1, m.DbTx). - Return(true, errors.New("failed to check if the block is virtualized")). - Once() - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.Name, func(t *testing.T) { - tc := testCase - tc.SetupMocks(m) - - res, err := s.JSONRPCCall("zkevm_isL2BlockVirtualized", 1) - require.NoError(t, err) - - if res.Result != nil { - var result bool - err = json.Unmarshal(res.Result, &result) - require.NoError(t, err) - assert.Equal(t, tc.ExpectedResult, result) - } - - if res.Error != nil || tc.ExpectedError != nil { - assert.Equal(t, tc.ExpectedError.ErrorCode(), res.Error.Code) - assert.Equal(t, tc.ExpectedError.Error(), res.Error.Message) - } - }) - } -} - -func TestBatchNumberOfL2Block(t *testing.T) { - s, m, _ := newSequencerMockedServer(t) - defer s.Stop() - blockNumber := uint64(1) - batchNumber := uint64(1) - - type testCase struct { - Name string - ExpectedResult uint64 - ExpectedError rpcError - SetupMocks func(m *mocks) - } - - testCases := []testCase{ - { - Name: "Query status of batch number of l2 block by its number successfully", - ExpectedResult: batchNumber, - SetupMocks: func(m *mocks) { - m.DbTx. - On("Commit", context.Background()). - Return(nil). - Once() - - m.State. - On("BeginStateTransaction", context.Background()). - Return(m.DbTx, nil). - Once() - - m.State. - On("GetBatchNumberOfL2Block", context.Background(), blockNumber, m.DbTx). - Return(batchNumber, nil). - Once() - }, - }, - { - Name: "Failed to query the consolidation status", - ExpectedResult: uint64(0), - ExpectedError: newRPCError(defaultErrorCode, "failed to get batch number of l2 batchNum"), - SetupMocks: func(m *mocks) { - m.DbTx. - On("Rollback", context.Background()). - Return(nil). - Once() - - m.State. - On("BeginStateTransaction", context.Background()). - Return(m.DbTx, nil). - Once() - - m.State. - On("GetBatchNumberOfL2Block", context.Background(), blockNumber, m.DbTx). - Return(uint64(0), errors.New("failed to get batch number of l2 batchNum")). - Once() - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.Name, func(t *testing.T) { - tc := testCase - tc.SetupMocks(m) - - res, err := s.JSONRPCCall("zkevm_batchNumberOfL2Block", blockNumber) - require.NoError(t, err) - - if res.Result != nil { - var result uint64 - err = json.Unmarshal(res.Result, &result) - require.NoError(t, err) - assert.Equal(t, tc.ExpectedResult, result) - } - - if res.Error != nil || tc.ExpectedError != nil { - assert.Equal(t, tc.ExpectedError.ErrorCode(), res.Error.Code) - assert.Equal(t, tc.ExpectedError.Error(), res.Error.Message) - } - }) - } -} - -func ptrUint64(n uint64) *uint64 { - return &n -} diff --git a/state/pgstatestorage.go b/state/pgstatestorage.go index 4440ce6c8e..b70175b681 100644 --- a/state/pgstatestorage.go +++ b/state/pgstatestorage.go @@ -360,8 +360,13 @@ func (p *PostgresStorage) GetVerifiedBatch(ctx context.Context, batchNumber uint agg string sr string ) + + const getVerifiedBatchSQL = ` + SELECT block_num, batch_num, tx_hash, aggregator, state_root, is_trusted + FROM state.verified_batch + WHERE batch_num = $1` + e := p.getExecQuerier(dbTx) - const getVerifiedBatchSQL = "SELECT block_num, batch_num, tx_hash, aggregator, state_root, is_trusted FROM state.verified_batch WHERE batch_num = $1" err := e.QueryRow(ctx, getVerifiedBatchSQL, batchNumber).Scan(&verifiedBatch.BlockNumber, &verifiedBatch.BatchNumber, &txHash, &agg, &sr, &verifiedBatch.IsTrusted) if errors.Is(err, pgx.ErrNoRows) { return nil, ErrNotFound @@ -542,6 +547,7 @@ func (p *PostgresStorage) GetBatchByNumber(ctx context.Context, batchNumber uint } else if err != nil { return nil, err } + return &batch, nil } @@ -586,37 +592,6 @@ func (p *PostgresStorage) GetBatchByL2BlockNumber(ctx context.Context, l2BlockNu return &batch, nil } -// GetVirtualBatchByNumber gets batch from batch table that exists on virtual batch -func (p *PostgresStorage) GetVirtualBatchByNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*Batch, error) { - const query = ` - SELECT - batch_num, - global_exit_root, - local_exit_root, - acc_input_hash, - state_root, - timestamp, - coinbase, - raw_txs_data, - forced_batch_num - FROM - state.batch - WHERE - batch_num = $1 AND - EXISTS (SELECT batch_num FROM state.virtual_batch WHERE batch_num = $1) - ` - e := p.getExecQuerier(dbTx) - row := e.QueryRow(ctx, query, batchNumber) - batch, err := scanBatch(row) - - if errors.Is(err, pgx.ErrNoRows) { - return nil, ErrNotFound - } else if err != nil { - return nil, err - } - return &batch, nil -} - // IsBatchVirtualized checks if batch is virtualized func (p *PostgresStorage) IsBatchVirtualized(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (bool, error) { const query = `SELECT EXISTS (SELECT 1 FROM state.virtual_batch WHERE batch_num = $1)` @@ -818,6 +793,31 @@ func (p *PostgresStorage) AddVirtualBatch(ctx context.Context, virtualBatch *Vir return err } +// GetVirtualBatch get an L1 virtualBatch. +func (p *PostgresStorage) GetVirtualBatch(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (*VirtualBatch, error) { + var ( + virtualBatch VirtualBatch + txHash string + coinbase string + ) + + const getVirtualBatchSQL = ` + SELECT block_num, batch_num, tx_hash, coinbase + FROM state.virtual_batch + WHERE batch_num = $1` + + e := p.getExecQuerier(dbTx) + err := e.QueryRow(ctx, getVirtualBatchSQL, batchNumber).Scan(&virtualBatch.BlockNumber, &virtualBatch.BatchNumber, &txHash, &coinbase) + if errors.Is(err, pgx.ErrNoRows) { + return nil, ErrNotFound + } else if err != nil { + return nil, err + } + virtualBatch.Coinbase = common.HexToAddress(coinbase) + virtualBatch.TxHash = common.HexToHash(txHash) + return &virtualBatch, nil +} + func (p *PostgresStorage) storeGenesisBatch(ctx context.Context, batch Batch, dbTx pgx.Tx) error { if batch.BatchNumber != 0 { return fmt.Errorf("%w. Got %d, should be 0", ErrUnexpectedBatch, batch.BatchNumber) @@ -951,8 +951,8 @@ func (p *PostgresStorage) GetNextForcedBatches(ctx context.Context, nextForcedBa return batches, nil } -// GetBatchNumberOfL2Block gets a batch number for l2 block by its number -func (p *PostgresStorage) GetBatchNumberOfL2Block(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (uint64, error) { +// BatchNumberByL2BlockNumber gets a batch number by a l2 block number +func (p *PostgresStorage) BatchNumberByL2BlockNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (uint64, error) { getBatchNumByBlockNum := "SELECT batch_num FROM state.l2block WHERE block_num = $1" batchNumber := uint64(0) q := p.getExecQuerier(dbTx) @@ -1491,6 +1491,44 @@ func (p *PostgresStorage) GetTxsByBlockNumber(ctx context.Context, blockNumber u return txs, nil } +// GetTxsByBatchNumber returns all the txs in a given batch +func (p *PostgresStorage) GetTxsByBatchNumber(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) ([]*types.Transaction, error) { + q := p.getExecQuerier(dbTx) + + const getTxsByBatchNumSQL = ` + SELECT encoded + FROM state.transaction t + INNER JOIN state.l2block b + ON b.block_num = t.l2_block_num + WHERE b.batch_num = $1` + + rows, err := q.Query(ctx, getTxsByBatchNumSQL, batchNumber) + + if errors.Is(err, pgx.ErrNoRows) { + return nil, ErrNotFound + } else if err != nil { + return nil, err + } + + defer rows.Close() + + txs := make([]*types.Transaction, 0, len(rows.RawValues())) + var encoded string + for rows.Next() { + if err = rows.Scan(&encoded); err != nil { + return nil, err + } + + tx, err := DecodeTx(encoded) + if err != nil { + return nil, err + } + txs = append(txs, tx) + } + + return txs, nil +} + // GetL2BlockHeaderByHash gets the block header by block number func (p *PostgresStorage) GetL2BlockHeaderByHash(ctx context.Context, hash common.Hash, dbTx pgx.Tx) (*types.Header, error) { header := &types.Header{} @@ -1546,7 +1584,7 @@ func (p *PostgresStorage) GetL2BlockHashesSince(ctx context.Context, since time. } // IsL2BlockConsolidated checks if the block ID is consolidated -func (p *PostgresStorage) IsL2BlockConsolidated(ctx context.Context, blockNumber int, dbTx pgx.Tx) (bool, error) { +func (p *PostgresStorage) IsL2BlockConsolidated(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (bool, error) { q := p.getExecQuerier(dbTx) rows, err := q.Query(ctx, isL2BlockConsolidated, blockNumber) if err != nil { @@ -1563,7 +1601,7 @@ func (p *PostgresStorage) IsL2BlockConsolidated(ctx context.Context, blockNumber } // IsL2BlockVirtualized checks if the block ID is virtualized -func (p *PostgresStorage) IsL2BlockVirtualized(ctx context.Context, blockNumber int, dbTx pgx.Tx) (bool, error) { +func (p *PostgresStorage) IsL2BlockVirtualized(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (bool, error) { q := p.getExecQuerier(dbTx) rows, err := q.Query(ctx, isL2BlockVirtualized, blockNumber) if err != nil { diff --git a/state/pgstatestorage_test.go b/state/pgstatestorage_test.go index cfcb7e734d..687879791d 100644 --- a/state/pgstatestorage_test.go +++ b/state/pgstatestorage_test.go @@ -82,7 +82,7 @@ func TestGetBatchByL2BlockNumber(t *testing.T) { err = pgStateStorage.AddL2Block(ctx, batchNumber, l2Block, receipts, dbTx) require.NoError(t, err) - result, err := pgStateStorage.GetBatchNumberOfL2Block(ctx, l2Block.Number().Uint64(), dbTx) + result, err := pgStateStorage.BatchNumberByL2BlockNumber(ctx, l2Block.Number().Uint64(), dbTx) require.NoError(t, err) assert.Equal(t, batchNumber, result) require.NoError(t, dbTx.Commit(ctx)) diff --git a/test/e2e/jsonrpc_test.go b/test/e2e/jsonrpc_test.go index a807471908..3bf0427f5f 100644 --- a/test/e2e/jsonrpc_test.go +++ b/test/e2e/jsonrpc_test.go @@ -699,7 +699,7 @@ func Test_WebSocketsRequest(t *testing.T) { require.NoError(t, err) str := strings.TrimPrefix(result, "0x") - balance := hex.DecodeHexToBig(str) + balance := hex.DecodeBig(str) require.NoError(t, err) assert.Equal(t, expectedBalance.String(), balance.String()) diff --git a/test/operations/wait.go b/test/operations/wait.go index d2229868c4..c8ba9397c5 100644 --- a/test/operations/wait.go +++ b/test/operations/wait.go @@ -13,6 +13,7 @@ import ( "os/signal" "time" + "github.com/0xPolygonHermez/zkevm-node/hex" "github.com/0xPolygonHermez/zkevm-node/jsonrpc" "github.com/0xPolygonHermez/zkevm-node/log" "github.com/ethereum/go-ethereum" @@ -235,7 +236,7 @@ func grpcHealthyCondition(address string) (bool, error) { // l2BlockConsolidationCondition func l2BlockConsolidationCondition(l2Block *big.Int) (bool, error) { l2NetworkURL := "http://localhost:8123" - response, err := jsonrpc.JSONRPCCall(l2NetworkURL, "zkevm_isL2BlockConsolidated", l2Block.Uint64()) + response, err := jsonrpc.JSONRPCCall(l2NetworkURL, "zkevm_isBlockConsolidated", hex.EncodeBig(l2Block)) if err != nil { return false, err } @@ -253,7 +254,7 @@ func l2BlockConsolidationCondition(l2Block *big.Int) (bool, error) { // l2BlockVirtualizationCondition func l2BlockVirtualizationCondition(l2Block *big.Int) (bool, error) { l2NetworkURL := "http://localhost:8123" - response, err := jsonrpc.JSONRPCCall(l2NetworkURL, "zkevm_isL2BlockVirtualized", l2Block.Uint64()) + response, err := jsonrpc.JSONRPCCall(l2NetworkURL, "zkevm_isBlockVirtualized", hex.EncodeBig(l2Block)) if err != nil { return false, err }