2023-02-15 12:28:19 +00:00
|
|
|
package transfer
|
2021-09-09 14:28:54 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"database/sql"
|
2023-09-29 17:56:27 +00:00
|
|
|
"encoding/hex"
|
2021-09-09 14:28:54 +00:00
|
|
|
"errors"
|
2022-07-04 07:48:30 +00:00
|
|
|
"fmt"
|
2021-09-09 14:28:54 +00:00
|
|
|
"math/big"
|
2022-07-04 07:48:30 +00:00
|
|
|
"strings"
|
2021-09-09 14:28:54 +00:00
|
|
|
"time"
|
|
|
|
|
2023-09-29 17:56:27 +00:00
|
|
|
ethTypes "github.com/ethereum/go-ethereum/core/types"
|
|
|
|
|
2021-09-09 14:28:54 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common"
|
2022-07-15 08:53:56 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
2023-07-07 14:23:30 +00:00
|
|
|
"github.com/ethereum/go-ethereum/event"
|
2021-09-09 14:28:54 +00:00
|
|
|
"github.com/ethereum/go-ethereum/log"
|
2022-07-15 08:53:56 +00:00
|
|
|
"github.com/status-im/status-go/account"
|
2023-09-29 17:56:27 +00:00
|
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
2022-07-15 08:53:56 +00:00
|
|
|
"github.com/status-im/status-go/eth-node/types"
|
|
|
|
"github.com/status-im/status-go/multiaccounts/accounts"
|
|
|
|
"github.com/status-im/status-go/params"
|
2021-09-09 14:28:54 +00:00
|
|
|
"github.com/status-im/status-go/services/wallet/bigint"
|
2022-09-13 07:10:59 +00:00
|
|
|
"github.com/status-im/status-go/services/wallet/bridge"
|
2023-05-11 07:50:07 +00:00
|
|
|
wallet_common "github.com/status-im/status-go/services/wallet/common"
|
2023-07-07 14:23:30 +00:00
|
|
|
"github.com/status-im/status-go/services/wallet/walletevent"
|
2023-09-29 17:56:27 +00:00
|
|
|
"github.com/status-im/status-go/signal"
|
2022-07-15 08:53:56 +00:00
|
|
|
"github.com/status-im/status-go/transactions"
|
2021-09-09 14:28:54 +00:00
|
|
|
)
|
|
|
|
|
2023-06-21 14:09:55 +00:00
|
|
|
type MultiTransactionIDType int64
|
|
|
|
|
2023-07-05 12:36:23 +00:00
|
|
|
const (
|
2023-06-21 14:09:55 +00:00
|
|
|
NoMultiTransactionID = MultiTransactionIDType(0)
|
2023-07-07 14:23:30 +00:00
|
|
|
|
|
|
|
// EventMTTransactionUpdate is emitted when a multi-transaction is updated (added or deleted)
|
|
|
|
EventMTTransactionUpdate walletevent.EventType = "multi-transaction-update"
|
2023-07-05 12:36:23 +00:00
|
|
|
)
|
|
|
|
|
2023-09-29 17:56:27 +00:00
|
|
|
type SignatureDetails struct {
|
|
|
|
R string `json:"r"`
|
|
|
|
S string `json:"s"`
|
|
|
|
V string `json:"v"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type TransactionDescription struct {
|
|
|
|
chainID uint64
|
|
|
|
builtTx *ethTypes.Transaction
|
|
|
|
signature []byte
|
2023-11-06 09:26:02 +00:00
|
|
|
unlock transactions.UnlockNonceFunc
|
2023-09-29 17:56:27 +00:00
|
|
|
}
|
|
|
|
|
2021-09-09 14:28:54 +00:00
|
|
|
type TransactionManager struct {
|
2023-06-21 14:09:55 +00:00
|
|
|
db *sql.DB
|
|
|
|
gethManager *account.GethManager
|
|
|
|
transactor *transactions.Transactor
|
|
|
|
config *params.NodeConfig
|
|
|
|
accountsDB *accounts.Database
|
2023-08-01 18:50:30 +00:00
|
|
|
pendingTracker *transactions.PendingTxTracker
|
2023-07-07 14:23:30 +00:00
|
|
|
eventFeed *event.Feed
|
2023-09-29 17:56:27 +00:00
|
|
|
|
|
|
|
multiTransactionForKeycardSigning *MultiTransaction
|
|
|
|
transactionsBridgeData []*bridge.TransactionBridge
|
|
|
|
transactionsForKeycardSingning map[common.Hash]*TransactionDescription
|
2022-07-15 08:53:56 +00:00
|
|
|
}
|
|
|
|
|
2023-07-07 14:23:30 +00:00
|
|
|
func NewTransactionManager(
|
|
|
|
db *sql.DB,
|
|
|
|
gethManager *account.GethManager,
|
|
|
|
transactor *transactions.Transactor,
|
|
|
|
config *params.NodeConfig,
|
|
|
|
accountsDB *accounts.Database,
|
2023-08-01 18:50:30 +00:00
|
|
|
pendingTxManager *transactions.PendingTxTracker,
|
2023-07-07 14:23:30 +00:00
|
|
|
eventFeed *event.Feed,
|
|
|
|
) *TransactionManager {
|
2023-02-15 12:28:19 +00:00
|
|
|
return &TransactionManager{
|
2023-06-21 14:09:55 +00:00
|
|
|
db: db,
|
|
|
|
gethManager: gethManager,
|
|
|
|
transactor: transactor,
|
|
|
|
config: config,
|
|
|
|
accountsDB: accountsDB,
|
2023-08-01 18:50:30 +00:00
|
|
|
pendingTracker: pendingTxManager,
|
2023-07-07 14:23:30 +00:00
|
|
|
eventFeed: eventFeed,
|
2023-02-15 12:28:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-13 14:20:48 +00:00
|
|
|
var (
|
|
|
|
emptyHash = common.Hash{}
|
|
|
|
)
|
|
|
|
|
2022-07-15 08:53:56 +00:00
|
|
|
type MultiTransactionType uint8
|
|
|
|
|
|
|
|
const (
|
|
|
|
MultiTransactionSend = iota
|
|
|
|
MultiTransactionSwap
|
|
|
|
MultiTransactionBridge
|
|
|
|
)
|
|
|
|
|
|
|
|
type MultiTransaction struct {
|
2023-06-13 14:20:48 +00:00
|
|
|
ID uint `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
|
2022-07-15 08:53:56 +00:00
|
|
|
}
|
|
|
|
|
2023-06-12 10:49:32 +00:00
|
|
|
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"`
|
|
|
|
Type MultiTransactionType `json:"type"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type MultiTransactionCommandResult struct {
|
2022-07-15 08:53:56 +00:00
|
|
|
ID int64 `json:"id"`
|
|
|
|
Hashes map[uint64][]types.Hash `json:"hashes"`
|
2021-09-09 14:28:54 +00:00
|
|
|
}
|
|
|
|
|
2023-04-21 11:59:29 +00:00
|
|
|
type TransactionIdentity struct {
|
2023-05-11 07:50:07 +00:00
|
|
|
ChainID wallet_common.ChainID `json:"chainId"`
|
|
|
|
Hash common.Hash `json:"hash"`
|
|
|
|
Address common.Address `json:"address"`
|
2023-04-21 11:59:29 +00:00
|
|
|
}
|
|
|
|
|
2023-06-13 14:20:48 +00:00
|
|
|
const multiTransactionColumns = "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"
|
2023-07-11 21:40:58 +00:00
|
|
|
const selectMultiTransactionColumns = "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"
|
2023-06-13 14:20:48 +00:00
|
|
|
|
|
|
|
func rowsToMultiTransactions(rows *sql.Rows) ([]*MultiTransaction, error) {
|
|
|
|
var multiTransactions []*MultiTransaction
|
|
|
|
for rows.Next() {
|
|
|
|
multiTransaction := &MultiTransaction{}
|
|
|
|
var fromAmountDB, toAmountDB sql.NullString
|
2023-07-12 21:51:43 +00:00
|
|
|
var fromTxHash, toTxHash sql.RawBytes
|
2023-06-13 14:20:48 +00:00
|
|
|
err := rows.Scan(
|
|
|
|
&multiTransaction.ID,
|
|
|
|
&multiTransaction.FromNetworkID,
|
2023-07-12 21:51:43 +00:00
|
|
|
&fromTxHash,
|
2023-06-13 14:20:48 +00:00
|
|
|
&multiTransaction.FromAddress,
|
|
|
|
&multiTransaction.FromAsset,
|
|
|
|
&fromAmountDB,
|
|
|
|
&multiTransaction.ToNetworkID,
|
2023-07-12 21:51:43 +00:00
|
|
|
&toTxHash,
|
2023-06-13 14:20:48 +00:00
|
|
|
&multiTransaction.ToAddress,
|
|
|
|
&multiTransaction.ToAsset,
|
|
|
|
&toAmountDB,
|
|
|
|
&multiTransaction.Type,
|
|
|
|
&multiTransaction.CrossTxID,
|
|
|
|
&multiTransaction.Timestamp,
|
|
|
|
)
|
2023-07-12 21:51:43 +00:00
|
|
|
if len(fromTxHash) > 0 {
|
|
|
|
multiTransaction.FromTxHash = common.BytesToHash(fromTxHash)
|
|
|
|
}
|
|
|
|
if len(toTxHash) > 0 {
|
|
|
|
multiTransaction.ToTxHash = common.BytesToHash(toTxHash)
|
|
|
|
}
|
2023-06-13 14:20:48 +00:00
|
|
|
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
|
|
|
|
}
|
2022-07-15 08:53:56 +00:00
|
|
|
|
2023-10-30 06:53:22 +00:00
|
|
|
func getMultiTransactionTimestamp(multiTransaction *MultiTransaction) uint64 {
|
|
|
|
if multiTransaction.Timestamp != 0 {
|
|
|
|
return multiTransaction.Timestamp
|
|
|
|
}
|
|
|
|
return uint64(time.Now().Unix())
|
|
|
|
}
|
|
|
|
|
2023-07-07 14:23:30 +00:00
|
|
|
// insertMultiTransaction inserts a multi transaction into the database and updates multi-transaction ID and timestamp
|
2023-03-01 19:35:19 +00:00
|
|
|
func insertMultiTransaction(db *sql.DB, multiTransaction *MultiTransaction) (MultiTransactionIDType, error) {
|
2023-06-13 14:20:48 +00:00
|
|
|
insert, err := db.Prepare(fmt.Sprintf(`INSERT INTO multi_transactions (%s)
|
|
|
|
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, multiTransactionColumns))
|
2022-07-15 08:53:56 +00:00
|
|
|
if err != nil {
|
2023-06-21 14:09:55 +00:00
|
|
|
return NoMultiTransactionID, err
|
2022-07-15 08:53:56 +00:00
|
|
|
}
|
2023-10-30 06:53:22 +00:00
|
|
|
timestamp := getMultiTransactionTimestamp(multiTransaction)
|
2022-07-15 08:53:56 +00:00
|
|
|
result, err := insert.Exec(
|
2023-06-13 14:20:48 +00:00
|
|
|
multiTransaction.FromNetworkID,
|
|
|
|
multiTransaction.FromTxHash,
|
2022-07-15 08:53:56 +00:00
|
|
|
multiTransaction.FromAddress,
|
|
|
|
multiTransaction.FromAsset,
|
|
|
|
multiTransaction.FromAmount.String(),
|
2023-06-13 14:20:48 +00:00
|
|
|
multiTransaction.ToNetworkID,
|
|
|
|
multiTransaction.ToTxHash,
|
2022-07-15 08:53:56 +00:00
|
|
|
multiTransaction.ToAddress,
|
|
|
|
multiTransaction.ToAsset,
|
2023-06-02 20:08:45 +00:00
|
|
|
multiTransaction.ToAmount.String(),
|
2022-07-15 08:53:56 +00:00
|
|
|
multiTransaction.Type,
|
2023-06-13 14:20:48 +00:00
|
|
|
multiTransaction.CrossTxID,
|
|
|
|
timestamp,
|
2022-07-15 08:53:56 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
2023-06-21 14:09:55 +00:00
|
|
|
return NoMultiTransactionID, err
|
2022-07-15 08:53:56 +00:00
|
|
|
}
|
|
|
|
defer insert.Close()
|
|
|
|
multiTransactionID, err := result.LastInsertId()
|
2023-06-13 14:20:48 +00:00
|
|
|
|
2023-10-30 06:53:22 +00:00
|
|
|
multiTransaction.Timestamp = timestamp
|
2023-06-13 14:20:48 +00:00
|
|
|
multiTransaction.ID = uint(multiTransactionID)
|
|
|
|
|
2023-03-01 19:35:19 +00:00
|
|
|
return MultiTransactionIDType(multiTransactionID), err
|
|
|
|
}
|
|
|
|
|
2023-06-02 20:08:45 +00:00
|
|
|
func (tm *TransactionManager) InsertMultiTransaction(multiTransaction *MultiTransaction) (MultiTransactionIDType, error) {
|
2023-07-07 14:23:30 +00:00
|
|
|
return tm.insertMultiTransactionAndNotify(tm.db, multiTransaction, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tm *TransactionManager) insertMultiTransactionAndNotify(db *sql.DB, multiTransaction *MultiTransaction, chainIDs []uint64) (MultiTransactionIDType, error) {
|
|
|
|
id, err := insertMultiTransaction(db, multiTransaction)
|
|
|
|
if err != nil {
|
|
|
|
publishMultiTransactionUpdatedEvent(db, multiTransaction, tm.eventFeed, chainIDs)
|
|
|
|
}
|
|
|
|
return id, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// publishMultiTransactionUpdatedEvent notify listeners of new multi transaction (used in activity history)
|
|
|
|
func publishMultiTransactionUpdatedEvent(db *sql.DB, multiTransaction *MultiTransaction, eventFeed *event.Feed, chainIDs []uint64) {
|
|
|
|
publishFn := func(chainID uint64) {
|
|
|
|
eventFeed.Send(walletevent.Event{
|
|
|
|
Type: EventMTTransactionUpdate,
|
|
|
|
ChainID: chainID,
|
|
|
|
Accounts: []common.Address{multiTransaction.FromAddress, multiTransaction.ToAddress},
|
|
|
|
At: int64(multiTransaction.Timestamp),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if len(chainIDs) > 0 {
|
|
|
|
for _, chainID := range chainIDs {
|
|
|
|
publishFn(chainID)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
publishFn(0)
|
|
|
|
}
|
2023-06-02 20:08:45 +00:00
|
|
|
}
|
|
|
|
|
2023-06-13 14:20:48 +00:00
|
|
|
func updateMultiTransaction(db *sql.DB, multiTransaction *MultiTransaction) error {
|
|
|
|
if MultiTransactionIDType(multiTransaction.ID) == NoMultiTransactionID {
|
|
|
|
return fmt.Errorf("no multitransaction ID")
|
|
|
|
}
|
|
|
|
|
|
|
|
update, err := db.Prepare(fmt.Sprintf(`REPLACE INTO multi_transactions (rowid, %s)
|
|
|
|
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, multiTransactionColumns))
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-10-30 06:53:22 +00:00
|
|
|
timestamp := getMultiTransactionTimestamp(multiTransaction)
|
2023-06-13 14:20:48 +00:00
|
|
|
_, err = update.Exec(
|
|
|
|
multiTransaction.ID,
|
|
|
|
multiTransaction.FromNetworkID,
|
|
|
|
multiTransaction.FromTxHash,
|
|
|
|
multiTransaction.FromAddress,
|
|
|
|
multiTransaction.FromAsset,
|
|
|
|
multiTransaction.FromAmount.String(),
|
|
|
|
multiTransaction.ToNetworkID,
|
|
|
|
multiTransaction.ToTxHash,
|
|
|
|
multiTransaction.ToAddress,
|
|
|
|
multiTransaction.ToAsset,
|
|
|
|
multiTransaction.ToAmount.String(),
|
|
|
|
multiTransaction.Type,
|
|
|
|
multiTransaction.CrossTxID,
|
2023-10-30 06:53:22 +00:00
|
|
|
timestamp,
|
2023-06-13 14:20:48 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return update.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tm *TransactionManager) UpdateMultiTransaction(multiTransaction *MultiTransaction) error {
|
|
|
|
return updateMultiTransaction(tm.db, multiTransaction)
|
|
|
|
}
|
|
|
|
|
2023-09-29 17:56:27 +00:00
|
|
|
// In case of keycard account, password should be empty
|
2023-06-21 14:09:55 +00:00
|
|
|
func (tm *TransactionManager) CreateMultiTransactionFromCommand(ctx context.Context, command *MultiTransactionCommand,
|
|
|
|
data []*bridge.TransactionBridge, bridges map[string]bridge.Bridge, password string) (*MultiTransactionCommandResult, error) {
|
|
|
|
|
|
|
|
multiTransaction := multiTransactionFromCommand(command)
|
|
|
|
|
2023-07-07 14:23:30 +00:00
|
|
|
chainIDs := make([]uint64, 0, len(data))
|
|
|
|
for _, tx := range data {
|
|
|
|
chainIDs = append(chainIDs, tx.ChainID)
|
|
|
|
}
|
2023-10-30 09:03:29 +00:00
|
|
|
if multiTransaction.Type == MultiTransactionSend && multiTransaction.FromNetworkID == 0 && len(chainIDs) == 1 {
|
|
|
|
multiTransaction.FromNetworkID = chainIDs[0]
|
|
|
|
}
|
2023-07-07 14:23:30 +00:00
|
|
|
multiTransactionID, err := tm.insertMultiTransactionAndNotify(tm.db, multiTransaction, chainIDs)
|
2023-06-21 14:09:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
multiTransaction.ID = uint(multiTransactionID)
|
2023-09-29 17:56:27 +00:00
|
|
|
if password == "" {
|
|
|
|
acc, err := tm.accountsDB.GetAccountByAddress(types.Address(multiTransaction.FromAddress))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
kp, err := tm.accountsDB.GetKeypairByKeyUID(acc.KeyUID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !kp.MigratedToKeycard() {
|
|
|
|
return nil, fmt.Errorf("account being used is not migrated to a keycard, password is required")
|
|
|
|
}
|
|
|
|
|
|
|
|
tm.multiTransactionForKeycardSigning = multiTransaction
|
|
|
|
tm.transactionsBridgeData = data
|
|
|
|
hashes, err := tm.buildTransactions(bridges)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
signal.SendTransactionsForSigningEvent(hashes)
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2023-06-21 14:09:55 +00:00
|
|
|
hashes, err := tm.sendTransactions(multiTransaction, data, bridges, password)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = tm.storePendingTransactions(multiTransaction, hashes, data)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &MultiTransactionCommandResult{
|
|
|
|
ID: int64(multiTransactionID),
|
|
|
|
Hashes: hashes,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2023-09-29 17:56:27 +00:00
|
|
|
func (tm *TransactionManager) ProceedWithTransactionsSignatures(ctx context.Context, signatures map[string]SignatureDetails) (*MultiTransactionCommandResult, error) {
|
|
|
|
if tm.multiTransactionForKeycardSigning == nil {
|
|
|
|
return nil, errors.New("no multi transaction to proceed with")
|
|
|
|
}
|
|
|
|
if len(tm.transactionsBridgeData) == 0 {
|
|
|
|
return nil, errors.New("no transactions bridge data to proceed with")
|
|
|
|
}
|
|
|
|
if len(tm.transactionsForKeycardSingning) == 0 {
|
|
|
|
return nil, errors.New("no transactions to proceed with")
|
|
|
|
}
|
|
|
|
if len(signatures) != len(tm.transactionsForKeycardSingning) {
|
|
|
|
return nil, errors.New("not all transactions have been signed")
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if all transactions have been signed
|
|
|
|
for hash, desc := range tm.transactionsForKeycardSingning {
|
|
|
|
sigDetails, ok := signatures[hash.String()]
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("missing signature for transaction %s", hash)
|
|
|
|
}
|
|
|
|
|
|
|
|
rBytes, _ := hex.DecodeString(sigDetails.R)
|
|
|
|
sBytes, _ := hex.DecodeString(sigDetails.S)
|
|
|
|
vByte := byte(0)
|
2023-11-06 09:26:02 +00:00
|
|
|
if sigDetails.V == "01" {
|
2023-09-29 17:56:27 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// send transactions
|
|
|
|
hashes := make(map[uint64][]types.Hash)
|
|
|
|
for _, desc := range tm.transactionsForKeycardSingning {
|
|
|
|
hash, err := tm.transactor.SendBuiltTransactionWithSignature(desc.chainID, desc.builtTx, desc.signature)
|
2023-11-06 09:26:02 +00:00
|
|
|
defer func() {
|
|
|
|
desc.unlock(err == nil, desc.builtTx.Nonce())
|
|
|
|
}()
|
2023-09-29 17:56:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
hashes[desc.chainID] = append(hashes[desc.chainID], hash)
|
|
|
|
}
|
|
|
|
|
|
|
|
err := tm.storePendingTransactions(tm.multiTransactionForKeycardSigning, hashes, tm.transactionsBridgeData)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &MultiTransactionCommandResult{
|
|
|
|
ID: int64(tm.multiTransactionForKeycardSigning.ID),
|
|
|
|
Hashes: hashes,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2023-06-21 14:09:55 +00:00
|
|
|
func (tm *TransactionManager) storePendingTransactions(multiTransaction *MultiTransaction,
|
|
|
|
hashes map[uint64][]types.Hash, data []*bridge.TransactionBridge) error {
|
|
|
|
|
|
|
|
txs := createPendingTransactions(hashes, data, multiTransaction)
|
|
|
|
for _, tx := range txs {
|
2023-08-01 18:50:30 +00:00
|
|
|
err := tm.pendingTracker.StoreAndTrackPendingTx(tx)
|
2023-06-21 14:09:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func createPendingTransactions(hashes map[uint64][]types.Hash, data []*bridge.TransactionBridge,
|
|
|
|
multiTransaction *MultiTransaction) []*transactions.PendingTransaction {
|
|
|
|
|
|
|
|
txs := make([]*transactions.PendingTransaction, 0)
|
|
|
|
for _, tx := range data {
|
|
|
|
for _, hash := range hashes[tx.ChainID] {
|
|
|
|
pendingTransaction := &transactions.PendingTransaction{
|
|
|
|
Hash: common.Hash(hash),
|
|
|
|
Timestamp: uint64(time.Now().Unix()),
|
|
|
|
Value: bigint.BigInt{Int: multiTransaction.FromAmount.ToInt()},
|
|
|
|
From: common.Address(tx.From()),
|
|
|
|
To: common.Address(tx.To()),
|
|
|
|
Data: tx.Data().String(),
|
|
|
|
Type: transactions.WalletTransfer,
|
2023-08-01 18:50:30 +00:00
|
|
|
ChainID: wallet_common.ChainID(tx.ChainID),
|
2023-06-21 14:09:55 +00:00
|
|
|
MultiTransactionID: int64(multiTransaction.ID),
|
|
|
|
Symbol: multiTransaction.FromAsset,
|
2023-08-01 18:50:30 +00:00
|
|
|
AutoDelete: new(bool),
|
2023-06-21 14:09:55 +00:00
|
|
|
}
|
2023-08-01 18:50:30 +00:00
|
|
|
// Transaction downloader will delete pending transaction as soon as it is confirmed
|
|
|
|
*pendingTransaction.AutoDelete = false
|
2023-06-21 14:09:55 +00:00
|
|
|
txs = append(txs, pendingTransaction)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return txs
|
|
|
|
}
|
|
|
|
|
|
|
|
func multiTransactionFromCommand(command *MultiTransactionCommand) *MultiTransaction {
|
|
|
|
|
|
|
|
log.Info("Creating multi transaction", "command", command)
|
|
|
|
|
2023-06-12 10:49:32 +00:00
|
|
|
multiTransaction := &MultiTransaction{
|
|
|
|
FromAddress: command.FromAddress,
|
|
|
|
ToAddress: command.ToAddress,
|
|
|
|
FromAsset: command.FromAsset,
|
|
|
|
ToAsset: command.ToAsset,
|
|
|
|
FromAmount: command.FromAmount,
|
2023-06-14 07:32:55 +00:00
|
|
|
ToAmount: new(hexutil.Big),
|
2023-06-12 10:49:32 +00:00
|
|
|
Type: command.Type,
|
|
|
|
}
|
|
|
|
|
2023-06-21 14:09:55 +00:00
|
|
|
return multiTransaction
|
|
|
|
}
|
2023-03-01 19:35:19 +00:00
|
|
|
|
2023-09-29 17:56:27 +00:00
|
|
|
func (tm *TransactionManager) buildTransactions(bridges map[string]bridge.Bridge) ([]string, error) {
|
|
|
|
tm.transactionsForKeycardSingning = make(map[common.Hash]*TransactionDescription)
|
|
|
|
var hashes []string
|
|
|
|
for _, bridgeTx := range tm.transactionsBridgeData {
|
2023-11-06 09:26:02 +00:00
|
|
|
builtTx, unlock, err := bridges[bridgeTx.BridgeName].BuildTransaction(bridgeTx)
|
2023-09-29 17:56:27 +00:00
|
|
|
if err != nil {
|
2023-11-06 09:26:02 +00:00
|
|
|
if unlock != nil {
|
|
|
|
unlock(false, 0) // unlock nonce in case of an error, otherwise keep it locked, until the transaction is sent
|
|
|
|
}
|
2023-09-29 17:56:27 +00:00
|
|
|
return hashes, err
|
|
|
|
}
|
|
|
|
|
|
|
|
signer := ethTypes.NewLondonSigner(big.NewInt(int64(bridgeTx.ChainID)))
|
|
|
|
txHash := signer.Hash(builtTx)
|
|
|
|
|
|
|
|
tm.transactionsForKeycardSingning[txHash] = &TransactionDescription{
|
|
|
|
chainID: bridgeTx.ChainID,
|
|
|
|
builtTx: builtTx,
|
2023-11-06 09:26:02 +00:00
|
|
|
unlock: unlock,
|
2023-09-29 17:56:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
hashes = append(hashes, txHash.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
return hashes, nil
|
|
|
|
}
|
|
|
|
|
2023-06-21 14:09:55 +00:00
|
|
|
func (tm *TransactionManager) sendTransactions(multiTransaction *MultiTransaction,
|
|
|
|
data []*bridge.TransactionBridge, bridges map[string]bridge.Bridge, password string) (
|
|
|
|
map[uint64][]types.Hash, error) {
|
|
|
|
|
|
|
|
log.Info("Making transactions", "multiTransaction", multiTransaction)
|
|
|
|
|
|
|
|
selectedAccount, err := tm.getVerifiedWalletAccount(multiTransaction.FromAddress.Hex(), password)
|
2022-07-15 08:53:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
hashes := make(map[uint64][]types.Hash)
|
2022-09-13 07:10:59 +00:00
|
|
|
for _, tx := range data {
|
|
|
|
hash, err := bridges[tx.BridgeName].Send(tx, selectedAccount)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
hashes[tx.ChainID] = append(hashes[tx.ChainID], hash)
|
2022-07-15 08:53:56 +00:00
|
|
|
}
|
2023-06-21 14:09:55 +00:00
|
|
|
return hashes, nil
|
2022-07-15 08:53:56 +00:00
|
|
|
}
|
|
|
|
|
2023-03-01 19:35:19 +00:00
|
|
|
func (tm *TransactionManager) GetMultiTransactions(ctx context.Context, ids []MultiTransactionIDType) ([]*MultiTransaction, error) {
|
|
|
|
placeholders := make([]string, len(ids))
|
|
|
|
args := make([]interface{}, len(ids))
|
|
|
|
for i, v := range ids {
|
|
|
|
placeholders[i] = "?"
|
|
|
|
args[i] = v
|
|
|
|
}
|
|
|
|
|
|
|
|
stmt, err := tm.db.Prepare(fmt.Sprintf(`SELECT rowid, %s
|
|
|
|
FROM multi_transactions
|
|
|
|
WHERE rowid in (%s)`,
|
2023-07-11 21:40:58 +00:00
|
|
|
selectMultiTransactionColumns,
|
2023-03-01 19:35:19 +00:00
|
|
|
strings.Join(placeholders, ",")))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer stmt.Close()
|
|
|
|
|
|
|
|
rows, err := stmt.Query(args...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
2023-06-13 14:20:48 +00:00
|
|
|
return rowsToMultiTransactions(rows)
|
|
|
|
}
|
2023-03-01 19:35:19 +00:00
|
|
|
|
2023-06-13 14:20:48 +00:00
|
|
|
func (tm *TransactionManager) getBridgeMultiTransactions(ctx context.Context, toChainID uint64, crossTxID string) ([]*MultiTransaction, error) {
|
|
|
|
stmt, err := tm.db.Prepare(fmt.Sprintf(`SELECT rowid, %s
|
|
|
|
FROM multi_transactions
|
|
|
|
WHERE type=? AND to_network_id=? AND cross_tx_id=?`,
|
|
|
|
multiTransactionColumns))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer stmt.Close()
|
2023-03-01 19:35:19 +00:00
|
|
|
|
2023-06-13 14:20:48 +00:00
|
|
|
rows, err := stmt.Query(MultiTransactionBridge, toChainID, crossTxID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
return rowsToMultiTransactions(rows)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tm *TransactionManager) GetBridgeOriginMultiTransaction(ctx context.Context, toChainID uint64, crossTxID string) (*MultiTransaction, error) {
|
|
|
|
multiTxs, err := tm.getBridgeMultiTransactions(ctx, toChainID, crossTxID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, multiTx := range multiTxs {
|
|
|
|
// Origin MultiTxs will have a missing "ToTxHash"
|
|
|
|
if multiTx.ToTxHash == emptyHash {
|
|
|
|
return multiTx, nil
|
2023-06-02 20:08:45 +00:00
|
|
|
}
|
2023-06-13 14:20:48 +00:00
|
|
|
}
|
2023-06-02 20:08:45 +00:00
|
|
|
|
2023-06-13 14:20:48 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tm *TransactionManager) GetBridgeDestinationMultiTransaction(ctx context.Context, toChainID uint64, crossTxID string) (*MultiTransaction, error) {
|
|
|
|
multiTxs, err := tm.getBridgeMultiTransactions(ctx, toChainID, crossTxID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-03-01 19:35:19 +00:00
|
|
|
}
|
|
|
|
|
2023-06-13 14:20:48 +00:00
|
|
|
for _, multiTx := range multiTxs {
|
|
|
|
// Destination MultiTxs will have a missing "FromTxHash"
|
|
|
|
if multiTx.FromTxHash == emptyHash {
|
|
|
|
return multiTx, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
2023-03-01 19:35:19 +00:00
|
|
|
}
|
|
|
|
|
2022-07-15 08:53:56 +00:00
|
|
|
func (tm *TransactionManager) getVerifiedWalletAccount(address, password string) (*account.SelectedExtKey, error) {
|
|
|
|
exists, err := tm.accountsDB.AddressExists(types.HexToAddress(address))
|
|
|
|
if err != nil {
|
|
|
|
log.Error("failed to query db for a given address", "address", address, "error", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !exists {
|
|
|
|
log.Error("failed to get a selected account", "err", transactions.ErrInvalidTxSender)
|
|
|
|
return nil, transactions.ErrAccountDoesntExist
|
|
|
|
}
|
|
|
|
|
|
|
|
key, err := tm.gethManager.VerifyAccountPassword(tm.config.KeyStoreDir, address, password)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("failed to verify account", "account", address, "error", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &account.SelectedExtKey{
|
|
|
|
Address: key.Address,
|
|
|
|
AccountKey: key,
|
|
|
|
}, nil
|
|
|
|
}
|