-
Notifications
You must be signed in to change notification settings - Fork 674
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
L1 Synchronization process check some L2Blocks from TrustedNode (#3445)
* L1 Synchronization process check some L2Blocks from TrustedNode
- Loading branch information
1 parent
b184e2b
commit 393d47f
Showing
19 changed files
with
879 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package actions | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"math/big" | ||
|
||
"github.com/0xPolygonHermez/zkevm-node/jsonrpc/types" | ||
"github.com/0xPolygonHermez/zkevm-node/log" | ||
"github.com/0xPolygonHermez/zkevm-node/state" | ||
"github.com/jackc/pgx/v4" | ||
) | ||
|
||
// Implements PostClosedBatchChecker | ||
|
||
type stateGetL2Block interface { | ||
GetL2BlockByNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*state.L2Block, error) | ||
GetLastL2BlockNumber(ctx context.Context, dbTx pgx.Tx) (uint64, error) | ||
} | ||
|
||
type trustedRPCGetL2Block interface { | ||
BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) | ||
} | ||
|
||
// CheckL2BlockHash is a struct that implements a checker of L2Block hash | ||
type CheckL2BlockHash struct { | ||
state stateGetL2Block | ||
trustedClient trustedRPCGetL2Block | ||
lastL2BlockChecked uint64 | ||
// Is a modulus used to choose the l2block to check | ||
modulusL2BlockToCheck uint64 | ||
} | ||
|
||
// NewCheckL2BlockHash creates a new CheckL2BlockHash | ||
func NewCheckL2BlockHash(state stateGetL2Block, | ||
trustedClient trustedRPCGetL2Block, | ||
initialL2BlockNumber uint64, | ||
modulusBlockNumber uint64) *CheckL2BlockHash { | ||
return &CheckL2BlockHash{ | ||
state: state, | ||
trustedClient: trustedClient, | ||
lastL2BlockChecked: initialL2BlockNumber, | ||
modulusL2BlockToCheck: modulusBlockNumber, | ||
} | ||
} | ||
|
||
// CheckL2Block checks the L2Block hash between the local and the trusted | ||
func (p *CheckL2BlockHash) CheckL2Block(ctx context.Context, dbTx pgx.Tx) error { | ||
lastLocalL2BlockNumber, err := p.state.GetLastL2BlockNumber(ctx, dbTx) | ||
if errors.Is(err, state.ErrNotFound) || errors.Is(err, state.ErrStateNotSynchronized) { | ||
log.Debugf("checkL2block:No L2Block in database. err: %s", err.Error()) | ||
return nil | ||
} | ||
if err != nil { | ||
log.Errorf("checkL2block: Error getting last L2Block from the database. err: %s", err.Error()) | ||
return err | ||
} | ||
shouldCheck, l2BlockNumber := p.GetNextL2BlockToCheck(lastLocalL2BlockNumber, p.GetMinimumL2BlockToCheck()) | ||
if !shouldCheck { | ||
return nil | ||
} | ||
err = p.iterationCheckL2Block(ctx, l2BlockNumber, dbTx) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
// GetNextL2BlockToCheck returns true is need to check and the blocknumber | ||
func (p *CheckL2BlockHash) GetNextL2BlockToCheck(lastLocalL2BlockNumber, minL2BlockNumberToCheck uint64) (bool, uint64) { | ||
l2BlockNumber := max(minL2BlockNumberToCheck, lastLocalL2BlockNumber) | ||
if l2BlockNumber > lastLocalL2BlockNumber { | ||
log.Infof("checkL2block: skip check L2block (next to check: %d) currently LastL2BlockNumber: %d", minL2BlockNumberToCheck, lastLocalL2BlockNumber) | ||
return false, 0 | ||
} | ||
return true, l2BlockNumber | ||
} | ||
|
||
// GetMinimumL2BlockToCheck returns the minimum L2Block to check | ||
func (p *CheckL2BlockHash) GetMinimumL2BlockToCheck() uint64 { | ||
if p.modulusL2BlockToCheck == 0 { | ||
return p.lastL2BlockChecked + 1 | ||
} | ||
return ((p.lastL2BlockChecked / p.modulusL2BlockToCheck) + 1) * p.modulusL2BlockToCheck | ||
} | ||
|
||
// GetL2Blocks returns localL2Block and trustedL2Block | ||
func (p *CheckL2BlockHash) GetL2Blocks(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*state.L2Block, *types.Block, error) { | ||
localL2Block, err := p.state.GetL2BlockByNumber(ctx, blockNumber, dbTx) | ||
if err != nil { | ||
log.Debugf("checkL2block: Error getting L2Block %d from the database. err: %s", blockNumber, err.Error()) | ||
return nil, nil, err | ||
} | ||
trustedL2Block, err := p.trustedClient.BlockByNumber(ctx, big.NewInt(int64(blockNumber))) | ||
if err != nil { | ||
log.Errorf("checkL2block: Error getting L2Block %d from the Trusted RPC. err:%s", blockNumber, err.Error()) | ||
return nil, nil, err | ||
} | ||
return localL2Block, trustedL2Block, nil | ||
} | ||
|
||
// CheckPostClosedBatch checks the last L2Block hash on close batch | ||
func (p *CheckL2BlockHash) iterationCheckL2Block(ctx context.Context, l2BlockNumber uint64, dbTx pgx.Tx) error { | ||
prefixLogs := fmt.Sprintf("checkL2block: L2BlockNumber: %d ", l2BlockNumber) | ||
localL2Block, trustedL2Block, err := p.GetL2Blocks(ctx, l2BlockNumber, dbTx) | ||
if errors.Is(err, state.ErrNotFound) || errors.Is(err, state.ErrStateNotSynchronized) { | ||
log.Debugf("%s not found in the database", prefixLogs, l2BlockNumber) | ||
return nil | ||
} | ||
if err != nil { | ||
log.Errorf("%s Error getting from the database and trusted. err: %s", prefixLogs, err.Error()) | ||
return err | ||
} | ||
if localL2Block == nil || trustedL2Block == nil { | ||
log.Errorf("%s localL2Block or trustedL2Block is nil", prefixLogs, l2BlockNumber) | ||
return nil | ||
} | ||
|
||
if err := compareL2Blocks(prefixLogs, localL2Block, trustedL2Block); err != nil { | ||
log.Errorf("%s Error comparing L2Blocks from the database and trusted. err: %s", prefixLogs, err.Error()) | ||
return err | ||
} | ||
|
||
log.Infof("%s checked L2Block in the database and the trusted batch are the same %s", prefixLogs, localL2Block.Hash().String()) | ||
// Compare the two blocks | ||
p.lastL2BlockChecked = l2BlockNumber | ||
return nil | ||
} | ||
|
||
func compareL2Blocks(prefixLogs string, localL2Block *state.L2Block, trustedL2Block *types.Block) error { | ||
if localL2Block == nil || trustedL2Block == nil || trustedL2Block.Hash == nil { | ||
return fmt.Errorf("%s localL2Block or trustedL2Block or trustedHash are nil", prefixLogs) | ||
} | ||
if localL2Block.Hash() != *trustedL2Block.Hash { | ||
return fmt.Errorf("%s localL2Block.Hash %s and trustedL2Block.Hash %s are different", prefixLogs, localL2Block.Hash().String(), (*trustedL2Block.Hash).String()) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package actions | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/0xPolygonHermez/zkevm-node/etherman" | ||
"github.com/jackc/pgx/v4" | ||
) | ||
|
||
// CheckL2BlockProcessorDecorator This class is just a decorator to call CheckL2Block | ||
type CheckL2BlockProcessorDecorator struct { | ||
L1EventProcessor | ||
l2blockChecker *CheckL2BlockHash | ||
} | ||
|
||
// NewCheckL2BlockDecorator creates a new CheckL2BlockDecorator | ||
func NewCheckL2BlockDecorator(l1EventProcessor L1EventProcessor, l2blockChecker *CheckL2BlockHash) *CheckL2BlockProcessorDecorator { | ||
return &CheckL2BlockProcessorDecorator{ | ||
L1EventProcessor: l1EventProcessor, | ||
l2blockChecker: l2blockChecker, | ||
} | ||
} | ||
|
||
// Process wraps the real Process and after check the L2Blocks | ||
func (p *CheckL2BlockProcessorDecorator) Process(ctx context.Context, order etherman.Order, l1Block *etherman.Block, dbTx pgx.Tx) error { | ||
res := p.L1EventProcessor.Process(ctx, order, l1Block, dbTx) | ||
if res != nil { | ||
return res | ||
} | ||
if p.l2blockChecker == nil { | ||
return nil | ||
} | ||
return p.l2blockChecker.CheckL2Block(ctx, dbTx) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package actions_test | ||
|
||
import ( | ||
"context" | ||
"math/big" | ||
"testing" | ||
|
||
rpctypes "github.com/0xPolygonHermez/zkevm-node/jsonrpc/types" | ||
"github.com/0xPolygonHermez/zkevm-node/state" | ||
"github.com/0xPolygonHermez/zkevm-node/synchronizer/actions" | ||
mock_syncinterfaces "github.com/0xPolygonHermez/zkevm-node/synchronizer/common/syncinterfaces/mocks" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/core/types" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/mock" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
type CheckL2BlocksTestData struct { | ||
sut *actions.CheckL2BlockHash | ||
mockState *mock_syncinterfaces.StateFullInterface | ||
zKEVMClient *mock_syncinterfaces.ZKEVMClientInterface | ||
} | ||
|
||
func TestCheckL2BlockHash_GetMinimumL2BlockToCheck(t *testing.T) { | ||
// Create an instance of CheckL2BlockHash | ||
values := []struct { | ||
initial uint64 | ||
modulus uint64 | ||
expected uint64 | ||
}{ | ||
{0, 10, 10}, | ||
{1, 10, 10}, | ||
{9, 10, 10}, | ||
{10, 10, 20}, | ||
{0, 0, 1}, | ||
{1, 0, 2}, | ||
} | ||
for _, data := range values { | ||
// Call the GetNextL2BlockToCheck method | ||
checkL2Block := actions.NewCheckL2BlockHash(nil, nil, data.initial, data.modulus) | ||
nextL2Block := checkL2Block.GetMinimumL2BlockToCheck() | ||
|
||
// Assert the expected result | ||
assert.Equal(t, data.expected, nextL2Block) | ||
} | ||
} | ||
|
||
func TestCheckL2BlockHashNotEnoughBlocksToCheck(t *testing.T) { | ||
data := newCheckL2BlocksTestData(t, 0, 10) | ||
// Call the CheckL2Block method | ||
data.mockState.EXPECT().GetLastL2BlockNumber(mock.Anything, mock.Anything).Return(uint64(0), nil) | ||
err := data.sut.CheckL2Block(context.Background(), nil) | ||
require.NoError(t, err) | ||
} | ||
|
||
func newCheckL2BlocksTestData(t *testing.T, initialL2Block, modulus uint64) CheckL2BlocksTestData { | ||
res := CheckL2BlocksTestData{ | ||
mockState: mock_syncinterfaces.NewStateFullInterface(t), | ||
zKEVMClient: mock_syncinterfaces.NewZKEVMClientInterface(t), | ||
} | ||
res.sut = actions.NewCheckL2BlockHash(res.mockState, res.zKEVMClient, initialL2Block, modulus) | ||
return res | ||
} | ||
func TestCheckL2BlockHash_GetNextL2BlockToCheck(t *testing.T) { | ||
values := []struct { | ||
lastLocalL2BlockNumber uint64 | ||
minL2BlockNumberToCheck uint64 | ||
expectedShouldCheck bool | ||
expectedNextL2BlockNumber uint64 | ||
}{ | ||
{0, 10, false, 0}, | ||
{10, 10, true, 10}, | ||
{9, 10, false, 0}, | ||
{10, 10, true, 10}, | ||
{0, 0, true, 0}, | ||
{1, 0, true, 1}, | ||
} | ||
|
||
for _, data := range values { | ||
checkL2Block := actions.NewCheckL2BlockHash(nil, nil, 0, 0) | ||
shouldCheck, nextL2Block := checkL2Block.GetNextL2BlockToCheck(data.lastLocalL2BlockNumber, data.minL2BlockNumberToCheck) | ||
|
||
assert.Equal(t, data.expectedShouldCheck, shouldCheck, data) | ||
assert.Equal(t, data.expectedNextL2BlockNumber, nextL2Block, data) | ||
} | ||
} | ||
|
||
func TestCheckL2BlockHashMatch(t *testing.T) { | ||
data := newCheckL2BlocksTestData(t, 1, 10) | ||
lastL2Block := uint64(14) | ||
lastL2BlockBigInt := big.NewInt(int64(lastL2Block)) | ||
gethHeader := types.Header{ | ||
Number: big.NewInt(int64(lastL2Block)), | ||
} | ||
stateBlock := state.NewL2Block(state.NewL2Header(&gethHeader), nil, nil, nil, nil) | ||
|
||
data.mockState.EXPECT().GetLastL2BlockNumber(mock.Anything, mock.Anything).Return(lastL2Block, nil) | ||
data.mockState.EXPECT().GetL2BlockByNumber(mock.Anything, lastL2Block, mock.Anything).Return(stateBlock, nil) | ||
l2blockHash := stateBlock.Hash() | ||
rpcL2Block := rpctypes.Block{ | ||
Hash: &l2blockHash, | ||
Number: rpctypes.ArgUint64(lastL2Block), | ||
} | ||
|
||
data.zKEVMClient.EXPECT().BlockByNumber(mock.Anything, lastL2BlockBigInt).Return(&rpcL2Block, nil) | ||
err := data.sut.CheckL2Block(context.Background(), nil) | ||
require.NoError(t, err) | ||
} | ||
|
||
func TestCheckL2BlockHashMissmatch(t *testing.T) { | ||
data := newCheckL2BlocksTestData(t, 1, 10) | ||
lastL2Block := uint64(14) | ||
lastL2BlockBigInt := big.NewInt(int64(lastL2Block)) | ||
gethHeader := types.Header{ | ||
Number: big.NewInt(int64(lastL2Block)), | ||
} | ||
stateBlock := state.NewL2Block(state.NewL2Header(&gethHeader), nil, nil, nil, nil) | ||
|
||
data.mockState.EXPECT().GetLastL2BlockNumber(mock.Anything, mock.Anything).Return(lastL2Block, nil) | ||
data.mockState.EXPECT().GetL2BlockByNumber(mock.Anything, lastL2Block, mock.Anything).Return(stateBlock, nil) | ||
l2blockHash := common.HexToHash("0x1234") | ||
rpcL2Block := rpctypes.Block{ | ||
Hash: &l2blockHash, | ||
Number: rpctypes.ArgUint64(lastL2Block), | ||
} | ||
|
||
data.zKEVMClient.EXPECT().BlockByNumber(mock.Anything, lastL2BlockBigInt).Return(&rpcL2Block, nil) | ||
err := data.sut.CheckL2Block(context.Background(), nil) | ||
require.Error(t, err) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.