github.com/status-im/status-go@v1.1.0/transactions/transactor.go (about) 1 package transactions 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "math/big" 9 "time" 10 11 ethereum "github.com/ethereum/go-ethereum" 12 "github.com/ethereum/go-ethereum/common" 13 "github.com/ethereum/go-ethereum/common/hexutil" 14 gethtypes "github.com/ethereum/go-ethereum/core/types" 15 "github.com/ethereum/go-ethereum/log" 16 17 "github.com/status-im/status-go/account" 18 "github.com/status-im/status-go/eth-node/crypto" 19 "github.com/status-im/status-go/eth-node/types" 20 "github.com/status-im/status-go/params" 21 "github.com/status-im/status-go/rpc" 22 "github.com/status-im/status-go/services/wallet/bigint" 23 wallet_common "github.com/status-im/status-go/services/wallet/common" 24 ) 25 26 const ( 27 // sendTxTimeout defines how many seconds to wait before returning result in sentTransaction(). 28 sendTxTimeout = 300 * time.Second 29 30 defaultGas = 90000 31 32 ValidSignatureSize = 65 33 ) 34 35 // ErrInvalidSignatureSize is returned if a signature is not 65 bytes to avoid panic from go-ethereum 36 var ErrInvalidSignatureSize = errors.New("signature size must be 65") 37 38 type ErrBadNonce struct { 39 nonce uint64 40 expectedNonce uint64 41 } 42 43 func (e *ErrBadNonce) Error() string { 44 return fmt.Sprintf("bad nonce. expected %d, got %d", e.expectedNonce, e.nonce) 45 } 46 47 // Transactor is an interface that defines the methods for validating and sending transactions. 48 type TransactorIface interface { 49 NextNonce(rpcClient rpc.ClientInterface, chainID uint64, from types.Address) (uint64, error) 50 EstimateGas(network *params.Network, from common.Address, to common.Address, value *big.Int, input []byte) (uint64, error) 51 SendTransaction(sendArgs SendTxArgs, verifiedAccount *account.SelectedExtKey, lastUsedNonce int64) (hash types.Hash, nonce uint64, err error) 52 SendTransactionWithChainID(chainID uint64, sendArgs SendTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, nonce uint64, err error) 53 ValidateAndBuildTransaction(chainID uint64, sendArgs SendTxArgs, lastUsedNonce int64) (tx *gethtypes.Transaction, nonce uint64, err error) 54 AddSignatureToTransaction(chainID uint64, tx *gethtypes.Transaction, sig []byte) (*gethtypes.Transaction, error) 55 SendRawTransaction(chainID uint64, rawTx string) error 56 BuildTransactionWithSignature(chainID uint64, args SendTxArgs, sig []byte) (*gethtypes.Transaction, error) 57 SendTransactionWithSignature(from common.Address, symbol string, multiTransactionID wallet_common.MultiTransactionIDType, tx *gethtypes.Transaction) (hash types.Hash, err error) 58 StoreAndTrackPendingTx(from common.Address, symbol string, chainID uint64, multiTransactionID wallet_common.MultiTransactionIDType, tx *gethtypes.Transaction) error 59 } 60 61 // Transactor validates, signs transactions. 62 // It uses upstream to propagate transactions to the Ethereum network. 63 type Transactor struct { 64 rpcWrapper *rpcWrapper 65 pendingTracker *PendingTxTracker 66 sendTxTimeout time.Duration 67 rpcCallTimeout time.Duration 68 networkID uint64 69 log log.Logger 70 } 71 72 // NewTransactor returns a new Manager. 73 func NewTransactor() *Transactor { 74 return &Transactor{ 75 sendTxTimeout: sendTxTimeout, 76 log: log.New("package", "status-go/transactions.Manager"), 77 } 78 } 79 80 // SetPendingTracker sets a pending tracker. 81 func (t *Transactor) SetPendingTracker(tracker *PendingTxTracker) { 82 t.pendingTracker = tracker 83 } 84 85 // SetNetworkID selects a correct network. 86 func (t *Transactor) SetNetworkID(networkID uint64) { 87 t.networkID = networkID 88 } 89 90 func (t *Transactor) NetworkID() uint64 { 91 return t.networkID 92 } 93 94 // SetRPC sets RPC params, a client and a timeout 95 func (t *Transactor) SetRPC(rpcClient *rpc.Client, timeout time.Duration) { 96 t.rpcWrapper = newRPCWrapper(rpcClient, rpcClient.UpstreamChainID) 97 t.rpcCallTimeout = timeout 98 } 99 100 func (t *Transactor) NextNonce(rpcClient rpc.ClientInterface, chainID uint64, from types.Address) (uint64, error) { 101 wrapper := newRPCWrapper(rpcClient, chainID) 102 ctx := context.Background() 103 nonce, err := wrapper.PendingNonceAt(ctx, common.Address(from)) 104 if err != nil { 105 return 0, err 106 } 107 108 // We need to take into consideration all pending transactions in case of Optimism, cause the network returns always 109 // the nonce of last executed tx + 1 for the next nonce value. 110 if chainID == wallet_common.OptimismMainnet || 111 chainID == wallet_common.OptimismSepolia || 112 chainID == wallet_common.OptimismGoerli { 113 if t.pendingTracker != nil { 114 countOfPendingTXs, err := t.pendingTracker.CountPendingTxsFromNonce(wallet_common.ChainID(chainID), common.Address(from), nonce) 115 if err != nil { 116 return 0, err 117 } 118 return nonce + countOfPendingTXs, nil 119 } 120 } 121 122 return nonce, err 123 } 124 125 func (t *Transactor) EstimateGas(network *params.Network, from common.Address, to common.Address, value *big.Int, input []byte) (uint64, error) { 126 rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, network.ChainID) 127 128 ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout) 129 defer cancel() 130 131 msg := ethereum.CallMsg{ 132 From: from, 133 To: &to, 134 Value: value, 135 Data: input, 136 } 137 138 return rpcWrapper.EstimateGas(ctx, msg) 139 } 140 141 // SendTransaction is an implementation of eth_sendTransaction. It queues the tx to the sign queue. 142 func (t *Transactor) SendTransaction(sendArgs SendTxArgs, verifiedAccount *account.SelectedExtKey, lastUsedNonce int64) (hash types.Hash, nonce uint64, err error) { 143 hash, nonce, err = t.validateAndPropagate(t.rpcWrapper, verifiedAccount, sendArgs, lastUsedNonce) 144 return 145 } 146 147 func (t *Transactor) SendTransactionWithChainID(chainID uint64, sendArgs SendTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, nonce uint64, err error) { 148 wrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID) 149 hash, nonce, err = t.validateAndPropagate(wrapper, verifiedAccount, sendArgs, lastUsedNonce) 150 return 151 } 152 153 func (t *Transactor) ValidateAndBuildTransaction(chainID uint64, sendArgs SendTxArgs, lastUsedNonce int64) (tx *gethtypes.Transaction, nonce uint64, err error) { 154 wrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID) 155 tx, err = t.validateAndBuildTransaction(wrapper, sendArgs, lastUsedNonce) 156 return tx, tx.Nonce(), err 157 } 158 159 func (t *Transactor) AddSignatureToTransaction(chainID uint64, tx *gethtypes.Transaction, sig []byte) (*gethtypes.Transaction, error) { 160 if len(sig) != ValidSignatureSize { 161 return nil, ErrInvalidSignatureSize 162 } 163 164 rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID) 165 chID := big.NewInt(int64(rpcWrapper.chainID)) 166 167 signer := gethtypes.NewLondonSigner(chID) 168 txWithSignature, err := tx.WithSignature(signer, sig) 169 if err != nil { 170 return nil, err 171 } 172 173 return txWithSignature, nil 174 } 175 176 func (t *Transactor) SendRawTransaction(chainID uint64, rawTx string) error { 177 rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID) 178 179 ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout) 180 defer cancel() 181 182 return rpcWrapper.SendRawTransaction(ctx, rawTx) 183 } 184 185 func createPendingTransaction(from common.Address, symbol string, chainID uint64, multiTransactionID wallet_common.MultiTransactionIDType, tx *gethtypes.Transaction) (pTx *PendingTransaction) { 186 187 pTx = &PendingTransaction{ 188 Hash: tx.Hash(), 189 Timestamp: uint64(time.Now().Unix()), 190 Value: bigint.BigInt{Int: tx.Value()}, 191 From: from, 192 To: *tx.To(), 193 Nonce: tx.Nonce(), 194 Data: string(tx.Data()), 195 Type: WalletTransfer, 196 ChainID: wallet_common.ChainID(chainID), 197 MultiTransactionID: multiTransactionID, 198 Symbol: symbol, 199 AutoDelete: new(bool), 200 } 201 // Transaction downloader will delete pending transaction as soon as it is confirmed 202 *pTx.AutoDelete = false 203 return 204 } 205 206 func (t *Transactor) StoreAndTrackPendingTx(from common.Address, symbol string, chainID uint64, multiTransactionID wallet_common.MultiTransactionIDType, tx *gethtypes.Transaction) error { 207 if t.pendingTracker == nil { 208 return nil 209 } 210 211 pTx := createPendingTransaction(from, symbol, chainID, multiTransactionID, tx) 212 return t.pendingTracker.StoreAndTrackPendingTx(pTx) 213 } 214 215 func (t *Transactor) sendTransaction(rpcWrapper *rpcWrapper, from common.Address, symbol string, 216 multiTransactionID wallet_common.MultiTransactionIDType, tx *gethtypes.Transaction) (hash types.Hash, err error) { 217 ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout) 218 defer cancel() 219 220 if err := rpcWrapper.SendTransaction(ctx, tx); err != nil { 221 return hash, err 222 } 223 224 err = t.StoreAndTrackPendingTx(from, symbol, rpcWrapper.chainID, multiTransactionID, tx) 225 if err != nil { 226 return hash, err 227 } 228 229 return types.Hash(tx.Hash()), nil 230 } 231 232 func (t *Transactor) SendTransactionWithSignature(from common.Address, symbol string, 233 multiTransactionID wallet_common.MultiTransactionIDType, tx *gethtypes.Transaction) (hash types.Hash, err error) { 234 rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, tx.ChainId().Uint64()) 235 236 return t.sendTransaction(rpcWrapper, from, symbol, multiTransactionID, tx) 237 } 238 239 // BuildTransactionAndSendWithSignature receive a transaction and a signature, serialize them together 240 // It's different from eth_sendRawTransaction because it receives a signature and not a serialized transaction with signature. 241 // Since the transactions is already signed, we assume it was validated and used the right nonce. 242 func (t *Transactor) BuildTransactionWithSignature(chainID uint64, args SendTxArgs, sig []byte) (*gethtypes.Transaction, error) { 243 if !args.Valid() { 244 return nil, ErrInvalidSendTxArgs 245 } 246 247 if len(sig) != ValidSignatureSize { 248 return nil, ErrInvalidSignatureSize 249 } 250 251 tx := t.buildTransaction(args) 252 expectedNonce, err := t.NextNonce(t.rpcWrapper.RPCClient, chainID, args.From) 253 if err != nil { 254 return nil, err 255 } 256 257 if tx.Nonce() != expectedNonce { 258 return nil, &ErrBadNonce{tx.Nonce(), expectedNonce} 259 } 260 261 txWithSignature, err := t.AddSignatureToTransaction(chainID, tx, sig) 262 if err != nil { 263 return nil, err 264 } 265 266 return txWithSignature, nil 267 } 268 269 func (t *Transactor) HashTransaction(args SendTxArgs) (validatedArgs SendTxArgs, hash types.Hash, err error) { 270 if !args.Valid() { 271 return validatedArgs, hash, ErrInvalidSendTxArgs 272 } 273 274 validatedArgs = args 275 276 nonce, err := t.NextNonce(t.rpcWrapper.RPCClient, t.rpcWrapper.chainID, args.From) 277 if err != nil { 278 return validatedArgs, hash, err 279 } 280 281 gasPrice := (*big.Int)(args.GasPrice) 282 gasFeeCap := (*big.Int)(args.MaxFeePerGas) 283 gasTipCap := (*big.Int)(args.MaxPriorityFeePerGas) 284 if args.GasPrice == nil && !args.IsDynamicFeeTx() { 285 ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout) 286 defer cancel() 287 gasPrice, err = t.rpcWrapper.SuggestGasPrice(ctx) 288 if err != nil { 289 return validatedArgs, hash, err 290 } 291 } 292 293 chainID := big.NewInt(int64(t.networkID)) 294 value := (*big.Int)(args.Value) 295 296 var gas uint64 297 if args.Gas == nil { 298 ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout) 299 defer cancel() 300 301 var ( 302 gethTo common.Address 303 gethToPtr *common.Address 304 ) 305 if args.To != nil { 306 gethTo = common.Address(*args.To) 307 gethToPtr = &gethTo 308 } 309 if args.IsDynamicFeeTx() { 310 gas, err = t.rpcWrapper.EstimateGas(ctx, ethereum.CallMsg{ 311 From: common.Address(args.From), 312 To: gethToPtr, 313 GasFeeCap: gasFeeCap, 314 GasTipCap: gasTipCap, 315 Value: value, 316 Data: args.GetInput(), 317 }) 318 } else { 319 gas, err = t.rpcWrapper.EstimateGas(ctx, ethereum.CallMsg{ 320 From: common.Address(args.From), 321 To: gethToPtr, 322 GasPrice: gasPrice, 323 Value: value, 324 Data: args.GetInput(), 325 }) 326 } 327 if err != nil { 328 return validatedArgs, hash, err 329 } 330 } else { 331 gas = uint64(*args.Gas) 332 } 333 334 newNonce := hexutil.Uint64(nonce) 335 newGas := hexutil.Uint64(gas) 336 validatedArgs.Nonce = &newNonce 337 if !args.IsDynamicFeeTx() { 338 validatedArgs.GasPrice = (*hexutil.Big)(gasPrice) 339 } else { 340 validatedArgs.MaxPriorityFeePerGas = (*hexutil.Big)(gasTipCap) 341 validatedArgs.MaxPriorityFeePerGas = (*hexutil.Big)(gasFeeCap) 342 } 343 validatedArgs.Gas = &newGas 344 345 tx := t.buildTransaction(validatedArgs) 346 hash = types.Hash(gethtypes.NewLondonSigner(chainID).Hash(tx)) 347 348 return validatedArgs, hash, nil 349 } 350 351 // make sure that only account which created the tx can complete it 352 func (t *Transactor) validateAccount(args SendTxArgs, selectedAccount *account.SelectedExtKey) error { 353 if selectedAccount == nil { 354 return account.ErrNoAccountSelected 355 } 356 357 if !bytes.Equal(args.From.Bytes(), selectedAccount.Address.Bytes()) { 358 return ErrInvalidTxSender 359 } 360 361 return nil 362 } 363 364 func (t *Transactor) validateAndBuildTransaction(rpcWrapper *rpcWrapper, args SendTxArgs, lastUsedNonce int64) (tx *gethtypes.Transaction, err error) { 365 if !args.Valid() { 366 return tx, ErrInvalidSendTxArgs 367 } 368 369 var nonce uint64 370 if args.Nonce != nil { 371 nonce = uint64(*args.Nonce) 372 } else { 373 // some chains, like arbitrum doesn't count pending txs in the nonce, so we need to calculate it manually 374 if lastUsedNonce < 0 { 375 nonce, err = t.NextNonce(rpcWrapper.RPCClient, rpcWrapper.chainID, args.From) 376 if err != nil { 377 return tx, err 378 } 379 } else { 380 nonce = uint64(lastUsedNonce) + 1 381 } 382 } 383 384 ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout) 385 defer cancel() 386 387 gasPrice := (*big.Int)(args.GasPrice) 388 // GasPrice should be estimated only for LegacyTx 389 if !args.IsDynamicFeeTx() && args.GasPrice == nil { 390 gasPrice, err = rpcWrapper.SuggestGasPrice(ctx) 391 if err != nil { 392 return tx, err 393 } 394 } 395 396 value := (*big.Int)(args.Value) 397 var gas uint64 398 if args.Gas != nil { 399 gas = uint64(*args.Gas) 400 } else { 401 ctx, cancel = context.WithTimeout(context.Background(), t.rpcCallTimeout) 402 defer cancel() 403 404 var ( 405 gethTo common.Address 406 gethToPtr *common.Address 407 ) 408 if args.To != nil { 409 gethTo = common.Address(*args.To) 410 gethToPtr = &gethTo 411 } 412 if args.IsDynamicFeeTx() { 413 gasFeeCap := (*big.Int)(args.MaxFeePerGas) 414 gasTipCap := (*big.Int)(args.MaxPriorityFeePerGas) 415 gas, err = t.rpcWrapper.EstimateGas(ctx, ethereum.CallMsg{ 416 From: common.Address(args.From), 417 To: gethToPtr, 418 GasFeeCap: gasFeeCap, 419 GasTipCap: gasTipCap, 420 Value: value, 421 Data: args.GetInput(), 422 }) 423 } else { 424 gas, err = t.rpcWrapper.EstimateGas(ctx, ethereum.CallMsg{ 425 From: common.Address(args.From), 426 To: gethToPtr, 427 GasPrice: gasPrice, 428 Value: value, 429 Data: args.GetInput(), 430 }) 431 } 432 if err != nil { 433 return tx, err 434 } 435 } 436 437 tx = t.buildTransactionWithOverrides(nonce, value, gas, gasPrice, args) 438 return tx, nil 439 } 440 441 func (t *Transactor) validateAndPropagate(rpcWrapper *rpcWrapper, selectedAccount *account.SelectedExtKey, args SendTxArgs, lastUsedNonce int64) (hash types.Hash, nonce uint64, err error) { 442 if err = t.validateAccount(args, selectedAccount); err != nil { 443 return hash, nonce, err 444 } 445 446 tx, err := t.validateAndBuildTransaction(rpcWrapper, args, lastUsedNonce) 447 if err != nil { 448 return hash, nonce, err 449 } 450 451 chainID := big.NewInt(int64(rpcWrapper.chainID)) 452 signedTx, err := gethtypes.SignTx(tx, gethtypes.NewLondonSigner(chainID), selectedAccount.AccountKey.PrivateKey) 453 if err != nil { 454 return hash, nonce, err 455 } 456 457 hash, err = t.sendTransaction(rpcWrapper, common.Address(args.From), args.Symbol, args.MultiTransactionID, signedTx) 458 return hash, tx.Nonce(), err 459 } 460 461 func (t *Transactor) buildTransaction(args SendTxArgs) *gethtypes.Transaction { 462 var ( 463 nonce uint64 464 value *big.Int 465 gas uint64 466 gasPrice *big.Int 467 ) 468 if args.Nonce != nil { 469 nonce = uint64(*args.Nonce) 470 } 471 if args.Value != nil { 472 value = (*big.Int)(args.Value) 473 } 474 if args.Gas != nil { 475 gas = uint64(*args.Gas) 476 } 477 if args.GasPrice != nil { 478 gasPrice = (*big.Int)(args.GasPrice) 479 } 480 481 return t.buildTransactionWithOverrides(nonce, value, gas, gasPrice, args) 482 } 483 484 func (t *Transactor) buildTransactionWithOverrides(nonce uint64, value *big.Int, gas uint64, gasPrice *big.Int, args SendTxArgs) *gethtypes.Transaction { 485 var tx *gethtypes.Transaction 486 487 if args.To != nil { 488 to := common.Address(*args.To) 489 var txData gethtypes.TxData 490 491 if args.IsDynamicFeeTx() { 492 gasTipCap := (*big.Int)(args.MaxPriorityFeePerGas) 493 gasFeeCap := (*big.Int)(args.MaxFeePerGas) 494 495 txData = &gethtypes.DynamicFeeTx{ 496 Nonce: nonce, 497 Gas: gas, 498 GasTipCap: gasTipCap, 499 GasFeeCap: gasFeeCap, 500 To: &to, 501 Value: value, 502 Data: args.GetInput(), 503 } 504 } else { 505 txData = &gethtypes.LegacyTx{ 506 Nonce: nonce, 507 GasPrice: gasPrice, 508 Gas: gas, 509 To: &to, 510 Value: value, 511 Data: args.GetInput(), 512 } 513 } 514 tx = gethtypes.NewTx(txData) 515 t.logNewTx(args, gas, gasPrice, value) 516 } else { 517 if args.IsDynamicFeeTx() { 518 gasTipCap := (*big.Int)(args.MaxPriorityFeePerGas) 519 gasFeeCap := (*big.Int)(args.MaxFeePerGas) 520 521 txData := &gethtypes.DynamicFeeTx{ 522 Nonce: nonce, 523 Value: value, 524 Gas: gas, 525 GasTipCap: gasTipCap, 526 GasFeeCap: gasFeeCap, 527 Data: args.GetInput(), 528 } 529 tx = gethtypes.NewTx(txData) 530 } else { 531 tx = gethtypes.NewContractCreation(nonce, value, gas, gasPrice, args.GetInput()) 532 } 533 t.logNewContract(args, gas, gasPrice, value, nonce) 534 } 535 536 return tx 537 } 538 539 func (t *Transactor) logNewTx(args SendTxArgs, gas uint64, gasPrice *big.Int, value *big.Int) { 540 t.log.Info("New transaction", 541 "From", args.From, 542 "To", *args.To, 543 "Gas", gas, 544 "GasPrice", gasPrice, 545 "Value", value, 546 ) 547 } 548 549 func (t *Transactor) logNewContract(args SendTxArgs, gas uint64, gasPrice *big.Int, value *big.Int, nonce uint64) { 550 t.log.Info("New contract", 551 "From", args.From, 552 "Gas", gas, 553 "GasPrice", gasPrice, 554 "Value", value, 555 "Contract address", crypto.CreateAddress(args.From, nonce), 556 ) 557 }