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