github.com/ethereum-optimism/optimism@v1.7.2/op-node/cmd/batch_decoder/fetch/fetch.go (about)

     1  package fetch
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"log"
     8  	"math/big"
     9  	"os"
    10  	"path"
    11  	"sync/atomic"
    12  	"time"
    13  
    14  	"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
    15  	"github.com/ethereum/go-ethereum/common"
    16  	"github.com/ethereum/go-ethereum/core/types"
    17  	"github.com/ethereum/go-ethereum/ethclient"
    18  	"golang.org/x/sync/errgroup"
    19  )
    20  
    21  type TransactionWithMetadata struct {
    22  	TxIndex     uint64             `json:"tx_index"`
    23  	InboxAddr   common.Address     `json:"inbox_address"`
    24  	BlockNumber uint64             `json:"block_number"`
    25  	BlockHash   common.Hash        `json:"block_hash"`
    26  	BlockTime   uint64             `json:"block_time"`
    27  	ChainId     uint64             `json:"chain_id"`
    28  	Sender      common.Address     `json:"sender"`
    29  	ValidSender bool               `json:"valid_sender"`
    30  	Frames      []derive.Frame     `json:"frames"`
    31  	FrameErr    string             `json:"frame_parse_error"`
    32  	ValidFrames bool               `json:"valid_data"`
    33  	Tx          *types.Transaction `json:"tx"`
    34  }
    35  
    36  type Config struct {
    37  	Start, End         uint64
    38  	ChainID            *big.Int
    39  	BatchInbox         common.Address
    40  	BatchSenders       map[common.Address]struct{}
    41  	OutDirectory       string
    42  	ConcurrentRequests uint64
    43  }
    44  
    45  // Batches fetches & stores all transactions sent to the batch inbox address in
    46  // the given block range (inclusive to exclusive).
    47  // The transactions & metadata are written to the out directory.
    48  func Batches(client *ethclient.Client, config Config) (totalValid, totalInvalid uint64) {
    49  	if err := os.MkdirAll(config.OutDirectory, 0750); err != nil {
    50  		log.Fatal(err)
    51  	}
    52  	signer := types.LatestSignerForChainID(config.ChainID)
    53  	concurrentRequests := int(config.ConcurrentRequests)
    54  
    55  	g, ctx := errgroup.WithContext(context.Background())
    56  	g.SetLimit(concurrentRequests)
    57  
    58  	for i := config.Start; i < config.End; i++ {
    59  		if err := ctx.Err(); err != nil {
    60  			break
    61  		}
    62  		number := i
    63  		g.Go(func() error {
    64  			valid, invalid, err := fetchBatchesPerBlock(ctx, client, number, signer, config)
    65  			if err != nil {
    66  				return fmt.Errorf("error occurred while fetching block %d: %w", number, err)
    67  			}
    68  			atomic.AddUint64(&totalValid, valid)
    69  			atomic.AddUint64(&totalInvalid, invalid)
    70  			return nil
    71  		})
    72  	}
    73  	if err := g.Wait(); err != nil {
    74  		log.Fatal(err)
    75  	}
    76  	return
    77  }
    78  
    79  // fetchBatchesPerBlock gets a block & the parses all of the transactions in the block.
    80  func fetchBatchesPerBlock(ctx context.Context, client *ethclient.Client, number uint64, signer types.Signer, config Config) (uint64, uint64, error) {
    81  	validBatchCount := uint64(0)
    82  	invalidBatchCount := uint64(0)
    83  	ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
    84  	defer cancel()
    85  	block, err := client.BlockByNumber(ctx, new(big.Int).SetUint64(number))
    86  	if err != nil {
    87  		return 0, 0, err
    88  	}
    89  	fmt.Println("Fetched block: ", number)
    90  	for i, tx := range block.Transactions() {
    91  		if tx.To() != nil && *tx.To() == config.BatchInbox {
    92  			sender, err := signer.Sender(tx)
    93  			if err != nil {
    94  				return 0, 0, err
    95  			}
    96  			validSender := true
    97  			if _, ok := config.BatchSenders[sender]; !ok {
    98  				fmt.Printf("Found a transaction (%s) from an invalid sender (%s)\n", tx.Hash().String(), sender.String())
    99  				invalidBatchCount += 1
   100  				validSender = false
   101  			}
   102  
   103  			validFrames := true
   104  			frameError := ""
   105  			frames, err := derive.ParseFrames(tx.Data())
   106  			if err != nil {
   107  				fmt.Printf("Found a transaction (%s) with invalid data: %v\n", tx.Hash().String(), err)
   108  				validFrames = false
   109  				frameError = err.Error()
   110  			}
   111  
   112  			if validSender && validFrames {
   113  				validBatchCount += 1
   114  			} else {
   115  				invalidBatchCount += 1
   116  			}
   117  
   118  			txm := &TransactionWithMetadata{
   119  				Tx:          tx,
   120  				Sender:      sender,
   121  				ValidSender: validSender,
   122  				TxIndex:     uint64(i),
   123  				BlockNumber: block.NumberU64(),
   124  				BlockHash:   block.Hash(),
   125  				BlockTime:   block.Time(),
   126  				ChainId:     config.ChainID.Uint64(),
   127  				InboxAddr:   config.BatchInbox,
   128  				Frames:      frames,
   129  				FrameErr:    frameError,
   130  				ValidFrames: validFrames,
   131  			}
   132  			filename := path.Join(config.OutDirectory, fmt.Sprintf("%s.json", tx.Hash().String()))
   133  			file, err := os.Create(filename)
   134  			if err != nil {
   135  				return 0, 0, err
   136  			}
   137  			defer file.Close()
   138  			enc := json.NewEncoder(file)
   139  			if err := enc.Encode(txm); err != nil {
   140  				return 0, 0, err
   141  			}
   142  		}
   143  	}
   144  	return validBatchCount, invalidBatchCount, nil
   145  }