diff --git a/docs/json-rpc-endpoints.md b/docs/json-rpc-endpoints.md index bf772925e1..eb5ee5703f 100644 --- a/docs/json-rpc-endpoints.md +++ b/docs/json-rpc-endpoints.md @@ -70,3 +70,4 @@ If the endpoint is not in the list below, it means this specific endpoint is not - `zkevm_isBlockVirtualized` - `zkevm_verifiedBatchNumber` - `zkevm_virtualBatchNumber` +- `zkevm_getExitRootsByGER` diff --git a/jsonrpc/client/zkevm.go b/jsonrpc/client/zkevm.go index 8b74859d0d..12b3cad0b8 100644 --- a/jsonrpc/client/zkevm.go +++ b/jsonrpc/client/zkevm.go @@ -7,6 +7,7 @@ import ( "github.com/0xPolygonHermez/zkevm-node/hex" "github.com/0xPolygonHermez/zkevm-node/jsonrpc/types" + "github.com/ethereum/go-ethereum/common" ) // BatchNumber returns the latest batch number @@ -56,3 +57,23 @@ func (c *Client) BatchByNumber(ctx context.Context, number *big.Int) (*types.Bat return result, nil } + +// ExitRootsByGER returns the exit roots accordingly to the provided Global Exit Root +func (c *Client) ExitRootsByGER(ctx context.Context, globalExitRoot common.Hash) (*types.ExitRoots, error) { + response, err := JSONRPCCall(c.url, "zkevm_getExitRootsByGER", globalExitRoot.String()) + if err != nil { + return nil, err + } + + if response.Error != nil { + return nil, response.Error.RPCError() + } + + var result *types.ExitRoots + err = json.Unmarshal(response.Result, &result) + if err != nil { + return nil, err + } + + return result, nil +} diff --git a/jsonrpc/endpoints_eth_test.go b/jsonrpc/endpoints_eth_test.go index ba95c79f01..aabe9855e0 100644 --- a/jsonrpc/endpoints_eth_test.go +++ b/jsonrpc/endpoints_eth_test.go @@ -1182,8 +1182,7 @@ func TestGetL2BlockByNumber(t *testing.T) { l2Header := state.NewL2Header(header) l2Header.GlobalExitRoot = common.HexToHash("0x16") - l2Header.LocalExitRoot = common.HexToHash("0x17") - l2Header.BlockInfoRoot = common.HexToHash("0x18") + l2Header.BlockInfoRoot = common.HexToHash("0x17") l2Block := state.NewL2Block(l2Header, signedTransactions, uncles, receipts, &trie.StackTrie{}) for _, receipt := range receipts { @@ -1258,7 +1257,6 @@ func TestGetL2BlockByNumber(t *testing.T) { Nonce: rpcBlockNonce, Hash: state.HashPtr(l2Block.Hash()), GlobalExitRoot: l2Block.GlobalExitRoot(), - LocalExitRoot: l2Block.LocalExitRoot(), BlockInfoRoot: l2Block.BlockInfoRoot(), Uncles: rpcUncles, Transactions: rpcTransactions, @@ -1496,7 +1494,6 @@ func TestGetL2BlockByNumber(t *testing.T) { assert.Nil(t, result.Hash) } assert.Equal(t, tc.ExpectedResult.GlobalExitRoot, result.GlobalExitRoot) - assert.Equal(t, tc.ExpectedResult.LocalExitRoot, result.LocalExitRoot) assert.Equal(t, tc.ExpectedResult.BlockInfoRoot, result.BlockInfoRoot) assert.Equal(t, len(tc.ExpectedResult.Transactions), len(result.Transactions)) diff --git a/jsonrpc/endpoints_zkevm.go b/jsonrpc/endpoints_zkevm.go index 0c957c055f..a917bfaa11 100644 --- a/jsonrpc/endpoints_zkevm.go +++ b/jsonrpc/endpoints_zkevm.go @@ -11,6 +11,7 @@ import ( "github.com/0xPolygonHermez/zkevm-node/jsonrpc/types" "github.com/0xPolygonHermez/zkevm-node/log" "github.com/0xPolygonHermez/zkevm-node/state" + "github.com/ethereum/go-ethereum/common" ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/jackc/pgx/v4" ) @@ -300,3 +301,20 @@ func (z *ZKEVMEndpoints) GetNativeBlockHashesInRange(filter NativeBlockHashBlock return nativeBlockHashes, nil }) } + +// GetExitRootsByGER returns the exit roots accordingly to the provided Global Exit Root +func (z *ZKEVMEndpoints) GetExitRootsByGER(globalExitRoot common.Hash) (interface{}, types.Error) { + return z.txMan.NewDbTxScope(z.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, types.Error) { + exitRoots, err := z.state.GetExitRootByGlobalExitRoot(ctx, globalExitRoot, dbTx) + if errors.Is(err, state.ErrNotFound) { + return nil, nil + } else if err != nil { + return RPCErrorResponse(types.DefaultErrorCode, "failed to get exit roots by global exit root from state", err, true) + } + + return types.ExitRoots{ + MainnetExitRoot: exitRoots.MainnetExitRoot, + RollupExitRoot: exitRoots.RollupExitRoot, + }, nil + }) +} diff --git a/jsonrpc/endpoints_zkevm.openrpc.json b/jsonrpc/endpoints_zkevm.openrpc.json index f70556a39f..6d859adb5a 100644 --- a/jsonrpc/endpoints_zkevm.openrpc.json +++ b/jsonrpc/endpoints_zkevm.openrpc.json @@ -343,6 +343,35 @@ "$ref": "#/components/schemas/NativeBlockHashes" } } + }, + { + "name": "zkevm_getExitRootsByGER", + "summary": "Gets the exit roots accordingly to the provided Global Exit Root", + "params": [ + { + "$ref": "#/components/schemas/Keccak" + } + ], + "result": { + "$ref": "#/components/schemas/ExitRoots" + }, + "examples": [ + { + "params": [ + { + "name": "global exit root", + "value": "0x0000000000000000000000000000000000000000000000000000000000000001" + } + ], + "result": { + "name": "Exit Roots", + "value": { + "mainnetExitRoot": "0x0000000000000000000000000000000000000000000000000000000000000002", + "rollupExitRoot": "0x0000000000000000000000000000000000000000000000000000000000000003" + } + } + } + ] } ], "components": { @@ -1153,6 +1182,19 @@ "$ref": "#/components/schemas/BlockNumber" } } + }, + "ExitRoots": { + "title": "ExitRoots", + "type": "object", + "readOnly": true, + "properties": { + "mainnetExitRoot": { + "$ref": "#/components/schemas/Keccak" + }, + "rollupExitRoot": { + "$ref": "#/components/schemas/Keccak" + } + } } } } diff --git a/jsonrpc/endpoints_zkevm_test.go b/jsonrpc/endpoints_zkevm_test.go index c845057233..0865401db6 100644 --- a/jsonrpc/endpoints_zkevm_test.go +++ b/jsonrpc/endpoints_zkevm_test.go @@ -4,12 +4,14 @@ import ( "context" "encoding/json" "errors" + "fmt" "math/big" "strings" "testing" "time" "github.com/0xPolygonHermez/zkevm-node/hex" + "github.com/0xPolygonHermez/zkevm-node/jsonrpc/client" "github.com/0xPolygonHermez/zkevm-node/jsonrpc/types" "github.com/0xPolygonHermez/zkevm-node/state" "github.com/0xPolygonHermez/zkevm-node/test/operations" @@ -1386,8 +1388,7 @@ func TestGetL2FullBlockByNumber(t *testing.T) { l2Header := state.NewL2Header(header) l2Header.GlobalExitRoot = common.HexToHash("0x16") - l2Header.LocalExitRoot = common.HexToHash("0x17") - l2Header.BlockInfoRoot = common.HexToHash("0x18") + l2Header.BlockInfoRoot = common.HexToHash("0x17") l2Block := state.NewL2Block(l2Header, signedTransactions, uncles, receipts, &trie.StackTrie{}) for _, receipt := range receipts { @@ -1462,7 +1463,6 @@ func TestGetL2FullBlockByNumber(t *testing.T) { Nonce: rpcBlockNonce, Hash: state.HashPtr(l2Block.Hash()), GlobalExitRoot: l2Block.GlobalExitRoot(), - LocalExitRoot: l2Block.LocalExitRoot(), BlockInfoRoot: l2Block.BlockInfoRoot(), Uncles: rpcUncles, Transactions: rpcTransactions, @@ -1708,7 +1708,6 @@ func TestGetL2FullBlockByNumber(t *testing.T) { assert.Nil(t, result.Hash) } assert.Equal(t, tc.ExpectedResult.GlobalExitRoot, result.GlobalExitRoot) - assert.Equal(t, tc.ExpectedResult.LocalExitRoot, result.LocalExitRoot) assert.Equal(t, tc.ExpectedResult.BlockInfoRoot, result.BlockInfoRoot) assert.Equal(t, len(tc.ExpectedResult.Transactions), len(result.Transactions)) @@ -1876,6 +1875,116 @@ func TestGetNativeBlockHashesInRange(t *testing.T) { } } +func TestGetExitRootsByGER(t *testing.T) { + type testCase struct { + Name string + GER common.Hash + ExpectedResult *types.ExitRoots + ExpectedError types.Error + SetupMocks func(*mockedServer, *mocksWrapper, *testCase) + } + + testCases := []testCase{ + { + Name: "GER not found", + GER: common.HexToHash("0x123"), + ExpectedResult: nil, + ExpectedError: nil, + SetupMocks: func(s *mockedServer, m *mocksWrapper, 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("GetExitRootByGlobalExitRoot", context.Background(), tc.GER, m.DbTx). + Return(nil, state.ErrNotFound) + }, + }, + { + Name: "get exit roots fails to load exit roots from state", + GER: common.HexToHash("0x123"), + ExpectedResult: nil, + ExpectedError: nil, + SetupMocks: func(s *mockedServer, m *mocksWrapper, 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("GetExitRootByGlobalExitRoot", context.Background(), tc.GER, m.DbTx). + Return(nil, fmt.Errorf("failed to load exit roots from state")) + }, + }, + { + Name: "get exit roots successfully", + GER: common.HexToHash("0x345"), + ExpectedResult: &types.ExitRoots{ + MainnetExitRoot: common.HexToHash("0x1"), + RollupExitRoot: common.HexToHash("0x2"), + }, + ExpectedError: nil, + SetupMocks: func(s *mockedServer, m *mocksWrapper, tc *testCase) { + m.DbTx. + On("Commit", context.Background()). + Return(nil). + Once() + + m.State. + On("BeginStateTransaction", context.Background()). + Return(m.DbTx, nil). + Once() + + er := &state.GlobalExitRoot{ + MainnetExitRoot: tc.ExpectedResult.MainnetExitRoot, + RollupExitRoot: tc.ExpectedResult.RollupExitRoot, + } + + m.State. + On("GetExitRootByGlobalExitRoot", context.Background(), tc.GER, m.DbTx). + Return(er, nil) + }, + }, + } + + s, m, _ := newSequencerMockedServer(t) + defer s.Stop() + + c := client.NewClient(s.ServerURL) + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + tc := testCase + testCase.SetupMocks(s, m, &tc) + + exitRoots, err := c.ExitRootsByGER(context.Background(), tc.GER) + require.NoError(t, err) + + if exitRoots != nil || tc.ExpectedResult != nil { + assert.Equal(t, tc.ExpectedResult.MainnetExitRoot.String(), exitRoots.MainnetExitRoot.String()) + assert.Equal(t, tc.ExpectedResult.RollupExitRoot.String(), exitRoots.RollupExitRoot.String()) + } + + if err != nil || tc.ExpectedError != nil { + rpcErr := err.(types.RPCError) + assert.Equal(t, tc.ExpectedError.ErrorCode(), rpcErr.ErrorCode()) + assert.Equal(t, tc.ExpectedError.Error(), rpcErr.Error()) + } + }) + } +} + func ptrUint64(n uint64) *uint64 { return &n } diff --git a/jsonrpc/types/types.go b/jsonrpc/types/types.go index 029f39e806..1eecb64301 100644 --- a/jsonrpc/types/types.go +++ b/jsonrpc/types/types.go @@ -260,7 +260,6 @@ type Block struct { Transactions []TransactionOrHash `json:"transactions"` Uncles []common.Hash `json:"uncles"` GlobalExitRoot common.Hash `json:"globalExitRoot"` - LocalExitRoot common.Hash `json:"localExitRoot"` BlockInfoRoot common.Hash `json:"blockInfoRoot"` } @@ -310,7 +309,6 @@ func NewBlock(hash *common.Hash, b *state.L2Block, receipts []types.Receipt, ful Hash: hash, Transactions: []TransactionOrHash{}, Uncles: []common.Hash{}, - LocalExitRoot: h.LocalExitRoot, GlobalExitRoot: h.GlobalExitRoot, BlockInfoRoot: h.BlockInfoRoot, } @@ -685,3 +683,9 @@ func NewLog(l types.Log) Log { Removed: l.Removed, } } + +// ExitRoots structure +type ExitRoots struct { + MainnetExitRoot common.Hash `json:"mainnetExitRoot"` + RollupExitRoot common.Hash `json:"rollupExitRoot"` +} diff --git a/state/l2block.go b/state/l2block.go index 07c4fddac0..c66f8db0d8 100644 --- a/state/l2block.go +++ b/state/l2block.go @@ -25,7 +25,6 @@ type gethBlock struct { // L2Header represents a block header in the L2. type L2Header struct { *gethHeader - LocalExitRoot common.Hash `json:"localExitRoot"` GlobalExitRoot common.Hash `json:"globalExitRoot"` BlockInfoRoot common.Hash `json:"blockInfoRoot"` } @@ -56,7 +55,6 @@ func (h *L2Header) MarshalJSON() ([]byte, error) { } } - m["localExitRoot"] = h.LocalExitRoot.String() m["globalExitRoot"] = h.GlobalExitRoot.String() m["blockInfoRoot"] = h.BlockInfoRoot.String() @@ -88,9 +86,6 @@ func (h *L2Header) UnmarshalJSON(input []byte) error { } h.gethHeader = &gethHeader{header} - if localExitRoot, found := m["localExitRoot"]; found { - h.LocalExitRoot = common.HexToHash(localExitRoot.(string)) - } if globalExitRoot, found := m["globalExitRoot"]; found { h.GlobalExitRoot = common.HexToHash(globalExitRoot.(string)) } @@ -113,11 +108,6 @@ type L2Block struct { ReceivedFrom interface{} } -// LocalExitRoot returns the header LocalExitRoot -func (b *L2Block) LocalExitRoot() common.Hash { - return b.Header().LocalExitRoot -} - // GlobalExitRoot returns the header GlobalExitRoot func (b *L2Block) GlobalExitRoot() common.Hash { return b.Header().GlobalExitRoot @@ -212,7 +202,6 @@ func CopyHeader(h *L2Header) *L2Header { } cpy := *h cpy.gethHeader = &gethHeader{types.CopyHeader(h.gethHeader.Header)} - cpy.LocalExitRoot = h.LocalExitRoot cpy.GlobalExitRoot = h.GlobalExitRoot cpy.BlockInfoRoot = h.BlockInfoRoot return &cpy diff --git a/state/transaction.go b/state/transaction.go index c86a6c24f3..700c83cb56 100644 --- a/state/transaction.go +++ b/state/transaction.go @@ -281,7 +281,6 @@ func (s *State) StoreL2Block(ctx context.Context, batchNumber uint64, l2Block *P l2Header.GlobalExitRoot = l2Block.GlobalExitRoot l2Header.BlockInfoRoot = l2Block.BlockInfoRoot - //TODO: l2header.LocalExitRoot?? transactions := []*types.Transaction{} storeTxsEGPData := []StoreTxEGPData{}