5
5
"fmt"
6
6
7
7
"github.com/rs/zerolog"
8
- "google.golang.org/grpc/codes"
9
- "google.golang.org/grpc/status"
10
8
11
9
"github.com/onflow/flow-go/engine/access/rest/http/request"
12
10
"github.com/onflow/flow-go/engine/access/rest/websockets/data_providers/models"
@@ -23,16 +21,16 @@ type accountStatusesArguments struct {
23
21
StartBlockID flow.Identifier // ID of the block to start subscription from
24
22
StartBlockHeight uint64 // Height of the block to start subscription from
25
23
Filter state_stream.AccountStatusFilter // Filter applied to events for a given subscription
26
- HeartbeatInterval * uint64 // Maximum number of blocks message won't be sent. Nil if not set
24
+ HeartbeatInterval uint64 // Maximum number of blocks message won't be sent
27
25
}
28
26
29
27
type AccountStatusesDataProvider struct {
30
28
* baseDataProvider
31
29
32
- logger zerolog. Logger
33
- stateStreamApi state_stream. API
34
-
35
- heartbeatInterval uint64
30
+ arguments accountStatusesArguments
31
+ messageIndex counters. StrictMonotonicCounter
32
+ blocksSinceLastMessage uint64
33
+ stateStreamApi state_stream. API
36
34
}
37
35
38
36
var _ DataProvider = (* AccountStatusesDataProvider )(nil )
@@ -44,55 +42,86 @@ func NewAccountStatusesDataProvider(
44
42
stateStreamApi state_stream.API ,
45
43
subscriptionID string ,
46
44
topic string ,
47
- arguments wsmodels.Arguments ,
45
+ rawArguments wsmodels.Arguments ,
48
46
send chan <- interface {},
49
47
chain flow.Chain ,
50
48
eventFilterConfig state_stream.EventFilterConfig ,
51
- heartbeatInterval uint64 ,
49
+ defaultHeartbeatInterval uint64 ,
52
50
) (* AccountStatusesDataProvider , error ) {
53
51
if stateStreamApi == nil {
54
52
return nil , fmt .Errorf ("this access node does not support streaming account statuses" )
55
53
}
56
54
57
- p := & AccountStatusesDataProvider {
58
- logger : logger .With ().Str ("component" , "account-statuses-data-provider" ).Logger (),
59
- stateStreamApi : stateStreamApi ,
60
- heartbeatInterval : heartbeatInterval ,
61
- }
62
-
63
- // Initialize arguments passed to the provider.
64
- accountStatusesArgs , err := parseAccountStatusesArguments (arguments , chain , eventFilterConfig )
55
+ args , err := parseAccountStatusesArguments (rawArguments , chain , eventFilterConfig , defaultHeartbeatInterval )
65
56
if err != nil {
66
57
return nil , fmt .Errorf ("invalid arguments for account statuses data provider: %w" , err )
67
58
}
68
- if accountStatusesArgs .HeartbeatInterval != nil {
69
- p .heartbeatInterval = * accountStatusesArgs .HeartbeatInterval
70
- }
71
-
72
- subCtx , cancel := context .WithCancel (ctx )
73
59
74
- p .baseDataProvider = newBaseDataProvider (
60
+ provider := newBaseDataProvider (
61
+ ctx ,
62
+ logger .With ().Str ("component" , "account-statuses-data-provider" ).Logger (),
63
+ nil ,
75
64
subscriptionID ,
76
65
topic ,
77
- arguments ,
78
- cancel ,
66
+ rawArguments ,
79
67
send ,
80
- p .createSubscription (subCtx , accountStatusesArgs ), // Set up a subscription to account statuses based on arguments.
81
68
)
82
69
83
- return p , nil
70
+ return & AccountStatusesDataProvider {
71
+ baseDataProvider : provider ,
72
+ arguments : args ,
73
+ messageIndex : counters .NewMonotonicCounter (0 ),
74
+ blocksSinceLastMessage : 0 ,
75
+ stateStreamApi : stateStreamApi ,
76
+ }, nil
84
77
}
85
78
86
79
// Run starts processing the subscription for events and handles responses.
80
+ // Must be called once.
87
81
//
88
- // Expected errors during normal operations:
89
- // - context.Canceled: if the operation is canceled, during an unsubscribe action.
82
+ // No errors expected during normal operations.
90
83
func (p * AccountStatusesDataProvider ) Run () error {
91
- return subscription .HandleSubscription (p .subscription , p .handleResponse ())
84
+ return run (
85
+ p .createAndStartSubscription (p .ctx , p .arguments ),
86
+ func (response * backend.AccountStatusesResponse ) error {
87
+ return p .sendResponse (response )
88
+ },
89
+ )
92
90
}
93
91
94
- // createSubscription creates a new subscription using the specified input arguments.
95
- func (p * AccountStatusesDataProvider ) createSubscription (ctx context.Context , args accountStatusesArguments ) subscription.Subscription {
92
+ // sendResponse processes an account statuses message and sends it to data provider's channel.
93
+ // This function is not safe to call concurrently.
94
+ //
95
+ // No errors are expected during normal operations
96
+ func (p * AccountStatusesDataProvider ) sendResponse (response * backend.AccountStatusesResponse ) error {
97
+ // Only send a response if there's meaningful data to send
98
+ // or the heartbeat interval limit is reached
99
+ p .blocksSinceLastMessage += 1
100
+ accountEmittedEvents := len (response .AccountEvents ) != 0
101
+ reachedHeartbeatLimit := p .blocksSinceLastMessage >= p .arguments .HeartbeatInterval
102
+ if ! accountEmittedEvents && ! reachedHeartbeatLimit {
103
+ return nil
104
+ }
105
+
106
+ accountStatusesPayload := models .NewAccountStatusesResponse (response , p .messageIndex .Value ())
107
+ resp := models.BaseDataProvidersResponse {
108
+ SubscriptionID : p .ID (),
109
+ Topic : p .Topic (),
110
+ Payload : accountStatusesPayload ,
111
+ }
112
+ p .send <- & resp
113
+
114
+ p .blocksSinceLastMessage = 0
115
+ p .messageIndex .Increment ()
116
+
117
+ return nil
118
+ }
119
+
120
+ // createAndStartSubscription creates a new subscription using the specified input arguments.
121
+ func (p * AccountStatusesDataProvider ) createAndStartSubscription (
122
+ ctx context.Context ,
123
+ args accountStatusesArguments ,
124
+ ) subscription.Subscription {
96
125
if args .StartBlockID != flow .ZeroID {
97
126
return p .stateStreamApi .SubscribeAccountStatusesFromStartBlockID (ctx , args .StartBlockID , args .Filter )
98
127
}
@@ -104,46 +133,12 @@ func (p *AccountStatusesDataProvider) createSubscription(ctx context.Context, ar
104
133
return p .stateStreamApi .SubscribeAccountStatusesFromLatestBlock (ctx , args .Filter )
105
134
}
106
135
107
- // handleResponse processes an account statuses and sends the formatted response.
108
- //
109
- // No errors are expected during normal operations.
110
- func (p * AccountStatusesDataProvider ) handleResponse () func (accountStatusesResponse * backend.AccountStatusesResponse ) error {
111
- blocksSinceLastMessage := uint64 (0 )
112
- messageIndex := counters .NewMonotonicCounter (0 )
113
-
114
- return func (accountStatusesResponse * backend.AccountStatusesResponse ) error {
115
- // check if there are any events in the response. if not, do not send a message unless the last
116
- // response was more than HeartbeatInterval blocks ago
117
- if len (accountStatusesResponse .AccountEvents ) == 0 {
118
- blocksSinceLastMessage ++
119
- if blocksSinceLastMessage < p .heartbeatInterval {
120
- return nil
121
- }
122
- }
123
- blocksSinceLastMessage = 0
124
-
125
- index := messageIndex .Value ()
126
- if ok := messageIndex .Set (messageIndex .Value () + 1 ); ! ok {
127
- return status .Errorf (codes .Internal , "message index already incremented to %d" , messageIndex .Value ())
128
- }
129
-
130
- accountStatusesPayload := models .NewAccountStatusesResponse (accountStatusesResponse , index )
131
- response := models.BaseDataProvidersResponse {
132
- SubscriptionID : p .ID (),
133
- Topic : p .Topic (),
134
- Payload : accountStatusesPayload ,
135
- }
136
- p .send <- & response
137
-
138
- return nil
139
- }
140
- }
141
-
142
136
// parseAccountStatusesArguments validates and initializes the account statuses arguments.
143
137
func parseAccountStatusesArguments (
144
138
arguments wsmodels.Arguments ,
145
139
chain flow.Chain ,
146
140
eventFilterConfig state_stream.EventFilterConfig ,
141
+ defaultHeartbeatInterval uint64 ,
147
142
) (accountStatusesArguments , error ) {
148
143
allowedFields := map [string ]struct {}{
149
144
"start_block_id" : {},
@@ -168,7 +163,7 @@ func parseAccountStatusesArguments(
168
163
args .StartBlockHeight = startBlockHeight
169
164
170
165
// Parse 'heartbeat_interval' argument
171
- heartbeatInterval , err := extractHeartbeatInterval (arguments )
166
+ heartbeatInterval , err := extractHeartbeatInterval (arguments , defaultHeartbeatInterval )
172
167
if err != nil {
173
168
return accountStatusesArguments {}, err
174
169
}
0 commit comments