Browse Source

Extend statedb and use prefixes, add debugapi

In statedb:
- Store all values using prefixes for keys to allow iteration
- Add methods MTGetRoot, GetAccounts

Implement debugapi, an http server with debugging endpoints:
- debugAPI.GET("sdb/batchnum", a.handleCurrentBatch)
- debugAPI.GET("sdb/mtroot", a.handleMTRoot)
- debugAPI.GET("sdb/accounts", a.handleAccounts)
- debugAPI.GET("sdb/accounts/:Idx", a.handleAccount)
feature/sql-semaphore1
Eduard S 3 years ago
parent
commit
6afbda5302
8 changed files with 344 additions and 32 deletions
  1. +63
    -12
      db/statedb/statedb.go
  2. +40
    -14
      db/statedb/statedb_test.go
  3. +1
    -1
      db/statedb/txprocessors.go
  4. +4
    -4
      db/statedb/utils.go
  5. +3
    -1
      go.mod
  6. +11
    -0
      go.sum
  7. +121
    -0
      test/debugapi/debugapi.go
  8. +101
    -0
      test/debugapi/debugapi_test.go

+ 63
- 12
db/statedb/statedb.go

@ -3,6 +3,7 @@ package statedb
import (
"errors"
"fmt"
"math/big"
"os"
"os/exec"
"strconv"
@ -29,7 +30,18 @@ var (
ErrToIdxNotFound = errors.New("ToIdx can not be found")
// KeyCurrentBatch is used as key in the db to store the current BatchNum
KeyCurrentBatch = []byte("currentbatch")
KeyCurrentBatch = []byte("k:currentbatch")
// PrefixKeyIdx is the key prefix for idx in the db
PrefixKeyIdx = []byte("i:")
// PrefixKeyAccHash is the key prefix for account hash in the db
PrefixKeyAccHash = []byte("h:")
// PrefixKeyMT is the key prefix for merkle tree in the db
PrefixKeyMT = []byte("m:")
// PrefixKeyAddr is the key prefix for address in the db
PrefixKeyAddr = []byte("a:")
// PrefixKeyAddrBJJ is the key prefix for address-babyjubjub in the db
PrefixKeyAddrBJJ = []byte("ab:")
)
const (
@ -80,7 +92,7 @@ func NewStateDB(path string, typ TypeStateDB, nLevels int) (*StateDB, error) {
var mt *merkletree.MerkleTree = nil
if typ == TypeSynchronizer || typ == TypeBatchBuilder {
mt, err = merkletree.NewMerkleTree(sto, nLevels)
mt, err = merkletree.NewMerkleTree(sto.WithPrefix(PrefixKeyMT), nLevels)
if err != nil {
return nil, err
}
@ -236,7 +248,7 @@ func (s *StateDB) Reset(batchNum common.BatchNum) error {
if s.mt != nil {
// open the MT for the current s.db
mt, err := merkletree.NewMerkleTree(s.db, s.mt.MaxLevels())
mt, err := merkletree.NewMerkleTree(s.db.WithPrefix(PrefixKeyMT), s.mt.MaxLevels())
if err != nil {
return err
}
@ -251,6 +263,35 @@ func (s *StateDB) GetAccount(idx common.Idx) (*common.Account, error) {
return getAccountInTreeDB(s.db, idx)
}
// GetAccounts returns all the accounts in the db. Use for debugging pruposes
// only.
func (s *StateDB) GetAccounts() ([]common.Account, error) {
idxDB := s.db.WithPrefix(PrefixKeyIdx)
idxs := []common.Idx{}
// NOTE: Current implementation of Iterate in the pebble interface is
// not efficient, as it iterates over all keys. Improve it following
// this example: https://github.com/cockroachdb/pebble/pull/923/files
if err := idxDB.Iterate(func(k []byte, v []byte) (bool, error) {
idx, err := common.IdxFromBytes(k)
if err != nil {
return false, err
}
idxs = append(idxs, idx)
return true, nil
}); err != nil {
return nil, err
}
accs := []common.Account{}
for i := range idxs {
acc, err := s.GetAccount(idxs[i])
if err != nil {
return nil, err
}
accs = append(accs, *acc)
}
return accs, nil
}
// getAccountInTreeDB is abstracted from StateDB to be used from StateDB and
// from ExitTree. GetAccount returns the account for the given Idx
func getAccountInTreeDB(sto db.Storage, idx common.Idx) (*common.Account, error) {
@ -258,17 +299,22 @@ func getAccountInTreeDB(sto db.Storage, idx common.Idx) (*common.Account, error)
if err != nil {
return nil, err
}
vBytes, err := sto.Get(idxBytes[:])
vBytes, err := sto.Get(append(PrefixKeyIdx, idxBytes[:]...))
if err != nil {
return nil, err
}
accBytes, err := sto.Get(vBytes)
accBytes, err := sto.Get(append(PrefixKeyAccHash, vBytes...))
if err != nil {
return nil, err
}
var b [32 * common.NLeafElems]byte
copy(b[:], accBytes)
return common.AccountFromBytes(b)
account, err := common.AccountFromBytes(b)
if err != nil {
return nil, err
}
account.Idx = idx
return account, nil
}
// CreateAccount creates a new Account in the StateDB for the given Idx. If
@ -309,16 +355,16 @@ func createAccountInTreeDB(sto db.Storage, mt *merkletree.MerkleTree, idx common
if err != nil {
return nil, err
}
_, err = tx.Get(idxBytes[:])
_, err = tx.Get(append(PrefixKeyIdx, idxBytes[:]...))
if err != db.ErrNotFound {
return nil, ErrAccountAlreadyExists
}
err = tx.Put(v.Bytes(), accountBytes[:])
err = tx.Put(append(PrefixKeyAccHash, v.Bytes()...), accountBytes[:])
if err != nil {
return nil, err
}
err = tx.Put(idxBytes[:], v.Bytes())
err = tx.Put(append(PrefixKeyIdx, idxBytes[:]...), v.Bytes())
if err != nil {
return nil, err
}
@ -360,7 +406,7 @@ func updateAccountInTreeDB(sto db.Storage, mt *merkletree.MerkleTree, idx common
if err != nil {
return nil, err
}
err = tx.Put(v.Bytes(), accountBytes[:])
err = tx.Put(append(PrefixKeyAccHash, v.Bytes()...), accountBytes[:])
if err != nil {
return nil, err
}
@ -368,7 +414,7 @@ func updateAccountInTreeDB(sto db.Storage, mt *merkletree.MerkleTree, idx common
if err != nil {
return nil, err
}
err = tx.Put(idxBytes[:], v.Bytes())
err = tx.Put(append(PrefixKeyIdx, idxBytes[:]...), v.Bytes())
if err != nil {
return nil, err
}
@ -391,6 +437,11 @@ func (s *StateDB) MTGetProof(idx common.Idx) (*merkletree.CircomVerifierProof, e
return s.mt.GenerateCircomVerifierProof(idx.BigInt(), s.mt.Root())
}
// MTGetRoot returns the current root of the underlying Merkle Tree
func (s *StateDB) MTGetRoot() *big.Int {
return s.mt.Root().BigInt()
}
// LocalStateDB represents the local StateDB which allows to make copies from
// the synchronizer StateDB, and is used by the tx-selector and the
// batch-builder. LocalStateDB is an in-memory storage.
@ -462,7 +513,7 @@ func (l *LocalStateDB) Reset(batchNum common.BatchNum, fromSynchronizer bool) er
return err
}
// open the MT for the current s.db
mt, err := merkletree.NewMerkleTree(l.db, l.mt.MaxLevels())
mt, err := merkletree.NewMerkleTree(l.db.WithPrefix(PrefixKeyMT), l.mt.MaxLevels())
if err != nil {
return err
}

+ 40
- 14
db/statedb/statedb_test.go

@ -26,6 +26,7 @@ func newAccount(t *testing.T, i int) *common.Account {
address := ethCrypto.PubkeyToAddress(key.PublicKey)
return &common.Account{
Idx: common.Idx(256 + i),
TokenID: common.TokenID(i),
Nonce: common.Nonce(i),
Balance: big.NewInt(1000),
@ -124,7 +125,7 @@ func TestStateDBWithoutMT(t *testing.T) {
// create test accounts
var accounts []*common.Account
for i := 0; i < 100; i++ {
for i := 0; i < 4; i++ {
accounts = append(accounts, newAccount(t, i))
}
@ -136,22 +137,22 @@ func TestStateDBWithoutMT(t *testing.T) {
// add test accounts
for i := 0; i < len(accounts); i++ {
_, err = sdb.CreateAccount(common.Idx(i), accounts[i])
_, err = sdb.CreateAccount(accounts[i].Idx, accounts[i])
assert.Nil(t, err)
}
for i := 0; i < len(accounts); i++ {
existingAccount := common.Idx(i)
existingAccount := accounts[i].Idx
accGetted, err := sdb.GetAccount(existingAccount)
assert.Nil(t, err)
assert.Equal(t, accounts[i], accGetted)
}
// try already existing idx and get error
existingAccount := common.Idx(1)
existingAccount := common.Idx(256)
_, err = sdb.GetAccount(existingAccount) // check that exist
assert.Nil(t, err)
_, err = sdb.CreateAccount(common.Idx(1), accounts[1]) // check that can not be created twice
_, err = sdb.CreateAccount(common.Idx(256), accounts[1]) // check that can not be created twice
assert.NotNil(t, err)
assert.Equal(t, ErrAccountAlreadyExists, err)
@ -188,35 +189,35 @@ func TestStateDBWithMT(t *testing.T) {
// add test accounts
for i := 0; i < len(accounts); i++ {
_, err = sdb.CreateAccount(common.Idx(i), accounts[i])
_, err = sdb.CreateAccount(accounts[i].Idx, accounts[i])
assert.Nil(t, err)
}
for i := 0; i < len(accounts); i++ {
accGetted, err := sdb.GetAccount(common.Idx(i))
accGetted, err := sdb.GetAccount(accounts[i].Idx)
assert.Nil(t, err)
assert.Equal(t, accounts[i], accGetted)
}
// try already existing idx and get error
_, err = sdb.GetAccount(common.Idx(1)) // check that exist
_, err = sdb.GetAccount(common.Idx(256)) // check that exist
assert.Nil(t, err)
_, err = sdb.CreateAccount(common.Idx(1), accounts[1]) // check that can not be created twice
_, err = sdb.CreateAccount(common.Idx(256), accounts[1]) // check that can not be created twice
assert.NotNil(t, err)
assert.Equal(t, ErrAccountAlreadyExists, err)
_, err = sdb.MTGetProof(common.Idx(1))
_, err = sdb.MTGetProof(common.Idx(256))
assert.Nil(t, err)
// update accounts
for i := 0; i < len(accounts); i++ {
accounts[i].Nonce = accounts[i].Nonce + 1
_, err = sdb.UpdateAccount(common.Idx(i), accounts[i])
_, err = sdb.UpdateAccount(accounts[i].Idx, accounts[i])
assert.Nil(t, err)
}
a, err := sdb.GetAccount(common.Idx(1)) // check that account value has been updated
a, err := sdb.GetAccount(common.Idx(256)) // check that account value has been updated
assert.Nil(t, err)
assert.Equal(t, accounts[1].Nonce, a.Nonce)
assert.Equal(t, accounts[0].Nonce, a.Nonce)
}
func TestCheckpoints(t *testing.T) {
@ -234,7 +235,7 @@ func TestCheckpoints(t *testing.T) {
// add test accounts
for i := 0; i < len(accounts); i++ {
_, err = sdb.CreateAccount(common.Idx(i), accounts[i])
_, err = sdb.CreateAccount(accounts[i].Idx, accounts[i])
assert.Nil(t, err)
}
@ -334,6 +335,31 @@ func TestCheckpoints(t *testing.T) {
}
}
func TestStateDBGetAccounts(t *testing.T) {
dir, err := ioutil.TempDir("", "tmpdb")
require.Nil(t, err)
sdb, err := NewStateDB(dir, TypeTxSelector, 0)
assert.Nil(t, err)
// create test accounts
var accounts []common.Account
for i := 0; i < 16; i++ {
account := newAccount(t, i)
accounts = append(accounts, *account)
}
// add test accounts
for i := range accounts {
_, err = sdb.CreateAccount(accounts[i].Idx, &accounts[i])
require.Nil(t, err)
}
dbAccounts, err := sdb.GetAccounts()
require.Nil(t, err)
assert.Equal(t, accounts, dbAccounts)
}
func printCheckpoints(t *testing.T, path string) {
files, err := ioutil.ReadDir(path)
assert.Nil(t, err)

+ 1
- 1
db/statedb/txprocessors.go

@ -14,7 +14,7 @@ import (
var (
// keyidx is used as key in the db to store the current Idx
keyidx = []byte("idx")
keyidx = []byte("k:idx")
)
func (s *StateDB) resetZKInputs() {

+ 4
- 4
db/statedb/utils.go

@ -51,12 +51,12 @@ func (s *StateDB) setIdxByEthAddrBJJ(idx common.Idx, addr ethCommon.Address, pk
if err != nil {
return err
}
err = tx.Put(k, idxBytes[:])
err = tx.Put(append(PrefixKeyAddrBJJ, k...), idxBytes[:])
if err != nil {
return err
}
// store Addr-idx
err = tx.Put(addr.Bytes(), idxBytes[:])
err = tx.Put(append(PrefixKeyAddr, addr.Bytes()...), idxBytes[:])
if err != nil {
return err
}
@ -71,7 +71,7 @@ func (s *StateDB) setIdxByEthAddrBJJ(idx common.Idx, addr ethCommon.Address, pk
// Ethereum Address. Will return common.Idx(0) and error in case that Idx is
// not found in the StateDB.
func (s *StateDB) GetIdxByEthAddr(addr ethCommon.Address) (common.Idx, error) {
b, err := s.db.Get(addr.Bytes())
b, err := s.db.Get(append(PrefixKeyAddr, addr.Bytes()...))
if err != nil {
return common.Idx(0), ErrToIdxNotFound
}
@ -94,7 +94,7 @@ func (s *StateDB) GetIdxByEthAddrBJJ(addr ethCommon.Address, pk *babyjub.PublicK
} else if !bytes.Equal(addr.Bytes(), common.EmptyAddr.Bytes()) && pk != nil {
// case ToEthAddr!=0 && ToBJJ!=0
k := concatEthAddrBJJ(addr, pk)
b, err := s.db.Get(k)
b, err := s.db.Get(append(PrefixKeyAddrBJJ, k...))
if err != nil {
return common.Idx(0), ErrToIdxNotFound
}

+ 3
- 1
go.mod

@ -7,7 +7,8 @@ require (
github.com/dghubble/sling v1.3.0
github.com/ethereum/go-ethereum v1.9.17
github.com/getkin/kin-openapi v0.22.0
github.com/gin-gonic/gin v1.4.0
github.com/gin-contrib/cors v1.3.1
github.com/gin-gonic/gin v1.5.0
github.com/go-sql-driver/mysql v1.5.0 // indirect
github.com/gobuffalo/packr/v2 v2.8.0
github.com/iden3/go-iden3-crypto v0.0.6-0.20200823174058-e04ca5764a15
@ -24,6 +25,7 @@ require (
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.16.0
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/net v0.0.0-20200822124328-c89045814202
golang.org/x/tools/gopls v0.5.0 // indirect
gopkg.in/go-playground/validator.v9 v9.29.1
)

+ 11
- 0
go.sum

@ -165,10 +165,16 @@ github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49P
github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA=
github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/gin-gonic/gin v1.5.0 h1:fi+bqFAx/oLK54somfCtEZs9HeH1LHVoEPUgARpTqyc=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@ -378,6 +384,8 @@ github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXT
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
@ -570,10 +578,12 @@ github.com/tyler-smith/go-bip39 v1.0.2 h1:+t3w+KwLXO6154GNJY+qUtIxLTmFjfUmpguQT1
github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.1.8 h1:/D9x7IRpfMHDlizVOgxrag5Fh+/NY+LtI8bsr+AswRA=
github.com/ugorji/go v1.1.8/go.mod h1:0lNM99SwWUIRhCXnigEMClngXBk/EmpTXa7mgiewYWA=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.1.8 h1:4dryPvxMP9OtkjIbuNeK2nb27M38XMHLGlfNSNph/5s=
github.com/ugorji/go/codec v1.1.8/go.mod h1:X00B19HDtwvKbQY2DcYjvZxKQp8mzrJoQ6EgoIY/D2E=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
@ -713,6 +723,7 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

+ 121
- 0
test/debugapi/debugapi.go

@ -0,0 +1,121 @@
package debugapi
import (
"context"
"net/http"
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/db/statedb"
"github.com/hermeznetwork/hermez-node/log"
)
func handleNoRoute(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{
"error": "404 page not found",
})
}
type errorMsg struct {
Message string
}
func badReq(err error, c *gin.Context) {
log.Errorw("Bad request", "err", err)
c.JSON(http.StatusBadRequest, errorMsg{
Message: err.Error(),
})
}
// DebugAPI is an http API with debugging endpoints
type DebugAPI struct {
addr string
stateDB *statedb.StateDB // synchronizer statedb
}
// NewDebugAPI creates a new DebugAPI
func NewDebugAPI(addr string, stateDB *statedb.StateDB) *DebugAPI {
return &DebugAPI{
stateDB: stateDB,
addr: addr,
}
}
func (a *DebugAPI) handleAccount(c *gin.Context) {
uri := struct {
Idx uint32
}{}
if err := c.ShouldBindUri(&uri); err != nil {
badReq(err, c)
return
}
account, err := a.stateDB.GetAccount(common.Idx(uri.Idx))
if err != nil {
badReq(err, c)
return
}
c.JSON(http.StatusOK, account)
}
func (a *DebugAPI) handleAccounts(c *gin.Context) {
accounts, err := a.stateDB.GetAccounts()
if err != nil {
badReq(err, c)
return
}
c.JSON(http.StatusOK, accounts)
}
func (a *DebugAPI) handleCurrentBatch(c *gin.Context) {
batchNum, err := a.stateDB.GetCurrentBatch()
if err != nil {
badReq(err, c)
return
}
c.JSON(http.StatusOK, batchNum)
}
func (a *DebugAPI) handleMTRoot(c *gin.Context) {
root := a.stateDB.MTGetRoot()
c.JSON(http.StatusOK, root)
}
// Run starts the http server of the DebugAPI. To stop it, pass a context with
// cancelation (see `debugapi_test.go` for an example).
func (a *DebugAPI) Run(ctx context.Context) error {
api := gin.Default()
api.NoRoute(handleNoRoute)
api.Use(cors.Default())
debugAPI := api.Group("/debug")
debugAPI.GET("sdb/batchnum", a.handleCurrentBatch)
debugAPI.GET("sdb/mtroot", a.handleMTRoot)
debugAPI.GET("sdb/accounts", a.handleAccounts)
debugAPI.GET("sdb/accounts/:Idx", a.handleAccount)
debugAPIServer := &http.Server{
Addr: a.addr,
Handler: api,
// Use some hardcoded numberes that are suitable for testing
ReadTimeout: 30 * time.Second, //nolint:gomnd
WriteTimeout: 30 * time.Second, //nolint:gomnd
MaxHeaderBytes: 1 << 20, //nolint:gomnd
}
go func() {
log.Infof("Debug API is ready at %v", a.addr)
if err := debugAPIServer.ListenAndServe(); err != nil &&
err != http.ErrServerClosed {
log.Fatalf("Listen: %s\n", err)
}
}()
<-ctx.Done()
log.Info("Stopping Debug API...")
if err := debugAPIServer.Shutdown(context.Background()); err != nil {
return err
}
log.Info("Debug API stopped")
return nil
}

+ 101
- 0
test/debugapi/debugapi_test.go

@ -0,0 +1,101 @@
package debugapi
import (
"crypto/ecdsa"
"fmt"
"io/ioutil"
"math/big"
"net/http"
"strconv"
"testing"
"github.com/dghubble/sling"
ethCrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/db/statedb"
"github.com/iden3/go-iden3-crypto/babyjub"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func newAccount(t *testing.T, i int) *common.Account {
var sk babyjub.PrivateKey
copy(sk[:], []byte(strconv.Itoa(i))) // only for testing
pk := sk.Public()
var key ecdsa.PrivateKey
key.D = big.NewInt(int64(i + 1)) // only for testing
key.PublicKey.X, key.PublicKey.Y = ethCrypto.S256().ScalarBaseMult(key.D.Bytes())
key.Curve = ethCrypto.S256()
address := ethCrypto.PubkeyToAddress(key.PublicKey)
return &common.Account{
Idx: common.Idx(256 + i),
TokenID: common.TokenID(i),
Nonce: common.Nonce(i),
Balance: big.NewInt(1000),
PublicKey: pk,
EthAddr: address,
}
}
func TestDebugAPI(t *testing.T) {
dir, err := ioutil.TempDir("", "tmpdb")
require.Nil(t, err)
sdb, err := statedb.NewStateDB(dir, statedb.TypeSynchronizer, 32)
require.Nil(t, err)
err = sdb.MakeCheckpoint() // Make a checkpoint to increment the batchNum
require.Nil(t, err)
addr := "localhost:12345"
debugAPI := NewDebugAPI(addr, sdb)
ctx, cancel := context.WithCancel(context.Background())
go func() {
err := debugAPI.Run(ctx)
require.Nil(t, err)
}()
var accounts []common.Account
for i := 0; i < 16; i++ {
account := newAccount(t, i)
accounts = append(accounts, *account)
_, err = sdb.CreateAccount(account.Idx, account)
require.Nil(t, err)
}
url := fmt.Sprintf("http://%v/debug/", addr)
var batchNum common.BatchNum
req, err := sling.New().Get(url).Path("sdb/batchnum").ReceiveSuccess(&batchNum)
require.Equal(t, http.StatusOK, req.StatusCode)
require.Nil(t, err)
assert.Equal(t, common.BatchNum(1), batchNum)
var mtroot *big.Int
req, err = sling.New().Get(url).Path("sdb/mtroot").ReceiveSuccess(&mtroot)
require.Equal(t, http.StatusOK, req.StatusCode)
require.Nil(t, err)
// Testing against a hardcoded value obtained by running the test and
// printing the value previously.
assert.Equal(t, "8902613552504893273500019895709436294962812188236308621387152512232191202510",
mtroot.String())
var accountAPI common.Account
req, err = sling.New().Get(url).
Path(fmt.Sprintf("sdb/accounts/%v", accounts[0].Idx)).
ReceiveSuccess(&accountAPI)
require.Equal(t, http.StatusOK, req.StatusCode)
require.Nil(t, err)
assert.Equal(t, accounts[0], accountAPI)
var accountsAPI []common.Account
req, err = sling.New().Get(url).Path("sdb/accounts").ReceiveSuccess(&accountsAPI)
require.Equal(t, http.StatusOK, req.StatusCode)
require.Nil(t, err)
assert.Equal(t, accounts, accountsAPI)
cancel()
}

Loading…
Cancel
Save