status-go/services/wallet/transaction.go

199 lines
5.8 KiB
Go

package wallet
import (
"context"
"database/sql"
"errors"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/services/wallet/async"
"github.com/status-im/status-go/services/wallet/bigint"
"github.com/status-im/status-go/services/wallet/chain"
)
type TransactionManager struct {
db *sql.DB
}
type PendingTrxType string
const (
RegisterENS PendingTrxType = "RegisterENS"
ReleaseENS PendingTrxType = "ReleaseENS"
SetPubKey PendingTrxType = "SetPubKey"
BuyStickerPack PendingTrxType = "BuyStickerPack"
WalletTransfer PendingTrxType = "WalletTransfer"
)
type PendingTransaction struct {
Hash common.Hash `json:"hash"`
Timestamp uint64 `json:"timestamp"`
Value bigint.BigInt `json:"value"`
From common.Address `json:"from"`
To common.Address `json:"to"`
Data string `json:"data"`
Symbol string `json:"symbol"`
GasPrice bigint.BigInt `json:"gasPrice"`
GasLimit bigint.BigInt `json:"gasLimit"`
Type PendingTrxType `json:"type"`
AdditionalData string `json:"additionalData"`
ChainID uint64 `json:"network_id"`
}
func (tm *TransactionManager) getAllPendings(chainID uint64) ([]*PendingTransaction, error) {
rows, err := tm.db.Query(`SELECT hash, timestamp, value, from_address, to_address, data,
symbol, gas_price, gas_limit, type, additional_data,
network_id
FROM pending_transactions
WHERE network_id = ?`, chainID)
if err != nil {
return nil, err
}
defer rows.Close()
var transactions []*PendingTransaction
for rows.Next() {
transaction := &PendingTransaction{
Value: bigint.BigInt{Int: new(big.Int)},
GasPrice: bigint.BigInt{Int: new(big.Int)},
GasLimit: bigint.BigInt{Int: new(big.Int)},
}
err := rows.Scan(&transaction.Hash,
&transaction.Timestamp,
(*bigint.SQLBigIntBytes)(transaction.Value.Int),
&transaction.From,
&transaction.To,
&transaction.Data,
&transaction.Symbol,
(*bigint.SQLBigIntBytes)(transaction.GasPrice.Int),
(*bigint.SQLBigIntBytes)(transaction.GasLimit.Int),
&transaction.Type,
&transaction.AdditionalData,
&transaction.ChainID,
)
if err != nil {
return nil, err
}
transactions = append(transactions, transaction)
}
return transactions, nil
}
func (tm *TransactionManager) getPendingByAddress(chainID uint64, address common.Address) ([]*PendingTransaction, error) {
rows, err := tm.db.Query(`SELECT hash, timestamp, value, from_address, to_address, data,
symbol, gas_price, gas_limit, type, additional_data,
network_id
FROM pending_transactions
WHERE network_id = ? AND from_address = ?`, chainID, address)
if err != nil {
return nil, err
}
defer rows.Close()
var transactions []*PendingTransaction
for rows.Next() {
transaction := &PendingTransaction{
Value: bigint.BigInt{Int: new(big.Int)},
GasPrice: bigint.BigInt{Int: new(big.Int)},
GasLimit: bigint.BigInt{Int: new(big.Int)},
}
err := rows.Scan(&transaction.Hash,
&transaction.Timestamp,
(*bigint.SQLBigIntBytes)(transaction.Value.Int),
&transaction.From,
&transaction.To,
&transaction.Data,
&transaction.Symbol,
(*bigint.SQLBigIntBytes)(transaction.GasPrice.Int),
(*bigint.SQLBigIntBytes)(transaction.GasLimit.Int),
&transaction.Type,
&transaction.AdditionalData,
&transaction.ChainID,
)
if err != nil {
return nil, err
}
transactions = append(transactions, transaction)
}
return transactions, nil
}
func (tm *TransactionManager) addPending(transaction PendingTransaction) error {
insert, err := tm.db.Prepare(`INSERT OR REPLACE INTO pending_transactions
(network_id, hash, timestamp, value, from_address, to_address,
data, symbol, gas_price, gas_limit, type, additional_data)
VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
if err != nil {
return err
}
_, err = insert.Exec(
transaction.ChainID,
transaction.Hash,
transaction.Timestamp,
(*bigint.SQLBigIntBytes)(transaction.Value.Int),
transaction.From,
transaction.To,
transaction.Data,
transaction.Symbol,
(*bigint.SQLBigIntBytes)(transaction.GasPrice.Int),
(*bigint.SQLBigIntBytes)(transaction.GasLimit.Int),
transaction.Type,
transaction.AdditionalData,
)
return err
}
func (tm *TransactionManager) deletePending(chainID uint64, hash common.Hash) error {
_, err := tm.db.Exec(`DELETE FROM pending_transactions WHERE network_id = ? AND hash = ?`, chainID, hash)
return err
}
func (tm *TransactionManager) watch(ctx context.Context, transactionHash common.Hash, client *chain.Client) error {
watchTxCommand := &watchTransactionCommand{
hash: transactionHash,
client: client,
}
commandContext, cancel := context.WithTimeout(ctx, 10*time.Minute)
defer cancel()
return watchTxCommand.Command()(commandContext)
}
type watchTransactionCommand struct {
client *chain.Client
hash common.Hash
}
func (c *watchTransactionCommand) Command() async.Command {
return async.FiniteCommand{
Interval: 10 * time.Second,
Runable: c.Run,
}.Run
}
func (c *watchTransactionCommand) Run(ctx context.Context) error {
requestContext, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
_, isPending, err := c.client.TransactionByHash(requestContext, c.hash)
if err != nil {
log.Error("Watching transaction error", "error", err)
return err
}
if isPending {
return errors.New("transaction is pending")
}
return nil
}