Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/ethereum/go-ethereum v1.16.9
github.com/ethersphere/batch-archive v0.0.7
github.com/ethersphere/go-price-oracle-abi v0.6.9
github.com/ethersphere/go-storage-incentives-abi v0.9.4
github.com/ethersphere/go-storage-incentives-abi v0.9.3-rc4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is intentional and will be updated before the merge?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only for testing on lighttestnet, will be removed before merge

github.com/ethersphere/go-sw3-abi v0.6.9
github.com/ethersphere/langos v1.0.0
github.com/go-playground/validator/v10 v10.19.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,8 @@ github.com/ethersphere/batch-archive v0.0.7 h1:vb616eZIU5znxYiUSIBrPBD3/T4scEBNb
github.com/ethersphere/batch-archive v0.0.7/go.mod h1:41BPb192NoK9CYjNB8BAE1J2MtiI/5aq0Wtas5O7A7Q=
github.com/ethersphere/go-price-oracle-abi v0.6.9 h1:bseen6he3PZv5GHOm+KD6s4awaFmVSD9LFx+HpB6rCU=
github.com/ethersphere/go-price-oracle-abi v0.6.9/go.mod h1:sI/Qj4/zJ23/b1enzwMMv0/hLTpPNVNacEwCWjo6yBk=
github.com/ethersphere/go-storage-incentives-abi v0.9.4 h1:mSIWXQXg5OQmH10QvXMV5w0vbSibFMaRlBL37gPLTM0=
github.com/ethersphere/go-storage-incentives-abi v0.9.4/go.mod h1:SXvJVtM4sEsaSKD0jc1ClpDLw8ErPoROZDme4Wrc/Nc=
github.com/ethersphere/go-storage-incentives-abi v0.9.3-rc4 h1:YK9FpiQz29ctU5V46CuwMt+4X5Xn8FTBwy6E2v/ix8s=
github.com/ethersphere/go-storage-incentives-abi v0.9.3-rc4/go.mod h1:SXvJVtM4sEsaSKD0jc1ClpDLw8ErPoROZDme4Wrc/Nc=
github.com/ethersphere/go-sw3-abi v0.6.9 h1:TnWLnYkWE5UvC17mQBdUmdkzhPhO8GcqvWy4wvd1QJQ=
github.com/ethersphere/go-sw3-abi v0.6.9/go.mod h1:BmpsvJ8idQZdYEtWnvxA8POYQ8Rl/NhyCdF0zLMOOJU=
github.com/ethersphere/langos v1.0.0 h1:NBtNKzXTTRSue95uOlzPN4py7Aofs0xWPzyj4AI1Vcc=
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@ func (m *mockContract) IsWinner(context.Context) (bool, error) {
return false, nil
}

func (m *mockContract) Claim(context.Context, redistribution.ChunkInclusionProofs) (common.Hash, error) {
func (m *mockContract) Claim(context.Context, redistribution.ChunkInclusionProofs, *redistribution.ClaimOpts) (common.Hash, error) {
m.mtx.Lock()
defer m.mtx.Unlock()
m.callsList = append(m.callsList, claimCall)
Expand Down
14 changes: 14 additions & 0 deletions pkg/postage/postagecontract/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type Interface interface {

type PostageBatchExpirer interface {
ExpireBatches(ctx context.Context) error
ExpectedReward(ctx context.Context) (*big.Int, error)
}

type postageContract struct {
Expand Down Expand Up @@ -339,6 +340,15 @@ func (c *postageContract) getProperty(ctx context.Context, propertyName string,
return nil
}

// ExpectedReward returns the current redistribution pot (totalPot) from the postage stamp contract.
func (c *postageContract) ExpectedReward(ctx context.Context) (*big.Int, error) {
pot := new(big.Int)
if err := c.getProperty(ctx, "totalPot", pot); err != nil {
return nil, fmt.Errorf("totalPot: %w", err)
}
return pot, nil
}

func (c *postageContract) getMinInitialBalance(ctx context.Context) (uint64, error) {
var lastPrice uint64
err := c.getProperty(ctx, "lastPrice", &lastPrice)
Expand Down Expand Up @@ -569,6 +579,10 @@ func (m *noOpPostageContract) ExpireBatches(context.Context) error {
return ErrChainDisabled
}

func (m *noOpPostageContract) ExpectedReward(context.Context) (*big.Int, error) {
return nil, ErrChainDisabled
}

func LookupERC20Address(ctx context.Context, transactionService transaction.Service, postageStampContractAddress common.Address, postageStampContractABI abi.ABI, chainEnabled bool) (common.Address, error) {
if !chainEnabled {
return common.Address{}, nil
Expand Down
12 changes: 6 additions & 6 deletions pkg/postage/postagecontract/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func TestCreateBatch(t *testing.T) {
postageStampContractABI,
bzzTokenAddress,
transactionMock.New(
transactionMock.WithSendWithRetryFunc(func(ctx context.Context, request *transaction.TxRequest) (common.Hash, *types.Receipt, error) {
transactionMock.WithSendWithRetryFunc(func(ctx context.Context, request *transaction.TxRequest, _ ...transaction.RetryOption) (common.Hash, *types.Receipt, error) {
switch *request.To {
case bzzTokenAddress:
return txHashApprove, &types.Receipt{Status: 1}, nil
Expand Down Expand Up @@ -308,7 +308,7 @@ func TestTopUpBatch(t *testing.T) {
postageStampContractABI,
bzzTokenAddress,
transactionMock.New(
transactionMock.WithSendWithRetryFunc(func(ctx context.Context, request *transaction.TxRequest) (common.Hash, *types.Receipt, error) {
transactionMock.WithSendWithRetryFunc(func(ctx context.Context, request *transaction.TxRequest, _ ...transaction.RetryOption) (common.Hash, *types.Receipt, error) {
switch *request.To {
case bzzTokenAddress:
return txHashApprove, &types.Receipt{Status: 1}, nil
Expand Down Expand Up @@ -468,7 +468,7 @@ func TestDiluteBatch(t *testing.T) {
postageStampContractABI,
bzzTokenAddress,
transactionMock.New(
transactionMock.WithSendWithRetryFunc(func(ctx context.Context, request *transaction.TxRequest) (common.Hash, *types.Receipt, error) {
transactionMock.WithSendWithRetryFunc(func(ctx context.Context, request *transaction.TxRequest, _ ...transaction.RetryOption) (common.Hash, *types.Receipt, error) {
if *request.To == postageStampAddress {
if bytes.Equal(expectedCallDataForExpireLimitedBatches[:32], request.Data[:32]) {
return txHashApprove, &types.Receipt{Status: 1}, nil
Expand Down Expand Up @@ -630,7 +630,7 @@ func TestBatchExpirer(t *testing.T) {
}
}
return nil, errors.New("unexpected call")
}), transactionMock.WithSendWithRetryFunc(func(ctx context.Context, request *transaction.TxRequest) (common.Hash, *types.Receipt, error) {
}), transactionMock.WithSendWithRetryFunc(func(ctx context.Context, request *transaction.TxRequest, _ ...transaction.RetryOption) (common.Hash, *types.Receipt, error) {
return common.Hash{}, &types.Receipt{Status: 1}, nil
}),
),
Expand Down Expand Up @@ -768,7 +768,7 @@ func TestBatchExpirer(t *testing.T) {
}
}
return nil, errors.New("unexpected call")
}), transactionMock.WithSendWithRetryFunc(func(ctx context.Context, request *transaction.TxRequest) (common.Hash, *types.Receipt, error) {
}), transactionMock.WithSendWithRetryFunc(func(ctx context.Context, request *transaction.TxRequest, _ ...transaction.RetryOption) (common.Hash, *types.Receipt, error) {
if *request.To == postageContractAddress {
if bytes.Equal(expectedCallDataForExpireLimitedBatches[:32], request.Data[:32]) {
return common.Hash{}, nil, fmt.Errorf("some error")
Expand Down Expand Up @@ -891,7 +891,7 @@ func TestBatchExpirer(t *testing.T) {
}
}
return nil, errors.New("unexpected call")
}), transactionMock.WithSendWithRetryFunc(func(ctx context.Context, request *transaction.TxRequest) (common.Hash, *types.Receipt, error) {
}), transactionMock.WithSendWithRetryFunc(func(ctx context.Context, request *transaction.TxRequest, _ ...transaction.RetryOption) (common.Hash, *types.Receipt, error) {
return common.Hash{}, &types.Receipt{Status: 0}, transaction.ErrTransactionReverted
}),
),
Expand Down
14 changes: 14 additions & 0 deletions pkg/postage/postagecontract/mock/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type contractMock struct {
expireBatches func(ctx context.Context) error
paused func(ctx context.Context) (bool, error)
minimumValidityBlocks func(ctx context.Context) (uint64, error)
expectedReward func(ctx context.Context) (*big.Int, error)
}

func (c *contractMock) CreateBatch(ctx context.Context, initialBalance *big.Int, depth uint8, immutable bool, label string) (common.Hash, []byte, error) {
Expand All @@ -45,6 +46,13 @@ func (c *contractMock) MinimumValidityBlocks(ctx context.Context) (uint64, error
return c.minimumValidityBlocks(ctx)
}

func (c *contractMock) ExpectedReward(ctx context.Context) (*big.Int, error) {
if c.expectedReward != nil {
return c.expectedReward(ctx)
}
return big.NewInt(1_000_000), nil
}

// Option is an option passed to New
type Option func(*contractMock)

Expand Down Expand Up @@ -94,3 +102,9 @@ func WithMinimumValidityBlocksFunc(f func(ctx context.Context) (uint64, error))
mock.minimumValidityBlocks = f
}
}

func WithExpectedRewardFunc(f func(ctx context.Context) (*big.Int, error)) Option {
return func(m *contractMock) {
m.expectedReward = f
}
}
37 changes: 30 additions & 7 deletions pkg/storageincentives/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ const (

// average tx gas used by transactions issued from agent
avgTxGas = 250_000

// forceClaimBlocksBeforeEnd is how many blocks before round end claim may
// bypass max-tx-cost when economics justify it (see redistribution.ClaimOpts).
forceClaimBlocksBeforeEnd = 10
)

type ChainBackend interface {
Expand All @@ -59,6 +63,7 @@ type Agent struct {
metrics metrics
backend ChainBackend
blocksPerRound uint64
blockTime time.Duration
contract redistribution.Contract
batchExpirer postagecontract.PostageBatchExpirer
redistributionStatuser staking.RedistributionStatuser
Expand Down Expand Up @@ -102,6 +107,7 @@ func New(overlay swarm.Address,
store: store,
fullSyncedFunc: fullSyncedFunc,
blocksPerRound: blocksPerRound,
blockTime: blockTime,
quit: make(chan struct{}),
redistributionStatuser: redistributionStatuser,
health: health,
Expand All @@ -116,7 +122,7 @@ func New(overlay swarm.Address,
a.state = state

a.wg.Add(1)
go a.start(blockTime, a.blocksPerRound, blocksPerPhase)
go a.start(a.blockTime, a.blocksPerRound, blocksPerPhase)

return a, nil
}
Expand Down Expand Up @@ -311,7 +317,7 @@ func (a *Agent) handleReveal(ctx context.Context, round uint64) error {
a.metrics.ErrReveal.Inc()
return err
}
a.state.AddFee(ctx, txHash)
a.state.AddRoundFee(ctx, round, txHash)

a.state.SetHasRevealed(round)

Expand Down Expand Up @@ -353,7 +359,7 @@ func (a *Agent) handleClaim(ctx context.Context, round uint64) error {

errBalance := a.state.SetBalance(ctx)
if errBalance != nil {
a.logger.Info("could not set balance", "err", err)
a.logger.Info("could not set balance", "err", errBalance)
}

sampleData, exists := a.state.SampleData(round - 1)
Expand All @@ -371,8 +377,25 @@ func (a *Agent) handleClaim(ctx context.Context, round uint64) error {
return fmt.Errorf("making inclusion proofs: %w", err)
}

txHash, err := a.contract.Claim(ctx, proofs)
reward, err := a.batchExpirer.ExpectedReward(ctx)
if err != nil {
a.logger.Warning("could not estimate claim reward, override max_tx_cost option will be disabled", "error", err)
}

opts := &redistribution.ClaimOpts{
OverrideAfterBlock: (round+1)*a.blocksPerRound - forceClaimBlocksBeforeEnd,
CurrentBlockFn: func() uint64 { return a.state.currentBlock() },
ExpectedReward: reward,
RoundFees: a.state.RoundFees(round),
}

txHash, err := a.contract.Claim(ctx, proofs, opts)
if err != nil {
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
a.logger.Info("claim aborted by context", "round", round, "err", err)
a.metrics.SkippedExpensivePhase.Inc()
return nil
}
a.metrics.ErrClaim.Inc()
return fmt.Errorf("claiming win: %w", err)
}
Expand All @@ -382,11 +405,11 @@ func (a *Agent) handleClaim(ctx context.Context, round uint64) error {
if errBalance == nil {
errReward := a.state.CalculateWinnerReward(ctx)
if errReward != nil {
a.logger.Info("calculate winner reward", "err", err)
a.logger.Info("calculate winner reward", "err", errReward)
}
}

a.state.AddFee(ctx, txHash)
a.state.AddRoundFee(ctx, round, txHash)

return nil
}
Expand Down Expand Up @@ -539,7 +562,7 @@ func (a *Agent) commit(ctx context.Context, sample SampleData, round uint64) err
a.metrics.ErrCommit.Inc()
return err
}
a.state.AddFee(ctx, txHash)
a.state.AddRoundFee(ctx, round, txHash)

a.state.SetCommitKey(round, key)

Expand Down
2 changes: 1 addition & 1 deletion pkg/storageincentives/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ func (m *mockContract) IsWinner(context.Context) (bool, error) {
return false, nil
}

func (m *mockContract) Claim(context.Context, redistribution.ChunkInclusionProofs) (common.Hash, error) {
func (m *mockContract) Claim(context.Context, redistribution.ChunkInclusionProofs, *redistribution.ClaimOpts) (common.Hash, error) {
m.mtx.Lock()
defer m.mtx.Unlock()
m.callsList = append(m.callsList, claimCall)
Expand Down
9 changes: 9 additions & 0 deletions pkg/storageincentives/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ type metrics struct {
ErrClaim prometheus.Counter
ErrWinner prometheus.Counter
ErrCheckIsPlaying prometheus.Counter

// cost control metrics
SkippedExpensivePhase prometheus.Counter
}

func newMetrics() metrics {
Expand Down Expand Up @@ -137,6 +140,12 @@ func newMetrics() metrics {
Name: "is_playing_errors",
Help: "total neighborhood selected errors while processing",
}),
SkippedExpensivePhase: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: m.Namespace,
Subsystem: subsystem,
Name: "skipped_expensive_phase",
Help: "Count of phases skipped because estimated tx cost exceeded configured limit.",
}),
}
}

Expand Down
12 changes: 12 additions & 0 deletions pkg/storageincentives/redistribution/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2026 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package redistribution

import "math/big"

// CanOverrideClaim exposes the unexported canOverrideClaim method for tests.
func CanOverrideClaim(c Contract, opts *ClaimOpts, gasFeeCap *big.Int) bool {
return c.(*contract).canOverrideClaim(opts, gasFeeCap)
}
Loading
Loading