Skip to content

Commit

Permalink
feat(debugapi, postage): topup batch handling (#2401)
Browse files Browse the repository at this point in the history
  • Loading branch information
aloknerurkar authored Aug 23, 2021
1 parent 136d9f2 commit 81988b1
Show file tree
Hide file tree
Showing 16 changed files with 651 additions and 61 deletions.
37 changes: 37 additions & 0 deletions openapi/SwarmDebug.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -871,3 +871,40 @@ paths:
$ref: "SwarmCommon.yaml#/components/responses/500"
default:
description: Default response

"/stamps/topup/{id}/{amount}":
patch:
summary: Top up an existing postage batch.
description: Be aware, this endpoint creates on-chain transactions and transfers BZZ from the node's Ethereum account and hence directly manipulates the wallet balance!
tags:
- Postage Stamps
parameters:
- in: path
name: id
schema:
$ref: "SwarmCommon.yaml#/components/schemas/BatchID"
required: true
description: Batch ID to top up
- in: path
name: amount
schema:
type: integer
required: true
description: Amount of BZZ per chunk to top up to an existing postage batch.
responses:
"202":
description: Returns the postage batch ID that was topped up
content:
application/json:
schema:
$ref: "SwarmCommon.yaml#/components/schemas/BatchIDResponse"
"400":
$ref: "SwarmCommon.yaml#/components/responses/400"
"429":
$ref: "SwarmCommon.yaml#/components/responses/429"
"402":
$ref: "SwarmCommon.yaml#/components/responses/402"
"500":
$ref: "SwarmCommon.yaml#/components/responses/500"
default:
description: Default response
60 changes: 60 additions & 0 deletions pkg/debugapi/postage.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,63 @@ func (s *Service) estimateBatchTTL(id []byte) (int64, error) {

return ttl.Int64(), nil
}

func (s *Service) postageTopUpHandler(w http.ResponseWriter, r *http.Request) {
idStr := mux.Vars(r)["id"]
if idStr == "" || len(idStr) != 64 {
s.logger.Error("topup batch: invalid batchID")
jsonhttp.BadRequest(w, "invalid batchID")
return
}
id, err := hex.DecodeString(idStr)
if err != nil {
s.logger.Debugf("topup batch: invalid batchID: %v", err)
s.logger.Error("topup batch: invalid batchID")
jsonhttp.BadRequest(w, "invalid batchID")
return
}

amount, ok := big.NewInt(0).SetString(mux.Vars(r)["amount"], 10)
if !ok {
s.logger.Error("topup batch: invalid amount")
jsonhttp.BadRequest(w, "invalid postage amount")
return
}

ctx := r.Context()
if price, ok := r.Header[gasPriceHeader]; ok {
p, ok := big.NewInt(0).SetString(price[0], 10)
if !ok {
s.logger.Error("topup batch: bad gas price")
jsonhttp.BadRequest(w, errBadGasPrice)
return
}
ctx = sctx.SetGasPrice(ctx, p)
}

if !s.postageCreateSem.TryAcquire(1) {
s.logger.Debug("topup batch: simultaneous on-chain operations not supported")
s.logger.Error("topup batch: simultaneous on-chain operations not supported")
jsonhttp.TooManyRequests(w, "simultaneous on-chain operations not supported")
return
}
defer s.postageCreateSem.Release(1)

err = s.postageContract.TopUpBatch(ctx, id, amount)
if err != nil {
if errors.Is(err, postagecontract.ErrInsufficientFunds) {
s.logger.Debugf("topup batch: out of funds: %v", err)
s.logger.Error("topup batch: out of funds")
jsonhttp.PaymentRequired(w, "out of funds")
return
}
s.logger.Debugf("topup batch: failed to create: %v", err)
s.logger.Error("topup batch: failed to create")
jsonhttp.InternalServerError(w, "cannot topup batch")
return
}

jsonhttp.Accepted(w, &postageCreateResponse{
BatchID: id,
})
}
118 changes: 118 additions & 0 deletions pkg/debugapi/postage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package debugapi_test

import (
"bytes"
"context"
"encoding/hex"
"errors"
Expand Down Expand Up @@ -368,3 +369,120 @@ func TestChainState(t *testing.T) {
)
})
}

func TestPostageTopUpStamp(t *testing.T) {
topupAmount := int64(1000)
topupBatch := func(id string, amount int64) string {
return fmt.Sprintf("/stamps/topup/%s/%d", id, amount)
}

t.Run("ok", func(t *testing.T) {
contract := contractMock.New(
contractMock.WithTopUpBatchFunc(func(ctx context.Context, id []byte, ib *big.Int) error {
if !bytes.Equal(id, batchOk) {
return errors.New("incorrect batch ID in call")
}
if ib.Cmp(big.NewInt(topupAmount)) != 0 {
return fmt.Errorf("called with wrong topup amount. wanted %d, got %d", topupAmount, ib)
}
return nil
}),
)
ts := newTestServer(t, testServerOptions{
PostageContract: contract,
})

jsonhttptest.Request(t, ts.Client, http.MethodPatch, topupBatch(batchOkStr, topupAmount), http.StatusAccepted,
jsonhttptest.WithExpectedJSONResponse(&debugapi.PostageCreateResponse{
BatchID: batchOk,
}),
)
})

t.Run("with-custom-gas", func(t *testing.T) {
contract := contractMock.New(
contractMock.WithTopUpBatchFunc(func(ctx context.Context, id []byte, ib *big.Int) error {
if !bytes.Equal(id, batchOk) {
return errors.New("incorrect batch ID in call")
}
if ib.Cmp(big.NewInt(topupAmount)) != 0 {
return fmt.Errorf("called with wrong topup amount. wanted %d, got %d", topupAmount, ib)
}
if sctx.GetGasPrice(ctx).Cmp(big.NewInt(10000)) != 0 {
return fmt.Errorf("called with wrong gas price. wanted %d, got %d", 10000, sctx.GetGasPrice(ctx))
}
return nil
}),
)
ts := newTestServer(t, testServerOptions{
PostageContract: contract,
})

jsonhttptest.Request(t, ts.Client, http.MethodPatch, topupBatch(batchOkStr, topupAmount), http.StatusAccepted,
jsonhttptest.WithRequestHeader("Gas-Price", "10000"),
jsonhttptest.WithExpectedJSONResponse(&debugapi.PostageCreateResponse{
BatchID: batchOk,
}),
)
})

t.Run("with-error", func(t *testing.T) {
contract := contractMock.New(
contractMock.WithTopUpBatchFunc(func(ctx context.Context, id []byte, ib *big.Int) error {
return errors.New("err")
}),
)
ts := newTestServer(t, testServerOptions{
PostageContract: contract,
})

jsonhttptest.Request(t, ts.Client, http.MethodPatch, topupBatch(batchOkStr, topupAmount), http.StatusInternalServerError,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusInternalServerError,
Message: "cannot topup batch",
}),
)
})

t.Run("out-of-funds", func(t *testing.T) {
contract := contractMock.New(
contractMock.WithTopUpBatchFunc(func(ctx context.Context, id []byte, ib *big.Int) error {
return postagecontract.ErrInsufficientFunds
}),
)
ts := newTestServer(t, testServerOptions{
PostageContract: contract,
})

jsonhttptest.Request(t, ts.Client, http.MethodPatch, topupBatch(batchOkStr, topupAmount), http.StatusPaymentRequired,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusPaymentRequired,
Message: "out of funds",
}),
)
})

t.Run("invalid batch id", func(t *testing.T) {
ts := newTestServer(t, testServerOptions{})

jsonhttptest.Request(t, ts.Client, http.MethodPatch, "/stamps/topup/abcd/2", http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusBadRequest,
Message: "invalid batchID",
}),
)
})

t.Run("invalid amount", func(t *testing.T) {
ts := newTestServer(t, testServerOptions{})

wrongURL := fmt.Sprintf("/stamps/topup/%s/amount", batchOkStr)

jsonhttptest.Request(t, ts.Client, http.MethodPatch, wrongURL, http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{
Code: http.StatusBadRequest,
Message: "invalid postage amount",
}),
)
})
}
6 changes: 6 additions & 0 deletions pkg/debugapi/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,12 @@ func (s *Service) newRouter() *mux.Router {
})),
)

router.Handle("/stamps/topup/{id}/{amount}", web.ChainHandlers(
web.FinalHandler(jsonhttp.MethodHandler{
"PATCH": http.HandlerFunc(s.postageTopUpHandler),
})),
)

return router
}

Expand Down
68 changes: 46 additions & 22 deletions pkg/node/devnode.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,28 +200,52 @@ func NewDevBee(logger logging.Logger, o *DevOptions) (b *DevBee, err error) {
}

post := mockPost.New()
postageContract := mockPostContract.New(mockPostContract.WithCreateBatchFunc(
func(ctx context.Context, initialBalance *big.Int, depth uint8, immutable bool, label string) ([]byte, error) {
id := postagetesting.MustNewID()
b := &postage.Batch{
ID: id,
Owner: overlayEthAddress.Bytes(),
Value: big.NewInt(0),
Depth: depth,
Immutable: immutable,
}

err := batchStore.Put(b, initialBalance, depth)
if err != nil {
return nil, err
}

stampIssuer := postage.NewStampIssuer(label, string(overlayEthAddress.Bytes()), id, initialBalance, depth, 0, 0, immutable)
post.Add(stampIssuer)

return id, nil
},
))
postageContract := mockPostContract.New(
mockPostContract.WithCreateBatchFunc(
func(ctx context.Context, initialBalance *big.Int, depth uint8, immutable bool, label string) ([]byte, error) {
id := postagetesting.MustNewID()
b := &postage.Batch{
ID: id,
Owner: overlayEthAddress.Bytes(),
Value: big.NewInt(0),
Depth: depth,
Immutable: immutable,
}

totalAmount := big.NewInt(0).Mul(initialBalance, big.NewInt(int64(1<<depth)))

err := batchStore.Put(b, totalAmount, depth)
if err != nil {
return nil, err
}

stampIssuer := postage.NewStampIssuer(label, string(overlayEthAddress.Bytes()), id, totalAmount, depth, 0, 0, immutable)
post.Add(stampIssuer)

return id, nil
},
),
mockPostContract.WithTopUpBatchFunc(
func(ctx context.Context, batchID []byte, topupAmount *big.Int) error {
batch, err := batchStore.Get(batchID)
if err != nil {
return err
}

totalAmount := big.NewInt(0).Mul(topupAmount, big.NewInt(int64(1<<batch.Depth)))

newBalance := big.NewInt(0).Add(totalAmount, batch.Value)

err = batchStore.Put(batch, newBalance, batch.Depth)
if err != nil {
return err
}

post.HandleTopUp(batch.ID, newBalance)
return nil
},
),
)

feedFactory := factory.New(storer)

Expand Down
1 change: 1 addition & 0 deletions pkg/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ func NewBee(addr string, publicKey *ecdsa.PublicKey, signer crypto.Signer, netwo
erc20Address,
transactionService,
post,
batchStore,
)

if natManager := p2ps.NATManager(); natManager != nil {
Expand Down
12 changes: 9 additions & 3 deletions pkg/postage/batchservice/batchservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type batchService struct {
logger logging.Logger
listener postage.Listener
owner []byte
batchListener postage.BatchCreationListener
batchListener postage.BatchEventListener

checksum hash.Hash // checksum hasher
resync bool
Expand All @@ -46,7 +46,7 @@ func New(
logger logging.Logger,
listener postage.Listener,
owner []byte,
batchListener postage.BatchCreationListener,
batchListener postage.BatchEventListener,
checksumFunc func() hash.Hash,
resync bool,
) (Interface, error) {
Expand Down Expand Up @@ -110,8 +110,9 @@ func (svc *batchService) Create(id, owner []byte, normalisedBalance *big.Int, de
}

if bytes.Equal(svc.owner, owner) && svc.batchListener != nil {
svc.batchListener.Handle(b)
svc.batchListener.HandleCreate(b)
}

cs, err := svc.updateChecksum(txHash)
if err != nil {
return fmt.Errorf("update checksum: %w", err)
Expand All @@ -133,6 +134,11 @@ func (svc *batchService) TopUp(id []byte, normalisedBalance *big.Int, txHash []b
if err != nil {
return fmt.Errorf("put: %w", err)
}

if bytes.Equal(svc.owner, b.Owner) && svc.batchListener != nil {
svc.batchListener.HandleTopUp(id, normalisedBalance)
}

cs, err := svc.updateChecksum(txHash)
if err != nil {
return fmt.Errorf("update checksum: %w", err)
Expand Down
Loading

0 comments on commit 81988b1

Please sign in to comment.