Commit c0576e0c authored by Peter Mrekaj's avatar Peter Mrekaj Committed by GitHub

refactor: move stamps API to debug API (#2171)

parent 7f5e2d98
......@@ -17,9 +17,6 @@ type (
TagResponse = tagResponse
TagRequest = tagRequest
ListTagsResponse = listTagsResponse
PostageCreateResponse = postageCreateResponse
PostageStampResponse = postageStampResponse
PostageStampsResponse = postageStampsResponse
)
var (
......
// Copyright 2021 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package api
import (
"encoding/hex"
"encoding/json"
"errors"
"math/big"
"net/http"
"strconv"
"github.com/ethersphere/bee/pkg/bigint"
"github.com/ethersphere/bee/pkg/jsonhttp"
"github.com/ethersphere/bee/pkg/postage/postagecontract"
"github.com/ethersphere/bee/pkg/sctx"
"github.com/gorilla/mux"
)
const (
gasPriceHeader = "Gas-Price"
immutableHeader = "Immutable"
errBadGasPrice = "bad gas price"
)
type batchID []byte
func (b batchID) MarshalJSON() ([]byte, error) {
return json.Marshal(hex.EncodeToString(b))
}
type postageCreateResponse struct {
BatchID batchID `json:"batchID"`
}
func (s *server) postageCreateHandler(w http.ResponseWriter, r *http.Request) {
depthStr := mux.Vars(r)["depth"]
amount, ok := big.NewInt(0).SetString(mux.Vars(r)["amount"], 10)
if !ok {
s.logger.Error("create batch: invalid amount")
jsonhttp.BadRequest(w, "invalid postage amount")
return
}
depth, err := strconv.ParseUint(depthStr, 10, 8)
if err != nil {
s.logger.Debugf("create batch: invalid depth: %v", err)
s.logger.Error("create batch: invalid depth")
jsonhttp.BadRequest(w, "invalid depth")
return
}
label := r.URL.Query().Get("label")
ctx := r.Context()
if price, ok := r.Header[gasPriceHeader]; ok {
p, ok := big.NewInt(0).SetString(price[0], 10)
if !ok {
s.logger.Error("create batch: bad gas price")
jsonhttp.BadRequest(w, errBadGasPrice)
return
}
ctx = sctx.SetGasPrice(ctx, p)
}
var immutable bool
if val, ok := r.Header[immutableHeader]; ok {
immutable, _ = strconv.ParseBool(val[0])
}
batchID, err := s.postageContract.CreateBatch(ctx, amount, uint8(depth), immutable, label)
if err != nil {
if errors.Is(err, postagecontract.ErrInsufficientFunds) {
s.logger.Debugf("create batch: out of funds: %v", err)
s.logger.Error("create batch: out of funds")
jsonhttp.BadRequest(w, "out of funds")
return
}
if errors.Is(err, postagecontract.ErrInvalidDepth) {
s.logger.Debugf("create batch: invalid depth: %v", err)
s.logger.Error("create batch: invalid depth")
jsonhttp.BadRequest(w, "invalid depth")
return
}
s.logger.Debugf("create batch: failed to create: %v", err)
s.logger.Error("create batch: failed to create")
jsonhttp.InternalServerError(w, "cannot create batch")
return
}
jsonhttp.Created(w, &postageCreateResponse{
BatchID: batchID,
})
}
type postageStampResponse struct {
BatchID batchID `json:"batchID"`
Utilization uint32 `json:"utilization"`
Usable bool `json:"usable"`
Label string `json:"label"`
Depth uint8 `json:"depth"`
Amount *bigint.BigInt `json:"amount"`
BucketDepth uint8 `json:"bucketDepth"`
BlockNumber uint64 `json:"blockNumber"`
ImmutableFlag bool `json:"immutableFlag"`
}
type postageStampsResponse struct {
Stamps []postageStampResponse `json:"stamps"`
}
func (s *server) postageGetStampsHandler(w http.ResponseWriter, _ *http.Request) {
resp := postageStampsResponse{}
for _, v := range s.post.StampIssuers() {
resp.Stamps = append(resp.Stamps, postageStampResponse{
BatchID: v.ID(),
Utilization: v.Utilization(),
Usable: s.post.IssuerUsable(v),
Label: v.Label(),
Depth: v.Depth(),
Amount: bigint.Wrap(v.Amount()),
BucketDepth: v.BucketDepth(),
BlockNumber: v.BlockNumber(),
ImmutableFlag: v.ImmutableFlag(),
})
}
jsonhttp.OK(w, resp)
}
func (s *server) postageGetStampHandler(w http.ResponseWriter, r *http.Request) {
idStr := mux.Vars(r)["id"]
if idStr == "" || len(idStr) != 64 {
s.logger.Error("get stamp issuer: invalid batchID")
jsonhttp.BadRequest(w, "invalid batchID")
return
}
id, err := hex.DecodeString(idStr)
if err != nil {
s.logger.Error("get stamp issuer: invalid batchID: %v", err)
s.logger.Error("get stamp issuer: invalid batchID")
jsonhttp.BadRequest(w, "invalid batchID")
return
}
issuer, err := s.post.GetStampIssuer(id)
if err != nil {
s.logger.Error("get stamp issuer: get issuer: %v", err)
s.logger.Error("get stamp issuer: get issuer")
jsonhttp.BadRequest(w, "cannot get issuer")
return
}
resp := postageStampResponse{
BatchID: id,
Utilization: issuer.Utilization(),
Usable: s.post.IssuerUsable(issuer),
Label: issuer.Label(),
Depth: issuer.Depth(),
Amount: bigint.Wrap(issuer.Amount()),
BucketDepth: issuer.BucketDepth(),
BlockNumber: issuer.BlockNumber(),
ImmutableFlag: issuer.ImmutableFlag(),
}
jsonhttp.OK(w, &resp)
}
// Copyright 2021 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package api_test
import (
"context"
"encoding/hex"
"errors"
"fmt"
"math/big"
"net/http"
"testing"
"github.com/ethersphere/bee/pkg/api"
"github.com/ethersphere/bee/pkg/bigint"
"github.com/ethersphere/bee/pkg/jsonhttp"
"github.com/ethersphere/bee/pkg/jsonhttp/jsonhttptest"
"github.com/ethersphere/bee/pkg/postage"
mockpost "github.com/ethersphere/bee/pkg/postage/mock"
"github.com/ethersphere/bee/pkg/postage/postagecontract"
contractMock "github.com/ethersphere/bee/pkg/postage/postagecontract/mock"
"github.com/ethersphere/bee/pkg/sctx"
)
func TestPostageCreateStamp(t *testing.T) {
batchID := []byte{1, 2, 3, 4}
initialBalance := int64(1000)
depth := uint8(1)
label := "label"
createBatch := func(amount int64, depth uint8, label string) string {
return fmt.Sprintf("/stamps/%d/%d?label=%s", amount, depth, label)
}
t.Run("ok", func(t *testing.T) {
contract := contractMock.New(
contractMock.WithCreateBatchFunc(func(ctx context.Context, ib *big.Int, d uint8, i bool, l string) ([]byte, error) {
if ib.Cmp(big.NewInt(initialBalance)) != 0 {
return nil, fmt.Errorf("called with wrong initial balance. wanted %d, got %d", initialBalance, ib)
}
if d != depth {
return nil, fmt.Errorf("called with wrong depth. wanted %d, got %d", depth, d)
}
if l != label {
return nil, fmt.Errorf("called with wrong label. wanted %s, got %s", label, l)
}
return batchID, nil
}),
)
client, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
jsonhttptest.Request(t, client, http.MethodPost, createBatch(initialBalance, depth, label), http.StatusCreated,
jsonhttptest.WithExpectedJSONResponse(&api.PostageCreateResponse{
BatchID: batchID,
}),
)
})
t.Run("with-custom-gas", func(t *testing.T) {
contract := contractMock.New(
contractMock.WithCreateBatchFunc(func(ctx context.Context, ib *big.Int, d uint8, i bool, l string) ([]byte, error) {
if ib.Cmp(big.NewInt(initialBalance)) != 0 {
return nil, fmt.Errorf("called with wrong initial balance. wanted %d, got %d", initialBalance, ib)
}
if d != depth {
return nil, fmt.Errorf("called with wrong depth. wanted %d, got %d", depth, d)
}
if l != label {
return nil, fmt.Errorf("called with wrong label. wanted %s, got %s", label, l)
}
if sctx.GetGasPrice(ctx).Cmp(big.NewInt(10000)) != 0 {
return nil, fmt.Errorf("called with wrong gas price. wanted %d, got %d", 10000, sctx.GetGasPrice(ctx))
}
return batchID, nil
}),
)
client, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
jsonhttptest.Request(t, client, http.MethodPost, createBatch(initialBalance, depth, label), http.StatusCreated,
jsonhttptest.WithRequestHeader("Gas-Price", "10000"),
jsonhttptest.WithExpectedJSONResponse(&api.PostageCreateResponse{
BatchID: batchID,
}),
)
})
t.Run("with-error", func(t *testing.T) {
contract := contractMock.New(
contractMock.WithCreateBatchFunc(func(ctx context.Context, ib *big.Int, d uint8, i bool, l string) ([]byte, error) {
return nil, errors.New("err")
}),
)
client, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
jsonhttptest.Request(t, client, http.MethodPost, createBatch(initialBalance, depth, label), http.StatusInternalServerError,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusInternalServerError,
Message: "cannot create batch",
}),
)
})
t.Run("out-of-funds", func(t *testing.T) {
contract := contractMock.New(
contractMock.WithCreateBatchFunc(func(ctx context.Context, ib *big.Int, d uint8, i bool, l string) ([]byte, error) {
return nil, postagecontract.ErrInsufficientFunds
}),
)
client, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
jsonhttptest.Request(t, client, http.MethodPost, createBatch(initialBalance, depth, label), http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusBadRequest,
Message: "out of funds",
}),
)
})
t.Run("invalid depth", func(t *testing.T) {
client, _, _ := newTestServer(t, testServerOptions{})
jsonhttptest.Request(t, client, http.MethodPost, "/stamps/1000/ab", http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusBadRequest,
Message: "invalid depth",
}),
)
})
t.Run("depth less than bucket depth", func(t *testing.T) {
contract := contractMock.New(
contractMock.WithCreateBatchFunc(func(ctx context.Context, ib *big.Int, d uint8, i bool, l string) ([]byte, error) {
return nil, postagecontract.ErrInvalidDepth
}),
)
client, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
jsonhttptest.Request(t, client, http.MethodPost, "/stamps/1000/9", http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusBadRequest,
Message: "invalid depth",
}),
)
})
t.Run("invalid balance", func(t *testing.T) {
client, _, _ := newTestServer(t, testServerOptions{})
jsonhttptest.Request(t, client, http.MethodPost, "/stamps/abcd/2", http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusBadRequest,
Message: "invalid postage amount",
}),
)
})
t.Run("immutable header", func(t *testing.T) {
var immutable bool
contract := contractMock.New(
contractMock.WithCreateBatchFunc(func(ctx context.Context, _ *big.Int, _ uint8, i bool, _ string) ([]byte, error) {
immutable = i
return batchID, nil
}),
)
client, _, _ := newTestServer(t, testServerOptions{
PostageContract: contract,
})
jsonhttptest.Request(t, client, http.MethodPost, "/stamps/1000/24", http.StatusCreated,
jsonhttptest.WithRequestHeader("Immutable", "true"),
jsonhttptest.WithExpectedJSONResponse(&api.PostageCreateResponse{
BatchID: batchID,
}),
)
if !immutable {
t.Fatalf("want true, got %v", immutable)
}
})
}
func TestPostageGetStamps(t *testing.T) {
si := postage.NewStampIssuer("", "", batchOk, big.NewInt(3), 11, 10, 1000, true)
mp := mockpost.New(mockpost.WithIssuer(si))
client, _, _ := newTestServer(t, testServerOptions{Post: mp})
jsonhttptest.Request(t, client, http.MethodGet, "/stamps", http.StatusOK,
jsonhttptest.WithExpectedJSONResponse(&api.PostageStampsResponse{
Stamps: []api.PostageStampResponse{
{
BatchID: batchOk,
Utilization: si.Utilization(),
Usable: true,
Label: si.Label(),
Depth: si.Depth(),
Amount: bigint.Wrap(si.Amount()),
BucketDepth: si.BucketDepth(),
BlockNumber: si.BlockNumber(),
ImmutableFlag: si.ImmutableFlag(),
},
},
}),
)
}
func TestPostageGetStamp(t *testing.T) {
si := postage.NewStampIssuer("", "", batchOk, big.NewInt(3), 11, 10, 1000, true)
mp := mockpost.New(mockpost.WithIssuer(si))
client, _, _ := newTestServer(t, testServerOptions{Post: mp})
t.Run("ok", func(t *testing.T) {
jsonhttptest.Request(t, client, http.MethodGet, "/stamps/"+batchOkStr, http.StatusOK,
jsonhttptest.WithExpectedJSONResponse(&api.PostageStampResponse{
BatchID: batchOk,
Utilization: si.Utilization(),
Usable: true,
Label: si.Label(),
Depth: si.Depth(),
Amount: bigint.Wrap(si.Amount()),
BucketDepth: si.BucketDepth(),
BlockNumber: si.BlockNumber(),
ImmutableFlag: si.ImmutableFlag(),
}),
)
})
t.Run("ok", func(t *testing.T) {
badBatch := []byte{0, 1, 2}
jsonhttptest.Request(t, client, http.MethodGet, "/stamps/"+hex.EncodeToString(badBatch), http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusBadRequest,
Message: "invalid batchID",
}),
)
})
t.Run("ok", func(t *testing.T) {
badBatch := []byte{0, 1, 2, 4}
jsonhttptest.Request(t, client, http.MethodGet, "/stamps/"+hex.EncodeToString(badBatch), http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusBadRequest,
Message: "invalid batchID",
}),
)
})
}
......@@ -156,27 +156,6 @@ func (s *server) setupRouting() {
})),
)
handle("/stamps", web.ChainHandlers(
s.gatewayModeForbidEndpointHandler,
web.FinalHandler(jsonhttp.MethodHandler{
"GET": http.HandlerFunc(s.postageGetStampsHandler),
})),
)
handle("/stamps/{id}", web.ChainHandlers(
s.gatewayModeForbidEndpointHandler,
web.FinalHandler(jsonhttp.MethodHandler{
"GET": http.HandlerFunc(s.postageGetStampHandler),
})),
)
handle("/stamps/{amount}/{depth}", web.ChainHandlers(
s.gatewayModeForbidEndpointHandler,
web.FinalHandler(jsonhttp.MethodHandler{
"POST": http.HandlerFunc(s.postageCreateHandler),
})),
)
s.Handler = web.ChainHandlers(
httpaccess.NewHTTPAccessLogHandler(s.logger, logrus.InfoLevel, s.tracer, "api access"),
handlers.CompressHandler,
......
......@@ -20,7 +20,7 @@ import (
"github.com/gorilla/mux"
)
var (
const (
errChequebookBalance = "cannot get chequebook balance"
errChequebookNoAmount = "did not specify amount"
errChequebookNoWithdraw = "cannot withdraw"
......@@ -37,6 +37,7 @@ var (
gasPriceHeader = "Gas-Price"
gasLimitHeader = "Gas-Limit"
immutableHeader = "Immutable"
)
type chequebookBalanceResponse struct {
......
......@@ -18,6 +18,7 @@ import (
"github.com/ethersphere/bee/pkg/p2p"
"github.com/ethersphere/bee/pkg/pingpong"
"github.com/ethersphere/bee/pkg/postage"
"github.com/ethersphere/bee/pkg/postage/postagecontract"
"github.com/ethersphere/bee/pkg/settlement"
"github.com/ethersphere/bee/pkg/settlement/swap"
"github.com/ethersphere/bee/pkg/settlement/swap/chequebook"
......@@ -41,7 +42,6 @@ type Service struct {
pingpong pingpong.Interface
topologyDriver topology.Driver
storer storage.Storer
logger logging.Logger
tracer *tracing.Tracer
tags *tags.Tags
accounting accounting.Interface
......@@ -51,6 +51,9 @@ type Service struct {
swap swap.Interface
batchStore postage.Storer
transaction transaction.Service
post postage.Service
postageContract postagecontract.Interface
logger logging.Logger
corsAllowedOrigins []string
metricsRegistry *prometheus.Registry
lightNodes *lightnode.Container
......@@ -81,7 +84,7 @@ func New(publicKey, pssPublicKey ecdsa.PublicKey, ethereumAddress common.Address
// Configure injects required dependencies and configuration parameters and
// constructs HTTP routes that depend on them. It is intended and safe to call
// this method only once.
func (s *Service) Configure(overlay swarm.Address, p2p p2p.DebugService, pingpong pingpong.Interface, topologyDriver topology.Driver, lightNodes *lightnode.Container, storer storage.Storer, tags *tags.Tags, accounting accounting.Interface, pseudosettle settlement.Interface, chequebookEnabled bool, swap swap.Interface, chequebook chequebook.Service, batchStore postage.Storer, transaction transaction.Service) {
func (s *Service) Configure(overlay swarm.Address, p2p p2p.DebugService, pingpong pingpong.Interface, topologyDriver topology.Driver, lightNodes *lightnode.Container, storer storage.Storer, tags *tags.Tags, accounting accounting.Interface, pseudosettle settlement.Interface, chequebookEnabled bool, swap swap.Interface, chequebook chequebook.Service, batchStore postage.Storer, transaction transaction.Service, post postage.Service, postageContract postagecontract.Interface) {
s.p2p = p2p
s.pingpong = pingpong
s.topologyDriver = topologyDriver
......@@ -96,6 +99,8 @@ func (s *Service) Configure(overlay swarm.Address, p2p p2p.DebugService, pingpon
s.pseudosettle = pseudosettle
s.transaction = transaction
s.overlay = &overlay
s.post = post
s.postageContract = postageContract
s.setRouter(s.newRouter())
}
......
......@@ -6,6 +6,7 @@ package debugapi_test
import (
"crypto/ecdsa"
"crypto/rand"
"encoding/hex"
"io/ioutil"
"net/http"
......@@ -24,6 +25,8 @@ import (
p2pmock "github.com/ethersphere/bee/pkg/p2p/mock"
"github.com/ethersphere/bee/pkg/pingpong"
"github.com/ethersphere/bee/pkg/postage"
mockpost "github.com/ethersphere/bee/pkg/postage/mock"
"github.com/ethersphere/bee/pkg/postage/postagecontract"
"github.com/ethersphere/bee/pkg/resolver"
chequebookmock "github.com/ethersphere/bee/pkg/settlement/swap/chequebook/mock"
swapmock "github.com/ethersphere/bee/pkg/settlement/swap/mock"
......@@ -37,6 +40,17 @@ import (
"resenje.org/web"
)
var (
batchOk = make([]byte, 32)
batchOkStr string
)
func init() {
_, _ = rand.Read(batchOk)
batchOkStr = hex.EncodeToString(batchOk)
}
type testServerOptions struct {
Overlay swarm.Address
PublicKey ecdsa.PublicKey
......@@ -55,6 +69,8 @@ type testServerOptions struct {
SwapOpts []swapmock.Option
BatchStore postage.Storer
TransactionOpts []transactionmock.Option
PostageContract postagecontract.Interface
Post postage.Service
}
type testServer struct {
......@@ -71,7 +87,7 @@ func newTestServer(t *testing.T, o testServerOptions) *testServer {
transaction := transactionmock.New(o.TransactionOpts...)
ln := lightnode.NewContainer(o.Overlay)
s := debugapi.New(o.PublicKey, o.PSSPublicKey, o.EthereumAddress, logging.New(ioutil.Discard, 0), nil, o.CORSAllowedOrigins)
s.Configure(o.Overlay, o.P2P, o.Pingpong, topologyDriver, ln, o.Storer, o.Tags, acc, settlement, true, swapserv, chequebook, o.BatchStore, transaction)
s.Configure(o.Overlay, o.P2P, o.Pingpong, topologyDriver, ln, o.Storer, o.Tags, acc, settlement, true, swapserv, chequebook, o.BatchStore, transaction, o.Post, o.PostageContract)
ts := httptest.NewServer(s)
t.Cleanup(ts.Close)
......@@ -169,7 +185,7 @@ func TestServer_Configure(t *testing.T) {
}),
)
s.Configure(o.Overlay, o.P2P, o.Pingpong, topologyDriver, ln, o.Storer, o.Tags, acc, settlement, true, swapserv, chequebook, nil, transaction)
s.Configure(o.Overlay, o.P2P, o.Pingpong, topologyDriver, ln, o.Storer, o.Tags, acc, settlement, true, swapserv, chequebook, nil, transaction, mockpost.New(), nil)
testBasicRouter(t, client)
jsonhttptest.Request(t, client, http.MethodGet, "/readiness", http.StatusOK,
......
......@@ -31,6 +31,9 @@ type (
TagResponse = tagResponse
ReserveStateResponse = reserveStateResponse
ChainStateResponse = chainStateResponse
PostageCreateResponse = postageCreateResponse
PostageStampResponse = postageStampResponse
PostageStampsResponse = postageStampsResponse
)
var (
......
......@@ -5,12 +5,162 @@
package debugapi
import (
"encoding/hex"
"encoding/json"
"errors"
"math/big"
"net/http"
"strconv"
"github.com/ethersphere/bee/pkg/bigint"
"github.com/ethersphere/bee/pkg/jsonhttp"
"github.com/ethersphere/bee/pkg/postage/postagecontract"
"github.com/ethersphere/bee/pkg/sctx"
"github.com/gorilla/mux"
)
type batchID []byte
func (b batchID) MarshalJSON() ([]byte, error) {
return json.Marshal(hex.EncodeToString(b))
}
type postageCreateResponse struct {
BatchID batchID `json:"batchID"`
}
func (s *Service) postageCreateHandler(w http.ResponseWriter, r *http.Request) {
depthStr := mux.Vars(r)["depth"]
amount, ok := big.NewInt(0).SetString(mux.Vars(r)["amount"], 10)
if !ok {
s.logger.Error("create batch: invalid amount")
jsonhttp.BadRequest(w, "invalid postage amount")
return
}
depth, err := strconv.ParseUint(depthStr, 10, 8)
if err != nil {
s.logger.Debugf("create batch: invalid depth: %v", err)
s.logger.Error("create batch: invalid depth")
jsonhttp.BadRequest(w, "invalid depth")
return
}
label := r.URL.Query().Get("label")
ctx := r.Context()
if price, ok := r.Header[gasPriceHeader]; ok {
p, ok := big.NewInt(0).SetString(price[0], 10)
if !ok {
s.logger.Error("create batch: bad gas price")
jsonhttp.BadRequest(w, errBadGasPrice)
return
}
ctx = sctx.SetGasPrice(ctx, p)
}
var immutable bool
if val, ok := r.Header[immutableHeader]; ok {
immutable, _ = strconv.ParseBool(val[0])
}
batchID, err := s.postageContract.CreateBatch(ctx, amount, uint8(depth), immutable, label)
if err != nil {
if errors.Is(err, postagecontract.ErrInsufficientFunds) {
s.logger.Debugf("create batch: out of funds: %v", err)
s.logger.Error("create batch: out of funds")
jsonhttp.BadRequest(w, "out of funds")
return
}
if errors.Is(err, postagecontract.ErrInvalidDepth) {
s.logger.Debugf("create batch: invalid depth: %v", err)
s.logger.Error("create batch: invalid depth")
jsonhttp.BadRequest(w, "invalid depth")
return
}
s.logger.Debugf("create batch: failed to create: %v", err)
s.logger.Error("create batch: failed to create")
jsonhttp.InternalServerError(w, "cannot create batch")
return
}
jsonhttp.Created(w, &postageCreateResponse{
BatchID: batchID,
})
}
type postageStampResponse struct {
BatchID batchID `json:"batchID"`
Utilization uint32 `json:"utilization"`
Usable bool `json:"usable"`
Label string `json:"label"`
Depth uint8 `json:"depth"`
Amount *bigint.BigInt `json:"amount"`
BucketDepth uint8 `json:"bucketDepth"`
BlockNumber uint64 `json:"blockNumber"`
ImmutableFlag bool `json:"immutableFlag"`
}
type postageStampsResponse struct {
Stamps []postageStampResponse `json:"stamps"`
}
func (s *Service) postageGetStampsHandler(w http.ResponseWriter, _ *http.Request) {
resp := postageStampsResponse{}
for _, v := range s.post.StampIssuers() {
resp.Stamps = append(resp.Stamps, postageStampResponse{
BatchID: v.ID(),
Utilization: v.Utilization(),
Usable: s.post.IssuerUsable(v),
Label: v.Label(),
Depth: v.Depth(),
Amount: bigint.Wrap(v.Amount()),
BucketDepth: v.BucketDepth(),
BlockNumber: v.BlockNumber(),
ImmutableFlag: v.ImmutableFlag(),
})
}
jsonhttp.OK(w, resp)
}
func (s *Service) postageGetStampHandler(w http.ResponseWriter, r *http.Request) {
idStr := mux.Vars(r)["id"]
if idStr == "" || len(idStr) != 64 {
s.logger.Error("get stamp issuer: invalid batchID")
jsonhttp.BadRequest(w, "invalid batchID")
return
}
id, err := hex.DecodeString(idStr)
if err != nil {
s.logger.Error("get stamp issuer: invalid batchID: %v", err)
s.logger.Error("get stamp issuer: invalid batchID")
jsonhttp.BadRequest(w, "invalid batchID")
return
}
issuer, err := s.post.GetStampIssuer(id)
if err != nil {
s.logger.Error("get stamp issuer: get issuer: %v", err)
s.logger.Error("get stamp issuer: get issuer")
jsonhttp.BadRequest(w, "cannot get issuer")
return
}
resp := postageStampResponse{
BatchID: id,
Utilization: issuer.Utilization(),
Usable: s.post.IssuerUsable(issuer),
Label: issuer.Label(),
Depth: issuer.Depth(),
Amount: bigint.Wrap(issuer.Amount()),
BucketDepth: issuer.BucketDepth(),
BlockNumber: issuer.BlockNumber(),
ImmutableFlag: issuer.ImmutableFlag(),
}
jsonhttp.OK(w, &resp)
}
type reserveStateResponse struct {
Radius uint8 `json:"radius"`
StorageRadius uint8 `json:"storageRadius"`
......
......@@ -5,17 +5,260 @@
package debugapi_test
import (
"context"
"encoding/hex"
"errors"
"fmt"
"math/big"
"net/http"
"testing"
"github.com/ethersphere/bee/pkg/bigint"
"github.com/ethersphere/bee/pkg/debugapi"
"github.com/ethersphere/bee/pkg/jsonhttp"
"github.com/ethersphere/bee/pkg/jsonhttp/jsonhttptest"
"github.com/ethersphere/bee/pkg/postage"
"github.com/ethersphere/bee/pkg/postage/batchstore/mock"
mockpost "github.com/ethersphere/bee/pkg/postage/mock"
"github.com/ethersphere/bee/pkg/postage/postagecontract"
contractMock "github.com/ethersphere/bee/pkg/postage/postagecontract/mock"
"github.com/ethersphere/bee/pkg/sctx"
)
func TestPostageCreateStamp(t *testing.T) {
batchID := []byte{1, 2, 3, 4}
initialBalance := int64(1000)
depth := uint8(1)
label := "label"
createBatch := func(amount int64, depth uint8, label string) string {
return fmt.Sprintf("/stamps/%d/%d?label=%s", amount, depth, label)
}
t.Run("ok", func(t *testing.T) {
contract := contractMock.New(
contractMock.WithCreateBatchFunc(func(ctx context.Context, ib *big.Int, d uint8, i bool, l string) ([]byte, error) {
if ib.Cmp(big.NewInt(initialBalance)) != 0 {
return nil, fmt.Errorf("called with wrong initial balance. wanted %d, got %d", initialBalance, ib)
}
if d != depth {
return nil, fmt.Errorf("called with wrong depth. wanted %d, got %d", depth, d)
}
if l != label {
return nil, fmt.Errorf("called with wrong label. wanted %s, got %s", label, l)
}
return batchID, nil
}),
)
ts := newTestServer(t, testServerOptions{
PostageContract: contract,
})
jsonhttptest.Request(t, ts.Client, http.MethodPost, createBatch(initialBalance, depth, label), http.StatusCreated,
jsonhttptest.WithExpectedJSONResponse(&debugapi.PostageCreateResponse{
BatchID: batchID,
}),
)
})
t.Run("with-custom-gas", func(t *testing.T) {
contract := contractMock.New(
contractMock.WithCreateBatchFunc(func(ctx context.Context, ib *big.Int, d uint8, i bool, l string) ([]byte, error) {
if ib.Cmp(big.NewInt(initialBalance)) != 0 {
return nil, fmt.Errorf("called with wrong initial balance. wanted %d, got %d", initialBalance, ib)
}
if d != depth {
return nil, fmt.Errorf("called with wrong depth. wanted %d, got %d", depth, d)
}
if l != label {
return nil, fmt.Errorf("called with wrong label. wanted %s, got %s", label, l)
}
if sctx.GetGasPrice(ctx).Cmp(big.NewInt(10000)) != 0 {
return nil, fmt.Errorf("called with wrong gas price. wanted %d, got %d", 10000, sctx.GetGasPrice(ctx))
}
return batchID, nil
}),
)
ts := newTestServer(t, testServerOptions{
PostageContract: contract,
})
jsonhttptest.Request(t, ts.Client, http.MethodPost, createBatch(initialBalance, depth, label), http.StatusCreated,
jsonhttptest.WithRequestHeader("Gas-Price", "10000"),
jsonhttptest.WithExpectedJSONResponse(&debugapi.PostageCreateResponse{
BatchID: batchID,
}),
)
})
t.Run("with-error", func(t *testing.T) {
contract := contractMock.New(
contractMock.WithCreateBatchFunc(func(ctx context.Context, ib *big.Int, d uint8, i bool, l string) ([]byte, error) {
return nil, errors.New("err")
}),
)
ts := newTestServer(t, testServerOptions{
PostageContract: contract,
})
jsonhttptest.Request(t, ts.Client, http.MethodPost, createBatch(initialBalance, depth, label), http.StatusInternalServerError,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusInternalServerError,
Message: "cannot create batch",
}),
)
})
t.Run("out-of-funds", func(t *testing.T) {
contract := contractMock.New(
contractMock.WithCreateBatchFunc(func(ctx context.Context, ib *big.Int, d uint8, i bool, l string) ([]byte, error) {
return nil, postagecontract.ErrInsufficientFunds
}),
)
ts := newTestServer(t, testServerOptions{
PostageContract: contract,
})
jsonhttptest.Request(t, ts.Client, http.MethodPost, createBatch(initialBalance, depth, label), http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusBadRequest,
Message: "out of funds",
}),
)
})
t.Run("invalid depth", func(t *testing.T) {
ts := newTestServer(t, testServerOptions{})
jsonhttptest.Request(t, ts.Client, http.MethodPost, "/stamps/1000/ab", http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusBadRequest,
Message: "invalid depth",
}),
)
})
t.Run("depth less than bucket depth", func(t *testing.T) {
contract := contractMock.New(
contractMock.WithCreateBatchFunc(func(ctx context.Context, ib *big.Int, d uint8, i bool, l string) ([]byte, error) {
return nil, postagecontract.ErrInvalidDepth
}),
)
ts := newTestServer(t, testServerOptions{
PostageContract: contract,
})
jsonhttptest.Request(t, ts.Client, http.MethodPost, "/stamps/1000/9", http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusBadRequest,
Message: "invalid depth",
}),
)
})
t.Run("invalid balance", func(t *testing.T) {
ts := newTestServer(t, testServerOptions{})
jsonhttptest.Request(t, ts.Client, http.MethodPost, "/stamps/abcd/2", http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusBadRequest,
Message: "invalid postage amount",
}),
)
})
t.Run("immutable header", func(t *testing.T) {
var immutable bool
contract := contractMock.New(
contractMock.WithCreateBatchFunc(func(ctx context.Context, _ *big.Int, _ uint8, i bool, _ string) ([]byte, error) {
immutable = i
return batchID, nil
}),
)
ts := newTestServer(t, testServerOptions{
PostageContract: contract,
})
jsonhttptest.Request(t, ts.Client, http.MethodPost, "/stamps/1000/24", http.StatusCreated,
jsonhttptest.WithRequestHeader("Immutable", "true"),
jsonhttptest.WithExpectedJSONResponse(&debugapi.PostageCreateResponse{
BatchID: batchID,
}),
)
if !immutable {
t.Fatalf("want true, got %v", immutable)
}
})
}
func TestPostageGetStamps(t *testing.T) {
si := postage.NewStampIssuer("", "", batchOk, big.NewInt(3), 11, 10, 1000, true)
mp := mockpost.New(mockpost.WithIssuer(si))
ts := newTestServer(t, testServerOptions{Post: mp})
jsonhttptest.Request(t, ts.Client, http.MethodGet, "/stamps", http.StatusOK,
jsonhttptest.WithExpectedJSONResponse(&debugapi.PostageStampsResponse{
Stamps: []debugapi.PostageStampResponse{
{
BatchID: batchOk,
Utilization: si.Utilization(),
Usable: true,
Label: si.Label(),
Depth: si.Depth(),
Amount: bigint.Wrap(si.Amount()),
BucketDepth: si.BucketDepth(),
BlockNumber: si.BlockNumber(),
ImmutableFlag: si.ImmutableFlag(),
},
},
}),
)
}
func TestPostageGetStamp(t *testing.T) {
si := postage.NewStampIssuer("", "", batchOk, big.NewInt(3), 11, 10, 1000, true)
mp := mockpost.New(mockpost.WithIssuer(si))
ts := newTestServer(t, testServerOptions{Post: mp})
t.Run("ok", func(t *testing.T) {
jsonhttptest.Request(t, ts.Client, http.MethodGet, "/stamps/"+batchOkStr, http.StatusOK,
jsonhttptest.WithExpectedJSONResponse(&debugapi.PostageStampResponse{
BatchID: batchOk,
Utilization: si.Utilization(),
Usable: true,
Label: si.Label(),
Depth: si.Depth(),
Amount: bigint.Wrap(si.Amount()),
BucketDepth: si.BucketDepth(),
BlockNumber: si.BlockNumber(),
ImmutableFlag: si.ImmutableFlag(),
}),
)
})
t.Run("ok", func(t *testing.T) {
badBatch := []byte{0, 1, 2}
jsonhttptest.Request(t, ts.Client, http.MethodGet, "/stamps/"+hex.EncodeToString(badBatch), http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusBadRequest,
Message: "invalid batchID",
}),
)
})
t.Run("ok", func(t *testing.T) {
badBatch := []byte{0, 1, 2, 4}
jsonhttptest.Request(t, ts.Client, http.MethodGet, "/stamps/"+hex.EncodeToString(badBatch), http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusBadRequest,
Message: "invalid batchID",
}),
)
})
}
func TestReserveState(t *testing.T) {
t.Run("ok", func(t *testing.T) {
ts := newTestServer(t, testServerOptions{
......
......@@ -183,6 +183,24 @@ func (s *Service) newRouter() *mux.Router {
"GET": http.HandlerFunc(s.getTagHandler),
})
router.Handle("/stamps", web.ChainHandlers(
web.FinalHandler(jsonhttp.MethodHandler{
"GET": http.HandlerFunc(s.postageGetStampsHandler),
})),
)
router.Handle("/stamps/{id}", web.ChainHandlers(
web.FinalHandler(jsonhttp.MethodHandler{
"GET": http.HandlerFunc(s.postageGetStampHandler),
})),
)
router.Handle("/stamps/{amount}/{depth}", web.ChainHandlers(
web.FinalHandler(jsonhttp.MethodHandler{
"POST": http.HandlerFunc(s.postageCreateHandler),
})),
)
return router
}
......
......@@ -751,7 +751,7 @@ func NewBee(addr string, publicKey *ecdsa.PublicKey, signer crypto.Signer, netwo
}
// inject dependencies and configure full debug api http path routes
debugAPIService.Configure(swarmAddress, p2ps, pingPong, kad, lightNodes, storer, tagService, acc, pseudosettleService, o.SwapEnable, swapService, chequebookService, batchStore, transactionService)
debugAPIService.Configure(swarmAddress, p2ps, pingPong, kad, lightNodes, storer, tagService, acc, pseudosettleService, o.SwapEnable, swapService, chequebookService, batchStore, transactionService, post, postageContractService)
}
if err := kad.Start(p2pCtx); err != nil {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment