Extend statedb and use prefixes, add debugapifeature/sql-semaphore1
@ -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 |
||||
|
} |
@ -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() |
||||
|
} |