Browse Source

Merge pull request #59 from hermeznetwork/feature/sql-tables4

Update SQL schemas
feature/sql-semaphore1
arnau 4 years ago
committed by GitHub
parent
commit
5d4840ce5c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 476 additions and 111 deletions
  1. +11
    -11
      batchbuilder/batchbuilder.go
  2. +28
    -11
      common/l1tx.go
  3. +15
    -2
      common/l2tx.go
  4. +49
    -27
      common/pooll2tx.go
  5. +13
    -13
      common/tx.go
  6. +4
    -4
      coordinator/batch.go
  7. +4
    -4
      coordinator/coordinator.go
  8. +9
    -11
      db/historydb/migrations/001_init.sql
  9. +72
    -17
      db/l2db/l2db.go
  10. +182
    -0
      db/l2db/l2db_test.go
  11. +37
    -0
      db/l2db/migrations/001_init.sql
  12. +40
    -0
      db/utils.go
  13. +2
    -1
      go.mod
  14. +5
    -5
      go.sum
  15. +5
    -5
      txselector/txselector.go

+ 11
- 11
batchbuilder/batchbuilder.go

@ -70,7 +70,7 @@ func (bb *BatchBuilder) Reset(batchNum uint64, fromSynchronizer bool) error {
}
// BuildBatch takes the transactions and returns the common.ZKInputs of the next batch
func (bb *BatchBuilder) BuildBatch(configBatch ConfigBatch, l1usertxs, l1coordinatortxs []common.L1Tx, l2txs []common.PoolL2Tx, tokenIDs []common.TokenID) (*common.ZKInputs, error) {
func (bb *BatchBuilder) BuildBatch(configBatch ConfigBatch, l1usertxs, l1coordinatortxs []*common.L1Tx, l2txs []*common.PoolL2Tx, tokenIDs []common.TokenID) (*common.ZKInputs, error) {
for _, tx := range l1usertxs {
err := bb.processL1Tx(tx)
if err != nil {
@ -88,7 +88,7 @@ func (bb *BatchBuilder) BuildBatch(configBatch ConfigBatch, l1usertxs, l1coordin
case common.TxTypeTransfer:
// go to the MT account of sender and receiver, and update
// balance & nonce
err := bb.applyTransfer(tx.Tx)
err := bb.applyTransfer(tx.Tx())
if err != nil {
return nil, err
}
@ -101,12 +101,12 @@ func (bb *BatchBuilder) BuildBatch(configBatch ConfigBatch, l1usertxs, l1coordin
return nil, nil
}
func (bb *BatchBuilder) processL1Tx(tx common.L1Tx) error {
func (bb *BatchBuilder) processL1Tx(tx *common.L1Tx) error {
switch tx.Type {
case common.TxTypeForceTransfer, common.TxTypeTransfer:
// go to the MT account of sender and receiver, and update balance
// & nonce
err := bb.applyTransfer(tx.Tx)
err := bb.applyTransfer(tx.Tx())
if err != nil {
return err
}
@ -136,7 +136,7 @@ func (bb *BatchBuilder) processL1Tx(tx common.L1Tx) error {
if err != nil {
return err
}
err = bb.applyTransfer(tx.Tx)
err = bb.applyTransfer(tx.Tx())
if err != nil {
return err
}
@ -150,12 +150,12 @@ func (bb *BatchBuilder) processL1Tx(tx common.L1Tx) error {
// applyCreateAccount creates a new account in the account of the depositer, it
// stores the deposit value
func (bb *BatchBuilder) applyCreateAccount(tx common.L1Tx) error {
func (bb *BatchBuilder) applyCreateAccount(tx *common.L1Tx) error {
account := &common.Account{
TokenID: tx.TokenID,
Nonce: 0,
Balance: tx.LoadAmount,
PublicKey: &tx.FromBJJ,
PublicKey: tx.FromBJJ,
EthAddr: tx.FromEthAddr,
}
@ -171,7 +171,7 @@ func (bb *BatchBuilder) applyCreateAccount(tx common.L1Tx) error {
// applyDeposit updates the balance in the account of the depositer, if
// andTransfer parameter is set to true, the method will also apply the
// Transfer of the L1Tx/DepositAndTransfer
func (bb *BatchBuilder) applyDeposit(tx common.L1Tx, transfer bool) error {
func (bb *BatchBuilder) applyDeposit(tx *common.L1Tx, transfer bool) error {
// deposit the tx.LoadAmount into the sender account
accSender, err := bb.localStateDB.GetAccount(tx.FromIdx)
if err != nil {
@ -186,9 +186,9 @@ func (bb *BatchBuilder) applyDeposit(tx common.L1Tx, transfer bool) error {
return err
}
// substract amount to the sender
accSender.Balance = new(big.Int).Sub(accSender.Balance, tx.Tx.Amount)
accSender.Balance = new(big.Int).Sub(accSender.Balance, tx.Amount)
// add amount to the receiver
accReceiver.Balance = new(big.Int).Add(accReceiver.Balance, tx.Tx.Amount)
accReceiver.Balance = new(big.Int).Add(accReceiver.Balance, tx.Amount)
// update receiver account in localStateDB
err = bb.localStateDB.UpdateAccount(tx.ToIdx, accReceiver)
if err != nil {
@ -205,7 +205,7 @@ func (bb *BatchBuilder) applyDeposit(tx common.L1Tx, transfer bool) error {
// applyTransfer updates the balance & nonce in the account of the sender, and
// the balance in the account of the receiver
func (bb *BatchBuilder) applyTransfer(tx common.Tx) error {
func (bb *BatchBuilder) applyTransfer(tx *common.Tx) error {
// get sender and receiver accounts from localStateDB
accSender, err := bb.localStateDB.GetAccount(tx.FromIdx)
if err != nil {

+ 28
- 11
common/l1tx.go

@ -9,15 +9,32 @@ import (
// L1Tx is a struct that represents a L1 tx
type L1Tx struct {
Tx
UserOrigin bool // true if the tx was originated by a user, false if it was aoriginated by a coordinator. Note that this differ from the spec for implementation simplification purpposes
PublicKey babyjub.PublicKey
LoadAmount *big.Int // amount transfered from L1 -> L2
EthBlockNum uint64 // Ethereum Block Number in which this L1Tx was added to the queue
EthTxHash eth.Hash // TxHash that added this L1Tx to the queue
Position int // Position among all the L1Txs in that batch
ToForgeL1TxsNum uint32 // toForgeL1TxsNum in which the tx was forged / will be forged
FromBJJ babyjub.PublicKey
CreateAccount bool // "from" + token ID is a new account
FromEthAddr eth.Address
// Stored in DB: mandatory fileds
TxID TxID `meddler:"tx_id"`
ToForgeL1TxsNum uint32 `meddler:"to_forge_l1_txs_num"` // toForgeL1TxsNum in which the tx was forged / will be forged
Position int `meddler:"position"`
UserOrigin bool `meddler:"user_origin"` // true if the tx was originated by a user, false if it was aoriginated by a coordinator. Note that this differ from the spec for implementation simplification purpposes
FromIdx Idx `meddler:"from_idx"` // FromIdx is used by L1Tx/Deposit to indicate the Idx receiver of the L1Tx.LoadAmount (deposit)
FromEthAddr eth.Address `meddler:"from_eth_addr"`
FromBJJ *babyjub.PublicKey `meddler:"from_bjj"`
ToIdx Idx `meddler:"to_idx"` // ToIdx is ignored in L1Tx/Deposit, but used in the L1Tx/DepositAndTransfer
TokenID TokenID `meddler:"token_id"`
Amount *big.Int `meddler:"amount,bigint"`
LoadAmount *big.Int `meddler:"load_amount,bigint"`
EthBlockNum uint64 `meddler:"eth_block_num"` // Ethereum Block Number in which this L1Tx was added to the queue
// Extra metadata, may be uninitialized
Type TxType `meddler:"-"` // optional, descrives which kind of tx it's
}
func (tx *L1Tx) Tx() *Tx {
return &Tx{
TxID: tx.TxID,
FromIdx: tx.FromIdx,
ToIdx: tx.ToIdx,
TokenID: tx.TokenID,
Amount: tx.Amount,
Nonce: 0,
Fee: 0,
Type: tx.Type,
}
}

+ 15
- 2
common/l2tx.go

@ -1,7 +1,20 @@
package common
import (
"math/big"
)
// L2Tx is a struct that represents an already forged L2 tx
type L2Tx struct {
Tx
Position int // Position among all the L1Txs in that batch
// Stored in DB: mandatory fileds
TxID TxID `meddler:"tx_id"`
BatchNum BatchNum `meddler:"batch_num"` // batchNum in which this tx was forged.
Position int `meddler:"position"`
FromIdx Idx `meddler:"from_idx"`
ToIdx Idx `meddler:"to_idx"`
Amount *big.Int `meddler:"amount,bigint"`
Fee FeeSelector `meddler:"fee"`
Nonce uint64 `meddler:"nonce"`
// Extra metadata, may be uninitialized
Type TxType `meddler:"-"` // optional, descrives which kind of tx it's
}

+ 49
- 27
common/pooll2tx.go

@ -10,37 +10,59 @@ import (
// PoolL2Tx is a struct that represents a L2Tx sent by an account to the coordinator hat is waiting to be forged
type PoolL2Tx struct {
Tx
ToBJJ babyjub.PublicKey
Status PoolL2TxStatus
RqTxCompressedData []byte // 253 bits, optional for atomic txs
RqTx RqTx
Timestamp time.Time // time when added to the tx pool
Signature babyjub.Signature // tx signature
ToEthAddr eth.Address
AbsoluteFee float64 // TODO add methods to calculate this value from Tx.Fee tables + priceupdater tables
// Stored in DB: mandatory fileds
TxID TxID `meddler:"tx_id"`
FromIdx Idx `meddler:"from_idx"` // FromIdx is used by L1Tx/Deposit to indicate the Idx receiver of the L1Tx.LoadAmount (deposit)
ToIdx Idx `meddler:"to_idx"` // ToIdx is ignored in L1Tx/Deposit, but used in the L1Tx/DepositAndTransfer
ToEthAddr eth.Address `meddler:"to_eth_addr"`
ToBJJ *babyjub.PublicKey `meddler:"to_bjj"` // TODO: stop using json, use scanner/valuer
TokenID TokenID `meddler:"token_id"`
Amount *big.Int `meddler:"amount,bigint"` // TODO: change to float16
Fee FeeSelector `meddler:"fee"`
Nonce uint64 `meddler:"nonce"` // effective 48 bits used
State PoolL2TxState `meddler:"state"`
Signature babyjub.Signature `meddler:"signature"` // tx signature
Timestamp time.Time `meddler:"timestamp,utctime"` // time when added to the tx pool
// Stored in DB: optional fileds, may be uninitialized
BatchNum BatchNum `meddler:"batch_num,zeroisnull"` // batchNum in which this tx was forged. Presence indicates "forged" state.
RqFromIdx Idx `meddler:"rq_from_idx,zeroisnull"` // FromIdx is used by L1Tx/Deposit to indicate the Idx receiver of the L1Tx.LoadAmount (deposit)
RqToIdx Idx `meddler:"rq_to_idx,zeroisnull"` // FromIdx is used by L1Tx/Deposit to indicate the Idx receiver of the L1Tx.LoadAmount (deposit)
RqToEthAddr eth.Address `meddler:"rq_to_eth_addr"`
RqToBJJ *babyjub.PublicKey `meddler:"rq_to_bjj"` // TODO: stop using json, use scanner/valuer
RqTokenID TokenID `meddler:"rq_token_id,zeroisnull"`
RqAmount *big.Int `meddler:"rq_amount,bigintnull"` // TODO: change to float16
RqFee FeeSelector `meddler:"rq_fee,zeroisnull"`
RqNonce uint64 `meddler:"rq_nonce,zeroisnull"` // effective 48 bits used
AbsoluteFee float64 `meddler:"absolute_fee,zeroisnull"`
AbsoluteFeeUpdate time.Time `meddler:"absolute_fee_update,utctimez"`
// Extra metadata, may be uninitialized
Type TxType `meddler:"-"` // optional, descrives which kind of tx it's
RqTxCompressedData []byte `meddler:"-"` // 253 bits, optional for atomic txs
}
// RqTx Transaction Data used to indicate that a transaction depends on another transaction
type RqTx struct {
FromEthAddr eth.Address
ToEthAddr eth.Address
TokenID TokenID
Amount *big.Int
FeeSelector FeeSelector
Nonce uint64 // effective 48 bits used
func (tx *PoolL2Tx) Tx() *Tx {
return &Tx{
TxID: tx.TxID,
FromIdx: tx.FromIdx,
ToIdx: tx.ToIdx,
TokenID: tx.TokenID,
Amount: tx.Amount,
Nonce: tx.Nonce,
Fee: tx.Fee,
Type: tx.Type,
}
}
// PoolL2TxStatus is a struct that represents the status of a L2 transaction
type PoolL2TxStatus string
// PoolL2TxState is a struct that represents the status of a L2 transaction
type PoolL2TxState string
const (
// PoolL2TxStatusPending represents a valid L2Tx that hasn't started the forging process
PoolL2TxStatusPending PoolL2TxStatus = "Pending"
// PoolL2TxStatusForging represents a valid L2Tx that has started the forging process
PoolL2TxStatusForging PoolL2TxStatus = "Forging"
// PoolL2TxStatusForged represents a L2Tx that has already been forged
PoolL2TxStatusForged PoolL2TxStatus = "Forged"
// PoolL2TxStatusInvalid represents a L2Tx that has been invalidated
PoolL2TxStatusInvalid PoolL2TxStatus = "Invalid"
// PoolL2TxStatePending represents a valid L2Tx that hasn't started the forging process
PoolL2TxStatePending PoolL2TxState = "pend"
// PoolL2TxStateForging represents a valid L2Tx that has started the forging process
PoolL2TxStateForging PoolL2TxState = "fing"
// PoolL2TxStateForged represents a L2Tx that has already been forged
PoolL2TxStateForged PoolL2TxState = "fged"
// PoolL2TxStateInvalid represents a L2Tx that has been invalidated
PoolL2TxStateInvalid PoolL2TxState = "invl"
)

+ 13
- 13
common/tx.go

@ -28,19 +28,6 @@ func IdxFromBigInt(b *big.Int) (Idx, error) {
return Idx(uint32(b.Int64())), nil
}
// Tx is a struct that represents a Hermez network transaction
type Tx struct {
TxID TxID
FromIdx Idx // FromIdx is used by L1Tx/Deposit to indicate the Idx receiver of the L1Tx.LoadAmount (deposit)
ToIdx Idx // ToIdx is ignored in L1Tx/Deposit, but used in the L1Tx/DepositAndTransfer
TokenID TokenID
Amount *big.Int
Nonce uint64 // effective 48 bits used
Fee FeeSelector
Type TxType // optional, descrives which kind of tx it's
BatchNum BatchNum // batchNum in which this tx was forged. Presence indicates "forged" state.
}
// TxID is the identifier of a Hermez network transaction
type TxID Hash // Hash is a guess
@ -71,3 +58,16 @@ const (
// TxTypeTransferToBJJ TBD
TxTypeTransferToBJJ TxType = "TxTypeTransferToBJJ"
)
// Tx is a struct used by the TxSelector & BatchBuilder as a generic type generated from L1Tx & PoolL2Tx
type Tx struct {
TxID TxID
FromIdx Idx // FromIdx is used by L1Tx/Deposit to indicate the Idx receiver of the L1Tx.LoadAmount (deposit)
ToIdx Idx // ToIdx is ignored in L1Tx/Deposit, but used in the L1Tx/DepositAndTransfer
TokenID TokenID
Amount *big.Int
Nonce uint64 // effective 48 bits used
Fee FeeSelector
Type TxType // optional, descrives which kind of tx it's
BatchNum BatchNum // batchNum in which this tx was forged. Presence indicates "forged" state.
}

+ 4
- 4
coordinator/batch.go

@ -14,9 +14,9 @@ type BatchInfo struct {
serverProof *ServerProofInfo
zkInputs *common.ZKInputs
proof *Proof
L1UserTxsExtra []common.L1Tx
L1OperatorTxs []common.L1Tx
L2Txs []common.PoolL2Tx
L1UserTxsExtra []*common.L1Tx
L1OperatorTxs []*common.L1Tx
L2Txs []*common.PoolL2Tx
// FeesInfo
}
@ -31,7 +31,7 @@ func NewBatchInfo(batchNum uint64, serverProof *ServerProofInfo) BatchInfo {
// SetTxsInfo sets the l1UserTxs, l1OperatorTxs and l2Txs to the BatchInfo data
// structure
func (bi *BatchInfo) SetTxsInfo(l1UserTxsExtra, l1OperatorTxs []common.L1Tx, l2Txs []common.PoolL2Tx) {
func (bi *BatchInfo) SetTxsInfo(l1UserTxsExtra, l1OperatorTxs []*common.L1Tx, l2Txs []*common.PoolL2Tx) {
// TBD parameter: feesInfo
bi.L1UserTxsExtra = l1UserTxsExtra
bi.L1OperatorTxs = l1OperatorTxs

+ 4
- 4
coordinator/coordinator.go

@ -98,14 +98,14 @@ func (c *Coordinator) forgeSequence() error {
c.batchNum = c.batchNum + 1
batchInfo := NewBatchInfo(c.batchNum, serverProofInfo) // to accumulate metadata of the batch
var l2Txs []common.PoolL2Tx
var l2Txs []*common.PoolL2Tx
// var feesInfo
var l1UserTxsExtra, l1OperatorTxs []common.L1Tx
var l1UserTxsExtra, l1OperatorTxs []*common.L1Tx
// 1. Decide if we forge L2Tx or L1+L2Tx
if c.shouldL1L2Batch() {
// 2a: L1+L2 txs
// l1UserTxs, toForgeL1TxsNumber := c.synchronizer.GetNextL1UserTxs() // TODO once synchronizer is ready, uncomment
var l1UserTxs []common.L1Tx = nil // tmp, depends on synchronizer
var l1UserTxs []*common.L1Tx = nil // tmp, depends on synchronizer
l1UserTxsExtra, l1OperatorTxs, l2Txs, err = c.txsel.GetL1L2TxSelection(c.batchNum, l1UserTxs) // TODO once feesInfo is added to method return, add the var
if err != nil {
return err
@ -210,7 +210,7 @@ func (c *Coordinator) purgeRemoveByTimeout() error {
return nil
}
func (c *Coordinator) purgeInvalidDueToL2TxsSelection(l2Txs []common.PoolL2Tx) error {
func (c *Coordinator) purgeInvalidDueToL2TxsSelection(l2Txs []*common.PoolL2Tx) error {
return nil
}

+ 9
- 11
db/historydb/migrations/001_init.sql

@ -10,7 +10,7 @@ CREATE TABLE slot_min_prices (
min_prices VARCHAR(200) NOT NULL
);
CREATE TABLE coordiantor (
CREATE TABLE coordianator (
forger_addr BYTEA NOT NULL,
eth_block_num BIGINT NOT NULL REFERENCES block (eth_block_num) ON DELETE CASCADE,
beneficiary_addr BYTEA NOT NULL,
@ -59,19 +59,17 @@ CREATE TABLE token (
CREATE TABLE l1tx (
tx_id BYTEA PRIMARY KEY,
from_idx BIGINT NOT NULL,
to_idx BIGINT NOT NULL,
token_id INT NOT NULL REFERENCES token (token_id),
amount NUMERIC NOT NULL,
nonce BIGINT NOT NULL,
fee INT NOT NULL,
eth_block_num BIGINT NOT NULL REFERENCES block (eth_block_num) ON DELETE CASCADE,
to_forge_l1_txs_num BIGINT NOT NULL,
position INT NOT NULL,
origin_user BOOLEAN NOT NULL,
user_origin BOOLEAN NOT NULL,
from_idx BIGINT NOT NULL,
from_eth_addr BYTEA NOT NULL,
from_bjj BYTEA NOT NULL,
load_amount BYTEA NOT NULL
to_idx BIGINT NOT NULL,
token_id INT NOT NULL REFERENCES token (token_id),
amount NUMERIC NOT NULL,
load_amount BYTEA NOT NULL,
eth_block_num BIGINT NOT NULL REFERENCES block (eth_block_num) ON DELETE CASCADE
);
CREATE TABLE l2tx (
@ -101,6 +99,6 @@ DROP TABLE token;
DROP TABLE bid;
DROP TABLE exit_tree;
DROP TABLE batch;
DROP TABLE coordiantor;
DROP TABLE coordianator;
DROP TABLE slot_min_prices;
DROP TABLE block;

+ 72
- 17
db/l2db/l2db.go

@ -1,17 +1,24 @@
package l2db
import (
"fmt"
"strconv"
"time"
eth "github.com/ethereum/go-ethereum/common"
"github.com/gobuffalo/packr/v2"
"github.com/hermeznetwork/hermez-node/common"
"github.com/jinzhu/gorm"
"github.com/hermeznetwork/hermez-node/db"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq" // driver for postgres DB
migrate "github.com/rubenv/sql-migrate"
"github.com/russross/meddler"
)
// L2DB stores L2 txs and authorization registers received by the coordinator and keeps them until they are no longer relevant
// due to them being forged or invalid after a safety period
type L2DB struct {
db *gorm.DB
db *sqlx.DB
safetyPeriod uint16
ttl time.Duration
maxTxs uint32
@ -25,23 +32,28 @@ type L2DB struct {
// (to prevent tx that won't ever be forged to stay there, will be used if maxTxs is exceeded).
// autoPurgePeriod will be used as delay between calls to Purge. If the value is 0, it will be disabled.
func NewL2DB(
dbDialect, dbArgs string,
port int, host, user, password, dbname string,
safetyPeriod uint16,
maxTxs uint32,
TTL time.Duration,
) (*L2DB, error) {
// init meddler
db.InitMeddler()
meddler.Default = meddler.PostgreSQL
// Stablish DB connection
db, err := gorm.Open(dbDialect, dbArgs)
psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
db, err := sqlx.Connect("postgres", psqlconn)
if err != nil {
return nil, err
}
// Create or update SQL schemas
// WARNING: AutoMigrate will ONLY create tables, missing columns and missing indexes,
// and WON’T change existing column’s type or delete unused columns to protect your data.
// more info: http://gorm.io/docs/migration.html
db.AutoMigrate(&common.PoolL2Tx{})
// TODO: db.AutoMigrate(&common.RegisterAuthorization{})
// Run DB migrations
migrations := &migrate.PackrMigrationSource{
Box: packr.New("history-migrations", "./migrations"),
}
if _, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up); err != nil {
return nil, err
}
return &L2DB{
db: db,
@ -53,38 +65,66 @@ func NewL2DB(
// AddTx inserts a tx into the L2DB
func (l2db *L2DB) AddTx(tx *common.PoolL2Tx) error {
return nil
return meddler.Insert(l2db.db, "tx_pool", tx)
}
// AddAccountCreationAuth inserts an account creation authorization into the DB
func (l2db *L2DB) AddAccountCreationAuth(auth *common.AccountCreationAuth) error { // TODO: AddRegisterAuthorization(auth &common.RegisterAuthorization)
func (l2db *L2DB) AddAccountCreationAuth(auth *common.AccountCreationAuth) error {
// TODO: impl
return nil
}
// GetTx return the specified Tx
func (l2db *L2DB) GetTx(txID common.TxID) (*common.PoolL2Tx, error) {
return nil, nil
tx := new(common.PoolL2Tx)
return tx, meddler.QueryRow(
l2db.db, tx,
"SELECT * FROM tx_pool WHERE tx_id = $1;",
txID,
)
}
// GetPendingTxs return all the pending txs of the L2DB
func (l2db *L2DB) GetPendingTxs() ([]common.PoolL2Tx, error) {
return nil, nil
func (l2db *L2DB) GetPendingTxs() ([]*common.PoolL2Tx, error) {
var txs []*common.PoolL2Tx
err := meddler.QueryAll(
l2db.db, &txs,
"SELECT * FROM tx_pool WHERE state = $1",
common.PoolL2TxStatePending,
)
return txs, err
}
// GetAccountCreationAuth return the authorization to make registers of an Ethereum address
func (l2db *L2DB) GetAccountCreationAuth(ethAddr eth.Address) (*common.AccountCreationAuth, error) {
// TODO: impl
return nil, nil
}
// StartForging updates the state of the transactions that will begin the forging process.
// The state of the txs referenced by txIDs will be changed from Pending -> Forging
func (l2db *L2DB) StartForging(txIDs []common.TxID) error {
return nil
func (l2db *L2DB) StartForging(txIDs []common.TxID, batchNum common.BatchNum) error {
query, args, err := sqlx.In(
`UPDATE tx_pool
SET state = ?, batch_num = ?
WHERE state = ? AND tx_id IN (?);`,
string(common.PoolL2TxStateForging),
strconv.Itoa(int(batchNum)),
string(common.PoolL2TxStatePending),
txIDs,
)
if err != nil {
return err
}
query = l2db.db.Rebind(query)
_, err = l2db.db.Exec(query, args...)
return err
}
// DoneForging updates the state of the transactions that have been forged
// so the state of the txs referenced by txIDs will be changed from Forging -> Forged
func (l2db *L2DB) DoneForging(txIDs []common.TxID) error {
// TODO: impl
return nil
}
@ -97,18 +137,33 @@ func (l2db *L2DB) InvalidateTxs(txIDs []common.TxID) error {
// CheckNonces invalidate txs with nonces that are smaller than their respective accounts nonces.
// The state of the affected txs will be changed from Pending -> Invalid
func (l2db *L2DB) CheckNonces(updatedAccounts []common.Account) error {
// TODO: impl
return nil
}
// GetTxsByAbsoluteFeeUpdate return the txs that have an AbsoluteFee updated before olderThan
func (l2db *L2DB) GetTxsByAbsoluteFeeUpdate(olderThan time.Time) ([]*common.PoolL2Tx, error) {
// TODO: impl
return nil, nil
}
// UpdateTxs update existing txs from the pool (TxID must exist)
func (l2db *L2DB) UpdateTxs(txs []*common.PoolL2Tx) error {
// TODO: impl
return nil
}
// Reorg updates the state of txs that were updated in a batch that has been discarted due to a blockchian reorg.
// The state of the affected txs can change form Forged -> Pending or from Invalid -> Pending
func (l2db *L2DB) Reorg(lastValidBatch common.BatchNum) error {
// TODO: impl
return nil
}
// Purge deletes transactions that have been forged or marked as invalid for longer than the safety period
// it also deletes txs that has been in the L2DB for longer than the ttl if maxTxs has been exceeded
func (l2db *L2DB) Purge() error {
// TODO: impl
return nil
}

+ 182
- 0
db/l2db/l2db_test.go

@ -0,0 +1,182 @@
package l2db
import (
"fmt"
"math/big"
"os"
"strconv"
"testing"
"time"
eth "github.com/ethereum/go-ethereum/common"
"github.com/hermeznetwork/hermez-node/common"
"github.com/iden3/go-iden3-crypto/babyjub"
"github.com/stretchr/testify/assert"
)
var l2DB *L2DB
// In order to run the test you need to run a Posgres DB with
// a database named "l2" that is accessible by
// user: "hermez"
// pass: set it using the env var POSTGRES_PASS
// This can be achieved by running: POSTGRES_PASS=your_strong_pass && sudo docker run --rm --name hermez-db-test -p 5432:5432 -e POSTGRES_DB=history -e POSTGRES_USER=hermez -e POSTGRES_PASSWORD=$POSTGRES_PASS -d postgres && sleep 2s && sudo docker exec -it hermez-db-test psql -a history -U hermez -c "CREATE DATABASE l2;"
// After running the test you can stop the container by running: sudo docker kill hermez-ydb-test
// If you already did that for the HistoryDB you don't have to do it again
func TestMain(m *testing.M) {
// init DB
var err error
pass := os.Getenv("POSTGRES_PASS")
l2DB, err = NewL2DB(5432, "localhost", "hermez", pass, "l2", 10, 512, 24*time.Hour)
if err != nil {
panic(err)
}
// Run tests
result := m.Run()
// Close DB
if err := l2DB.Close(); err != nil {
fmt.Println("Error closing the history DB:", err)
}
os.Exit(result)
}
func TestAddTx(t *testing.T) {
const nInserts = 20
cleanDB()
txs := genTxs(nInserts)
for _, tx := range txs {
err := l2DB.AddTx(tx)
assert.NoError(t, err)
fetchedTx, err := l2DB.GetTx(tx.TxID)
assert.NoError(t, err)
assert.Equal(t, tx.Timestamp.Unix(), fetchedTx.Timestamp.Unix())
tx.Timestamp = fetchedTx.Timestamp
assert.Equal(t, tx.AbsoluteFeeUpdate.Unix(), fetchedTx.AbsoluteFeeUpdate.Unix())
tx.Timestamp = fetchedTx.Timestamp
tx.AbsoluteFeeUpdate = fetchedTx.AbsoluteFeeUpdate
assert.Equal(t, tx, fetchedTx)
}
}
func BenchmarkAddTx(b *testing.B) {
const nInserts = 20
cleanDB()
txs := genTxs(nInserts)
now := time.Now()
for _, tx := range txs {
_ = l2DB.AddTx(tx)
}
elapsedTime := time.Since(now)
fmt.Println("Time to insert 2048 txs:", elapsedTime)
}
func TestGetPending(t *testing.T) {
const nInserts = 20
cleanDB()
txs := genTxs(nInserts)
var pendingTxs []*common.PoolL2Tx
for _, tx := range txs {
err := l2DB.AddTx(tx)
assert.NoError(t, err)
if tx.State == common.PoolL2TxStatePending {
pendingTxs = append(pendingTxs, tx)
}
}
fetchedTxs, err := l2DB.GetPendingTxs()
assert.NoError(t, err)
assert.Equal(t, len(pendingTxs), len(fetchedTxs))
for i, fetchedTx := range fetchedTxs {
assert.Equal(t, pendingTxs[i].Timestamp.Unix(), fetchedTx.Timestamp.Unix())
pendingTxs[i].Timestamp = fetchedTx.Timestamp
assert.Equal(t, pendingTxs[i].AbsoluteFeeUpdate.Unix(), fetchedTx.AbsoluteFeeUpdate.Unix())
pendingTxs[i].AbsoluteFeeUpdate = fetchedTx.AbsoluteFeeUpdate
assert.Equal(t, pendingTxs[i], fetchedTx)
}
}
func TestStartForging(t *testing.T) {
const nInserts = 24
const fakeBlockNum = 33
cleanDB()
txs := genTxs(nInserts)
var startForgingTxs []*common.PoolL2Tx
var startForgingTxIDs []common.TxID
randomizer := 0
for _, tx := range txs {
err := l2DB.AddTx(tx)
assert.NoError(t, err)
if tx.State == common.PoolL2TxStatePending && randomizer%2 == 0 {
randomizer++
startForgingTxs = append(startForgingTxs, tx)
startForgingTxIDs = append(startForgingTxIDs, tx.TxID)
}
if tx.State == common.PoolL2TxStateForging {
startForgingTxs = append(startForgingTxs, tx)
}
}
fmt.Println(startForgingTxs) // TODO added print here to avoid lint complaining about startForgingTxs not being used
err := l2DB.StartForging(startForgingTxIDs, fakeBlockNum)
assert.NoError(t, err)
// TODO: Fetch txs and check that they've been updated correctly
}
func genTxs(n int) []*common.PoolL2Tx {
// WARNING: This tx doesn't follow the protocol (signature, txID, ...)
// it's just to test geting/seting from/to the DB.
// Type and RqTxCompressedData: not initialized because it's not stored
// on the DB and add noise when checking results.
txs := make([]*common.PoolL2Tx, 0, n)
privK := babyjub.NewRandPrivKey()
for i := 0; i < n; i++ {
var state common.PoolL2TxState
if i%4 == 0 {
state = common.PoolL2TxStatePending
} else if i%4 == 1 {
state = common.PoolL2TxStateInvalid
} else if i%4 == 2 {
state = common.PoolL2TxStateForging
} else if i%4 == 3 {
state = common.PoolL2TxStateForged
}
tx := &common.PoolL2Tx{
TxID: common.TxID(common.Hash([]byte(strconv.Itoa(i)))),
FromIdx: 47,
ToIdx: 96,
ToEthAddr: eth.BigToAddress(big.NewInt(234523534)),
ToBJJ: privK.Public(),
TokenID: 73,
Amount: big.NewInt(3487762374627846747),
Fee: 99,
Nonce: 28,
State: state,
Signature: *privK.SignPoseidon(big.NewInt(674238462)),
Timestamp: time.Now().UTC(),
}
if i%2 == 0 { // Optional parameters: rq
tx.RqFromIdx = 893
tx.RqToIdx = 334
tx.RqToEthAddr = eth.BigToAddress(big.NewInt(239457111187))
tx.RqToBJJ = privK.Public()
tx.RqTokenID = 222
tx.RqAmount = big.NewInt(3487762374627846747)
tx.RqFee = 11
tx.RqNonce = 78
}
if i%3 == 0 { // Optional parameters: things that get updated "a posteriori"
tx.BatchNum = 489
tx.AbsoluteFee = 39.12345
tx.AbsoluteFeeUpdate = time.Now().UTC()
}
txs = append(txs, tx)
}
return txs
}
func cleanDB() {
if _, err := l2DB.db.Exec("DELETE FROM tx_pool"); err != nil {
panic(err)
}
if _, err := l2DB.db.Exec("DELETE FROM account_creation_auth"); err != nil {
panic(err)
}
}

+ 37
- 0
db/l2db/migrations/001_init.sql

@ -0,0 +1,37 @@
-- +migrate Up
CREATE TABLE tx_pool (
tx_id BYTEA PRIMARY KEY,
from_idx BIGINT NOT NULL,
to_idx BIGINT NOT NULL,
to_eth_addr BYTEA NOT NULL,
to_bjj BYTEA NOT NULL,
token_id INT NOT NULL,
amount BYTEA NOT NULL,
fee SMALLINT NOT NULL,
nonce BIGINT NOT NULL,
state CHAR(4) NOT NULL,
batch_num BIGINT,
rq_from_idx BIGINT,
rq_to_idx BIGINT,
rq_to_eth_addr BYTEA,
rq_to_bjj BYTEA,
rq_token_id INT,
rq_amount BYTEA,
rq_fee SMALLINT,
rq_nonce BIGINT,
signature BYTEA NOT NULL,
timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL,
absolute_fee NUMERIC,
absolute_fee_update TIMESTAMP WITHOUT TIME ZONE
);
CREATE TABLE account_creation_auth (
eth_addr BYTEA PRIMARY KEY,
bjj BYTEA NOT NULL,
account_creation_auth_sig BYTEA NOT NULL,
timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL
);
-- +migrate Down
DROP TABLE account_creation_auth;
DROP TABLE tx_pool;

+ 40
- 0
db/utils.go

@ -13,6 +13,7 @@ import (
// InitMeddler registers tags to be used to read/write from SQL DBs using meddler
func InitMeddler() {
meddler.Register("bigint", BigIntMeddler{})
meddler.Register("bigintnull", BigIntNullMeddler{})
}
// BulkInsert performs a bulk insert with a single statement into the specified table. Example:
@ -76,3 +77,42 @@ func (b BigIntMeddler) PreWrite(fieldPtr interface{}) (saveValue interface{}, er
return str, nil
}
// BigIntNullMeddler encodes or decodes the field value to or from JSON
type BigIntNullMeddler struct{}
// PreRead is called before a Scan operation for fields that have the BigIntNullMeddler
func (b BigIntNullMeddler) PreRead(fieldAddr interface{}) (scanTarget interface{}, err error) {
return &fieldAddr, nil
}
// PostRead is called after a Scan operation for fields that have the BigIntNullMeddler
func (b BigIntNullMeddler) PostRead(fieldPtr, scanTarget interface{}) error {
sv := reflect.ValueOf(scanTarget)
if sv.Elem().IsNil() {
// null column, so set target to be zero value
fv := reflect.ValueOf(fieldPtr)
fv.Elem().Set(reflect.Zero(fv.Elem().Type()))
return nil
}
// not null
encoded := new([]byte)
refEnc := reflect.ValueOf(encoded)
refEnc.Elem().Set(sv.Elem().Elem())
data, err := base64.StdEncoding.DecodeString(string(*encoded))
if err != nil {
return fmt.Errorf("big.Int decode error: %v", err)
}
field := fieldPtr.(**big.Int)
*field = new(big.Int).SetBytes(data)
return nil
}
// PreWrite is called before an Insert or Update operation for fields that have the BigIntNullMeddler
func (b BigIntNullMeddler) PreWrite(fieldPtr interface{}) (saveValue interface{}, err error) {
field := fieldPtr.(*big.Int)
if field == nil {
return nil, nil
}
return base64.StdEncoding.EncodeToString(field.Bytes()), nil
}

+ 2
- 1
go.mod

@ -5,9 +5,10 @@ go 1.14
require (
github.com/dghubble/sling v1.3.0
github.com/ethereum/go-ethereum v1.9.17
github.com/go-sql-driver/mysql v1.5.0 // indirect
github.com/gobuffalo/packr/v2 v2.8.0
github.com/iden3/go-iden3-core v0.0.8
github.com/iden3/go-iden3-crypto v0.0.6-0.20200806115047-327a8175d6eb
github.com/iden3/go-iden3-crypto v0.0.6-0.20200814110325-70841d78e7d8
github.com/iden3/go-merkletree v0.0.0-20200815144208-1f1bd54b93ae
github.com/jinzhu/gorm v1.9.15
github.com/jmoiron/sqlx v1.2.0

+ 5
- 5
go.sum

@ -111,7 +111,6 @@ github.com/dchest/blake512 v1.0.0/go.mod h1:FV1x7xPPLWukZlpDpWQ88rF/SFwZ5qbskrzh
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dghubble/sling v1.3.0 h1:pZHjCJq4zJvc6qVQ5wN1jo5oNZlNE0+8T/h0XeXBUKU=
github.com/dghubble/sling v1.3.0/go.mod h1:XXShWaBWKzNLhu2OxikSNFrlsvowtz4kyRuXUG7oQKY=
@ -134,7 +133,6 @@ github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/ethereum/go-ethereum v1.9.12/go.mod h1:PvsVkQmhZFx92Y+h2ylythYlheEDt/uBgFbl61Js/jo=
github.com/ethereum/go-ethereum v1.9.13/go.mod h1:qwN9d1GLyDh0N7Ab8bMGd0H9knaji2jOBm2RrMGjXls=
@ -272,6 +270,10 @@ github.com/iden3/go-iden3-crypto v0.0.6-0.20200723082457-29a66457f0bf h1:/7L5dEq
github.com/iden3/go-iden3-crypto v0.0.6-0.20200723082457-29a66457f0bf/go.mod h1:XKw1oDwYn2CIxKOtr7m/mL5jMn4mLOxAxtZBRxQBev8=
github.com/iden3/go-iden3-crypto v0.0.6-0.20200806115047-327a8175d6eb h1:4Vqq5ZoqQd9t3Uj7MUbak7eHmtaYs8mqQoo8T1DS7tA=
github.com/iden3/go-iden3-crypto v0.0.6-0.20200806115047-327a8175d6eb/go.mod h1:XKw1oDwYn2CIxKOtr7m/mL5jMn4mLOxAxtZBRxQBev8=
github.com/iden3/go-iden3-crypto v0.0.6-0.20200813145745-66519124ca17 h1:aqy++85MX/ylQyKkuoK7zJ8hHEW5mT4mLL6pguo+X8Y=
github.com/iden3/go-iden3-crypto v0.0.6-0.20200813145745-66519124ca17/go.mod h1:oBgthFLboAWi9feaBUFy7OxEcyn9vA1khHSL/WwWFyg=
github.com/iden3/go-iden3-crypto v0.0.6-0.20200814110325-70841d78e7d8 h1:1QeVvr/DjiBpk8tw3vBfYD+zs0JeYl3fPIUA/foDq3w=
github.com/iden3/go-iden3-crypto v0.0.6-0.20200814110325-70841d78e7d8/go.mod h1:oBgthFLboAWi9feaBUFy7OxEcyn9vA1khHSL/WwWFyg=
github.com/iden3/go-merkletree v0.0.0-20200723202738-75e24244a1e3 h1:QR6LqG1HqqPE4myiLR73QFIieAfwODG3bqo1juuaqVI=
github.com/iden3/go-merkletree v0.0.0-20200723202738-75e24244a1e3/go.mod h1:Fc49UeywIsj8nUfb5lxBzmWrMeMmqzTJ5F0OcjdiEME=
github.com/iden3/go-merkletree v0.0.0-20200806171216-dd600560e44c h1:EzVMSVkwKdfcOR1a+rZe9dfbtAj6KdJnBxOEZ5B+gCQ=
@ -293,11 +295,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
github.com/jinzhu/gorm v1.9.15 h1:OdR1qFvtXktlxk73XFYMiYn9ywzTwytqe4QkuMRqc38=
github.com/jinzhu/gorm v1.9.15/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
@ -608,6 +607,7 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=

+ 5
- 5
txselector/txselector.go

@ -9,7 +9,7 @@ import (
)
// txs implements the interface Sort for an array of Tx
type txs []common.PoolL2Tx
type txs []*common.PoolL2Tx
func (t txs) Len() int {
return len(t)
@ -61,7 +61,7 @@ func (txsel *TxSelector) Reset(batchNum uint64) error {
}
// GetL2TxSelection returns a selection of the L2Txs for the next batch, from the L2DB pool
func (txsel *TxSelector) GetL2TxSelection(batchNum uint64) ([]common.PoolL2Tx, error) {
func (txsel *TxSelector) GetL2TxSelection(batchNum uint64) ([]*common.PoolL2Tx, error) {
// get pending l2-tx from tx-pool
l2TxsRaw, err := txsel.l2db.GetPendingTxs() // once l2db ready, maybe use parameter 'batchNum'
if err != nil {
@ -90,7 +90,7 @@ func (txsel *TxSelector) GetL2TxSelection(batchNum uint64) ([]common.PoolL2Tx, e
}
// GetL1L2TxSelection returns the selection of L1 + L2 txs
func (txsel *TxSelector) GetL1L2TxSelection(batchNum uint64, l1txs []common.L1Tx) ([]common.L1Tx, []common.L1Tx, []common.PoolL2Tx, error) {
func (txsel *TxSelector) GetL1L2TxSelection(batchNum uint64, l1txs []*common.L1Tx) ([]*common.L1Tx, []*common.L1Tx, []*common.PoolL2Tx, error) {
// apply l1-user-tx to localAccountDB
// create new leaves
// update balances
@ -113,7 +113,7 @@ func (txsel *TxSelector) GetL1L2TxSelection(batchNum uint64, l1txs []common.L1Tx
}
// prepare (from the selected l2txs) pending to create from the AccountCreationAuthsDB
var accountCreationAuths []common.Account
var accountCreationAuths []*common.Account
// TODO once DB ready:
// if tx.ToIdx is in AccountCreationAuthsDB, take it and add it to
// the array 'accountCreationAuths'
@ -155,7 +155,7 @@ func (txsel *TxSelector) getL2Profitable(txs txs, max uint64) txs {
sort.Sort(txs)
return txs[:max]
}
func (txsel *TxSelector) createL1OperatorTxForL2Tx(accounts []common.Account) []common.L1Tx {
func (txsel *TxSelector) createL1OperatorTxForL2Tx(accounts []*common.Account) []*common.L1Tx {
//
return nil
}

Loading…
Cancel
Save