feat: added functionality to ask a client to sign a transaction and an endpoint to continue sending using provided signature

This functionality is needed in case the user wants to send a transaction and
signs it using the signature provided by the keycard (or any other compatible way).
This commit is contained in:
Sale Djenic 2023-09-29 19:56:27 +02:00 committed by saledjenic
parent abac55c778
commit b348cca15c
18 changed files with 300 additions and 90 deletions

View File

@ -1897,7 +1897,7 @@ func (b *GethStatusBackend) SendTransactionWithChainID(chainID uint64, sendArgs
} }
func (b *GethStatusBackend) SendTransactionWithSignature(sendArgs transactions.SendTxArgs, sig []byte) (hash types.Hash, err error) { func (b *GethStatusBackend) SendTransactionWithSignature(sendArgs transactions.SendTxArgs, sig []byte) (hash types.Hash, err error) {
hash, err = b.transactor.SendTransactionWithSignature(sendArgs, sig) hash, err = b.transactor.SendTransactionWithSignature(b.transactor.NetworkID(), sendArgs, sig)
if err != nil { if err != nil {
return return
} }

2
go.mod
View File

@ -2,7 +2,7 @@ module github.com/status-im/status-go
go 1.19 go 1.19
replace github.com/ethereum/go-ethereum v1.10.26 => github.com/status-im/go-ethereum v1.10.25-status.7 replace github.com/ethereum/go-ethereum v1.10.26 => github.com/status-im/go-ethereum v1.10.25-status.9
replace github.com/docker/docker => github.com/docker/engine v1.4.2-0.20190717161051-705d9623b7c1 replace github.com/docker/docker => github.com/docker/engine v1.4.2-0.20190717161051-705d9623b7c1

4
go.sum
View File

@ -1982,8 +1982,8 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/status-im/doubleratchet v3.0.0+incompatible h1:aJ1ejcSERpSzmWZBgtfYtiU2nF0Q8ZkGyuEPYETXkCY= github.com/status-im/doubleratchet v3.0.0+incompatible h1:aJ1ejcSERpSzmWZBgtfYtiU2nF0Q8ZkGyuEPYETXkCY=
github.com/status-im/doubleratchet v3.0.0+incompatible/go.mod h1:1sqR0+yhiM/bd+wrdX79AOt2csZuJOni0nUDzKNuqOU= github.com/status-im/doubleratchet v3.0.0+incompatible/go.mod h1:1sqR0+yhiM/bd+wrdX79AOt2csZuJOni0nUDzKNuqOU=
github.com/status-im/go-ethereum v1.10.25-status.7 h1:egCCdvUQSdfnnDcv2vP1RCuIDr184eYlBvyucxuKJj8= github.com/status-im/go-ethereum v1.10.25-status.9 h1:NDuRs5TC4JjqPcYE8/sUtspdA+OwV1JRy3bbRLdIcL0=
github.com/status-im/go-ethereum v1.10.25-status.7/go.mod h1:Dt4K5JYMhJRdtXJwBEyGZLZn9iz/chSOZyjVmt5ZhwQ= github.com/status-im/go-ethereum v1.10.25-status.9/go.mod h1:Dt4K5JYMhJRdtXJwBEyGZLZn9iz/chSOZyjVmt5ZhwQ=
github.com/status-im/go-multiaddr-ethv4 v1.2.5 h1:pN+ey6wYKbvNNu5/xq9+VL0N8Yq0pZUTbZp0URg+Yn4= github.com/status-im/go-multiaddr-ethv4 v1.2.5 h1:pN+ey6wYKbvNNu5/xq9+VL0N8Yq0pZUTbZp0URg+Yn4=
github.com/status-im/go-multiaddr-ethv4 v1.2.5/go.mod h1:Fhe/18yWU5QwlAYiOO3Bb1BLe0bn5YobcNBHsjRr4kk= github.com/status-im/go-multiaddr-ethv4 v1.2.5/go.mod h1:Fhe/18yWU5QwlAYiOO3Bb1BLe0bn5YobcNBHsjRr4kk=
github.com/status-im/go-sqlcipher/v4 v4.5.4-status.2 h1:Oi9JTAI2DZEe5UKlpUcvKBCCSn3ULsLIrix7jPnEoPE= github.com/status-im/go-sqlcipher/v4 v4.5.4-status.2 h1:Oi9JTAI2DZEe5UKlpUcvKBCCSn3ULsLIrix7jPnEoPE=

View File

@ -553,6 +553,11 @@ func (api *API) CreateMultiTransaction(ctx context.Context, multiTransactionComm
return api.s.transactionManager.CreateMultiTransactionFromCommand(ctx, multiTransactionCommand, data, api.router.bridges, password) return api.s.transactionManager.CreateMultiTransactionFromCommand(ctx, multiTransactionCommand, data, api.router.bridges, password)
} }
func (api *API) ProceedWithTransactionsSignatures(ctx context.Context, signatures map[string]transfer.SignatureDetails) (*transfer.MultiTransactionCommandResult, error) {
log.Debug("[WalletAPI:: ProceedWithTransactionsSignatures] sign with signatures and send multi transaction")
return api.s.transactionManager.ProceedWithTransactionsSignatures(ctx, signatures)
}
func (api *API) GetMultiTransactions(ctx context.Context, transactionIDs []transfer.MultiTransactionIDType) ([]*transfer.MultiTransaction, error) { func (api *API) GetMultiTransactions(ctx context.Context, transactionIDs []transfer.MultiTransactionIDType) ([]*transfer.MultiTransaction, error) {
log.Debug("wallet.api.GetMultiTransactions", "IDs.len", len(transactionIDs)) log.Debug("wallet.api.GetMultiTransactions", "IDs.len", len(transactionIDs))
return api.s.transactionManager.GetMultiTransactions(ctx, transactionIDs) return api.s.transactionManager.GetMultiTransactions(ctx, transactionIDs)

View File

@ -94,4 +94,5 @@ type Bridge interface {
CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error) CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error)
Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (types.Hash, error) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (types.Hash, error)
GetContractAddress(network *params.Network, token *token.Token) *common.Address GetContractAddress(network *params.Network, token *token.Token) *common.Address
BuildTransaction(sendArgs *TransactionBridge) (*ethTypes.Transaction, error)
} }

View File

@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
ethTypes "github.com/ethereum/go-ethereum/core/types" ethTypes "github.com/ethereum/go-ethereum/core/types"
@ -244,33 +245,32 @@ func (s *CBridge) GetContractAddress(network *params.Network, token *token.Token
return nil return nil
} }
func (s *CBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (types.Hash, error) { func (s *CBridge) sendOrBuild(sendArgs *TransactionBridge, signerFn bind.SignerFn) (*ethTypes.Transaction, error) {
fromNetwork := s.rpcClient.NetworkManager.Find(sendArgs.ChainID) fromNetwork := s.rpcClient.NetworkManager.Find(sendArgs.ChainID)
if fromNetwork == nil { if fromNetwork == nil {
return types.HexToHash(""), errors.New("network not found") return nil, errors.New("network not found")
} }
tk := s.tokenManager.FindToken(fromNetwork, sendArgs.CbridgeTx.Symbol) tk := s.tokenManager.FindToken(fromNetwork, sendArgs.CbridgeTx.Symbol)
if tk == nil { if tk == nil {
return types.HexToHash(""), errors.New("token not found") return nil, errors.New("token not found")
} }
addrs := s.GetContractAddress(fromNetwork, nil) addrs := s.GetContractAddress(fromNetwork, nil)
if addrs == nil { if addrs == nil {
return types.HexToHash(""), errors.New("contract not found") return nil, errors.New("contract not found")
} }
backend, err := s.rpcClient.EthClient(sendArgs.ChainID) backend, err := s.rpcClient.EthClient(sendArgs.ChainID)
if err != nil { if err != nil {
return types.HexToHash(""), err return nil, err
} }
contract, err := celer.NewCeler(*addrs, backend) contract, err := celer.NewCeler(*addrs, backend)
if err != nil { if err != nil {
return types.HexToHash(""), err return nil, err
} }
txOpts := sendArgs.CbridgeTx.ToTransactOpts(getSigner(sendArgs.ChainID, sendArgs.CbridgeTx.From, verifiedAccount)) txOpts := sendArgs.CbridgeTx.ToTransactOpts(signerFn)
var tx *ethTypes.Transaction
if tk.IsNative() { if tk.IsNative() {
tx, err = contract.SendNative( return contract.SendNative(
txOpts, txOpts,
sendArgs.CbridgeTx.Recipient, sendArgs.CbridgeTx.Recipient,
(*big.Int)(sendArgs.CbridgeTx.Amount), (*big.Int)(sendArgs.CbridgeTx.Amount),
@ -278,8 +278,9 @@ func (s *CBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.Sel
uint64(time.Now().UnixMilli()), uint64(time.Now().UnixMilli()),
500, 500,
) )
} else { }
tx, err = contract.Send(
return contract.Send(
txOpts, txOpts,
sendArgs.CbridgeTx.Recipient, sendArgs.CbridgeTx.Recipient,
tk.Address, tk.Address,
@ -289,6 +290,9 @@ func (s *CBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.Sel
500, 500,
) )
} }
func (s *CBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (types.Hash, error) {
tx, err := s.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.CbridgeTx.From, verifiedAccount))
if err != nil { if err != nil {
return types.HexToHash(""), err return types.HexToHash(""), err
} }
@ -296,6 +300,10 @@ func (s *CBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.Sel
return types.Hash(tx.Hash()), nil return types.Hash(tx.Hash()), nil
} }
func (s *CBridge) BuildTransaction(sendArgs *TransactionBridge) (*ethTypes.Transaction, error) {
return s.sendOrBuild(sendArgs, nil)
}
func (s *CBridge) CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error) { func (s *CBridge) CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error) {
amt, err := s.estimateAmt(from, to, amountIn, symbol) amt, err := s.estimateAmt(from, to, amountIn, symbol)
if err != nil { if err != nil {

View File

@ -3,8 +3,10 @@ package bridge
import ( import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/status-im/status-go/account" "github.com/status-im/status-go/account"
"github.com/status-im/status-go/contracts/community-tokens/collectibles" "github.com/status-im/status-go/contracts/community-tokens/collectibles"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
@ -74,32 +76,43 @@ func (s *ERC721TransferBridge) EstimateGas(from, to *params.Network, account com
return 80000, nil return 80000, nil
} }
func (s *ERC721TransferBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) { func (s *ERC721TransferBridge) sendOrBuild(sendArgs *TransactionBridge, signerFn bind.SignerFn) (tx *ethTypes.Transaction, err error) {
ethClient, err := s.rpcClient.EthClient(sendArgs.ChainID) ethClient, err := s.rpcClient.EthClient(sendArgs.ChainID)
if err != nil { if err != nil {
return hash, err return tx, err
} }
contract, err := collectibles.NewCollectibles(common.Address(*sendArgs.ERC721TransferTx.To), ethClient) contract, err := collectibles.NewCollectibles(common.Address(*sendArgs.ERC721TransferTx.To), ethClient)
if err != nil { if err != nil {
return hash, err return tx, err
} }
nonce, unlock, err := s.transactor.NextNonce(s.rpcClient, sendArgs.ChainID, sendArgs.ERC721TransferTx.From) nonce, unlock, err := s.transactor.NextNonce(s.rpcClient, sendArgs.ChainID, sendArgs.ERC721TransferTx.From)
if err != nil { if err != nil {
return hash, err return tx, err
} }
defer func() { defer func() {
unlock(err == nil, nonce) unlock(err == nil, nonce)
}() }()
argNonce := hexutil.Uint64(nonce) argNonce := hexutil.Uint64(nonce)
sendArgs.ERC721TransferTx.Nonce = &argNonce sendArgs.ERC721TransferTx.Nonce = &argNonce
txOpts := sendArgs.ERC721TransferTx.ToTransactOpts(getSigner(sendArgs.ChainID, sendArgs.ERC721TransferTx.From, verifiedAccount)) txOpts := sendArgs.ERC721TransferTx.ToTransactOpts(signerFn)
tx, err := contract.SafeTransferFrom(txOpts, common.Address(sendArgs.ERC721TransferTx.From), sendArgs.ERC721TransferTx.Recipient, sendArgs.ERC721TransferTx.TokenID.ToInt()) return contract.SafeTransferFrom(txOpts, common.Address(sendArgs.ERC721TransferTx.From), sendArgs.ERC721TransferTx.Recipient,
sendArgs.ERC721TransferTx.TokenID.ToInt())
}
func (s *ERC721TransferBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) {
tx, err := s.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.ERC721TransferTx.From, verifiedAccount))
if err != nil { if err != nil {
return hash, err return hash, err
} }
return types.Hash(tx.Hash()), nil return types.Hash(tx.Hash()), nil
} }
func (s *ERC721TransferBridge) BuildTransaction(sendArgs *TransactionBridge) (*ethTypes.Transaction, error) {
return s.sendOrBuild(sendArgs, nil)
}
func (s *ERC721TransferBridge) CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error) { func (s *ERC721TransferBridge) CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error) {
return amountIn, nil return amountIn, nil
} }

View File

@ -9,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/status-im/status-go/account" "github.com/status-im/status-go/account"
"github.com/status-im/status-go/contracts" "github.com/status-im/status-go/contracts"
"github.com/status-im/status-go/contracts/hop" "github.com/status-im/status-go/contracts/hop"
@ -173,15 +174,15 @@ func (h *HopBridge) GetContractAddress(network *params.Network, token *token.Tok
return &address return &address
} }
func (h *HopBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) { func (h *HopBridge) sendOrBuild(sendArgs *TransactionBridge, signerFn bind.SignerFn) (tx *ethTypes.Transaction, err error) {
fromNetwork := h.contractMaker.RPCClient.NetworkManager.Find(sendArgs.ChainID) fromNetwork := h.contractMaker.RPCClient.NetworkManager.Find(sendArgs.ChainID)
if fromNetwork == nil { if fromNetwork == nil {
return hash, err return tx, err
} }
nonce, unlock, err := h.transactor.NextNonce(h.contractMaker.RPCClient, sendArgs.ChainID, sendArgs.HopTx.From) nonce, unlock, err := h.transactor.NextNonce(h.contractMaker.RPCClient, sendArgs.ChainID, sendArgs.HopTx.From)
if err != nil { if err != nil {
return hash, err return tx, err
} }
defer func() { defer func() {
unlock(err == nil, nonce) unlock(err == nil, nonce)
@ -191,25 +192,37 @@ func (h *HopBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.S
token := h.tokenManager.FindToken(fromNetwork, sendArgs.HopTx.Symbol) token := h.tokenManager.FindToken(fromNetwork, sendArgs.HopTx.Symbol)
if fromNetwork.Layer == 1 { if fromNetwork.Layer == 1 {
hash, err = h.sendToL2(sendArgs.ChainID, sendArgs.HopTx, verifiedAccount, token) tx, err = h.sendToL2(sendArgs.ChainID, sendArgs.HopTx, signerFn, token)
return hash, err return tx, err
} }
hash, err = h.swapAndSend(sendArgs.ChainID, sendArgs.HopTx, verifiedAccount, token) tx, err = h.swapAndSend(sendArgs.ChainID, sendArgs.HopTx, signerFn, token)
return hash, err return tx, err
} }
func (h *HopBridge) sendToL2(chainID uint64, hopArgs *HopTxArgs, verifiedAccount *account.SelectedExtKey, token *token.Token) (hash types.Hash, err error) { func (h *HopBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) {
tx, err := h.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.HopTx.From, verifiedAccount))
if err != nil {
return types.Hash{}, err
}
return types.Hash(tx.Hash()), nil
}
func (h *HopBridge) BuildTransaction(sendArgs *TransactionBridge) (*ethTypes.Transaction, error) {
return h.sendOrBuild(sendArgs, nil)
}
func (h *HopBridge) sendToL2(chainID uint64, hopArgs *HopTxArgs, signerFn bind.SignerFn, token *token.Token) (tx *ethTypes.Transaction, err error) {
bridge, err := h.contractMaker.NewHopL1Bridge(chainID, hopArgs.Symbol) bridge, err := h.contractMaker.NewHopL1Bridge(chainID, hopArgs.Symbol)
if err != nil { if err != nil {
return hash, err return tx, err
} }
txOpts := hopArgs.ToTransactOpts(getSigner(chainID, hopArgs.From, verifiedAccount)) txOpts := hopArgs.ToTransactOpts(signerFn)
if token.IsNative() { if token.IsNative() {
txOpts.Value = (*big.Int)(hopArgs.Amount) txOpts.Value = (*big.Int)(hopArgs.Amount)
} }
now := time.Now() now := time.Now()
deadline := big.NewInt(now.Unix() + 604800) deadline := big.NewInt(now.Unix() + 604800)
tx, err := bridge.SendToL2( tx, err = bridge.SendToL2(
txOpts, txOpts,
big.NewInt(int64(hopArgs.ChainID)), big.NewInt(int64(hopArgs.ChainID)),
hopArgs.Recipient, hopArgs.Recipient,
@ -220,25 +233,22 @@ func (h *HopBridge) sendToL2(chainID uint64, hopArgs *HopTxArgs, verifiedAccount
big.NewInt(0), big.NewInt(0),
) )
if err != nil { return tx, err
return hash, err
}
return types.Hash(tx.Hash()), nil
} }
func (h *HopBridge) swapAndSend(chainID uint64, hopArgs *HopTxArgs, verifiedAccount *account.SelectedExtKey, token *token.Token) (hash types.Hash, err error) { func (h *HopBridge) swapAndSend(chainID uint64, hopArgs *HopTxArgs, signerFn bind.SignerFn, token *token.Token) (tx *ethTypes.Transaction, err error) {
ammWrapper, err := h.contractMaker.NewHopL2AmmWrapper(chainID, hopArgs.Symbol) ammWrapper, err := h.contractMaker.NewHopL2AmmWrapper(chainID, hopArgs.Symbol)
if err != nil { if err != nil {
return hash, err return tx, err
} }
txOpts := hopArgs.ToTransactOpts(getSigner(chainID, hopArgs.From, verifiedAccount)) txOpts := hopArgs.ToTransactOpts(signerFn)
if token.IsNative() { if token.IsNative() {
txOpts.Value = (*big.Int)(hopArgs.Amount) txOpts.Value = (*big.Int)(hopArgs.Amount)
} }
now := time.Now() now := time.Now()
deadline := big.NewInt(now.Unix() + 604800) deadline := big.NewInt(now.Unix() + 604800)
tx, err := ammWrapper.SwapAndSend( tx, err = ammWrapper.SwapAndSend(
txOpts, txOpts,
big.NewInt(int64(hopArgs.ChainID)), big.NewInt(int64(hopArgs.ChainID)),
hopArgs.Recipient, hopArgs.Recipient,
@ -250,11 +260,7 @@ func (h *HopBridge) swapAndSend(chainID uint64, hopArgs *HopTxArgs, verifiedAcco
deadline, deadline,
) )
if err != nil { return tx, err
return hash, err
}
return types.Hash(tx.Hash()), nil
} }
// CalculateBonderFees logics come from: https://docs.hop.exchange/fee-calculation // CalculateBonderFees logics come from: https://docs.hop.exchange/fee-calculation

View File

@ -4,6 +4,7 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/status-im/status-go/account" "github.com/status-im/status-go/account"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
@ -44,6 +45,10 @@ func (s *TransferBridge) Send(sendArgs *TransactionBridge, verifiedAccount *acco
return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, verifiedAccount) return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, verifiedAccount)
} }
func (s *TransferBridge) BuildTransaction(sendArgs *TransactionBridge) (*ethTypes.Transaction, error) {
return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx)
}
func (s *TransferBridge) CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error) { func (s *TransferBridge) CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error) {
return amountIn, nil return amountIn, nil
} }

View File

@ -715,7 +715,7 @@ func TestFindBlocksCommand(t *testing.T) {
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{}) db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err) require.NoError(t, err)
tm := &TransactionManager{db, nil, nil, nil, nil, nil, nil} tm := &TransactionManager{db, nil, nil, nil, nil, nil, nil, nil, nil, nil}
wdb := NewDB(db) wdb := NewDB(db)
tc := &TestClient{ tc := &TestClient{

View File

@ -3,17 +3,21 @@ package transfer
import ( import (
"context" "context"
"database/sql" "database/sql"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"strings" "strings"
"time" "time"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/account" "github.com/status-im/status-go/account"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
@ -21,6 +25,7 @@ import (
"github.com/status-im/status-go/services/wallet/bridge" "github.com/status-im/status-go/services/wallet/bridge"
wallet_common "github.com/status-im/status-go/services/wallet/common" wallet_common "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/walletevent" "github.com/status-im/status-go/services/wallet/walletevent"
"github.com/status-im/status-go/signal"
"github.com/status-im/status-go/transactions" "github.com/status-im/status-go/transactions"
) )
@ -33,6 +38,18 @@ const (
EventMTTransactionUpdate walletevent.EventType = "multi-transaction-update" EventMTTransactionUpdate walletevent.EventType = "multi-transaction-update"
) )
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
}
type TransactionManager struct { type TransactionManager struct {
db *sql.DB db *sql.DB
gethManager *account.GethManager gethManager *account.GethManager
@ -41,6 +58,10 @@ type TransactionManager struct {
accountsDB *accounts.Database accountsDB *accounts.Database
pendingTracker *transactions.PendingTxTracker pendingTracker *transactions.PendingTxTracker
eventFeed *event.Feed eventFeed *event.Feed
multiTransactionForKeycardSigning *MultiTransaction
transactionsBridgeData []*bridge.TransactionBridge
transactionsForKeycardSingning map[common.Hash]*TransactionDescription
} }
func NewTransactionManager( func NewTransactionManager(
@ -271,6 +292,7 @@ func (tm *TransactionManager) UpdateMultiTransaction(multiTransaction *MultiTran
return updateMultiTransaction(tm.db, multiTransaction) return updateMultiTransaction(tm.db, multiTransaction)
} }
// In case of keycard account, password should be empty
func (tm *TransactionManager) CreateMultiTransactionFromCommand(ctx context.Context, command *MultiTransactionCommand, func (tm *TransactionManager) CreateMultiTransactionFromCommand(ctx context.Context, command *MultiTransactionCommand,
data []*bridge.TransactionBridge, bridges map[string]bridge.Bridge, password string) (*MultiTransactionCommandResult, error) { data []*bridge.TransactionBridge, bridges map[string]bridge.Bridge, password string) (*MultiTransactionCommandResult, error) {
@ -286,6 +308,33 @@ func (tm *TransactionManager) CreateMultiTransactionFromCommand(ctx context.Cont
} }
multiTransaction.ID = uint(multiTransactionID) multiTransaction.ID = uint(multiTransactionID)
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
}
hashes, err := tm.sendTransactions(multiTransaction, data, bridges, password) hashes, err := tm.sendTransactions(multiTransaction, data, bridges, password)
if err != nil { if err != nil {
return nil, err return nil, err
@ -302,6 +351,61 @@ func (tm *TransactionManager) CreateMultiTransactionFromCommand(ctx context.Cont
}, nil }, nil
} }
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)
if sigDetails.V == "1" {
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)
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
}
func (tm *TransactionManager) storePendingTransactions(multiTransaction *MultiTransaction, func (tm *TransactionManager) storePendingTransactions(multiTransaction *MultiTransaction,
hashes map[uint64][]types.Hash, data []*bridge.TransactionBridge) error { hashes map[uint64][]types.Hash, data []*bridge.TransactionBridge) error {
@ -359,6 +463,29 @@ func multiTransactionFromCommand(command *MultiTransactionCommand) *MultiTransac
return multiTransaction return multiTransaction
} }
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 {
builtTx, err := bridges[bridgeTx.BridgeName].BuildTransaction(bridgeTx)
if err != nil {
return hashes, err
}
signer := ethTypes.NewLondonSigner(big.NewInt(int64(bridgeTx.ChainID)))
txHash := signer.Hash(builtTx)
tm.transactionsForKeycardSingning[txHash] = &TransactionDescription{
chainID: bridgeTx.ChainID,
builtTx: builtTx,
}
hashes = append(hashes, txHash.String())
}
return hashes, nil
}
func (tm *TransactionManager) sendTransactions(multiTransaction *MultiTransaction, func (tm *TransactionManager) sendTransactions(multiTransaction *MultiTransaction,
data []*bridge.TransactionBridge, bridges map[string]bridge.Bridge, password string) ( data []*bridge.TransactionBridge, bridges map[string]bridge.Bridge, password string) (
map[uint64][]types.Hash, error) { map[uint64][]types.Hash, error) {

View File

@ -17,7 +17,7 @@ import (
func setupTestTransactionDB(t *testing.T) (*TransactionManager, func()) { func setupTestTransactionDB(t *testing.T) (*TransactionManager, func()) {
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{}) db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err) require.NoError(t, err)
return &TransactionManager{db, nil, nil, nil, nil, nil, nil}, func() { return &TransactionManager{db, nil, nil, nil, nil, nil, nil, nil, nil, nil}, func() {
require.NoError(t, db.Close()) require.NoError(t, db.Close())
} }
} }

View File

@ -4,7 +4,16 @@ const (
walletEvent = "wallet" walletEvent = "wallet"
) )
type UnsignedTransactions struct {
Type string `json:"type"`
Transactions []string `json:"transactions"`
}
// SendWalletEvent sends event from services/wallet/events. // SendWalletEvent sends event from services/wallet/events.
func SendWalletEvent(event interface{}) { func SendWalletEvent(event interface{}) {
send(walletEvent, event) send(walletEvent, event)
} }
func SendTransactionsForSigningEvent(transactions []string) {
send(walletEvent, UnsignedTransactions{Type: "sing-transactions", Transactions: transactions})
}

View File

@ -26,7 +26,7 @@ const (
defaultGas = 90000 defaultGas = 90000
validSignatureSize = 65 ValidSignatureSize = 65
) )
// ErrInvalidSignatureSize is returned if a signature is not 65 bytes to avoid panic from go-ethereum // ErrInvalidSignatureSize is returned if a signature is not 65 bytes to avoid panic from go-ethereum
@ -93,23 +93,50 @@ func (t *Transactor) SendTransactionWithChainID(chainID uint64, sendArgs SendTxA
return return
} }
func (t *Transactor) ValidateAndBuildTransaction(chainID uint64, sendArgs SendTxArgs) (tx *gethtypes.Transaction, err error) {
wrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID)
tx, err = t.validateAndBuildTransaction(wrapper, sendArgs)
return
}
func (t *Transactor) SendBuiltTransactionWithSignature(chainID uint64, tx *gethtypes.Transaction, sig []byte) (hash types.Hash, err error) {
if len(sig) != ValidSignatureSize {
return hash, ErrInvalidSignatureSize
}
rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID)
chID := big.NewInt(int64(rpcWrapper.chainID))
signer := gethtypes.NewLondonSigner(chID)
signedTx, err := tx.WithSignature(signer, sig)
if err != nil {
return hash, err
}
ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
defer cancel()
if err := rpcWrapper.SendTransaction(ctx, signedTx); err != nil {
return hash, err
}
return types.Hash(signedTx.Hash()), nil
}
// SendTransactionWithSignature receive a transaction and a signature, serialize them together and propage it to the network. // SendTransactionWithSignature receive a transaction and a signature, serialize them together and propage it to the network.
// It's different from eth_sendRawTransaction because it receives a signature and not a serialized transaction with signature. // It's different from eth_sendRawTransaction because it receives a signature and not a serialized transaction with signature.
// Since the transactions is already signed, we assume it was validated and used the right nonce. // Since the transactions is already signed, we assume it was validated and used the right nonce.
func (t *Transactor) SendTransactionWithSignature(args SendTxArgs, sig []byte) (hash types.Hash, err error) { func (t *Transactor) SendTransactionWithSignature(chainID uint64, args SendTxArgs, sig []byte) (hash types.Hash, err error) {
if !args.Valid() { if !args.Valid() {
return hash, ErrInvalidSendTxArgs return hash, ErrInvalidSendTxArgs
} }
if len(sig) != validSignatureSize { if len(sig) != ValidSignatureSize {
return hash, ErrInvalidSignatureSize return hash, ErrInvalidSignatureSize
} }
chainID := big.NewInt(int64(t.networkID))
signer := gethtypes.NewLondonSigner(chainID)
tx := t.buildTransaction(args) tx := t.buildTransaction(args)
expectedNonce, unlock, err := t.nonce.Next(t.rpcWrapper, args.From) rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID)
expectedNonce, unlock, err := t.nonce.Next(rpcWrapper, args.From)
if err != nil { if err != nil {
return hash, err return hash, err
} }
@ -121,18 +148,7 @@ func (t *Transactor) SendTransactionWithSignature(args SendTxArgs, sig []byte) (
return hash, &ErrBadNonce{tx.Nonce(), expectedNonce} return hash, &ErrBadNonce{tx.Nonce(), expectedNonce}
} }
signedTx, err := tx.WithSignature(signer, sig) return t.SendBuiltTransactionWithSignature(chainID, tx, sig)
if err != nil {
return hash, err
}
ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
defer cancel()
if err := t.rpcWrapper.SendTransaction(ctx, signedTx); err != nil {
return hash, err
}
return types.Hash(signedTx.Hash()), nil
} }
func (t *Transactor) HashTransaction(args SendTxArgs) (validatedArgs SendTxArgs, hash types.Hash, err error) { func (t *Transactor) HashTransaction(args SendTxArgs) (validatedArgs SendTxArgs, hash types.Hash, err error) {
@ -235,18 +251,14 @@ func (t *Transactor) validateAccount(args SendTxArgs, selectedAccount *account.S
return nil return nil
} }
func (t *Transactor) validateAndPropagate(rpcWrapper *rpcWrapper, selectedAccount *account.SelectedExtKey, args SendTxArgs) (hash types.Hash, err error) { func (t *Transactor) validateAndBuildTransaction(rpcWrapper *rpcWrapper, args SendTxArgs) (tx *gethtypes.Transaction, err error) {
if err = t.validateAccount(args, selectedAccount); err != nil {
return hash, err
}
if !args.Valid() { if !args.Valid() {
return hash, ErrInvalidSendTxArgs return tx, ErrInvalidSendTxArgs
} }
nonce, unlock, err := t.nonce.Next(rpcWrapper, args.From) nonce, unlock, err := t.nonce.Next(rpcWrapper, args.From)
if err != nil { if err != nil {
return hash, err return tx, err
} }
if args.Nonce != nil { if args.Nonce != nil {
nonce = uint64(*args.Nonce) nonce = uint64(*args.Nonce)
@ -261,10 +273,10 @@ func (t *Transactor) validateAndPropagate(rpcWrapper *rpcWrapper, selectedAccoun
if !args.IsDynamicFeeTx() && args.GasPrice == nil { if !args.IsDynamicFeeTx() && args.GasPrice == nil {
gasPrice, err = rpcWrapper.SuggestGasPrice(ctx) gasPrice, err = rpcWrapper.SuggestGasPrice(ctx)
if err != nil { if err != nil {
return hash, err return tx, err
} }
} }
chainID := big.NewInt(int64(rpcWrapper.chainID))
value := (*big.Int)(args.Value) value := (*big.Int)(args.Value)
var gas uint64 var gas uint64
if args.Gas != nil { if args.Gas != nil {
@ -289,20 +301,34 @@ func (t *Transactor) validateAndPropagate(rpcWrapper *rpcWrapper, selectedAccoun
Data: args.GetInput(), Data: args.GetInput(),
}) })
if err != nil { if err != nil {
return hash, err return tx, err
} }
if gas < defaultGas { if gas < defaultGas {
t.log.Info("default gas will be used because estimated is lower", "estimated", gas, "default", defaultGas) t.log.Info("default gas will be used because estimated is lower", "estimated", gas, "default", defaultGas)
gas = defaultGas gas = defaultGas
} }
} }
tx := t.buildTransactionWithOverrides(nonce, value, gas, gasPrice, args) tx = t.buildTransactionWithOverrides(nonce, value, gas, gasPrice, args)
return tx, nil
}
func (t *Transactor) validateAndPropagate(rpcWrapper *rpcWrapper, selectedAccount *account.SelectedExtKey, args SendTxArgs) (hash types.Hash, err error) {
if err = t.validateAccount(args, selectedAccount); err != nil {
return hash, err
}
tx, err := t.validateAndBuildTransaction(rpcWrapper, args)
if err != nil {
return hash, err
}
chainID := big.NewInt(int64(rpcWrapper.chainID))
signedTx, err := gethtypes.SignTx(tx, gethtypes.NewLondonSigner(chainID), selectedAccount.AccountKey.PrivateKey) signedTx, err := gethtypes.SignTx(tx, gethtypes.NewLondonSigner(chainID), selectedAccount.AccountKey.PrivateKey)
if err != nil { if err != nil {
return hash, err return hash, err
} }
// ctx, cancel = context.WithTimeout(context.Background(), t.rpcCallTimeout) ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
// defer cancel() defer cancel()
if err := rpcWrapper.SendTransaction(ctx, signedTx); err != nil { if err := rpcWrapper.SendTransaction(ctx, signedTx); err != nil {
return hash, err return hash, err

View File

@ -374,7 +374,7 @@ func (s *TransactorSuite) TestSendTransactionWithSignature() {
Return(common.Hash{}, nil) Return(common.Hash{}, nil)
} }
_, err = s.manager.SendTransactionWithSignature(args, sig) _, err = s.manager.SendTransactionWithSignature(s.nodeConfig.NetworkID, args, sig)
if scenario.expectError { if scenario.expectError {
s.Error(err) s.Error(err)
// local nonce should not be incremented // local nonce should not be incremented
@ -393,7 +393,7 @@ func (s *TransactorSuite) TestSendTransactionWithSignature() {
func (s *TransactorSuite) TestSendTransactionWithSignature_InvalidSignature() { func (s *TransactorSuite) TestSendTransactionWithSignature_InvalidSignature() {
args := SendTxArgs{} args := SendTxArgs{}
_, err := s.manager.SendTransactionWithSignature(args, []byte{}) _, err := s.manager.SendTransactionWithSignature(1, args, []byte{})
s.Equal(ErrInvalidSignatureSize, err) s.Equal(ErrInvalidSignatureSize, err)
} }

View File

@ -106,6 +106,11 @@ func (args SendTxArgs) ToTransactOpts(signerFn bind.SignerFn) *bind.TransactOpts
gasLimit = uint64(*args.Gas) gasLimit = uint64(*args.Gas)
} }
var noSign = false
if signerFn == nil {
noSign = true
}
return &bind.TransactOpts{ return &bind.TransactOpts{
From: common.Address(args.From), From: common.Address(args.From),
Signer: signerFn, Signer: signerFn,
@ -114,6 +119,7 @@ func (args SendTxArgs) ToTransactOpts(signerFn bind.SignerFn) *bind.TransactOpts
GasFeeCap: gasFeeCap, GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap, GasTipCap: gasTipCap,
Nonce: nonce, Nonce: nonce,
NoSign: noSign,
} }
} }

View File

@ -61,6 +61,7 @@ type TransactOpts struct {
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
NoSign bool // Do all transact steps but do not sign or send the transaction
NoSend bool // Do all transact steps but do not send the transaction NoSend bool // Do all transact steps but do not send the transaction
} }
@ -387,6 +388,9 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
if err != nil { if err != nil {
return nil, err return nil, err
} }
if opts.NoSign {
return rawTx, nil
}
// Sign the transaction and schedule it for execution // Sign the transaction and schedule it for execution
if opts.Signer == nil { if opts.Signer == nil {
return nil, errors.New("no signer to authorize the transaction with") return nil, errors.New("no signer to authorize the transaction with")

2
vendor/modules.txt vendored
View File

@ -208,7 +208,7 @@ github.com/edsrzf/mmap-go
## explicit; go 1.14 ## explicit; go 1.14
github.com/elastic/gosigar github.com/elastic/gosigar
github.com/elastic/gosigar/sys/windows github.com/elastic/gosigar/sys/windows
# github.com/ethereum/go-ethereum v1.10.26 => github.com/status-im/go-ethereum v1.10.25-status.7 # github.com/ethereum/go-ethereum v1.10.26 => github.com/status-im/go-ethereum v1.10.25-status.9
## explicit; go 1.17 ## explicit; go 1.17
github.com/ethereum/go-ethereum github.com/ethereum/go-ethereum
github.com/ethereum/go-ethereum/accounts github.com/ethereum/go-ethereum/accounts