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
import (
"crypto/ecdsa"
"math/big"
"net/http"
"sync"
......@@ -57,6 +58,7 @@ type Service struct {
corsAllowedOrigins []string
metricsRegistry *prometheus.Registry
lightNodes *lightnode.Container
blockTime *big.Int
// handler is changed in the Configure method
handler http.Handler
handlerMu sync.RWMutex
......@@ -66,7 +68,7 @@ type Service struct {
// 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
// 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.publicKey = publicKey
s.pssPublicKey = pssPublicKey
......@@ -74,6 +76,7 @@ func New(publicKey, pssPublicKey ecdsa.PublicKey, ethereumAddress common.Address
s.logger = logger
s.tracer = tracer
s.corsAllowedOrigins = corsAllowedOrigins
s.blockTime = blockTime
s.metricsRegistry = newMetricsRegistry()
s.transaction = transaction
......
......@@ -9,6 +9,7 @@ import (
"crypto/rand"
"encoding/hex"
"io/ioutil"
"math/big"
"net/http"
"net/http/httptest"
"net/url"
......@@ -86,7 +87,7 @@ func newTestServer(t *testing.T, o testServerOptions) *testServer {
swapserv := swapmock.New(o.SwapOpts...)
transaction := transactionmock.New(o.TransactionOpts...)
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)
ts := httptest.NewServer(s)
t.Cleanup(ts.Close)
......@@ -154,7 +155,7 @@ func TestServer_Configure(t *testing.T) {
swapserv := swapmock.New(o.SwapOpts...)
ln := lightnode.NewContainer(o.Overlay)
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)
t.Cleanup(ts.Close)
......
......@@ -102,6 +102,7 @@ type postageStampResponse struct {
BlockNumber uint64 `json:"blockNumber"`
ImmutableFlag bool `json:"immutableFlag"`
Exists bool `json:"exists"`
BatchTTL int64 `json:"batchTTL"`
}
type postageStampsResponse struct {
......@@ -123,13 +124,20 @@ type bucketData struct {
func (s *Service) postageGetStampsHandler(w http.ResponseWriter, _ *http.Request) {
resp := postageStampsResponse{}
for _, v := range s.post.StampIssuers() {
exists, err := s.post.BatchExists(v.ID())
exists, err := s.batchStore.Exists(v.ID())
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")
jsonhttp.InternalServerError(w, "unable to check batch")
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{
BatchID: v.ID(),
Utilization: v.Utilization(),
......@@ -141,6 +149,7 @@ func (s *Service) postageGetStampsHandler(w http.ResponseWriter, _ *http.Request
BlockNumber: v.BlockNumber(),
ImmutableFlag: v.ImmutableFlag(),
Exists: exists,
BatchTTL: batchTTL,
})
}
jsonhttp.OK(w, resp)
......@@ -155,7 +164,7 @@ func (s *Service) postageGetStampBucketsHandler(w http.ResponseWriter, r *http.R
}
id, err := hex.DecodeString(idStr)
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")
jsonhttp.BadRequest(w, "invalid batchID")
return
......@@ -163,7 +172,7 @@ func (s *Service) postageGetStampBucketsHandler(w http.ResponseWriter, r *http.R
issuer, err := s.post.GetStampIssuer(id)
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")
jsonhttp.BadRequest(w, "cannot get batch")
return
......@@ -193,7 +202,7 @@ func (s *Service) postageGetStampHandler(w http.ResponseWriter, r *http.Request)
}
id, err := hex.DecodeString(idStr)
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")
jsonhttp.BadRequest(w, "invalid batchID")
return
......@@ -201,18 +210,26 @@ func (s *Service) postageGetStampHandler(w http.ResponseWriter, r *http.Request)
issuer, err := s.post.GetStampIssuer(id)
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")
jsonhttp.BadRequest(w, "cannot get issuer")
return
}
exists, err := s.post.BatchExists(id)
exists, err := s.batchStore.Exists(id)
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")
jsonhttp.InternalServerError(w, "unable to check batch")
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{
BatchID: id,
Utilization: issuer.Utilization(),
......@@ -224,6 +241,7 @@ func (s *Service) postageGetStampHandler(w http.ResponseWriter, r *http.Request)
BlockNumber: issuer.BlockNumber(),
ImmutableFlag: issuer.ImmutableFlag(),
Exists: exists,
BatchTTL: batchTTL,
}
jsonhttp.OK(w, &resp)
}
......@@ -263,3 +281,29 @@ func (s *Service) chainStateHandler(w http.ResponseWriter, _ *http.Request) {
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 (
mockpost "github.com/ethersphere/bee/pkg/postage/mock"
"github.com/ethersphere/bee/pkg/postage/postagecontract"
contractMock "github.com/ethersphere/bee/pkg/postage/postagecontract/mock"
postagetesting "github.com/ethersphere/bee/pkg/postage/testing"
"github.com/ethersphere/bee/pkg/sctx"
)
......@@ -194,15 +195,19 @@ func TestPostageCreateStamp(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))
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.WithExpectedJSONResponse(&debugapi.PostageStampsResponse{
Stamps: []debugapi.PostageStampResponse{
{
BatchID: batchOk,
BatchID: b.ID,
Utilization: si.Utilization(),
Usable: true,
Label: si.Label(),
......@@ -212,6 +217,7 @@ func TestPostageGetStamps(t *testing.T) {
BlockNumber: si.BlockNumber(),
ImmutableFlag: si.ImmutableFlag(),
Exists: true,
BatchTTL: 15, // ((value-totalAmount)/pricePerBlock)*blockTime=((20-5)/2)*2.
},
},
}),
......@@ -219,14 +225,18 @@ func TestPostageGetStamps(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))
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) {
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{
BatchID: batchOk,
BatchID: b.ID,
Utilization: si.Utilization(),
Usable: true,
Label: si.Label(),
......@@ -236,6 +246,7 @@ func TestPostageGetStamp(t *testing.T) {
BlockNumber: si.BlockNumber(),
ImmutableFlag: si.ImmutableFlag(),
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
return nil, fmt.Errorf("eth address: %w", err)
}
// 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)
if err != nil {
......
......@@ -69,11 +69,6 @@ func (m *mockPostage) IssuerUsable(_ *postage.StampIssuer) bool {
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) Close() error {
......
......@@ -34,7 +34,6 @@ type Service interface {
StampIssuers() []*StampIssuer
GetStampIssuer([]byte) (*StampIssuer, error)
IssuerUsable(*StampIssuer) bool
BatchExists([]byte) (bool, error)
BatchCreationListener
io.Closer
}
......@@ -124,11 +123,6 @@ func (ps *service) IssuerUsable(st *StampIssuer) bool {
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.
func (ps *service) GetStampIssuer(batchID []byte) (*StampIssuer, error) {
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