-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathworker.go
131 lines (108 loc) · 3.26 KB
/
worker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package async_treasury
import (
"context"
"sync"
"time"
"github.com/newrelic/go-agent/v3/newrelic"
"github.com/pkg/errors"
"github.com/code-payments/code-server/pkg/database/query"
"github.com/code-payments/code-server/pkg/metrics"
"github.com/code-payments/code-server/pkg/retry"
splitter_token "github.com/code-payments/code-server/pkg/solana/splitter"
"github.com/code-payments/code-server/pkg/code/data/treasury"
)
const (
maxRecordBatchSize = 10
)
var (
// todo: distributed lock
treasuryPoolLock sync.Mutex
)
func (p *service) worker(serviceCtx context.Context, state treasury.TreasuryPoolState, interval time.Duration) error {
delay := interval
var cursor query.Cursor
err := retry.Loop(
func() (err error) {
time.Sleep(delay)
nr := serviceCtx.Value(metrics.NewRelicContextKey).(*newrelic.Application)
m := nr.StartTransaction("async__treasury_pool_service__handle_" + state.String())
defer m.End()
tracedCtx := newrelic.NewContext(serviceCtx, m)
// Get a batch of records in similar state
items, err := p.data.GetAllTreasuryPoolsByState(
tracedCtx,
state,
query.WithCursor(cursor),
query.WithDirection(query.Ascending),
query.WithLimit(maxRecordBatchSize),
)
if err != nil && err != treasury.ErrTreasuryPoolNotFound {
cursor = query.EmptyCursor
return err
}
// Process the batch of accounts in parallel
var wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
go func(record *treasury.Record) {
defer wg.Done()
err := p.handle(tracedCtx, record)
if err != nil {
m.NoticeError(err)
}
}(item)
}
wg.Wait()
// Update cursor to point to the next set of pool
if len(items) > 0 {
cursor = query.ToCursor(items[len(items)-1].Id)
} else {
cursor = query.EmptyCursor
}
return nil
},
retry.NonRetriableErrors(context.Canceled),
)
return err
}
func (p *service) handle(ctx context.Context, record *treasury.Record) error {
switch record.State {
case treasury.TreasuryPoolStateAvailable:
return p.handleAvailable(ctx, record)
default:
return nil
}
}
func (p *service) handleAvailable(ctx context.Context, record *treasury.Record) error {
err := p.updateAccountState(ctx, record)
if err != nil && err != treasury.ErrStaleTreasuryPoolState {
return err
}
err = p.syncMerkleTree(ctx, record)
if err != nil {
return err
}
// Runs last, since we have an expectation that our state is up-to-date before
// saving a new recent root.
return p.maybeSaveRecentRoot(ctx, record)
}
func (p *service) updateAccountState(ctx context.Context, record *treasury.Record) error {
if record.DataVersion != splitter_token.DataVersion1 {
return errors.New("unsupported data version")
}
// todo: Use a smarter block. Perhaps from the last finalized payment?
data, solanaBlock, err := p.data.GetBlockchainAccountDataAfterBlock(ctx, record.Address, record.SolanaBlock)
if err != nil {
return errors.Wrap(err, "error querying latest account data from blockchain")
}
var unmarshalled splitter_token.PoolAccount
err = unmarshalled.Unmarshal(data)
if err != nil {
return errors.Wrap(err, "error unmarshalling account data")
}
err = record.Update(&unmarshalled, solanaBlock)
if err != nil {
return err
}
return p.data.SaveTreasuryPool(ctx, record)
}