Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

postage: add create endpoint #1142

Merged
merged 15 commits into from
Jan 26, 2021
70 changes: 35 additions & 35 deletions cmd/bee/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,40 @@ import (
)

const (
optionNameDataDir = "data-dir"
optionNameDBCapacity = "db-capacity"
optionNamePassword = "password"
optionNamePasswordFile = "password-file"
optionNameAPIAddr = "api-addr"
optionNameP2PAddr = "p2p-addr"
optionNameNATAddr = "nat-addr"
optionNameP2PWSEnable = "p2p-ws-enable"
optionNameP2PQUICEnable = "p2p-quic-enable"
optionNameDebugAPIEnable = "debug-api-enable"
optionNameDebugAPIAddr = "debug-api-addr"
optionNameBootnodes = "bootnode"
optionNameNetworkID = "network-id"
optionWelcomeMessage = "welcome-message"
optionCORSAllowedOrigins = "cors-allowed-origins"
optionNameStandalone = "standalone"
optionNameTracingEnabled = "tracing-enable"
optionNameTracingEndpoint = "tracing-endpoint"
optionNameTracingServiceName = "tracing-service-name"
optionNameVerbosity = "verbosity"
optionNameGlobalPinningEnabled = "global-pinning-enable"
optionNamePaymentThreshold = "payment-threshold"
optionNamePaymentTolerance = "payment-tolerance"
optionNamePaymentEarly = "payment-early"
optionNameResolverEndpoints = "resolver-options"
optionNameGatewayMode = "gateway-mode"
optionNameClefSignerEnable = "clef-signer-enable"
optionNameClefSignerEndpoint = "clef-signer-endpoint"
optionNameSwapEndpoint = "swap-endpoint"
optionNameSwapFactoryAddress = "swap-factory-address"
optionNameSwapInitialDeposit = "swap-initial-deposit"
optionNameSwapEnable = "swap-enable"
optionNamePostageStampAddress = "postage-stamp-address"
optionNamePriceOracleAddress = "price-oracle-address"
optionNameDataDir = "data-dir"
optionNameDBCapacity = "db-capacity"
optionNamePassword = "password"
optionNamePasswordFile = "password-file"
optionNameAPIAddr = "api-addr"
optionNameP2PAddr = "p2p-addr"
optionNameNATAddr = "nat-addr"
optionNameP2PWSEnable = "p2p-ws-enable"
optionNameP2PQUICEnable = "p2p-quic-enable"
optionNameDebugAPIEnable = "debug-api-enable"
optionNameDebugAPIAddr = "debug-api-addr"
optionNameBootnodes = "bootnode"
optionNameNetworkID = "network-id"
optionWelcomeMessage = "welcome-message"
optionCORSAllowedOrigins = "cors-allowed-origins"
optionNameStandalone = "standalone"
optionNameTracingEnabled = "tracing-enable"
optionNameTracingEndpoint = "tracing-endpoint"
optionNameTracingServiceName = "tracing-service-name"
optionNameVerbosity = "verbosity"
optionNameGlobalPinningEnabled = "global-pinning-enable"
optionNamePaymentThreshold = "payment-threshold"
optionNamePaymentTolerance = "payment-tolerance"
optionNamePaymentEarly = "payment-early"
optionNameResolverEndpoints = "resolver-options"
optionNameGatewayMode = "gateway-mode"
optionNameClefSignerEnable = "clef-signer-enable"
optionNameClefSignerEndpoint = "clef-signer-endpoint"
optionNameSwapEndpoint = "swap-endpoint"
optionNameSwapFactoryAddress = "swap-factory-address"
optionNameSwapInitialDeposit = "swap-initial-deposit"
optionNameSwapEnable = "swap-enable"
optionNamePostageContractAddress = "postage-stamp-address"
optionNamePriceOracleAddress = "price-oracle-address"
)

func init() {
Expand Down Expand Up @@ -206,6 +206,6 @@ func (c *command) setAllFlags(cmd *cobra.Command) {
cmd.Flags().String(optionNameSwapFactoryAddress, "", "swap factory address")
cmd.Flags().Uint64(optionNameSwapInitialDeposit, 100000000, "initial deposit if deploying a new chequebook")
cmd.Flags().Bool(optionNameSwapEnable, true, "enable swap")
cmd.Flags().String(optionNamePostageStampAddress, "", "postage stamp address")
cmd.Flags().String(optionNamePostageContractAddress, "", "postage stamp contract address")
cmd.Flags().String(optionNamePriceOracleAddress, "", "price oracle address")
}
2 changes: 1 addition & 1 deletion cmd/bee/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ Welcome to the Swarm.... Bzzz Bzzzz Bzzzz
SwapFactoryAddress: c.config.GetString(optionNameSwapFactoryAddress),
SwapInitialDeposit: c.config.GetUint64(optionNameSwapInitialDeposit),
SwapEnable: c.config.GetBool(optionNameSwapEnable),
PostageStampAddress: c.config.GetString(optionNamePostageStampAddress),
PostageContractAddress: c.config.GetString(optionNamePostageContractAddress),
PriceOracleAddress: c.config.GetString(optionNamePriceOracleAddress),
})
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/internal/file/io_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func newTestServer(t *testing.T, storer storage.Storer) *url.URL {
signer := crypto.NewDefaultSigner(pk)
mockPostage := mockpost.New()

s := api.New(tags.NewTags(store, logger), storer, nil, nil, nil, mockPostage, signer, logger, nil, api.Options{})
s := api.New(tags.NewTags(store, logger), storer, nil, nil, nil, mockPostage, nil, signer, logger, nil, api.Options{})
ts := httptest.NewServer(s)
srvUrl, err := url.Parse(ts.URL)
if err != nil {
Expand Down
47 changes: 25 additions & 22 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/ethersphere/bee/pkg/logging"
m "github.com/ethersphere/bee/pkg/metrics"
"github.com/ethersphere/bee/pkg/postage"
"github.com/ethersphere/bee/pkg/postage/postagecontract"
"github.com/ethersphere/bee/pkg/pss"
"github.com/ethersphere/bee/pkg/resolver"
"github.com/ethersphere/bee/pkg/storage"
Expand Down Expand Up @@ -66,15 +67,16 @@ type Service interface {
}

type server struct {
Tags *tags.Tags
Storer storage.Storer
Resolver resolver.Interface
Pss pss.Interface
Traversal traversal.Service
Logger logging.Logger
Tracer *tracing.Tracer
signer crypto.Signer
post postage.Service
Tags *tags.Tags
Storer storage.Storer
Resolver resolver.Interface
Pss pss.Interface
Traversal traversal.Service
Logger logging.Logger
Tracer *tracing.Tracer
signer crypto.Signer
post postage.Service
postageContract postagecontract.Interface
Options
http.Handler
metrics metrics
Expand All @@ -95,20 +97,21 @@ const (
)

// New will create a and initialize a new API service.
func New(tags *tags.Tags, storer storage.Storer, resolver resolver.Interface, pss pss.Interface, traversalService traversal.Service, post postage.Service, signer crypto.Signer, logger logging.Logger, tracer *tracing.Tracer, o Options) Service {
func New(tags *tags.Tags, storer storage.Storer, resolver resolver.Interface, pss pss.Interface, traversalService traversal.Service, post postage.Service, postageContract postagecontract.Interface, signer crypto.Signer, logger logging.Logger, tracer *tracing.Tracer, o Options) Service {
s := &server{
Tags: tags,
Storer: storer,
Resolver: resolver,
Pss: pss,
Traversal: traversalService,
post: post,
signer: signer,
Options: o,
Logger: logger,
Tracer: tracer,
metrics: newMetrics(),
quit: make(chan struct{}),
Tags: tags,
Storer: storer,
Resolver: resolver,
Pss: pss,
Traversal: traversalService,
post: post,
postageContract: postageContract,
signer: signer,
Options: o,
Logger: logger,
Tracer: tracer,
metrics: newMetrics(),
quit: make(chan struct{}),
}

s.setupRouting()
Expand Down
6 changes: 4 additions & 2 deletions pkg/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/ethersphere/bee/pkg/jsonhttp/jsonhttptest"
"github.com/ethersphere/bee/pkg/logging"
mockpost "github.com/ethersphere/bee/pkg/postage/mock"
"github.com/ethersphere/bee/pkg/postage/postagecontract"
"github.com/ethersphere/bee/pkg/pss"
"github.com/ethersphere/bee/pkg/resolver"
resolverMock "github.com/ethersphere/bee/pkg/resolver/mock"
Expand All @@ -37,6 +38,7 @@ type testServerOptions struct {
Resolver resolver.Interface
Pss pss.Interface
Traversal traversal.Service
PostageContract postagecontract.Interface
WsPath string
Tags *tags.Tags
GatewayMode bool
Expand All @@ -59,7 +61,7 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket.
if o.WsPingPeriod == 0 {
o.WsPingPeriod = 60 * time.Second
}
s := api.New(o.Tags, o.Storer, o.Resolver, o.Pss, o.Traversal, mockPostage, signer, o.Logger, nil, api.Options{
s := api.New(o.Tags, o.Storer, o.Resolver, o.Pss, o.Traversal, mockPostage, o.PostageContract, signer, o.Logger, nil, api.Options{
GatewayMode: o.GatewayMode,
WsPingPeriod: o.WsPingPeriod,
})
Expand Down Expand Up @@ -160,7 +162,7 @@ func TestParseName(t *testing.T) {
signer := crypto.NewDefaultSigner(pk)
mockPostage := mockpost.New()

s := api.New(nil, nil, tC.res, nil, nil, mockPostage, signer, log, nil, api.Options{}).(*api.Server)
s := api.New(nil, nil, tC.res, nil, nil, mockPostage, nil, signer, log, nil, api.Options{}).(*api.Server)

t.Run(tC.desc, func(t *testing.T) {
got, err := s.ResolveNameOrAddress(tC.name)
Expand Down
1 change: 1 addition & 0 deletions pkg/api/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type (
PinnedChunk = pinnedChunk
ListPinnedChunksResponse = listPinnedChunksResponse
UpdatePinCounter = updatePinCounter
PostageCreateResponse = postageCreateResponse
)

var (
Expand Down
52 changes: 52 additions & 0 deletions pkg/api/postage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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
acud marked this conversation as resolved.
Show resolved Hide resolved

import (
"math/big"
"net/http"
"strconv"

"github.com/ethersphere/bee/pkg/jsonhttp"
"github.com/gorilla/mux"
)

type postageCreateResponse struct {
BatchID []byte `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")
acud marked this conversation as resolved.
Show resolved Hide resolved
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")

batchID, err := s.postageContract.CreateBatch(r.Context(), amount, uint8(depth), label)
if err != nil {
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.OK(w, &postageCreateResponse{
BatchID: batchID,
})
}
95 changes: 95 additions & 0 deletions pkg/api/postage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// 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"
"errors"
"fmt"
"math/big"
"net/http"
"testing"

"github.com/ethersphere/bee/pkg/api"
"github.com/ethersphere/bee/pkg/jsonhttp"
"github.com/ethersphere/bee/pkg/jsonhttp/jsonhttptest"
contractMock "github.com/ethersphere/bee/pkg/postage/postagecontract/mock"
)

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, 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.StatusOK,
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, 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("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("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",
}),
)
})
}
7 changes: 7 additions & 0 deletions pkg/api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,13 @@ func (s *server) setupRouting() {
})),
)

handle(router, "/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,
Expand Down
Loading