From 4843c668f22447d2326120e3f85d53a6fd84eef1 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 19 Dec 2024 15:01:59 +0530 Subject: [PATCH 01/33] Add GetLedgerEntries and GetAccountLedgerSeq to rpcService --- internal/entities/rpc.go | 14 ++++++++++++ internal/services/mocks.go | 10 +++++++++ internal/services/rpc_service.go | 37 ++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/internal/entities/rpc.go b/internal/entities/rpc.go index 5f6c1aa..a271faa 100644 --- a/internal/entities/rpc.go +++ b/internal/entities/rpc.go @@ -77,6 +77,19 @@ type RPCSendTransactionResult struct { ErrorResultXDR string `json:"errorResultXdr"` } +type LedgerEntryResult struct { + KeyXDR string `json:"key,omitempty"` + DataXDR string `json:"xdr,omitempty"` + LastModifiedLedger uint32 `json:"lastModifiedLedgerSeq"` + // The ledger sequence until the entry is live, available for entries that have associated ttl ledger entries. + LiveUntilLedgerSeq *uint32 `json:"liveUntilLedgerSeq,omitempty"` +} + +type RPCGetLedgerEntriesResult struct { + LatestLedger uint32 `json:"latestLedger"` + Entries []LedgerEntryResult `json:"entries"` +} + type RPCPagination struct { Cursor string `json:"cursor,omitempty"` Limit int `json:"limit"` @@ -87,4 +100,5 @@ type RPCParams struct { Hash string `json:"hash,omitempty"` StartLedger int64 `json:"startLedger,omitempty"` Pagination RPCPagination `json:"pagination,omitempty"` + LedgerKeys []string `json:"keys,omitempty"` } diff --git a/internal/services/mocks.go b/internal/services/mocks.go index d8c6d0b..5b02b4e 100644 --- a/internal/services/mocks.go +++ b/internal/services/mocks.go @@ -31,3 +31,13 @@ func (r *RPCServiceMock) GetHealth() (entities.RPCGetHealthResult, error) { args := r.Called() return args.Get(0).(entities.RPCGetHealthResult), args.Error(1) } + +func (r *RPCServiceMock) GetLedgerEntries(keys []string) (entities.RPCGetLedgerEntriesResult, error) { + args := r.Called(keys) + return args.Get(0).(entities.RPCGetLedgerEntriesResult), args.Error(1) +} + +func (r *RPCServiceMock) GetAccountLedgerSequence(address string) (int64, error) { + args := r.Called(address) + return args.Get(0).(int64), args.Error(1) +} diff --git a/internal/services/rpc_service.go b/internal/services/rpc_service.go index 29b56a2..1663db0 100644 --- a/internal/services/rpc_service.go +++ b/internal/services/rpc_service.go @@ -20,6 +20,8 @@ type RPCService interface { GetTransactions(startLedger int64, startCursor string, limit int) (entities.RPCGetTransactionsResult, error) SendTransaction(transactionXDR string) (entities.RPCSendTransactionResult, error) GetHealth() (entities.RPCGetHealthResult, error) + GetLedgerEntries(keys []string) (entities.RPCGetLedgerEntriesResult, error) + GetAccountLedgerSequence(address string) (int64, error) } type rpcService struct { @@ -100,6 +102,22 @@ func (r *rpcService) GetHealth() (entities.RPCGetHealthResult, error) { return result, nil } +func (r *rpcService) GetLedgerEntries(keys []string) (entities.RPCGetLedgerEntriesResult, error) { + resultBytes, err := r.sendRPCRequest("getLedgerEntries", entities.RPCParams{ + LedgerKeys: keys, + }) + if err != nil { + return entities.RPCGetLedgerEntriesResult{}, fmt.Errorf("sending getLedgerEntries request: %w", err) + } + + var result entities.RPCGetLedgerEntriesResult + err = json.Unmarshal(resultBytes, &result) + if err != nil { + return entities.RPCGetLedgerEntriesResult{}, fmt.Errorf("parsing getLedgerEntries result JSON: %w", err) + } + return result, nil +} + func (r *rpcService) SendTransaction(transactionXDR string) (entities.RPCSendTransactionResult, error) { resultBytes, err := r.sendRPCRequest("sendTransaction", entities.RPCParams{Transaction: transactionXDR}) @@ -116,6 +134,25 @@ func (r *rpcService) SendTransaction(transactionXDR string) (entities.RPCSendTra return result, nil } +func (r *rpcService) GetAccountLedgerSequence(address string) (int64, error) { + keyXdr, err := utils.GetAccountLedgerKey(address) + if err != nil { + return 0, fmt.Errorf("getting ledger key for distribution account public key: %w", err) + } + result, err := r.GetLedgerEntries([]string{keyXdr}) + if err != nil { + return 0, fmt.Errorf("getting ledger entry for distribution account public key: %w", err) + } + if len(result.Entries) == 0 { + return 0, fmt.Errorf("entry not found for distribution account public key") + } + accountEntry, err := utils.DecodeAccountFromLedgerEntry(result.Entries[0]) + if err != nil { + return 0, fmt.Errorf("decoding account entry for distribution account public key: %w", err) + } + return int64(accountEntry.SeqNum), nil +} + func (r *rpcService) sendRPCRequest(method string, params entities.RPCParams) (json.RawMessage, error) { payload := map[string]interface{}{ From 8952c397afef9f326b5f90f4fc3ba60005fe1876 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 19 Dec 2024 15:02:22 +0530 Subject: [PATCH 02/33] Use RPC ledgerEntries to get account detail instead of horizon --- internal/serve/serve.go | 13 ++++--- internal/services/channel_account_service.go | 17 +++++++-- internal/utils/utils.go | 36 ++++++++++++++++++++ 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/internal/serve/serve.go b/internal/serve/serve.go index 5c4eacd..0d5af25 100644 --- a/internal/serve/serve.go +++ b/internal/serve/serve.go @@ -13,6 +13,7 @@ import ( "github.com/stellar/go/support/log" "github.com/stellar/go/support/render/health" "github.com/stellar/go/xdr" + "github.com/stellar/wallet-backend/internal/apptracker" "github.com/stellar/wallet-backend/internal/data" "github.com/stellar/wallet-backend/internal/db" @@ -172,6 +173,12 @@ func initHandlerDeps(cfg Configs) (handlerDeps, error) { return handlerDeps{}, fmt.Errorf("instantiating account sponsorship service: %w", err) } + httpClient := http.Client{Timeout: time.Duration(30 * time.Second)} + rpcService, err := services.NewRPCService(cfg.RPCURL, &httpClient) + if err != nil { + return handlerDeps{}, fmt.Errorf("instantiating rpc service: %w", err) + } + paymentService, err := services.NewPaymentService(models, cfg.ServerBaseURL) if err != nil { return handlerDeps{}, fmt.Errorf("instantiating payment service: %w", err) @@ -180,6 +187,7 @@ func initHandlerDeps(cfg Configs) (handlerDeps, error) { channelAccountService, err := services.NewChannelAccountService(services.ChannelAccountServiceOptions{ DB: dbConnectionPool, HorizonClient: &horizonClient, + RPCService: rpcService, BaseFee: int64(cfg.BaseFee), DistributionAccountSignatureClient: cfg.DistributionAccountSignatureClient, ChannelAccountStore: store.NewChannelAccountModel(dbConnectionPool), @@ -202,11 +210,6 @@ func initHandlerDeps(cfg Configs) (handlerDeps, error) { if err != nil { return handlerDeps{}, fmt.Errorf("instantiating tss transaction service: %w", err) } - httpClient := http.Client{Timeout: time.Duration(30 * time.Second)} - rpcService, err := services.NewRPCService(cfg.RPCURL, &httpClient) - if err != nil { - return handlerDeps{}, fmt.Errorf("instantiating rpc service: %w", err) - } store, err := tssstore.NewStore(dbConnectionPool) if err != nil { diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index 80a6788..e426699 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -7,6 +7,7 @@ import ( "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" "github.com/stellar/go/txnbuild" + "github.com/stellar/wallet-backend/internal/db" "github.com/stellar/wallet-backend/internal/signing" "github.com/stellar/wallet-backend/internal/signing/store" @@ -20,6 +21,7 @@ type ChannelAccountService interface { type channelAccountService struct { DB db.ConnectionPool HorizonClient horizonclient.ClientInterface + RPCService RPCService BaseFee int64 DistributionAccountSignatureClient signing.SignatureClient ChannelAccountStore store.ChannelAccountStore @@ -82,14 +84,17 @@ func (s *channelAccountService) EnsureChannelAccounts(ctx context.Context, numbe } func (s *channelAccountService) submitCreateChannelAccountsOnChainTransaction(ctx context.Context, distributionAccountPublicKey string, ops []txnbuild.Operation) error { - distributionAccount, err := s.HorizonClient.AccountDetail(horizonclient.AccountRequest{AccountID: distributionAccountPublicKey}) + accountSeq, err := s.RPCService.GetAccountLedgerSequence(distributionAccountPublicKey) if err != nil { - return fmt.Errorf("getting account detail of distribution account: %w", err) + return fmt.Errorf("getting ledger sequence for distribution account public key: %w", err) } tx, err := txnbuild.NewTransaction( txnbuild.TransactionParams{ - SourceAccount: &distributionAccount, + SourceAccount: &txnbuild.SimpleAccount{ + AccountID: distributionAccountPublicKey, + Sequence: accountSeq, + }, IncrementSequenceNum: true, Operations: ops, BaseFee: s.BaseFee, @@ -131,6 +136,7 @@ func (s *channelAccountService) submitCreateChannelAccountsOnChainTransaction(ct type ChannelAccountServiceOptions struct { DB db.ConnectionPool HorizonClient horizonclient.ClientInterface + RPCService RPCService BaseFee int64 DistributionAccountSignatureClient signing.SignatureClient ChannelAccountStore store.ChannelAccountStore @@ -147,6 +153,10 @@ func (o *ChannelAccountServiceOptions) Validate() error { return fmt.Errorf("horizon client cannot be nil") } + if o.RPCService == nil { + return fmt.Errorf("rpc client cannot be nil") + } + if o.BaseFee < int64(txnbuild.MinBaseFee) { return fmt.Errorf("base fee is lower than the minimum network fee") } @@ -178,6 +188,7 @@ func NewChannelAccountService(opts ChannelAccountServiceOptions) (*channelAccoun return &channelAccountService{ DB: opts.DB, HorizonClient: opts.HorizonClient, + RPCService: opts.RPCService, BaseFee: opts.BaseFee, DistributionAccountSignatureClient: opts.DistributionAccountSignatureClient, ChannelAccountStore: opts.ChannelAccountStore, diff --git a/internal/utils/utils.go b/internal/utils/utils.go index f200d3d..d2e7872 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -4,6 +4,11 @@ import ( "bytes" "reflect" "strings" + + "github.com/stellar/go/strkey" + "github.com/stellar/go/xdr" + + "github.com/stellar/wallet-backend/internal/entities" ) // SanitizeUTF8 sanitizes a string to comply to the UTF-8 character set and Postgres' code zero byte constraint @@ -32,3 +37,34 @@ func UnwrapInterfaceToPointer[T any](i interface{}) *T { func PointOf[T any](value T) *T { return &value } + +func GetAccountLedgerKey(address string) (string, error) { + decoded, err := strkey.Decode(strkey.VersionByteAccountID, address) + if err != nil { + return "", err + } + var key xdr.Uint256 + copy(key[:], decoded) + keyXdr, err := xdr.LedgerKey{ + Type: xdr.LedgerEntryTypeAccount, + Account: &xdr.LedgerKeyAccount{ + AccountId: xdr.AccountId(xdr.PublicKey{ + Type: xdr.PublicKeyTypePublicKeyTypeEd25519, + Ed25519: &key, + }), + }, + }.MarshalBinaryBase64() + if err != nil { + return "", err + } + return keyXdr, nil +} + +func DecodeAccountFromLedgerEntry(entry entities.LedgerEntryResult) (xdr.AccountEntry, error) { + var data xdr.LedgerEntryData + err := xdr.SafeUnmarshalBase64(entry.DataXDR, &data) + if err != nil { + return xdr.AccountEntry{}, err + } + return data.MustAccount(), nil +} From 317d52f6cce18272360072876b3520986a07a5f7 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 19 Dec 2024 15:19:27 +0530 Subject: [PATCH 03/33] Replace horizon in account sponsorship and transaction services --- internal/serve/serve.go | 16 +++++++-------- .../services/account_sponsorship_service.go | 20 ++++++++++++------- internal/services/rpc_service.go | 8 ++++---- internal/tss/services/transaction_service.go | 6 +++++- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/internal/serve/serve.go b/internal/serve/serve.go index 0d5af25..2276087 100644 --- a/internal/serve/serve.go +++ b/internal/serve/serve.go @@ -155,6 +155,12 @@ func initHandlerDeps(cfg Configs) (handlerDeps, error) { HTTP: &http.Client{Timeout: 40 * time.Second}, } + httpClient := http.Client{Timeout: time.Duration(30 * time.Second)} + rpcService, err := services.NewRPCService(cfg.RPCURL, &httpClient) + if err != nil { + return handlerDeps{}, fmt.Errorf("instantiating rpc service: %w", err) + } + accountService, err := services.NewAccountService(models) if err != nil { return handlerDeps{}, fmt.Errorf("instantiating account service: %w", err) @@ -163,7 +169,7 @@ func initHandlerDeps(cfg Configs) (handlerDeps, error) { accountSponsorshipService, err := services.NewAccountSponsorshipService(services.AccountSponsorshipServiceOptions{ DistributionAccountSignatureClient: cfg.DistributionAccountSignatureClient, ChannelAccountSignatureClient: cfg.ChannelAccountSignatureClient, - HorizonClient: &horizonClient, + RPCService: rpcService, MaxSponsoredBaseReserves: cfg.MaxSponsoredBaseReserves, BaseFee: int64(cfg.BaseFee), Models: models, @@ -173,12 +179,6 @@ func initHandlerDeps(cfg Configs) (handlerDeps, error) { return handlerDeps{}, fmt.Errorf("instantiating account sponsorship service: %w", err) } - httpClient := http.Client{Timeout: time.Duration(30 * time.Second)} - rpcService, err := services.NewRPCService(cfg.RPCURL, &httpClient) - if err != nil { - return handlerDeps{}, fmt.Errorf("instantiating rpc service: %w", err) - } - paymentService, err := services.NewPaymentService(models, cfg.ServerBaseURL) if err != nil { return handlerDeps{}, fmt.Errorf("instantiating payment service: %w", err) @@ -203,7 +203,7 @@ func initHandlerDeps(cfg Configs) (handlerDeps, error) { tssTxService, err := tssservices.NewTransactionService(tssservices.TransactionServiceOptions{ DistributionAccountSignatureClient: cfg.DistributionAccountSignatureClient, ChannelAccountSignatureClient: cfg.ChannelAccountSignatureClient, - HorizonClient: &horizonClient, + RPCService: rpcService, BaseFee: int64(cfg.BaseFee), }) diff --git a/internal/services/account_sponsorship_service.go b/internal/services/account_sponsorship_service.go index 1d15872..8fb3cef 100644 --- a/internal/services/account_sponsorship_service.go +++ b/internal/services/account_sponsorship_service.go @@ -10,6 +10,7 @@ import ( "github.com/stellar/go/support/log" "github.com/stellar/go/txnbuild" "github.com/stellar/go/xdr" + "github.com/stellar/wallet-backend/internal/data" "github.com/stellar/wallet-backend/internal/entities" "github.com/stellar/wallet-backend/internal/signing" @@ -35,6 +36,7 @@ const ( // Sufficient to cover three average ledger close time. CreateAccountTxnTimeBounds = 18 CreateAccountTxnTimeBoundsSafetyMargin = 12 + accountNotFoundError = "entry not found for account public key" ) type AccountSponsorshipService interface { @@ -45,7 +47,7 @@ type AccountSponsorshipService interface { type accountSponsorshipService struct { DistributionAccountSignatureClient signing.SignatureClient ChannelAccountSignatureClient signing.SignatureClient - HorizonClient horizonclient.ClientInterface + RPCService RPCService MaxSponsoredBaseReserves int BaseFee int64 Models *data.Models @@ -56,11 +58,11 @@ var _ AccountSponsorshipService = (*accountSponsorshipService)(nil) func (s *accountSponsorshipService) SponsorAccountCreationTransaction(ctx context.Context, accountToSponsor string, signers []entities.Signer, supportedAssets []entities.Asset) (string, string, error) { // Check the accountToSponsor does not exist on Stellar - _, err := s.HorizonClient.AccountDetail(horizonclient.AccountRequest{AccountID: accountToSponsor}) + _, err := s.RPCService.GetAccountLedgerSequence(accountToSponsor) if err == nil { return "", "", ErrAccountAlreadyExists } - if !horizonclient.IsNotFoundError(err) { + if err.Error() != accountNotFoundError { return "", "", fmt.Errorf("getting details for account %s: %w", accountToSponsor, err) } @@ -125,14 +127,17 @@ func (s *accountSponsorshipService) SponsorAccountCreationTransaction(ctx contex return "", "", fmt.Errorf("getting channel account public key: %w", err) } - channelAccount, err := s.HorizonClient.AccountDetail(horizonclient.AccountRequest{AccountID: channelAccountPublicKey}) + channelAccountSeq, err := s.RPCService.GetAccountLedgerSequence(channelAccountPublicKey) if err != nil { - return "", "", fmt.Errorf("getting distribution account details: %w", err) + return "", "", fmt.Errorf("getting channel account sequence number: %w", err) } tx, err := txnbuild.NewTransaction( txnbuild.TransactionParams{ - SourceAccount: &channelAccount, + SourceAccount: &txnbuild.SimpleAccount{ + AccountID: channelAccountPublicKey, + Sequence: channelAccountSeq, + }, IncrementSequenceNum: true, Operations: ops, BaseFee: s.BaseFee, @@ -229,6 +234,7 @@ type AccountSponsorshipServiceOptions struct { DistributionAccountSignatureClient signing.SignatureClient ChannelAccountSignatureClient signing.SignatureClient HorizonClient horizonclient.ClientInterface + RPCService RPCService MaxSponsoredBaseReserves int BaseFee int64 Models *data.Models @@ -267,7 +273,7 @@ func NewAccountSponsorshipService(opts AccountSponsorshipServiceOptions) (*accou return &accountSponsorshipService{ DistributionAccountSignatureClient: opts.DistributionAccountSignatureClient, ChannelAccountSignatureClient: opts.ChannelAccountSignatureClient, - HorizonClient: opts.HorizonClient, + RPCService: opts.RPCService, MaxSponsoredBaseReserves: opts.MaxSponsoredBaseReserves, BaseFee: opts.BaseFee, Models: opts.Models, diff --git a/internal/services/rpc_service.go b/internal/services/rpc_service.go index 1663db0..5d96a79 100644 --- a/internal/services/rpc_service.go +++ b/internal/services/rpc_service.go @@ -137,18 +137,18 @@ func (r *rpcService) SendTransaction(transactionXDR string) (entities.RPCSendTra func (r *rpcService) GetAccountLedgerSequence(address string) (int64, error) { keyXdr, err := utils.GetAccountLedgerKey(address) if err != nil { - return 0, fmt.Errorf("getting ledger key for distribution account public key: %w", err) + return 0, fmt.Errorf("getting ledger key for account public key: %w", err) } result, err := r.GetLedgerEntries([]string{keyXdr}) if err != nil { - return 0, fmt.Errorf("getting ledger entry for distribution account public key: %w", err) + return 0, fmt.Errorf("getting ledger entry for account public key: %w", err) } if len(result.Entries) == 0 { - return 0, fmt.Errorf("entry not found for distribution account public key") + return 0, fmt.Errorf("entry not found for account public key") } accountEntry, err := utils.DecodeAccountFromLedgerEntry(result.Entries[0]) if err != nil { - return 0, fmt.Errorf("decoding account entry for distribution account public key: %w", err) + return 0, fmt.Errorf("decoding account entry for account public key: %w", err) } return int64(accountEntry.SeqNum), nil } diff --git a/internal/tss/services/transaction_service.go b/internal/tss/services/transaction_service.go index f3afd90..7028cbf 100644 --- a/internal/tss/services/transaction_service.go +++ b/internal/tss/services/transaction_service.go @@ -7,6 +7,8 @@ import ( "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/support/log" "github.com/stellar/go/txnbuild" + + "github.com/stellar/wallet-backend/internal/services" "github.com/stellar/wallet-backend/internal/signing" tsserror "github.com/stellar/wallet-backend/internal/tss/errors" ) @@ -20,6 +22,7 @@ type transactionService struct { DistributionAccountSignatureClient signing.SignatureClient ChannelAccountSignatureClient signing.SignatureClient HorizonClient horizonclient.ClientInterface + RPCService services.RPCService BaseFee int64 } @@ -29,6 +32,7 @@ type TransactionServiceOptions struct { DistributionAccountSignatureClient signing.SignatureClient ChannelAccountSignatureClient signing.SignatureClient HorizonClient horizonclient.ClientInterface + RPCService services.RPCService BaseFee int64 } @@ -59,7 +63,7 @@ func NewTransactionService(opts TransactionServiceOptions) (*transactionService, return &transactionService{ DistributionAccountSignatureClient: opts.DistributionAccountSignatureClient, ChannelAccountSignatureClient: opts.ChannelAccountSignatureClient, - HorizonClient: opts.HorizonClient, + RPCService: opts.RPCService, BaseFee: opts.BaseFee, }, nil } From 212b05a67951b0322cd66479bf97ce98b57a0330 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 19 Dec 2024 15:29:12 +0530 Subject: [PATCH 04/33] Change function naming --- internal/services/rpc_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/rpc_service.go b/internal/services/rpc_service.go index 5d96a79..3279baf 100644 --- a/internal/services/rpc_service.go +++ b/internal/services/rpc_service.go @@ -146,7 +146,7 @@ func (r *rpcService) GetAccountLedgerSequence(address string) (int64, error) { if len(result.Entries) == 0 { return 0, fmt.Errorf("entry not found for account public key") } - accountEntry, err := utils.DecodeAccountFromLedgerEntry(result.Entries[0]) + accountEntry, err := utils.GetAccountFromLedgerEntry(result.Entries[0]) if err != nil { return 0, fmt.Errorf("decoding account entry for account public key: %w", err) } From f78b9ea8875c00c38a65c6082833a32903d56676 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 19 Dec 2024 15:29:25 +0530 Subject: [PATCH 05/33] Change function naming - 2 --- internal/utils/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/utils/utils.go b/internal/utils/utils.go index d2e7872..1dc18b5 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -60,7 +60,7 @@ func GetAccountLedgerKey(address string) (string, error) { return keyXdr, nil } -func DecodeAccountFromLedgerEntry(entry entities.LedgerEntryResult) (xdr.AccountEntry, error) { +func GetAccountFromLedgerEntry(entry entities.LedgerEntryResult) (xdr.AccountEntry, error) { var data xdr.LedgerEntryData err := xdr.SafeUnmarshalBase64(entry.DataXDR, &data) if err != nil { From eb33f06ea34848eed7a7810d70941fca67582afb Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 19 Dec 2024 18:30:12 +0530 Subject: [PATCH 06/33] Fix failing tests --- .../services/account_sponsorship_service.go | 6 -- .../account_sponsorship_service_test.go | 80 ++++++------------- .../services/channel_account_service_test.go | 45 ++++++----- internal/tss/services/transaction_service.go | 20 ++--- 4 files changed, 57 insertions(+), 94 deletions(-) diff --git a/internal/services/account_sponsorship_service.go b/internal/services/account_sponsorship_service.go index 8fb3cef..b6ccc01 100644 --- a/internal/services/account_sponsorship_service.go +++ b/internal/services/account_sponsorship_service.go @@ -6,7 +6,6 @@ import ( "fmt" "slices" - "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/support/log" "github.com/stellar/go/txnbuild" "github.com/stellar/go/xdr" @@ -233,7 +232,6 @@ func (s *accountSponsorshipService) WrapTransaction(ctx context.Context, tx *txn type AccountSponsorshipServiceOptions struct { DistributionAccountSignatureClient signing.SignatureClient ChannelAccountSignatureClient signing.SignatureClient - HorizonClient horizonclient.ClientInterface RPCService RPCService MaxSponsoredBaseReserves int BaseFee int64 @@ -250,10 +248,6 @@ func (o *AccountSponsorshipServiceOptions) Validate() error { return fmt.Errorf("channel account signature client cannot be nil") } - if o.HorizonClient == nil { - return fmt.Errorf("horizon client cannot be nil") - } - if o.BaseFee < int64(txnbuild.MinBaseFee) { return fmt.Errorf("base fee is lower than the minimum network fee") } diff --git a/internal/services/account_sponsorship_service_test.go b/internal/services/account_sponsorship_service_test.go index 73beb8d..8b6e3d1 100644 --- a/internal/services/account_sponsorship_service_test.go +++ b/internal/services/account_sponsorship_service_test.go @@ -2,24 +2,22 @@ package services import ( "context" - "net/http" + "errors" "testing" - "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" "github.com/stellar/go/network" - "github.com/stellar/go/protocols/horizon" - "github.com/stellar/go/support/render/problem" "github.com/stellar/go/txnbuild" "github.com/stellar/go/xdr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stellar/wallet-backend/internal/data" "github.com/stellar/wallet-backend/internal/db" "github.com/stellar/wallet-backend/internal/db/dbtest" "github.com/stellar/wallet-backend/internal/entities" "github.com/stellar/wallet-backend/internal/signing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" ) func TestAccountSponsorshipServiceSponsorAccountCreationTransaction(t *testing.T) { @@ -34,14 +32,13 @@ func TestAccountSponsorshipServiceSponsorAccountCreationTransaction(t *testing.T signatureClient := signing.SignatureClientMock{} defer signatureClient.AssertExpectations(t) - horizonClient := horizonclient.MockClient{} - defer horizonClient.AssertExpectations(t) + mockRPCService := RPCServiceMock{} ctx := context.Background() s, err := NewAccountSponsorshipService(AccountSponsorshipServiceOptions{ DistributionAccountSignatureClient: &signatureClient, ChannelAccountSignatureClient: &signatureClient, - HorizonClient: &horizonClient, + RPCService: &mockRPCService, MaxSponsoredBaseReserves: 10, BaseFee: txnbuild.MinBaseFee, Models: models, @@ -52,12 +49,11 @@ func TestAccountSponsorshipServiceSponsorAccountCreationTransaction(t *testing.T t.Run("account_already_exists", func(t *testing.T) { accountToSponsor := keypair.MustRandom().Address() - horizonClient. - On("AccountDetail", horizonclient.AccountRequest{ - AccountID: accountToSponsor, - }). - Return(horizon.Account{}, nil). + mockRPCService. + On("GetAccountLedgerSequence", accountToSponsor). + Return(int64(1), nil). Once() + defer mockRPCService.AssertExpectations(t) txe, networkPassphrase, err := s.SponsorAccountCreationTransaction(ctx, accountToSponsor, []entities.Signer{}, []entities.Asset{}) assert.ErrorIs(t, ErrAccountAlreadyExists, err) @@ -68,17 +64,11 @@ func TestAccountSponsorshipServiceSponsorAccountCreationTransaction(t *testing.T t.Run("invalid_signers_weight", func(t *testing.T) { accountToSponsor := keypair.MustRandom().Address() - horizonClient. - On("AccountDetail", horizonclient.AccountRequest{ - AccountID: accountToSponsor, - }). - Return(horizon.Account{}, horizonclient.Error{ - Response: &http.Response{}, - Problem: problem.P{ - Type: "https://stellar.org/horizon-errors/not_found", - }, - }). + mockRPCService. + On("GetAccountLedgerSequence", accountToSponsor). + Return(int64(0), errors.New(accountNotFoundError)). Once() + defer mockRPCService.AssertExpectations(t) signers := []entities.Signer{ { @@ -97,17 +87,11 @@ func TestAccountSponsorshipServiceSponsorAccountCreationTransaction(t *testing.T t.Run("sponsorship_limit_reached", func(t *testing.T) { accountToSponsor := keypair.MustRandom().Address() - horizonClient. - On("AccountDetail", horizonclient.AccountRequest{ - AccountID: accountToSponsor, - }). - Return(horizon.Account{}, horizonclient.Error{ - Response: &http.Response{}, - Problem: problem.P{ - Type: "https://stellar.org/horizon-errors/not_found", - }, - }). + mockRPCService. + On("GetAccountLedgerSequence", accountToSponsor). + Return(int64(0), errors.New(accountNotFoundError)). Once() + defer mockRPCService.AssertExpectations(t) signers := []entities.Signer{ { @@ -202,25 +186,14 @@ func TestAccountSponsorshipServiceSponsorAccountCreationTransaction(t *testing.T }, } - horizonClient. - On("AccountDetail", horizonclient.AccountRequest{ - AccountID: accountToSponsor, - }). - Return(horizon.Account{}, horizonclient.Error{ - Response: &http.Response{}, - Problem: problem.P{ - Type: "https://stellar.org/horizon-errors/not_found", - }, - }). + mockRPCService. + On("GetAccountLedgerSequence", accountToSponsor). + Return(int64(0), errors.New(accountNotFoundError)). Once(). - On("AccountDetail", horizonclient.AccountRequest{ - AccountID: distributionAccount.Address(), - }). - Return(horizon.Account{ - AccountID: distributionAccount.Address(), - Sequence: 1, - }, nil). + On("GetAccountLedgerSequence", distributionAccount.Address()). + Return(int64(1), nil). Once() + defer mockRPCService.AssertExpectations(t) signedTx := txnbuild.Transaction{} signatureClient. @@ -336,14 +309,11 @@ func TestAccountSponsorshipServiceWrapTransaction(t *testing.T) { signatureClient := signing.SignatureClientMock{} defer signatureClient.AssertExpectations(t) - horizonClient := horizonclient.MockClient{} - defer horizonClient.AssertExpectations(t) ctx := context.Background() s, err := NewAccountSponsorshipService(AccountSponsorshipServiceOptions{ DistributionAccountSignatureClient: &signatureClient, ChannelAccountSignatureClient: &signatureClient, - HorizonClient: &horizonClient, MaxSponsoredBaseReserves: 10, BaseFee: txnbuild.MinBaseFee, Models: models, diff --git a/internal/services/channel_account_service_test.go b/internal/services/channel_account_service_test.go index e2bdaac..fcdcce1 100644 --- a/internal/services/channel_account_service_test.go +++ b/internal/services/channel_account_service_test.go @@ -12,14 +12,15 @@ import ( "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/support/render/problem" "github.com/stellar/go/txnbuild" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stellar/wallet-backend/internal/db" "github.com/stellar/wallet-backend/internal/db/dbtest" "github.com/stellar/wallet-backend/internal/signing" "github.com/stellar/wallet-backend/internal/signing/store" signingutils "github.com/stellar/wallet-backend/internal/signing/utils" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" ) func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { @@ -32,6 +33,7 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { ctx := context.Background() horizonClient := horizonclient.MockClient{} + mockRPCService := RPCServiceMock{} signatureClient := signing.SignatureClientMock{} channelAccountStore := store.ChannelAccountStoreMock{} privateKeyEncrypter := signingutils.DefaultPrivateKeyEncrypter{} @@ -39,6 +41,7 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { s, err := NewChannelAccountService(ChannelAccountServiceOptions{ DB: dbConnectionPool, HorizonClient: &horizonClient, + RPCService: &mockRPCService, BaseFee: 100 * txnbuild.MinBaseFee, DistributionAccountSignatureClient: &signatureClient, ChannelAccountStore: &channelAccountStore, @@ -102,12 +105,6 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { defer signatureClient.AssertExpectations(t) horizonClient. - On("AccountDetail", horizonclient.AccountRequest{AccountID: distributionAccount.Address()}). - Return(horizon.Account{ - AccountID: distributionAccount.Address(), - Sequence: 123, - }, nil). - Once(). On("SubmitTransaction", mock.AnythingOfType("*txnbuild.Transaction")). Return(horizon.Transaction{}, horizonclient.Error{ Response: &http.Response{}, @@ -121,6 +118,12 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { Once() defer horizonClient.AssertExpectations(t) + mockRPCService. + On("GetAccountLedgerSequence", distributionAccount.Address()). + Return(int64(123), nil). + Once() + defer mockRPCService.AssertExpectations(t) + err := s.EnsureChannelAccounts(ctx, 5) require.Error(t, err) @@ -173,12 +176,6 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { defer signatureClient.AssertExpectations(t) horizonClient. - On("AccountDetail", horizonclient.AccountRequest{AccountID: distributionAccount.Address()}). - Return(horizon.Account{ - AccountID: distributionAccount.Address(), - Sequence: 123, - }, nil). - Once(). On("SubmitTransaction", mock.AnythingOfType("*txnbuild.Transaction")). Return(horizon.Transaction{}, horizonclient.Error{ Response: &http.Response{}, @@ -192,6 +189,12 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { Once() defer horizonClient.AssertExpectations(t) + mockRPCService. + On("GetAccountLedgerSequence", distributionAccount.Address()). + Return(int64(123), nil). + Once() + defer mockRPCService.AssertExpectations(t) + err := s.EnsureChannelAccounts(ctx, 5) assert.EqualError(t, err, `submitting create channel accounts on chain transaction: submitting transaction: Type: , Title: Some bad request error, Status: 400, Detail: Bad Request, Extras: map[]: horizon error: "Some bad request error" - check horizon.Error.Problem for more information`) }) @@ -240,17 +243,17 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { defer signatureClient.AssertExpectations(t) horizonClient. - On("AccountDetail", horizonclient.AccountRequest{AccountID: distributionAccount.Address()}). - Return(horizon.Account{ - AccountID: distributionAccount.Address(), - Sequence: 123, - }, nil). - Once(). On("SubmitTransaction", mock.AnythingOfType("*txnbuild.Transaction")). Return(horizon.Transaction{}, nil). Once() defer horizonClient.AssertExpectations(t) + mockRPCService. + On("GetAccountLedgerSequence", distributionAccount.Address()). + Return(int64(123), nil). + Once() + defer mockRPCService.AssertExpectations(t) + channelAccountStore. On("BatchInsert", ctx, dbConnectionPool, mock.AnythingOfType("[]*store.ChannelAccount")). Run(func(args mock.Arguments) { diff --git a/internal/tss/services/transaction_service.go b/internal/tss/services/transaction_service.go index 7028cbf..8aaad05 100644 --- a/internal/tss/services/transaction_service.go +++ b/internal/tss/services/transaction_service.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/support/log" "github.com/stellar/go/txnbuild" @@ -21,7 +20,6 @@ type TransactionService interface { type transactionService struct { DistributionAccountSignatureClient signing.SignatureClient ChannelAccountSignatureClient signing.SignatureClient - HorizonClient horizonclient.ClientInterface RPCService services.RPCService BaseFee int64 } @@ -31,7 +29,6 @@ var _ TransactionService = (*transactionService)(nil) type TransactionServiceOptions struct { DistributionAccountSignatureClient signing.SignatureClient ChannelAccountSignatureClient signing.SignatureClient - HorizonClient horizonclient.ClientInterface RPCService services.RPCService BaseFee int64 } @@ -45,10 +42,6 @@ func (o *TransactionServiceOptions) ValidateOptions() error { return fmt.Errorf("channel account signature client cannot be nil") } - if o.HorizonClient == nil { - return fmt.Errorf("horizon client cannot be nil") - } - if o.BaseFee < int64(txnbuild.MinBaseFee) { return fmt.Errorf("base fee is lower than the minimum network fee") } @@ -104,9 +97,9 @@ func (t *transactionService) SignAndBuildNewFeeBumpTransaction(ctx context.Conte if err != nil { return nil, fmt.Errorf("getting channel account public key: %w", err) } - channelAccount, err := t.HorizonClient.AccountDetail(horizonclient.AccountRequest{AccountID: channelAccountPublicKey}) + channelAccountSeq, err := t.RPCService.GetAccountLedgerSequence(channelAccountPublicKey) if err != nil { - return nil, fmt.Errorf("getting channel account details from horizon: %w", err) + return nil, fmt.Errorf("getting channel account ledger sequence: %w", err) } distributionAccountPublicKey, err := t.DistributionAccountSignatureClient.GetAccountPublicKey(ctx) @@ -122,9 +115,12 @@ func (t *transactionService) SignAndBuildNewFeeBumpTransaction(ctx context.Conte tx, err := txnbuild.NewTransaction( txnbuild.TransactionParams{ - SourceAccount: &channelAccount, - Operations: operations, - BaseFee: int64(t.BaseFee), + SourceAccount: &txnbuild.SimpleAccount{ + AccountID: channelAccountPublicKey, + Sequence: channelAccountSeq, + }, + Operations: operations, + BaseFee: int64(t.BaseFee), Preconditions: txnbuild.Preconditions{ TimeBounds: txnbuild.NewTimeout(120), }, From 1c17a60c5bb64e2487c3f9e2941b930ccc078c87 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 19 Dec 2024 18:44:58 +0530 Subject: [PATCH 07/33] Fix failing tests - 2 --- .../services/account_sponsorship_service.go | 4 + internal/tss/services/transaction_service.go | 4 + .../tss/services/transaction_service_test.go | 83 +++++++++---------- 3 files changed, 46 insertions(+), 45 deletions(-) diff --git a/internal/services/account_sponsorship_service.go b/internal/services/account_sponsorship_service.go index b6ccc01..9877a23 100644 --- a/internal/services/account_sponsorship_service.go +++ b/internal/services/account_sponsorship_service.go @@ -244,6 +244,10 @@ func (o *AccountSponsorshipServiceOptions) Validate() error { return fmt.Errorf("distribution account signature client cannot be nil") } + if o.RPCService == nil { + return fmt.Errorf("rpc client cannot be nil") + } + if o.ChannelAccountSignatureClient == nil { return fmt.Errorf("channel account signature client cannot be nil") } diff --git a/internal/tss/services/transaction_service.go b/internal/tss/services/transaction_service.go index 8aaad05..9bdd729 100644 --- a/internal/tss/services/transaction_service.go +++ b/internal/tss/services/transaction_service.go @@ -38,6 +38,10 @@ func (o *TransactionServiceOptions) ValidateOptions() error { return fmt.Errorf("distribution account signature client cannot be nil") } + if o.RPCService == nil { + return fmt.Errorf("rpc client cannot be nil") + } + if o.ChannelAccountSignatureClient == nil { return fmt.Errorf("channel account signature client cannot be nil") } diff --git a/internal/tss/services/transaction_service_test.go b/internal/tss/services/transaction_service_test.go index 3035932..528eb43 100644 --- a/internal/tss/services/transaction_service_test.go +++ b/internal/tss/services/transaction_service_test.go @@ -5,15 +5,15 @@ import ( "errors" "testing" - "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" - "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/txnbuild" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/stellar/wallet-backend/internal/services" "github.com/stellar/wallet-backend/internal/signing" tsserror "github.com/stellar/wallet-backend/internal/tss/errors" "github.com/stellar/wallet-backend/internal/tss/utils" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ) func TestValidateOptions(t *testing.T) { @@ -21,7 +21,7 @@ func TestValidateOptions(t *testing.T) { opts := TransactionServiceOptions{ DistributionAccountSignatureClient: nil, ChannelAccountSignatureClient: &signing.SignatureClientMock{}, - HorizonClient: &horizonclient.MockClient{}, + RPCService: &services.RPCServiceMock{}, BaseFee: 114, } err := opts.ValidateOptions() @@ -33,29 +33,29 @@ func TestValidateOptions(t *testing.T) { opts := TransactionServiceOptions{ DistributionAccountSignatureClient: &signing.SignatureClientMock{}, ChannelAccountSignatureClient: nil, - HorizonClient: &horizonclient.MockClient{}, + RPCService: &services.RPCServiceMock{}, BaseFee: 114, } err := opts.ValidateOptions() assert.Equal(t, "channel account signature client cannot be nil", err.Error()) }) - t.Run("return_error_when_horizon_client_nil", func(t *testing.T) { + t.Run("return_error_when_rpc_client_nil", func(t *testing.T) { opts := TransactionServiceOptions{ DistributionAccountSignatureClient: &signing.SignatureClientMock{}, ChannelAccountSignatureClient: &signing.SignatureClientMock{}, - HorizonClient: nil, + RPCService: nil, BaseFee: 114, } err := opts.ValidateOptions() - assert.Equal(t, "horizon client cannot be nil", err.Error()) + assert.Equal(t, "rpc client cannot be nil", err.Error()) }) t.Run("return_error_when_base_fee_too_low", func(t *testing.T) { opts := TransactionServiceOptions{ DistributionAccountSignatureClient: &signing.SignatureClientMock{}, ChannelAccountSignatureClient: &signing.SignatureClientMock{}, - HorizonClient: &horizonclient.MockClient{}, + RPCService: &services.RPCServiceMock{}, BaseFee: txnbuild.MinBaseFee - 10, } err := opts.ValidateOptions() @@ -85,12 +85,11 @@ func TestSignAndBuildNewFeeBumpTransaction(t *testing.T) { defer distributionAccountSignatureClient.AssertExpectations(t) channelAccountSignatureClient := signing.SignatureClientMock{} defer channelAccountSignatureClient.AssertExpectations(t) - horizonClient := horizonclient.MockClient{} - defer horizonClient.AssertExpectations(t) + mockRPCService := &services.RPCServiceMock{} txService, _ := NewTransactionService(TransactionServiceOptions{ DistributionAccountSignatureClient: &distributionAccountSignatureClient, ChannelAccountSignatureClient: &channelAccountSignatureClient, - HorizonClient: &horizonClient, + RPCService: mockRPCService, BaseFee: 114, }) @@ -113,23 +112,22 @@ func TestSignAndBuildNewFeeBumpTransaction(t *testing.T) { assert.Equal(t, "getting channel account public key: channel accounts unavailable", err.Error()) }) - t.Run("horizon_client_get_account_detail_err", func(t *testing.T) { + t.Run("rpc_client_get_account_seq_err", func(t *testing.T) { channelAccount := keypair.MustRandom() channelAccountSignatureClient. On("GetAccountPublicKey", context.Background()). Return(channelAccount.Address(), nil). Once() - horizonClient. - On("AccountDetail", horizonclient.AccountRequest{ - AccountID: channelAccount.Address(), - }). - Return(horizon.Account{}, errors.New("horizon down")). + mockRPCService. + On("GetAccountLedgerSequence", channelAccount.Address()). + Return(int64(0), errors.New("rpc service down")). Once() + defer mockRPCService.AssertExpectations(t) feeBumpTx, err := txService.SignAndBuildNewFeeBumpTransaction(context.Background(), txStr) assert.Empty(t, feeBumpTx) - assert.Equal(t, "getting channel account details from horizon: horizon down", err.Error()) + assert.Equal(t, "getting channel account ledger sequence: rpc service down", err.Error()) }) t.Run("distribution_account_signature_client_get_account_public_key_err", func(t *testing.T) { @@ -144,12 +142,11 @@ func TestSignAndBuildNewFeeBumpTransaction(t *testing.T) { Return("", errors.New("client down")). Once() - horizonClient. - On("AccountDetail", horizonclient.AccountRequest{ - AccountID: channelAccount.Address(), - }). - Return(horizon.Account{AccountID: channelAccount.Address(), Sequence: 1}, nil). + mockRPCService. + On("GetAccountLedgerSequence", channelAccount.Address()). + Return(int64(1), nil). Once() + defer mockRPCService.AssertExpectations(t) feeBumpTx, err := txService.SignAndBuildNewFeeBumpTransaction(context.Background(), txStr) assert.Empty(t, feeBumpTx) @@ -171,12 +168,11 @@ func TestSignAndBuildNewFeeBumpTransaction(t *testing.T) { Return(distributionAccount.Address(), nil). Once() - horizonClient. - On("AccountDetail", horizonclient.AccountRequest{ - AccountID: channelAccount.Address(), - }). - Return(horizon.Account{AccountID: channelAccount.Address(), Sequence: 1}, nil). + mockRPCService. + On("GetAccountLedgerSequence", channelAccount.Address()). + Return(int64(1), nil). Once() + defer mockRPCService.AssertExpectations(t) feeBumpTx, err := txService.SignAndBuildNewFeeBumpTransaction(context.Background(), txStr) assert.Empty(t, feeBumpTx) @@ -203,12 +199,11 @@ func TestSignAndBuildNewFeeBumpTransaction(t *testing.T) { Return(nil, errors.New("unable to sign")). Once() - horizonClient. - On("AccountDetail", horizonclient.AccountRequest{ - AccountID: channelAccount.Address(), - }). - Return(horizon.Account{AccountID: channelAccount.Address(), Sequence: 1}, nil). + mockRPCService. + On("GetAccountLedgerSequence", channelAccount.Address()). + Return(int64(1), nil). Once() + defer mockRPCService.AssertExpectations(t) feeBumpTx, err := txService.SignAndBuildNewFeeBumpTransaction(context.Background(), txStr) assert.Empty(t, feeBumpTx) @@ -237,12 +232,11 @@ func TestSignAndBuildNewFeeBumpTransaction(t *testing.T) { Return(nil, errors.New("unable to sign")). Once() - horizonClient. - On("AccountDetail", horizonclient.AccountRequest{ - AccountID: channelAccount.Address(), - }). - Return(horizon.Account{AccountID: channelAccount.Address(), Sequence: 1}, nil). + mockRPCService. + On("GetAccountLedgerSequence", channelAccount.Address()). + Return(int64(1), nil). Once() + defer mockRPCService.AssertExpectations(t) feeBumpTx, err := txService.SignAndBuildNewFeeBumpTransaction(context.Background(), txStr) assert.Empty(t, feeBumpTx) @@ -278,12 +272,11 @@ func TestSignAndBuildNewFeeBumpTransaction(t *testing.T) { Return(testFeeBumpTx, nil). Once() - horizonClient. - On("AccountDetail", horizonclient.AccountRequest{ - AccountID: channelAccount.Address(), - }). - Return(horizon.Account{AccountID: channelAccount.Address(), Sequence: 1}, nil). + mockRPCService. + On("GetAccountLedgerSequence", channelAccount.Address()). + Return(int64(1), nil). Once() + defer mockRPCService.AssertExpectations(t) feeBumpTx, err := txService.SignAndBuildNewFeeBumpTransaction(context.Background(), txStr) assert.Equal(t, feeBumpTx, testFeeBumpTx) From d4801944e9c1fbadac439bb66950c7ed0f480e79 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 20 Dec 2024 00:17:41 +0530 Subject: [PATCH 08/33] Fix failing tests - 3 --- internal/services/account_sponsorship_service_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/services/account_sponsorship_service_test.go b/internal/services/account_sponsorship_service_test.go index 8b6e3d1..42ce4d3 100644 --- a/internal/services/account_sponsorship_service_test.go +++ b/internal/services/account_sponsorship_service_test.go @@ -310,10 +310,13 @@ func TestAccountSponsorshipServiceWrapTransaction(t *testing.T) { signatureClient := signing.SignatureClientMock{} defer signatureClient.AssertExpectations(t) + mockRPCService := RPCServiceMock{} + ctx := context.Background() s, err := NewAccountSponsorshipService(AccountSponsorshipServiceOptions{ DistributionAccountSignatureClient: &signatureClient, ChannelAccountSignatureClient: &signatureClient, + RPCService: &mockRPCService, MaxSponsoredBaseReserves: 10, BaseFee: txnbuild.MinBaseFee, Models: models, From cd7c6f0103380abf92209ad492cbd26f258f67c0 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 20 Dec 2024 14:27:06 +0530 Subject: [PATCH 09/33] Fix tests after merging from master --- internal/tss/services/transaction_service.go | 5 ----- internal/tss/services/transaction_service_test.go | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/internal/tss/services/transaction_service.go b/internal/tss/services/transaction_service.go index 2f24c13..64b3559 100644 --- a/internal/tss/services/transaction_service.go +++ b/internal/tss/services/transaction_service.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "github.com/stellar/go/support/log" "github.com/stellar/go/txnbuild" "github.com/stellar/wallet-backend/internal/services" @@ -46,10 +45,6 @@ func (o *TransactionServiceOptions) ValidateOptions() error { return fmt.Errorf("channel account signature client cannot be nil") } - if o.HorizonClient == nil { - return fmt.Errorf("horizon client cannot be nil") - } - if o.BaseFee < int64(txnbuild.MinBaseFee) { return fmt.Errorf("base fee is lower than the minimum network fee") } diff --git a/internal/tss/services/transaction_service_test.go b/internal/tss/services/transaction_service_test.go index 9c05c0d..05b2d75 100644 --- a/internal/tss/services/transaction_service_test.go +++ b/internal/tss/services/transaction_service_test.go @@ -104,7 +104,7 @@ func TestBuildAndSignTransactionWithChannelAccount(t *testing.T) { channelAccountSignatureClient.AssertExpectations(t) assert.Empty(t, tx) - assert.Equal(t, "getting channel account details from horizon: horizon down", err.Error()) + assert.Equal(t, "getting channel account ledger sequence: rpc service down", err.Error()) }) t.Run("build_tx_fails", func(t *testing.T) { @@ -195,7 +195,7 @@ func TestBuildFeeBumpTransaction(t *testing.T) { txService, _ := NewTransactionService(TransactionServiceOptions{ DistributionAccountSignatureClient: &distributionAccountSignatureClient, ChannelAccountSignatureClient: &channelAccountSignatureClient, - RPCService: mockRPCService, + RPCService: mockRPCService, BaseFee: 114, }) From 6caa7cbfd833aef281b0547f5e7e93b50999404a Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 20 Dec 2024 16:38:46 +0530 Subject: [PATCH 10/33] Replace horizon with tss.Router for creating channel accounts --- cmd/channel_account.go | 12 +- cmd/serve.go | 2 +- cmd/utils/global_options.go | 13 +- internal/serve/serve.go | 49 +++--- internal/services/channel_account_service.go | 41 ++--- .../services/channel_account_service_test.go | 164 ++---------------- 6 files changed, 59 insertions(+), 222 deletions(-) diff --git a/cmd/channel_account.go b/cmd/channel_account.go index f3aeb2e..2674f81 100644 --- a/cmd/channel_account.go +++ b/cmd/channel_account.go @@ -2,14 +2,12 @@ package cmd import ( "fmt" - "net/http" "strconv" - "time" "github.com/spf13/cobra" - "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/support/config" "github.com/stellar/go/support/log" + "github.com/stellar/wallet-backend/cmd/utils" "github.com/stellar/wallet-backend/internal/db" "github.com/stellar/wallet-backend/internal/services" @@ -19,7 +17,6 @@ import ( type channelAccountCmdConfigOptions struct { DatabaseURL string - HorizonClientURL string NetworkPassphrase string BaseFee int DistributionAccountPrivateKey string @@ -36,7 +33,6 @@ func (c *channelAccountCmd) Command() *cobra.Command { utils.DatabaseURLOption(&cfg.DatabaseURL), utils.NetworkPassphraseOption(&cfg.NetworkPassphrase), utils.BaseFeeOption(&cfg.BaseFee), - utils.HorizonClientURLOption(&cfg.HorizonClientURL), utils.ChannelAccountEncryptionPassphraseOption(&cfg.EncryptionPassphrase), } @@ -78,11 +74,7 @@ func (c *channelAccountCmd) Command() *cobra.Command { channelAccountModel := store.ChannelAccountModel{DB: dbConnectionPool} privateKeyEncrypter := signingutils.DefaultPrivateKeyEncrypter{} c.channelAccountService, err = services.NewChannelAccountService(services.ChannelAccountServiceOptions{ - DB: dbConnectionPool, - HorizonClient: &horizonclient.Client{ - HorizonURL: cfg.HorizonClientURL, - HTTP: &http.Client{Timeout: 40 * time.Second}, - }, + DB: dbConnectionPool, BaseFee: int64(cfg.BaseFee), DistributionAccountSignatureClient: signatureClient, ChannelAccountStore: &channelAccountModel, diff --git a/cmd/serve.go b/cmd/serve.go index 3968911..afbcaeb 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -8,6 +8,7 @@ import ( "github.com/spf13/cobra" "github.com/stellar/go/support/config" "github.com/stellar/go/support/log" + "github.com/stellar/wallet-backend/cmd/utils" "github.com/stellar/wallet-backend/internal/apptracker/sentry" "github.com/stellar/wallet-backend/internal/db" @@ -28,7 +29,6 @@ func (c *serveCmd) Command() *cobra.Command { utils.LogLevelOption(&cfg.LogLevel), utils.NetworkPassphraseOption(&cfg.NetworkPassphrase), utils.BaseFeeOption(&cfg.BaseFee), - utils.HorizonClientURLOption(&cfg.HorizonClientURL), utils.RPCURLOption(&cfg.RPCURL), utils.RPCCallerChannelBufferSizeOption(&cfg.RPCCallerServiceChannelBufferSize), utils.RPCCallerChannelMaxWorkersOption(&cfg.RPCCallerServiceChannelMaxWorkers), diff --git a/cmd/utils/global_options.go b/cmd/utils/global_options.go index 1b719b8..5973464 100644 --- a/cmd/utils/global_options.go +++ b/cmd/utils/global_options.go @@ -4,10 +4,10 @@ import ( "go/types" "github.com/sirupsen/logrus" - "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/network" "github.com/stellar/go/support/config" "github.com/stellar/go/txnbuild" + "github.com/stellar/wallet-backend/internal/signing" ) @@ -56,17 +56,6 @@ func BaseFeeOption(configKey *int) *config.ConfigOption { } } -func HorizonClientURLOption(configKey *string) *config.ConfigOption { - return &config.ConfigOption{ - Name: "horizon-url", - Usage: "The URL of the Stellar Horizon server which this application will communicate with.", - OptType: types.String, - ConfigKey: configKey, - FlagDefault: horizonclient.DefaultTestNetClient.HorizonURL, - Required: true, - } -} - func RPCURLOption(configKey *string) *config.ConfigOption { return &config.ConfigOption{ Name: "rpc-url", diff --git a/internal/serve/serve.go b/internal/serve/serve.go index fdd1f0b..e5153e8 100644 --- a/internal/serve/serve.go +++ b/internal/serve/serve.go @@ -8,7 +8,6 @@ import ( "github.com/go-chi/chi" "github.com/sirupsen/logrus" - "github.com/stellar/go/clients/horizonclient" supporthttp "github.com/stellar/go/support/http" "github.com/stellar/go/support/log" "github.com/stellar/go/support/render/health" @@ -61,7 +60,6 @@ type Configs struct { NetworkPassphrase string MaxSponsoredBaseReserves int BaseFee int - HorizonClientURL string DistributionAccountSignatureClient signing.SignatureClient ChannelAccountSignatureClient signing.SignatureClient // TSS @@ -151,11 +149,6 @@ func initHandlerDeps(cfg Configs) (handlerDeps, error) { return handlerDeps{}, fmt.Errorf("instantiating stellar signature verifier: %w", err) } - horizonClient := horizonclient.Client{ - HorizonURL: cfg.HorizonClientURL, - HTTP: &http.Client{Timeout: 40 * time.Second}, - } - httpClient := http.Client{Timeout: time.Duration(30 * time.Second)} rpcService, err := services.NewRPCService(cfg.RPCURL, &httpClient) if err != nil { @@ -185,21 +178,6 @@ func initHandlerDeps(cfg Configs) (handlerDeps, error) { return handlerDeps{}, fmt.Errorf("instantiating payment service: %w", err) } - channelAccountService, err := services.NewChannelAccountService(services.ChannelAccountServiceOptions{ - DB: dbConnectionPool, - HorizonClient: &horizonClient, - RPCService: rpcService, - BaseFee: int64(cfg.BaseFee), - DistributionAccountSignatureClient: cfg.DistributionAccountSignatureClient, - ChannelAccountStore: store.NewChannelAccountModel(dbConnectionPool), - PrivateKeyEncrypter: &signingutils.DefaultPrivateKeyEncrypter{}, - EncryptionPassphrase: cfg.EncryptionPassphrase, - }) - if err != nil { - return handlerDeps{}, fmt.Errorf("instantiating channel account service: %w", err) - } - go ensureChannelAccounts(channelAccountService, int64(cfg.NumberOfChannelAccounts)) - // TSS setup tssTxService, err := tssservices.NewTransactionService(tssservices.TransactionServiceOptions{ DistributionAccountSignatureClient: cfg.DistributionAccountSignatureClient, @@ -212,19 +190,19 @@ func initHandlerDeps(cfg Configs) (handlerDeps, error) { return handlerDeps{}, fmt.Errorf("instantiating tss transaction service: %w", err) } - store, err := tssstore.NewStore(dbConnectionPool) + tssStore, err := tssstore.NewStore(dbConnectionPool) if err != nil { return handlerDeps{}, fmt.Errorf("instantiating tss store: %w", err) } txManager := tssservices.NewTransactionManager(tssservices.TransactionManagerConfigs{ TxService: tssTxService, RPCService: rpcService, - Store: store, + Store: tssStore, }) rpcCallerChannel := tsschannel.NewRPCCallerChannel(tsschannel.RPCCallerChannelConfigs{ TxManager: txManager, - Store: store, + Store: tssStore, MaxBufferSize: cfg.RPCCallerServiceChannelBufferSize, MaxWorkers: cfg.RPCCallerServiceChannelMaxWorkers, }) @@ -248,7 +226,7 @@ func initHandlerDeps(cfg Configs) (handlerDeps, error) { httpClient = http.Client{Timeout: time.Duration(30 * time.Second)} webhookChannel := tsschannel.NewWebhookChannel(tsschannel.WebhookChannelConfigs{ HTTPClient: &httpClient, - Store: store, + Store: tssStore, MaxBufferSize: cfg.WebhookHandlerServiceChannelMaxBufferSize, MaxWorkers: cfg.WebhookHandlerServiceChannelMaxWorkers, MaxRetries: cfg.WebhookHandlerServiceChannelMaxRetries, @@ -266,11 +244,26 @@ func initHandlerDeps(cfg Configs) (handlerDeps, error) { errorJitterChannel.SetRouter(router) errorNonJitterChannel.SetRouter(router) - poolPopulator, err := tssservices.NewPoolPopulator(router, store, rpcService) + poolPopulator, err := tssservices.NewPoolPopulator(router, tssStore, rpcService) if err != nil { return handlerDeps{}, fmt.Errorf("instantiating tss pool populator") } + channelAccountService, err := services.NewChannelAccountService(services.ChannelAccountServiceOptions{ + DB: dbConnectionPool, + RPCService: rpcService, + BaseFee: int64(cfg.BaseFee), + Router: router, + DistributionAccountSignatureClient: cfg.DistributionAccountSignatureClient, + ChannelAccountStore: store.NewChannelAccountModel(dbConnectionPool), + PrivateKeyEncrypter: &signingutils.DefaultPrivateKeyEncrypter{}, + EncryptionPassphrase: cfg.EncryptionPassphrase, + }) + if err != nil { + return handlerDeps{}, fmt.Errorf("instantiating channel account service: %w", err) + } + go ensureChannelAccounts(channelAccountService, int64(cfg.NumberOfChannelAccounts)) + return handlerDeps{ Models: models, SignatureVerifier: signatureVerifier, @@ -287,7 +280,7 @@ func initHandlerDeps(cfg Configs) (handlerDeps, error) { WebhookChannel: webhookChannel, TSSRouter: router, PoolPopulator: poolPopulator, - TSSStore: store, + TSSStore: tssStore, TSSTransactionService: tssTxService, }, nil } diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index e426699..0c58b4f 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" "github.com/stellar/go/txnbuild" @@ -12,6 +11,8 @@ import ( "github.com/stellar/wallet-backend/internal/signing" "github.com/stellar/wallet-backend/internal/signing/store" signingutils "github.com/stellar/wallet-backend/internal/signing/utils" + "github.com/stellar/wallet-backend/internal/tss" + "github.com/stellar/wallet-backend/internal/tss/router" ) type ChannelAccountService interface { @@ -20,9 +21,9 @@ type ChannelAccountService interface { type channelAccountService struct { DB db.ConnectionPool - HorizonClient horizonclient.ClientInterface RPCService RPCService BaseFee int64 + Router router.Router DistributionAccountSignatureClient signing.SignatureClient ChannelAccountStore store.ChannelAccountStore PrivateKeyEncrypter signingutils.PrivateKeyEncrypter @@ -115,29 +116,29 @@ func (s *channelAccountService) submitCreateChannelAccountsOnChainTransaction(ct return fmt.Errorf("getting transaction hash: %w", err) } - _, err = s.HorizonClient.SubmitTransaction(signedTx) + signedTxXDR, err := signedTx.Base64() if err != nil { - if hError := horizonclient.GetError(err); hError != nil { - hProblem := hError.Problem - if hProblem.Type == "https://stellar.org/horizon-errors/timeout" { - return fmt.Errorf("horizon request timed out while creating a channel account. Transaction hash: %s", hash) - } - - errString := fmt.Sprintf("Type: %s, Title: %s, Status: %d, Detail: %s, Extras: %v", hProblem.Type, hProblem.Title, hProblem.Status, hProblem.Detail, hProblem.Extras) - return fmt.Errorf("submitting transaction: %s: %w", errString, err) - } else { - return fmt.Errorf("submitting transaction: %w", err) - } + return fmt.Errorf("getting transaction envelope: %w", err) + } + + payload := tss.Payload{ + TransactionXDR: signedTxXDR, + WebhookURL: "http://localhost:8001/internal/webhook", + FeeBump: false, } + err = s.Router.Route(payload) + if err != nil { + return fmt.Errorf("routing payload for transaction hash: %s: %w", hash, err) + } return nil } type ChannelAccountServiceOptions struct { DB db.ConnectionPool - HorizonClient horizonclient.ClientInterface RPCService RPCService BaseFee int64 + Router router.Router DistributionAccountSignatureClient signing.SignatureClient ChannelAccountStore store.ChannelAccountStore PrivateKeyEncrypter signingutils.PrivateKeyEncrypter @@ -149,10 +150,6 @@ func (o *ChannelAccountServiceOptions) Validate() error { return fmt.Errorf("DB cannot be nil") } - if o.HorizonClient == nil { - return fmt.Errorf("horizon client cannot be nil") - } - if o.RPCService == nil { return fmt.Errorf("rpc client cannot be nil") } @@ -161,6 +158,10 @@ func (o *ChannelAccountServiceOptions) Validate() error { return fmt.Errorf("base fee is lower than the minimum network fee") } + if o.Router == nil { + return fmt.Errorf("router cannot be nil") + } + if o.DistributionAccountSignatureClient == nil { return fmt.Errorf("distribution account signature client cannot be nil") } @@ -187,9 +188,9 @@ func NewChannelAccountService(opts ChannelAccountServiceOptions) (*channelAccoun return &channelAccountService{ DB: opts.DB, - HorizonClient: opts.HorizonClient, RPCService: opts.RPCService, BaseFee: opts.BaseFee, + Router: opts.Router, DistributionAccountSignatureClient: opts.DistributionAccountSignatureClient, ChannelAccountStore: opts.ChannelAccountStore, PrivateKeyEncrypter: opts.PrivateKeyEncrypter, diff --git a/internal/services/channel_account_service_test.go b/internal/services/channel_account_service_test.go index fcdcce1..6153d78 100644 --- a/internal/services/channel_account_service_test.go +++ b/internal/services/channel_account_service_test.go @@ -2,15 +2,10 @@ package services import ( "context" - "fmt" - "net/http" "testing" - "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" "github.com/stellar/go/network" - "github.com/stellar/go/protocols/horizon" - "github.com/stellar/go/support/render/problem" "github.com/stellar/go/txnbuild" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -21,6 +16,11 @@ import ( "github.com/stellar/wallet-backend/internal/signing" "github.com/stellar/wallet-backend/internal/signing/store" signingutils "github.com/stellar/wallet-backend/internal/signing/utils" + "github.com/stellar/wallet-backend/internal/tss/router" +) + +const ( + expectedSignedTxXDR = "AAAAAgAAAABzJXo57U3V8mgLwf1pJowWwdKaRnE5nAQz+GmCU6mTTAAAdTAAAAAAAAAAfAAAAAEAAAAAAAAAAAAAAABnZUxfAAAAAAAAAAMAAAABAAAAAHMlejntTdXyaAvB/WkmjBbB0ppGcTmcBDP4aYJTqZNMAAAAAAAAAABnIBWaE9Fj9MhsWMALgdGez3pgrMe1dgkp9kGxwuE72QAAAAAAmJaAAAAAAQAAAABzJXo57U3V8mgLwf1pJowWwdKaRnE5nAQz+GmCU6mTTAAAAAAAAAAAo3MvfmdjhC69U9b3cZtcpVPOAzZrMn0s36vctGpGdY4AAAAAAJiWgAAAAAEAAAAAcyV6Oe1N1fJoC8H9aSaMFsHSmkZxOZwEM/hpglOpk0wAAAAAAAAAAF6CPFvQgzoMbeUDqMHbkE5oVvJz4DjDOF+r9+rahWKsAAAAAACYloAAAAAAAAAAAVOpk0wAAABACYSZiZrCRj92clXUFVTQGqafI2cmi235zQFGsVzXomHv7fRYzwvoqnJP8j451FJuUm5jg+oacKZaEotZ4DwxDA==" ) func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { @@ -32,7 +32,7 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { defer dbConnectionPool.Close() ctx := context.Background() - horizonClient := horizonclient.MockClient{} + mockRouter := router.MockRouter{} mockRPCService := RPCServiceMock{} signatureClient := signing.SignatureClientMock{} channelAccountStore := store.ChannelAccountStoreMock{} @@ -40,9 +40,9 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { passphrase := "test" s, err := NewChannelAccountService(ChannelAccountServiceOptions{ DB: dbConnectionPool, - HorizonClient: &horizonClient, RPCService: &mockRPCService, BaseFee: 100 * txnbuild.MinBaseFee, + Router: &mockRouter, DistributionAccountSignatureClient: &signatureClient, ChannelAccountStore: &channelAccountStore, PrivateKeyEncrypter: &privateKeyEncrypter, @@ -61,7 +61,7 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { require.NoError(t, err) }) - t.Run("horizon_timeout", func(t *testing.T) { + t.Run("successfully_ensures_the_channel_accounts_creation", func(t *testing.T) { channelAccountStore. On("Count", ctx). Return(2, nil). @@ -104,155 +104,17 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { Once() defer signatureClient.AssertExpectations(t) - horizonClient. - On("SubmitTransaction", mock.AnythingOfType("*txnbuild.Transaction")). - Return(horizon.Transaction{}, horizonclient.Error{ - Response: &http.Response{}, - Problem: problem.P{ - Type: "https://stellar.org/horizon-errors/timeout", - Status: http.StatusRequestTimeout, - Detail: "Timeout", - Extras: map[string]interface{}{}, - }, - }). - Once() - defer horizonClient.AssertExpectations(t) - mockRPCService. On("GetAccountLedgerSequence", distributionAccount.Address()). Return(int64(123), nil). Once() defer mockRPCService.AssertExpectations(t) - err := s.EnsureChannelAccounts(ctx, 5) - require.Error(t, err) - - txHash, hashErr := signedTx.HashHex(network.TestNetworkPassphrase) - require.NoError(t, hashErr) - assert.EqualError(t, err, fmt.Sprintf("submitting create channel accounts on chain transaction: horizon request timed out while creating a channel account. Transaction hash: %s", txHash)) - }) - - t.Run("horizon_bad_request", func(t *testing.T) { - channelAccountStore. - On("Count", ctx). - Return(2, nil). - Once() - defer channelAccountStore.AssertExpectations(t) - - distributionAccount := keypair.MustRandom() - channelAccountsAddressesBeingInserted := []string{} - signedTx := txnbuild.Transaction{} - signatureClient. - On("GetAccountPublicKey", ctx). - Return(distributionAccount.Address(), nil). - Once(). - On("SignStellarTransaction", ctx, mock.AnythingOfType("*txnbuild.Transaction"), []string{distributionAccount.Address()}). - Run(func(args mock.Arguments) { - tx, ok := args.Get(1).(*txnbuild.Transaction) - require.True(t, ok) - - assert.Equal(t, distributionAccount.Address(), tx.SourceAccount().AccountID) - assert.Len(t, tx.Operations(), 3) - - for _, op := range tx.Operations() { - caOp, ok := op.(*txnbuild.CreateAccount) - require.True(t, ok) - - assert.Equal(t, "1", caOp.Amount) - assert.Equal(t, distributionAccount.Address(), caOp.SourceAccount) - channelAccountsAddressesBeingInserted = append(channelAccountsAddressesBeingInserted, caOp.Destination) - } - - tx, err = tx.Sign(network.TestNetworkPassphrase, distributionAccount) - require.NoError(t, err) - - signedTx = *tx - }). - Return(&signedTx, nil). - Once(). - On("NetworkPassphrase"). - Return(network.TestNetworkPassphrase). - Once() - defer signatureClient.AssertExpectations(t) - - horizonClient. - On("SubmitTransaction", mock.AnythingOfType("*txnbuild.Transaction")). - Return(horizon.Transaction{}, horizonclient.Error{ - Response: &http.Response{}, - Problem: problem.P{ - Title: "Some bad request error", - Status: http.StatusBadRequest, - Detail: "Bad Request", - Extras: map[string]interface{}{}, - }, - }). - Once() - defer horizonClient.AssertExpectations(t) - - mockRPCService. - On("GetAccountLedgerSequence", distributionAccount.Address()). - Return(int64(123), nil). - Once() - defer mockRPCService.AssertExpectations(t) - - err := s.EnsureChannelAccounts(ctx, 5) - assert.EqualError(t, err, `submitting create channel accounts on chain transaction: submitting transaction: Type: , Title: Some bad request error, Status: 400, Detail: Bad Request, Extras: map[]: horizon error: "Some bad request error" - check horizon.Error.Problem for more information`) - }) - - t.Run("successfully_ensures_the_channel_accounts_creation", func(t *testing.T) { - channelAccountStore. - On("Count", ctx). - Return(2, nil). - Once() - defer channelAccountStore.AssertExpectations(t) - - distributionAccount := keypair.MustRandom() - channelAccountsAddressesBeingInserted := []string{} - signedTx := txnbuild.Transaction{} - signatureClient. - On("GetAccountPublicKey", ctx). - Return(distributionAccount.Address(), nil). - Once(). - On("SignStellarTransaction", ctx, mock.AnythingOfType("*txnbuild.Transaction"), []string{distributionAccount.Address()}). - Run(func(args mock.Arguments) { - tx, ok := args.Get(1).(*txnbuild.Transaction) - require.True(t, ok) - - assert.Equal(t, distributionAccount.Address(), tx.SourceAccount().AccountID) - assert.Len(t, tx.Operations(), 3) - - for _, op := range tx.Operations() { - caOp, ok := op.(*txnbuild.CreateAccount) - require.True(t, ok) - - assert.Equal(t, "1", caOp.Amount) - assert.Equal(t, distributionAccount.Address(), caOp.SourceAccount) - channelAccountsAddressesBeingInserted = append(channelAccountsAddressesBeingInserted, caOp.Destination) - } - - tx, err = tx.Sign(network.TestNetworkPassphrase, distributionAccount) - require.NoError(t, err) - - signedTx = *tx - }). - Return(&signedTx, nil). - Once(). - On("NetworkPassphrase"). - Return(network.TestNetworkPassphrase). - Once() - defer signatureClient.AssertExpectations(t) - - horizonClient. - On("SubmitTransaction", mock.AnythingOfType("*txnbuild.Transaction")). - Return(horizon.Transaction{}, nil). - Once() - defer horizonClient.AssertExpectations(t) - - mockRPCService. - On("GetAccountLedgerSequence", distributionAccount.Address()). - Return(int64(123), nil). + mockRouter. + On("Route", mock.AnythingOfType("tss.Payload")). + Return(nil). Once() - defer mockRPCService.AssertExpectations(t) + defer mockRouter.AssertExpectations(t) channelAccountStore. On("BatchInsert", ctx, dbConnectionPool, mock.AnythingOfType("[]*store.ChannelAccount")). @@ -271,7 +133,7 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { Once() defer channelAccountStore.AssertExpectations(t) - err := s.EnsureChannelAccounts(ctx, 5) + err = s.EnsureChannelAccounts(ctx, 5) require.NoError(t, err) }) } From 4c89e14795774598b4d3cacb14b65e404b1b2be8 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 20 Dec 2024 16:42:50 +0530 Subject: [PATCH 11/33] Remove unused variable --- internal/services/channel_account_service_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/services/channel_account_service_test.go b/internal/services/channel_account_service_test.go index 6153d78..883c500 100644 --- a/internal/services/channel_account_service_test.go +++ b/internal/services/channel_account_service_test.go @@ -19,10 +19,6 @@ import ( "github.com/stellar/wallet-backend/internal/tss/router" ) -const ( - expectedSignedTxXDR = "AAAAAgAAAABzJXo57U3V8mgLwf1pJowWwdKaRnE5nAQz+GmCU6mTTAAAdTAAAAAAAAAAfAAAAAEAAAAAAAAAAAAAAABnZUxfAAAAAAAAAAMAAAABAAAAAHMlejntTdXyaAvB/WkmjBbB0ppGcTmcBDP4aYJTqZNMAAAAAAAAAABnIBWaE9Fj9MhsWMALgdGez3pgrMe1dgkp9kGxwuE72QAAAAAAmJaAAAAAAQAAAABzJXo57U3V8mgLwf1pJowWwdKaRnE5nAQz+GmCU6mTTAAAAAAAAAAAo3MvfmdjhC69U9b3cZtcpVPOAzZrMn0s36vctGpGdY4AAAAAAJiWgAAAAAEAAAAAcyV6Oe1N1fJoC8H9aSaMFsHSmkZxOZwEM/hpglOpk0wAAAAAAAAAAF6CPFvQgzoMbeUDqMHbkE5oVvJz4DjDOF+r9+rahWKsAAAAAACYloAAAAAAAAAAAVOpk0wAAABACYSZiZrCRj92clXUFVTQGqafI2cmi235zQFGsVzXomHv7fRYzwvoqnJP8j451FJuUm5jg+oacKZaEotZ4DwxDA==" -) - func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { dbt := dbtest.Open(t) defer dbt.Close() From eeeb0c3c3d510e811c1d770b3a31865c2da8818c Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 20 Dec 2024 17:02:23 +0530 Subject: [PATCH 12/33] Go mod tidy --- go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/go.mod b/go.mod index 78c9efb..9041914 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,6 @@ require ( github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect From 38cd52be2990bd2e79b9901f772408bec8d4eff2 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 20 Dec 2024 19:00:32 +0530 Subject: [PATCH 13/33] Add new tests --- internal/services/channel_account_service.go | 43 +++++++++-- .../services/channel_account_service_test.go | 76 +++++++++++++++++++ 2 files changed, 114 insertions(+), 5 deletions(-) diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index 0c58b4f..0e3bb16 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -3,11 +3,13 @@ package services import ( "context" "fmt" + "time" "github.com/stellar/go/keypair" "github.com/stellar/go/txnbuild" "github.com/stellar/wallet-backend/internal/db" + "github.com/stellar/wallet-backend/internal/entities" "github.com/stellar/wallet-backend/internal/signing" "github.com/stellar/wallet-backend/internal/signing/store" signingutils "github.com/stellar/wallet-backend/internal/signing/utils" @@ -15,6 +17,11 @@ import ( "github.com/stellar/wallet-backend/internal/tss/router" ) +const ( + maxRetriesForChannelAccountCreation = 30 + sleepDurationForChannelAccountCreation = 10 * time.Second +) + type ChannelAccountService interface { EnsureChannelAccounts(ctx context.Context, number int64) error } @@ -122,14 +129,40 @@ func (s *channelAccountService) submitCreateChannelAccountsOnChainTransaction(ct } payload := tss.Payload{ - TransactionXDR: signedTxXDR, - WebhookURL: "http://localhost:8001/internal/webhook", - FeeBump: false, + TransactionHash: hash, + TransactionXDR: signedTxXDR, + WebhookURL: "http://localhost:8001/internal/webhook", + FeeBump: false, } - err = s.Router.Route(payload) + err = s.submitToTSS(ctx, payload) + if err != nil { + return fmt.Errorf("submitting transaction hash: %s: %w", hash, err) + } + return nil +} + +func (s *channelAccountService) submitToTSS(_ context.Context, payload tss.Payload) error { + err := s.Router.Route(payload) if err != nil { - return fmt.Errorf("routing payload for transaction hash: %s: %w", hash, err) + return fmt.Errorf("routing payload: %w", err) + } + time.Sleep(sleepDurationForChannelAccountCreation) + + for _ = range maxRetriesForChannelAccountCreation { + txResult, err := s.RPCService.GetTransaction(payload.TransactionHash) + if err != nil { + return fmt.Errorf("getting transaction response: %w", err) + } + + switch txResult.Status { + case entities.SuccessStatus: + return nil + case entities.NotFoundStatus, entities.PendingStatus: + continue + case entities.ErrorStatus: + return fmt.Errorf("error submitting transaction: %s: %s", payload.TransactionHash, txResult.ErrorResultXDR) + } } return nil } diff --git a/internal/services/channel_account_service_test.go b/internal/services/channel_account_service_test.go index 883c500..f78ef19 100644 --- a/internal/services/channel_account_service_test.go +++ b/internal/services/channel_account_service_test.go @@ -13,6 +13,7 @@ import ( "github.com/stellar/wallet-backend/internal/db" "github.com/stellar/wallet-backend/internal/db/dbtest" + "github.com/stellar/wallet-backend/internal/entities" "github.com/stellar/wallet-backend/internal/signing" "github.com/stellar/wallet-backend/internal/signing/store" signingutils "github.com/stellar/wallet-backend/internal/signing/utils" @@ -104,6 +105,12 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { On("GetAccountLedgerSequence", distributionAccount.Address()). Return(int64(123), nil). Once() + + // Mock GetTransaction to simulate successful transaction + mockRPCService. + On("GetTransaction", mock.AnythingOfType("string")). + Return(entities.RPCGetTransactionResult{Status: entities.SuccessStatus}, nil). + Once() defer mockRPCService.AssertExpectations(t) mockRouter. @@ -132,4 +139,73 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { err = s.EnsureChannelAccounts(ctx, 5) require.NoError(t, err) }) + + t.Run("fails_when_transaction_submission_fails", func(t *testing.T) { + channelAccountStore. + On("Count", ctx). + Return(2, nil). + Once() + defer channelAccountStore.AssertExpectations(t) + + distributionAccount := keypair.MustRandom() + channelAccountsAddressesBeingInserted := []string{} + signedTx := txnbuild.Transaction{} + signatureClient. + On("GetAccountPublicKey", ctx). + Return(distributionAccount.Address(), nil). + Once(). + On("SignStellarTransaction", ctx, mock.AnythingOfType("*txnbuild.Transaction"), []string{distributionAccount.Address()}). + Run(func(args mock.Arguments) { + tx, ok := args.Get(1).(*txnbuild.Transaction) + require.True(t, ok) + + assert.Equal(t, distributionAccount.Address(), tx.SourceAccount().AccountID) + assert.Len(t, tx.Operations(), 3) + + for _, op := range tx.Operations() { + caOp, ok := op.(*txnbuild.CreateAccount) + require.True(t, ok) + + assert.Equal(t, "1", caOp.Amount) + assert.Equal(t, distributionAccount.Address(), caOp.SourceAccount) + channelAccountsAddressesBeingInserted = append(channelAccountsAddressesBeingInserted, caOp.Destination) + } + + tx, err = tx.Sign(network.TestNetworkPassphrase, distributionAccount) + require.NoError(t, err) + + signedTx = *tx + }). + Return(&signedTx, nil). + Once(). + On("NetworkPassphrase"). + Return(network.TestNetworkPassphrase). + Once() + defer signatureClient.AssertExpectations(t) + + mockRPCService. + On("GetAccountLedgerSequence", distributionAccount.Address()). + Return(int64(123), nil). + Once() + + // Mock GetTransaction to simulate failed transaction + mockRPCService. + On("GetTransaction", mock.AnythingOfType("string")). + Return(entities.RPCGetTransactionResult{ + Status: entities.ErrorStatus, + ErrorResultXDR: "error_xdr", + }, nil). + Once() + defer mockRPCService.AssertExpectations(t) + + mockRouter. + On("Route", mock.AnythingOfType("tss.Payload")). + Return(nil). + Once() + defer mockRouter.AssertExpectations(t) + + err = s.EnsureChannelAccounts(ctx, 5) + require.Error(t, err) + assert.Contains(t, err.Error(), "error submitting transaction") + }) } From 7cb4091e1174016c8451f33564d77f304f4340ab Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 20 Dec 2024 19:10:53 +0530 Subject: [PATCH 14/33] Add other cases in switch-case --- internal/services/channel_account_service.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index 0e3bb16..a004ebe 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -160,8 +160,8 @@ func (s *channelAccountService) submitToTSS(_ context.Context, payload tss.Paylo return nil case entities.NotFoundStatus, entities.PendingStatus: continue - case entities.ErrorStatus: - return fmt.Errorf("error submitting transaction: %s: %s", payload.TransactionHash, txResult.ErrorResultXDR) + case entities.ErrorStatus, entities.FailedStatus, entities.DuplicateStatus: + return fmt.Errorf("submitting transaction: %s: %s", payload.TransactionHash, txResult.ErrorResultXDR) } } return nil From b634c7467864b0a2d7b2de83a846296040324291 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 20 Dec 2024 19:18:28 +0530 Subject: [PATCH 15/33] Add other cases in switch-case - 2 --- internal/services/channel_account_service.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index a004ebe..0bd3ec9 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -156,12 +156,12 @@ func (s *channelAccountService) submitToTSS(_ context.Context, payload tss.Paylo } switch txResult.Status { - case entities.SuccessStatus: + case entities.SuccessStatus, entities.DuplicateStatus: return nil case entities.NotFoundStatus, entities.PendingStatus: continue - case entities.ErrorStatus, entities.FailedStatus, entities.DuplicateStatus: - return fmt.Errorf("submitting transaction: %s: %s", payload.TransactionHash, txResult.ErrorResultXDR) + case entities.ErrorStatus, entities.FailedStatus, entities.TryAgainLaterStatus: + return fmt.Errorf("submitting transaction: %s: %s: %s", payload.TransactionHash, txResult.Status, txResult.ErrorResultXDR) } } return nil From 2f706615728151b2a5df8430e00a79303f8cdbff Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 20 Dec 2024 19:19:06 +0530 Subject: [PATCH 16/33] Add other cases in switch-case - 3 --- internal/services/channel_account_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index 0bd3ec9..f2bb9b5 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -161,7 +161,7 @@ func (s *channelAccountService) submitToTSS(_ context.Context, payload tss.Paylo case entities.NotFoundStatus, entities.PendingStatus: continue case entities.ErrorStatus, entities.FailedStatus, entities.TryAgainLaterStatus: - return fmt.Errorf("submitting transaction: %s: %s: %s", payload.TransactionHash, txResult.Status, txResult.ErrorResultXDR) + return fmt.Errorf("error submitting transaction: %s: %s: %s", payload.TransactionHash, txResult.Status, txResult.ErrorResultXDR) } } return nil From 8a8d1f2ab4b656edcdbe82dfb0cfbef478b56bfd Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 20 Dec 2024 19:50:37 +0530 Subject: [PATCH 17/33] Add account public key to error string --- internal/services/account_sponsorship_service.go | 2 +- internal/services/channel_account_service.go | 2 +- internal/tss/services/transaction_service.go | 2 +- internal/tss/services/transaction_service_test.go | 4 +++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/services/account_sponsorship_service.go b/internal/services/account_sponsorship_service.go index 9877a23..e9257f3 100644 --- a/internal/services/account_sponsorship_service.go +++ b/internal/services/account_sponsorship_service.go @@ -128,7 +128,7 @@ func (s *accountSponsorshipService) SponsorAccountCreationTransaction(ctx contex channelAccountSeq, err := s.RPCService.GetAccountLedgerSequence(channelAccountPublicKey) if err != nil { - return "", "", fmt.Errorf("getting channel account sequence number: %w", err) + return "", "", fmt.Errorf("getting sequence number for channel account public key: %s: %w", channelAccountPublicKey, err) } tx, err := txnbuild.NewTransaction( diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index f2bb9b5..1c1b3a0 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -94,7 +94,7 @@ func (s *channelAccountService) EnsureChannelAccounts(ctx context.Context, numbe func (s *channelAccountService) submitCreateChannelAccountsOnChainTransaction(ctx context.Context, distributionAccountPublicKey string, ops []txnbuild.Operation) error { accountSeq, err := s.RPCService.GetAccountLedgerSequence(distributionAccountPublicKey) if err != nil { - return fmt.Errorf("getting ledger sequence for distribution account public key: %w", err) + return fmt.Errorf("getting ledger sequence for distribution account public key: %s: %w", distributionAccountPublicKey, err) } tx, err := txnbuild.NewTransaction( diff --git a/internal/tss/services/transaction_service.go b/internal/tss/services/transaction_service.go index 64b3559..179584b 100644 --- a/internal/tss/services/transaction_service.go +++ b/internal/tss/services/transaction_service.go @@ -75,7 +75,7 @@ func (t *transactionService) BuildAndSignTransactionWithChannelAccount(ctx conte } channelAccountSeq, err := t.RPCService.GetAccountLedgerSequence(channelAccountPublicKey) if err != nil { - return nil, fmt.Errorf("getting channel account ledger sequence: %w", err) + return nil, fmt.Errorf("getting ledger sequence for channel account public key: %s: %w", channelAccountPublicKey, err) } tx, err := txnbuild.NewTransaction( txnbuild.TransactionParams{ diff --git a/internal/tss/services/transaction_service_test.go b/internal/tss/services/transaction_service_test.go index 05b2d75..d037d86 100644 --- a/internal/tss/services/transaction_service_test.go +++ b/internal/tss/services/transaction_service_test.go @@ -3,6 +3,7 @@ package services import ( "context" "errors" + "fmt" "testing" "github.com/stellar/go/keypair" @@ -104,7 +105,8 @@ func TestBuildAndSignTransactionWithChannelAccount(t *testing.T) { channelAccountSignatureClient.AssertExpectations(t) assert.Empty(t, tx) - assert.Equal(t, "getting channel account ledger sequence: rpc service down", err.Error()) + expectedErr := fmt.Errorf("getting ledger sequence for channel account public key: %s: rpc service down", channelAccount.Address()) + assert.Equal(t, expectedErr.Error(), err.Error()) }) t.Run("build_tx_fails", func(t *testing.T) { From 319d1e68915904ac852434f93d683d4922ba6a2f Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 2 Jan 2025 15:37:08 -0500 Subject: [PATCH 18/33] Small nit changes --- .gitignore | 2 ++ internal/services/channel_account_service.go | 2 +- internal/services/channel_account_service_test.go | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 48c389b..a2bad2a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ captive-core*/ .env .DS_Store +.idea +__debug_bin* diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index 1c1b3a0..03586f5 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -149,7 +149,7 @@ func (s *channelAccountService) submitToTSS(_ context.Context, payload tss.Paylo } time.Sleep(sleepDurationForChannelAccountCreation) - for _ = range maxRetriesForChannelAccountCreation { + for range maxRetriesForChannelAccountCreation { txResult, err := s.RPCService.GetTransaction(payload.TransactionHash) if err != nil { return fmt.Errorf("getting transaction response: %w", err) diff --git a/internal/services/channel_account_service_test.go b/internal/services/channel_account_service_test.go index f78ef19..f7b48ac 100644 --- a/internal/services/channel_account_service_test.go +++ b/internal/services/channel_account_service_test.go @@ -106,7 +106,6 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { Return(int64(123), nil). Once() - // Mock GetTransaction to simulate successful transaction mockRPCService. On("GetTransaction", mock.AnythingOfType("string")). Return(entities.RPCGetTransactionResult{Status: entities.SuccessStatus}, nil). From 0b12aa3faf31270fe3aaf85425545414aa153b58 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 2 Jan 2025 15:41:28 -0500 Subject: [PATCH 19/33] small nit changes - 2 --- internal/services/channel_account_service_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/services/channel_account_service_test.go b/internal/services/channel_account_service_test.go index f7b48ac..0399432 100644 --- a/internal/services/channel_account_service_test.go +++ b/internal/services/channel_account_service_test.go @@ -187,7 +187,6 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { Return(int64(123), nil). Once() - // Mock GetTransaction to simulate failed transaction mockRPCService. On("GetTransaction", mock.AnythingOfType("string")). Return(entities.RPCGetTransactionResult{ From 167fc63bdccc50518cd6b35abd07e11a39da730c Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 3 Jan 2025 15:26:32 -0500 Subject: [PATCH 20/33] Fix how error is checked --- internal/services/account_sponsorship_service.go | 4 ++-- internal/services/account_sponsorship_service_test.go | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/services/account_sponsorship_service.go b/internal/services/account_sponsorship_service.go index e9257f3..aebfa11 100644 --- a/internal/services/account_sponsorship_service.go +++ b/internal/services/account_sponsorship_service.go @@ -21,6 +21,7 @@ var ( ErrAccountNotEligibleForBeingSponsored = errors.New("account not eligible for being sponsored") ErrFeeExceedsMaximumBaseFee = errors.New("fee exceeds maximum base fee to sponsor") ErrNoSignaturesProvided = errors.New("should have at least one signature") + ErrAccountNotFound = errors.New("account not found") ) type ErrOperationNotAllowed struct { @@ -35,7 +36,6 @@ const ( // Sufficient to cover three average ledger close time. CreateAccountTxnTimeBounds = 18 CreateAccountTxnTimeBoundsSafetyMargin = 12 - accountNotFoundError = "entry not found for account public key" ) type AccountSponsorshipService interface { @@ -61,7 +61,7 @@ func (s *accountSponsorshipService) SponsorAccountCreationTransaction(ctx contex if err == nil { return "", "", ErrAccountAlreadyExists } - if err.Error() != accountNotFoundError { + if !errors.Is(err, ErrAccountNotFound) { return "", "", fmt.Errorf("getting details for account %s: %w", accountToSponsor, err) } diff --git a/internal/services/account_sponsorship_service_test.go b/internal/services/account_sponsorship_service_test.go index 42ce4d3..12ea1f7 100644 --- a/internal/services/account_sponsorship_service_test.go +++ b/internal/services/account_sponsorship_service_test.go @@ -2,7 +2,6 @@ package services import ( "context" - "errors" "testing" "github.com/stellar/go/keypair" @@ -66,7 +65,7 @@ func TestAccountSponsorshipServiceSponsorAccountCreationTransaction(t *testing.T mockRPCService. On("GetAccountLedgerSequence", accountToSponsor). - Return(int64(0), errors.New(accountNotFoundError)). + Return(int64(0), ErrAccountNotFound). Once() defer mockRPCService.AssertExpectations(t) @@ -89,7 +88,7 @@ func TestAccountSponsorshipServiceSponsorAccountCreationTransaction(t *testing.T mockRPCService. On("GetAccountLedgerSequence", accountToSponsor). - Return(int64(0), errors.New(accountNotFoundError)). + Return(int64(0), ErrAccountNotFound). Once() defer mockRPCService.AssertExpectations(t) @@ -188,7 +187,7 @@ func TestAccountSponsorshipServiceSponsorAccountCreationTransaction(t *testing.T mockRPCService. On("GetAccountLedgerSequence", accountToSponsor). - Return(int64(0), errors.New(accountNotFoundError)). + Return(int64(0), ErrAccountNotFound). Once(). On("GetAccountLedgerSequence", distributionAccount.Address()). Return(int64(1), nil). From f2b4f36fddc82068d5a8f11a2e1b606d489ec3d2 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 7 Jan 2025 15:49:32 -0500 Subject: [PATCH 21/33] Use RPC to submit create channel account txn instead of TSS --- internal/services/channel_account_service.go | 62 ++++++++++++-------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index 03586f5..e6b2e67 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -13,13 +13,12 @@ import ( "github.com/stellar/wallet-backend/internal/signing" "github.com/stellar/wallet-backend/internal/signing/store" signingutils "github.com/stellar/wallet-backend/internal/signing/utils" - "github.com/stellar/wallet-backend/internal/tss" "github.com/stellar/wallet-backend/internal/tss/router" ) const ( - maxRetriesForChannelAccountCreation = 30 - sleepDurationForChannelAccountCreation = 10 * time.Second + maxRetriesForChannelAccountCreation = 10 + sleepDelayForChannelAccountCreation = 10 * time.Second ) type ChannelAccountService interface { @@ -128,43 +127,60 @@ func (s *channelAccountService) submitCreateChannelAccountsOnChainTransaction(ct return fmt.Errorf("getting transaction envelope: %w", err) } - payload := tss.Payload{ - TransactionHash: hash, - TransactionXDR: signedTxXDR, - WebhookURL: "http://localhost:8001/internal/webhook", - FeeBump: false, + err = s.submitTransaction(ctx, hash, signedTxXDR) + if err != nil { + return fmt.Errorf("submitting channel account transaction to rpc service: %w", err) } - err = s.submitToTSS(ctx, payload) + err = s.getTransactionStatus(ctx, hash) if err != nil { - return fmt.Errorf("submitting transaction hash: %s: %w", hash, err) + return fmt.Errorf("getting transaction status: %w", err) } + return nil } -func (s *channelAccountService) submitToTSS(_ context.Context, payload tss.Payload) error { - err := s.Router.Route(payload) - if err != nil { - return fmt.Errorf("routing payload: %w", err) +func (s *channelAccountService) submitTransaction(_ context.Context, hash string, signedTxXDR string) error { + for range maxRetriesForChannelAccountCreation { + result, err := s.RPCService.SendTransaction(signedTxXDR) + if err != nil { + return fmt.Errorf("sending transaction: %s: %w", hash, err) + } + + switch result.Status { + case entities.PendingStatus: + return nil + case entities.ErrorStatus: + return fmt.Errorf("transaction failed %s: %s", result.ErrorResultXDR, hash) + case entities.TryAgainLaterStatus: + time.Sleep(sleepDelayForChannelAccountCreation) + continue + } + } - time.Sleep(sleepDurationForChannelAccountCreation) + return fmt.Errorf("transaction did not complete after %d attempts", maxRetriesForChannelAccountCreation) +} + +func (s *channelAccountService) getTransactionStatus(_ context.Context, hash string) error { for range maxRetriesForChannelAccountCreation { - txResult, err := s.RPCService.GetTransaction(payload.TransactionHash) + txResult, err := s.RPCService.GetTransaction(hash) if err != nil { - return fmt.Errorf("getting transaction response: %w", err) + return fmt.Errorf("getting transaction status response: %w", err) } switch txResult.Status { - case entities.SuccessStatus, entities.DuplicateStatus: - return nil - case entities.NotFoundStatus, entities.PendingStatus: + case entities.NotFoundStatus: + time.Sleep(sleepDelayForChannelAccountCreation) continue - case entities.ErrorStatus, entities.FailedStatus, entities.TryAgainLaterStatus: - return fmt.Errorf("error submitting transaction: %s: %s: %s", payload.TransactionHash, txResult.Status, txResult.ErrorResultXDR) + case entities.SuccessStatus: + return nil + case entities.FailedStatus: + return fmt.Errorf("transaction failed: %s: %s: %s", hash, txResult.Status, txResult.ErrorResultXDR) } } - return nil + + return fmt.Errorf("failed to get transaction status after %d attempts", maxRetriesForChannelAccountCreation) } type ChannelAccountServiceOptions struct { From 01a64ada0346717ec5bcdeccff98e32de3f374c1 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 7 Jan 2025 16:34:50 -0500 Subject: [PATCH 22/33] add rpc health check before creating channel accounts --- internal/services/channel_account_service.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index e6b2e67..b9daf6c 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -91,6 +91,11 @@ func (s *channelAccountService) EnsureChannelAccounts(ctx context.Context, numbe } func (s *channelAccountService) submitCreateChannelAccountsOnChainTransaction(ctx context.Context, distributionAccountPublicKey string, ops []txnbuild.Operation) error { + err := waitForRPCServiceHealth(ctx, s.RPCService) + if err != nil { + return fmt.Errorf("rpc service did not become healthy: %w", err) + } + accountSeq, err := s.RPCService.GetAccountLedgerSequence(distributionAccountPublicKey) if err != nil { return fmt.Errorf("getting ledger sequence for distribution account public key: %s: %w", distributionAccountPublicKey, err) @@ -140,6 +145,20 @@ func (s *channelAccountService) submitCreateChannelAccountsOnChainTransaction(ct return nil } +func waitForRPCServiceHealth(ctx context.Context, rpcService RPCService) error { + heartbeat := make(chan entities.RPCGetHealthResult, 1) + go trackRPCServiceHealth(ctx, heartbeat, nil, rpcService) + + for { + select { + case <-heartbeat: + return nil + case <-ctx.Done(): + return fmt.Errorf("context cancelled: %w", ctx.Err()) + } + } +} + func (s *channelAccountService) submitTransaction(_ context.Context, hash string, signedTxXDR string) error { for range maxRetriesForChannelAccountCreation { result, err := s.RPCService.SendTransaction(signedTxXDR) From 64a2e97caa51919c40393e2fa380a8c35e193edd Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 7 Jan 2025 16:43:58 -0500 Subject: [PATCH 23/33] Use ctx to cancel the running goroutine for trackingRPCHealth --- internal/services/channel_account_service.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index b9daf6c..8bf40a8 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -146,8 +146,11 @@ func (s *channelAccountService) submitCreateChannelAccountsOnChainTransaction(ct } func waitForRPCServiceHealth(ctx context.Context, rpcService RPCService) error { + cancelCtx, cancel := context.WithCancel(ctx) + defer cancel() + heartbeat := make(chan entities.RPCGetHealthResult, 1) - go trackRPCServiceHealth(ctx, heartbeat, nil, rpcService) + go trackRPCServiceHealth(cancelCtx, heartbeat, nil, rpcService) for { select { From e29f01d8ab8ccb6111a541fc9201ada959506d7b Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 7 Jan 2025 17:35:56 -0500 Subject: [PATCH 24/33] update tests to account for new changes --- .../services/channel_account_service_test.go | 235 ++++++++++++++++-- 1 file changed, 213 insertions(+), 22 deletions(-) diff --git a/internal/services/channel_account_service_test.go b/internal/services/channel_account_service_test.go index 0399432..65fc611 100644 --- a/internal/services/channel_account_service_test.go +++ b/internal/services/channel_account_service_test.go @@ -2,7 +2,9 @@ package services import ( "context" + "fmt" "testing" + "time" "github.com/stellar/go/keypair" "github.com/stellar/go/network" @@ -101,23 +103,26 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { Once() defer signatureClient.AssertExpectations(t) + mockRPCService. + On("GetHealth"). + Return(entities.RPCGetHealthResult{Status: "healthy"}, nil) + mockRPCService. On("GetAccountLedgerSequence", distributionAccount.Address()). Return(int64(123), nil). Once() + mockRPCService. + On("SendTransaction", mock.AnythingOfType("string")). + Return(entities.RPCSendTransactionResult{Status: entities.PendingStatus}, nil). + Once() + mockRPCService. On("GetTransaction", mock.AnythingOfType("string")). Return(entities.RPCGetTransactionResult{Status: entities.SuccessStatus}, nil). Once() defer mockRPCService.AssertExpectations(t) - mockRouter. - On("Route", mock.AnythingOfType("tss.Payload")). - Return(nil). - Once() - defer mockRouter.AssertExpectations(t) - channelAccountStore. On("BatchInsert", ctx, dbConnectionPool, mock.AnythingOfType("[]*store.ChannelAccount")). Run(func(args mock.Arguments) { @@ -147,7 +152,6 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { defer channelAccountStore.AssertExpectations(t) distributionAccount := keypair.MustRandom() - channelAccountsAddressesBeingInserted := []string{} signedTx := txnbuild.Transaction{} signatureClient. On("GetAccountPublicKey", ctx). @@ -158,17 +162,58 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { tx, ok := args.Get(1).(*txnbuild.Transaction) require.True(t, ok) - assert.Equal(t, distributionAccount.Address(), tx.SourceAccount().AccountID) - assert.Len(t, tx.Operations(), 3) + tx, err = tx.Sign(network.TestNetworkPassphrase, distributionAccount) + require.NoError(t, err) - for _, op := range tx.Operations() { - caOp, ok := op.(*txnbuild.CreateAccount) - require.True(t, ok) + signedTx = *tx + }). + Return(&signedTx, nil). + Once(). + On("NetworkPassphrase"). + Return(network.TestNetworkPassphrase). + Once() + defer signatureClient.AssertExpectations(t) - assert.Equal(t, "1", caOp.Amount) - assert.Equal(t, distributionAccount.Address(), caOp.SourceAccount) - channelAccountsAddressesBeingInserted = append(channelAccountsAddressesBeingInserted, caOp.Destination) - } + mockRPCService. + On("GetHealth"). + Return(entities.RPCGetHealthResult{Status: "healthy"}, nil) + + mockRPCService. + On("GetAccountLedgerSequence", distributionAccount.Address()). + Return(int64(123), nil). + Once() + + mockRPCService. + On("SendTransaction", mock.AnythingOfType("string")). + Return(entities.RPCSendTransactionResult{ + Status: entities.ErrorStatus, + ErrorResultXDR: "error_xdr", + }, nil). + Once() + defer mockRPCService.AssertExpectations(t) + + err = s.EnsureChannelAccounts(ctx, 5) + require.Error(t, err) + assert.Contains(t, err.Error(), "transaction failed error_xdr") + }) + + t.Run("fails_when_transaction_status_check_fails", func(t *testing.T) { + channelAccountStore. + On("Count", ctx). + Return(2, nil). + Once() + defer channelAccountStore.AssertExpectations(t) + + distributionAccount := keypair.MustRandom() + signedTx := txnbuild.Transaction{} + signatureClient. + On("GetAccountPublicKey", ctx). + Return(distributionAccount.Address(), nil). + Once(). + On("SignStellarTransaction", ctx, mock.AnythingOfType("*txnbuild.Transaction"), []string{distributionAccount.Address()}). + Run(func(args mock.Arguments) { + tx, ok := args.Get(1).(*txnbuild.Transaction) + require.True(t, ok) tx, err = tx.Sign(network.TestNetworkPassphrase, distributionAccount) require.NoError(t, err) @@ -182,28 +227,174 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { Once() defer signatureClient.AssertExpectations(t) + mockRPCService. + On("GetHealth"). + Return(entities.RPCGetHealthResult{Status: "healthy"}, nil) + mockRPCService. On("GetAccountLedgerSequence", distributionAccount.Address()). Return(int64(123), nil). Once() + mockRPCService. + On("SendTransaction", mock.AnythingOfType("string")). + Return(entities.RPCSendTransactionResult{Status: entities.PendingStatus}, nil). + Once() + mockRPCService. On("GetTransaction", mock.AnythingOfType("string")). Return(entities.RPCGetTransactionResult{ + Status: entities.FailedStatus, + ErrorResultXDR: "error_xdr", + }, nil). + Once() + defer mockRPCService.AssertExpectations(t) + + err = s.EnsureChannelAccounts(ctx, 5) + require.Error(t, err) + assert.Contains(t, err.Error(), "transaction failed") + }) +} + +func TestWaitForRPCServiceHealth(t *testing.T) { + mockRPCService := RPCServiceMock{} + ctx := context.Background() + + t.Run("successful", func(t *testing.T) { + mockRPCService. + On("GetHealth"). + Return(entities.RPCGetHealthResult{Status: "healthy"}, nil). + Once() + defer mockRPCService.AssertExpectations(t) + + err := waitForRPCServiceHealth(ctx, &mockRPCService) + require.NoError(t, err) + }) + + t.Run("context_cancelled", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + mockRPCService. + On("GetHealth"). + Return(entities.RPCGetHealthResult{}, fmt.Errorf("connection failed")) + defer mockRPCService.AssertExpectations(t) + + err := waitForRPCServiceHealth(ctx, &mockRPCService) + require.Error(t, err) + assert.Contains(t, err.Error(), "context cancelled") + }) +} + +func TestSubmitTransaction(t *testing.T) { + dbt := dbtest.Open(t) + defer dbt.Close() + + dbConnectionPool, err := db.OpenDBConnectionPool(dbt.DSN) + require.NoError(t, err) + defer dbConnectionPool.Close() + + mockRPCService := RPCServiceMock{} + mockRouter := router.MockRouter{} + signatureClient := signing.SignatureClientMock{} + channelAccountStore := store.ChannelAccountStoreMock{} + privateKeyEncrypter := signingutils.DefaultPrivateKeyEncrypter{} + passphrase := "test" + s, err := NewChannelAccountService(ChannelAccountServiceOptions{ + DB: dbConnectionPool, + RPCService: &mockRPCService, + BaseFee: 100 * txnbuild.MinBaseFee, + Router: &mockRouter, + DistributionAccountSignatureClient: &signatureClient, + ChannelAccountStore: &channelAccountStore, + PrivateKeyEncrypter: &privateKeyEncrypter, + EncryptionPassphrase: passphrase, + }) + require.NoError(t, err) + + ctx := context.Background() + hash := "test_hash" + signedTxXDR := "test_xdr" + + t.Run("successful_pending", func(t *testing.T) { + mockRPCService. + On("SendTransaction", signedTxXDR). + Return(entities.RPCSendTransactionResult{Status: entities.PendingStatus}, nil). + Once() + defer mockRPCService.AssertExpectations(t) + + err := s.submitTransaction(ctx, hash, signedTxXDR) + require.NoError(t, err) + }) + + t.Run("error_status", func(t *testing.T) { + mockRPCService. + On("SendTransaction", signedTxXDR). + Return(entities.RPCSendTransactionResult{ Status: entities.ErrorStatus, ErrorResultXDR: "error_xdr", }, nil). Once() defer mockRPCService.AssertExpectations(t) - mockRouter. - On("Route", mock.AnythingOfType("tss.Payload")). - Return(nil). + err := s.submitTransaction(ctx, hash, signedTxXDR) + require.Error(t, err) + assert.Contains(t, err.Error(), "transaction failed error_xdr") + }) +} + +func TestGetTransactionStatus(t *testing.T) { + dbt := dbtest.Open(t) + defer dbt.Close() + + dbConnectionPool, err := db.OpenDBConnectionPool(dbt.DSN) + require.NoError(t, err) + defer dbConnectionPool.Close() + + mockRPCService := RPCServiceMock{} + mockRouter := router.MockRouter{} + signatureClient := signing.SignatureClientMock{} + channelAccountStore := store.ChannelAccountStoreMock{} + privateKeyEncrypter := signingutils.DefaultPrivateKeyEncrypter{} + passphrase := "test" + s, err := NewChannelAccountService(ChannelAccountServiceOptions{ + DB: dbConnectionPool, + RPCService: &mockRPCService, + BaseFee: 100 * txnbuild.MinBaseFee, + Router: &mockRouter, + DistributionAccountSignatureClient: &signatureClient, + ChannelAccountStore: &channelAccountStore, + PrivateKeyEncrypter: &privateKeyEncrypter, + EncryptionPassphrase: passphrase, + }) + require.NoError(t, err) + + ctx := context.Background() + hash := "test_hash" + + t.Run("successful", func(t *testing.T) { + mockRPCService. + On("GetTransaction", hash). + Return(entities.RPCGetTransactionResult{Status: entities.SuccessStatus}, nil). + Once() + defer mockRPCService.AssertExpectations(t) + + err := s.getTransactionStatus(ctx, hash) + require.NoError(t, err) + }) + + t.Run("failed_status", func(t *testing.T) { + mockRPCService. + On("GetTransaction", hash). + Return(entities.RPCGetTransactionResult{ + Status: entities.FailedStatus, + ErrorResultXDR: "error_xdr", + }, nil). Once() - defer mockRouter.AssertExpectations(t) + defer mockRPCService.AssertExpectations(t) - err = s.EnsureChannelAccounts(ctx, 5) + err := s.getTransactionStatus(ctx, hash) require.Error(t, err) - assert.Contains(t, err.Error(), "error submitting transaction") + assert.Contains(t, err.Error(), "transaction failed") }) } From ca87960ac7294c6baa611b50cab7cb445091c7d9 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 7 Jan 2025 17:47:44 -0500 Subject: [PATCH 25/33] remove router from channel account service --- internal/serve/serve.go | 1 - internal/services/channel_account_service.go | 14 +++----------- internal/services/channel_account_service_test.go | 7 ------- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/internal/serve/serve.go b/internal/serve/serve.go index e5153e8..4da8b41 100644 --- a/internal/serve/serve.go +++ b/internal/serve/serve.go @@ -253,7 +253,6 @@ func initHandlerDeps(cfg Configs) (handlerDeps, error) { DB: dbConnectionPool, RPCService: rpcService, BaseFee: int64(cfg.BaseFee), - Router: router, DistributionAccountSignatureClient: cfg.DistributionAccountSignatureClient, ChannelAccountStore: store.NewChannelAccountModel(dbConnectionPool), PrivateKeyEncrypter: &signingutils.DefaultPrivateKeyEncrypter{}, diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index 8bf40a8..e5a2bd5 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -13,12 +13,11 @@ import ( "github.com/stellar/wallet-backend/internal/signing" "github.com/stellar/wallet-backend/internal/signing/store" signingutils "github.com/stellar/wallet-backend/internal/signing/utils" - "github.com/stellar/wallet-backend/internal/tss/router" ) const ( - maxRetriesForChannelAccountCreation = 10 - sleepDelayForChannelAccountCreation = 10 * time.Second + maxRetriesForChannelAccountCreation = 10 + sleepDelayForChannelAccountCreation = 10 * time.Second ) type ChannelAccountService interface { @@ -29,7 +28,6 @@ type channelAccountService struct { DB db.ConnectionPool RPCService RPCService BaseFee int64 - Router router.Router DistributionAccountSignatureClient signing.SignatureClient ChannelAccountStore store.ChannelAccountStore PrivateKeyEncrypter signingutils.PrivateKeyEncrypter @@ -178,7 +176,7 @@ func (s *channelAccountService) submitTransaction(_ context.Context, hash string time.Sleep(sleepDelayForChannelAccountCreation) continue } - + } return fmt.Errorf("transaction did not complete after %d attempts", maxRetriesForChannelAccountCreation) @@ -209,7 +207,6 @@ type ChannelAccountServiceOptions struct { DB db.ConnectionPool RPCService RPCService BaseFee int64 - Router router.Router DistributionAccountSignatureClient signing.SignatureClient ChannelAccountStore store.ChannelAccountStore PrivateKeyEncrypter signingutils.PrivateKeyEncrypter @@ -229,10 +226,6 @@ func (o *ChannelAccountServiceOptions) Validate() error { return fmt.Errorf("base fee is lower than the minimum network fee") } - if o.Router == nil { - return fmt.Errorf("router cannot be nil") - } - if o.DistributionAccountSignatureClient == nil { return fmt.Errorf("distribution account signature client cannot be nil") } @@ -261,7 +254,6 @@ func NewChannelAccountService(opts ChannelAccountServiceOptions) (*channelAccoun DB: opts.DB, RPCService: opts.RPCService, BaseFee: opts.BaseFee, - Router: opts.Router, DistributionAccountSignatureClient: opts.DistributionAccountSignatureClient, ChannelAccountStore: opts.ChannelAccountStore, PrivateKeyEncrypter: opts.PrivateKeyEncrypter, diff --git a/internal/services/channel_account_service_test.go b/internal/services/channel_account_service_test.go index 65fc611..a369568 100644 --- a/internal/services/channel_account_service_test.go +++ b/internal/services/channel_account_service_test.go @@ -19,7 +19,6 @@ import ( "github.com/stellar/wallet-backend/internal/signing" "github.com/stellar/wallet-backend/internal/signing/store" signingutils "github.com/stellar/wallet-backend/internal/signing/utils" - "github.com/stellar/wallet-backend/internal/tss/router" ) func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { @@ -31,7 +30,6 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { defer dbConnectionPool.Close() ctx := context.Background() - mockRouter := router.MockRouter{} mockRPCService := RPCServiceMock{} signatureClient := signing.SignatureClientMock{} channelAccountStore := store.ChannelAccountStoreMock{} @@ -41,7 +39,6 @@ func TestChannelAccountServiceEnsureChannelAccounts(t *testing.T) { DB: dbConnectionPool, RPCService: &mockRPCService, BaseFee: 100 * txnbuild.MinBaseFee, - Router: &mockRouter, DistributionAccountSignatureClient: &signatureClient, ChannelAccountStore: &channelAccountStore, PrivateKeyEncrypter: &privateKeyEncrypter, @@ -295,7 +292,6 @@ func TestSubmitTransaction(t *testing.T) { defer dbConnectionPool.Close() mockRPCService := RPCServiceMock{} - mockRouter := router.MockRouter{} signatureClient := signing.SignatureClientMock{} channelAccountStore := store.ChannelAccountStoreMock{} privateKeyEncrypter := signingutils.DefaultPrivateKeyEncrypter{} @@ -304,7 +300,6 @@ func TestSubmitTransaction(t *testing.T) { DB: dbConnectionPool, RPCService: &mockRPCService, BaseFee: 100 * txnbuild.MinBaseFee, - Router: &mockRouter, DistributionAccountSignatureClient: &signatureClient, ChannelAccountStore: &channelAccountStore, PrivateKeyEncrypter: &privateKeyEncrypter, @@ -352,7 +347,6 @@ func TestGetTransactionStatus(t *testing.T) { defer dbConnectionPool.Close() mockRPCService := RPCServiceMock{} - mockRouter := router.MockRouter{} signatureClient := signing.SignatureClientMock{} channelAccountStore := store.ChannelAccountStoreMock{} privateKeyEncrypter := signingutils.DefaultPrivateKeyEncrypter{} @@ -361,7 +355,6 @@ func TestGetTransactionStatus(t *testing.T) { DB: dbConnectionPool, RPCService: &mockRPCService, BaseFee: 100 * txnbuild.MinBaseFee, - Router: &mockRouter, DistributionAccountSignatureClient: &signatureClient, ChannelAccountStore: &channelAccountStore, PrivateKeyEncrypter: &privateKeyEncrypter, From cf55a99a417a6f5cf8855a66f8b5d40981e3dd9a Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 7 Jan 2025 17:52:54 -0500 Subject: [PATCH 26/33] ignore switch-case linter --- internal/services/channel_account_service.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index e5a2bd5..fbb33dc 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -167,6 +167,7 @@ func (s *channelAccountService) submitTransaction(_ context.Context, hash string return fmt.Errorf("sending transaction: %s: %w", hash, err) } + //exhaustive:ignore switch result.Status { case entities.PendingStatus: return nil @@ -189,6 +190,7 @@ func (s *channelAccountService) getTransactionStatus(_ context.Context, hash str return fmt.Errorf("getting transaction status response: %w", err) } + //exhaustive:ignore switch txResult.Status { case entities.NotFoundStatus: time.Sleep(sleepDelayForChannelAccountCreation) From 54b438d1a1f05a577b1a6a0d84b03a0d827d9ee3 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Wed, 8 Jan 2025 17:15:21 -0500 Subject: [PATCH 27/33] change function name --- internal/services/channel_account_service.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index fbb33dc..53b4bc4 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -135,7 +135,7 @@ func (s *channelAccountService) submitCreateChannelAccountsOnChainTransaction(ct return fmt.Errorf("submitting channel account transaction to rpc service: %w", err) } - err = s.getTransactionStatus(ctx, hash) + err = s.waitForTransactionConfirmation(ctx, hash) if err != nil { return fmt.Errorf("getting transaction status: %w", err) } @@ -183,7 +183,7 @@ func (s *channelAccountService) submitTransaction(_ context.Context, hash string return fmt.Errorf("transaction did not complete after %d attempts", maxRetriesForChannelAccountCreation) } -func (s *channelAccountService) getTransactionStatus(_ context.Context, hash string) error { +func (s *channelAccountService) waitForTransactionConfirmation(_ context.Context, hash string) error { for range maxRetriesForChannelAccountCreation { txResult, err := s.RPCService.GetTransaction(hash) if err != nil { From 7e67128878cc85a07c4a56e079f5571bcee7969f Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Wed, 8 Jan 2025 17:37:46 -0500 Subject: [PATCH 28/33] add context timeouts to avoid waiting for rpc health indefinitely --- internal/services/channel_account_service.go | 15 ++++++++++----- internal/services/channel_account_service_test.go | 8 ++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index 53b4bc4..a38fe92 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -18,6 +18,7 @@ import ( const ( maxRetriesForChannelAccountCreation = 10 sleepDelayForChannelAccountCreation = 10 * time.Second + rpcHealthCheckTimeout = 5 * time.Minute // We want a slightly longer timeout to give time to rpc to catch up to the tip when we start wallet-backend ) type ChannelAccountService interface { @@ -144,18 +145,22 @@ func (s *channelAccountService) submitCreateChannelAccountsOnChainTransaction(ct } func waitForRPCServiceHealth(ctx context.Context, rpcService RPCService) error { - cancelCtx, cancel := context.WithCancel(ctx) - defer cancel() + // Create a cancellable context for the heartbeat goroutine + heartbeatCtx, cancelHeartbeat := context.WithCancel(ctx) + // Create a timeout context as a child of heartbeatCtx + timeoutCtx, _ := context.WithTimeout(heartbeatCtx, rpcHealthCheckTimeout) + // Canceling the parent context (heartbeatCtx) will automatically cancel child (timeoutCtx) + defer cancelHeartbeat() heartbeat := make(chan entities.RPCGetHealthResult, 1) - go trackRPCServiceHealth(cancelCtx, heartbeat, nil, rpcService) + go trackRPCServiceHealth(heartbeatCtx, heartbeat, nil, rpcService) for { select { case <-heartbeat: return nil - case <-ctx.Done(): - return fmt.Errorf("context cancelled: %w", ctx.Err()) + case <-timeoutCtx.Done(): + return fmt.Errorf("context timeout: %w", timeoutCtx.Err()) } } } diff --git a/internal/services/channel_account_service_test.go b/internal/services/channel_account_service_test.go index a369568..60b18b4 100644 --- a/internal/services/channel_account_service_test.go +++ b/internal/services/channel_account_service_test.go @@ -279,7 +279,7 @@ func TestWaitForRPCServiceHealth(t *testing.T) { err := waitForRPCServiceHealth(ctx, &mockRPCService) require.Error(t, err) - assert.Contains(t, err.Error(), "context cancelled") + assert.Contains(t, err.Error(), "context timeout") }) } @@ -338,7 +338,7 @@ func TestSubmitTransaction(t *testing.T) { }) } -func TestGetTransactionStatus(t *testing.T) { +func TestWaitForTransactionConfirmation(t *testing.T) { dbt := dbtest.Open(t) defer dbt.Close() @@ -372,7 +372,7 @@ func TestGetTransactionStatus(t *testing.T) { Once() defer mockRPCService.AssertExpectations(t) - err := s.getTransactionStatus(ctx, hash) + err := s.waitForTransactionConfirmation(ctx, hash) require.NoError(t, err) }) @@ -386,7 +386,7 @@ func TestGetTransactionStatus(t *testing.T) { Once() defer mockRPCService.AssertExpectations(t) - err := s.getTransactionStatus(ctx, hash) + err := s.waitForTransactionConfirmation(ctx, hash) require.Error(t, err) assert.Contains(t, err.Error(), "transaction failed") }) From a5528ae0abbd7e2cc2e8c07737df492229b2f456 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Wed, 8 Jan 2025 17:44:08 -0500 Subject: [PATCH 29/33] add cancelTimeout() --- internal/services/channel_account_service.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index a38fe92..8ec8d34 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -145,14 +145,17 @@ func (s *channelAccountService) submitCreateChannelAccountsOnChainTransaction(ct } func waitForRPCServiceHealth(ctx context.Context, rpcService RPCService) error { - // Create a cancellable context for the heartbeat goroutine + // Create a cancellable context for the heartbeat goroutine, once rpc returns healthy status. heartbeatCtx, cancelHeartbeat := context.WithCancel(ctx) - // Create a timeout context as a child of heartbeatCtx - timeoutCtx, _ := context.WithTimeout(heartbeatCtx, rpcHealthCheckTimeout) - // Canceling the parent context (heartbeatCtx) will automatically cancel child (timeoutCtx) - defer cancelHeartbeat() - + // Create a timeout context so that we can exit the goroutine if the rpc service does not become healthy in a reasonable time. + timeoutCtx, cancelTimeout := context.WithTimeout(heartbeatCtx, rpcHealthCheckTimeout) heartbeat := make(chan entities.RPCGetHealthResult, 1) + defer func() { + cancelHeartbeat() + cancelTimeout() + close(heartbeat) + }() + go trackRPCServiceHealth(heartbeatCtx, heartbeat, nil, rpcService) for { From f75bd3720118db9be9d425a73e1f0a0dd884479b Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 9 Jan 2025 11:34:48 -0500 Subject: [PATCH 30/33] do not close heartbeat channel --- internal/services/channel_account_service.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index 8ec8d34..e7029bd 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -153,7 +153,6 @@ func waitForRPCServiceHealth(ctx context.Context, rpcService RPCService) error { defer func() { cancelHeartbeat() cancelTimeout() - close(heartbeat) }() go trackRPCServiceHealth(heartbeatCtx, heartbeat, nil, rpcService) From d860d8ba77956ed1cd97241f5695157ee55506a4 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 9 Jan 2025 15:06:13 -0500 Subject: [PATCH 31/33] timeout message --- internal/services/channel_account_service.go | 2 +- internal/services/channel_account_service_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index e7029bd..1c47336 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -162,7 +162,7 @@ func waitForRPCServiceHealth(ctx context.Context, rpcService RPCService) error { case <-heartbeat: return nil case <-timeoutCtx.Done(): - return fmt.Errorf("context timeout: %w", timeoutCtx.Err()) + return fmt.Errorf("context timed out waiting for rpc service to become healthy: %w", timeoutCtx.Err()) } } } diff --git a/internal/services/channel_account_service_test.go b/internal/services/channel_account_service_test.go index 60b18b4..a187e46 100644 --- a/internal/services/channel_account_service_test.go +++ b/internal/services/channel_account_service_test.go @@ -279,7 +279,7 @@ func TestWaitForRPCServiceHealth(t *testing.T) { err := waitForRPCServiceHealth(ctx, &mockRPCService) require.Error(t, err) - assert.Contains(t, err.Error(), "context timeout") + assert.Contains(t, err.Error(), "context timed out waiting for rpc service to become healthy") }) } From f5579ab3d5b5bd7483a7df15f4a1fbe09e54a5dc Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 9 Jan 2025 18:10:11 -0500 Subject: [PATCH 32/33] remove timeout for rpc in channel accounts --- internal/services/channel_account_service.go | 11 +++-------- internal/services/channel_account_service_test.go | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index 1c47336..9674268 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -147,13 +147,8 @@ func (s *channelAccountService) submitCreateChannelAccountsOnChainTransaction(ct func waitForRPCServiceHealth(ctx context.Context, rpcService RPCService) error { // Create a cancellable context for the heartbeat goroutine, once rpc returns healthy status. heartbeatCtx, cancelHeartbeat := context.WithCancel(ctx) - // Create a timeout context so that we can exit the goroutine if the rpc service does not become healthy in a reasonable time. - timeoutCtx, cancelTimeout := context.WithTimeout(heartbeatCtx, rpcHealthCheckTimeout) heartbeat := make(chan entities.RPCGetHealthResult, 1) - defer func() { - cancelHeartbeat() - cancelTimeout() - }() + defer cancelHeartbeat() go trackRPCServiceHealth(heartbeatCtx, heartbeat, nil, rpcService) @@ -161,8 +156,8 @@ func waitForRPCServiceHealth(ctx context.Context, rpcService RPCService) error { select { case <-heartbeat: return nil - case <-timeoutCtx.Done(): - return fmt.Errorf("context timed out waiting for rpc service to become healthy: %w", timeoutCtx.Err()) + case <-ctx.Done(): + return fmt.Errorf("context cancelled while waiting for rpc service to become healthy: %w", ctx.Err()) } } } diff --git a/internal/services/channel_account_service_test.go b/internal/services/channel_account_service_test.go index a187e46..8991074 100644 --- a/internal/services/channel_account_service_test.go +++ b/internal/services/channel_account_service_test.go @@ -279,7 +279,7 @@ func TestWaitForRPCServiceHealth(t *testing.T) { err := waitForRPCServiceHealth(ctx, &mockRPCService) require.Error(t, err) - assert.Contains(t, err.Error(), "context timed out waiting for rpc service to become healthy") + assert.Contains(t, err.Error(), "context cancelled while waiting for rpc service to become healthy") }) } From 269abe3d597755a8ec999b72d48f13c4e5eb18f0 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 9 Jan 2025 18:26:20 -0500 Subject: [PATCH 33/33] change number of retries --- internal/services/channel_account_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/channel_account_service.go b/internal/services/channel_account_service.go index 9674268..b3a8a93 100644 --- a/internal/services/channel_account_service.go +++ b/internal/services/channel_account_service.go @@ -16,7 +16,7 @@ import ( ) const ( - maxRetriesForChannelAccountCreation = 10 + maxRetriesForChannelAccountCreation = 50 sleepDelayForChannelAccountCreation = 10 * time.Second rpcHealthCheckTimeout = 5 * time.Minute // We want a slightly longer timeout to give time to rpc to catch up to the tip when we start wallet-backend )