-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add subscribe account statuses endpoint #762
base: master
Are you sure you want to change the base?
Changes from all commits
50f189a
0c0934c
e03245c
4ebcd54
30e2f97
6374402
9748a29
f998352
3aeee13
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ import ( | |
"errors" | ||
"fmt" | ||
"io" | ||
"sync/atomic" | ||
|
||
"github.com/onflow/flow/protobuf/go/flow/entities" | ||
"google.golang.org/grpc" | ||
|
@@ -84,6 +85,7 @@ type BaseClient struct { | |
close func() error | ||
jsonOptions []json.Option | ||
eventEncoding flow.EventEncodingVersion | ||
messageIndex atomic.Uint64 | ||
} | ||
|
||
// NewBaseClient creates a new gRPC handler for network communication. | ||
|
@@ -1296,3 +1298,159 @@ func receiveBlocksFromClient[Client interface { | |
} | ||
} | ||
} | ||
|
||
func (c *BaseClient) SubscribeAccountStatusesFromStartHeight( | ||
ctx context.Context, | ||
startHeight uint64, | ||
filter flow.AccountStatusFilter, | ||
opts ...grpc.CallOption, | ||
) (<-chan flow.AccountStatus, <-chan error, error) { | ||
request := &executiondata.SubscribeAccountStatusesFromStartHeightRequest{ | ||
StartBlockHeight: startHeight, | ||
EventEncodingVersion: c.eventEncoding, | ||
} | ||
request.Filter = &executiondata.StatusFilter{ | ||
EventType: filter.EventTypes, | ||
Address: filter.Addresses, | ||
} | ||
|
||
subscribeClient, err := c.executionDataClient.SubscribeAccountStatusesFromStartHeight(ctx, request, opts...) | ||
if err != nil { | ||
return nil, nil, newRPCError(err) | ||
} | ||
|
||
accountStatutesChan := make(chan flow.AccountStatus) | ||
errChan := make(chan error) | ||
|
||
go func() { | ||
defer close(accountStatutesChan) | ||
defer close(errChan) | ||
receiveAccountStatusesFromClient(ctx, subscribeClient, accountStatutesChan, errChan, &c.messageIndex) | ||
}() | ||
|
||
return accountStatutesChan, errChan, nil | ||
} | ||
|
||
func (c *BaseClient) SubscribeAccountStatusesFromStartBlockID( | ||
ctx context.Context, | ||
startBlockID flow.Identifier, | ||
filter flow.AccountStatusFilter, | ||
opts ...grpc.CallOption, | ||
) (<-chan flow.AccountStatus, <-chan error, error) { | ||
request := &executiondata.SubscribeAccountStatusesFromStartBlockIDRequest{ | ||
StartBlockId: startBlockID.Bytes(), | ||
EventEncodingVersion: c.eventEncoding, | ||
} | ||
request.Filter = &executiondata.StatusFilter{ | ||
EventType: filter.EventTypes, | ||
Address: filter.Addresses, | ||
} | ||
|
||
subscribeClient, err := c.executionDataClient.SubscribeAccountStatusesFromStartBlockID(ctx, request, opts...) | ||
if err != nil { | ||
return nil, nil, newRPCError(err) | ||
} | ||
|
||
accountStatutesChan := make(chan flow.AccountStatus) | ||
errChan := make(chan error) | ||
|
||
go func() { | ||
defer close(accountStatutesChan) | ||
defer close(errChan) | ||
receiveAccountStatusesFromClient(ctx, subscribeClient, accountStatutesChan, errChan, &c.messageIndex) | ||
}() | ||
|
||
return accountStatutesChan, errChan, nil | ||
} | ||
|
||
func (c *BaseClient) SubscribeAccountStatusesFromLatestBlock( | ||
ctx context.Context, | ||
filter flow.AccountStatusFilter, | ||
opts ...grpc.CallOption, | ||
) (<-chan flow.AccountStatus, <-chan error, error) { | ||
request := &executiondata.SubscribeAccountStatusesFromLatestBlockRequest{ | ||
EventEncodingVersion: c.eventEncoding, | ||
} | ||
request.Filter = &executiondata.StatusFilter{ | ||
EventType: filter.EventTypes, | ||
Address: filter.Addresses, | ||
} | ||
|
||
subscribeClient, err := c.executionDataClient.SubscribeAccountStatusesFromLatestBlock(ctx, request, opts...) | ||
if err != nil { | ||
return nil, nil, newRPCError(err) | ||
} | ||
|
||
accountStatutesChan := make(chan flow.AccountStatus) | ||
errChan := make(chan error) | ||
|
||
go func() { | ||
defer close(accountStatutesChan) | ||
defer close(errChan) | ||
receiveAccountStatusesFromClient(ctx, subscribeClient, accountStatutesChan, errChan, &c.messageIndex) | ||
}() | ||
|
||
return accountStatutesChan, errChan, nil | ||
} | ||
|
||
func receiveAccountStatusesFromClient[Client interface { | ||
Recv() (*executiondata.SubscribeAccountStatusesResponse, error) | ||
}]( | ||
ctx context.Context, | ||
client Client, | ||
accountStatutesChan chan<- flow.AccountStatus, | ||
errChan chan<- error, | ||
previousMsgIndex *atomic.Uint64, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. message index is specific to a stream. So when you start a new stream, the responses start with message index 0 (or 1, I don't remember how it started), then increment. I think it's fine to track it only within this function as a regular uint64 value. There should not be any concurrent access. |
||
) { | ||
sendErr := func(err error) { | ||
select { | ||
case <-ctx.Done(): | ||
case errChan <- err: | ||
} | ||
} | ||
|
||
for { | ||
accountStatusResponse, err := client.Recv() | ||
if err != nil { | ||
if err == io.EOF { | ||
// End of stream, return gracefully | ||
return | ||
} | ||
|
||
sendErr(fmt.Errorf("error receiving account status: %w", err)) | ||
return | ||
} | ||
|
||
accountStatus, err := convert.MessageToAccountStatus(accountStatusResponse) | ||
if err != nil { | ||
sendErr(fmt.Errorf("error converting message to account status: %w", err)) | ||
return | ||
} | ||
|
||
illia-malachyn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err = checkAndIncrementMessageIndex(previousMsgIndex, accountStatus.MessageIndex); err != nil { | ||
sendErr(fmt.Errorf("error checking message index. messages are not ordered: %w", err)) | ||
return | ||
} | ||
|
||
select { | ||
case <-ctx.Done(): | ||
return | ||
case accountStatutesChan <- accountStatus: | ||
} | ||
} | ||
} | ||
|
||
func checkAndIncrementMessageIndex(previousMessageIndex *atomic.Uint64, currentMessageIndex uint64) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @peterargue is this implementation good enough? It might be good to add more tests for different msg indexes. However, I'm not sure what cases to cover. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can simply it a bit by tracking "next" instead of the current. next := 0 // since we start at 0, the first expected should be 0
for {
if msg.MessageIndex != next {
return err
}
next = msg.MessageIndex + 1
} |
||
local := previousMessageIndex.Load() | ||
|
||
if local == 0 && currentMessageIndex == 0 { | ||
return nil | ||
} | ||
|
||
if currentMessageIndex != local+1 { | ||
return errors.New("current message index is not exactly one larger than the previous one") | ||
} | ||
|
||
previousMessageIndex.Add(1) | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using
Client
as the name is a little confusing, especially within a client library. how about breaking this out to a stand along type since it's used in a bunch of places, and renaming it toStream
?