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

     1  package reassemble
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"log"
     8  	"math/big"
     9  	"os"
    10  	"path"
    11  	"sort"
    12  
    13  	"github.com/ethereum-optimism/optimism/op-node/cmd/batch_decoder/fetch"
    14  	"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
    15  	"github.com/ethereum-optimism/optimism/op-service/eth"
    16  	"github.com/ethereum/go-ethereum/common"
    17  )
    18  
    19  type ChannelWithMetadata struct {
    20  	ID             derive.ChannelID    `json:"id"`
    21  	IsReady        bool                `json:"is_ready"`
    22  	InvalidFrames  bool                `json:"invalid_frames"`
    23  	InvalidBatches bool                `json:"invalid_batches"`
    24  	Frames         []FrameWithMetadata `json:"frames"`
    25  	Batches        []derive.Batch      `json:"batches"`
    26  	BatchTypes     []int               `json:"batch_types"`
    27  }
    28  
    29  type FrameWithMetadata struct {
    30  	TxHash         common.Hash  `json:"transaction_hash"`
    31  	InclusionBlock uint64       `json:"inclusion_block"`
    32  	Timestamp      uint64       `json:"timestamp"`
    33  	BlockHash      common.Hash  `json:"block_hash"`
    34  	Frame          derive.Frame `json:"frame"`
    35  }
    36  
    37  type Config struct {
    38  	BatchInbox    common.Address
    39  	InDirectory   string
    40  	OutDirectory  string
    41  	L2ChainID     *big.Int
    42  	L2GenesisTime uint64
    43  	L2BlockTime   uint64
    44  }
    45  
    46  func LoadFrames(directory string, inbox common.Address) []FrameWithMetadata {
    47  	txns := loadTransactions(directory, inbox)
    48  	// Sort first by block number then by transaction index inside the block number range.
    49  	// This is to match the order they are processed in derivation.
    50  	sort.Slice(txns, func(i, j int) bool {
    51  		if txns[i].BlockNumber == txns[j].BlockNumber {
    52  			return txns[i].TxIndex < txns[j].TxIndex
    53  		} else {
    54  			return txns[i].BlockNumber < txns[j].BlockNumber
    55  		}
    56  
    57  	})
    58  	return transactionsToFrames(txns)
    59  }
    60  
    61  // Channels loads all transactions from the given input directory that are submitted to the
    62  // specified batch inbox and then re-assembles all channels & writes the re-assembled channels
    63  // to the out directory.
    64  func Channels(config Config) {
    65  	if err := os.MkdirAll(config.OutDirectory, 0750); err != nil {
    66  		log.Fatal(err)
    67  	}
    68  	frames := LoadFrames(config.InDirectory, config.BatchInbox)
    69  	framesByChannel := make(map[derive.ChannelID][]FrameWithMetadata)
    70  	for _, frame := range frames {
    71  		framesByChannel[frame.Frame.ID] = append(framesByChannel[frame.Frame.ID], frame)
    72  	}
    73  	for id, frames := range framesByChannel {
    74  		ch := processFrames(config, id, frames)
    75  		filename := path.Join(config.OutDirectory, fmt.Sprintf("%s.json", id.String()))
    76  		if err := writeChannel(ch, filename); err != nil {
    77  			log.Fatal(err)
    78  		}
    79  	}
    80  }
    81  
    82  func writeChannel(ch ChannelWithMetadata, filename string) error {
    83  	file, err := os.Create(filename)
    84  	if err != nil {
    85  		log.Fatal(err)
    86  	}
    87  	defer file.Close()
    88  	enc := json.NewEncoder(file)
    89  	return enc.Encode(ch)
    90  }
    91  
    92  func processFrames(cfg Config, id derive.ChannelID, frames []FrameWithMetadata) ChannelWithMetadata {
    93  	ch := derive.NewChannel(id, eth.L1BlockRef{Number: frames[0].InclusionBlock})
    94  	invalidFrame := false
    95  
    96  	for _, frame := range frames {
    97  		if ch.IsReady() {
    98  			fmt.Printf("Channel %v is ready despite having more frames\n", id.String())
    99  			invalidFrame = true
   100  			break
   101  		}
   102  		if err := ch.AddFrame(frame.Frame, eth.L1BlockRef{Number: frame.InclusionBlock}); err != nil {
   103  			fmt.Printf("Error adding to channel %v. Err: %v\n", id.String(), err)
   104  			invalidFrame = true
   105  		}
   106  	}
   107  
   108  	var batches []derive.Batch
   109  	var batchTypes []int
   110  	invalidBatches := false
   111  	if ch.IsReady() {
   112  		br, err := derive.BatchReader(ch.Reader())
   113  		if err == nil {
   114  			for batchData, err := br(); err != io.EOF; batchData, err = br() {
   115  				if err != nil {
   116  					fmt.Printf("Error reading batchData for channel %v. Err: %v\n", id.String(), err)
   117  					invalidBatches = true
   118  				} else {
   119  					batchType := batchData.GetBatchType()
   120  					batchTypes = append(batchTypes, int(batchType))
   121  					switch batchType {
   122  					case derive.SingularBatchType:
   123  						singularBatch, err := derive.GetSingularBatch(batchData)
   124  						if err != nil {
   125  							invalidBatches = true
   126  							fmt.Printf("Error converting singularBatch from batchData for channel %v. Err: %v\n", id.String(), err)
   127  						}
   128  						// singularBatch will be nil when errored
   129  						batches = append(batches, singularBatch)
   130  					case derive.SpanBatchType:
   131  						spanBatch, err := derive.DeriveSpanBatch(batchData, cfg.L2BlockTime, cfg.L2GenesisTime, cfg.L2ChainID)
   132  						if err != nil {
   133  							invalidBatches = true
   134  							fmt.Printf("Error deriving spanBatch from batchData for channel %v. Err: %v\n", id.String(), err)
   135  						}
   136  						// spanBatch will be nil when errored
   137  						batches = append(batches, spanBatch)
   138  					default:
   139  						fmt.Printf("unrecognized batch type: %d for channel %v.\n", batchData.GetBatchType(), id.String())
   140  					}
   141  				}
   142  			}
   143  		} else {
   144  			fmt.Printf("Error creating batch reader for channel %v. Err: %v\n", id.String(), err)
   145  		}
   146  	} else {
   147  		fmt.Printf("Channel %v is not ready\n", id.String())
   148  	}
   149  
   150  	return ChannelWithMetadata{
   151  		ID:             id,
   152  		Frames:         frames,
   153  		IsReady:        ch.IsReady(),
   154  		InvalidFrames:  invalidFrame,
   155  		InvalidBatches: invalidBatches,
   156  		Batches:        batches,
   157  		BatchTypes:     batchTypes,
   158  	}
   159  }
   160  
   161  func transactionsToFrames(txns []fetch.TransactionWithMetadata) []FrameWithMetadata {
   162  	var out []FrameWithMetadata
   163  	for _, tx := range txns {
   164  		for _, frame := range tx.Frames {
   165  			fm := FrameWithMetadata{
   166  				TxHash:         tx.Tx.Hash(),
   167  				InclusionBlock: tx.BlockNumber,
   168  				BlockHash:      tx.BlockHash,
   169  				Timestamp:      tx.BlockTime,
   170  				Frame:          frame,
   171  			}
   172  			out = append(out, fm)
   173  		}
   174  	}
   175  	return out
   176  }
   177  
   178  // if inbox is the zero address, it will load all frames
   179  func loadTransactions(dir string, inbox common.Address) []fetch.TransactionWithMetadata {
   180  	files, err := os.ReadDir(dir)
   181  	if err != nil {
   182  		log.Fatal(err)
   183  	}
   184  	var out []fetch.TransactionWithMetadata
   185  	for _, file := range files {
   186  		f := path.Join(dir, file.Name())
   187  		txm := loadTransactionsFile(f)
   188  		if (inbox == common.Address{} || txm.InboxAddr == inbox) && txm.ValidSender {
   189  			out = append(out, txm)
   190  		}
   191  	}
   192  	return out
   193  }
   194  
   195  func loadTransactionsFile(file string) fetch.TransactionWithMetadata {
   196  	f, err := os.Open(file)
   197  	if err != nil {
   198  		log.Fatal(err)
   199  	}
   200  	defer f.Close()
   201  	dec := json.NewDecoder(f)
   202  	var txm fetch.TransactionWithMetadata
   203  	if err := dec.Decode(&txm); err != nil {
   204  		log.Fatalf("Failed to decode %v. Err: %v\n", file, err)
   205  	}
   206  	return txm
   207  }