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() {}