hermez-node/txselector/txselector.go
txselector/txselector.goRaw
1package txselector
2
3// current: very simple version of TxSelector
4
5import (
6 "bytes"
7 "math/big"
8 "sort"
9
10 ethCommon "github.com/ethereum/go-ethereum/common"
11 "github.com/hermeznetwork/hermez-node/common"
12 "github.com/hermeznetwork/hermez-node/db/l2db"
13 "github.com/hermeznetwork/hermez-node/db/statedb"
14 "github.com/hermeznetwork/hermez-node/log"
15 "github.com/hermeznetwork/tracerr"
16 "github.com/iden3/go-iden3-crypto/babyjub"
17)
18
19// txs implements the interface Sort for an array of Tx
20type txs []common.PoolL2Tx
21
22func (t txs) Len() int {
23 return len(t)
24}
25func (t txs) Swap(i, j int) {
26 t[i], t[j] = t[j], t[i]
27}
28func (t txs) Less(i, j int) bool {
29 return t[i].AbsoluteFee > t[j].AbsoluteFee
30}
31
32// TxSelector implements all the functionalities to select the txs for the next batch
33type TxSelector struct {
34 // MaxL1UserTxs is the maximum L1-user-tx for a batch
35 MaxL1UserTxs uint64
36 // MaxL1OperatorTxs is the maximum L1-operator-tx for a batch
37 MaxL1OperatorTxs uint64
38 // MaxTxs is the maximum txs for a batch
39 MaxTxs uint64
40
41 l2db *l2db.L2DB
42 localAccountsDB *statedb.LocalStateDB
43}
44
45// NewTxSelector returns a *TxSelector
46func NewTxSelector(dbpath string, synchronizerStateDB *statedb.StateDB, l2 *l2db.L2DB, maxL1UserTxs, maxL1OperatorTxs, maxTxs uint64) (*TxSelector, error) {
47 localAccountsDB, err := statedb.NewLocalStateDB(dbpath, synchronizerStateDB, statedb.TypeTxSelector, 0) // without merkletree
48 if err != nil {
49 return nil, tracerr.Wrap(err)
50 }
51
52 return &TxSelector{
53 MaxL1UserTxs: maxL1UserTxs,
54 MaxL1OperatorTxs: maxL1OperatorTxs,
55 MaxTxs: maxTxs,
56 l2db: l2,
57 localAccountsDB: localAccountsDB,
58 }, nil
59}
60
61// LocalAccountsDB returns the LocalStateDB of the TxSelector
62func (txsel *TxSelector) LocalAccountsDB() *statedb.LocalStateDB {
63 return txsel.localAccountsDB
64}
65
66// Reset tells the TxSelector to get it's internal AccountsDB
67// from the required `batchNum`
68func (txsel *TxSelector) Reset(batchNum common.BatchNum) error {
69 err := txsel.localAccountsDB.Reset(batchNum, true)
70 if err != nil {
71 return tracerr.Wrap(err)
72 }
73 return nil
74}
75
76// GetL2TxSelection returns the L1CoordinatorTxs and a selection of the L2Txs
77// for the next batch, from the L2DB pool
78func (txsel *TxSelector) GetL2TxSelection(coordIdxs []common.Idx, batchNum common.BatchNum) ([]common.L1Tx, []common.PoolL2Tx, error) {
79 _, l1CoordinatorTxs, l2Txs, err := txsel.GetL1L2TxSelection(coordIdxs, batchNum, []common.L1Tx{})
80 return l1CoordinatorTxs, l2Txs, tracerr.Wrap(err)
81}
82
83// GetL1L2TxSelection returns the selection of L1 + L2 txs
84func (txsel *TxSelector) GetL1L2TxSelection(coordIdxs []common.Idx, batchNum common.BatchNum, l1Txs []common.L1Tx) ([]common.L1Tx, []common.L1Tx, []common.PoolL2Tx, error) {
85 // apply l1-user-tx to localAccountDB
86 // create new leaves
87 // update balances
88 // update nonces
89
90 // get pending l2-tx from tx-pool
91 l2TxsRaw, err := txsel.l2db.GetPendingTxs() // (batchID)
92 if err != nil {
93 return nil, nil, nil, tracerr.Wrap(err)
94 }
95
96 var validTxs txs
97 var l1CoordinatorTxs []common.L1Tx
98 positionL1 := len(l1Txs)
99
100 // if tx.ToIdx>=256, tx.ToIdx should exist to localAccountsDB, if so,
101 // tx is used. if tx.ToIdx==0, check if tx.ToEthAddr/tx.ToBJJ exist in
102 // localAccountsDB, if yes tx is used; if not, check if tx.ToEthAddr is
103 // in AccountCreationAuthDB, if so, tx is used and L1CoordinatorTx of
104 // CreateAccountAndDeposit is created.
105 for i := 0; i < len(l2TxsRaw); i++ {
106 if l2TxsRaw[i].ToIdx == 0 {
107 if checkAlreadyPendingToCreate(l1CoordinatorTxs, l2TxsRaw[i].ToEthAddr, l2TxsRaw[i].ToBJJ) {
108 // if L2Tx needs a new L1CoordinatorTx of CreateAccount type,
109 // and a previous L2Tx in the current process already created
110 // a L1CoordinatorTx of this type, in the DB there still seem
111 // that needs to create a new L1CoordinatorTx, but as is already
112 // created, the tx is valid
113 validTxs = append(validTxs, l2TxsRaw[i])
114 continue
115 }
116
117 if !bytes.Equal(l2TxsRaw[i].ToEthAddr.Bytes(), common.EmptyAddr.Bytes()) &&
118 !bytes.Equal(l2TxsRaw[i].ToEthAddr.Bytes(), common.FFAddr.Bytes()) {
119 // case: ToEthAddr != 0x00 neither 0xff
120 var accAuth *common.AccountCreationAuth
121 if l2TxsRaw[i].ToBJJ != nil {
122 // case: ToBJJ!=0:
123 // if idx exist for EthAddr&BJJ use it
124 _, err := txsel.localAccountsDB.GetIdxByEthAddrBJJ(l2TxsRaw[i].ToEthAddr, l2TxsRaw[i].ToBJJ, l2TxsRaw[i].TokenID)
125 if err == nil {
126 // account for ToEthAddr&ToBJJ already exist,
127 // there is no need to create a new one.
128 // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
129 validTxs = append(validTxs, l2TxsRaw[i])
130 continue
131 }
132 // if not, check if AccountCreationAuth exist for that ToEthAddr&BJJ
133 // accAuth, err = txsel.l2db.GetAccountCreationAuthBJJ(l2TxsRaw[i].ToEthAddr, l2TxsRaw[i].ToBJJ)
134 accAuth, err = txsel.l2db.GetAccountCreationAuth(l2TxsRaw[i].ToEthAddr)
135 if err != nil {
136 // not found, l2Tx will not be added in the selection
137 log.Debugw("invalid L2Tx: ToIdx not found in StateDB, neither ToEthAddr & ToBJJ found in AccountCreationAuths L2DB", "ToIdx", l2TxsRaw[i].ToIdx, "ToEthAddr", l2TxsRaw[i].ToEthAddr)
138 continue
139 }
140 if accAuth.BJJ != l2TxsRaw[i].ToBJJ {
141 // if AccountCreationAuth.BJJ is not the same than in the tx, tx is not accepted
142 log.Debugw("invalid L2Tx: ToIdx not found in StateDB, neither ToEthAddr & ToBJJ found in AccountCreationAuths L2DB", "ToIdx", l2TxsRaw[i].ToIdx, "ToEthAddr", l2TxsRaw[i].ToEthAddr, "ToBJJ", l2TxsRaw[i].ToBJJ)
143 continue
144 }
145 validTxs = append(validTxs, l2TxsRaw[i])
146 } else {
147 // case: ToBJJ==0:
148 // if idx exist for EthAddr use it
149 _, err := txsel.localAccountsDB.GetIdxByEthAddr(l2TxsRaw[i].ToEthAddr, l2TxsRaw[i].TokenID)
150 if err == nil {
151 // account for ToEthAddr already exist,
152 // there is no need to create a new one.
153 // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
154 validTxs = append(validTxs, l2TxsRaw[i])
155 continue
156 }
157 // if not, check if AccountCreationAuth exist for that ToEthAddr
158 accAuth, err = txsel.l2db.GetAccountCreationAuth(l2TxsRaw[i].ToEthAddr)
159 if err != nil {
160 // not found, l2Tx will not be added in the selection
161 log.Debugw("invalid L2Tx: ToIdx not found in StateDB, neither ToEthAddr found in AccountCreationAuths L2DB", "ToIdx", l2TxsRaw[i].ToIdx, "ToEthAddr", l2TxsRaw[i].ToEthAddr)
162 continue
163 }
164 validTxs = append(validTxs, l2TxsRaw[i])
165 }
166 // create L1CoordinatorTx for the accountCreation
167 l1CoordinatorTx := common.L1Tx{
168 Position: positionL1,
169 UserOrigin: false,
170 FromEthAddr: accAuth.EthAddr,
171 FromBJJ: accAuth.BJJ,
172 TokenID: l2TxsRaw[i].TokenID,
173 DepositAmount: big.NewInt(0),
174 Type: common.TxTypeCreateAccountDeposit,
175 }
176 positionL1++
177 l1CoordinatorTxs = append(l1CoordinatorTxs, l1CoordinatorTx)
178 } else if bytes.Equal(l2TxsRaw[i].ToEthAddr.Bytes(), common.FFAddr.Bytes()) && l2TxsRaw[i].ToBJJ != nil {
179 // if idx exist for EthAddr&BJJ use it
180 _, err := txsel.localAccountsDB.GetIdxByEthAddrBJJ(l2TxsRaw[i].ToEthAddr, l2TxsRaw[i].ToBJJ, l2TxsRaw[i].TokenID)
181 if err == nil {
182 // account for ToEthAddr&ToBJJ already exist, (where ToEthAddr==0xff)
183 // there is no need to create a new one.
184 // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
185 validTxs = append(validTxs, l2TxsRaw[i])
186 continue
187 }
188 // if idx don't exist for EthAddr&BJJ,
189 // coordinator can create a new account without
190 // L1Authorization, as ToEthAddr==0xff
191 // create L1CoordinatorTx for the accountCreation
192 l1CoordinatorTx := common.L1Tx{
193 Position: positionL1,
194 UserOrigin: false,
195 FromEthAddr: l2TxsRaw[i].ToEthAddr,
196 FromBJJ: l2TxsRaw[i].ToBJJ,
197 TokenID: l2TxsRaw[i].TokenID,
198 DepositAmount: big.NewInt(0),
199 Type: common.TxTypeCreateAccountDeposit,
200 }
201 positionL1++
202 l1CoordinatorTxs = append(l1CoordinatorTxs, l1CoordinatorTx)
203 }
204 } else if l2TxsRaw[i].ToIdx >= common.IdxUserThreshold {
205 _, err = txsel.localAccountsDB.GetAccount(l2TxsRaw[i].ToIdx)
206 if err != nil {
207 // tx not valid
208 log.Debugw("invalid L2Tx: ToIdx not found in StateDB", "ToIdx", l2TxsRaw[i].ToIdx)
209 continue
210 }
211 // Account found in the DB, include the l2Tx in the selection
212 validTxs = append(validTxs, l2TxsRaw[i])
213 } else if l2TxsRaw[i].ToIdx == common.Idx(1) {
214 // valid txs (of Exit type)
215 validTxs = append(validTxs, l2TxsRaw[i])
216 }
217 }
218
219 // get most profitable L2-tx
220 maxL2Txs := txsel.MaxTxs - uint64(len(l1CoordinatorTxs)) // - len(l1UserTxs) // TODO if there are L1UserTxs take them in to account
221 l2Txs := txsel.getL2Profitable(validTxs, maxL2Txs)
222
223 //nolint:gomnd
224 ptc := statedb.ProcessTxsConfig{ // TODO TMP
225 NLevels: 32,
226 MaxFeeTx: 64,
227 MaxTx: 512,
228 MaxL1Tx: 64,
229 }
230 // process the txs in the local AccountsDB
231 _, err = txsel.localAccountsDB.ProcessTxs(ptc, coordIdxs, l1Txs, l1CoordinatorTxs, l2Txs)
232 if err != nil {
233 return nil, nil, nil, tracerr.Wrap(err)
234 }
235 err = txsel.localAccountsDB.MakeCheckpoint()
236 if err != nil {
237 return nil, nil, nil, tracerr.Wrap(err)
238 }
239
240 return l1Txs, l1CoordinatorTxs, l2Txs, nil
241}
242
243func checkAlreadyPendingToCreate(l1CoordinatorTxs []common.L1Tx, addr ethCommon.Address, bjj *babyjub.PublicKey) bool {
244 for i := 0; i < len(l1CoordinatorTxs); i++ {
245 if bytes.Equal(l1CoordinatorTxs[i].FromEthAddr.Bytes(), addr.Bytes()) {
246 if bjj == nil {
247 return true
248 }
249 if l1CoordinatorTxs[i].FromBJJ == bjj {
250 return true
251 }
252 }
253 }
254 return false
255}
256
257// getL2Profitable returns the profitable selection of L2Txssorted by Nonce
258func (txsel *TxSelector) getL2Profitable(txs txs, max uint64) txs {
259 sort.Sort(txs)
260 if len(txs) < int(max) {
261 return txs
262 }
263 txs = txs[:max]
264
265 // sort l2Txs by Nonce. This can be done in many different ways, what
266 // is needed is to output the txs where the Nonce of txs for each
267 // Account is sorted, but the txs can not be grouped by sender Account
268 // neither by Fee. This is because later on the Nonces will need to be
269 // sequential for the zkproof generation.
270 sort.SliceStable(txs, func(i, j int) bool {
271 return txs[i].Nonce < txs[j].Nonce
272 })
273
274 return txs
275}