-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
dabcf40
commit 2c6db44
Showing
9 changed files
with
410 additions
and
33 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package chaingersender | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"math/big" | ||
"time" | ||
|
||
"github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/pessimisticglobalexitroot" | ||
"github.com/0xPolygon/cdk/log" | ||
"github.com/0xPolygonHermez/zkevm-ethtx-manager/ethtxmanager" | ||
"github.com/ethereum/go-ethereum" | ||
"github.com/ethereum/go-ethereum/accounts/abi/bind" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/core/types" | ||
) | ||
|
||
var ( | ||
waitPeriodMonitorTx = time.Second * 5 | ||
) | ||
|
||
type EthClienter interface { | ||
ethereum.LogFilterer | ||
ethereum.BlockNumberReader | ||
ethereum.ChainReader | ||
bind.ContractBackend | ||
} | ||
|
||
type EthTxManager interface { | ||
Remove(ctx context.Context, id common.Hash) error | ||
ResultsByStatus(ctx context.Context, statuses []ethtxmanager.MonitoredTxStatus) ([]ethtxmanager.MonitoredTxResult, error) | ||
Result(ctx context.Context, id common.Hash) (ethtxmanager.MonitoredTxResult, error) | ||
Add(ctx context.Context, to *common.Address, forcedNonce *uint64, value *big.Int, data []byte, gasOffset uint64, sidecar *types.BlobTxSidecar) (common.Hash, error) | ||
} | ||
|
||
type EVMChainGERSender struct { | ||
gerContract *pessimisticglobalexitroot.Pessimisticglobalexitroot | ||
gerAddr common.Address | ||
sender common.Address | ||
client EthClienter | ||
ethTxMan EthTxManager | ||
gasOffset uint64 | ||
} | ||
|
||
func NewEVMChainGERSender( | ||
globalExitRoot, sender common.Address, | ||
client EthClienter, | ||
ethTxMan EthTxManager, | ||
gasOffset uint64, | ||
) (*EVMChainGERSender, error) { | ||
gerContract, err := pessimisticglobalexitroot.NewPessimisticglobalexitroot(globalExitRoot, client) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &EVMChainGERSender{ | ||
gerContract: gerContract, | ||
gerAddr: globalExitRoot, | ||
sender: sender, | ||
client: client, | ||
ethTxMan: ethTxMan, | ||
gasOffset: gasOffset, | ||
}, nil | ||
} | ||
|
||
func (c *EVMChainGERSender) IsGERAlreadyInjected(ger common.Hash) (bool, error) { | ||
timestamp, err := c.gerContract.GlobalExitRootMap(&bind.CallOpts{Pending: false}, ger) | ||
if err != nil { | ||
return false, err | ||
} | ||
return timestamp.Cmp(big.NewInt(0)) == 0, nil | ||
} | ||
|
||
func (c *EVMChainGERSender) UpdateGERWaitUntilMined(ctx context.Context, ger common.Hash) error { | ||
opts := &bind.TransactOpts{ | ||
From: c.sender, | ||
// Hardcode dummy values to avoid calls to the client, | ||
// ethtxmanager is going to handle that | ||
NoSend: true, | ||
Nonce: big.NewInt(1), | ||
GasLimit: 1, | ||
GasPrice: big.NewInt(1), | ||
} | ||
tx, err := c.gerContract.UpdateGlobalExitRoot(opts, ger) | ||
if err != nil { | ||
return err | ||
} | ||
id, err := c.ethTxMan.Add(ctx, &c.gerAddr, nil, big.NewInt(0), tx.Data(), c.gasOffset, nil) | ||
if err != nil { | ||
return err | ||
} | ||
for { | ||
time.Sleep(waitPeriodMonitorTx) | ||
res, err := c.ethTxMan.Result(ctx, id) | ||
if err != nil { | ||
log.Error("error calling ethTxMan.Result: ", err) | ||
} | ||
switch res.Status { | ||
case ethtxmanager.MonitoredTxStatusCreated: | ||
continue | ||
case ethtxmanager.MonitoredTxStatusSent: | ||
continue | ||
case ethtxmanager.MonitoredTxStatusFailed: | ||
return fmt.Errorf("tx %s failed", res.ID) | ||
case ethtxmanager.MonitoredTxStatusMined: | ||
return nil | ||
case ethtxmanager.MonitoredTxStatusSafe: | ||
return nil | ||
case ethtxmanager.MonitoredTxStatusFinalized: | ||
return nil | ||
default: | ||
log.Error("unexpected tx status: ", res.Status) | ||
} | ||
} | ||
} |
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,147 @@ | ||
package aggoracle_test | ||
|
||
import ( | ||
"context" | ||
"math/big" | ||
"strconv" | ||
"testing" | ||
"time" | ||
|
||
"github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/globalexitrootnopush0" | ||
"github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/pessimisticglobalexitroot" | ||
"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/reorgdetector" | ||
"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" | ||
"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) | ||
require.NoError(t, err) | ||
oracle.Start(ctx) | ||
|
||
runTest(t, gerL1Contract, sender, l1Client, authL1) | ||
} | ||
|
||
func commonSetup(t *testing.T) ( | ||
*simulated.Backend, | ||
*l1infotreesync.L1InfoTreeSync, | ||
*globalexitrootnopush0.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 | ||
rdm := NewReorgDetectorMock(t) | ||
rdm.On("Subscribe", mock.Anything).Return(&reorgdetector.Subscription{}) | ||
rdm.On("AddBlockToTrack", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) | ||
reorg, err := reorgdetector.New(ctx) | ||
require.NoError(t, err) | ||
// Syncer | ||
dbPath := t.TempDir() | ||
syncer, err := l1infotreesync.New(ctx, dbPath, gerL1Addr, 10, etherman.LatestBlock, reorg, l1Client.Client(), 32) | ||
require.NoError(t, err) | ||
syncer.Sync(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) | ||
sender, err := chaingersender.NewEVMChainGERSender(gerL2Addr, authL2.From, l2Client.Client(), nil, 0) | ||
require.NoError(t, err) | ||
|
||
return sender | ||
} | ||
|
||
func newSimulatedL1(auth *bind.TransactOpts) ( | ||
client *simulated.Backend, | ||
gerAddr common.Address, | ||
gerContract *globalexitrootnopush0.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 = globalexitrootnopush0.DeployGlobalexitrootnopush0(auth, client.Client(), auth.From, auth.From) | ||
|
||
client.Commit() | ||
return | ||
} | ||
|
||
func newSimulatedEVMAggSovereignChain(auth *bind.TransactOpts) ( | ||
client *simulated.Backend, | ||
gerAddr common.Address, | ||
gerContract *pessimisticglobalexitroot.Pessimisticglobalexitroot, | ||
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 = pessimisticglobalexitroot.DeployPessimisticglobalexitroot(auth, client.Client(), auth.From) | ||
|
||
client.Commit() | ||
return | ||
} | ||
|
||
func runTest( | ||
t *testing.T, | ||
gerL1Contract *globalexitrootnopush0.Globalexitrootnopush0, | ||
sender aggoracle.ChainSender, | ||
l1Client *simulated.Backend, | ||
authL1 *bind.TransactOpts, | ||
) { | ||
for i := 0; i < 10; i++ { | ||
_, err := gerL1Contract.UpdateExitRoot(authL1, common.HexToHash(strconv.Itoa(i))) | ||
require.NoError(t, err) | ||
l1Client.Commit() | ||
time.Sleep(time.Second * 30) | ||
expectedGER, err := gerL1Contract.GetLastGlobalExitRoot(&bind.CallOpts{Pending: false}) | ||
require.NoError(t, err) | ||
isInjected, err := sender.IsGERAlreadyInjected(expectedGER) | ||
require.NoError(t, err) | ||
require.True(t, isInjected) | ||
} | ||
} | ||
|
||
type ReorgDetector interface { | ||
Subscribe(id string) *reorgdetector.Subscription | ||
AddBlockToTrack(ctx context.Context, id string, blockNum uint64, blockHash common.Hash) error | ||
} |
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,100 @@ | ||
package aggoracle | ||
|
||
import ( | ||
"context" | ||
"math/big" | ||
"time" | ||
|
||
"github.com/0xPolygon/cdk/etherman" | ||
"github.com/0xPolygon/cdk/l1infotreesync" | ||
"github.com/0xPolygon/cdk/log" | ||
"github.com/ethereum/go-ethereum" | ||
"github.com/ethereum/go-ethereum/accounts/abi/bind" | ||
"github.com/ethereum/go-ethereum/common" | ||
) | ||
|
||
var ( | ||
waitPeriodNextGER = time.Second * 30 | ||
) | ||
|
||
type EthClienter interface { | ||
ethereum.LogFilterer | ||
ethereum.BlockNumberReader | ||
ethereum.ChainReader | ||
bind.ContractBackend | ||
} | ||
|
||
type L1InfoTreer interface { | ||
GetLatestInfoUntilBlock(ctx context.Context, blockNum uint64) (*l1infotreesync.L1InfoTreeLeaf, error) | ||
} | ||
|
||
type ChainSender interface { | ||
IsGERAlreadyInjected(ger common.Hash) (bool, error) | ||
UpdateGERWaitUntilMined(ctx context.Context, ger common.Hash) error | ||
} | ||
|
||
type AggOracle struct { | ||
ticker *time.Ticker | ||
l1Client EthClienter | ||
l1Info L1InfoTreer | ||
chainSender ChainSender | ||
blockFinality *big.Int | ||
} | ||
|
||
func New( | ||
chainSender ChainSender, | ||
l1Client EthClienter, | ||
l1InfoTreeSyncer L1InfoTreer, | ||
blockFinalityType etherman.BlockNumberFinality, | ||
) (*AggOracle, error) { | ||
ticker := time.NewTicker(waitPeriodNextGER) | ||
finality, err := blockFinalityType.ToBlockNum() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &AggOracle{ | ||
ticker: ticker, | ||
l1Client: l1Client, | ||
l1Info: l1InfoTreeSyncer, | ||
chainSender: chainSender, | ||
blockFinality: finality, | ||
}, nil | ||
} | ||
|
||
func (a *AggOracle) Start(ctx context.Context) { | ||
for { | ||
select { | ||
case <-a.ticker.C: | ||
gerToInject, err := a.getLastFinalisedGER(ctx) | ||
if err != nil { | ||
log.Error("error calling getLastFinalisedGER: ", err) | ||
continue | ||
} | ||
if alreadyInjectd, err := a.chainSender.IsGERAlreadyInjected(gerToInject); err != nil { | ||
log.Error("error calling isGERAlreadyInjected: ", err) | ||
continue | ||
} else if alreadyInjectd { | ||
log.Debugf("GER %s already injected", gerToInject.Hex()) | ||
continue | ||
} | ||
if err := a.chainSender.UpdateGERWaitUntilMined(ctx, gerToInject); err != nil { | ||
log.Error("error calling updateGERWaitUntilMined: ", err) | ||
continue | ||
} | ||
case <-ctx.Done(): | ||
return | ||
} | ||
} | ||
} | ||
|
||
func (a *AggOracle) getLastFinalisedGER(ctx context.Context) (common.Hash, error) { | ||
header, err := a.l1Client.HeaderByNumber(ctx, a.blockFinality) | ||
if err != nil { | ||
return common.Hash{}, err | ||
} | ||
info, err := a.l1Info.GetLatestInfoUntilBlock(ctx, header.Number.Uint64()) | ||
if err != nil { | ||
return common.Hash{}, err | ||
} | ||
return info.GlobalExitRoot, 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
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.