Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
satellite/payments/storjscan: add client and list all payments API call
Change-Id: I1f5065b3d15cc93f4b42868941e82e04af364565
- Loading branch information
Showing
6 changed files
with
389 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
satellite/payments/storjscan/blockchaintest/blockchaintest.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// Copyright (C) 2022 Storj Labs, Inc. | ||
// See LICENSE for copying information. | ||
|
||
package blockchaintest | ||
|
||
import ( | ||
"storj.io/common/testrand" | ||
"storj.io/storj/private/blockchain" | ||
) | ||
|
||
// NewAddress creates new blockchain address for testing. | ||
func NewAddress() blockchain.Address { | ||
var address blockchain.Address | ||
b := testrand.BytesInt(blockchain.AddressLength) | ||
copy(address[:], b) | ||
return address | ||
} | ||
|
||
// NewHash creates new blockchain hash for testing. | ||
func NewHash() blockchain.Hash { | ||
var h blockchain.Hash | ||
b := testrand.BytesInt(blockchain.HashLength) | ||
copy(h[:], b) | ||
return h | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
// Copyright (C) 2022 Storj Labs, Inc. | ||
// See LICENSE for copying information. | ||
|
||
package storjscan | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"math/big" | ||
"net/http" | ||
"strconv" | ||
"time" | ||
|
||
"github.com/zeebo/errs" | ||
|
||
"storj.io/storj/private/blockchain" | ||
) | ||
|
||
var ( | ||
// ClientErr is general purpose storjscan client error class. | ||
ClientErr = errs.Class("storjscan client") | ||
// ClientErrUnauthorized is unauthorized err storjscan client error class. | ||
ClientErrUnauthorized = errs.Class("storjscan client unauthorized") | ||
) | ||
|
||
// Header holds ethereum blockchain block header data. | ||
type Header struct { | ||
Hash blockchain.Hash | ||
Number int64 | ||
Timestamp time.Time | ||
} | ||
|
||
// Payment holds storjscan payment data. | ||
type Payment struct { | ||
From blockchain.Address | ||
To blockchain.Address | ||
TokenValue *big.Int | ||
BlockHash blockchain.Hash | ||
BlockNumber int64 | ||
Transaction blockchain.Hash | ||
LogIndex int | ||
Timestamp time.Time | ||
} | ||
|
||
// LatestPayments contains latest payments and latest chain block header. | ||
type LatestPayments struct { | ||
LatestBlock Header | ||
Payments []Payment | ||
} | ||
|
||
// Client is storjscan HTTP API client. | ||
type Client struct { | ||
endpoint string | ||
identifier string | ||
secret string | ||
http http.Client | ||
} | ||
|
||
// NewClient creates new storjscan API client. | ||
func NewClient(endpoint, identifier, secret string) *Client { | ||
return &Client{ | ||
endpoint: endpoint, | ||
identifier: identifier, | ||
secret: secret, | ||
http: http.Client{}, | ||
} | ||
} | ||
|
||
// Payments retrieves all payments after specified block for wallets associated with particular API key. | ||
func (client *Client) Payments(ctx context.Context, from int64) (_ LatestPayments, err error) { | ||
defer mon.Task()(&ctx)(&err) | ||
|
||
p := client.endpoint + "/api/v0/tokens/payments" | ||
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p, nil) | ||
if err != nil { | ||
return LatestPayments{}, ClientErr.Wrap(err) | ||
} | ||
|
||
req.SetBasicAuth(client.identifier, client.secret) | ||
|
||
query := req.URL.Query() | ||
query.Set("from", strconv.FormatInt(from, 10)) | ||
req.URL.RawQuery = query.Encode() | ||
|
||
resp, err := client.http.Do(req) | ||
if err != nil { | ||
return LatestPayments{}, ClientErr.Wrap(err) | ||
} | ||
defer func() { | ||
err = errs.Combine(err, ClientErr.Wrap(resp.Body.Close())) | ||
}() | ||
|
||
if resp.StatusCode != http.StatusOK { | ||
var data struct { | ||
Error string `json:"error"` | ||
} | ||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { | ||
return LatestPayments{}, ClientErr.Wrap(err) | ||
} | ||
|
||
switch resp.StatusCode { | ||
case http.StatusUnauthorized: | ||
return LatestPayments{}, ClientErrUnauthorized.New("%s", data.Error) | ||
default: | ||
return LatestPayments{}, ClientErr.New("%s", data.Error) | ||
} | ||
} | ||
|
||
var payments LatestPayments | ||
if err := json.NewDecoder(resp.Body).Decode(&payments); err != nil { | ||
return LatestPayments{}, ClientErr.Wrap(err) | ||
} | ||
|
||
return payments, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
// Copyright (C) 2022 Storj Labs, Inc. | ||
// See LICENSE for copying information. | ||
|
||
package storjscan_test | ||
|
||
import ( | ||
"math/big" | ||
"net/http" | ||
"net/http/httptest" | ||
"strconv" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/require" | ||
"github.com/zeebo/errs" | ||
|
||
"storj.io/common/testcontext" | ||
"storj.io/storj/satellite/payments/storjscan" | ||
"storj.io/storj/satellite/payments/storjscan/blockchaintest" | ||
"storj.io/storj/satellite/payments/storjscan/storjscantest" | ||
) | ||
|
||
func TestClientMocked(t *testing.T) { | ||
ctx := testcontext.New(t) | ||
now := time.Now().Round(time.Second).UTC() | ||
|
||
var payments []storjscan.Payment | ||
for i := 0; i < 100; i++ { | ||
payments = append(payments, storjscan.Payment{ | ||
From: blockchaintest.NewAddress(), | ||
To: blockchaintest.NewAddress(), | ||
TokenValue: new(big.Int).SetInt64(int64(i)), | ||
BlockHash: blockchaintest.NewHash(), | ||
BlockNumber: int64(i), | ||
Transaction: blockchaintest.NewHash(), | ||
LogIndex: i, | ||
Timestamp: now.Add(time.Duration(i) * time.Second), | ||
}) | ||
} | ||
latestBlock := storjscan.Header{ | ||
Hash: payments[len(payments)-1].BlockHash, | ||
Number: payments[len(payments)-1].BlockNumber, | ||
Timestamp: payments[len(payments)-1].Timestamp, | ||
} | ||
|
||
const ( | ||
identifier = "eu" | ||
secret = "secret" | ||
) | ||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
var err error | ||
|
||
if err := storjscantest.CheckAuth(r, identifier, secret); err != nil { | ||
storjscantest.ServeJSONError(t, w, http.StatusUnauthorized, err) | ||
return | ||
} | ||
|
||
var from int64 | ||
if s := r.URL.Query().Get("from"); s != "" { | ||
from, err = strconv.ParseInt(s, 10, 64) | ||
if err != nil { | ||
storjscantest.ServeJSONError(t, w, http.StatusBadRequest, errs.New("from parameter is missing")) | ||
return | ||
} | ||
} | ||
|
||
storjscantest.ServePayments(t, w, from, latestBlock, payments) | ||
})) | ||
defer server.Close() | ||
|
||
client := storjscan.NewClient(server.URL, "eu", "secret") | ||
|
||
t.Run("all payments from 0", func(t *testing.T) { | ||
actual, err := client.Payments(ctx, 0) | ||
require.NoError(t, err) | ||
require.Equal(t, latestBlock, actual.LatestBlock) | ||
require.Equal(t, len(payments), len(actual.Payments)) | ||
require.Equal(t, payments, actual.Payments) | ||
}) | ||
t.Run("payments from 50", func(t *testing.T) { | ||
actual, err := client.Payments(ctx, 50) | ||
require.NoError(t, err) | ||
require.Equal(t, latestBlock, actual.LatestBlock) | ||
require.Equal(t, 50, len(actual.Payments)) | ||
require.Equal(t, payments[50:], actual.Payments) | ||
}) | ||
} | ||
|
||
func TestClientMockedUnauthorized(t *testing.T) { | ||
ctx := testcontext.New(t) | ||
|
||
const ( | ||
identifier = "eu" | ||
secret = "secret" | ||
) | ||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
if err := storjscantest.CheckAuth(r, identifier, secret); err != nil { | ||
storjscantest.ServeJSONError(t, w, http.StatusUnauthorized, err) | ||
return | ||
} | ||
})) | ||
defer server.Close() | ||
|
||
t.Run("empty credentials", func(t *testing.T) { | ||
client := storjscan.NewClient(server.URL, "", "") | ||
_, err := client.Payments(ctx, 0) | ||
require.Error(t, err) | ||
require.True(t, storjscan.ClientErrUnauthorized.Has(err)) | ||
require.Equal(t, "identifier is invalid", errs.Unwrap(err).Error()) | ||
}) | ||
|
||
t.Run("invalid identifier", func(t *testing.T) { | ||
client := storjscan.NewClient(server.URL, "invalid", "secret") | ||
_, err := client.Payments(ctx, 0) | ||
require.Error(t, err) | ||
require.True(t, storjscan.ClientErrUnauthorized.Has(err)) | ||
require.Equal(t, "identifier is invalid", errs.Unwrap(err).Error()) | ||
}) | ||
|
||
t.Run("invalid secret", func(t *testing.T) { | ||
client := storjscan.NewClient(server.URL, "eu", "invalid") | ||
_, err := client.Payments(ctx, 0) | ||
require.Error(t, err) | ||
require.True(t, storjscan.ClientErrUnauthorized.Has(err)) | ||
require.Equal(t, "secret is invalid", errs.Unwrap(err).Error()) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// Copyright (C) 2022 Storj Labs, Inc. | ||
// See LICENSE for copying information. | ||
|
||
package storjscan | ||
|
||
import "github.com/spacemonkeygo/monkit/v3" | ||
|
||
var mon = monkit.Package() |
Oops, something went wrong.
0bf1252
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This commit has been mentioned on Storj Community Forum (official). There might be relevant details there:
https://forum.storj.io/t/release-preparation-v1-56/18583/1