Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add new endpoint zkevm_getLatestGlobalExitRoot #3103

Merged
merged 11 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions db/migrations/state/0014.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- +migrate Up
CREATE INDEX IF NOT EXISTS idx_batch_global_exit_root ON state.batch (global_exit_root);

-- +migrate Down
DROP INDEX IF EXISTS state.idx_batch_global_exit_root;

49 changes: 49 additions & 0 deletions db/migrations/state/0014_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package migrations_test

import (
"database/sql"
"testing"

"github.com/stretchr/testify/assert"
)

// this migration changes length of the token name
type migrationTest0014 struct{}

func (m migrationTest0014) InsertData(db *sql.DB) error {
return nil
}

func (m migrationTest0014) RunAssertsAfterMigrationUp(t *testing.T, db *sql.DB) {
indexes := []string{
"idx_batch_global_exit_root",
}
// Check indexes adding
for _, idx := range indexes {
// getIndex
const getIndex = `SELECT count(*) FROM pg_indexes WHERE indexname = $1;`
row := db.QueryRow(getIndex, idx)
var result int
assert.NoError(t, row.Scan(&result))
assert.Equal(t, 1, result)
}
}

func (m migrationTest0014) RunAssertsAfterMigrationDown(t *testing.T, db *sql.DB) {
indexes := []string{
"idx_batch_global_exit_root",
}
// Check indexes removing
for _, idx := range indexes {
// getIndex
const getIndex = `SELECT count(*) FROM pg_indexes WHERE indexname = $1;`
row := db.QueryRow(getIndex, idx)
var result int
assert.NoError(t, row.Scan(&result))
assert.Equal(t, 0, result)
}
}

func TestMigration0014(t *testing.T) {
runMigrationTest(t, 14, migrationTest0014{})
}
20 changes: 20 additions & 0 deletions jsonrpc/client/zkevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,23 @@ func (c *Client) ExitRootsByGER(ctx context.Context, globalExitRoot common.Hash)

return result, nil
}

// GetLatestGlobalExitRoot returns the latest global exit root
func (c *Client) GetLatestGlobalExitRoot(ctx context.Context) (common.Hash, error) {
response, err := JSONRPCCall(c.url, "zkevm_getLatestGlobalExitRoot")
if err != nil {
return common.Hash{}, err
}

if response.Error != nil {
return common.Hash{}, response.Error.RPCError()
}

var result string
err = json.Unmarshal(response.Result, &result)
if err != nil {
return common.Hash{}, err
}

return common.HexToHash(result), nil
}
14 changes: 14 additions & 0 deletions jsonrpc/endpoints_zkevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,3 +535,17 @@ func (z *ZKEVMEndpoints) getBlockByArg(ctx context.Context, blockArg *types.Bloc

return block, nil
}

// GetLatestGlobalExitRoot returns the last global exit root used by l2
func (z *ZKEVMEndpoints) GetLatestGlobalExitRoot() (interface{}, types.Error) {
return z.txMan.NewDbTxScope(z.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, types.Error) {
var err error

ger, err := z.state.GetLatestBatchGlobalExitRoot(ctx, dbTx)
if err != nil {
return RPCErrorResponse(types.DefaultErrorCode, "couldn't load the last global exit root", err, true)
}

return ger.String(), nil
})
}
12 changes: 12 additions & 0 deletions jsonrpc/endpoints_zkevm.openrpc.json
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,18 @@
}
}
]
},
{
"name": "zkevm_getLatestGlobalExitRoot",
"summary": "Returns the latest global exit root used in a batch.",
"params": [
],
"result": {
"name": "GER",
"schema": {
"$ref": "#/components/schemas/Keccak"
}
}
}
],
"components": {
Expand Down
78 changes: 78 additions & 0 deletions jsonrpc/endpoints_zkevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2586,3 +2586,81 @@ func TestGetExitRootsByGER(t *testing.T) {
})
}
}

func TestGetLatestGlobalExitRoot(t *testing.T) {
type testCase struct {
Name string
ExpectedResult *common.Hash
ExpectedError types.Error
SetupMocks func(*mocksWrapper, *testCase)
}

testCases := []testCase{
{
Name: "failed to load GER from state",
ExpectedResult: nil,
ExpectedError: types.NewRPCError(types.DefaultErrorCode, "couldn't load the last global exit root"),
SetupMocks: func(m *mocksWrapper, 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("GetLatestBatchGlobalExitRoot", context.Background(), m.DbTx).
Return(nil, fmt.Errorf("failed to load GER from state")).
Once()
},
},
{
Name: "Get latest GER successfully",
ExpectedResult: state.Ptr(common.HexToHash("0x1")),
ExpectedError: nil,
SetupMocks: func(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("GetLatestBatchGlobalExitRoot", context.Background(), m.DbTx).
Return(common.HexToHash("0x1"), nil).
Once()
},
},
}

s, m, _ := newSequencerMockedServer(t)
defer s.Stop()

zkEVMClient := client.NewClient(s.ServerURL)

for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
tc := testCase
testCase.SetupMocks(m, &tc)

ger, err := zkEVMClient.GetLatestGlobalExitRoot(context.Background())

if tc.ExpectedResult != nil {
assert.Equal(t, tc.ExpectedResult.String(), ger.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())
}
})
}
}
30 changes: 30 additions & 0 deletions jsonrpc/mocks/mock_state.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions jsonrpc/types/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ type StateInterface interface {
GetLastVerifiedL2BlockNumberUntilL1Block(ctx context.Context, l1FinalizedBlockNumber uint64, dbTx pgx.Tx) (uint64, error)
GetLastVerifiedBatchNumberUntilL1Block(ctx context.Context, l1BlockNumber uint64, dbTx pgx.Tx) (uint64, error)
GetBatchTimestamp(ctx context.Context, batchNumber uint64, forcedForkId *uint64, dbTx pgx.Tx) (*time.Time, error)
GetLatestBatchGlobalExitRoot(ctx context.Context, dbTx pgx.Tx) (common.Hash, error)
}

// EthermanInterface provides integration with L1
Expand Down
1 change: 1 addition & 0 deletions state/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,5 @@ type storage interface {
GetBlockByNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*Block, error)
GetVirtualBatchParentHash(ctx context.Context, batchNumber uint64, dbTx pgx.Tx) (common.Hash, error)
GetForcedBatchParentHash(ctx context.Context, forcedBatchNumber uint64, dbTx pgx.Tx) (common.Hash, error)
GetLatestBatchGlobalExitRoot(ctx context.Context, dbTx pgx.Tx) (common.Hash, error)
}
59 changes: 59 additions & 0 deletions state/mocks/mock_storage.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions state/pgstatestorage/batch.go
Original file line number Diff line number Diff line change
Expand Up @@ -1008,3 +1008,20 @@ func (p *PostgresStorage) GetForcedBatchParentHash(ctx context.Context, forcedBa
}
return common.HexToHash(parentHash), nil
}

// GetLatestBatchGlobalExitRoot gets the last GER that is not zero from batches
func (p *PostgresStorage) GetLatestBatchGlobalExitRoot(ctx context.Context, dbTx pgx.Tx) (common.Hash, error) {
var lastGER string
const query = "SELECT global_exit_root FROM state.batch where global_exit_root != $1 ORDER BY batch_num DESC LIMIT 1"

q := p.getExecQuerier(dbTx)
err := q.QueryRow(ctx, query, state.ZeroHash.String()).Scan(&lastGER)

if errors.Is(err, pgx.ErrNoRows) {
return state.ZeroHash, nil
} else if err != nil {
return state.ZeroHash, err
}

return common.HexToHash(lastGER), nil
}
Loading