diff --git a/openapi/SwarmCommon.yaml b/openapi/SwarmCommon.yaml index 303245501f9..47b4ced792d 100644 --- a/openapi/SwarmCommon.yaml +++ b/openapi/SwarmCommon.yaml @@ -63,6 +63,8 @@ components: $ref: "#/components/schemas/BigInt" currentPrice: $ref: "#/components/schemas/BigInt" + minimumValidityBlocks: + type: integer PeerAccountingData: type: object diff --git a/pkg/api/postage.go b/pkg/api/postage.go index 7cd74a23e87..4b2fd48c9d2 100644 --- a/pkg/api/postage.go +++ b/pkg/api/postage.go @@ -417,10 +417,11 @@ type reserveStateResponse struct { } type chainStateResponse struct { - ChainTip uint64 `json:"chainTip"` // ChainTip (block height). - Block uint64 `json:"block"` // The block number of the last postage event. - TotalAmount *bigint.BigInt `json:"totalAmount"` // Cumulative amount paid per stamp. - CurrentPrice *bigint.BigInt `json:"currentPrice"` // Bzz/chunk/block normalised price. + ChainTip uint64 `json:"chainTip"` // ChainTip (block height). + Block uint64 `json:"block"` // The block number of the last postage event. + TotalAmount *bigint.BigInt `json:"totalAmount"` // Cumulative amount paid per stamp. + CurrentPrice *bigint.BigInt `json:"currentPrice"` // Bzz/chunk/block normalised price. + MinimumValidityBlocks uint64 `json:"minimumValidityBlocks,omitempty"` // Minimum number of blocks a new postage batch must remain valid. } func (s *Service) reserveStateHandler(w http.ResponseWriter, _ *http.Request) { @@ -454,11 +455,19 @@ func (s *Service) chainStateHandler(w http.ResponseWriter, r *http.Request) { jsonhttp.InternalServerError(w, "block number unavailable") return } + minimumValidityBlocks, err := s.postageContract.MinimumValidityBlocks(r.Context()) + if err != nil && !errors.Is(err, postagecontract.ErrChainDisabled) { + logger.Debug("get minimum validity blocks failed", "error", err) + logger.Error(nil, "get minimum validity blocks failed") + jsonhttp.InternalServerError(w, "minimum validity blocks unavailable") + return + } jsonhttp.OK(w, chainStateResponse{ - ChainTip: chainTip, - Block: state.Block, - TotalAmount: bigint.Wrap(state.TotalAmount), - CurrentPrice: bigint.Wrap(state.CurrentPrice), + ChainTip: chainTip, + Block: state.Block, + TotalAmount: bigint.Wrap(state.TotalAmount), + CurrentPrice: bigint.Wrap(state.CurrentPrice), + MinimumValidityBlocks: minimumValidityBlocks, }) } diff --git a/pkg/api/postage_test.go b/pkg/api/postage_test.go index c83aceefe6a..a9aeb51fdc4 100644 --- a/pkg/api/postage_test.go +++ b/pkg/api/postage_test.go @@ -468,18 +468,25 @@ func TestChainState(t *testing.T) { TotalAmount: big.NewInt(50), CurrentPrice: big.NewInt(5), } + contract := contractMock.New( + contractMock.WithMinimumValidityBlocksFunc(func(ctx context.Context) (uint64, error) { + return 17280, nil + }), + ) ts, _, _, _ := newTestServer(t, testServerOptions{ BatchStore: mock.New(mock.WithChainState(cs)), BackendOpts: []backendmock.Option{backendmock.WithBlockNumberFunc(func(ctx context.Context) (uint64, error) { return 1, nil })}, + PostageContract: contract, }) jsonhttptest.Request(t, ts, http.MethodGet, "/chainstate", http.StatusOK, jsonhttptest.WithExpectedJSONResponse(&api.ChainStateResponse{ - ChainTip: 1, - Block: 123456, - TotalAmount: bigint.Wrap(big.NewInt(50)), - CurrentPrice: bigint.Wrap(big.NewInt(5)), + ChainTip: 1, + Block: 123456, + TotalAmount: bigint.Wrap(big.NewInt(50)), + CurrentPrice: bigint.Wrap(big.NewInt(5)), + MinimumValidityBlocks: 17280, }), ) }) diff --git a/pkg/postage/postagecontract/contract.go b/pkg/postage/postagecontract/contract.go index cae599db166..42317014b38 100644 --- a/pkg/postage/postagecontract/contract.go +++ b/pkg/postage/postagecontract/contract.go @@ -46,6 +46,7 @@ type Interface interface { TopUpBatch(ctx context.Context, batchID []byte, topupBalance *big.Int) (common.Hash, error) DiluteBatch(ctx context.Context, batchID []byte, newDepth uint8) (common.Hash, error) Paused(ctx context.Context) (bool, error) + MinimumValidityBlocks(ctx context.Context) (uint64, error) PostageBatchExpirer } @@ -508,6 +509,14 @@ func (c *postageContract) Paused(ctx context.Context) (bool, error) { return results[0].(bool), nil } +func (c *postageContract) MinimumValidityBlocks(ctx context.Context) (uint64, error) { + var minimumValidityBlocks uint64 + if err := c.getProperty(ctx, "minimumValidityBlocks", &minimumValidityBlocks); err != nil { + return 0, err + } + return minimumValidityBlocks, nil +} + type batchCreatedEvent struct { BatchId [32]byte TotalAmount *big.Int @@ -536,6 +545,10 @@ func (m *noOpPostageContract) Paused(context.Context) (bool, error) { return false, nil } +func (m *noOpPostageContract) MinimumValidityBlocks(context.Context) (uint64, error) { + return 0, ErrChainDisabled +} + func (m *noOpPostageContract) ExpireBatches(context.Context) error { return ErrChainDisabled } diff --git a/pkg/postage/postagecontract/mock/contract.go b/pkg/postage/postagecontract/mock/contract.go index c670cf62dce..c8d1879f9eb 100644 --- a/pkg/postage/postagecontract/mock/contract.go +++ b/pkg/postage/postagecontract/mock/contract.go @@ -13,11 +13,12 @@ import ( ) type contractMock struct { - createBatch func(ctx context.Context, initialBalance *big.Int, depth uint8, immutable bool, label string) (common.Hash, []byte, error) - topupBatch func(ctx context.Context, id []byte, amount *big.Int) (common.Hash, error) - diluteBatch func(ctx context.Context, id []byte, newDepth uint8) (common.Hash, error) - expireBatches func(ctx context.Context) error - paused func(ctx context.Context) (bool, error) + createBatch func(ctx context.Context, initialBalance *big.Int, depth uint8, immutable bool, label string) (common.Hash, []byte, error) + topupBatch func(ctx context.Context, id []byte, amount *big.Int) (common.Hash, error) + diluteBatch func(ctx context.Context, id []byte, newDepth uint8) (common.Hash, error) + expireBatches func(ctx context.Context) error + paused func(ctx context.Context) (bool, error) + minimumValidityBlocks func(ctx context.Context) (uint64, error) } func (c *contractMock) CreateBatch(ctx context.Context, initialBalance *big.Int, depth uint8, immutable bool, label string) (common.Hash, []byte, error) { @@ -40,6 +41,10 @@ func (s *contractMock) Paused(ctx context.Context) (bool, error) { return s.paused(ctx) } +func (c *contractMock) MinimumValidityBlocks(ctx context.Context) (uint64, error) { + return c.minimumValidityBlocks(ctx) +} + // Option is an option passed to New type Option func(*contractMock) @@ -83,3 +88,9 @@ func WithPaused(f func(ctx context.Context) (bool, error)) Option { mock.paused = f } } + +func WithMinimumValidityBlocksFunc(f func(ctx context.Context) (uint64, error)) Option { + return func(mock *contractMock) { + mock.minimumValidityBlocks = f + } +}