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)
This commit is contained in:
Eduard S
2020-10-19 13:53:39 +02:00
parent c67b1df8cd
commit 6afbda5302
8 changed files with 344 additions and 32 deletions

121
test/debugapi/debugapi.go Normal file
View File

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

View File

@@ -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()
}