Skip to content

Commit

Permalink
refactored sequencer and added tests (0xPolygonHermez#521)
Browse files Browse the repository at this point in the history
* refactored sequencer and added tests

* fixing lint errors

* fixing lint errors

* fixing sequencer_internal_test.go

* fixed pr comments

* changed oversizeddata error back to error checking from string

* deleted redundant error string

* refactored setTxsToPendingState function

* refactored setTxsToPendingState function
  • Loading branch information
Mikelle authored Mar 28, 2022
1 parent 4617366 commit 48892d7
Show file tree
Hide file tree
Showing 4 changed files with 648 additions and 43 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ install-git-hooks: ## Moves hook files to the .git/hooks directory
generate-mocks: ## Generates mocks for the tests, using mockery tool
mockery --name=etherman --dir=sequencer/strategy/txprofitabilitychecker --output=sequencer/strategy/txprofitabilitychecker --outpkg=txprofitabilitychecker_test --filename=etherman-mock_test.go
mockery --name=batchProcessor --dir=sequencer/strategy/txselector --output=sequencer/strategy/txselector --outpkg=txselector_test --filename=batchprocessor-mock_test.go
mockery --name=etherman --dir=sequencer --output=sequencer --outpkg=sequencer --structname=ethermanMock --filename=etherman-mock_test.go

.PHONY: generate-code-from-proto
generate-code-from-proto: ## Generates code from proto files
Expand Down
127 changes: 127 additions & 0 deletions sequencer/etherman-mock_test.go

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

130 changes: 87 additions & 43 deletions sequencer/sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/hermeznetwork/hermez-core/encoding"
"github.com/hermeznetwork/hermez-core/log"
"github.com/hermeznetwork/hermez-core/pool"
Expand Down Expand Up @@ -119,93 +120,136 @@ func (s *Sequencer) Stop() {

func (s *Sequencer) tryProposeBatch() {
// 1. Wait for synchronizer to sync last batch
if !s.isSynced() {
return
}
// 2. get pending txs from the pool
txs, ok := s.getPendingTxs()
if !ok {
return
}
// 3. Run selection
selectedTxs, selectedTxsHashes, ok := s.selectTxs(txs)
if !ok {
return
}
// 4. Is selection profitable?
// check is it profitable to send selection
var isSent bool
for !isSent {
selectedTxs, selectedTxsHashes, isSent = s.sendBatchToEthereum(selectedTxs, selectedTxsHashes)
if len(selectedTxs) == 0 {
return
}
}
// NO: discard selection and wait for the new batch
}

func (s *Sequencer) isSynced() bool {
lastSyncedBatchNum, err := s.State.GetLastBatchNumber(s.ctx)
if err != nil {
log.Errorf("failed to get last synced batch, err: %v", err)
return
return false
}
lastEthBatchNum, err := s.State.GetLastBatchNumberSeenOnEthereum(s.ctx)
if err != nil {
log.Errorf("failed to get last eth batch, err: %v", err)
return
return false
}
if lastSyncedBatchNum+s.cfg.SyncedBlockDif < lastEthBatchNum {
log.Infof("waiting for the state to be synced, lastSyncedBatchNum: %d, lastEthBatchNum: %d", lastSyncedBatchNum, lastEthBatchNum)
return
return false
}
return true
}

// 2. get pending txs from the pool
func (s *Sequencer) getPendingTxs() ([]pool.Transaction, bool) {
txs, err := s.Pool.GetPendingTxs(s.ctx, amountOfPendingTxsRequested)
if err != nil {
log.Errorf("failed to get pending txs, err: %v", err)
return
return nil, false
}

if len(txs) == 0 {
log.Infof("transactions pool is empty, waiting for the new txs...")
return
return nil, false
}

// 3. Run selection
// init batch processor
return txs, true
}

func (s *Sequencer) selectTxs(txs []pool.Transaction) ([]*types.Transaction, []common.Hash, bool) {
lastVirtualBatch, err := s.State.GetLastBatch(s.ctx, true)
if err != nil {
log.Errorf("failed to get last batch from the state, err: %v", err)
return
return nil, nil, false
}
// init batch processor
bp, err := s.State.NewBatchProcessor(s.ctx, s.Address, lastVirtualBatch.Header.Root[:])
if err != nil {
log.Errorf("failed to create new batch processor, err: %v", err)
return
return nil, nil, false
}

// select txs
selectedTxs, selectedTxsHashes, invalidTxsHashes, err := s.TxSelector.SelectTxs(s.ctx, bp, txs, s.Address)
if err != nil {
log.Errorf("failed to select txs, err: %v", err)
return
return nil, nil, false
}

if err = s.Pool.UpdateTxsState(s.ctx, invalidTxsHashes, pool.TxStateInvalid); err != nil {
log.Errorf("failed to update txs state to invalid, err: %v", err)
return
return nil, nil, false
}
return selectedTxs, selectedTxsHashes, true
}

// 4. Is selection profitable?
// check is it profitable to send selection
var isSent bool
for !isSent {
isProfitable, aggregatorReward, err := s.TxProfitabilityChecker.IsProfitable(s.ctx, selectedTxs)
func isDataForEthTxTooBig(err error) bool {
if strings.Contains(err.Error(), errGasRequiredExceedsAllowance) ||
errors.As(err, &core.ErrOversizedData) ||
strings.Contains(err.Error(), errContentLengthTooLarge) {
return true
}
return false
}

func (s *Sequencer) sendBatchToEthereum(selectedTxs []*types.Transaction, selectedTxsHashes []common.Hash) ([]*types.Transaction, []common.Hash, bool) {
isProfitable, aggregatorReward, err := s.TxProfitabilityChecker.IsProfitable(s.ctx, selectedTxs)
if err != nil {
log.Errorf("failed to check that txs are profitable or not, err: %v", err)
return nil, nil, false
}
if isProfitable && len(selectedTxs) > 0 {
// YES: send selection to Ethereum
sendBatchTx, err := s.EthMan.SendBatch(s.ctx, selectedTxs, aggregatorReward)
if err != nil {
log.Errorf("failed to check that txs are profitable or not, err: %v", err)
return
}
if isProfitable && len(selectedTxs) > 0 {
// YES: send selection to Ethereum
sendBatchTx, err := s.EthMan.SendBatch(s.ctx, selectedTxs, aggregatorReward)
if err != nil {
if strings.Contains(err.Error(), errGasRequiredExceedsAllowance) ||
errors.Is(err, core.ErrOversizedData) ||
strings.Contains(err.Error(), errContentLengthTooLarge) {
cutSelectedTxs := (len(selectedTxs) - 1) * percentageToCutSelectedTxs / fullPercentage
selectedTxs = selectedTxs[:cutSelectedTxs]
selectedTxsHashes = selectedTxsHashes[:cutSelectedTxs]
continue
}
log.Errorf("failed to send batch proposal to ethereum, err: %v", err)
return
if isDataForEthTxTooBig(err) {
selectedTxs, selectedTxsHashes = cutSelectedTxs(selectedTxs, selectedTxsHashes)
return selectedTxs, selectedTxsHashes, false
}
// update txs in the pool as selected
err = s.Pool.UpdateTxsState(s.ctx, selectedTxsHashes, pool.TxStateSelected)
if err != nil {
log.Warnf("failed to update txs state to selected, err: %v", err)
}
isSent = true
log.Infof("finished updating selected transactions state in the pool")
log.Infof("batch proposal sent successfully: %s", sendBatchTx.Hash().Hex())
log.Errorf("failed to send batch proposal to ethereum, err: %v", err)
return nil, nil, false
}
// update txs in the pool as selected
err = s.Pool.UpdateTxsState(s.ctx, selectedTxsHashes, pool.TxStateSelected)
if err != nil {
// it's fatal here, bcs txs are selected and sent to ethereum, but txs were not updated in the local db.
// probably txs should be updated manually. If sequencer doesn't fail here, those txs will be sent again
// and sequencer will lose tokens
log.Fatalf("failed to update txs state to selected, selectedTxsHashes: %v, err: %v", selectedTxsHashes, err)
}
log.Infof("finished updating selected transactions state in the pool")
log.Infof("batch proposal sent successfully: %s", sendBatchTx.Hash().Hex())
}
// NO: discard selection and wait for the new batch
return nil, nil, true
}

func cutSelectedTxs(selectedTxs []*types.Transaction, selectedTxsHashes []common.Hash) ([]*types.Transaction, []common.Hash) {
cutSelectedTxs := len(selectedTxs) * percentageToCutSelectedTxs / fullPercentage
selectedTxs = selectedTxs[:cutSelectedTxs]
selectedTxsHashes = selectedTxsHashes[:cutSelectedTxs]
return selectedTxs, selectedTxsHashes
}

func getChainID(ctx context.Context, st stateInterface, ethMan etherman, seqAddress common.Address) (uint64, error) {
Expand Down
Loading

0 comments on commit 48892d7

Please sign in to comment.