mirror of
https://github.com/status-im/status-go.git
synced 2025-01-09 14:16:21 +00:00
79b1c547d1
* chore_: unused `BuildTx` function removed from the processor interface and types that are implement it Since the `BuildTx` function is not used anywhere, it's removed from the code. * fix_: resolving nonce improvements When the app sends more than a single tx from the same account on the same chain, some chains do not return appropriate nonce (they do not consider pending txs), because of that we place more tx with the same nonce, where all but the first one fail. Changes in this PR keep track of nonces being used in the same sending/bridging flow, which means for the first tx from the multi txs the app asks the chain for the nonce, and every next nonce is resolved by incrementing the last used nonce by 1.
233 lines
7.3 KiB
Go
233 lines
7.3 KiB
Go
package transfer
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/status-im/status-go/account"
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
|
"github.com/status-im/status-go/eth-node/types"
|
|
wallet_common "github.com/status-im/status-go/services/wallet/common"
|
|
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
|
|
)
|
|
|
|
func rowsToMultiTransactions(rows *sql.Rows) ([]*MultiTransaction, error) {
|
|
var multiTransactions []*MultiTransaction
|
|
for rows.Next() {
|
|
multiTransaction := &MultiTransaction{}
|
|
var fromAmountDB, toAmountDB sql.NullString
|
|
var fromTxHash, toTxHash sql.RawBytes
|
|
err := rows.Scan(
|
|
&multiTransaction.ID,
|
|
&multiTransaction.FromNetworkID,
|
|
&fromTxHash,
|
|
&multiTransaction.FromAddress,
|
|
&multiTransaction.FromAsset,
|
|
&fromAmountDB,
|
|
&multiTransaction.ToNetworkID,
|
|
&toTxHash,
|
|
&multiTransaction.ToAddress,
|
|
&multiTransaction.ToAsset,
|
|
&toAmountDB,
|
|
&multiTransaction.Type,
|
|
&multiTransaction.CrossTxID,
|
|
&multiTransaction.Timestamp,
|
|
)
|
|
if len(fromTxHash) > 0 {
|
|
multiTransaction.FromTxHash = common.BytesToHash(fromTxHash)
|
|
}
|
|
if len(toTxHash) > 0 {
|
|
multiTransaction.ToTxHash = common.BytesToHash(toTxHash)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if fromAmountDB.Valid {
|
|
multiTransaction.FromAmount = new(hexutil.Big)
|
|
if _, ok := (*big.Int)(multiTransaction.FromAmount).SetString(fromAmountDB.String, 0); !ok {
|
|
return nil, errors.New("failed to convert fromAmountDB.String to big.Int: " + fromAmountDB.String)
|
|
}
|
|
}
|
|
|
|
if toAmountDB.Valid {
|
|
multiTransaction.ToAmount = new(hexutil.Big)
|
|
if _, ok := (*big.Int)(multiTransaction.ToAmount).SetString(toAmountDB.String, 0); !ok {
|
|
return nil, errors.New("failed to convert fromAmountDB.String to big.Int: " + toAmountDB.String)
|
|
}
|
|
}
|
|
|
|
multiTransactions = append(multiTransactions, multiTransaction)
|
|
}
|
|
|
|
return multiTransactions, nil
|
|
}
|
|
|
|
func addSignaturesToTransactions(transactions map[common.Hash]*TransactionDescription, signatures map[string]SignatureDetails) error {
|
|
if len(transactions) == 0 {
|
|
return errors.New("no transactions to proceed with")
|
|
}
|
|
if len(signatures) != len(transactions) {
|
|
return errors.New("not all transactions have been signed")
|
|
}
|
|
|
|
// check if all transactions have been signed
|
|
for hash, desc := range transactions {
|
|
sigDetails, ok := signatures[hash.String()]
|
|
if !ok {
|
|
return fmt.Errorf("missing signature for transaction %s", hash)
|
|
}
|
|
|
|
rBytes, _ := hex.DecodeString(sigDetails.R)
|
|
sBytes, _ := hex.DecodeString(sigDetails.S)
|
|
vByte := byte(0)
|
|
if sigDetails.V == "01" {
|
|
vByte = 1
|
|
}
|
|
|
|
desc.signature = make([]byte, crypto.SignatureLength)
|
|
copy(desc.signature[32-len(rBytes):32], rBytes)
|
|
copy(desc.signature[64-len(rBytes):64], sBytes)
|
|
desc.signature[64] = vByte
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func multiTransactionFromCommand(command *MultiTransactionCommand) *MultiTransaction {
|
|
toAmount := new(hexutil.Big)
|
|
if command.ToAmount != nil {
|
|
toAmount = command.ToAmount
|
|
}
|
|
multiTransaction := NewMultiTransaction(
|
|
/* Timestamp: */ uint64(time.Now().Unix()),
|
|
/* FromNetworkID: */ 0,
|
|
/* ToNetworkID: */ 0,
|
|
/* FromTxHash: */ common.Hash{},
|
|
/* ToTxHash: */ common.Hash{},
|
|
/* FromAddress: */ command.FromAddress,
|
|
/* ToAddress: */ command.ToAddress,
|
|
/* FromAsset: */ command.FromAsset,
|
|
/* ToAsset: */ command.ToAsset,
|
|
/* FromAmount: */ command.FromAmount,
|
|
/* ToAmount: */ toAmount,
|
|
/* Type: */ command.Type,
|
|
/* CrossTxID: */ "",
|
|
)
|
|
|
|
return multiTransaction
|
|
}
|
|
|
|
func updateDataFromMultiTx(data []*pathprocessor.MultipathProcessorTxArgs, multiTransaction *MultiTransaction) {
|
|
for _, tx := range data {
|
|
if tx.TransferTx != nil {
|
|
tx.TransferTx.MultiTransactionID = multiTransaction.ID
|
|
tx.TransferTx.Symbol = multiTransaction.FromAsset
|
|
}
|
|
if tx.HopTx != nil {
|
|
tx.HopTx.MultiTransactionID = multiTransaction.ID
|
|
tx.HopTx.Symbol = multiTransaction.FromAsset
|
|
}
|
|
if tx.CbridgeTx != nil {
|
|
tx.CbridgeTx.MultiTransactionID = multiTransaction.ID
|
|
tx.CbridgeTx.Symbol = multiTransaction.FromAsset
|
|
}
|
|
if tx.ERC721TransferTx != nil {
|
|
tx.ERC721TransferTx.MultiTransactionID = multiTransaction.ID
|
|
tx.ERC721TransferTx.Symbol = multiTransaction.FromAsset
|
|
}
|
|
if tx.ERC1155TransferTx != nil {
|
|
tx.ERC1155TransferTx.MultiTransactionID = multiTransaction.ID
|
|
tx.ERC1155TransferTx.Symbol = multiTransaction.FromAsset
|
|
}
|
|
if tx.SwapTx != nil {
|
|
tx.SwapTx.MultiTransactionID = multiTransaction.ID
|
|
tx.SwapTx.Symbol = multiTransaction.FromAsset
|
|
}
|
|
}
|
|
}
|
|
|
|
func sendTransactions(data []*pathprocessor.MultipathProcessorTxArgs, pathProcessors map[string]pathprocessor.PathProcessor, account *account.SelectedExtKey) (
|
|
map[uint64][]types.Hash, error) {
|
|
|
|
hashes := make(map[uint64][]types.Hash)
|
|
usedNonces := make(map[uint64]int64)
|
|
for _, tx := range data {
|
|
|
|
lastUsedNonce := int64(-1)
|
|
if nonce, ok := usedNonces[tx.ChainID]; ok {
|
|
lastUsedNonce = nonce
|
|
}
|
|
|
|
hash, usedNonce, err := pathProcessors[tx.Name].Send(tx, lastUsedNonce, account)
|
|
if err != nil {
|
|
return nil, err // TODO: One of transfers within transaction could have been sent. Need to notify user about it
|
|
}
|
|
|
|
hashes[tx.ChainID] = append(hashes[tx.ChainID], hash)
|
|
usedNonces[tx.ChainID] = int64(usedNonce)
|
|
}
|
|
return hashes, nil
|
|
}
|
|
|
|
func idFromTimestamp() wallet_common.MultiTransactionIDType {
|
|
return wallet_common.MultiTransactionIDType(time.Now().UnixMilli())
|
|
}
|
|
|
|
var multiTransactionIDGenerator func() wallet_common.MultiTransactionIDType = idFromTimestamp
|
|
|
|
func (tm *TransactionManager) removeMultiTransactionByAddress(address common.Address) error {
|
|
// We must not remove those transactions, where from_address and to_address are different and both are stored in accounts DB
|
|
// and one of them is equal to the address, as we want to keep the records for the other address
|
|
// That is why we don't use cascade delete here with references to transfers table, as we might have 2 records in multi_transactions
|
|
// for the same transaction, one for each address
|
|
|
|
details := NewMultiTxDetails()
|
|
details.FromAddress = address
|
|
mtxs, err := tm.storage.ReadMultiTransactions(details)
|
|
|
|
ids := make([]wallet_common.MultiTransactionIDType, 0)
|
|
for _, mtx := range mtxs {
|
|
// Remove self transactions as well, leave only those where we have the counterparty in accounts DB
|
|
if mtx.FromAddress != mtx.ToAddress {
|
|
// If both addresses are stored in accounts DB, we don't remove the record
|
|
var addressToCheck common.Address
|
|
if mtx.FromAddress == address {
|
|
addressToCheck = mtx.ToAddress
|
|
} else {
|
|
addressToCheck = mtx.FromAddress
|
|
}
|
|
counterpartyExists, err := tm.accountsDB.AddressExists(types.Address(addressToCheck))
|
|
if err != nil {
|
|
log.Error("Failed to query accounts db for a given address", "address", address, "error", err)
|
|
continue
|
|
}
|
|
|
|
// Skip removal if counterparty is in accounts DB and removed address is not sender
|
|
if counterpartyExists && address != mtx.FromAddress {
|
|
continue
|
|
}
|
|
}
|
|
|
|
ids = append(ids, mtx.ID)
|
|
}
|
|
|
|
if len(ids) > 0 {
|
|
for _, id := range ids {
|
|
err = tm.storage.DeleteMultiTransaction(id)
|
|
if err != nil {
|
|
log.Error("Failed to delete multi transaction", "id", id, "error", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|