hermez-node/coordinator/coordinator.go
coordinator/coordinator.goRaw
1package coordinator
2
3import (
4 "fmt"
5
6 ethCommon "github.com/ethereum/go-ethereum/common"
7 "github.com/hermeznetwork/hermez-node/batchbuilder"
8 "github.com/hermeznetwork/hermez-node/common"
9 "github.com/hermeznetwork/hermez-node/db/historydb"
10 "github.com/hermeznetwork/hermez-node/eth"
11 "github.com/hermeznetwork/hermez-node/log"
12 "github.com/hermeznetwork/hermez-node/txselector"
13 kvdb "github.com/iden3/go-merkletree/db"
14 "github.com/iden3/go-merkletree/db/memory"
15)
16
17// ErrStop is returned when the function is stopped asynchronously via the stop
18// channel. It doesn't indicate an error.
19var ErrStop = fmt.Errorf("Stopped")
20
21// Config contains the Coordinator configuration
22type Config struct {
23 ForgerAddress ethCommon.Address
24}
25
26// Coordinator implements the Coordinator type
27type Coordinator struct {
28 forging bool
29 isForgeSeq bool // WIP just for testing while implementing
30
31 config Config
32
33 batchNum common.BatchNum
34 serverProofPool *ServerProofPool
35
36 // synchronizer *synchronizer.Synchronizer
37 hdb *historydb.HistoryDB
38 txsel *txselector.TxSelector
39 batchBuilder *batchbuilder.BatchBuilder
40
41 ethClient eth.ClientInterface
42 ethTxStore kvdb.Storage
43}
44
45// NewCoordinator creates a new Coordinator
46func NewCoordinator(conf Config,
47 hdb *historydb.HistoryDB,
48 txsel *txselector.TxSelector,
49 bb *batchbuilder.BatchBuilder,
50 serverProofs []ServerProofInterface,
51 ethClient *eth.Client) *Coordinator { // once synchronizer is ready, synchronizer.Synchronizer will be passed as parameter here
52 serverProofPool := NewServerProofPool(len(serverProofs))
53 for _, serverProof := range serverProofs {
54 serverProofPool.Add(serverProof)
55 }
56 c := Coordinator{
57 config: conf,
58 serverProofPool: serverProofPool,
59 hdb: hdb,
60 txsel: txsel,
61 batchBuilder: bb,
62 ethClient: ethClient,
63 ethTxStore: memory.NewMemoryStorage(),
64 }
65 return &c
66}
67
68// ForgeLoopFn is the function ran in a loop that checks if it's time to forge
69// and forges a batch if so and sends it to outBatchCh. Returns true if it's
70// the coordinator turn to forge.
71func (c *Coordinator) ForgeLoopFn(outBatchCh chan *BatchInfo, stopCh chan bool) (bool, error) {
72 if !c.isForgeSequence() {
73 if c.forging {
74 log.Info("stop forging")
75 c.forging = false
76 }
77 log.Debug("not in forge time")
78 return false, nil
79 }
80 log.Debug("forge time")
81 if !c.forging {
82 log.Info("start forging")
83 // c.batchNum = c.hdb.GetLastBatchNum() // uncomment when HistoryDB is ready
84 err := c.txsel.Reset(c.batchNum)
85 if err != nil {
86 log.Errorw("TxSelector.Reset", "error", err)
87 return true, err
88 }
89 err = c.batchBuilder.Reset(c.batchNum, true)
90 if err != nil {
91 log.Errorw("BatchBuilder.Reset", "error", err)
92 return true, err
93 }
94 // c.batchQueue = NewBatchQueue()
95 c.forging = true
96 }
97 // TODO once synchronizer has this method ready:
98 // If there's been a reorg, handle it
99 // handleReorg() function decides if the reorg must restart the pipeline or not
100 // if c.synchronizer.Reorg():
101 _ = c.handleReorg()
102
103 // 0. If there's an available server proof: Start pipeline for batchNum = batchNum + 1.
104 // non-blocking call, returns nil if a server proof is
105 // not available, or non-nil otherwise.
106 serverProof, err := c.serverProofPool.Get(stopCh)
107 if err != nil {
108 return true, err
109 }
110 log.Debugw("got serverProof", "server", serverProof)
111
112 log.Debugw("start forge")
113 batchInfo, err := c.forge(serverProof)
114 if err != nil {
115 log.Errorw("forge", "error", err)
116 return true, err
117 }
118 log.Debugw("end forge", "batchNum", batchInfo.batchNum)
119 outBatchCh <- batchInfo
120 return true, nil
121}
122
123// GetProofCallForgeLoopFn is the function ran in a loop that gets a forged
124// batch via inBatchCh, waits for the proof server to finish, calls the ForgeBatch
125// function in the Rollup Smart Contract, and sends the batch to outBatchCh.
126func (c *Coordinator) GetProofCallForgeLoopFn(inBatchCh, outBatchCh chan *BatchInfo, stopCh chan bool) error {
127 select {
128 case <-stopCh:
129 log.Info("forgeLoopFn stopped")
130 return ErrStop
131 case batchInfo := <-inBatchCh:
132 log.Debugw("start getProofCallForge", "batchNum", batchInfo.batchNum)
133 if err := c.getProofCallForge(batchInfo, stopCh); err != nil {
134 return err
135 }
136 log.Debugw("end getProofCallForge", "batchNum", batchInfo.batchNum)
137 outBatchCh <- batchInfo
138 }
139 return nil
140}
141
142// ForgeCallConfirmLoopFn is the function ran in a loop that gets a batch that
143// has been sent to the Rollup Smart Contract via inBatchCh and waits for the
144// ethereum transaction confirmation.
145func (c *Coordinator) ForgeCallConfirmLoopFn(inBatchCh chan *BatchInfo, stopCh chan bool) error {
146 select {
147 case <-stopCh:
148 log.Info("forgeConfirmLoopFn stopped")
149 return ErrStop
150 case batchInfo := <-inBatchCh:
151 log.Debugw("start forgeCallConfirm", "batchNum", batchInfo.batchNum)
152 if err := c.forgeCallConfirm(batchInfo); err != nil {
153 return err
154 }
155 log.Debugw("end forgeCallConfirm", "batchNum", batchInfo.batchNum)
156 }
157 return nil
158}
159
160func (c *Coordinator) forge(serverProof ServerProofInterface) (*BatchInfo, error) {
161 // remove transactions from the pool that have been there for too long
162 err := c.purgeRemoveByTimeout()
163 if err != nil {
164 return nil, err
165 }
166
167 c.batchNum = c.batchNum + 1
168 batchInfo := NewBatchInfo(c.batchNum, serverProof) // to accumulate metadata of the batch
169
170 var poolL2Txs []*common.PoolL2Tx
171 // var feesInfo
172 var l1UserTxsExtra, l1OperatorTxs []*common.L1Tx
173 // 1. Decide if we forge L2Tx or L1+L2Tx
174 if c.shouldL1L2Batch() {
175 // 2a: L1+L2 txs
176 // l1UserTxs, toForgeL1TxsNumber := c.hdb.GetNextL1UserTxs() // TODO once HistoryDB is ready, uncomment
177 var l1UserTxs []*common.L1Tx = nil // tmp, depends on HistoryDB
178 l1UserTxsExtra, l1OperatorTxs, poolL2Txs, err = c.txsel.GetL1L2TxSelection(c.batchNum, l1UserTxs) // TODO once feesInfo is added to method return, add the var
179 if err != nil {
180 return nil, err
181 }
182 } else {
183 // 2b: only L2 txs
184 poolL2Txs, err = c.txsel.GetL2TxSelection(c.batchNum) // TODO once feesInfo is added to method return, add the var
185 if err != nil {
186 return nil, err
187 }
188 l1UserTxsExtra = nil
189 l1OperatorTxs = nil
190 }
191
192 // Run purger to invalidate transactions that become invalid beause of
193 // the poolL2Txs selected. Will mark as invalid the txs that have a
194 // (fromIdx, nonce) which already appears in the selected txs (includes
195 // all the nonces smaller than the current one)
196 err = c.purgeInvalidDueToL2TxsSelection(poolL2Txs)
197 if err != nil {
198 return nil, err
199 }
200
201 // 3. Save metadata from TxSelector output for BatchNum
202 batchInfo.SetTxsInfo(l1UserTxsExtra, l1OperatorTxs, poolL2Txs) // TODO feesInfo
203
204 // 4. Call BatchBuilder with TxSelector output
205 configBatch := &batchbuilder.ConfigBatch{
206 ForgerAddress: c.config.ForgerAddress,
207 }
208 zkInputs, err := c.batchBuilder.BuildBatch(configBatch, l1UserTxsExtra, l1OperatorTxs, poolL2Txs, nil) // TODO []common.TokenID --> feesInfo
209 if err != nil {
210 return nil, err
211 }
212
213 // 5. Save metadata from BatchBuilder output for BatchNum
214 batchInfo.SetZKInputs(zkInputs)
215
216 // 6. Call an idle server proof with BatchBuilder output, save server proof info for batchNum
217 err = batchInfo.serverProof.CalculateProof(zkInputs)
218 if err != nil {
219 return nil, err
220 }
221
222 return &batchInfo, nil
223}
224
225// getProofCallForge gets the generated zkProof & sends it to the SmartContract
226func (c *Coordinator) getProofCallForge(batchInfo *BatchInfo, stopCh chan bool) error {
227 serverProof := batchInfo.serverProof
228 proof, err := serverProof.GetProof(stopCh) // blocking call, until not resolved don't continue. Returns when the proof server has calculated the proof
229 if err != nil {
230 return err
231 }
232 batchInfo.SetProof(proof)
233 forgeBatchArgs := c.prepareForgeBatchArgs(batchInfo)
234 _, err = c.ethClient.RollupForgeBatch(forgeBatchArgs)
235 if err != nil {
236 return err
237 }
238 log.Debugf("ethClient ForgeCall sent, batchNum: %d", c.batchNum)
239
240 // TODO once tx data type is defined, store ethTx (returned by ForgeCall)
241 // TBD if use ethTxStore as a disk k-v database, or use a Queue
242 // tx, err := c.ethTxStore.NewTx()
243 // if err != nil {
244 // return err
245 // }
246 // tx.Put(ethTx.Hash(), ethTx.Bytes())
247 // if err := tx.Commit(); err!=nil {
248 // return nil
249 // }
250
251 return nil
252}
253
254func (c *Coordinator) forgeCallConfirm(batchInfo *BatchInfo) error {
255 // TODO strategy of this sequence TBD
256 // confirm eth txs and mark them as accepted sequence
257 // ethTx := ethTxStore.GetFirstPending()
258 // waitForAccepted(ethTx) // blocking call, returns once the ethTx is mined
259 // ethTxStore.MarkAccepted(ethTx)
260 return nil
261}
262
263func (c *Coordinator) handleReorg() error {
264 return nil // TODO
265}
266
267// isForgeSequence returns true if the node is the Forger in the current ethereum block
268func (c *Coordinator) isForgeSequence() bool {
269 return c.isForgeSeq // TODO
270}
271
272func (c *Coordinator) purgeRemoveByTimeout() error {
273 return nil // TODO
274}
275
276func (c *Coordinator) purgeInvalidDueToL2TxsSelection(l2Txs []*common.PoolL2Tx) error {
277 return nil // TODO
278}
279
280func (c *Coordinator) shouldL1L2Batch() bool {
281 return false // TODO
282}
283
284func (c *Coordinator) prepareForgeBatchArgs(batchInfo *BatchInfo) *eth.RollupForgeBatchArgs {
285 return nil // TODO
286}