Commit 5aa39499 authored by mrekucci's avatar mrekucci Committed by GitHub

feat: estimate expected lifetime of batches (#2336)

parent 2d069305
...@@ -9,6 +9,7 @@ package debugapi ...@@ -9,6 +9,7 @@ package debugapi
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"math/big"
"net/http" "net/http"
"sync" "sync"
...@@ -57,6 +58,7 @@ type Service struct { ...@@ -57,6 +58,7 @@ type Service struct {
corsAllowedOrigins []string corsAllowedOrigins []string
metricsRegistry *prometheus.Registry metricsRegistry *prometheus.Registry
lightNodes *lightnode.Container lightNodes *lightnode.Container
blockTime *big.Int
// handler is changed in the Configure method // handler is changed in the Configure method
handler http.Handler handler http.Handler
handlerMu sync.RWMutex handlerMu sync.RWMutex
...@@ -66,7 +68,7 @@ type Service struct { ...@@ -66,7 +68,7 @@ type Service struct {
// to expose /addresses, /health endpoints, Go metrics and pprof. It is useful to expose // to expose /addresses, /health endpoints, Go metrics and pprof. It is useful to expose
// these endpoints before all dependencies are configured and injected to have // these endpoints before all dependencies are configured and injected to have
// access to basic debugging tools and /health endpoint. // access to basic debugging tools and /health endpoint.
func New(publicKey, pssPublicKey ecdsa.PublicKey, ethereumAddress common.Address, logger logging.Logger, tracer *tracing.Tracer, corsAllowedOrigins []string, transaction transaction.Service) *Service { func New(publicKey, pssPublicKey ecdsa.PublicKey, ethereumAddress common.Address, logger logging.Logger, tracer *tracing.Tracer, corsAllowedOrigins []string, blockTime *big.Int, transaction transaction.Service) *Service {
s := new(Service) s := new(Service)
s.publicKey = publicKey s.publicKey = publicKey
s.pssPublicKey = pssPublicKey s.pssPublicKey = pssPublicKey
...@@ -74,6 +76,7 @@ func New(publicKey, pssPublicKey ecdsa.PublicKey, ethereumAddress common.Address ...@@ -74,6 +76,7 @@ func New(publicKey, pssPublicKey ecdsa.PublicKey, ethereumAddress common.Address
s.logger = logger s.logger = logger
s.tracer = tracer s.tracer = tracer
s.corsAllowedOrigins = corsAllowedOrigins s.corsAllowedOrigins = corsAllowedOrigins
s.blockTime = blockTime
s.metricsRegistry = newMetricsRegistry() s.metricsRegistry = newMetricsRegistry()
s.transaction = transaction s.transaction = transaction
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"io/ioutil" "io/ioutil"
"math/big"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
...@@ -86,7 +87,7 @@ func newTestServer(t *testing.T, o testServerOptions) *testServer { ...@@ -86,7 +87,7 @@ func newTestServer(t *testing.T, o testServerOptions) *testServer {
swapserv := swapmock.New(o.SwapOpts...) swapserv := swapmock.New(o.SwapOpts...)
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, transaction) s := debugapi.New(o.PublicKey, o.PSSPublicKey, o.EthereumAddress, logging.New(ioutil.Discard, 0), nil, o.CORSAllowedOrigins, big.NewInt(2), transaction)
s.Configure(o.Overlay, o.P2P, o.Pingpong, topologyDriver, ln, o.Storer, o.Tags, acc, settlement, true, swapserv, chequebook, o.BatchStore, o.Post, o.PostageContract) s.Configure(o.Overlay, o.P2P, o.Pingpong, topologyDriver, ln, o.Storer, o.Tags, acc, settlement, true, swapserv, chequebook, o.BatchStore, o.Post, o.PostageContract)
ts := httptest.NewServer(s) ts := httptest.NewServer(s)
t.Cleanup(ts.Close) t.Cleanup(ts.Close)
...@@ -154,7 +155,7 @@ func TestServer_Configure(t *testing.T) { ...@@ -154,7 +155,7 @@ func TestServer_Configure(t *testing.T) {
swapserv := swapmock.New(o.SwapOpts...) swapserv := swapmock.New(o.SwapOpts...)
ln := lightnode.NewContainer(o.Overlay) ln := lightnode.NewContainer(o.Overlay)
transaction := transactionmock.New(o.TransactionOpts...) transaction := transactionmock.New(o.TransactionOpts...)
s := debugapi.New(o.PublicKey, o.PSSPublicKey, o.EthereumAddress, logging.New(ioutil.Discard, 0), nil, nil, transaction) s := debugapi.New(o.PublicKey, o.PSSPublicKey, o.EthereumAddress, logging.New(ioutil.Discard, 0), nil, nil, big.NewInt(2), transaction)
ts := httptest.NewServer(s) ts := httptest.NewServer(s)
t.Cleanup(ts.Close) t.Cleanup(ts.Close)
......
...@@ -102,6 +102,7 @@ type postageStampResponse struct { ...@@ -102,6 +102,7 @@ type postageStampResponse struct {
BlockNumber uint64 `json:"blockNumber"` BlockNumber uint64 `json:"blockNumber"`
ImmutableFlag bool `json:"immutableFlag"` ImmutableFlag bool `json:"immutableFlag"`
Exists bool `json:"exists"` Exists bool `json:"exists"`
BatchTTL int64 `json:"batchTTL"`
} }
type postageStampsResponse struct { type postageStampsResponse struct {
...@@ -123,13 +124,20 @@ type bucketData struct { ...@@ -123,13 +124,20 @@ type bucketData struct {
func (s *Service) postageGetStampsHandler(w http.ResponseWriter, _ *http.Request) { func (s *Service) postageGetStampsHandler(w http.ResponseWriter, _ *http.Request) {
resp := postageStampsResponse{} resp := postageStampsResponse{}
for _, v := range s.post.StampIssuers() { for _, v := range s.post.StampIssuers() {
exists, err := s.post.BatchExists(v.ID()) exists, err := s.batchStore.Exists(v.ID())
if err != nil { if err != nil {
s.logger.Errorf("get stamp issuer: check batch: %v", err) s.logger.Debugf("get stamp issuer: check batch: %v", err)
s.logger.Error("get stamp issuer: check batch") s.logger.Error("get stamp issuer: check batch")
jsonhttp.InternalServerError(w, "unable to check batch") jsonhttp.InternalServerError(w, "unable to check batch")
return return
} }
batchTTL, err := s.estimateBatchTTL(v.ID())
if err != nil {
s.logger.Debugf("get stamp issuer: estimate batch expiration: %v", err)
s.logger.Error("get stamp issuer: estimate batch expiration")
jsonhttp.InternalServerError(w, "unable to estimate batch expiration")
return
}
resp.Stamps = append(resp.Stamps, postageStampResponse{ resp.Stamps = append(resp.Stamps, postageStampResponse{
BatchID: v.ID(), BatchID: v.ID(),
Utilization: v.Utilization(), Utilization: v.Utilization(),
...@@ -141,6 +149,7 @@ func (s *Service) postageGetStampsHandler(w http.ResponseWriter, _ *http.Request ...@@ -141,6 +149,7 @@ func (s *Service) postageGetStampsHandler(w http.ResponseWriter, _ *http.Request
BlockNumber: v.BlockNumber(), BlockNumber: v.BlockNumber(),
ImmutableFlag: v.ImmutableFlag(), ImmutableFlag: v.ImmutableFlag(),
Exists: exists, Exists: exists,
BatchTTL: batchTTL,
}) })
} }
jsonhttp.OK(w, resp) jsonhttp.OK(w, resp)
...@@ -155,7 +164,7 @@ func (s *Service) postageGetStampBucketsHandler(w http.ResponseWriter, r *http.R ...@@ -155,7 +164,7 @@ func (s *Service) postageGetStampBucketsHandler(w http.ResponseWriter, r *http.R
} }
id, err := hex.DecodeString(idStr) id, err := hex.DecodeString(idStr)
if err != nil { if err != nil {
s.logger.Error("get stamp issuer: invalid batchID: %v", err) s.logger.Debugf("get stamp issuer: invalid batchID: %v", err)
s.logger.Error("get stamp issuer: invalid batchID") s.logger.Error("get stamp issuer: invalid batchID")
jsonhttp.BadRequest(w, "invalid batchID") jsonhttp.BadRequest(w, "invalid batchID")
return return
...@@ -163,7 +172,7 @@ func (s *Service) postageGetStampBucketsHandler(w http.ResponseWriter, r *http.R ...@@ -163,7 +172,7 @@ func (s *Service) postageGetStampBucketsHandler(w http.ResponseWriter, r *http.R
issuer, err := s.post.GetStampIssuer(id) issuer, err := s.post.GetStampIssuer(id)
if err != nil { if err != nil {
s.logger.Error("get stamp issuer: get issuer: %v", err) s.logger.Debugf("get stamp issuer: get issuer: %v", err)
s.logger.Error("get stamp issuer: get issuer") s.logger.Error("get stamp issuer: get issuer")
jsonhttp.BadRequest(w, "cannot get batch") jsonhttp.BadRequest(w, "cannot get batch")
return return
...@@ -193,7 +202,7 @@ func (s *Service) postageGetStampHandler(w http.ResponseWriter, r *http.Request) ...@@ -193,7 +202,7 @@ func (s *Service) postageGetStampHandler(w http.ResponseWriter, r *http.Request)
} }
id, err := hex.DecodeString(idStr) id, err := hex.DecodeString(idStr)
if err != nil { if err != nil {
s.logger.Errorf("get stamp issuer: invalid batchID: %v", err) s.logger.Debugf("get stamp issuer: invalid batchID: %v", err)
s.logger.Error("get stamp issuer: invalid batchID") s.logger.Error("get stamp issuer: invalid batchID")
jsonhttp.BadRequest(w, "invalid batchID") jsonhttp.BadRequest(w, "invalid batchID")
return return
...@@ -201,18 +210,26 @@ func (s *Service) postageGetStampHandler(w http.ResponseWriter, r *http.Request) ...@@ -201,18 +210,26 @@ func (s *Service) postageGetStampHandler(w http.ResponseWriter, r *http.Request)
issuer, err := s.post.GetStampIssuer(id) issuer, err := s.post.GetStampIssuer(id)
if err != nil { if err != nil {
s.logger.Errorf("get stamp issuer: get issuer: %v", err) s.logger.Debugf("get stamp issuer: get issuer: %v", err)
s.logger.Error("get stamp issuer: get issuer") s.logger.Error("get stamp issuer: get issuer")
jsonhttp.BadRequest(w, "cannot get issuer") jsonhttp.BadRequest(w, "cannot get issuer")
return return
} }
exists, err := s.post.BatchExists(id) exists, err := s.batchStore.Exists(id)
if err != nil { if err != nil {
s.logger.Errorf("get stamp issuer: check batch: %v", err) s.logger.Debugf("get stamp issuer: check batch: %v", err)
s.logger.Error("get stamp issuer: check batch") s.logger.Error("get stamp issuer: check batch")
jsonhttp.InternalServerError(w, "unable to check batch") jsonhttp.InternalServerError(w, "unable to check batch")
return return
} }
batchTTL, err := s.estimateBatchTTL(id)
if err != nil {
s.logger.Debugf("get stamp issuer: estimate batch expiration: %v", err)
s.logger.Error("get stamp issuer: estimate batch expiration")
jsonhttp.InternalServerError(w, "unable to estimate batch expiration")
return
}
resp := postageStampResponse{ resp := postageStampResponse{
BatchID: id, BatchID: id,
Utilization: issuer.Utilization(), Utilization: issuer.Utilization(),
...@@ -224,6 +241,7 @@ func (s *Service) postageGetStampHandler(w http.ResponseWriter, r *http.Request) ...@@ -224,6 +241,7 @@ func (s *Service) postageGetStampHandler(w http.ResponseWriter, r *http.Request)
BlockNumber: issuer.BlockNumber(), BlockNumber: issuer.BlockNumber(),
ImmutableFlag: issuer.ImmutableFlag(), ImmutableFlag: issuer.ImmutableFlag(),
Exists: exists, Exists: exists,
BatchTTL: batchTTL,
} }
jsonhttp.OK(w, &resp) jsonhttp.OK(w, &resp)
} }
...@@ -263,3 +281,29 @@ func (s *Service) chainStateHandler(w http.ResponseWriter, _ *http.Request) { ...@@ -263,3 +281,29 @@ func (s *Service) chainStateHandler(w http.ResponseWriter, _ *http.Request) {
CurrentPrice: bigint.Wrap(state.CurrentPrice), CurrentPrice: bigint.Wrap(state.CurrentPrice),
}) })
} }
// estimateBatchTTL estimates the time remaining until the batch expires.
// The -1 signals that the batch never expires.
func (s *Service) estimateBatchTTL(id []byte) (int64, error) {
state := s.batchStore.GetChainState()
batch, err := s.batchStore.Get(id)
if err != nil {
return 0, err
}
var (
normalizedBalance = batch.Value
cumulativePayout = state.TotalAmount
pricePerBlock = state.CurrentPrice
)
if len(pricePerBlock.Bits()) == 0 {
return -1, nil
}
ttl := new(big.Int).Sub(normalizedBalance, cumulativePayout)
ttl = ttl.Mul(ttl, s.blockTime)
ttl = ttl.Div(ttl, pricePerBlock)
return ttl.Int64(), nil
}
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
mockpost "github.com/ethersphere/bee/pkg/postage/mock" mockpost "github.com/ethersphere/bee/pkg/postage/mock"
"github.com/ethersphere/bee/pkg/postage/postagecontract" "github.com/ethersphere/bee/pkg/postage/postagecontract"
contractMock "github.com/ethersphere/bee/pkg/postage/postagecontract/mock" contractMock "github.com/ethersphere/bee/pkg/postage/postagecontract/mock"
postagetesting "github.com/ethersphere/bee/pkg/postage/testing"
"github.com/ethersphere/bee/pkg/sctx" "github.com/ethersphere/bee/pkg/sctx"
) )
...@@ -194,15 +195,19 @@ func TestPostageCreateStamp(t *testing.T) { ...@@ -194,15 +195,19 @@ func TestPostageCreateStamp(t *testing.T) {
} }
func TestPostageGetStamps(t *testing.T) { func TestPostageGetStamps(t *testing.T) {
si := postage.NewStampIssuer("", "", batchOk, big.NewInt(3), 11, 10, 1000, true) b := postagetesting.MustNewBatch()
b.Value = big.NewInt(20)
si := postage.NewStampIssuer("", "", b.ID, big.NewInt(3), 11, 10, 1000, true)
mp := mockpost.New(mockpost.WithIssuer(si)) mp := mockpost.New(mockpost.WithIssuer(si))
ts := newTestServer(t, testServerOptions{Post: mp}) cs := &postage.ChainState{Block: 10, TotalAmount: big.NewInt(5), CurrentPrice: big.NewInt(2)}
bs := mock.New(mock.WithChainState(cs), mock.WithBatch(b))
ts := newTestServer(t, testServerOptions{Post: mp, BatchStore: bs})
jsonhttptest.Request(t, ts.Client, http.MethodGet, "/stamps", http.StatusOK, jsonhttptest.Request(t, ts.Client, http.MethodGet, "/stamps", http.StatusOK,
jsonhttptest.WithExpectedJSONResponse(&debugapi.PostageStampsResponse{ jsonhttptest.WithExpectedJSONResponse(&debugapi.PostageStampsResponse{
Stamps: []debugapi.PostageStampResponse{ Stamps: []debugapi.PostageStampResponse{
{ {
BatchID: batchOk, BatchID: b.ID,
Utilization: si.Utilization(), Utilization: si.Utilization(),
Usable: true, Usable: true,
Label: si.Label(), Label: si.Label(),
...@@ -212,6 +217,7 @@ func TestPostageGetStamps(t *testing.T) { ...@@ -212,6 +217,7 @@ func TestPostageGetStamps(t *testing.T) {
BlockNumber: si.BlockNumber(), BlockNumber: si.BlockNumber(),
ImmutableFlag: si.ImmutableFlag(), ImmutableFlag: si.ImmutableFlag(),
Exists: true, Exists: true,
BatchTTL: 15, // ((value-totalAmount)/pricePerBlock)*blockTime=((20-5)/2)*2.
}, },
}, },
}), }),
...@@ -219,14 +225,18 @@ func TestPostageGetStamps(t *testing.T) { ...@@ -219,14 +225,18 @@ func TestPostageGetStamps(t *testing.T) {
} }
func TestPostageGetStamp(t *testing.T) { func TestPostageGetStamp(t *testing.T) {
si := postage.NewStampIssuer("", "", batchOk, big.NewInt(3), 11, 10, 1000, true) b := postagetesting.MustNewBatch()
b.Value = big.NewInt(20)
si := postage.NewStampIssuer("", "", b.ID, big.NewInt(3), 11, 10, 1000, true)
mp := mockpost.New(mockpost.WithIssuer(si)) mp := mockpost.New(mockpost.WithIssuer(si))
ts := newTestServer(t, testServerOptions{Post: mp}) cs := &postage.ChainState{Block: 10, TotalAmount: big.NewInt(5), CurrentPrice: big.NewInt(2)}
bs := mock.New(mock.WithChainState(cs), mock.WithBatch(b))
ts := newTestServer(t, testServerOptions{Post: mp, BatchStore: bs})
t.Run("ok", func(t *testing.T) { t.Run("ok", func(t *testing.T) {
jsonhttptest.Request(t, ts.Client, http.MethodGet, "/stamps/"+batchOkStr, http.StatusOK, jsonhttptest.Request(t, ts.Client, http.MethodGet, "/stamps/"+hex.EncodeToString(b.ID), http.StatusOK,
jsonhttptest.WithExpectedJSONResponse(&debugapi.PostageStampResponse{ jsonhttptest.WithExpectedJSONResponse(&debugapi.PostageStampResponse{
BatchID: batchOk, BatchID: b.ID,
Utilization: si.Utilization(), Utilization: si.Utilization(),
Usable: true, Usable: true,
Label: si.Label(), Label: si.Label(),
...@@ -236,6 +246,7 @@ func TestPostageGetStamp(t *testing.T) { ...@@ -236,6 +246,7 @@ func TestPostageGetStamp(t *testing.T) {
BlockNumber: si.BlockNumber(), BlockNumber: si.BlockNumber(),
ImmutableFlag: si.ImmutableFlag(), ImmutableFlag: si.ImmutableFlag(),
Exists: true, Exists: true,
BatchTTL: 15, // ((value-totalAmount)/pricePerBlock)*blockTime=((20-5)/2)*2.
}), }),
) )
}) })
......
...@@ -239,7 +239,7 @@ func NewBee(addr string, publicKey *ecdsa.PublicKey, signer crypto.Signer, netwo ...@@ -239,7 +239,7 @@ func NewBee(addr string, publicKey *ecdsa.PublicKey, signer crypto.Signer, netwo
return nil, fmt.Errorf("eth address: %w", err) return nil, fmt.Errorf("eth address: %w", err)
} }
// set up basic debug api endpoints for debugging and /health endpoint // set up basic debug api endpoints for debugging and /health endpoint
debugAPIService = debugapi.New(*publicKey, pssPrivateKey.PublicKey, overlayEthAddress, logger, tracer, o.CORSAllowedOrigins, transactionService) debugAPIService = debugapi.New(*publicKey, pssPrivateKey.PublicKey, overlayEthAddress, logger, tracer, o.CORSAllowedOrigins, big.NewInt(int64(o.BlockTime)), transactionService)
debugAPIListener, err := net.Listen("tcp", o.DebugAPIAddr) debugAPIListener, err := net.Listen("tcp", o.DebugAPIAddr)
if err != nil { if err != nil {
......
...@@ -69,11 +69,6 @@ func (m *mockPostage) IssuerUsable(_ *postage.StampIssuer) bool { ...@@ -69,11 +69,6 @@ func (m *mockPostage) IssuerUsable(_ *postage.StampIssuer) bool {
return true return true
} }
// BatchExists returns always true.
func (m *mockPostage) BatchExists(_ []byte) (bool, error) {
return true, nil
}
func (m *mockPostage) Handle(_ *postage.Batch) {} func (m *mockPostage) Handle(_ *postage.Batch) {}
func (m *mockPostage) Close() error { func (m *mockPostage) Close() error {
......
...@@ -34,7 +34,6 @@ type Service interface { ...@@ -34,7 +34,6 @@ type Service interface {
StampIssuers() []*StampIssuer StampIssuers() []*StampIssuer
GetStampIssuer([]byte) (*StampIssuer, error) GetStampIssuer([]byte) (*StampIssuer, error)
IssuerUsable(*StampIssuer) bool IssuerUsable(*StampIssuer) bool
BatchExists([]byte) (bool, error)
BatchCreationListener BatchCreationListener
io.Closer io.Closer
} }
...@@ -124,11 +123,6 @@ func (ps *service) IssuerUsable(st *StampIssuer) bool { ...@@ -124,11 +123,6 @@ func (ps *service) IssuerUsable(st *StampIssuer) bool {
return true return true
} }
// BatchExists returns true if the batch referenced by the given id exists.
func (ps *service) BatchExists(id []byte) (bool, error) {
return ps.postageStore.Exists(id)
}
// GetStampIssuer finds a stamp issuer by batch ID. // GetStampIssuer finds a stamp issuer by batch ID.
func (ps *service) GetStampIssuer(batchID []byte) (*StampIssuer, error) { func (ps *service) GetStampIssuer(batchID []byte) (*StampIssuer, error) {
ps.lock.Lock() ps.lock.Lock()
......
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