Update coordinator to work better under real net

- cli / node
    - Update handler of SIGINT so that after 3 SIGINTs, the process terminates
      unconditionally
- coordinator
    - Store stats without pointer
    - In all functions that send a variable via channel, check for context done
      to avoid deadlock (due to no process reading from the channel, which has
      no queue) when the node is stopped.
    - Abstract `canForge` so that it can be used outside of the `Coordinator`
    - In `canForge` check the blockNumber in current and next slot.
    - Update tests due to smart contract changes in slot handling, and minimum
      bid defaults
    - TxManager
        - Add consts, vars and stats to allow evaluating `canForge`
        - Add `canForge` method (not used yet)
        - Store batch and nonces status (last success and last pending)
        - Track nonces internally instead of relying on the ethereum node (this
          is required to work with ganache when there are pending txs)
        - Handle the (common) case of the receipt not being found after the tx
          is sent.
        - Don't start the main loop until we get an initial messae fo the stats
          and vars (so that in the loop the stats and vars are set to
          synchronizer values)
- eth / ethereum client
    - Add necessary methods to create the auth object for transactions manually
      so that we can set the nonce, gas price, gas limit, etc manually
    - Update `RollupForgeBatch` to take an auth object as input (so that the
      coordinator can set parameters manually)
- synchronizer
    - In stats, add `NextSlot`
This commit is contained in:
Eduard S
2021-01-15 19:37:39 +01:00
parent 168432c559
commit 70482605c4
20 changed files with 664 additions and 308 deletions

View File

@@ -6,6 +6,7 @@ import (
"math/big"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
ethKeystore "github.com/ethereum/go-ethereum/accounts/keystore"
@@ -35,6 +36,12 @@ type EthereumInterface interface {
EthERC20Consts(ethCommon.Address) (*ERC20Consts, error)
EthChainID() (*big.Int, error)
EthPendingNonceAt(ctx context.Context, account ethCommon.Address) (uint64, error)
EthNonceAt(ctx context.Context, account ethCommon.Address, blockNumber *big.Int) (uint64, error)
EthSuggestGasPrice(ctx context.Context) (*big.Int, error)
EthKeyStore() *ethKeystore.KeyStore
EthCall(ctx context.Context, tx *types.Transaction, blockNum *big.Int) ([]byte, error)
}
var (
@@ -118,6 +125,43 @@ func (c *EthereumClient) EthAddress() (*ethCommon.Address, error) {
return &c.account.Address, nil
}
// EthSuggestGasPrice retrieves the currently suggested gas price to allow a
// timely execution of a transaction.
func (c *EthereumClient) EthSuggestGasPrice(ctx context.Context) (*big.Int, error) {
return c.client.SuggestGasPrice(ctx)
}
// EthKeyStore returns the keystore in the EthereumClient
func (c *EthereumClient) EthKeyStore() *ethKeystore.KeyStore {
return c.ks
}
// NewAuth builds a new auth object to make a transaction
func (c *EthereumClient) NewAuth() (*bind.TransactOpts, error) {
if c.account == nil {
return nil, tracerr.Wrap(ErrAccountNil)
}
gasPrice, err := c.client.SuggestGasPrice(context.Background())
if err != nil {
return nil, tracerr.Wrap(err)
}
inc := new(big.Int).Set(gasPrice)
inc.Div(inc, new(big.Int).SetUint64(c.config.GasPriceDiv))
gasPrice.Add(gasPrice, inc)
log.Debugw("Transaction metadata", "gasPrice", gasPrice)
auth, err := bind.NewKeyStoreTransactorWithChainID(c.ks, *c.account, c.chainID)
if err != nil {
return nil, tracerr.Wrap(err)
}
auth.Value = big.NewInt(0) // in wei
auth.GasLimit = c.config.CallGasLimit
auth.GasPrice = gasPrice
return auth, nil
}
// CallAuth performs a Smart Contract method call that requires authorization.
// This call requires a valid account with Ether that can be spend during the
// call.
@@ -250,3 +294,35 @@ func newCallOpts() *bind.CallOpts {
From: ethCommon.HexToAddress("0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"),
}
}
// EthPendingNonceAt returns the account nonce of the given account in the pending
// state. This is the nonce that should be used for the next transaction.
func (c *EthereumClient) EthPendingNonceAt(ctx context.Context,
account ethCommon.Address) (uint64, error) {
return c.client.PendingNonceAt(ctx, account)
}
// EthNonceAt returns the account nonce of the given account. The block number can
// be nil, in which case the nonce is taken from the latest known block.
func (c *EthereumClient) EthNonceAt(ctx context.Context,
account ethCommon.Address, blockNumber *big.Int) (uint64, error) {
return c.client.NonceAt(ctx, account, blockNumber)
}
// EthCall runs the transaction as a call (without paying) in the local node at
// blockNum.
func (c *EthereumClient) EthCall(ctx context.Context, tx *types.Transaction,
blockNum *big.Int) ([]byte, error) {
if c.account == nil {
return nil, tracerr.Wrap(ErrAccountNil)
}
msg := ethereum.CallMsg{
From: c.account.Address,
To: tx.To(),
Gas: tx.Gas(),
GasPrice: tx.GasPrice(),
Value: tx.Value(),
Data: tx.Data(),
}
return c.client.CallContract(ctx, msg, blockNum)
}

View File

@@ -242,7 +242,7 @@ type RollupInterface interface {
// Public Functions
RollupForgeBatch(*RollupForgeBatchArgs) (*types.Transaction, error)
RollupForgeBatch(*RollupForgeBatchArgs, *bind.TransactOpts) (*types.Transaction, error)
RollupAddToken(tokenAddress ethCommon.Address, feeAddToken, deadline *big.Int) (*types.Transaction, error)
RollupWithdrawMerkleProof(babyPubKey babyjub.PublicKeyComp, tokenID uint32, numExitRoot, idx int64, amount *big.Int, siblings []*big.Int, instantWithdraw bool) (*types.Transaction, error)
@@ -323,70 +323,77 @@ func NewRollupClient(client *EthereumClient, address ethCommon.Address, tokenHEZ
}
// RollupForgeBatch is the interface to call the smart contract function
func (c *RollupClient) RollupForgeBatch(args *RollupForgeBatchArgs) (tx *types.Transaction, err error) {
if tx, err = c.client.CallAuth(
1000000, //nolint:gomnd
func(ec *ethclient.Client, auth *bind.TransactOpts) (*types.Transaction, error) {
nLevels := c.consts.Verifiers[args.VerifierIdx].NLevels
lenBytes := nLevels / 8 //nolint:gomnd
newLastIdx := big.NewInt(int64(args.NewLastIdx))
// L1CoordinatorBytes
var l1CoordinatorBytes []byte
for i := 0; i < len(args.L1CoordinatorTxs); i++ {
l1 := args.L1CoordinatorTxs[i]
bytesl1, err := l1.BytesCoordinatorTx(args.L1CoordinatorTxsAuths[i])
if err != nil {
return nil, tracerr.Wrap(err)
}
l1CoordinatorBytes = append(l1CoordinatorBytes, bytesl1[:]...)
}
// L1L2TxData
var l1l2TxData []byte
for i := 0; i < len(args.L1UserTxs); i++ {
l1User := args.L1UserTxs[i]
bytesl1User, err := l1User.BytesDataAvailability(uint32(nLevels))
if err != nil {
return nil, tracerr.Wrap(err)
}
l1l2TxData = append(l1l2TxData, bytesl1User[:]...)
}
for i := 0; i < len(args.L1CoordinatorTxs); i++ {
l1Coord := args.L1CoordinatorTxs[i]
bytesl1Coord, err := l1Coord.BytesDataAvailability(uint32(nLevels))
if err != nil {
return nil, tracerr.Wrap(err)
}
l1l2TxData = append(l1l2TxData, bytesl1Coord[:]...)
}
for i := 0; i < len(args.L2TxsData); i++ {
l2 := args.L2TxsData[i]
bytesl2, err := l2.BytesDataAvailability(uint32(nLevels))
if err != nil {
return nil, tracerr.Wrap(err)
}
l1l2TxData = append(l1l2TxData, bytesl2[:]...)
}
// FeeIdxCoordinator
var feeIdxCoordinator []byte
if len(args.FeeIdxCoordinator) > common.RollupConstMaxFeeIdxCoordinator {
return nil, tracerr.Wrap(fmt.Errorf("len(args.FeeIdxCoordinator) > %v",
common.RollupConstMaxFeeIdxCoordinator))
}
for i := 0; i < common.RollupConstMaxFeeIdxCoordinator; i++ {
feeIdx := common.Idx(0)
if i < len(args.FeeIdxCoordinator) {
feeIdx = args.FeeIdxCoordinator[i]
}
bytesFeeIdx, err := feeIdx.Bytes()
if err != nil {
return nil, tracerr.Wrap(err)
}
feeIdxCoordinator = append(feeIdxCoordinator, bytesFeeIdx[len(bytesFeeIdx)-int(lenBytes):]...)
}
return c.hermez.ForgeBatch(auth, newLastIdx, args.NewStRoot, args.NewExitRoot, l1CoordinatorBytes, l1l2TxData, feeIdxCoordinator, args.VerifierIdx, args.L1Batch, args.ProofA, args.ProofB, args.ProofC)
},
); err != nil {
return nil, tracerr.Wrap(fmt.Errorf("Failed forge batch: %w", err))
func (c *RollupClient) RollupForgeBatch(args *RollupForgeBatchArgs, auth *bind.TransactOpts) (tx *types.Transaction, err error) {
if auth == nil {
auth, err := c.client.NewAuth()
if err != nil {
return nil, err
}
auth.GasLimit = 1000000
}
nLevels := c.consts.Verifiers[args.VerifierIdx].NLevels
lenBytes := nLevels / 8 //nolint:gomnd
newLastIdx := big.NewInt(int64(args.NewLastIdx))
// L1CoordinatorBytes
var l1CoordinatorBytes []byte
for i := 0; i < len(args.L1CoordinatorTxs); i++ {
l1 := args.L1CoordinatorTxs[i]
bytesl1, err := l1.BytesCoordinatorTx(args.L1CoordinatorTxsAuths[i])
if err != nil {
return nil, tracerr.Wrap(err)
}
l1CoordinatorBytes = append(l1CoordinatorBytes, bytesl1[:]...)
}
// L1L2TxData
var l1l2TxData []byte
for i := 0; i < len(args.L1UserTxs); i++ {
l1User := args.L1UserTxs[i]
bytesl1User, err := l1User.BytesDataAvailability(uint32(nLevels))
if err != nil {
return nil, tracerr.Wrap(err)
}
l1l2TxData = append(l1l2TxData, bytesl1User[:]...)
}
for i := 0; i < len(args.L1CoordinatorTxs); i++ {
l1Coord := args.L1CoordinatorTxs[i]
bytesl1Coord, err := l1Coord.BytesDataAvailability(uint32(nLevels))
if err != nil {
return nil, tracerr.Wrap(err)
}
l1l2TxData = append(l1l2TxData, bytesl1Coord[:]...)
}
for i := 0; i < len(args.L2TxsData); i++ {
l2 := args.L2TxsData[i]
bytesl2, err := l2.BytesDataAvailability(uint32(nLevels))
if err != nil {
return nil, tracerr.Wrap(err)
}
l1l2TxData = append(l1l2TxData, bytesl2[:]...)
}
// FeeIdxCoordinator
var feeIdxCoordinator []byte
if len(args.FeeIdxCoordinator) > common.RollupConstMaxFeeIdxCoordinator {
return nil, tracerr.Wrap(fmt.Errorf("len(args.FeeIdxCoordinator) > %v",
common.RollupConstMaxFeeIdxCoordinator))
}
for i := 0; i < common.RollupConstMaxFeeIdxCoordinator; i++ {
feeIdx := common.Idx(0)
if i < len(args.FeeIdxCoordinator) {
feeIdx = args.FeeIdxCoordinator[i]
}
bytesFeeIdx, err := feeIdx.Bytes()
if err != nil {
return nil, tracerr.Wrap(err)
}
feeIdxCoordinator = append(feeIdxCoordinator,
bytesFeeIdx[len(bytesFeeIdx)-int(lenBytes):]...)
}
tx, err = c.hermez.ForgeBatch(auth, newLastIdx, args.NewStRoot, args.NewExitRoot,
l1CoordinatorBytes, l1l2TxData, feeIdxCoordinator, args.VerifierIdx, args.L1Batch,
args.ProofA, args.ProofB, args.ProofC)
if err != nil {
return nil, tracerr.Wrap(fmt.Errorf("Failed Hermez.ForgeBatch: %w", err))
}
return tx, nil
}

View File

@@ -169,7 +169,9 @@ func TestRollupForgeBatch(t *testing.T) {
args.ProofC[1] = big.NewInt(0)
argsForge = args
_, err = rollupClient.RollupForgeBatch(argsForge)
auth, err := rollupClient.client.NewAuth()
require.NoError(t, err)
_, err = rollupClient.RollupForgeBatch(argsForge, auth)
require.NoError(t, err)
currentBlockNum, err = rollupClient.client.EthLastBlock()
@@ -818,7 +820,9 @@ func TestRollupL1UserTxERC20PermitForceExit(t *testing.T) {
func TestRollupForgeBatch2(t *testing.T) {
// Forge Batch 2
_, err := rollupClient.RollupForgeBatch(argsForge)
auth, err := rollupClient.client.NewAuth()
require.NoError(t, err)
_, err = rollupClient.RollupForgeBatch(argsForge, auth)
require.NoError(t, err)
currentBlockNum, err := rollupClient.client.EthLastBlock()
require.NoError(t, err)
@@ -871,7 +875,9 @@ func TestRollupForgeBatch2(t *testing.T) {
argsForge = args
_, err = rollupClient.RollupForgeBatch(argsForge)
auth, err = rollupClient.client.NewAuth()
require.NoError(t, err)
_, err = rollupClient.RollupForgeBatch(argsForge, auth)
require.NoError(t, err)
currentBlockNum, err = rollupClient.client.EthLastBlock()