github.com/ethersphere/bee/v2@v2.2.0/pkg/storageincentives/redistribution/redistribution.go (about)

     1  // Copyright 2022 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package redistribution
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"math/big"
    11  
    12  	"github.com/ethereum/go-ethereum/accounts/abi"
    13  	"github.com/ethereum/go-ethereum/common"
    14  	"github.com/ethersphere/bee/v2/pkg/log"
    15  	"github.com/ethersphere/bee/v2/pkg/sctx"
    16  	"github.com/ethersphere/bee/v2/pkg/swarm"
    17  	"github.com/ethersphere/bee/v2/pkg/transaction"
    18  )
    19  
    20  const loggerName = "redistributionContract"
    21  
    22  type Contract interface {
    23  	ReserveSalt(context.Context) ([]byte, error)
    24  	IsPlaying(context.Context, uint8) (bool, error)
    25  	IsWinner(context.Context) (bool, error)
    26  	Claim(context.Context, ChunkInclusionProofs) (common.Hash, error)
    27  	Commit(context.Context, []byte, uint64) (common.Hash, error)
    28  	Reveal(context.Context, uint8, []byte, []byte) (common.Hash, error)
    29  }
    30  
    31  type contract struct {
    32  	overlay                   swarm.Address
    33  	owner                     common.Address
    34  	logger                    log.Logger
    35  	txService                 transaction.Service
    36  	incentivesContractAddress common.Address
    37  	incentivesContractABI     abi.ABI
    38  	gasLimit                  uint64
    39  }
    40  
    41  func New(
    42  	overlay swarm.Address,
    43  	owner common.Address,
    44  	logger log.Logger,
    45  	txService transaction.Service,
    46  	incentivesContractAddress common.Address,
    47  	incentivesContractABI abi.ABI,
    48  	setGasLimit bool,
    49  ) Contract {
    50  
    51  	var gasLimit uint64
    52  	if setGasLimit {
    53  		gasLimit = transaction.DefaultGasLimit
    54  	}
    55  
    56  	return &contract{
    57  		overlay:                   overlay,
    58  		owner:                     owner,
    59  		logger:                    logger.WithName(loggerName).Register(),
    60  		txService:                 txService,
    61  		incentivesContractAddress: incentivesContractAddress,
    62  		incentivesContractABI:     incentivesContractABI,
    63  		gasLimit:                  gasLimit,
    64  	}
    65  }
    66  
    67  // IsPlaying checks if the overlay is participating in the upcoming round.
    68  func (c *contract) IsPlaying(ctx context.Context, depth uint8) (bool, error) {
    69  	callData, err := c.incentivesContractABI.Pack("isParticipatingInUpcomingRound", c.owner, depth)
    70  	if err != nil {
    71  		return false, err
    72  	}
    73  
    74  	result, err := c.callTx(ctx, callData)
    75  	if err != nil {
    76  		return false, fmt.Errorf("IsPlaying: owner %v depth %d: %w", c.owner, depth, err)
    77  	}
    78  
    79  	results, err := c.incentivesContractABI.Unpack("isParticipatingInUpcomingRound", result)
    80  	if err != nil {
    81  		return false, fmt.Errorf("IsPlaying: results %v: %w", results, err)
    82  	}
    83  
    84  	return results[0].(bool), nil
    85  }
    86  
    87  // IsWinner checks if the overlay is winner by sending a transaction to blockchain.
    88  func (c *contract) IsWinner(ctx context.Context) (isWinner bool, err error) {
    89  	callData, err := c.incentivesContractABI.Pack("isWinner", common.BytesToHash(c.overlay.Bytes()))
    90  	if err != nil {
    91  		return false, err
    92  	}
    93  
    94  	result, err := c.callTx(ctx, callData)
    95  	if err != nil {
    96  		return false, fmt.Errorf("IsWinner: overlay %v : %w", common.BytesToHash(c.overlay.Bytes()), err)
    97  	}
    98  
    99  	results, err := c.incentivesContractABI.Unpack("isWinner", result)
   100  	if err != nil {
   101  		return false, fmt.Errorf("IsWinner: results %v : %w", results, err)
   102  	}
   103  	return results[0].(bool), nil
   104  }
   105  
   106  // Claim sends a transaction to blockchain if a win is claimed.
   107  func (c *contract) Claim(ctx context.Context, proofs ChunkInclusionProofs) (common.Hash, error) {
   108  	callData, err := c.incentivesContractABI.Pack("claim", proofs.A, proofs.B, proofs.C)
   109  	if err != nil {
   110  		return common.Hash{}, err
   111  	}
   112  	request := &transaction.TxRequest{
   113  		To:                   &c.incentivesContractAddress,
   114  		Data:                 callData,
   115  		GasPrice:             sctx.GetGasPrice(ctx),
   116  		GasLimit:             max(sctx.GetGasLimit(ctx), c.gasLimit),
   117  		MinEstimatedGasLimit: 500_000,
   118  		Value:                big.NewInt(0),
   119  		Description:          "claim win transaction",
   120  	}
   121  	txHash, err := c.sendAndWait(ctx, request, 50)
   122  	if err != nil {
   123  		return txHash, fmt.Errorf("claim: %w", err)
   124  	}
   125  
   126  	return txHash, nil
   127  }
   128  
   129  // Commit submits the obfusHash hash by sending a transaction to the blockchain.
   130  func (c *contract) Commit(ctx context.Context, obfusHash []byte, round uint64) (common.Hash, error) {
   131  	callData, err := c.incentivesContractABI.Pack("commit", common.BytesToHash(obfusHash), round)
   132  	if err != nil {
   133  		return common.Hash{}, err
   134  	}
   135  	request := &transaction.TxRequest{
   136  		To:                   &c.incentivesContractAddress,
   137  		Data:                 callData,
   138  		GasPrice:             sctx.GetGasPrice(ctx),
   139  		GasLimit:             max(sctx.GetGasLimit(ctx), c.gasLimit),
   140  		MinEstimatedGasLimit: 500_000,
   141  		Value:                big.NewInt(0),
   142  		Description:          "commit transaction",
   143  	}
   144  	txHash, err := c.sendAndWait(ctx, request, 50)
   145  	if err != nil {
   146  		return txHash, fmt.Errorf("commit: obfusHash %v: %w", common.BytesToHash(obfusHash), err)
   147  	}
   148  
   149  	return txHash, nil
   150  }
   151  
   152  // Reveal submits the storageDepth, reserveCommitmentHash and RandomNonce in a transaction to blockchain.
   153  func (c *contract) Reveal(ctx context.Context, storageDepth uint8, reserveCommitmentHash []byte, RandomNonce []byte) (common.Hash, error) {
   154  	callData, err := c.incentivesContractABI.Pack("reveal", storageDepth, common.BytesToHash(reserveCommitmentHash), common.BytesToHash(RandomNonce))
   155  	if err != nil {
   156  		return common.Hash{}, err
   157  	}
   158  	request := &transaction.TxRequest{
   159  		To:                   &c.incentivesContractAddress,
   160  		Data:                 callData,
   161  		GasPrice:             sctx.GetGasPrice(ctx),
   162  		GasLimit:             max(sctx.GetGasLimit(ctx), c.gasLimit),
   163  		MinEstimatedGasLimit: 500_000,
   164  		Value:                big.NewInt(0),
   165  		Description:          "reveal transaction",
   166  	}
   167  	txHash, err := c.sendAndWait(ctx, request, 50)
   168  	if err != nil {
   169  		return txHash, fmt.Errorf("reveal: storageDepth %d reserveCommitmentHash %v RandomNonce %v: %w", storageDepth, common.BytesToHash(reserveCommitmentHash), common.BytesToHash(RandomNonce), err)
   170  	}
   171  
   172  	return txHash, nil
   173  }
   174  
   175  // ReserveSalt provides the current round anchor by transacting on the blockchain.
   176  func (c *contract) ReserveSalt(ctx context.Context) ([]byte, error) {
   177  	callData, err := c.incentivesContractABI.Pack("currentRoundAnchor")
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	result, err := c.callTx(ctx, callData)
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	results, err := c.incentivesContractABI.Unpack("currentRoundAnchor", result)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  	salt := results[0].([32]byte)
   192  	return salt[:], nil
   193  }
   194  
   195  func (c *contract) sendAndWait(ctx context.Context, request *transaction.TxRequest, boostPercent int) (txHash common.Hash, err error) {
   196  	defer func() {
   197  		err = c.txService.UnwrapABIError(
   198  			ctx,
   199  			request,
   200  			err,
   201  			c.incentivesContractABI.Errors,
   202  		)
   203  	}()
   204  
   205  	txHash, err = c.txService.Send(ctx, request, boostPercent)
   206  	if err != nil {
   207  		return txHash, err
   208  	}
   209  	receipt, err := c.txService.WaitForReceipt(ctx, txHash)
   210  	if err != nil {
   211  		return txHash, err
   212  	}
   213  
   214  	if receipt.Status == 0 {
   215  		return txHash, transaction.ErrTransactionReverted
   216  	}
   217  	return txHash, nil
   218  }
   219  
   220  // callTx simulates a transaction based on tx request.
   221  func (c *contract) callTx(ctx context.Context, callData []byte) ([]byte, error) {
   222  	result, err := c.txService.Call(ctx, &transaction.TxRequest{
   223  		To:   &c.incentivesContractAddress,
   224  		Data: callData,
   225  	})
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  	return result, nil
   230  }