status-go/services/wallet/routeexecution/manager.go

235 lines
7.8 KiB
Go
Raw Normal View History

package routeexecution
import (
"context"
"database/sql"
"time"
"go.uber.org/zap"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/logutils"
status_common "github.com/status-im/status-go/common"
statusErrors "github.com/status-im/status-go/errors"
"github.com/status-im/status-go/services/wallet/requests"
"github.com/status-im/status-go/services/wallet/responses"
"github.com/status-im/status-go/services/wallet/routeexecution/storage"
"github.com/status-im/status-go/services/wallet/router"
pathProcessorCommon "github.com/status-im/status-go/services/wallet/router/pathprocessor/common"
"github.com/status-im/status-go/services/wallet/router/sendtype"
"github.com/status-im/status-go/services/wallet/transfer"
"github.com/status-im/status-go/services/wallet/walletevent"
"github.com/status-im/status-go/services/wallet/wallettypes"
"github.com/status-im/status-go/signal"
)
const (
EventRouteExecutionTransactionSent walletevent.EventType = walletevent.InternalEventTypePrefix + "wallet-route-execution-transaction-sent"
)
type Manager struct {
router *router.Router
transactionManager *transfer.TransactionManager
transferController *transfer.Controller
db *storage.DB
eventFeed *event.Feed
// Local data used for storage purposes
buildInputParams *requests.RouterBuildTransactionsParams
}
func NewManager(walletDB *sql.DB, eventFeed *event.Feed, router *router.Router, transactionManager *transfer.TransactionManager, transferController *transfer.Controller) *Manager {
return &Manager{
router: router,
transactionManager: transactionManager,
transferController: transferController,
db: storage.NewDB(walletDB),
eventFeed: eventFeed,
}
}
func (m *Manager) clearLocalRouteData() {
m.buildInputParams = nil
m.transactionManager.ClearLocalRouterTransactionsData()
}
func (m *Manager) BuildTransactionsFromRoute(ctx context.Context, buildInputParams *requests.RouterBuildTransactionsParams) {
go func() {
defer status_common.LogOnPanic()
m.router.StopSuggestedRoutesAsyncCalculation()
var err error
response := &responses.RouterTransactionsForSigning{
SendDetails: &responses.SendDetails{
Uuid: buildInputParams.Uuid,
},
}
defer func() {
if err != nil {
m.clearLocalRouteData()
err = statusErrors.CreateErrorResponseFromError(err)
response.SendDetails.ErrorResponse = err.(*statusErrors.ErrorResponse)
}
signal.SendWalletEvent(signal.SignRouterTransactions, response)
}()
route, routeInputParams := m.router.GetBestRouteAndAssociatedInputParams()
if routeInputParams.Uuid != buildInputParams.Uuid {
// should never be here
err = ErrCannotResolveRouteId
return
}
m.buildInputParams = buildInputParams
response.SendDetails.UpdateFields(routeInputParams)
// notify client that sending transactions started (has 3 steps, building txs, signing txs, sending txs)
signal.SendWalletEvent(signal.RouterSendingTransactionsStarted, response.SendDetails)
response.SigningDetails, err = m.transactionManager.BuildTransactionsFromRoute(
route,
m.router.GetPathProcessors(),
transfer.BuildRouteExtraParams{
AddressFrom: routeInputParams.AddrFrom,
AddressTo: routeInputParams.AddrTo,
Username: routeInputParams.Username,
PublicKey: routeInputParams.PublicKey,
PackID: routeInputParams.PackID.ToInt(),
SlippagePercentage: buildInputParams.SlippagePercentage,
},
)
}()
}
func (m *Manager) SendRouterTransactionsWithSignatures(ctx context.Context, sendInputParams *requests.RouterSendTransactionsParams) {
go func() {
defer status_common.LogOnPanic()
var (
err error
routeInputParams requests.RouteInputParams
)
response := &responses.RouterSentTransactions{
SendDetails: &responses.SendDetails{
Uuid: sendInputParams.Uuid,
},
}
defer func() {
clearLocalData := true
if routeInputParams.SendType == sendtype.Swap {
// in case of swap don't clear local data if an approval is placed, but swap tx is not sent yet
if m.transactionManager.ApprovalRequiredForPath(pathProcessorCommon.ProcessorSwapParaswapName) &&
m.transactionManager.ApprovalPlacedForPath(pathProcessorCommon.ProcessorSwapParaswapName) &&
!m.transactionManager.TxPlacedForPath(pathProcessorCommon.ProcessorSwapParaswapName) {
clearLocalData = false
}
}
if clearLocalData {
m.clearLocalRouteData()
}
if err != nil {
err = statusErrors.CreateErrorResponseFromError(err)
response.SendDetails.ErrorResponse = err.(*statusErrors.ErrorResponse)
}
signal.SendWalletEvent(signal.RouterTransactionsSent, response)
event := walletevent.Event{
Type: EventRouteExecutionTransactionSent,
EventParams: response,
}
m.eventFeed.Send(event)
}()
_, routeInputParams = m.router.GetBestRouteAndAssociatedInputParams()
if routeInputParams.Uuid != sendInputParams.Uuid {
err = ErrCannotResolveRouteId
return
}
response.SendDetails.UpdateFields(routeInputParams)
err = m.transactionManager.ValidateAndAddSignaturesToRouterTransactions(sendInputParams.Signatures)
if err != nil {
return
}
//////////////////////////////////////////////////////////////////////////////
// prepare multitx
var mtType transfer.MultiTransactionType = transfer.MultiTransactionSend
if routeInputParams.SendType == sendtype.Bridge {
mtType = transfer.MultiTransactionBridge
} else if routeInputParams.SendType == sendtype.Swap {
mtType = transfer.MultiTransactionSwap
}
multiTx := transfer.NewMultiTransaction(
/* Timestamp: */ uint64(time.Now().Unix()),
/* FromNetworkID: */ 0,
/* ToNetworkID: */ 0,
/* FromTxHash: */ common.Hash{},
/* ToTxHash: */ common.Hash{},
/* FromAddress: */ routeInputParams.AddrFrom,
/* ToAddress: */ routeInputParams.AddrTo,
/* FromAsset: */ routeInputParams.TokenID,
/* ToAsset: */ routeInputParams.ToTokenID,
/* FromAmount: */ routeInputParams.AmountIn,
/* ToAmount: */ routeInputParams.AmountOut,
/* Type: */ mtType,
/* CrossTxID: */ "",
)
_, err = m.transactionManager.InsertMultiTransaction(multiTx)
if err != nil {
return
}
//////////////////////////////////////////////////////////////////////////////
response.SentTransactions, err = m.transactionManager.SendRouterTransactions(ctx, multiTx)
if err != nil {
log.Error("Error sending router transactions", "error", err)
// TODO #16556: Handle partially successful Tx sends?
// Don't return, store whichever transactions were successfully sent
}
// don't overwrite err since we want to process it in the deferred function
var tmpErr error
routerTransactions := m.transactionManager.GetRouterTransactions()
routeData := wallettypes.NewRouteData(&routeInputParams, m.buildInputParams, routerTransactions)
tmpErr = m.db.PutRouteData(routeData)
if tmpErr != nil {
log.Error("Error storing route data", "error", tmpErr)
}
var (
chainIDs []uint64
addresses []common.Address
)
for _, tx := range response.SentTransactions {
chainIDs = append(chainIDs, tx.FromChain)
addresses = append(addresses, common.Address(tx.FromAddress))
go func(chainId uint64, txHash common.Hash) {
defer status_common.LogOnPanic()
tmpErr = m.transactionManager.WatchTransaction(context.Background(), chainId, txHash)
if tmpErr != nil {
logutils.ZapLogger().Error("Error watching transaction", zap.Error(tmpErr))
return
}
}(tx.FromChain, common.Hash(tx.Hash))
}
tmpErr = m.transferController.CheckRecentHistory(chainIDs, addresses)
if tmpErr != nil {
logutils.ZapLogger().Error("Error checking recent history", zap.Error(tmpErr))
}
}()
}