github.com/ethersphere/bee/v2@v2.2.0/pkg/transaction/transaction.go (about) 1 // Copyright 2020 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 transaction 6 7 import ( 8 "bytes" 9 "errors" 10 "fmt" 11 "io" 12 "math/big" 13 "strings" 14 "sync" 15 "time" 16 17 "github.com/ethereum/go-ethereum" 18 "github.com/ethereum/go-ethereum/accounts/abi" 19 "github.com/ethereum/go-ethereum/common" 20 "github.com/ethereum/go-ethereum/core/types" 21 "github.com/ethereum/go-ethereum/rpc" 22 "github.com/ethersphere/bee/v2/pkg/crypto" 23 "github.com/ethersphere/bee/v2/pkg/log" 24 "github.com/ethersphere/bee/v2/pkg/sctx" 25 "github.com/ethersphere/bee/v2/pkg/storage" 26 "golang.org/x/net/context" 27 ) 28 29 // loggerName is the tree path name of the logger for this package. 30 const loggerName = "transaction" 31 32 const ( 33 noncePrefix = "transaction_nonce_" 34 storedTransactionPrefix = "transaction_stored_" 35 pendingTransactionPrefix = "transaction_pending_" 36 ) 37 38 var ( 39 // ErrTransactionReverted denotes that the sent transaction has been 40 // reverted. 41 ErrTransactionReverted = errors.New("transaction reverted") 42 ErrUnknownTransaction = errors.New("unknown transaction") 43 ErrAlreadyImported = errors.New("already imported") 44 ) 45 46 const ( 47 DefaultTipBoostPercent = 20 48 DefaultGasLimit = 1_000_000 49 ) 50 51 // TxRequest describes a request for a transaction that can be executed. 52 type TxRequest struct { 53 To *common.Address // recipient of the transaction 54 Data []byte // transaction data 55 GasPrice *big.Int // gas price or nil if suggested gas price should be used 56 GasLimit uint64 // gas limit or 0 if it should be estimated 57 MinEstimatedGasLimit uint64 // minimum gas limit to use if the gas limit was estimated; it will not apply when this value is 0 or when GasLimit is not 0 58 GasFeeCap *big.Int // adds a cap to maximum fee user is willing to pay 59 Value *big.Int // amount of wei to send 60 Description string // optional description 61 } 62 63 type StoredTransaction struct { 64 To *common.Address // recipient of the transaction 65 Data []byte // transaction data 66 GasPrice *big.Int // used gas price 67 GasLimit uint64 // used gas limit 68 GasTipBoost int // adds a tip for the miner for prioritizing transaction 69 GasTipCap *big.Int // adds a cap to the tip 70 GasFeeCap *big.Int // adds a cap to maximum fee user is willing to pay 71 Value *big.Int // amount of wei to send 72 Nonce uint64 // used nonce 73 Created int64 // creation timestamp 74 Description string // description 75 } 76 77 // Service is the service to send transactions. It takes care of gas price, gas 78 // limit and nonce management. 79 type Service interface { 80 io.Closer 81 // Send creates a transaction based on the request (with gasprice increased by provided percentage) and sends it. 82 Send(ctx context.Context, request *TxRequest, tipCapBoostPercent int) (txHash common.Hash, err error) 83 // Call simulate a transaction based on the request. 84 Call(ctx context.Context, request *TxRequest) (result []byte, err error) 85 // WaitForReceipt waits until either the transaction with the given hash has been mined or the context is cancelled. 86 // This is only valid for transaction sent by this service. 87 WaitForReceipt(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error) 88 // WatchSentTransaction start watching the given transaction. 89 // This wraps the monitors watch function by loading the correct nonce from the store. 90 // This is only valid for transaction sent by this service. 91 WatchSentTransaction(txHash common.Hash) (<-chan types.Receipt, <-chan error, error) 92 // StoredTransaction retrieves the stored information for the transaction 93 StoredTransaction(txHash common.Hash) (*StoredTransaction, error) 94 // PendingTransactions retrieves the list of all pending transaction hashes 95 PendingTransactions() ([]common.Hash, error) 96 // ResendTransaction resends a previously sent transaction 97 // This operation can be useful if for some reason the transaction vanished from the eth networks pending pool 98 ResendTransaction(ctx context.Context, txHash common.Hash) error 99 // CancelTransaction cancels a previously sent transaction by double-spending its nonce with zero-transfer one 100 CancelTransaction(ctx context.Context, originalTxHash common.Hash) (common.Hash, error) 101 // TransactionFee retrieves the transaction fee 102 TransactionFee(ctx context.Context, txHash common.Hash) (*big.Int, error) 103 // UnwrapABIError tries to unwrap the ABI error if the given error is not nil. 104 // The original error is wrapped together with the ABI error if it exists. 105 UnwrapABIError(ctx context.Context, req *TxRequest, err error, abiErrors map[string]abi.Error) error 106 } 107 108 type transactionService struct { 109 wg sync.WaitGroup 110 lock sync.Mutex 111 ctx context.Context 112 cancel context.CancelFunc 113 114 logger log.Logger 115 backend Backend 116 signer crypto.Signer 117 sender common.Address 118 store storage.StateStorer 119 chainID *big.Int 120 monitor Monitor 121 } 122 123 // NewService creates a new transaction service. 124 func NewService(logger log.Logger, overlayEthAddress common.Address, backend Backend, signer crypto.Signer, store storage.StateStorer, chainID *big.Int, monitor Monitor) (Service, error) { 125 senderAddress, err := signer.EthereumAddress() 126 if err != nil { 127 return nil, err 128 } 129 130 ctx, cancel := context.WithCancel(context.Background()) 131 132 t := &transactionService{ 133 ctx: ctx, 134 cancel: cancel, 135 logger: logger.WithName(loggerName).WithValues("sender_address", overlayEthAddress).Register(), 136 backend: backend, 137 signer: signer, 138 sender: senderAddress, 139 store: store, 140 chainID: chainID, 141 monitor: monitor, 142 } 143 144 err = t.waitForAllPendingTx() 145 if err != nil { 146 return nil, err 147 } 148 149 return t, nil 150 } 151 152 func (t *transactionService) waitForAllPendingTx() error { 153 pendingTxs, err := t.PendingTransactions() 154 if err != nil { 155 return err 156 } 157 158 pendingTxs = t.filterPendingTransactions(t.ctx, pendingTxs) 159 160 for _, txHash := range pendingTxs { 161 t.waitForPendingTx(txHash) 162 } 163 164 return nil 165 } 166 167 // Send creates and signs a transaction based on the request and sends it. 168 func (t *transactionService) Send(ctx context.Context, request *TxRequest, boostPercent int) (txHash common.Hash, err error) { 169 loggerV1 := t.logger.V(1).Register() 170 171 t.lock.Lock() 172 defer t.lock.Unlock() 173 174 nonce, err := t.nextNonce(ctx) 175 if err != nil { 176 return common.Hash{}, err 177 } 178 179 tx, err := t.prepareTransaction(ctx, request, nonce, boostPercent) 180 if err != nil { 181 return common.Hash{}, err 182 } 183 184 signedTx, err := t.signer.SignTx(tx, t.chainID) 185 if err != nil { 186 return common.Hash{}, err 187 } 188 189 loggerV1.Debug("sending transaction", "tx", signedTx.Hash(), "nonce", nonce) 190 191 err = t.backend.SendTransaction(ctx, signedTx) 192 if err != nil { 193 return common.Hash{}, err 194 } 195 196 err = t.putNonce(nonce + 1) 197 if err != nil { 198 return common.Hash{}, err 199 } 200 201 txHash = signedTx.Hash() 202 203 err = t.store.Put(storedTransactionKey(txHash), StoredTransaction{ 204 To: signedTx.To(), 205 Data: signedTx.Data(), 206 GasPrice: signedTx.GasPrice(), 207 GasLimit: signedTx.Gas(), 208 GasTipBoost: boostPercent, 209 GasTipCap: signedTx.GasTipCap(), 210 GasFeeCap: signedTx.GasFeeCap(), 211 Value: signedTx.Value(), 212 Nonce: signedTx.Nonce(), 213 Created: time.Now().Unix(), 214 Description: request.Description, 215 }) 216 if err != nil { 217 return common.Hash{}, err 218 } 219 220 err = t.store.Put(pendingTransactionKey(txHash), struct{}{}) 221 if err != nil { 222 return common.Hash{}, err 223 } 224 225 t.waitForPendingTx(txHash) 226 227 return signedTx.Hash(), nil 228 } 229 230 func (t *transactionService) waitForPendingTx(txHash common.Hash) { 231 t.wg.Add(1) 232 go func() { 233 defer t.wg.Done() 234 switch _, err := t.WaitForReceipt(t.ctx, txHash); { 235 case err == nil: 236 t.logger.Info("pending transaction confirmed", "tx", txHash) 237 err = t.store.Delete(pendingTransactionKey(txHash)) 238 if err != nil { 239 t.logger.Error(err, "unregistering finished pending transaction failed", "tx", txHash) 240 } 241 default: 242 if errors.Is(err, ErrTransactionCancelled) { 243 t.logger.Warning("pending transaction cancelled", "tx", txHash) 244 } else { 245 t.logger.Error(err, "waiting for pending transaction failed", "tx", txHash) 246 } 247 } 248 }() 249 } 250 251 func (t *transactionService) Call(ctx context.Context, request *TxRequest) ([]byte, error) { 252 msg := ethereum.CallMsg{ 253 From: t.sender, 254 To: request.To, 255 Data: request.Data, 256 GasPrice: request.GasPrice, 257 Gas: request.GasLimit, 258 Value: request.Value, 259 } 260 data, err := t.backend.CallContract(ctx, msg, nil) 261 if err != nil { 262 return nil, err 263 } 264 265 return data, nil 266 } 267 268 func (t *transactionService) StoredTransaction(txHash common.Hash) (*StoredTransaction, error) { 269 var tx StoredTransaction 270 err := t.store.Get(storedTransactionKey(txHash), &tx) 271 if err != nil { 272 if errors.Is(err, storage.ErrNotFound) { 273 return nil, ErrUnknownTransaction 274 } 275 return nil, err 276 } 277 return &tx, nil 278 } 279 280 // prepareTransaction creates a signable transaction based on a request. 281 func (t *transactionService) prepareTransaction(ctx context.Context, request *TxRequest, nonce uint64, boostPercent int) (tx *types.Transaction, err error) { 282 var gasLimit uint64 283 if request.GasLimit == 0 { 284 gasLimit, err = t.backend.EstimateGas(ctx, ethereum.CallMsg{ 285 From: t.sender, 286 To: request.To, 287 Data: request.Data, 288 }) 289 if err != nil { 290 t.logger.Debug("estimate gas failed", "error", err) 291 gasLimit = request.MinEstimatedGasLimit 292 } 293 294 gasLimit += gasLimit / 4 // add 25% on top 295 if gasLimit < request.MinEstimatedGasLimit { 296 gasLimit = request.MinEstimatedGasLimit 297 } 298 } else { 299 gasLimit = request.GasLimit 300 } 301 302 /* 303 Transactions are EIP 1559 dynamic transactions where there are three fee related fields: 304 1. base fee is the price that will be burned as part of the transaction. 305 2. max fee is the max price we are willing to spend as gas price. 306 3. max priority fee is max price want to give to the miner to prioritize the transaction. 307 as an example: 308 if base fee is 15, max fee is 20, and max priority is 3, gas price will be 15 + 3 = 18 309 if base is 15, max fee is 20, and max priority fee is 10, 310 gas price will be 15 + 10 = 25, but since 25 > 20, gas price is 20. 311 notice that gas price does not exceed 20 as defined by max fee. 312 */ 313 314 gasFeeCap, gasTipCap, err := t.suggestedFeeAndTip(ctx, request.GasPrice, boostPercent) 315 if err != nil { 316 return nil, err 317 } 318 319 return types.NewTx(&types.DynamicFeeTx{ 320 Nonce: nonce, 321 ChainID: t.chainID, 322 To: request.To, 323 Value: request.Value, 324 Gas: gasLimit, 325 GasFeeCap: gasFeeCap, 326 GasTipCap: gasTipCap, 327 Data: request.Data, 328 }), nil 329 } 330 331 func (t *transactionService) suggestedFeeAndTip(ctx context.Context, gasPrice *big.Int, boostPercent int) (*big.Int, *big.Int, error) { 332 var err error 333 334 if gasPrice == nil { 335 gasPrice, err = t.backend.SuggestGasPrice(ctx) 336 if err != nil { 337 return nil, nil, err 338 } 339 gasPrice = new(big.Int).Div(new(big.Int).Mul(big.NewInt(int64(boostPercent)+100), gasPrice), big.NewInt(100)) 340 } 341 342 gasTipCap, err := t.backend.SuggestGasTipCap(ctx) 343 if err != nil { 344 return nil, nil, err 345 } 346 347 gasTipCap = new(big.Int).Div(new(big.Int).Mul(big.NewInt(int64(boostPercent)+100), gasTipCap), big.NewInt(100)) 348 gasFeeCap := new(big.Int).Add(gasTipCap, gasPrice) 349 350 t.logger.Debug("prepare transaction", "gas_price", gasPrice, "gas_max_fee", gasFeeCap, "gas_max_tip", gasTipCap) 351 352 return gasFeeCap, gasTipCap, nil 353 354 } 355 356 func (t *transactionService) nonceKey() string { 357 return fmt.Sprintf("%s%x", noncePrefix, t.sender) 358 } 359 360 func storedTransactionKey(txHash common.Hash) string { 361 return fmt.Sprintf("%s%x", storedTransactionPrefix, txHash) 362 } 363 364 func pendingTransactionKey(txHash common.Hash) string { 365 return fmt.Sprintf("%s%x", pendingTransactionPrefix, txHash) 366 } 367 368 func (t *transactionService) nextNonce(ctx context.Context) (uint64, error) { 369 onchainNonce, err := t.backend.PendingNonceAt(ctx, t.sender) 370 if err != nil { 371 return 0, err 372 } 373 374 var nonce uint64 375 err = t.store.Get(t.nonceKey(), &nonce) 376 if err != nil { 377 // If no nonce was found locally used whatever we get from the backend. 378 if errors.Is(err, storage.ErrNotFound) { 379 return onchainNonce, nil 380 } 381 return 0, err 382 } 383 384 // If the nonce onchain is larger than what we have there were external 385 // transactions and we need to update our nonce. 386 if onchainNonce > nonce { 387 return onchainNonce, nil 388 } 389 return nonce, nil 390 } 391 392 func (t *transactionService) putNonce(nonce uint64) error { 393 return t.store.Put(t.nonceKey(), nonce) 394 } 395 396 // WaitForReceipt waits until either the transaction with the given hash has 397 // been mined or the context is cancelled. 398 func (t *transactionService) WaitForReceipt(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error) { 399 receiptC, errC, err := t.WatchSentTransaction(txHash) 400 if err != nil { 401 return nil, err 402 } 403 select { 404 case receipt := <-receiptC: 405 return &receipt, nil 406 case err := <-errC: 407 return nil, err 408 // don't wait longer than the context that was passed in 409 case <-ctx.Done(): 410 return nil, ctx.Err() 411 } 412 } 413 414 func (t *transactionService) WatchSentTransaction(txHash common.Hash) (<-chan types.Receipt, <-chan error, error) { 415 t.lock.Lock() 416 defer t.lock.Unlock() 417 418 // loading the tx here guarantees it was in fact sent from this transaction service 419 // also it allows us to avoid having to load the transaction during the watch loop 420 storedTransaction, err := t.StoredTransaction(txHash) 421 if err != nil { 422 return nil, nil, err 423 } 424 425 return t.monitor.WatchTransaction(txHash, storedTransaction.Nonce) 426 } 427 428 func (t *transactionService) PendingTransactions() ([]common.Hash, error) { 429 var txHashes []common.Hash = make([]common.Hash, 0) 430 err := t.store.Iterate(pendingTransactionPrefix, func(key, value []byte) (stop bool, err error) { 431 txHash := common.HexToHash(strings.TrimPrefix(string(key), pendingTransactionPrefix)) 432 txHashes = append(txHashes, txHash) 433 return false, nil 434 }) 435 if err != nil { 436 return nil, err 437 } 438 return txHashes, nil 439 } 440 441 // filterPendingTransactions will filter supplied transaction hashes removing those that are not pending anymore. 442 // Removed transactions will be also removed from store. 443 func (t *transactionService) filterPendingTransactions(ctx context.Context, txHashes []common.Hash) []common.Hash { 444 result := make([]common.Hash, 0, len(txHashes)) 445 446 for _, txHash := range txHashes { 447 _, isPending, err := t.backend.TransactionByHash(ctx, txHash) 448 449 // When error occurres consider transaction as pending (so this transaction won't be filtered out), 450 // unless it was not found 451 if err != nil { 452 if errors.Is(err, ethereum.NotFound) { 453 t.logger.Error(err, "pending transactions not found", "tx", txHash) 454 455 isPending = false 456 } else { 457 isPending = true 458 } 459 } 460 461 if isPending { 462 result = append(result, txHash) 463 } else { 464 err := t.store.Delete(pendingTransactionKey(txHash)) 465 if err != nil { 466 t.logger.Error(err, "error while unregistering transaction as pending", "tx", txHash) 467 } 468 } 469 } 470 471 return result 472 } 473 474 func (t *transactionService) ResendTransaction(ctx context.Context, txHash common.Hash) error { 475 storedTransaction, err := t.StoredTransaction(txHash) 476 if err != nil { 477 return err 478 } 479 480 gasFeeCap, gasTipCap, err := t.suggestedFeeAndTip(ctx, sctx.GetGasPrice(ctx), storedTransaction.GasTipBoost) 481 if err != nil { 482 return err 483 } 484 485 tx := types.NewTx(&types.DynamicFeeTx{ 486 Nonce: storedTransaction.Nonce, 487 ChainID: t.chainID, 488 To: storedTransaction.To, 489 Value: storedTransaction.Value, 490 Gas: storedTransaction.GasLimit, 491 GasTipCap: gasTipCap, 492 GasFeeCap: gasFeeCap, 493 Data: storedTransaction.Data, 494 }) 495 496 signedTx, err := t.signer.SignTx(tx, t.chainID) 497 if err != nil { 498 return err 499 } 500 501 if signedTx.Hash() != txHash { 502 return errors.New("transaction hash changed") 503 } 504 505 err = t.backend.SendTransaction(t.ctx, signedTx) 506 if err != nil { 507 if strings.Contains(err.Error(), "already imported") { 508 return ErrAlreadyImported 509 } 510 } 511 return nil 512 } 513 514 func (t *transactionService) CancelTransaction(ctx context.Context, originalTxHash common.Hash) (common.Hash, error) { 515 storedTransaction, err := t.StoredTransaction(originalTxHash) 516 if err != nil { 517 return common.Hash{}, err 518 } 519 520 gasFeeCap, gasTipCap, err := t.suggestedFeeAndTip(ctx, sctx.GetGasPrice(ctx), 0) 521 if err != nil { 522 return common.Hash{}, err 523 } 524 525 if gasFeeCap.Cmp(storedTransaction.GasFeeCap) <= 0 { 526 gasFeeCap = storedTransaction.GasFeeCap 527 } 528 529 if gasTipCap.Cmp(storedTransaction.GasTipCap) <= 0 { 530 gasTipCap = storedTransaction.GasTipCap 531 } 532 533 gasTipCap = new(big.Int).Div(new(big.Int).Mul(big.NewInt(int64(10)+100), gasTipCap), big.NewInt(100)) 534 535 gasFeeCap.Add(gasFeeCap, gasTipCap) 536 537 signedTx, err := t.signer.SignTx(types.NewTx(&types.DynamicFeeTx{ 538 Nonce: storedTransaction.Nonce, 539 ChainID: t.chainID, 540 To: &t.sender, 541 Value: big.NewInt(0), 542 Gas: 21000, 543 GasTipCap: gasTipCap, 544 GasFeeCap: gasFeeCap, 545 Data: []byte{}, 546 }), t.chainID) 547 if err != nil { 548 return common.Hash{}, err 549 } 550 551 err = t.backend.SendTransaction(t.ctx, signedTx) 552 if err != nil { 553 return common.Hash{}, err 554 } 555 556 txHash := signedTx.Hash() 557 err = t.store.Put(storedTransactionKey(txHash), StoredTransaction{ 558 To: signedTx.To(), 559 Data: signedTx.Data(), 560 GasPrice: signedTx.GasPrice(), 561 GasLimit: signedTx.Gas(), 562 GasFeeCap: signedTx.GasFeeCap(), 563 GasTipBoost: storedTransaction.GasTipBoost, 564 GasTipCap: signedTx.GasTipCap(), 565 Value: signedTx.Value(), 566 Nonce: signedTx.Nonce(), 567 Created: time.Now().Unix(), 568 Description: fmt.Sprintf("%s (cancellation)", storedTransaction.Description), 569 }) 570 if err != nil { 571 return common.Hash{}, err 572 } 573 574 err = t.store.Put(pendingTransactionKey(txHash), struct{}{}) 575 if err != nil { 576 return common.Hash{}, err 577 } 578 579 t.waitForPendingTx(txHash) 580 581 return txHash, err 582 } 583 584 func (t *transactionService) Close() error { 585 t.cancel() 586 t.wg.Wait() 587 return nil 588 } 589 590 func (t *transactionService) TransactionFee(ctx context.Context, txHash common.Hash) (*big.Int, error) { 591 trx, _, err := t.backend.TransactionByHash(ctx, txHash) 592 if err != nil { 593 return nil, err 594 } 595 return trx.Cost(), nil 596 } 597 598 func (t *transactionService) UnwrapABIError(ctx context.Context, req *TxRequest, err error, abiErrors map[string]abi.Error) error { 599 if err == nil { 600 return nil 601 } 602 603 _, cErr := t.Call(ctx, req) 604 if cErr == nil { 605 return err 606 } 607 err = fmt.Errorf("%w: %s", err, cErr) //nolint:errorlint 608 609 var derr rpc.DataError 610 if !errors.As(cErr, &derr) { 611 return err 612 } 613 614 res, ok := derr.ErrorData().(string) 615 if !ok { 616 return err 617 } 618 buf := common.FromHex(res) 619 620 if reason, uErr := abi.UnpackRevert(buf); uErr == nil { 621 return fmt.Errorf("%w: %s", err, reason) 622 } 623 624 for _, abiError := range abiErrors { 625 if !bytes.Equal(buf[:4], abiError.ID[:4]) { 626 continue 627 } 628 629 data, uErr := abiError.Unpack(buf) 630 if uErr != nil { 631 continue 632 } 633 634 values, ok := data.([]interface{}) 635 if !ok { 636 values = make([]interface{}, len(abiError.Inputs)) 637 for i := range values { 638 values[i] = "?" 639 } 640 } 641 642 params := make([]string, len(abiError.Inputs)) 643 for i, input := range abiError.Inputs { 644 if input.Name == "" { 645 input.Name = fmt.Sprintf("arg%d", i) 646 } 647 params[i] = fmt.Sprintf("%s=%v", input.Name, values[i]) 648 649 } 650 651 return fmt.Errorf("%w: %s(%s)", err, abiError.Name, strings.Join(params, ",")) 652 } 653 654 return err 655 }