Skip to content

Commit

Permalink
feature: bridge RPC
Browse files Browse the repository at this point in the history
* implementation completed, missing tests

* WIP

* WIP

* WIP

* sync refactor

* decouple sync processors from EVM

* Add CLI for aggOracle

* WIP

* wip

* WIP

* start reorg detector

* fix docker

* pass test with docker

* add TODO

* go mod tidy

* Implement base addLeaf

* pass test vectors

* fix UT

* Add PR review suggestions from Stefan-Ethernal

* fix UTs

* Add PR review suggestions from Stefan-Ethernal

* fix datacommittee_test deploying proxy contract

* fix UTs

* WIP

* WIP

* abstract tree

* Fix UTs

* Add PR review suggestions from joanestebanr

* Fix evmdownloader problems

* refactor tree add and rollback to be atomic

* simplify l1infotree

* implementation done, test WIP

* pass E2E test

* remove outdated coment

* make test shorter

* Add coments

* WIP

* added E2E test

* fix duplicated key case

* Feature/lastgersync (#33)

* wip

* wip

* implementation done

* separated setup logic from aggoracle e2e

* pass e2e

* Fix sync UTs

* Feature/claim sponsor (#35)

* implementation done, missing finish e2e test

* pass E2E

* Feature/rpc (#40)

* wip

* wip

* implementation done

* separated setup logic from aggoracle e2e

* pass e2e

* Fix sync UTs

* implementation done, missing finish e2e test

* pass E2E

* WIP

* instantiate rpc on the cmd

* add default config

* WIP

* wip

* pass e2e test

* close db txs

* fix conflicts

* add l1Info as part of the claim response

* increase time.sleep to pass UT on GHA

* apply requests from ToniRamirezM

* fix block finality config

* apply PR requests by Stefan-Ethernal

* apply PR requests by goran-ethernal

* Fixing warnings and go mod tidy

* fix UTs

---------

Co-authored-by: Stefan Negovanović <[email protected]>
Co-authored-by: joanestebanr <[email protected]>
  • Loading branch information
3 people committed Sep 17, 2024
1 parent d8212b9 commit 3dff8af
Show file tree
Hide file tree
Showing 50 changed files with 4,446 additions and 420 deletions.
4 changes: 2 additions & 2 deletions aggoracle/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ var (
type Config struct {
TargetChainType TargetChainType `mapstructure:"TargetChainType"`
URLRPCL1 string `mapstructure:"URLRPCL1"`
// TODO: BlockFinality doesnt work as per the jsonschema
BlockFinality string `jsonschema:"enum=latest,enum=safe, enum=pending, enum=finalized" mapstructure:"BlockFinality"`
// BlockFinality indicates the status of the blocks that will be queried in order to sync
BlockFinality string `jsonschema:"enum=LatestBlock, enum=SafeBlock, enum=PendingBlock, enum=FinalizedBlock, enum=EarliestBlock" mapstructure:"BlockFinality"`
WaitPeriodNextGER types.Duration `mapstructure:"WaitPeriodNextGER"`
EVMSender chaingersender.EVMConfig `mapstructure:"EVMSender"`
}
179 changes: 3 additions & 176 deletions aggoracle/e2e_test.go
Original file line number Diff line number Diff line change
@@ -1,196 +1,23 @@
package aggoracle_test

import (
"context"
"errors"
"fmt"
"math/big"
"strconv"
"testing"
"time"

gerContractL1 "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/globalexitrootnopush0"
gerContractEVMChain "github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/pessimisticglobalexitrootnopush0"
"github.com/0xPolygon/cdk/aggoracle"
"github.com/0xPolygon/cdk/aggoracle/chaingersender"
"github.com/0xPolygon/cdk/etherman"
"github.com/0xPolygon/cdk/l1infotreesync"
"github.com/0xPolygon/cdk/log"
"github.com/0xPolygon/cdk/reorgdetector"
ethtxmanager "github.com/0xPolygonHermez/zkevm-ethtx-manager/ethtxmanager"
"github.com/ethereum/go-ethereum"
"github.com/0xPolygon/cdk/test/helpers"
"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/ethereum/go-ethereum/ethclient/simulated"
mock "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

func TestEVM(t *testing.T) {
ctx := context.Background()
l1Client, syncer, gerL1Contract, authL1 := commonSetup(t)
sender := evmSetup(t)
oracle, err := aggoracle.New(sender, l1Client.Client(), syncer, etherman.LatestBlock, time.Millisecond)
require.NoError(t, err)
go oracle.Start(ctx)

runTest(t, gerL1Contract, sender, l1Client, authL1)
}

func commonSetup(t *testing.T) (
*simulated.Backend,
*l1infotreesync.L1InfoTreeSync,
*gerContractL1.Globalexitrootnopush0,
*bind.TransactOpts,
) {
// Config and spin up
ctx := context.Background()
// Simulated L1
privateKeyL1, err := crypto.GenerateKey()
require.NoError(t, err)
authL1, err := bind.NewKeyedTransactorWithChainID(privateKeyL1, big.NewInt(1337))
require.NoError(t, err)
l1Client, gerL1Addr, gerL1Contract, err := newSimulatedL1(authL1)
require.NoError(t, err)
// Reorg detector
dbPathReorgDetector := t.TempDir()
reorg, err := reorgdetector.New(ctx, l1Client.Client(), dbPathReorgDetector)
require.NoError(t, err)
// Syncer
dbPathSyncer := t.TempDir()
syncer, err := l1infotreesync.New(ctx, dbPathSyncer, gerL1Addr, common.Address{}, 10, etherman.LatestBlock, reorg, l1Client.Client(), time.Millisecond, 0, 100*time.Millisecond, 3)
require.NoError(t, err)
go syncer.Start(ctx)

return l1Client, syncer, gerL1Contract, authL1
}

func evmSetup(t *testing.T) aggoracle.ChainSender {
privateKeyL2, err := crypto.GenerateKey()
require.NoError(t, err)
authL2, err := bind.NewKeyedTransactorWithChainID(privateKeyL2, big.NewInt(1337))
require.NoError(t, err)
l2Client, gerL2Addr, _, err := newSimulatedEVMAggSovereignChain(authL2)
require.NoError(t, err)
ethTxManMock := aggoracle.NewEthTxManagerMock(t)
// id, err := c.ethTxMan.Add(ctx, &c.gerAddr, nil, big.NewInt(0), tx.Data(), c.gasOffset, nil)
ethTxManMock.On("Add", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Run(func(args mock.Arguments) {
ctx := context.Background()
nonce, err := l2Client.Client().PendingNonceAt(ctx, authL2.From)
if err != nil {
log.Error(err)
return
}
gas, err := l2Client.Client().EstimateGas(ctx, ethereum.CallMsg{
From: authL2.From,
To: args.Get(1).(*common.Address),
Value: big.NewInt(0),
Data: args.Get(4).([]byte),
})
if err != nil {
log.Error(err)
res, err := l2Client.Client().CallContract(ctx, ethereum.CallMsg{
From: authL2.From,
To: args.Get(1).(*common.Address),
Value: big.NewInt(0),
Data: args.Get(4).([]byte),
}, nil)
log.Debugf("contract call: %s", res)
if err != nil {
log.Error(err)
}
return
}
price, err := l2Client.Client().SuggestGasPrice(ctx)
if err != nil {
log.Error(err)
}
tx := types.NewTx(&types.LegacyTx{
To: args.Get(1).(*common.Address),
Nonce: nonce,
Value: big.NewInt(0),
Data: args.Get(4).([]byte),
Gas: gas,
GasPrice: price,
})
tx.Gas()
signedTx, err := authL2.Signer(authL2.From, tx)
if err != nil {
log.Error(err)
return
}
err = l2Client.Client().SendTransaction(ctx, signedTx)
if err != nil {
log.Error(err)
return
}
l2Client.Commit()
}).
Return(common.Hash{}, nil)
// res, err := c.ethTxMan.Result(ctx, id)
ethTxManMock.On("Result", mock.Anything, mock.Anything).
Return(ethtxmanager.MonitoredTxResult{Status: ethtxmanager.MonitoredTxStatusMined}, nil)
sender, err := chaingersender.NewEVMChainGERSender(gerL2Addr, authL2.From, l2Client.Client(), ethTxManMock, 0, time.Millisecond*50)
require.NoError(t, err)

return sender
}

func newSimulatedL1(auth *bind.TransactOpts) (
client *simulated.Backend,
gerAddr common.Address,
gerContract *gerContractL1.Globalexitrootnopush0,
err error,
) {
balance, _ := new(big.Int).SetString("10000000000000000000000000", 10) //nolint:gomnd
address := auth.From
genesisAlloc := map[common.Address]types.Account{
address: {
Balance: balance,
},
}
blockGasLimit := uint64(999999999999999999) //nolint:gomnd
client = simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit))

gerAddr, _, gerContract, err = gerContractL1.DeployGlobalexitrootnopush0(auth, client.Client(), auth.From, auth.From)

client.Commit()
return
}

func newSimulatedEVMAggSovereignChain(auth *bind.TransactOpts) (
client *simulated.Backend,
gerAddr common.Address,
gerContract *gerContractEVMChain.Pessimisticglobalexitrootnopush0,
err error,
) {
balance, _ := new(big.Int).SetString("10000000000000000000000000", 10) //nolint:gomnd
address := auth.From
genesisAlloc := map[common.Address]types.Account{
address: {
Balance: balance,
},
}
blockGasLimit := uint64(999999999999999999) //nolint:gomnd
client = simulated.NewBackend(genesisAlloc, simulated.WithBlockGasLimit(blockGasLimit))

gerAddr, _, gerContract, err = gerContractEVMChain.DeployPessimisticglobalexitrootnopush0(auth, client.Client(), auth.From)
if err != nil {
return
}
client.Commit()

_GLOBAL_EXIT_ROOT_SETTER_ROLE := common.HexToHash("0x7b95520991dfda409891be0afa2635b63540f92ee996fda0bf695a166e5c5176")
_, err = gerContract.GrantRole(auth, _GLOBAL_EXIT_ROOT_SETTER_ROLE, auth.From)
client.Commit()
hasRole, _ := gerContract.HasRole(&bind.CallOpts{Pending: false}, _GLOBAL_EXIT_ROOT_SETTER_ROLE, auth.From)
if !hasRole {
err = errors.New("failed to set role")
}
return
env := helpers.SetupAggoracleWithEVMChain(t)
runTest(t, env.GERL1Contract, env.AggOracleSender, env.L1Client, env.AuthL1)
}

func runTest(
Expand Down
55 changes: 41 additions & 14 deletions bridgesync/bridgesync.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,7 @@ const (
downloadBufferSize = 1000
)

var (
retryAfterErrorPeriod = time.Second * 10
maxRetryAttemptsAfterError = 5
)

type LocalBridgeSync struct {
type BridgeSync struct {
processor *processor
driver *sync.EVMDriver
}
Expand All @@ -35,8 +30,11 @@ func NewL1(
rd sync.ReorgDetector,
ethClient EthClienter,
initialBlock uint64,
) (*LocalBridgeSync, error) {
return new(
waitForNewBlocksPeriod time.Duration,
retryAfterErrorPeriod time.Duration,
maxRetryAttemptsAfterError int,
) (*BridgeSync, error) {
return newBridgeSync(
ctx,
dbPath,
bridge,
Expand All @@ -46,6 +44,9 @@ func NewL1(
ethClient,
initialBlock,
bridgeSyncL1,
waitForNewBlocksPeriod,
retryAfterErrorPeriod,
maxRetryAttemptsAfterError,
)
}

Expand All @@ -59,8 +60,11 @@ func NewL2(
rd sync.ReorgDetector,
ethClient EthClienter,
initialBlock uint64,
) (*LocalBridgeSync, error) {
return new(
waitForNewBlocksPeriod time.Duration,
retryAfterErrorPeriod time.Duration,
maxRetryAttemptsAfterError int,
) (*BridgeSync, error) {
return newBridgeSync(
ctx,
dbPath,
bridge,
Expand All @@ -70,10 +74,13 @@ func NewL2(
ethClient,
initialBlock,
bridgeSyncL2,
waitForNewBlocksPeriod,
retryAfterErrorPeriod,
maxRetryAttemptsAfterError,
)
}

func new(
func newBridgeSync(
ctx context.Context,
dbPath string,
bridge common.Address,
Expand All @@ -83,7 +90,10 @@ func new(
ethClient EthClienter,
initialBlock uint64,
l1OrL2ID string,
) (*LocalBridgeSync, error) {
waitForNewBlocksPeriod time.Duration,
retryAfterErrorPeriod time.Duration,
maxRetryAttemptsAfterError int,
) (*BridgeSync, error) {
processor, err := newProcessor(ctx, dbPath, l1OrL2ID)
if err != nil {
return nil, err
Expand All @@ -110,6 +120,7 @@ func new(
return nil, err
}
downloader, err := sync.NewEVMDownloader(
l1OrL2ID,
ethClient,
syncBlockChunkSize,
blockFinalityType,
Expand All @@ -126,13 +137,29 @@ func new(
if err != nil {
return nil, err
}
return &LocalBridgeSync{
return &BridgeSync{
processor: processor,
driver: driver,
}, nil
}

// Start starts the synchronization process
func (s *LocalBridgeSync) Start(ctx context.Context) {
func (s *BridgeSync) Start(ctx context.Context) {
s.driver.Sync(ctx)
}

func (s *BridgeSync) GetLastProcessedBlock(ctx context.Context) (uint64, error) {
return s.processor.GetLastProcessedBlock(ctx)
}

func (s *BridgeSync) GetBridgeIndexByRoot(ctx context.Context, root common.Hash) (uint32, error) {
return s.processor.exitTree.GetIndexByRoot(ctx, root)
}

func (s *BridgeSync) GetClaimsAndBridges(ctx context.Context, fromBlock, toBlock uint64) ([]Event, error) {
return s.processor.GetClaimsAndBridges(ctx, fromBlock, toBlock)
}

func (s *BridgeSync) GetProof(ctx context.Context, depositCount uint32, localExitRoot common.Hash) ([32]common.Hash, error) {
return s.processor.exitTree.GetProof(ctx, depositCount, localExitRoot)
}
27 changes: 27 additions & 0 deletions bridgesync/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package bridgesync

import (
"github.com/0xPolygon/cdk/config/types"
"github.com/ethereum/go-ethereum/common"
)

type Config struct {
// DBPath path of the DB
DBPath string `mapstructure:"DBPath"`
// BlockFinality indicates the status of the blocks that will be queried in order to sync
BlockFinality string `jsonschema:"enum=LatestBlock, enum=SafeBlock, enum=PendingBlock, enum=FinalizedBlock, enum=EarliestBlock" mapstructure:"BlockFinality"`
// InitialBlockNum is the first block that will be queried when starting the synchronization from scratch.
// It should be a number equal or bellow the creation of the bridge contract
InitialBlockNum uint64 `mapstructure:"InitialBlockNum"`
// BridgeAddr is the address of the bridge smart contract
BridgeAddr common.Address `mapstructure:"BridgeAddr"`
// SyncBlockChunkSize is the amount of blocks that will be queried to the client on each request
SyncBlockChunkSize uint64 `mapstructure:"SyncBlockChunkSize"`
// RetryAfterErrorPeriod is the time that will be waited when an unexpected error happens before retry
RetryAfterErrorPeriod types.Duration `mapstructure:"RetryAfterErrorPeriod"`
// MaxRetryAttemptsAfterError is the maximum number of consecutive attempts that will happen before panicing.
// Any number smaller than zero will be considered as unlimited retries
MaxRetryAttemptsAfterError int `mapstructure:"MaxRetryAttemptsAfterError"`
// WaitForNewBlocksPeriod time that will be waited when the synchronizer has reached the latest block
WaitForNewBlocksPeriod types.Duration `mapstructure:"WaitForNewBlocksPeriod"`
}
7 changes: 1 addition & 6 deletions bridgesync/downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package bridgesync
import (
"fmt"
"math/big"
"time"

"github.com/0xPolygon/cdk-contracts-tooling/contracts/etrog/polygonzkevmbridge"
"github.com/0xPolygon/cdk-contracts-tooling/contracts/etrog/polygonzkevmbridgev2"
Expand All @@ -15,10 +14,6 @@ import (
"github.com/ethereum/go-ethereum/crypto"
)

const (
waitForNewBlocksPeriod = time.Millisecond * 100
)

var (
bridgeEventSignature = crypto.Keccak256Hash([]byte("BridgeEvent(uint8,uint32,address,uint32,address,uint256,bytes,uint32)"))
claimEventSignature = crypto.Keccak256Hash([]byte("ClaimEvent(uint256,uint32,address,address,uint256)"))
Expand Down Expand Up @@ -82,7 +77,7 @@ func buildAppender(client EthClienter, bridge common.Address) (sync.LogAppenderM
return nil
}

appender[claimEventSignature] = func(b *sync.EVMBlock, l types.Log) error {
appender[claimEventSignaturePreEtrog] = func(b *sync.EVMBlock, l types.Log) error {
claim, err := bridgeContractV1.ParseClaimEvent(l)
if err != nil {
return fmt.Errorf(
Expand Down
Loading

0 comments on commit 3dff8af

Please sign in to comment.