status-go/services/wallet/transfer/transaction_manager.go
Sale Djenic 28506bcd17 chore_: improvements of the sending route generated by the router process
This commit simplifies the sending process of the best route suggested by the router.
It also makes the sending process the same for accounts (key pairs) migrated to a keycard
and those stored locally in local keystore files.

Deprecated endpoints:
- `CreateMultiTransaction`
- `ProceedWithTransactionsSignatures`

Deprecated signal:
- `wallet.sign.transactions`

New endpoints:
- `BuildTransactionsFromRoute`
- `SendRouterTransactionsWithSignatures`

The flow for sending the best router suggested by the router:
- call `BuildTransactionsFromRoute`
- wait for the `wallet.router.sign-transactions` signal
- sign received hashes using `SignMessage` call or sign on keycard
- call `SendRouterTransactionsWithSignatures` with the signatures of signed hashes from the previous step
- `wallet.router.transactions-sent` signal will be sent after transactions are sent or if an error occurs

New signals:
- `wallet.router.sending-transactions-started` // notifies client that the sending transactions process started
- `wallet.router.sign-transactions` // notifies client about the list of transactions that need to be signed
- `wallet.router.transactions-sent` // notifies client about transactions that are sent
- `wallet.transaction.status-changed` // notifies about status of sent transactions
2024-10-01 14:30:33 +02:00

288 lines
9.2 KiB
Go

package transfer
import (
"fmt"
"math/big"
"time"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/event"
"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"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params"
wallet_common "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
"github.com/status-im/status-go/services/wallet/router/routes"
"github.com/status-im/status-go/transactions"
)
type SignatureDetails struct {
R string `json:"r"`
S string `json:"s"`
V string `json:"v"`
}
func (sd *SignatureDetails) Validate() error {
if len(sd.R) != 64 || len(sd.S) != 64 || len(sd.V) != 2 {
return ErrInvalidSignatureDetails
}
return nil
}
// TODO: remove this struct once mobile switches to the new approach
type TransactionDescription struct {
chainID uint64
from common.Address
builtTx *ethTypes.Transaction
signature []byte
}
type RouterTransactionDetails struct {
routerPath *routes.Path
txArgs *transactions.SendTxArgs
tx *ethTypes.Transaction
txHashToSign types.Hash
txSignature []byte
txSentHash types.Hash
approvalTxArgs *transactions.SendTxArgs
approvalTx *ethTypes.Transaction
approvalHashToSign types.Hash
approvalSignature []byte
approvalTxSentHash types.Hash
}
type TransactionManager struct {
storage MultiTransactionStorage
gethManager *account.GethManager
transactor transactions.TransactorIface
config *params.NodeConfig
accountsDB accounts.AccountsStorage
pendingTracker *transactions.PendingTxTracker
eventFeed *event.Feed
// TODO: remove this struct once mobile switches to the new approach
multiTransactionForKeycardSigning *MultiTransaction
multipathTransactionsData []*pathprocessor.MultipathProcessorTxArgs
transactionsForKeycardSigning map[common.Hash]*TransactionDescription
// used in a new approach
routerTransactions []*RouterTransactionDetails
}
type MultiTransactionStorage interface {
CreateMultiTransaction(tx *MultiTransaction) error
ReadMultiTransactions(details *MultiTxDetails) ([]*MultiTransaction, error)
UpdateMultiTransaction(tx *MultiTransaction) error
DeleteMultiTransaction(id wallet_common.MultiTransactionIDType) error
}
func NewTransactionManager(
storage MultiTransactionStorage,
gethManager *account.GethManager,
transactor transactions.TransactorIface,
config *params.NodeConfig,
accountsDB accounts.AccountsStorage,
pendingTxManager *transactions.PendingTxTracker,
eventFeed *event.Feed,
) *TransactionManager {
return &TransactionManager{
storage: storage,
gethManager: gethManager,
transactor: transactor,
config: config,
accountsDB: accountsDB,
pendingTracker: pendingTxManager,
eventFeed: eventFeed,
}
}
var (
emptyHash = common.Hash{}
)
type MultiTransactionType uint8
const (
MultiTransactionSend = iota
MultiTransactionSwap
MultiTransactionBridge
MultiTransactionApprove
MultiTransactionTypeInvalid = 255
)
type MultiTransaction struct {
ID wallet_common.MultiTransactionIDType `json:"id"`
Timestamp uint64 `json:"timestamp"`
FromNetworkID uint64 `json:"fromNetworkID"`
ToNetworkID uint64 `json:"toNetworkID"`
FromTxHash common.Hash `json:"fromTxHash"`
ToTxHash common.Hash `json:"toTxHash"`
FromAddress common.Address `json:"fromAddress"`
ToAddress common.Address `json:"toAddress"`
FromAsset string `json:"fromAsset"`
ToAsset string `json:"toAsset"`
FromAmount *hexutil.Big `json:"fromAmount"`
ToAmount *hexutil.Big `json:"toAmount"`
Type MultiTransactionType `json:"type"`
CrossTxID string
}
type MultiTransactionCommand struct {
FromAddress common.Address `json:"fromAddress"`
ToAddress common.Address `json:"toAddress"`
FromAsset string `json:"fromAsset"`
ToAsset string `json:"toAsset"`
FromAmount *hexutil.Big `json:"fromAmount"`
ToAmount *hexutil.Big `json:"toAmount"`
Type MultiTransactionType `json:"type"`
}
type MultiTransactionCommandResult struct {
ID int64 `json:"id"`
Hashes map[uint64][]types.Hash `json:"hashes"`
}
type TransactionIdentity struct {
ChainID wallet_common.ChainID `json:"chainId"`
Hash common.Hash `json:"hash"`
Address common.Address `json:"address"`
}
type TxResponse struct {
KeyUID string `json:"keyUid,omitempty"`
Address types.Address `json:"address,omitempty"`
AddressPath string `json:"addressPath,omitempty"`
SignOnKeycard bool `json:"signOnKeycard,omitempty"`
ChainID uint64 `json:"chainId,omitempty"`
MessageToSign interface{} `json:"messageToSign,omitempty"`
TxArgs transactions.SendTxArgs `json:"txArgs,omitempty"`
RawTx string `json:"rawTx,omitempty"`
TxHash common.Hash `json:"txHash,omitempty"`
}
func NewMultiTransaction(timestamp uint64, fromNetworkID, toNetworkID uint64, fromTxHash, toTxHash common.Hash, fromAddress, toAddress common.Address, fromAsset, toAsset string, fromAmount, toAmount *hexutil.Big, txType MultiTransactionType, crossTxID string) *MultiTransaction {
if timestamp == 0 {
timestamp = uint64(time.Now().Unix())
}
return &MultiTransaction{
ID: multiTransactionIDGenerator(),
Timestamp: timestamp,
FromNetworkID: fromNetworkID,
ToNetworkID: toNetworkID,
FromTxHash: fromTxHash,
ToTxHash: toTxHash,
FromAddress: fromAddress,
ToAddress: toAddress,
FromAsset: fromAsset,
ToAsset: toAsset,
FromAmount: fromAmount,
ToAmount: toAmount,
Type: txType,
CrossTxID: crossTxID,
}
}
func (tm *TransactionManager) SignMessage(message types.HexBytes, account *types.Key) (string, error) {
if account == nil || account.PrivateKey == nil {
return "", fmt.Errorf("account or private key is nil")
}
signature, err := crypto.Sign(message[:], account.PrivateKey)
return types.EncodeHex(signature), err
}
func (tm *TransactionManager) BuildTransaction(chainID uint64, sendArgs transactions.SendTxArgs) (response *TxResponse, err error) {
account, err := tm.accountsDB.GetAccountByAddress(sendArgs.From)
if err != nil {
return nil, fmt.Errorf("failed to resolve account: %w", err)
}
kp, err := tm.accountsDB.GetKeypairByKeyUID(account.KeyUID)
if err != nil {
return nil, err
}
txBeingSigned, _, err := tm.transactor.ValidateAndBuildTransaction(chainID, sendArgs, -1)
if err != nil {
return nil, err
}
// Set potential missing fields that were added while building the transaction
if sendArgs.Value == nil {
value := hexutil.Big(*txBeingSigned.Value())
sendArgs.Value = &value
}
if sendArgs.Nonce == nil {
nonce := hexutil.Uint64(txBeingSigned.Nonce())
sendArgs.Nonce = &nonce
}
if sendArgs.Gas == nil {
gas := hexutil.Uint64(txBeingSigned.Gas())
sendArgs.Gas = &gas
}
if sendArgs.GasPrice == nil {
gasPrice := hexutil.Big(*txBeingSigned.GasPrice())
sendArgs.GasPrice = &gasPrice
}
if sendArgs.IsDynamicFeeTx() {
if sendArgs.MaxPriorityFeePerGas == nil {
maxPriorityFeePerGas := hexutil.Big(*txBeingSigned.GasTipCap())
sendArgs.MaxPriorityFeePerGas = &maxPriorityFeePerGas
}
if sendArgs.MaxFeePerGas == nil {
maxFeePerGas := hexutil.Big(*txBeingSigned.GasFeeCap())
sendArgs.MaxFeePerGas = &maxFeePerGas
}
}
signer := ethTypes.NewLondonSigner(new(big.Int).SetUint64(chainID))
return &TxResponse{
KeyUID: account.KeyUID,
Address: account.Address,
AddressPath: account.Path,
SignOnKeycard: kp.MigratedToKeycard(),
ChainID: chainID,
MessageToSign: signer.Hash(txBeingSigned),
TxArgs: sendArgs,
}, nil
}
func (tm *TransactionManager) BuildRawTransaction(chainID uint64, sendArgs transactions.SendTxArgs, signature []byte) (response *TxResponse, err error) {
tx, err := tm.transactor.BuildTransactionWithSignature(chainID, sendArgs, signature)
if err != nil {
return nil, err
}
data, err := tx.MarshalBinary()
if err != nil {
return nil, err
}
return &TxResponse{
ChainID: chainID,
TxArgs: sendArgs,
RawTx: types.EncodeHex(data),
TxHash: tx.Hash(),
}, nil
}
func (tm *TransactionManager) SendTransactionWithSignature(chainID uint64, sendArgs transactions.SendTxArgs, signature []byte) (hash types.Hash, err error) {
txWithSignature, err := tm.transactor.BuildTransactionWithSignature(chainID, sendArgs, signature)
if err != nil {
return hash, err
}
hash, err = tm.transactor.SendTransactionWithSignature(common.Address(sendArgs.From), sendArgs.Symbol, sendArgs.MultiTransactionID, txWithSignature)
return hash, err
}