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 }