github.com/ethereum-optimism/optimism@v1.7.2/op-node/rollup/async/asyncgossiper.go (about)

     1  package async
     2  
     3  import (
     4  	"context"
     5  	"sync/atomic"
     6  
     7  	"github.com/ethereum/go-ethereum/log"
     8  
     9  	"github.com/ethereum-optimism/optimism/op-service/eth"
    10  )
    11  
    12  type AsyncGossiper interface {
    13  	Gossip(payload *eth.ExecutionPayloadEnvelope)
    14  	Get() *eth.ExecutionPayloadEnvelope
    15  	Clear()
    16  	Stop()
    17  	Start()
    18  }
    19  
    20  // SimpleAsyncGossiper is a component that stores and gossips a single payload at a time
    21  // it uses a separate goroutine to handle gossiping the payload asynchronously
    22  // the payload can be accessed by the Get function to be reused when the payload was gossiped but not inserted
    23  // exposed functions are synchronous, and block until the async routine is able to start handling the request
    24  type SimpleAsyncGossiper struct {
    25  	running atomic.Bool
    26  	// channel to add new payloads to gossip
    27  	set chan *eth.ExecutionPayloadEnvelope
    28  	// channel to request getting the currently gossiping payload
    29  	get chan chan *eth.ExecutionPayloadEnvelope
    30  	// channel to request clearing the currently gossiping payload
    31  	clear chan struct{}
    32  	// channel to request stopping the handling loop
    33  	stop chan struct{}
    34  
    35  	currentPayload *eth.ExecutionPayloadEnvelope
    36  	ctx            context.Context
    37  	net            Network
    38  	log            log.Logger
    39  	metrics        Metrics
    40  }
    41  
    42  // To avoid import cycles, we define a new Network interface here
    43  // this interface is compatable with driver.Network
    44  type Network interface {
    45  	PublishL2Payload(ctx context.Context, payload *eth.ExecutionPayloadEnvelope) error
    46  }
    47  
    48  // To avoid import cycles, we define a new Metrics interface here
    49  // this interface is compatable with driver.Metrics
    50  type Metrics interface {
    51  	RecordPublishingError()
    52  }
    53  
    54  func NewAsyncGossiper(ctx context.Context, net Network, log log.Logger, metrics Metrics) *SimpleAsyncGossiper {
    55  	return &SimpleAsyncGossiper{
    56  		running: atomic.Bool{},
    57  		set:     make(chan *eth.ExecutionPayloadEnvelope),
    58  		get:     make(chan chan *eth.ExecutionPayloadEnvelope),
    59  		clear:   make(chan struct{}),
    60  		stop:    make(chan struct{}),
    61  
    62  		currentPayload: nil,
    63  		net:            net,
    64  		ctx:            ctx,
    65  		log:            log,
    66  		metrics:        metrics,
    67  	}
    68  }
    69  
    70  // Gossip is a synchronous function to store and gossip a payload
    71  // it blocks until the payload can be taken by the async routine
    72  func (p *SimpleAsyncGossiper) Gossip(payload *eth.ExecutionPayloadEnvelope) {
    73  	p.set <- payload
    74  }
    75  
    76  // Get is a synchronous function to get the currently held payload
    77  // it blocks until the async routine is able to return the payload
    78  func (p *SimpleAsyncGossiper) Get() *eth.ExecutionPayloadEnvelope {
    79  	c := make(chan *eth.ExecutionPayloadEnvelope)
    80  	p.get <- c
    81  	return <-c
    82  }
    83  
    84  // Clear is a synchronous function to clear the currently gossiping payload
    85  // it blocks until the signal to clear is picked up by the async routine
    86  func (p *SimpleAsyncGossiper) Clear() {
    87  	p.clear <- struct{}{}
    88  }
    89  
    90  // Stop is a synchronous function to stop the async routine
    91  // it blocks until the async routine accepts the signal
    92  func (p *SimpleAsyncGossiper) Stop() {
    93  	p.stop <- struct{}{}
    94  }
    95  
    96  // Start starts the AsyncGossiper's gossiping loop on a separate goroutine
    97  // each behavior of the loop is handled by a select case on a channel, plus an internal handler function call
    98  func (p *SimpleAsyncGossiper) Start() {
    99  	// if the gossiping is already running, return
   100  	if p.running.Load() {
   101  		return
   102  	}
   103  	p.running.Store(true)
   104  	// else, start the handling loop
   105  	go func() {
   106  		defer p.running.Store(false)
   107  		for {
   108  			select {
   109  			// new payloads to be gossiped are found in the `set` channel
   110  			case payload := <-p.set:
   111  				p.gossip(p.ctx, payload)
   112  			// requests to get the current payload are found in the `get` channel
   113  			case c := <-p.get:
   114  				p.getPayload(c)
   115  			// requests to clear the current payload are found in the `clear` channel
   116  			case <-p.clear:
   117  				p.clearPayload()
   118  			// if the context is done, return
   119  			case <-p.stop:
   120  				return
   121  			}
   122  		}
   123  	}()
   124  }
   125  
   126  // gossip is the internal handler function for gossiping the current payload
   127  // and storing the payload in the async AsyncGossiper's state
   128  // it is called by the Start loop when a new payload is set
   129  // the payload is only stored if the publish is successful
   130  func (p *SimpleAsyncGossiper) gossip(ctx context.Context, payload *eth.ExecutionPayloadEnvelope) {
   131  	if err := p.net.PublishL2Payload(ctx, payload); err == nil {
   132  		p.currentPayload = payload
   133  	} else {
   134  		p.log.Warn("failed to publish newly created block",
   135  			"id", payload.ExecutionPayload.ID(),
   136  			"hash", payload.ExecutionPayload.BlockHash,
   137  			"err", err)
   138  		p.metrics.RecordPublishingError()
   139  	}
   140  }
   141  
   142  // getPayload is the internal handler function for getting the current payload
   143  // c is the channel the caller expects to receive the payload on
   144  func (p *SimpleAsyncGossiper) getPayload(c chan *eth.ExecutionPayloadEnvelope) {
   145  	c <- p.currentPayload
   146  }
   147  
   148  // clearPayload is the internal handler function for clearing the current payload
   149  func (p *SimpleAsyncGossiper) clearPayload() {
   150  	p.currentPayload = nil
   151  }
   152  
   153  // NoOpGossiper is a no-op implementation of AsyncGossiper
   154  // it serves as a placeholder for when the AsyncGossiper is not needed
   155  type NoOpGossiper struct{}
   156  
   157  func (NoOpGossiper) Gossip(payload *eth.ExecutionPayloadEnvelope) {}
   158  func (NoOpGossiper) Get() *eth.ExecutionPayloadEnvelope           { return nil }
   159  func (NoOpGossiper) Clear()                                       {}
   160  func (NoOpGossiper) Stop()                                        {}
   161  func (NoOpGossiper) Start()                                       {}