Skip to content

Commit 3a5a705

Browse files
author
jeffyanta
authored
Improve CalculateFromBlockchain (#47)
1 parent 96210e9 commit 3a5a705

File tree

3 files changed

+43
-15
lines changed

3 files changed

+43
-15
lines changed

pkg/code/balance/calculator.go

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/code-payments/code-server/pkg/code/common"
1212
code_data "github.com/code-payments/code-server/pkg/code/data"
13+
"github.com/code-payments/code-server/pkg/code/data/balance"
1314
"github.com/code-payments/code-server/pkg/code/data/timelock"
1415
"github.com/code-payments/code-server/pkg/metrics"
1516
"github.com/code-payments/code-server/pkg/solana"
@@ -109,14 +110,41 @@ func CalculateFromCache(ctx context.Context, data code_data.Provider, tokenAccou
109110
//
110111
// todo: add a batching variant
111112
func CalculateFromBlockchain(ctx context.Context, data code_data.Provider, tokenAccount *common.Account) (uint64, error) {
113+
var cachedQuarks uint64
114+
var cachedSlot uint64
115+
checkpointRecord, err := data.GetBalanceCheckpoint(ctx, tokenAccount.PublicKey().ToBase58())
116+
if err == nil {
117+
cachedQuarks = checkpointRecord.Quarks
118+
cachedSlot = checkpointRecord.SlotCheckpoint
119+
} else if err != balance.ErrCheckpointNotFound {
120+
return 0, err
121+
}
122+
112123
// todo: we may need something that's more resistant to RPC nodes with stale account state
113-
balance, err := data.GetBlockchainBalance(ctx, tokenAccount.PublicKey().ToBase58())
124+
quarks, slot, err := data.GetBlockchainBalance(ctx, tokenAccount.PublicKey().ToBase58())
114125
if err == solana.ErrNoBalance {
115126
return 0, nil
116127
} else if err != nil {
117-
return 0, err
128+
// RPC node threw an error. Return the cached balance
129+
return cachedQuarks, nil
118130
}
119-
return balance, nil
131+
132+
// RPC node is behind, use cached balance
133+
if cachedSlot > slot {
134+
return cachedQuarks, nil
135+
}
136+
137+
// Observed a balance that's more recent. Best-effort update the checkpoint.
138+
if cachedSlot == 0 || (slot > cachedSlot && quarks != cachedQuarks) {
139+
newCheckpointRecord := &balance.Record{
140+
TokenAccount: tokenAccount.PublicKey().ToBase58(),
141+
Quarks: quarks,
142+
SlotCheckpoint: slot,
143+
}
144+
data.SaveBalanceCheckpoint(ctx, newCheckpointRecord)
145+
}
146+
147+
return quarks, nil
120148
}
121149

122150
// Calculate calculates a token account's balance using a starting point and a set

pkg/code/data/blockchain.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type BlockchainData interface {
2323

2424
GetBlockchainAccountInfo(ctx context.Context, account string, commitment solana.Commitment) (*solana.AccountInfo, error)
2525
GetBlockchainAccountDataAfterBlock(ctx context.Context, account string, slot uint64) ([]byte, uint64, error)
26-
GetBlockchainBalance(ctx context.Context, account string) (uint64, error)
26+
GetBlockchainBalance(ctx context.Context, account string) (uint64, uint64, error)
2727
GetBlockchainBlock(ctx context.Context, slot uint64) (*solana.Block, error)
2828
GetBlockchainBlockSignatures(ctx context.Context, slot uint64) ([]string, error)
2929
GetBlockchainBlocksWithLimit(ctx context.Context, start uint64, limit uint64) ([]uint64, error)
@@ -284,21 +284,21 @@ func (dp *BlockchainProvider) GetBlockchainMinimumBalanceForRentExemption(ctx co
284284
return res, err
285285
}
286286

287-
func (dp *BlockchainProvider) GetBlockchainBalance(ctx context.Context, account string) (uint64, error) {
287+
func (dp *BlockchainProvider) GetBlockchainBalance(ctx context.Context, account string) (uint64, uint64, error) {
288288
tracer := metrics.TraceMethodCall(ctx, blockchainProviderMetricsName, "GetBlockchainBalance")
289289
defer tracer.End()
290290

291291
accountId, err := base58.Decode(account)
292292
if err != nil {
293-
return 0, err
293+
return 0, 0, err
294294
}
295295

296-
res, err := dp.sc.GetTokenAccountBalance(accountId)
296+
quarks, slot, err := dp.sc.GetTokenAccountBalance(accountId)
297297

298298
if err != nil {
299299
tracer.OnError(err)
300300
}
301-
return res, err
301+
return quarks, slot, err
302302
}
303303

304304
func (dp *BlockchainProvider) GetBlockchainTransactionTokenBalances(ctx context.Context, sig string) (*solana.TransactionTokenBalances, error) {

pkg/solana/client.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ type Client interface {
184184
GetSignatureStatuses([]Signature) ([]*SignatureStatus, error)
185185
GetSignaturesForAddress(owner ed25519.PublicKey, commitment Commitment, limit uint64, before, until string) ([]*TransactionSignature, error)
186186
GetSlot(Commitment) (uint64, error)
187-
GetTokenAccountBalance(ed25519.PublicKey) (uint64, error)
187+
GetTokenAccountBalance(ed25519.PublicKey) (uint64, uint64, error)
188188
GetTokenAccountsByOwner(owner, mint ed25519.PublicKey) ([]ed25519.PublicKey, error)
189189
GetTransaction(Signature, Commitment) (ConfirmedTransaction, error)
190190
GetTransactionTokenBalances(Signature) (TransactionTokenBalances, error)
@@ -691,7 +691,7 @@ func (c *client) GetBalance(account ed25519.PublicKey) (uint64, error) {
691691
return 0, errors.Errorf("invalid value in response")
692692
}
693693

694-
func (c *client) GetTokenAccountBalance(account ed25519.PublicKey) (uint64, error) {
694+
func (c *client) GetTokenAccountBalance(account ed25519.PublicKey) (uint64, uint64, error) {
695695
var resp struct {
696696
Context struct {
697697
Slot int64 `json:"slot"`
@@ -701,22 +701,22 @@ func (c *client) GetTokenAccountBalance(account ed25519.PublicKey) (uint64, erro
701701
if err := c.call(&resp, "getTokenAccountBalance", base58.Encode(account[:]), CommitmentProcessed); err != nil {
702702
jsonRPCErr, ok := err.(*jsonrpc.RPCError)
703703
if !ok {
704-
return 0, errors.Wrapf(err, "getTokenAccountBalance() failed to send request")
704+
return 0, 0, errors.Wrapf(err, "getTokenAccountBalance() failed to send request")
705705
}
706706

707707
if jsonRPCErr.Code == invalidParamCode {
708-
return 0, ErrNoBalance
708+
return 0, 0, ErrNoBalance
709709
}
710710

711-
return 0, errors.Wrapf(err, "getTokenAccountBalance() failed to send request")
711+
return 0, 0, errors.Wrapf(err, "getTokenAccountBalance() failed to send request")
712712
}
713713

714714
quarks, err := strconv.ParseUint(resp.Value.Amount, 10, 64)
715715
if err != nil {
716-
return 0, errors.Errorf("invalid value in response")
716+
return 0, 0, errors.Errorf("invalid value in response")
717717
}
718718

719-
return quarks, nil
719+
return quarks, uint64(resp.Context.Slot), nil
720720
}
721721

722722
func (c *client) SubmitTransaction(txn Transaction, commitment Commitment) (Signature, error) {

0 commit comments

Comments
 (0)