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 ( ...@@ -17,9 +17,6 @@ type (
TagResponse = tagResponse TagResponse = tagResponse
TagRequest = tagRequest TagRequest = tagRequest
ListTagsResponse = listTagsResponse ListTagsResponse = listTagsResponse
PostageCreateResponse = postageCreateResponse
PostageStampResponse = postageStampResponse
PostageStampsResponse = postageStampsResponse
) )
var ( 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() { ...@@ -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( s.Handler = web.ChainHandlers(
httpaccess.NewHTTPAccessLogHandler(s.logger, logrus.InfoLevel, s.tracer, "api access"), httpaccess.NewHTTPAccessLogHandler(s.logger, logrus.InfoLevel, s.tracer, "api access"),
handlers.CompressHandler, handlers.CompressHandler,
......
...@@ -20,7 +20,7 @@ import ( ...@@ -20,7 +20,7 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
var ( const (
errChequebookBalance = "cannot get chequebook balance" errChequebookBalance = "cannot get chequebook balance"
errChequebookNoAmount = "did not specify amount" errChequebookNoAmount = "did not specify amount"
errChequebookNoWithdraw = "cannot withdraw" errChequebookNoWithdraw = "cannot withdraw"
...@@ -35,8 +35,9 @@ var ( ...@@ -35,8 +35,9 @@ var (
errBadGasPrice = "bad gas price" errBadGasPrice = "bad gas price"
errBadGasLimit = "bad gas limit" errBadGasLimit = "bad gas limit"
gasPriceHeader = "Gas-Price" gasPriceHeader = "Gas-Price"
gasLimitHeader = "Gas-Limit" gasLimitHeader = "Gas-Limit"
immutableHeader = "Immutable"
) )
type chequebookBalanceResponse struct { type chequebookBalanceResponse struct {
......
...@@ -18,6 +18,7 @@ import ( ...@@ -18,6 +18,7 @@ import (
"github.com/ethersphere/bee/pkg/p2p" "github.com/ethersphere/bee/pkg/p2p"
"github.com/ethersphere/bee/pkg/pingpong" "github.com/ethersphere/bee/pkg/pingpong"
"github.com/ethersphere/bee/pkg/postage" "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"
"github.com/ethersphere/bee/pkg/settlement/swap" "github.com/ethersphere/bee/pkg/settlement/swap"
"github.com/ethersphere/bee/pkg/settlement/swap/chequebook" "github.com/ethersphere/bee/pkg/settlement/swap/chequebook"
...@@ -41,7 +42,6 @@ type Service struct { ...@@ -41,7 +42,6 @@ type Service struct {
pingpong pingpong.Interface pingpong pingpong.Interface
topologyDriver topology.Driver topologyDriver topology.Driver
storer storage.Storer storer storage.Storer
logger logging.Logger
tracer *tracing.Tracer tracer *tracing.Tracer
tags *tags.Tags tags *tags.Tags
accounting accounting.Interface accounting accounting.Interface
...@@ -51,6 +51,9 @@ type Service struct { ...@@ -51,6 +51,9 @@ type Service struct {
swap swap.Interface swap swap.Interface
batchStore postage.Storer batchStore postage.Storer
transaction transaction.Service transaction transaction.Service
post postage.Service
postageContract postagecontract.Interface
logger logging.Logger
corsAllowedOrigins []string corsAllowedOrigins []string
metricsRegistry *prometheus.Registry metricsRegistry *prometheus.Registry
lightNodes *lightnode.Container lightNodes *lightnode.Container
...@@ -81,7 +84,7 @@ func New(publicKey, pssPublicKey ecdsa.PublicKey, ethereumAddress common.Address ...@@ -81,7 +84,7 @@ func New(publicKey, pssPublicKey ecdsa.PublicKey, ethereumAddress common.Address
// Configure injects required dependencies and configuration parameters and // Configure injects required dependencies and configuration parameters and
// constructs HTTP routes that depend on them. It is intended and safe to call // constructs HTTP routes that depend on them. It is intended and safe to call
// this method only once. // 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.p2p = p2p
s.pingpong = pingpong s.pingpong = pingpong
s.topologyDriver = topologyDriver s.topologyDriver = topologyDriver
...@@ -96,6 +99,8 @@ func (s *Service) Configure(overlay swarm.Address, p2p p2p.DebugService, pingpon ...@@ -96,6 +99,8 @@ func (s *Service) Configure(overlay swarm.Address, p2p p2p.DebugService, pingpon
s.pseudosettle = pseudosettle s.pseudosettle = pseudosettle
s.transaction = transaction s.transaction = transaction
s.overlay = &overlay s.overlay = &overlay
s.post = post
s.postageContract = postageContract
s.setRouter(s.newRouter()) s.setRouter(s.newRouter())
} }
......
...@@ -6,6 +6,7 @@ package debugapi_test ...@@ -6,6 +6,7 @@ package debugapi_test
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/rand"
"encoding/hex" "encoding/hex"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
...@@ -24,6 +25,8 @@ import ( ...@@ -24,6 +25,8 @@ import (
p2pmock "github.com/ethersphere/bee/pkg/p2p/mock" p2pmock "github.com/ethersphere/bee/pkg/p2p/mock"
"github.com/ethersphere/bee/pkg/pingpong" "github.com/ethersphere/bee/pkg/pingpong"
"github.com/ethersphere/bee/pkg/postage" "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" "github.com/ethersphere/bee/pkg/resolver"
chequebookmock "github.com/ethersphere/bee/pkg/settlement/swap/chequebook/mock" chequebookmock "github.com/ethersphere/bee/pkg/settlement/swap/chequebook/mock"
swapmock "github.com/ethersphere/bee/pkg/settlement/swap/mock" swapmock "github.com/ethersphere/bee/pkg/settlement/swap/mock"
...@@ -37,6 +40,17 @@ import ( ...@@ -37,6 +40,17 @@ import (
"resenje.org/web" "resenje.org/web"
) )
var (
batchOk = make([]byte, 32)
batchOkStr string
)
func init() {
_, _ = rand.Read(batchOk)
batchOkStr = hex.EncodeToString(batchOk)
}
type testServerOptions struct { type testServerOptions struct {
Overlay swarm.Address Overlay swarm.Address
PublicKey ecdsa.PublicKey PublicKey ecdsa.PublicKey
...@@ -55,6 +69,8 @@ type testServerOptions struct { ...@@ -55,6 +69,8 @@ type testServerOptions struct {
SwapOpts []swapmock.Option SwapOpts []swapmock.Option
BatchStore postage.Storer BatchStore postage.Storer
TransactionOpts []transactionmock.Option TransactionOpts []transactionmock.Option
PostageContract postagecontract.Interface
Post postage.Service
} }
type testServer struct { type testServer struct {
...@@ -71,7 +87,7 @@ func newTestServer(t *testing.T, o testServerOptions) *testServer { ...@@ -71,7 +87,7 @@ func newTestServer(t *testing.T, o testServerOptions) *testServer {
transaction := transactionmock.New(o.TransactionOpts...) transaction := transactionmock.New(o.TransactionOpts...)
ln := lightnode.NewContainer(o.Overlay) ln := lightnode.NewContainer(o.Overlay)
s := debugapi.New(o.PublicKey, o.PSSPublicKey, o.EthereumAddress, logging.New(ioutil.Discard, 0), nil, o.CORSAllowedOrigins) 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) ts := httptest.NewServer(s)
t.Cleanup(ts.Close) t.Cleanup(ts.Close)
...@@ -169,7 +185,7 @@ func TestServer_Configure(t *testing.T) { ...@@ -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) testBasicRouter(t, client)
jsonhttptest.Request(t, client, http.MethodGet, "/readiness", http.StatusOK, jsonhttptest.Request(t, client, http.MethodGet, "/readiness", http.StatusOK,
......
...@@ -31,6 +31,9 @@ type ( ...@@ -31,6 +31,9 @@ type (
TagResponse = tagResponse TagResponse = tagResponse
ReserveStateResponse = reserveStateResponse ReserveStateResponse = reserveStateResponse
ChainStateResponse = chainStateResponse ChainStateResponse = chainStateResponse
PostageCreateResponse = postageCreateResponse
PostageStampResponse = postageStampResponse
PostageStampsResponse = postageStampsResponse
) )
var ( var (
......
...@@ -5,12 +5,162 @@ ...@@ -5,12 +5,162 @@
package debugapi package debugapi
import ( import (
"encoding/hex"
"encoding/json"
"errors"
"math/big"
"net/http" "net/http"
"strconv"
"github.com/ethersphere/bee/pkg/bigint" "github.com/ethersphere/bee/pkg/bigint"
"github.com/ethersphere/bee/pkg/jsonhttp" "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 { type reserveStateResponse struct {
Radius uint8 `json:"radius"` Radius uint8 `json:"radius"`
StorageRadius uint8 `json:"storageRadius"` StorageRadius uint8 `json:"storageRadius"`
......
...@@ -5,17 +5,260 @@ ...@@ -5,17 +5,260 @@
package debugapi_test package debugapi_test
import ( import (
"context"
"encoding/hex"
"errors"
"fmt"
"math/big" "math/big"
"net/http" "net/http"
"testing" "testing"
"github.com/ethersphere/bee/pkg/bigint" "github.com/ethersphere/bee/pkg/bigint"
"github.com/ethersphere/bee/pkg/debugapi" "github.com/ethersphere/bee/pkg/debugapi"
"github.com/ethersphere/bee/pkg/jsonhttp"
"github.com/ethersphere/bee/pkg/jsonhttp/jsonhttptest" "github.com/ethersphere/bee/pkg/jsonhttp/jsonhttptest"
"github.com/ethersphere/bee/pkg/postage" "github.com/ethersphere/bee/pkg/postage"
"github.com/ethersphere/bee/pkg/postage/batchstore/mock" "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) { func TestReserveState(t *testing.T) {
t.Run("ok", func(t *testing.T) { t.Run("ok", func(t *testing.T) {
ts := newTestServer(t, testServerOptions{ ts := newTestServer(t, testServerOptions{
......
...@@ -183,6 +183,24 @@ func (s *Service) newRouter() *mux.Router { ...@@ -183,6 +183,24 @@ func (s *Service) newRouter() *mux.Router {
"GET": http.HandlerFunc(s.getTagHandler), "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 return router
} }
......
...@@ -751,7 +751,7 @@ func NewBee(addr string, publicKey *ecdsa.PublicKey, signer crypto.Signer, netwo ...@@ -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 // 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 { 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