chore(walletconnect)_: various improvements applied

- `WalletConnectTransfer` identified as a new transfer type
- Wallet-related endpoints that logically belong to the wallet moved from the wallet connect service
- Wallet connect service now receives `transfer.TransactionManager` instead of `transactions.Transactor`
- Deadlock issue when trying to send the tx with the wrong nonce fixed
This commit is contained in:
Sale Djenic 2023-12-01 17:42:08 +01:00 committed by saledjenic
parent 04873ef880
commit cfa542378d
8 changed files with 115 additions and 192 deletions

View File

@ -541,6 +541,23 @@ func (api *API) BuildTransaction(ctx context.Context, chainID uint64, sendTxArgs
return api.s.transactionManager.BuildTransaction(chainID, params) return api.s.transactionManager.BuildTransaction(chainID, params)
} }
func (api *API) BuildRawTransaction(ctx context.Context, chainID uint64, sendTxArgsJSON string, signature string) (response *transfer.TxResponse, err error) {
log.Debug("[WalletAPI::BuildRawTransaction]", "chainID", chainID, "sendTxArgsJSON", sendTxArgsJSON, "signature", signature)
sig, err := hex.DecodeString(signature)
if err != nil {
return nil, err
}
var params transactions.SendTxArgs
err = json.Unmarshal([]byte(sendTxArgsJSON), &params)
if err != nil {
return nil, err
}
return api.s.transactionManager.BuildRawTransaction(chainID, params, sig)
}
func (api *API) SendTransactionWithSignature(ctx context.Context, chainID uint64, txType transactions.PendingTrxType, func (api *API) SendTransactionWithSignature(ctx context.Context, chainID uint64, txType transactions.PendingTrxType,
sendTxArgsJSON string, signature string) (hash types.Hash, err error) { sendTxArgsJSON string, signature string) (hash types.Hash, err error) {
log.Debug("[WalletAPI::SendTransactionWithSignature]", "chainID", chainID, "txType", txType, "sendTxArgsJSON", sendTxArgsJSON, "signature", signature) log.Debug("[WalletAPI::SendTransactionWithSignature]", "chainID", chainID, "txType", txType, "sendTxArgsJSON", sendTxArgsJSON, "signature", signature)
@ -641,34 +658,6 @@ func (api *API) FetchChainIDForURL(ctx context.Context, rpcURL string) (*big.Int
return client.ChainID(ctx) return client.ChainID(ctx)
} }
// WCSignMessage signs a message for the passed address using the provided password and returns the signature
func (api *API) WCSignMessage(ctx context.Context, message types.HexBytes, address common.Address, password string) (string, error) {
log.Debug("wallet.api.wc.SignMessage", "message", message, "address", address, "password", password)
return api.s.walletConnect.SignMessage(message, address, password)
}
// WCBuildRawTransaction builds raw transaction using the provided signature and returns the RLP-encoded transaction object
func (api *API) WCBuildRawTransaction(signature string) (string, error) {
log.Debug("wallet.api.wc.BuildRawTransaction", "signature", signature)
return api.s.walletConnect.BuildRawTransaction(signature)
}
// WCSendRawTransaction sends provided raw transaction and returns the transaction hash
func (api *API) WCSendRawTransaction(rawTx string) (string, error) {
log.Debug("wallet.api.wc.SendRawTransaction", "rawTx", rawTx)
return api.s.walletConnect.SendRawTransaction(rawTx)
}
// WCSendTransactionWithSignature sends transaction with the provided signature and returns the transaction hash
func (api *API) WCSendTransactionWithSignature(signature string) (string, error) {
log.Debug("wallet.api.wc.SendTransactionWithSignature", "signature", signature)
return api.s.walletConnect.SendTransactionWithSignature(signature)
}
// WCPairSessionProposal responds to "session_proposal" event // WCPairSessionProposal responds to "session_proposal" event
func (api *API) WCPairSessionProposal(ctx context.Context, sessionProposalJSON string) (*wc.PairSessionResponse, error) { func (api *API) WCPairSessionProposal(ctx context.Context, sessionProposalJSON string) (*wc.PairSessionResponse, error) {
log.Debug("wallet.api.wc.PairSessionProposal", "proposal.len", len(sessionProposalJSON)) log.Debug("wallet.api.wc.PairSessionProposal", "proposal.len", len(sessionProposalJSON))
@ -709,7 +698,7 @@ func (api *API) WCHasActivePairings(ctx context.Context) (bool, error) {
} }
// WCSessionRequest responds to "session_request" event // WCSessionRequest responds to "session_request" event
func (api *API) WCSessionRequest(ctx context.Context, sessionRequestJSON string) (*wc.SessionRequestResponse, error) { func (api *API) WCSessionRequest(ctx context.Context, sessionRequestJSON string) (*transfer.TxResponse, error) {
log.Debug("wallet.api.wc.SessionRequest", "request.len", len(sessionRequestJSON)) log.Debug("wallet.api.wc.SessionRequest", "request.len", len(sessionRequestJSON))
var request wc.SessionRequest var request wc.SessionRequest

View File

@ -144,7 +144,7 @@ func NewService(
activity := activity.NewService(db, tokenManager, collectiblesManager, feed) activity := activity.NewService(db, tokenManager, collectiblesManager, feed)
walletconnect := walletconnect.NewService(db, rpcClient.NetworkManager, accountsDB, transactor, gethManager, feed, config) walletconnect := walletconnect.NewService(db, rpcClient.NetworkManager, accountsDB, transactionManager, gethManager, feed, config)
return &Service{ return &Service{
db: db, db: db,

View File

@ -132,8 +132,10 @@ type TxResponse struct {
AddressPath string `json:"addressPath,omitempty"` AddressPath string `json:"addressPath,omitempty"`
SignOnKeycard bool `json:"signOnKeycard,omitempty"` SignOnKeycard bool `json:"signOnKeycard,omitempty"`
ChainID uint64 `json:"chainId,omitempty"` ChainID uint64 `json:"chainId,omitempty"`
MesageToSign interface{} `json:"messageToSign,omitempty"` MessageToSign interface{} `json:"messageToSign,omitempty"`
TxArgs transactions.SendTxArgs `json:"txArgs,omitempty"` TxArgs transactions.SendTxArgs `json:"txArgs,omitempty"`
RawTx string `json:"rawTx,omitempty"`
TxHash common.Hash `json:"txHash,omitempty"`
} }
func (tm *TransactionManager) SignMessage(message types.HexBytes, address common.Address, password string) (string, error) { func (tm *TransactionManager) SignMessage(message types.HexBytes, address common.Address, password string) (string, error) {
@ -159,15 +161,15 @@ func (tm *TransactionManager) BuildTransaction(chainID uint64, sendArgs transact
} }
txBeingSigned, unlock, err := tm.transactor.ValidateAndBuildTransaction(chainID, sendArgs) txBeingSigned, unlock, err := tm.transactor.ValidateAndBuildTransaction(chainID, sendArgs)
if err != nil {
return nil, err
}
// We have to unlock the nonce, cause we don't know what will happen on the client side (will user accept/reject) an action. // We have to unlock the nonce, cause we don't know what will happen on the client side (will user accept/reject) an action.
if unlock != nil { if unlock != nil {
defer func() { defer func() {
unlock(false, 0) unlock(false, 0)
}() }()
} }
if err != nil {
return nil, err
}
// Set potential missing fields that were added while building the transaction // Set potential missing fields that were added while building the transaction
if sendArgs.Value == nil { if sendArgs.Value == nil {
@ -178,17 +180,24 @@ func (tm *TransactionManager) BuildTransaction(chainID uint64, sendArgs transact
nonce := hexutil.Uint64(txBeingSigned.Nonce()) nonce := hexutil.Uint64(txBeingSigned.Nonce())
sendArgs.Nonce = &nonce sendArgs.Nonce = &nonce
} }
if sendArgs.Gas == nil {
gas := hexutil.Uint64(txBeingSigned.Gas())
sendArgs.Gas = &gas
}
if sendArgs.GasPrice == nil { if sendArgs.GasPrice == nil {
gasPrice := hexutil.Big(*txBeingSigned.GasPrice()) gasPrice := hexutil.Big(*txBeingSigned.GasPrice())
sendArgs.GasPrice = &gasPrice sendArgs.GasPrice = &gasPrice
} }
if sendArgs.MaxPriorityFeePerGas == nil {
maxPriorityFeePerGas := hexutil.Big(*txBeingSigned.GasTipCap()) if sendArgs.IsDynamicFeeTx() {
sendArgs.MaxPriorityFeePerGas = &maxPriorityFeePerGas if sendArgs.MaxPriorityFeePerGas == nil {
} maxPriorityFeePerGas := hexutil.Big(*txBeingSigned.GasTipCap())
if sendArgs.MaxFeePerGas == nil { sendArgs.MaxPriorityFeePerGas = &maxPriorityFeePerGas
maxFeePerGas := hexutil.Big(*txBeingSigned.GasFeeCap()) }
sendArgs.MaxFeePerGas = &maxFeePerGas if sendArgs.MaxFeePerGas == nil {
maxFeePerGas := hexutil.Big(*txBeingSigned.GasFeeCap())
sendArgs.MaxFeePerGas = &maxFeePerGas
}
} }
signer := ethTypes.NewLondonSigner(new(big.Int).SetUint64(chainID)) signer := ethTypes.NewLondonSigner(new(big.Int).SetUint64(chainID))
@ -199,11 +208,36 @@ func (tm *TransactionManager) BuildTransaction(chainID uint64, sendArgs transact
AddressPath: account.Path, AddressPath: account.Path,
SignOnKeycard: kp.MigratedToKeycard(), SignOnKeycard: kp.MigratedToKeycard(),
ChainID: chainID, ChainID: chainID,
MesageToSign: signer.Hash(txBeingSigned), MessageToSign: signer.Hash(txBeingSigned),
TxArgs: sendArgs, TxArgs: sendArgs,
}, nil }, nil
} }
func (tm *TransactionManager) BuildRawTransaction(chainID uint64, sendArgs transactions.SendTxArgs, signature []byte) (response *TxResponse, err error) {
tx, unlock, err := tm.transactor.BuildTransactionWithSignature(chainID, sendArgs, signature)
// We have to unlock the nonce, cause we don't know what will happen on the client side (will user accept/reject) an action.
if unlock != nil {
defer func() {
unlock(false, 0)
}()
}
if err != nil {
return nil, err
}
data, err := tx.MarshalBinary()
if err != nil {
return nil, err
}
return &TxResponse{
ChainID: chainID,
TxArgs: sendArgs,
RawTx: types.EncodeHex(data),
TxHash: tx.Hash(),
}, nil
}
func (tm *TransactionManager) SendTransactionWithSignature(chainID uint64, txType transactions.PendingTrxType, sendArgs transactions.SendTxArgs, signature []byte) (hash types.Hash, err error) { func (tm *TransactionManager) SendTransactionWithSignature(chainID uint64, txType transactions.PendingTrxType, sendArgs transactions.SendTxArgs, signature []byte) (hash types.Hash, err error) {
hash, err = tm.transactor.BuildTransactionAndSendWithSignature(chainID, sendArgs, signature) hash, err = tm.transactor.BuildTransactionAndSendWithSignature(chainID, sendArgs, signature)
if err != nil { if err != nil {

View File

@ -1,18 +1,14 @@
package walletconnect package walletconnect
import ( import (
"encoding/hex"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"math/big"
"strings" "strings"
"github.com/ethereum/go-ethereum/common"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/signer/core/apitypes" "github.com/ethereum/go-ethereum/signer/core/apitypes"
"github.com/status-im/status-go/eth-node/crypto" "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/services/wallet/transfer"
"github.com/status-im/status-go/transactions" "github.com/status-im/status-go/transactions"
) )
@ -65,7 +61,7 @@ func (n *sendTransactionParams) MarshalJSON() ([]byte, error) {
return json.Marshal(n.SendTxArgs) return json.Marshal(n.SendTxArgs)
} }
func (s *Service) buildTransaction(request SessionRequest) (response *SessionRequestResponse, err error) { func (s *Service) buildTransaction(request SessionRequest) (response *transfer.TxResponse, err error) {
if len(request.Params.Request.Params) != 1 { if len(request.Params.Request.Params) != 1 {
return nil, ErrorInvalidParamsCount return nil, ErrorInvalidParamsCount
} }
@ -75,62 +71,16 @@ func (s *Service) buildTransaction(request SessionRequest) (response *SessionReq
return nil, err return nil, err
} }
account, err := s.accountsDB.GetAccountByAddress(params.From)
if err != nil {
return nil, fmt.Errorf("failed to get active account: %w", err)
}
kp, err := s.accountsDB.GetKeypairByKeyUID(account.KeyUID)
if err != nil {
return nil, err
}
_, chainID, err := parseCaip2ChainID(request.Params.ChainID) _, chainID, err := parseCaip2ChainID(request.Params.ChainID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// In this case we can ignore `unlock` function received from `ValidateAndBuildTransaction` cause `Nonce` return s.transactionManager.BuildTransaction(chainID, params.SendTxArgs)
// will be always set by the initiator of this transaction (by the dapp).
// Though we will need sort out completely that part since Nonce kept in the local cache is not the most recent one,
// instead of that we should always ask network what's the most recent known Nonce for the account.
// Logged issue to handle that: https://github.com/status-im/status-go/issues/4335
txBeingSigned, _, err := s.transactor.ValidateAndBuildTransaction(chainID, params.SendTxArgs)
if err != nil {
return nil, err
}
s.txSignDetails = &txSigningDetails{
from: common.Address(account.Address),
chainID: chainID,
txBeingSigned: txBeingSigned,
}
signer := ethTypes.NewLondonSigner(new(big.Int).SetUint64(chainID))
return &SessionRequestResponse{
KeyUID: account.KeyUID,
Address: account.Address,
AddressPath: account.Path,
SignOnKeycard: kp.MigratedToKeycard(),
MessageToSign: signer.Hash(txBeingSigned),
}, nil
}
func (s *Service) addSignatureToTransaction(signature string) (*ethTypes.Transaction, error) {
if s.txSignDetails.txBeingSigned == nil {
return nil, errors.New("no tx to sign")
}
signatureBytes, err := hex.DecodeString(signature)
if err != nil {
return nil, err
}
return s.transactor.AddSignatureToTransaction(s.txSignDetails.chainID, s.txSignDetails.txBeingSigned, signatureBytes)
} }
func (s *Service) buildMessage(request SessionRequest, addressIndex int, messageIndex int, func (s *Service) buildMessage(request SessionRequest, addressIndex int, messageIndex int,
handleTypedData bool) (response *SessionRequestResponse, err error) { handleTypedData bool) (response *transfer.TxResponse, err error) {
if len(request.Params.Request.Params) != 2 { if len(request.Params.Request.Params) != 2 {
return nil, ErrorInvalidParamsCount return nil, ErrorInvalidParamsCount
} }
@ -178,7 +128,7 @@ func (s *Service) buildMessage(request SessionRequest, addressIndex int, message
} }
} }
return &SessionRequestResponse{ return &transfer.TxResponse{
KeyUID: account.KeyUID, KeyUID: account.KeyUID,
Address: account.Address, Address: account.Address,
AddressPath: account.Path, AddressPath: account.Path,

View File

@ -6,102 +6,42 @@ import (
"strings" "strings"
"time" "time"
"github.com/ethereum/go-ethereum/common"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"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/multiaccounts/accounts" "github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc/network" "github.com/status-im/status-go/rpc/network"
"github.com/status-im/status-go/transactions" "github.com/status-im/status-go/services/wallet/transfer"
) )
type txSigningDetails struct {
chainID uint64
from common.Address
txBeingSigned *ethTypes.Transaction
txHash common.Hash
}
type Service struct { type Service struct {
db *sql.DB db *sql.DB
networkManager *network.Manager networkManager *network.Manager
accountsDB *accounts.Database accountsDB *accounts.Database
eventFeed *event.Feed eventFeed *event.Feed
transactor *transactions.Transactor transactionManager *transfer.TransactionManager
gethManager *account.GethManager gethManager *account.GethManager
config *params.NodeConfig config *params.NodeConfig
txSignDetails *txSigningDetails
} }
func NewService(db *sql.DB, networkManager *network.Manager, accountsDB *accounts.Database, transactor *transactions.Transactor, gethManager *account.GethManager, eventFeed *event.Feed, config *params.NodeConfig) *Service { func NewService(db *sql.DB, networkManager *network.Manager, accountsDB *accounts.Database,
transactionManager *transfer.TransactionManager, gethManager *account.GethManager, eventFeed *event.Feed,
config *params.NodeConfig) *Service {
return &Service{ return &Service{
db: db, db: db,
networkManager: networkManager, networkManager: networkManager,
accountsDB: accountsDB, accountsDB: accountsDB,
eventFeed: eventFeed, eventFeed: eventFeed,
transactor: transactor, transactionManager: transactionManager,
gethManager: gethManager, gethManager: gethManager,
config: config, config: config,
} }
} }
func (s *Service) SignMessage(message types.HexBytes, address common.Address, password string) (string, error) {
selectedAccount, err := s.gethManager.VerifyAccountPassword(s.config.KeyStoreDir, address.Hex(), password)
if err != nil {
return "", err
}
signature, err := crypto.Sign(message[:], selectedAccount.PrivateKey)
return types.EncodeHex(signature), err
}
func (s *Service) BuildRawTransaction(signature string) (string, error) {
txWithSignature, err := s.addSignatureToTransaction(signature)
if err != nil {
return "", err
}
data, err := txWithSignature.MarshalBinary()
if err != nil {
return "", err
}
s.txSignDetails.txHash = txWithSignature.Hash()
return types.EncodeHex(data), nil
}
func (s *Service) SendRawTransaction(rawTx string) (string, error) {
err := s.transactor.SendRawTransaction(s.txSignDetails.chainID, rawTx)
if err != nil {
return "", err
}
return s.txSignDetails.txHash.Hex(), nil
}
func (s *Service) SendTransactionWithSignature(signature string) (string, error) {
txWithSignature, err := s.addSignatureToTransaction(signature)
if err != nil {
return "", err
}
hash, err := s.transactor.SendTransactionWithSignature(txWithSignature)
if err != nil {
return "", err
}
return hash.Hex(), nil
}
func (s *Service) PairSessionProposal(proposal SessionProposal) (*PairSessionResponse, error) { func (s *Service) PairSessionProposal(proposal SessionProposal) (*PairSessionResponse, error) {
if !proposal.Valid() { if !proposal.Valid() {
return nil, ErrorInvalidSessionProposal return nil, ErrorInvalidSessionProposal
@ -208,7 +148,7 @@ func (s *Service) HasActivePairings() (bool, error) {
return HasActivePairings(s.db, time.Now().Unix()) return HasActivePairings(s.db, time.Now().Unix())
} }
func (s *Service) SessionRequest(request SessionRequest) (response *SessionRequestResponse, err error) { func (s *Service) SessionRequest(request SessionRequest) (response *transfer.TxResponse, err error) {
// TODO #12434: should we check topic for validity? It might make sense if we // TODO #12434: should we check topic for validity? It might make sense if we
// want to cache the paired sessions // want to cache the paired sessions

View File

@ -9,7 +9,6 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"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/services/wallet/walletevent" "github.com/status-im/status-go/services/wallet/walletevent"
) )
@ -102,14 +101,6 @@ type SessionDelete struct {
Topic Topic `json:"topic"` Topic Topic `json:"topic"`
} }
type SessionRequestResponse struct {
KeyUID string `json:"keyUid,omitempty"`
Address types.Address `json:"address,omitempty"`
AddressPath string `json:"addressPath,omitempty"`
SignOnKeycard bool `json:"signOnKeycard,omitempty"`
MessageToSign interface{} `json:"messageToSign,omitempty"`
}
// Valid namespace // Valid namespace
func (n *Namespace) Valid(namespaceName string, chainID *uint64) bool { func (n *Namespace) Valid(namespaceName string, chainID *uint64) bool {
if chainID == nil { if chainID == nil {

View File

@ -359,6 +359,7 @@ const (
BurnCommunityToken PendingTrxType = "BurnCommunityToken" BurnCommunityToken PendingTrxType = "BurnCommunityToken"
DeployOwnerToken PendingTrxType = "DeployOwnerToken" DeployOwnerToken PendingTrxType = "DeployOwnerToken"
SetSignerPublicKey PendingTrxType = "SetSignerPublicKey" SetSignerPublicKey PendingTrxType = "SetSignerPublicKey"
WalletConnectTransfer PendingTrxType = "WalletConnectTransfer"
) )
type PendingTransaction struct { type PendingTransaction struct {

View File

@ -167,32 +167,50 @@ func (t *Transactor) AddSignatureToTransactionAndSend(chainID uint64, tx *gethty
// 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) BuildTransactionAndSendWithSignature(chainID uint64, args SendTxArgs, sig []byte) (hash types.Hash, err error) { func (t *Transactor) BuildTransactionAndSendWithSignature(chainID uint64, args SendTxArgs, sig []byte) (hash types.Hash, err error) {
txWithSignature, unlock, err := t.BuildTransactionWithSignature(chainID, args, sig)
if unlock != nil {
defer func() {
var nonce uint64
if txWithSignature != nil {
nonce = txWithSignature.Nonce()
}
unlock(err == nil, nonce)
}()
}
if err != nil {
return hash, err
}
hash, err = t.SendTransactionWithSignature(txWithSignature)
return hash, err
}
func (t *Transactor) BuildTransactionWithSignature(chainID uint64, args SendTxArgs, sig []byte) (*gethtypes.Transaction, UnlockNonceFunc, error) {
if !args.Valid() { if !args.Valid() {
return hash, ErrInvalidSendTxArgs return nil, nil, ErrInvalidSendTxArgs
} }
if len(sig) != ValidSignatureSize { if len(sig) != ValidSignatureSize {
return hash, ErrInvalidSignatureSize return nil, nil, ErrInvalidSignatureSize
} }
tx := t.buildTransaction(args) tx := t.buildTransaction(args)
rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID) rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID)
expectedNonce, unlock, err := t.nonce.Next(rpcWrapper, args.From) expectedNonce, unlock, err := t.nonce.Next(rpcWrapper, args.From)
if err != nil { if err != nil {
return hash, err return nil, nil, err
}
if unlock != nil {
defer func() {
unlock(err == nil, expectedNonce)
}()
} }
if tx.Nonce() != expectedNonce { if tx.Nonce() != expectedNonce {
return hash, &ErrBadNonce{tx.Nonce(), expectedNonce} return nil, unlock, &ErrBadNonce{tx.Nonce(), expectedNonce}
} }
hash, err = t.AddSignatureToTransactionAndSend(chainID, tx, sig) txWithSignature, err := t.AddSignatureToTransaction(chainID, tx, sig)
return hash, err if err != nil {
return nil, unlock, err
}
return txWithSignature, unlock, 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) {