status-go/services/wallet/transfer/transaction_manager_multitr...

212 lines
7.5 KiB
Go

package transfer
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/pkg/errors"
"go.uber.org/zap"
"github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/account"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/logutils"
wallet_common "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/requests"
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
"github.com/status-im/status-go/services/wallet/walletevent"
"github.com/status-im/status-go/signal"
"github.com/status-im/status-go/transactions"
)
const multiTransactionColumns = "id, from_network_id, from_tx_hash, from_address, from_asset, from_amount, to_network_id, to_tx_hash, to_address, to_asset, to_amount, type, cross_tx_id, timestamp"
const selectMultiTransactionColumns = "id, COALESCE(from_network_id, 0), from_tx_hash, from_address, from_asset, from_amount, COALESCE(to_network_id, 0), to_tx_hash, to_address, to_asset, to_amount, type, cross_tx_id, timestamp"
var pendingTxTimeout time.Duration = 10 * time.Minute
var ErrWatchPendingTxTimeout = errors.New("timeout watching for pending transaction")
var ErrPendingTxNotExists = errors.New("pending transaction does not exist")
func (tm *TransactionManager) InsertMultiTransaction(multiTransaction *MultiTransaction) (wallet_common.MultiTransactionIDType, error) {
return multiTransaction.ID, tm.storage.CreateMultiTransaction(multiTransaction)
}
func (tm *TransactionManager) UpdateMultiTransaction(multiTransaction *MultiTransaction) error {
return tm.storage.UpdateMultiTransaction(multiTransaction)
}
func (tm *TransactionManager) CreateMultiTransactionFromCommand(command *MultiTransactionCommand,
data []*pathprocessor.MultipathProcessorTxArgs) (*MultiTransaction, error) {
multiTransaction := multiTransactionFromCommand(command)
// Extract network from args
switch multiTransaction.Type {
case MultiTransactionSend, MultiTransactionApprove, MultiTransactionSwap:
if multiTransaction.FromNetworkID == wallet_common.UnknownChainID && len(data) == 1 {
multiTransaction.FromNetworkID = data[0].ChainID
}
case MultiTransactionBridge:
if len(data) == 1 && data[0].HopTx != nil {
if multiTransaction.FromNetworkID == wallet_common.UnknownChainID {
multiTransaction.FromNetworkID = data[0].HopTx.ChainID
}
if multiTransaction.ToNetworkID == wallet_common.UnknownChainID {
multiTransaction.ToNetworkID = data[0].HopTx.ChainIDTo
}
}
default:
return nil, fmt.Errorf("unsupported multi transaction type: %v", multiTransaction.Type)
}
return multiTransaction, nil
}
func (tm *TransactionManager) SendTransactionForSigningToKeycard(ctx context.Context, multiTransaction *MultiTransaction, data []*pathprocessor.MultipathProcessorTxArgs, pathProcessors map[string]pathprocessor.PathProcessor) error {
acc, err := tm.accountsDB.GetAccountByAddress(types.Address(multiTransaction.FromAddress))
if err != nil {
return err
}
kp, err := tm.accountsDB.GetKeypairByKeyUID(acc.KeyUID)
if err != nil {
return err
}
if !kp.MigratedToKeycard() {
return fmt.Errorf("account being used is not migrated to a keycard, password is required")
}
tm.multiTransactionForKeycardSigning = multiTransaction
tm.multipathTransactionsData = data
hashes, err := tm.buildTransactions(pathProcessors)
if err != nil {
return err
}
signal.SendWalletEvent(signal.SignTransactions, hashes)
return nil
}
func (tm *TransactionManager) SendTransactions(ctx context.Context, multiTransaction *MultiTransaction, data []*pathprocessor.MultipathProcessorTxArgs, pathProcessors map[string]pathprocessor.PathProcessor, account *account.SelectedExtKey) (*MultiTransactionCommandResult, error) {
updateDataFromMultiTx(data, multiTransaction)
hashes, err := sendTransactions(data, pathProcessors, account)
if err != nil {
return nil, err
}
return &MultiTransactionCommandResult{
ID: int64(multiTransaction.ID),
Hashes: hashes,
}, nil
}
func (tm *TransactionManager) ProceedWithTransactionsSignatures(ctx context.Context, signatures map[string]requests.SignatureDetails) (*MultiTransactionCommandResult, error) {
if err := addSignaturesToTransactions(tm.transactionsForKeycardSigning, signatures); err != nil {
return nil, err
}
// send transactions
hashes := make(map[uint64][]types.Hash)
for _, desc := range tm.transactionsForKeycardSigning {
txWithSignature, err := tm.transactor.AddSignatureToTransaction(desc.chainID, desc.builtTx, desc.signature)
if err != nil {
return nil, err
}
hash, err := tm.transactor.SendTransactionWithSignature(desc.from, tm.multiTransactionForKeycardSigning.FromAsset, tm.multiTransactionForKeycardSigning.ID, txWithSignature)
if err != nil {
return nil, err // TODO: One of transfers within transaction could have been sent. Need to notify user about it
}
hashes[desc.chainID] = append(hashes[desc.chainID], hash)
}
_, err := tm.InsertMultiTransaction(tm.multiTransactionForKeycardSigning)
if err != nil {
logutils.ZapLogger().Error("failed to insert multi transaction", zap.Error(err))
}
return &MultiTransactionCommandResult{
ID: int64(tm.multiTransactionForKeycardSigning.ID),
Hashes: hashes,
}, nil
}
func (tm *TransactionManager) GetMultiTransactions(ctx context.Context, ids []wallet_common.MultiTransactionIDType) ([]*MultiTransaction, error) {
return tm.storage.ReadMultiTransactions(&MultiTxDetails{IDs: ids})
}
func (tm *TransactionManager) GetBridgeOriginMultiTransaction(ctx context.Context, toChainID uint64, crossTxID string) (*MultiTransaction, error) {
details := NewMultiTxDetails()
details.ToChainID = toChainID
details.CrossTxID = crossTxID
multiTxs, err := tm.storage.ReadMultiTransactions(details)
if err != nil {
return nil, err
}
for _, multiTx := range multiTxs {
// Origin MultiTxs will have a missing "ToTxHash"
if multiTx.ToTxHash == emptyHash {
return multiTx, nil
}
}
return nil, nil
}
func (tm *TransactionManager) GetBridgeDestinationMultiTransaction(ctx context.Context, toChainID uint64, crossTxID string) (*MultiTransaction, error) {
details := NewMultiTxDetails()
details.ToChainID = toChainID
details.CrossTxID = crossTxID
multiTxs, err := tm.storage.ReadMultiTransactions(details)
if err != nil {
return nil, err
}
for _, multiTx := range multiTxs {
// Destination MultiTxs will have a missing "FromTxHash"
if multiTx.FromTxHash == emptyHash {
return multiTx, nil
}
}
return nil, nil
}
func (tm *TransactionManager) WatchTransaction(ctx context.Context, chainID uint64, transactionHash common.Hash) error {
// Workaround to keep the blocking call until the clients use the PendingTxTracker APIs
eventChan := make(chan walletevent.Event, 2)
sub := tm.eventFeed.Subscribe(eventChan)
defer sub.Unsubscribe()
status, err := tm.pendingTracker.Watch(ctx, wallet_common.ChainID(chainID), transactionHash)
if err == nil && *status != transactions.Pending {
logutils.ZapLogger().Error("transaction is not pending", zap.String("status", *status))
return nil
}
for {
select {
case we := <-eventChan:
if transactions.EventPendingTransactionStatusChanged == we.Type {
var p transactions.StatusChangedPayload
err = json.Unmarshal([]byte(we.Message), &p)
if err != nil {
return err
}
if p.ChainID == wallet_common.ChainID(chainID) && p.Hash == transactionHash {
signal.SendWalletEvent(signal.TransactionStatusChanged, p)
return nil
}
}
case <-time.After(pendingTxTimeout):
return ErrWatchPendingTxTimeout
}
}
}