From 2200f52687d47a5c5ebd7141525d7adace88ac71 Mon Sep 17 00:00:00 2001 From: Patrick O'Grady Date: Tue, 27 Oct 2020 10:25:02 -0700 Subject: [PATCH] First pass at GetBalance --- indexer/indexer.go | 42 +++++++++++++++++++++++++----- main.go | 2 +- services/account_service.go | 52 +++++++++++++++++++++++++++---------- services/errors.go | 18 ++++++++++--- services/types.go | 11 +++++++- 5 files changed, 99 insertions(+), 26 deletions(-) diff --git a/indexer/indexer.go b/indexer/indexer.go index 76fa0b1..60289d2 100644 --- a/indexer/indexer.go +++ b/indexer/indexer.go @@ -84,11 +84,12 @@ type Indexer struct { client Client - asserter *asserter.Asserter - database storage.Database - blockStorage *storage.BlockStorage - coinStorage *storage.CoinStorage - workers []storage.BlockWorker + asserter *asserter.Asserter + database storage.Database + blockStorage *storage.BlockStorage + balanceStorage *storage.BalanceStorage + coinStorage *storage.CoinStorage + workers []storage.BlockWorker waiter *waitTable } @@ -208,6 +209,7 @@ func Initialize( &BalanceStorageHelper{asserter}, &BalanceStorageHandler{}, ) + i.balanceStorage = balanceStorage i.workers = []storage.BlockWorker{coinStorage, balanceStorage} @@ -758,7 +760,11 @@ func (i *Indexer) GetBlockTransaction( blockIdentifier *types.BlockIdentifier, transactionIdentifier *types.TransactionIdentifier, ) (*types.Transaction, error) { - return i.blockStorage.GetBlockTransaction(ctx, blockIdentifier, transactionIdentifier) + return i.blockStorage.GetBlockTransaction( + ctx, + blockIdentifier, + transactionIdentifier, + ) } // GetCoins returns all unspent coins for a particular *types.AccountIdentifier. @@ -768,3 +774,27 @@ func (i *Indexer) GetCoins( ) ([]*types.Coin, *types.BlockIdentifier, error) { return i.coinStorage.GetCoins(ctx, accountIdentifier) } + +func (i *Indexer) GetBalance( + ctx context.Context, + accountIdentifier *types.AccountIdentifier, + currency *types.Currency, + blockIdentifier *types.PartialBlockIdentifier, +) (*types.Amount, *types.BlockIdentifier, error) { + blockResponse, err := i.GetBlockLazy(ctx, blockIdentifier) + if err != nil { + return nil, nil, err + } + + amount, err := i.balanceStorage.GetBalance( + ctx, + accountIdentifier, + currency, + blockResponse.Block.BlockIdentifier, + ) + if err != nil { + return nil, nil, err + } + + return amount, blockResponse.Block.BlockIdentifier, nil +} diff --git a/main.go b/main.go index 6886f69..8271f45 100644 --- a/main.go +++ b/main.go @@ -154,7 +154,7 @@ func main() { // requests. asserter, err := asserter.NewServer( bitcoin.OperationTypes, - false, + true, []*types.NetworkIdentifier{cfg.Network}, nil, ) diff --git a/services/account_service.go b/services/account_service.go index 04a786f..ce7d72b 100644 --- a/services/account_service.go +++ b/services/account_service.go @@ -49,27 +49,51 @@ func (s *AccountAPIService) AccountBalance( return nil, wrapErr(ErrUnavailableOffline, nil) } - coins, block, err := s.i.GetCoins(ctx, request.AccountIdentifier) - if err != nil { - return nil, wrapErr(ErrUnableToGetCoins, err) + // If we are fetching the current balance, + // return all coins for an address and calculate + // the balance from those coins. + if request.BlockIdentifier == nil { + coins, block, err := s.i.GetCoins(ctx, request.AccountIdentifier) + if err != nil { + return nil, wrapErr(ErrUnableToGetCoins, err) + } + + balance := "0" + for _, coin := range coins { + balance, err = types.AddValues(balance, coin.Amount.Value) + if err != nil { + return nil, wrapErr(ErrUnableToParseIntermediateResult, err) + } + } + + return &types.AccountBalanceResponse{ + BlockIdentifier: block, + Coins: coins, + Balances: []*types.Amount{ + { + Value: balance, + Currency: s.config.Currency, + }, + }, + }, nil } - balance := "0" - for _, coin := range coins { - balance, err = types.AddValues(balance, coin.Amount.Value) - if err != nil { - return nil, wrapErr(ErrUnableToParseIntermediateResult, err) - } + // If we are fetching a historical balance, + // use balance storage and don't return coins. + amount, block, err := s.i.GetBalance( + ctx, + request.AccountIdentifier, + s.config.Currency, + request.BlockIdentifier, + ) + if err != nil { + return nil, wrapErr(ErrUnableToGetBalance, err) } return &types.AccountBalanceResponse{ BlockIdentifier: block, - Coins: coins, Balances: []*types.Amount{ - { - Value: balance, - Currency: s.config.Currency, - }, + amount, }, }, nil } diff --git a/services/errors.go b/services/errors.go index 43c6ad5..85ac75f 100644 --- a/services/errors.go +++ b/services/errors.go @@ -40,6 +40,7 @@ var ( ErrUnableToGetCoins, ErrTransactionNotFound, ErrCouldNotGetFeeRate, + ErrUnableToGetBalance, } // ErrUnimplemented is returned when an endpoint @@ -59,8 +60,9 @@ var ( // ErrNotReady is returned when bitcoind is not // yet ready to serve queries. ErrNotReady = &types.Error{ - Code: 2, //nolint - Message: "Bitcoind is not ready", + Code: 2, //nolint + Message: "Bitcoind is not ready", + Retriable: true, } // ErrBitcoind is returned when bitcoind @@ -173,6 +175,14 @@ var ( Code: 17, // nolint Message: "Could not get suggested fee rate", } + + // ErrUnableToGetBalance is returned by the indexer + // when it is not possible to get the balance + // of a *types.AccountIdentifier. + ErrUnableToGetBalance = &types.Error{ + Code: 18, //nolint + Message: "Unable to get balance", + } ) // wrapErr adds details to the types.Error provided. We use a function @@ -180,8 +190,8 @@ var ( // errors. func wrapErr(rErr *types.Error, err error) *types.Error { newErr := &types.Error{ - Code: rErr.Code, - Message: rErr.Message, + Code: rErr.Code, + Message: rErr.Message, Retriable: rErr.Retriable, } if err != nil { diff --git a/services/types.go b/services/types.go index f147d32..75c7620 100644 --- a/services/types.go +++ b/services/types.go @@ -48,7 +48,10 @@ type Client interface { // Indexer is used by the servicers to get block and account data. type Indexer interface { - GetBlockLazy(context.Context, *types.PartialBlockIdentifier) (*types.BlockResponse, error) + GetBlockLazy( + context.Context, + *types.PartialBlockIdentifier, + ) (*types.BlockResponse, error) GetBlockTransaction( context.Context, *types.BlockIdentifier, @@ -62,6 +65,12 @@ type Indexer interface { context.Context, []*types.Coin, ) ([]*bitcoin.ScriptPubKey, error) + GetBalance( + context.Context, + *types.AccountIdentifier, + *types.Currency, + *types.PartialBlockIdentifier, + ) (*types.Amount, *types.BlockIdentifier, error) } type unsignedTransaction struct {