Skip to content
This repository was archived by the owner on May 31, 2023. It is now read-only.

Commit de2e293

Browse files
author
Richard Patel
committed
add GetAllPriceAccounts, support commitment level and slots context
1 parent e208699 commit de2e293

File tree

6 files changed

+227
-69
lines changed

6 files changed

+227
-69
lines changed

accounts.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,24 @@ func (m *MappingAccount) ProductKeys() []solana.PublicKey {
261261
}
262262
return m.Products[:m.Num]
263263
}
264+
265+
// ProductAccountEntry is a versioned product account and its pubkey.
266+
type ProductAccountEntry struct {
267+
*ProductAccount
268+
Pubkey solana.PublicKey `json:"pubkey"`
269+
Slot uint64 `json:"slot"`
270+
}
271+
272+
// PriceAccountEntry is a versioned price account and its pubkey.
273+
type PriceAccountEntry struct {
274+
*PriceAccount
275+
Pubkey solana.PublicKey `json:"pubkey"`
276+
Slot uint64 `json:"slot"`
277+
}
278+
279+
// MappingAccountEntry is a versioned mapping account and its pubkey.
280+
type MappingAccountEntry struct {
281+
*MappingAccount
282+
Pubkey solana.PublicKey `json:"pubkey"`
283+
Slot uint64 `json:"slot"`
284+
}

handler.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@ func (p *PriceEventHandler) getComponentCallbacks(priceKey solana.PublicKey, pub
9090
return res
9191
}
9292

93-
func (p *PriceEventHandler) consume(updates <-chan PriceAccountUpdate) {
93+
func (p *PriceEventHandler) consume(updates <-chan PriceAccountEntry) {
9494
for update := range updates {
95-
p.processUpdate(update.Pubkey, update.Price)
95+
p.processUpdate(update.Pubkey, update.PriceAccount)
9696
}
9797
}
9898

query.go

Lines changed: 135 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,53 +20,70 @@ import (
2020
"fmt"
2121

2222
"github.com/gagliardetto/solana-go"
23+
"github.com/gagliardetto/solana-go/rpc"
2324
)
2425

2526
// GetPriceAccount retrieves a price account from the blockchain.
26-
func (c *Client) GetPriceAccount(ctx context.Context, priceKey solana.PublicKey) (*PriceAccount, error) {
27+
func (c *Client) GetPriceAccount(ctx context.Context, priceKey solana.PublicKey, commitment rpc.CommitmentType) (PriceAccountEntry, error) {
2728
price := new(PriceAccount)
28-
if err := c.queryFor(ctx, price, priceKey); err != nil {
29-
return nil, err
29+
slot, err := c.queryFor(ctx, price, priceKey, commitment)
30+
if err != nil {
31+
return PriceAccountEntry{}, err
3032
}
31-
return price, nil
33+
return PriceAccountEntry{
34+
PriceAccount: price,
35+
Pubkey: priceKey,
36+
Slot: slot,
37+
}, nil
3238
}
3339

3440
// GetProductAccount retrieves a product account from the blockchain.
35-
func (c *Client) GetProductAccount(ctx context.Context, productKey solana.PublicKey) (*ProductAccount, error) {
41+
func (c *Client) GetProductAccount(ctx context.Context, productKey solana.PublicKey, commitment rpc.CommitmentType) (ProductAccountEntry, error) {
3642
product := new(ProductAccount)
37-
if err := c.queryFor(ctx, product, productKey); err != nil {
38-
return nil, err
43+
slot, err := c.queryFor(ctx, product, productKey, commitment)
44+
if err != nil {
45+
return ProductAccountEntry{}, err
3946
}
40-
return product, nil
47+
return ProductAccountEntry{
48+
ProductAccount: product,
49+
Pubkey: productKey,
50+
Slot: slot,
51+
}, nil
4152
}
4253

4354
// GetMappingAccount retrieves a single mapping account from the blockchain.
44-
func (c *Client) GetMappingAccount(ctx context.Context, mappingKey solana.PublicKey) (*MappingAccount, error) {
55+
func (c *Client) GetMappingAccount(ctx context.Context, mappingKey solana.PublicKey, commitment rpc.CommitmentType) (MappingAccountEntry, error) {
4556
mapping := new(MappingAccount)
46-
if err := c.queryFor(ctx, mapping, mappingKey); err != nil {
47-
return nil, err
57+
slot, err := c.queryFor(ctx, mapping, mappingKey, commitment)
58+
if err != nil {
59+
return MappingAccountEntry{}, err
4860
}
49-
return mapping, nil
61+
return MappingAccountEntry{
62+
MappingAccount: mapping,
63+
Pubkey: mappingKey,
64+
Slot: slot,
65+
}, nil
5066
}
5167

52-
func (c *Client) queryFor(ctx context.Context, acc encoding.BinaryUnmarshaler, key solana.PublicKey) error {
53-
info, err := c.RPC.GetAccountInfo(ctx, key)
68+
func (c *Client) queryFor(ctx context.Context, acc encoding.BinaryUnmarshaler, key solana.PublicKey, commitment rpc.CommitmentType) (slot uint64, err error) {
69+
info, err := c.RPC.GetAccountInfoWithOpts(ctx, key, &rpc.GetAccountInfoOpts{Commitment: commitment})
5470
if err != nil {
55-
return err
71+
return 0, err
5672
}
5773

74+
slot = info.Context.Slot
5875
data := info.Value.Data.GetBinary()
59-
return acc.UnmarshalBinary(data)
76+
return slot, acc.UnmarshalBinary(data)
6077
}
6178

6279
// GetAllProductKeys lists all mapping accounts for product account pubkeys.
63-
func (c *Client) GetAllProductKeys(ctx context.Context) ([]solana.PublicKey, error) {
80+
func (c *Client) GetAllProductKeys(ctx context.Context, commitment rpc.CommitmentType) ([]solana.PublicKey, error) {
6481
var products []solana.PublicKey
6582
next := c.Env.Mapping
6683

6784
const maxAccounts = 128 // arbitrary limit on the mapping account list length
6885
for i := 0; i < maxAccounts && !next.IsZero(); i++ {
69-
acc, err := c.GetMappingAccount(ctx, next)
86+
acc, err := c.GetMappingAccount(ctx, next, commitment)
7087
if err != nil {
7188
return products, fmt.Errorf("error getting mapping account %s (#%d): %w", next, i+1, err)
7289
}
@@ -77,17 +94,11 @@ func (c *Client) GetAllProductKeys(ctx context.Context) ([]solana.PublicKey, err
7794
return products, nil
7895
}
7996

80-
// ProductAccountEntry is a product account and its pubkey.
81-
type ProductAccountEntry struct {
82-
ProductAccount
83-
Pubkey solana.PublicKey `json:"pubkey"`
84-
}
85-
86-
// GetAllProducts returns all product accounts.
97+
// GetAllProductAccounts returns all product accounts.
8798
//
8899
// Aborts and returns an error if any product account failed to fetch.
89-
func (c *Client) GetAllProducts(ctx context.Context) ([]ProductAccountEntry, error) {
90-
keys, err := c.GetAllProductKeys(ctx)
100+
func (c *Client) GetAllProductAccounts(ctx context.Context, commitment rpc.CommitmentType) ([]ProductAccountEntry, error) {
101+
keys, err := c.GetAllProductKeys(ctx, commitment)
91102
if err != nil {
92103
return nil, err
93104
}
@@ -103,16 +114,21 @@ func (c *Client) GetAllProducts(ctx context.Context) ([]ProductAccountEntry, err
103114
keys = nil
104115
}
105116

106-
if err := c.getProductsPage(ctx, &accs, nextKeys); err != nil {
117+
if err := c.getProductAccountsPage(ctx, &accs, nextKeys, commitment); err != nil {
107118
return accs, err
108119
}
109120
}
110121

111122
return accs, nil
112123
}
113124

114-
func (c *Client) getProductsPage(ctx context.Context, accs *[]ProductAccountEntry, keys []solana.PublicKey) error {
115-
res, err := c.RPC.GetMultipleAccounts(ctx, keys...)
125+
func (c *Client) getProductAccountsPage(
126+
ctx context.Context,
127+
accs *[]ProductAccountEntry, // accounts out
128+
keys []solana.PublicKey, // keys in
129+
commitment rpc.CommitmentType,
130+
) error {
131+
res, err := c.RPC.GetMultipleAccountsWithOpts(ctx, keys, &rpc.GetMultipleAccountsOpts{Commitment: commitment})
116132
if err != nil {
117133
return err
118134
}
@@ -123,13 +139,101 @@ func (c *Client) getProductsPage(ctx context.Context, accs *[]ProductAccountEntr
123139

124140
for i, info := range res.Value {
125141
accountData := info.Data.GetBinary()
126-
var acc ProductAccount
142+
acc := new(ProductAccount)
127143
if err := acc.UnmarshalBinary(accountData); err != nil {
128144
return fmt.Errorf("failed to retrieve product account %s: %w", keys[i], err)
129145
}
130146
*accs = append(*accs, ProductAccountEntry{
131147
ProductAccount: acc,
132148
Pubkey: keys[i],
149+
Slot: res.Context.Slot,
150+
})
151+
}
152+
153+
return nil
154+
}
155+
156+
// GetAllPriceAccounts returns all price accounts.
157+
//
158+
// Aborts and returns an error if any product account failed to fetch.
159+
func (c *Client) GetAllPriceAccounts(ctx context.Context, commitment rpc.CommitmentType) ([]PriceAccountEntry, error) {
160+
// Start by enumerating all product accounts. They contain the first price account of each product.
161+
products, err := c.GetAllProductAccounts(ctx, commitment)
162+
if err != nil {
163+
return nil, err
164+
}
165+
166+
// List of keys that we need to fetch.
167+
keys := make([]solana.PublicKey, 0, len(products))
168+
for _, product := range products {
169+
if !product.FirstPrice.IsZero() {
170+
keys = append(keys, product.FirstPrice)
171+
}
172+
}
173+
174+
return c.GetPriceAccountsRecursive(ctx, commitment, keys...)
175+
}
176+
177+
// GetPriceAccountsRecursive retrieves the price accounts of the given public keys.
178+
//
179+
// If these price accounts have successors, their contents will be fetched as well, recursively.
180+
// When called with the ProductAccountHeader.FirstPrice, it will fetch all price accounts of a product.
181+
func (c *Client) GetPriceAccountsRecursive(ctx context.Context, commitment rpc.CommitmentType, priceKeys ...solana.PublicKey) ([]PriceAccountEntry, error) {
182+
// Set of accounts seen to prevent infinite loops of price account linked lists.
183+
// Technically, infinite loops should never occur. But you never know.
184+
seen := make(map[solana.PublicKey]struct{})
185+
186+
var accs []PriceAccountEntry
187+
for len(priceKeys) > 0 {
188+
// Get next block of keys from list.
189+
nextKeys := priceKeys
190+
if len(nextKeys) > c.AccountsBatchSize {
191+
nextKeys = nextKeys[:c.AccountsBatchSize]
192+
priceKeys = priceKeys[c.AccountsBatchSize:]
193+
} else {
194+
priceKeys = nil
195+
}
196+
197+
if err := c.getPriceAccountsPage(ctx, &accs, nextKeys, &priceKeys, seen, commitment); err != nil {
198+
return accs, err
199+
}
200+
}
201+
202+
return accs, nil
203+
}
204+
205+
func (c *Client) getPriceAccountsPage(
206+
ctx context.Context,
207+
accs *[]PriceAccountEntry, // accounts out
208+
nextKeys []solana.PublicKey, // keys in
209+
allKeys *[]solana.PublicKey, // keys out
210+
visitedKeys map[solana.PublicKey]struct{}, // keys seen
211+
commitment rpc.CommitmentType,
212+
) error {
213+
res, err := c.RPC.GetMultipleAccountsWithOpts(ctx, nextKeys, &rpc.GetMultipleAccountsOpts{Commitment: commitment})
214+
if err != nil {
215+
return err
216+
}
217+
218+
if len(res.Value) != len(nextKeys) {
219+
return fmt.Errorf("unexpected number of price accounts, asked for %d but got %d", len(nextKeys), len(res.Value))
220+
}
221+
222+
for i, info := range res.Value {
223+
accountData := info.Data.GetBinary()
224+
acc := new(PriceAccount)
225+
if err := acc.UnmarshalBinary(accountData); err != nil {
226+
return fmt.Errorf("failed to retrieve product account %s: %w", nextKeys[i], err)
227+
}
228+
_, seen := visitedKeys[acc.Next]
229+
if !seen && !acc.Next.IsZero() {
230+
*allKeys = append(*allKeys, acc.Next)
231+
visitedKeys[acc.Next] = struct{}{}
232+
}
233+
*accs = append(*accs, PriceAccountEntry{
234+
PriceAccount: acc,
235+
Pubkey: nextKeys[i],
236+
Slot: res.Context.Slot,
133237
})
134238
}
135239

0 commit comments

Comments
 (0)