github.com/status-im/status-go@v1.1.0/services/wallet/transfer/transaction_manager_multitransaction.go (about)

     1  package transfer
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/pkg/errors"
    10  
    11  	"github.com/ethereum/go-ethereum/common"
    12  	"github.com/ethereum/go-ethereum/log"
    13  	"github.com/status-im/status-go/account"
    14  	"github.com/status-im/status-go/eth-node/types"
    15  	wallet_common "github.com/status-im/status-go/services/wallet/common"
    16  	"github.com/status-im/status-go/services/wallet/router/pathprocessor"
    17  	"github.com/status-im/status-go/services/wallet/walletevent"
    18  	"github.com/status-im/status-go/signal"
    19  	"github.com/status-im/status-go/transactions"
    20  )
    21  
    22  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"
    23  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"
    24  
    25  var pendingTxTimeout time.Duration = 10 * time.Minute
    26  var ErrWatchPendingTxTimeout = errors.New("timeout watching for pending transaction")
    27  var ErrPendingTxNotExists = errors.New("pending transaction does not exist")
    28  
    29  func (tm *TransactionManager) InsertMultiTransaction(multiTransaction *MultiTransaction) (wallet_common.MultiTransactionIDType, error) {
    30  	return multiTransaction.ID, tm.storage.CreateMultiTransaction(multiTransaction)
    31  }
    32  
    33  func (tm *TransactionManager) UpdateMultiTransaction(multiTransaction *MultiTransaction) error {
    34  	return tm.storage.UpdateMultiTransaction(multiTransaction)
    35  }
    36  
    37  func (tm *TransactionManager) CreateMultiTransactionFromCommand(command *MultiTransactionCommand,
    38  	data []*pathprocessor.MultipathProcessorTxArgs) (*MultiTransaction, error) {
    39  
    40  	multiTransaction := multiTransactionFromCommand(command)
    41  
    42  	// Extract network from args
    43  	switch multiTransaction.Type {
    44  	case MultiTransactionSend, MultiTransactionApprove, MultiTransactionSwap:
    45  		if multiTransaction.FromNetworkID == wallet_common.UnknownChainID && len(data) == 1 {
    46  			multiTransaction.FromNetworkID = data[0].ChainID
    47  		}
    48  	case MultiTransactionBridge:
    49  		if len(data) == 1 && data[0].HopTx != nil {
    50  			if multiTransaction.FromNetworkID == wallet_common.UnknownChainID {
    51  				multiTransaction.FromNetworkID = data[0].HopTx.ChainID
    52  			}
    53  			if multiTransaction.ToNetworkID == wallet_common.UnknownChainID {
    54  				multiTransaction.ToNetworkID = data[0].HopTx.ChainIDTo
    55  			}
    56  		}
    57  	default:
    58  		return nil, fmt.Errorf("unsupported multi transaction type: %v", multiTransaction.Type)
    59  	}
    60  
    61  	return multiTransaction, nil
    62  }
    63  
    64  func (tm *TransactionManager) SendTransactionForSigningToKeycard(ctx context.Context, multiTransaction *MultiTransaction, data []*pathprocessor.MultipathProcessorTxArgs, pathProcessors map[string]pathprocessor.PathProcessor) error {
    65  	acc, err := tm.accountsDB.GetAccountByAddress(types.Address(multiTransaction.FromAddress))
    66  	if err != nil {
    67  		return err
    68  	}
    69  
    70  	kp, err := tm.accountsDB.GetKeypairByKeyUID(acc.KeyUID)
    71  	if err != nil {
    72  		return err
    73  	}
    74  
    75  	if !kp.MigratedToKeycard() {
    76  		return fmt.Errorf("account being used is not migrated to a keycard, password is required")
    77  	}
    78  
    79  	tm.multiTransactionForKeycardSigning = multiTransaction
    80  	tm.multipathTransactionsData = data
    81  	hashes, err := tm.buildTransactions(pathProcessors)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	signal.SendWalletEvent(signal.SignTransactions, hashes)
    87  
    88  	return nil
    89  }
    90  
    91  func (tm *TransactionManager) SendTransactions(ctx context.Context, multiTransaction *MultiTransaction, data []*pathprocessor.MultipathProcessorTxArgs, pathProcessors map[string]pathprocessor.PathProcessor, account *account.SelectedExtKey) (*MultiTransactionCommandResult, error) {
    92  	updateDataFromMultiTx(data, multiTransaction)
    93  	hashes, err := sendTransactions(data, pathProcessors, account)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	return &MultiTransactionCommandResult{
    99  		ID:     int64(multiTransaction.ID),
   100  		Hashes: hashes,
   101  	}, nil
   102  }
   103  
   104  func (tm *TransactionManager) ProceedWithTransactionsSignatures(ctx context.Context, signatures map[string]SignatureDetails) (*MultiTransactionCommandResult, error) {
   105  	if err := addSignaturesToTransactions(tm.transactionsForKeycardSigning, signatures); err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	// send transactions
   110  	hashes := make(map[uint64][]types.Hash)
   111  	for _, desc := range tm.transactionsForKeycardSigning {
   112  		txWithSignature, err := tm.transactor.AddSignatureToTransaction(desc.chainID, desc.builtTx, desc.signature)
   113  		if err != nil {
   114  			return nil, err
   115  		}
   116  
   117  		hash, err := tm.transactor.SendTransactionWithSignature(desc.from, tm.multiTransactionForKeycardSigning.FromAsset, tm.multiTransactionForKeycardSigning.ID, txWithSignature)
   118  		if err != nil {
   119  			return nil, err // TODO: One of transfers within transaction could have been sent. Need to notify user about it
   120  		}
   121  		hashes[desc.chainID] = append(hashes[desc.chainID], hash)
   122  	}
   123  
   124  	_, err := tm.InsertMultiTransaction(tm.multiTransactionForKeycardSigning)
   125  	if err != nil {
   126  		log.Error("failed to insert multi transaction", "err", err)
   127  	}
   128  
   129  	return &MultiTransactionCommandResult{
   130  		ID:     int64(tm.multiTransactionForKeycardSigning.ID),
   131  		Hashes: hashes,
   132  	}, nil
   133  }
   134  
   135  func (tm *TransactionManager) GetMultiTransactions(ctx context.Context, ids []wallet_common.MultiTransactionIDType) ([]*MultiTransaction, error) {
   136  	return tm.storage.ReadMultiTransactions(&MultiTxDetails{IDs: ids})
   137  }
   138  
   139  func (tm *TransactionManager) GetBridgeOriginMultiTransaction(ctx context.Context, toChainID uint64, crossTxID string) (*MultiTransaction, error) {
   140  	details := NewMultiTxDetails()
   141  	details.ToChainID = toChainID
   142  	details.CrossTxID = crossTxID
   143  
   144  	multiTxs, err := tm.storage.ReadMultiTransactions(details)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	for _, multiTx := range multiTxs {
   150  		// Origin MultiTxs will have a missing "ToTxHash"
   151  		if multiTx.ToTxHash == emptyHash {
   152  			return multiTx, nil
   153  		}
   154  	}
   155  
   156  	return nil, nil
   157  }
   158  
   159  func (tm *TransactionManager) GetBridgeDestinationMultiTransaction(ctx context.Context, toChainID uint64, crossTxID string) (*MultiTransaction, error) {
   160  	details := NewMultiTxDetails()
   161  	details.ToChainID = toChainID
   162  	details.CrossTxID = crossTxID
   163  
   164  	multiTxs, err := tm.storage.ReadMultiTransactions(details)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	for _, multiTx := range multiTxs {
   170  		// Destination MultiTxs will have a missing "FromTxHash"
   171  		if multiTx.FromTxHash == emptyHash {
   172  			return multiTx, nil
   173  		}
   174  	}
   175  
   176  	return nil, nil
   177  }
   178  
   179  func (tm *TransactionManager) WatchTransaction(ctx context.Context, chainID uint64, transactionHash common.Hash) error {
   180  	// Workaround to keep the blocking call until the clients use the PendingTxTracker APIs
   181  	eventChan := make(chan walletevent.Event, 2)
   182  	sub := tm.eventFeed.Subscribe(eventChan)
   183  	defer sub.Unsubscribe()
   184  
   185  	status, err := tm.pendingTracker.Watch(ctx, wallet_common.ChainID(chainID), transactionHash)
   186  	if err == nil && *status != transactions.Pending {
   187  		log.Error("transaction is not pending", "status", status)
   188  		return nil
   189  	}
   190  
   191  	for {
   192  		select {
   193  		case we := <-eventChan:
   194  			if transactions.EventPendingTransactionStatusChanged == we.Type {
   195  				var p transactions.StatusChangedPayload
   196  				err = json.Unmarshal([]byte(we.Message), &p)
   197  				if err != nil {
   198  					return err
   199  				}
   200  				if p.ChainID == wallet_common.ChainID(chainID) && p.Hash == transactionHash {
   201  					return nil
   202  				}
   203  			}
   204  		case <-time.After(pendingTxTimeout):
   205  			return ErrWatchPendingTxTimeout
   206  		}
   207  	}
   208  }