@@ -2,6 +2,7 @@ package balance
22
33import (
44 "context"
5+ "time"
56
67 "github.com/pkg/errors"
78 "github.com/sirupsen/logrus"
@@ -128,17 +129,30 @@ func CalculateFromCache(ctx context.Context, data code_data.Provider, tokenAccou
128129func CalculateFromBlockchain (ctx context.Context , data code_data.Provider , tokenAccount * common.Account ) (uint64 , Source , error ) {
129130 var cachedQuarks uint64
130131 var cachedSlot uint64
132+ var cachedUpdateTs time.Time
131133 checkpointRecord , err := data .GetBalanceCheckpoint (ctx , tokenAccount .PublicKey ().ToBase58 ())
132134 if err == nil {
133135 cachedQuarks = checkpointRecord .Quarks
134136 cachedSlot = checkpointRecord .SlotCheckpoint
137+ cachedUpdateTs = checkpointRecord .LastUpdatedAt
135138 } else if err != balance .ErrCheckpointNotFound {
136139 return 0 , UnknownSource , err
137140 }
138141
139142 // todo: we may need something that's more resistant to RPC nodes with stale account state
140143 quarks , slot , err := data .GetBlockchainBalance (ctx , tokenAccount .PublicKey ().ToBase58 ())
141144 if err == solana .ErrNoBalance {
145+ // We can't tell whether
146+ // 1. RPC node is behind, and observed a state before the account existed
147+ // 2. RPC node is ahead, and the account was closed
148+ // because we don't have a slot to compare against the checkpoint.
149+ //
150+ // If the checkpoint was recently updated, we opt to trust that, optimizing
151+ // to reduce potential race conditions for 1.
152+ if time .Since (cachedUpdateTs ) < 5 * time .Minute {
153+ return cachedQuarks , CacheSource , nil
154+ }
155+
142156 return 0 , BlockchainSource , nil
143157 } else if err != nil {
144158 // RPC node threw an error. Return the cached balance
0 commit comments