Browse Source

Update SQL schemas

feature/sql-semaphore1
a_bennassar 4 years ago
committed by arnaucube
parent
commit
cb1b820256
12 changed files with 418 additions and 108 deletions
  1. +5
    -5
      batchbuilder/batchbuilder.go
  2. +15
    -11
      common/l1tx.go
  3. +15
    -2
      common/l2tx.go
  4. +38
    -29
      common/pooll2tx.go
  5. +0
    -13
      common/tx.go
  6. +9
    -11
      db/historydb/migrations/001_init.sql
  7. +72
    -17
      db/l2db/l2db.go
  8. +181
    -0
      db/l2db/l2db_test.go
  9. +37
    -0
      db/l2db/migrations/001_init.sql
  10. +40
    -0
      db/utils.go
  11. +1
    -0
      go.mod
  12. +5
    -20
      go.sum

+ 5
- 5
batchbuilder/batchbuilder.go

@ -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
}
@ -106,7 +106,7 @@ func (bb *BatchBuilder) processL1Tx(tx common.L1Tx) error {
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
}
@ -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 {

+ 15
- 11
common/l1tx.go

@ -9,15 +9,19 @@ 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
}

+ 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
}

+ 38
- 29
common/pooll2tx.go

@ -10,37 +10,46 @@ 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
}
// 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"
)

+ 0
- 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

+ 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
}

+ 181
- 0
db/l2db/l2db_test.go

@ -0,0 +1,181 @@
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)
}
}
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
}

+ 1
- 0
go.mod

@ -5,6 +5,7 @@ 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

+ 5
- 20
go.sum

@ -19,7 +19,6 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
@ -35,7 +34,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
@ -111,8 +109,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=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
@ -134,8 +130,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=
github.com/ethereum/go-ethereum v1.9.17 h1:2D02O8KcoyQHxfizvMi0vGXXzFIkQTMeKXwt0+4SYEA=
@ -270,12 +264,14 @@ github.com/iden3/go-iden3-core v0.0.8/go.mod h1:URNjIhMql6sEbWubIGrjJdw5wHCE1Pk1
github.com/iden3/go-iden3-crypto v0.0.5/go.mod h1:XKw1oDwYn2CIxKOtr7m/mL5jMn4mLOxAxtZBRxQBev8=
github.com/iden3/go-iden3-crypto v0.0.6-0.20200723082457-29a66457f0bf h1:/7L5dEqctuzJY2g8OEQct+1Y+n2sMKyd4JoYhw2jy1s=
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-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=
github.com/iden3/go-merkletree v0.0.0-20200806171216-dd600560e44c/go.mod h1:Fc49UeywIsj8nUfb5lxBzmWrMeMmqzTJ5F0OcjdiEME=
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-20200807083900-f6f82d8375d5 h1:qvWSCt3AYxj65uTdW6lLSKlrbckcHghOAW4TwdfJ+N8=
github.com/iden3/go-merkletree v0.0.0-20200807083900-f6f82d8375d5/go.mod h1:Fc49UeywIsj8nUfb5lxBzmWrMeMmqzTJ5F0OcjdiEME=
github.com/iden3/go-merkletree v0.0.0-20200815105542-2277604e65dd h1:AkPlODLWkgQT9p1k6LnO3aRLIIeVL9ENof/YW87QL14=
@ -293,12 +289,6 @@ 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=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
@ -332,7 +322,6 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
@ -362,7 +351,6 @@ github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@ -558,7 +546,6 @@ golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -583,7 +570,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -604,10 +590,9 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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=

Loading…
Cancel
Save