arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/db/l2db/l2db.go
db/l2db/l2db.goRaw
  1package l2db
  2
  3import (
  4	"fmt"
  5	"time"
  6
  7	ethCommon "github.com/ethereum/go-ethereum/common"
  8	"github.com/gobuffalo/packr/v2"
  9	"github.com/hermeznetwork/hermez-node/common"
 10	"github.com/hermeznetwork/hermez-node/db"
 11	"github.com/jmoiron/sqlx"
 12
 13	//nolint:errcheck // driver for postgres DB
 14	_ "github.com/lib/pq"
 15	migrate "github.com/rubenv/sql-migrate"
 16	"github.com/russross/meddler"
 17)
 18
 19// TODO(Edu): Check DB consistency while there's concurrent use from Coordinator/TxSelector & API
 20
 21// L2DB stores L2 txs and authorization registers received by the coordinator and keeps them until they are no longer relevant
 22// due to them being forged or invalid after a safety period
 23type L2DB struct {
 24	db           *sqlx.DB
 25	safetyPeriod common.BatchNum
 26	ttl          time.Duration
 27	maxTxs       uint32
 28}
 29
 30// NewL2DB creates a L2DB.
 31// To create it, it's needed postgres configuration, safety period expressed in batches,
 32// maxTxs that the DB should have and TTL (time to live) for pending txs.
 33func NewL2DB(
 34	port int, host, user, password, dbname string,
 35	safetyPeriod common.BatchNum,
 36	maxTxs uint32,
 37	TTL time.Duration,
 38) (*L2DB, error) {
 39	// init meddler
 40	db.InitMeddler()
 41	meddler.Default = meddler.PostgreSQL
 42	// Stablish DB connection
 43	psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
 44	db, err := sqlx.Connect("postgres", psqlconn)
 45	if err != nil {
 46		return nil, err
 47	}
 48
 49	// Run DB migrations
 50	migrations := &migrate.PackrMigrationSource{
 51		Box: packr.New("history-migrations", "./migrations"),
 52	}
 53	if _, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up); err != nil {
 54		return nil, err
 55	}
 56
 57	return &L2DB{
 58		db:           db,
 59		safetyPeriod: safetyPeriod,
 60		ttl:          TTL,
 61		maxTxs:       maxTxs,
 62	}, nil
 63}
 64
 65// DB returns a pointer to the L2DB.db. This method should be used only for
 66// internal testing purposes.
 67func (l2db *L2DB) DB() *sqlx.DB {
 68	return l2db.db
 69}
 70
 71// AddAccountCreationAuth inserts an account creation authorization into the DB
 72func (l2db *L2DB) AddAccountCreationAuth(auth *common.AccountCreationAuth) error {
 73	return meddler.Insert(l2db.db, "account_creation_auth", auth)
 74}
 75
 76// GetAccountCreationAuth returns an account creation authorization into the DB
 77func (l2db *L2DB) GetAccountCreationAuth(addr ethCommon.Address) (*common.AccountCreationAuth, error) {
 78	auth := new(common.AccountCreationAuth)
 79	return auth, meddler.QueryRow(
 80		l2db.db, auth,
 81		"SELECT * FROM account_creation_auth WHERE eth_addr = $1;",
 82		addr,
 83	)
 84}
 85
 86// AddTx inserts a tx into the L2DB
 87func (l2db *L2DB) AddTx(tx *common.PoolL2Tx) error {
 88	return meddler.Insert(l2db.db, "tx_pool", tx)
 89}
 90
 91// GetTx return the specified Tx
 92func (l2db *L2DB) GetTx(txID common.TxID) (*common.PoolL2Tx, error) {
 93	tx := new(common.PoolL2Tx)
 94	return tx, meddler.QueryRow(
 95		l2db.db, tx,
 96		"SELECT * FROM tx_pool WHERE tx_id = $1;",
 97		txID,
 98	)
 99}
100
101// GetPendingTxs return all the pending txs of the L2DB
102func (l2db *L2DB) GetPendingTxs() ([]*common.PoolL2Tx, error) {
103	var txs []*common.PoolL2Tx
104	err := meddler.QueryAll(
105		l2db.db, &txs,
106		"SELECT * FROM tx_pool WHERE state = $1",
107		common.PoolL2TxStatePending,
108	)
109	return txs, err
110}
111
112// StartForging updates the state of the transactions that will begin the forging process.
113// The state of the txs referenced by txIDs will be changed from Pending -> Forging
114func (l2db *L2DB) StartForging(txIDs []common.TxID, batchNum common.BatchNum) error {
115	query, args, err := sqlx.In(
116		`UPDATE tx_pool
117		SET state = ?, batch_num = ?
118		WHERE state = ? AND tx_id IN (?);`,
119		common.PoolL2TxStateForging,
120		batchNum,
121		common.PoolL2TxStatePending,
122		txIDs,
123	)
124	if err != nil {
125		return err
126	}
127	query = l2db.db.Rebind(query)
128	_, err = l2db.db.Exec(query, args...)
129	return err
130}
131
132// DoneForging updates the state of the transactions that have been forged
133// so the state of the txs referenced by txIDs will be changed from Forging -> Forged
134func (l2db *L2DB) DoneForging(txIDs []common.TxID, batchNum common.BatchNum) error {
135	query, args, err := sqlx.In(
136		`UPDATE tx_pool
137		SET state = ?, batch_num = ?
138		WHERE state = ? AND tx_id IN (?);`,
139		common.PoolL2TxStateForged,
140		batchNum,
141		common.PoolL2TxStateForging,
142		txIDs,
143	)
144	if err != nil {
145		return err
146	}
147	query = l2db.db.Rebind(query)
148	_, err = l2db.db.Exec(query, args...)
149	return err
150}
151
152// InvalidateTxs updates the state of the transactions that are invalid.
153// The state of the txs referenced by txIDs will be changed from * -> Invalid
154func (l2db *L2DB) InvalidateTxs(txIDs []common.TxID, batchNum common.BatchNum) error {
155	query, args, err := sqlx.In(
156		`UPDATE tx_pool
157		SET state = ?, batch_num = ?
158		WHERE tx_id IN (?);`,
159		common.PoolL2TxStateInvalid,
160		batchNum,
161		txIDs,
162	)
163	if err != nil {
164		return err
165	}
166	query = l2db.db.Rebind(query)
167	_, err = l2db.db.Exec(query, args...)
168	return err
169}
170
171// CheckNonces invalidate txs with nonces that are smaller or equal than their respective accounts nonces.
172// The state of the affected txs will be changed from Pending -> Invalid
173func (l2db *L2DB) CheckNonces(updatedAccounts []common.Account, batchNum common.BatchNum) error {
174	txn, err := l2db.db.Begin()
175	if err != nil {
176		return err
177	}
178	defer func() {
179		// Rollback the transaction if there was an error.
180		if err != nil {
181			err = txn.Rollback()
182		}
183	}()
184	for i := 0; i < len(updatedAccounts); i++ {
185		_, err = txn.Exec(
186			`UPDATE tx_pool
187			SET state = $1, batch_num = $2
188			WHERE state = $3 AND from_idx = $4 AND nonce <= $5;`,
189			common.PoolL2TxStateInvalid,
190			batchNum,
191			common.PoolL2TxStatePending,
192			updatedAccounts[i].Idx,
193			updatedAccounts[i].Nonce,
194		)
195		if err != nil {
196			return err
197		}
198	}
199	return txn.Commit()
200}
201
202// UpdateTxValue updates the absolute fee and value of txs given a token list that include their price in USD
203func (l2db *L2DB) UpdateTxValue(tokens []common.Token) error {
204	// WARNING: this is very slow and should be optimized
205	txn, err := l2db.db.Begin()
206	if err != nil {
207		return err
208	}
209	defer func() {
210		// Rollback the transaction if there was an error.
211		if err != nil {
212			err = txn.Rollback()
213		}
214	}()
215	now := time.Now()
216	for i := 0; i < len(tokens); i++ {
217		_, err = txn.Exec(
218			`UPDATE tx_pool
219			SET usd_update = $1, value_usd = amount_f * $2, fee_usd = $2 * amount_f * CASE
220				WHEN fee = 0 THEN 0	
221				WHEN fee >= 1 AND fee <= 32 THEN POWER(10,-24+(fee::float/2))	
222				WHEN fee >= 33 AND fee <= 223 THEN POWER(10,-8+(0.041666666666667*(fee::float-32)))	
223				WHEN fee >= 224 AND fee <= 255 THEN POWER(10,fee-224) END
224			WHERE token_id = $3;`,
225			now,
226			tokens[i].USD,
227			tokens[i].TokenID,
228		)
229		if err != nil {
230			return err
231		}
232	}
233	return txn.Commit()
234}
235
236// Reorg updates the state of txs that were updated in a batch that has been discarted due to a blockchain reorg.
237// The state of the affected txs can change form Forged -> Pending or from Invalid -> Pending
238func (l2db *L2DB) Reorg(lastValidBatch common.BatchNum) error {
239	_, err := l2db.db.Exec(
240		`UPDATE tx_pool SET batch_num = NULL, state = $1 
241		WHERE (state = $2 OR state = $3) AND batch_num > $4`,
242		common.PoolL2TxStatePending,
243		common.PoolL2TxStateForged,
244		common.PoolL2TxStateInvalid,
245		lastValidBatch,
246	)
247	return err
248}
249
250// Purge deletes transactions that have been forged or marked as invalid for longer than the safety period
251// it also deletes txs that has been in the L2DB for longer than the ttl if maxTxs has been exceeded
252func (l2db *L2DB) Purge(currentBatchNum common.BatchNum) error {
253	txn, err := l2db.db.Begin()
254	if err != nil {
255		return err
256	}
257	defer func() {
258		// Rollback the transaction if there was an error.
259		if err != nil {
260			err = txn.Rollback()
261		}
262	}()
263	// Delete pending txs that have been in the pool after the TTL if maxTxs is reached
264	now := time.Now().UTC().Unix()
265	_, err = txn.Exec(
266		`DELETE FROM tx_pool WHERE (SELECT count(*) FROM tx_pool) > $1 AND timestamp < $2`,
267		l2db.maxTxs,
268		time.Unix(now-int64(l2db.ttl.Seconds()), 0),
269	)
270	if err != nil {
271		return err
272	}
273	// Delete txs that have been marked as forged / invalid after the safety period
274	_, err = txn.Exec(
275		`DELETE FROM tx_pool 
276		WHERE batch_num < $1 AND (state = $2 OR state = $3)`,
277		currentBatchNum-l2db.safetyPeriod,
278		common.PoolL2TxStateForged,
279		common.PoolL2TxStateInvalid,
280	)
281	if err != nil {
282		return err
283	}
284	return txn.Commit()
285}
286
287// Close frees the resources used by the L2DB
288func (l2db *L2DB) Close() error {
289	return l2db.db.Close()
290}