From 477242ed522dd4481652a933f5b2eac724662028 Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Fri, 1 Dec 2023 17:26:21 +0200 Subject: [PATCH 01/23] Added first part of document --- ...1114-accessnode-streaming-api-expansion.md | 282 ++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 protocol/20231114-accessnode-streaming-api-expansion.md diff --git a/protocol/20231114-accessnode-streaming-api-expansion.md b/protocol/20231114-accessnode-streaming-api-expansion.md new file mode 100644 index 00000000..52df6edb --- /dev/null +++ b/protocol/20231114-accessnode-streaming-api-expansion.md @@ -0,0 +1,282 @@ +--- +status: draft +flip: NNN (set to the issue number) +authors: Andrii Slisarchuk (andriyslisarchuk@gmail.com) +sponsor: AN Expert (core-contributor@example.org) +updated: 2023-11-14 +--- + +# [FLIP DRAFT] Access Streaming API Expansion + +## Objective + +This FLIP document aims to logically extend the functionality proposed in [FLIP73](https://github.com/onflow/flips/blob/main/protocol/20230309-accessnode-event-streaming-api.md). + +## Motivation and User Benefit + +The newly implemented Access Streaming API offers limited functionality, featuring a single gRPC subscription, `SubscribeExecutionData`, and one gRPC/REST subscription, `SubscribeEvents`. On the other hand, the regular polling Access API version has a variety of endpoints for use. This FLIP identifies essential subscriptions within the current Access Subscription API, crucially based on user proposals, aiming to expand its functionality. Furthermore, it addresses recommended improvements to existing REST WebSocket connections, as suggested by the community, aimed at resolving issues arising from a large number of connections while maintaining granularity within the subscription API. + +The enhanced version seeks to provide a simpler and more efficient REST WebSocket connection, offering a broader range of useful subscription endpoints for obtaining blockchain updates. + +## Design Proposal + +The Access Node Subscription API would introduce the following new streaming endpoints: + +- SubscribeBlocks +- SubscribeBlocksHeaders +- SubscribeBlocksLightweight +- SendAndSubscribeTransactionStatuses +- SubscribeAccountStatuses - ? +- SubscribeResourcesMovement - ? +- SubscribeResourcesChanges - ? +- SubscribeTransactionResults - ? + +Additionally, it proposes an improved version of the REST WebSocket Subscription with a single connection. + +### WebSocket Subscriptions with a Single Connection + +In the current version of REST subscription, [a new WebSocket connection is created](https://github.com/onflow/flow-go/blob/9577079fd24aad4c1fc1fc8710ef2686ead85ca4/engine/access/rest/routes/websocket_handler.go#L288) for each subscription request. However, with the potential growth in the number of endpoints available for subscription, this behavior becomes undesirable due to several reasons: + +- It results in a system for connection handling on the client side that is difficult to maintain. +- It causes unnecessary network load, impacting the overall performance of the application. +- Eventually, it will hit the connection limit, posing constraints. +- It complicates future expansion efforts. + +To address this, an improvement in WebSocket behavior is proposed by implementing a single connection for each subscription. This improvement is commonly referred to as the "WebSocket Components Subscription Pattern", and an example of its React implementation can be found [here](https://blog.stackademic.com/websockets-and-react-wscontext-and-components-subscription-pattern-4e580fc67bb5). + +The proposed enhancement involves the following aspects: + +- The `WSHandler` will initiate a connection only for the initial subscription and retain it for subsequent subscriptions. +- An unsubscribe mechanism will be introduced. With multiple subscriptions on a single connection, simply closing this connection to halt updates becomes insufficient. Therefore, a dedicated endpoint for unsubscribing from undesired updates is necessary. +- Each subscription response will now include a message type. For instance, the `SubscribeEvents` endpoint will result in a message with `type: "Events"`, while `SubscribeBlocksHeaders` will yield `type: "BlockHeader"` in messages, and so forth. This is essential to distinguish between various subscription results that will be delivered through a single connection. + +### SubscribeBlocks + +This endpoint enables users to subscribe to the streaming of blocks, commencing from a provided block ID or height. Additionally, users are required to specify the desired status of the streamed blocks to receive. Each block's response should include the `Block` containing `Header` and `Payload`. + +- **BlockHeight** (Block height of the streamed block) +- **BlockId** (Block ID of the streamed block) +- **BlockStatus** (`BlockStatusSealed` or `BlockStatusFinalized`) + +Usage example: + +```go +req := &executiondata.SubscribeBlocksRequest{ + // If no start block height or ID is provided, the latest block is used + StartBlockHeight: 1234, + // or StartBlockID: startBlockID[:], + BlockStatus: BlockStatus.BlockStatusFinalized, // Use BlockStatusSealed to receive only finalized or only sealed blocks +} + +stream, err := client.SubscribeBlocks(ctx, req) +if err != nil { + log.Fatalf("error subscribing to blocks: %v", err) +} + +for { + resp, err := stream.Recv() + if err == io.EOF { + break + } + + if err != nil { + log.Fatalf("error receiving block: %v", err) + } + + block := resp.GetBlock() + + log.Printf("received block with ID: %x, Height: %d, and Payload Hash: %x", + block.ID(), + block.Header.Height, + block.Payload.Hash(), + ) +} +``` + +### SubscribeBlocksHeaders + +This endpoint enables users to subscribe to the streaming of block headers, commencing from a provided block ID or height. Additionally, users are required to specify the desired status of the streamed blocks to receive. The response for each block header should include the block's `Header`. This is a lighter version of `SubscribeBlocks` as it does not include the heavier `Payload` field. + +- **BlockHeight** (Block height of the streamed block) +- **BlockId** (Block ID of the streamed block) +- **BlockStatus** (`BlockStatusSealed` or `BlockStatusFinalized`) + +Usage example: + +```go +req := &executiondata.SubscribeBlocksRequest{ + // If no start block height or Id is provided, the latest block is used + StartBlockHeight: 1234, + // or StartBlockId: startBlockID[:], + BlockStatus: BlockStatus.BlockStatusFinalized // Use BlockStatusSealed to receive only finalized or only sealed blocks headers +} + +stream, err := client.SubscribeBlocksHeaders(ctx, req) +if err != nil { + log.Fatalf("error subscribing to blocks headers: %v", err) + +} + +for { + resp, err := stream.Recv() + if err == io.EOF { + break + } + + if err != nil { + log.Fatalf("error receiving block header: %v", err) + } + + blockHeader := resp.GetBlockHeader() + + log.Printf("received block header with ID: %x, Height: %d and Payload Hash: %x", + blockHeader.ID(), + blockHeader.Height, + blockHeader.PayloadHash, + ) +} +``` + +### SubscribeBlocksLightweight + +This endpoint enables users to subscribe to the streaming of lightweight block information, commencing from a provided block ID or height. Additionally, users are required to specify the desired status of the streamed blocks to receive. The response for each block should include only the block's `ID`, `Height` and `Timestamp`. This is the lightest version among all block subscriptions. + +- **BlockHeight** (Block height of the streamed block) +- **BlockId** (Block ID of the streamed block) +- **BlockStatus** (`BlockStatusSealed` or `BlockStatusFinalized`) + +Usage example: + +```go +req := &executiondata.SubscribeBlocksLightweightRequest{ + // If no start block height or Id is provided, the latest block is used + StartBlockHeight: 1234, + // or StartBlockId: startBlockID[:], + BlockStatus: BlockStatus.BlockStatusFinalized // Use BlockStatusSealed to receive only finalized or only sealed blocks information +} + +stream, err := client.SubscribeBlocksLightweight(ctx, req) +if err != nil { + log.Fatalf("error subscribing to lightweight blocks : %v", err) + +} + +for { + resp, err := stream.Recv() + if err == io.EOF { + break + } + + if err != nil { + log.Fatalf("error receiving block lightweight: %v", err) + } + + log.Printf("received lightweight block with ID: %x, Height: %d, parent block ID: %x and Timestamp: %s", + resp.GetBlockId(), + resp.GetBlockHeight(), + resp.GetParentBlockId(), + resp.GetBlockTimestamp().String(), + ) +} +``` + +### SendAndSubscribeTransactionStatuses + +Streaming of a transaction and status changes + +This would be great. Something like a SendAndSubscribe endpoint where you submit a tx, and the status is streamed back until the block the tx was included in is sealed. + +- **TransactionMsg** (transaction that will be submited to stream status) + +Usage example: + +```go +txMsg, err := transactionToMessage(tx) +if err != nil { + log.Fatalf("error converting transaction to message: %v", err) +} + +req := &executiondata.SendTransactionRequest{ + Transaction: txMsg, +} + +stream, err := client.SendAndSubscribeTransactionStatuses(ctx, req) +if err != nil { + log.Fatalf("error subscribing to transaction statuses : %v", err) + +} + +for { + resp, err := stream.Recv() + if err == io.EOF { + break + } + + if err != nil { + log.Fatalf("error receiving transaction status: %v", err) + } + + txResult := resp.GetTransactionResult() + + log.Printf("received transaction status with tx ID: %x, status: %s, and error: %v", + resp.GetTransactionID(), + txResult.Status.String(), + txResult.Error, + ) +} +``` + +### SubscribeAccountStatuses - ? + +Streaming of an account state changes + +This should give possibility to subscribe to account and track statuses changed (Created, Updated) + +- **Address** (address of account to stream status changes) + +Usage example: + +```go +req := &executiondata.SubscribeAccountStatusesRequest{ + Address: "123456789abcdef0", +} + +stream, err := client.SubscribeAccountStatuses(ctx, req) +if err != nil { + log.Fatalf("error subscribing to account statuses : %v", err) + +} + +for { + resp, err := stream.Recv() + if err == io.EOF { + break + } + + if err != nil { + log.Fatalf("error receiving account status: %v", err) + } + + // ????? +} +``` + +### SubscribeResourcesMovement - ? + +Streaming of resource movement by type + +https://github.com/onflow/flow-go-sdk/blob/master/examples/storage_usage/main.go + +### SubscribeResourcesChanges - ? + +Streaming of resource changes + +these would be awesome. I think there’s some R&D(Research and Development) work needed before it’s possible but definitely valuable. Especially the account state changes as bluesign noted. + +### SubscribeTransactionResults - ? + +Streaming of transaction results starting from the provided block ID or height, and /or collection ID and/or transaction ID. The result should consist of an array of TransactionResult. + +This sounds like effectively a streaming version of GetTransactionResultsByBlockID. + +I can see some value in searching for transaction ID, where the behavior could be to return the result when the tx is executed/sealed. We’d have to explore this a bit more. I think there may be some open questions for this (e.g. what block is passed in to start? when does it stop searching? what happens if the tx is never finalized?). Much of that would be simplified in an endpoint like the next one. From 6befd29db6303ce337e59abde2f50b10f26c5cb2 Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Fri, 1 Dec 2023 20:11:40 +0200 Subject: [PATCH 02/23] Added extended descriptions to endpoints. --- ...1114-accessnode-streaming-api-expansion.md | 78 ++++++++----------- 1 file changed, 33 insertions(+), 45 deletions(-) diff --git a/protocol/20231114-accessnode-streaming-api-expansion.md b/protocol/20231114-accessnode-streaming-api-expansion.md index 52df6edb..9437dc5a 100644 --- a/protocol/20231114-accessnode-streaming-api-expansion.md +++ b/protocol/20231114-accessnode-streaming-api-expansion.md @@ -182,101 +182,89 @@ for { ### SendAndSubscribeTransactionStatuses -Streaming of a transaction and status changes +This endpoint enables users to send a transaction and immediately subscribe to its status changes. The status is streamed back until the block containing the transaction becomes sealed. Each response for the transaction status should include the `ID`, `Status` and `Error` fields. -This would be great. Something like a SendAndSubscribe endpoint where you submit a tx, and the status is streamed back until the block the tx was included in is sealed. - -- **TransactionMsg** (transaction that will be submited to stream status) +- **TransactionMsg** (transaction submitted to stream status) Usage example: ```go txMsg, err := transactionToMessage(tx) if err != nil { - log.Fatalf("error converting transaction to message: %v", err) + log.Fatalf("error converting transaction to message: %v", err) } req := &executiondata.SendTransactionRequest{ - Transaction: txMsg, + Transaction: txMsg, } stream, err := client.SendAndSubscribeTransactionStatuses(ctx, req) if err != nil { - log.Fatalf("error subscribing to transaction statuses : %v", err) - + log.Fatalf("error subscribing to transaction statuses: %v", err) } for { - resp, err := stream.Recv() - if err == io.EOF { - break - } + resp, err := stream.Recv() + if err == io.EOF { + break + } - if err != nil { - log.Fatalf("error receiving transaction status: %v", err) - } + if err != nil { + log.Fatalf("error receiving transaction status: %v", err) + } - txResult := resp.GetTransactionResult() + txResult := resp.GetTransactionResult() - log.Printf("received transaction status with tx ID: %x, status: %s, and error: %v", - resp.GetTransactionID(), - txResult.Status.String(), + log.Printf("received transaction status with tx ID: %x, status: %s, and error: %v", + resp.GetTransactionID(), + txResult.Status.String(), txResult.Error, - ) + ) } ``` ### SubscribeAccountStatuses - ? -Streaming of an account state changes +This endpoint allows users to subscribe to the streaming of account status changes. Each response for the account status should include the `?` fields. -This should give possibility to subscribe to account and track statuses changed (Created, Updated) - -- **Address** (address of account to stream status changes) +- **Address** (address of the account to stream status changes) Usage example: ```go req := &executiondata.SubscribeAccountStatusesRequest{ - Address: "123456789abcdef0", + Address: "123456789abcdef0", } stream, err := client.SubscribeAccountStatuses(ctx, req) if err != nil { - log.Fatalf("error subscribing to account statuses : %v", err) - + log.Fatalf("error subscribing to account statuses: %v", err) } for { - resp, err := stream.Recv() - if err == io.EOF { - break - } + resp, err := stream.Recv() + if err == io.EOF { + break + } - if err != nil { - log.Fatalf("error receiving account status: %v", err) - } + if err != nil { + log.Fatalf("error receiving account status: %v", err) + } - // ????? + // Insert appropriate handling for received account status } + ``` ### SubscribeResourcesMovement - ? -Streaming of resource movement by type +Streaming of resource movement by type - Research required https://github.com/onflow/flow-go-sdk/blob/master/examples/storage_usage/main.go ### SubscribeResourcesChanges - ? -Streaming of resource changes +Streaming of resource changes - Research required -these would be awesome. I think there’s some R&D(Research and Development) work needed before it’s possible but definitely valuable. Especially the account state changes as bluesign noted. - -### SubscribeTransactionResults - ? - -Streaming of transaction results starting from the provided block ID or height, and /or collection ID and/or transaction ID. The result should consist of an array of TransactionResult. - -This sounds like effectively a streaming version of GetTransactionResultsByBlockID. +https://github.com/onflow/flow-go-sdk/blob/master/examples/storage_usage/main.go -I can see some value in searching for transaction ID, where the behavior could be to return the result when the tx is executed/sealed. We’d have to explore this a bit more. I think there may be some open questions for this (e.g. what block is passed in to start? when does it stop searching? what happens if the tx is never finalized?). Much of that would be simplified in an endpoint like the next one. From f5621f0edbe53a3eaed67a878bf6b08efe2a4adb Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Fri, 1 Dec 2023 21:45:39 +0200 Subject: [PATCH 03/23] Added performance and dependencies --- ...31114-accessnode-streaming-api-expansion.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/protocol/20231114-accessnode-streaming-api-expansion.md b/protocol/20231114-accessnode-streaming-api-expansion.md index 9437dc5a..7e805086 100644 --- a/protocol/20231114-accessnode-streaming-api-expansion.md +++ b/protocol/20231114-accessnode-streaming-api-expansion.md @@ -14,7 +14,7 @@ This FLIP document aims to logically extend the functionality proposed in [FLIP7 ## Motivation and User Benefit -The newly implemented Access Streaming API offers limited functionality, featuring a single gRPC subscription, `SubscribeExecutionData`, and one gRPC/REST subscription, `SubscribeEvents`. On the other hand, the regular polling Access API version has a variety of endpoints for use. This FLIP identifies essential subscriptions within the current Access Subscription API, crucially based on user proposals, aiming to expand its functionality. Furthermore, it addresses recommended improvements to existing REST WebSocket connections, as suggested by the community, aimed at resolving issues arising from a large number of connections while maintaining granularity within the subscription API. +The newly implemented Access Streaming API offers limited functionality, featuring a single gRPC subscription, `SubscribeExecutionData`, and one gRPC/REST subscription, `SubscribeEvents` (see [onflow/flow-go#3723](https://github.com/onflow/flow-go/pull/3723) for implementation detail). On the other hand, the regular polling Access API version has a variety of endpoints for use. This FLIP identifies essential subscriptions within the current Access Subscription API, crucially based on user proposals, aiming to expand its functionality. Furthermore, it addresses recommended improvements to existing REST WebSocket connections, as suggested by the community, aimed at resolving issues arising from a large number of connections while maintaining granularity within the subscription API. The enhanced version seeks to provide a simpler and more efficient REST WebSocket connection, offering a broader range of useful subscription endpoints for obtaining blockchain updates. @@ -29,7 +29,6 @@ The Access Node Subscription API would introduce the following new streaming end - SubscribeAccountStatuses - ? - SubscribeResourcesMovement - ? - SubscribeResourcesChanges - ? -- SubscribeTransactionResults - ? Additionally, it proposes an improved version of the REST WebSocket Subscription with a single connection. @@ -268,3 +267,18 @@ Streaming of resource changes - Research required https://github.com/onflow/flow-go-sdk/blob/master/examples/storage_usage/main.go +### Performance Implications + +The addition of more subscription endpoints aims to provide users with an efficient means to retrieve promptly available blockchain data, potentially shifting the focus away from polling toward subscription-based access. This transition should decrease unnecessary polling requests for commonly sought-after data, thereby reducing the overall system load. Consequently, users will effortlessly obtain commonly used data without additional complexity on the client side. + +Regarding the REST aspect, it's crucial to emphasize the importance of evaluating the efficiency gained by transitioning websocket subscriptions from multiple connections to a single connection. This evaluation should encompass testing for potential limitations, edge cases, and overall performance metrics under normal and critical load conditions. + +### Nodes and Clients + +The anticipated performance gains and drawbacks should correspond to those outlined in [FLIP73 -> Nodes](https://github.com/onflow/flips/blob/main/protocol/20230309-accessnode-event-streaming-api.md#nodes) and [FLIP73 -> Clients](https://github.com/onflow/flips/blob/main/protocol/20230309-accessnode-event-streaming-api.md#clients). However, these outcomes will correlate with a broader range of available endpoints and their respective usage. + + +### Dependencies + +This FLIP should not necessitate any new dependencies. Its foundation will primarily rely on the existing functionality of the Access Streaming API and involve modifications and enhancements to the current codebase. + From 02ecadc276a57ef06efc9c09235c4a3d36a5230d Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Wed, 6 Dec 2023 10:57:05 +0200 Subject: [PATCH 04/23] Added first draft for resources changes and movement. Left ref links for now. --- ...1114-accessnode-streaming-api-expansion.md | 73 ++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/protocol/20231114-accessnode-streaming-api-expansion.md b/protocol/20231114-accessnode-streaming-api-expansion.md index 7e805086..6a881115 100644 --- a/protocol/20231114-accessnode-streaming-api-expansion.md +++ b/protocol/20231114-accessnode-streaming-api-expansion.md @@ -257,12 +257,82 @@ for { ### SubscribeResourcesMovement - ? +This endpoint allows users to subscribe to the streaming of account resource movement. Each response for the account resource movement should include the `?` fields. + +- **Address** (address of the account to stream resources movement) +- **Type** (type of the resources in the account storage to stream) + +Usage example: + +```go +req := &executiondata.SubscribeResourcesMovementRequest{ + Address: "123456789abcdef0", + Type: "", +} + +stream, err := client.SubscribeResourcesMovement(ctx, req) +if err != nil { + log.Fatalf("error subscribing to account resource movement: %v", err) +} + +for { + resp, err := stream.Recv() + if err == io.EOF { + break + } + + if err != nil { + log.Fatalf("error receiving account resource movement: %v", err) + } + + // Insert appropriate handling for received account status +} + +``` + Streaming of resource movement by type - Research required -https://github.com/onflow/flow-go-sdk/blob/master/examples/storage_usage/main.go +- https://github.com/onflow/flow-go-sdk/blob/master/examples/storage_usage/main.go +- https://developers.flow.com/tools/flow-js-testing/api#storage-inspection +- https://github.com/onflow/flow-js-testing/blob/e2d4f1a3c8fe8549a01785566090d8c8adcb6903/src/storage.js#L257 +- https://github.com/onflow/flow-go/blob/master/fvm/environment/accounts_status.go +- https://developers.flow.com/build/basics/events#core-events +- https://forum.flow.com/t/suggestion-default-events/2170 + ### SubscribeResourcesChanges - ? +This endpoint allows users to subscribe to the streaming of account resource changes. Each response for the account resource changes should include the `?` fields. + +- **Address** (address of the account to stream status changes) + +Usage example: + +```go +req := &executiondata.SubscribeResourcesChangesRequest{ + Address: "123456789abcdef0", +} + +stream, err := client.SubscribeResourcesChanges(ctx, req) +if err != nil { + log.Fatalf("error subscribing to account resources statuses: %v", err) +} + +for { + resp, err := stream.Recv() + if err == io.EOF { + break + } + + if err != nil { + log.Fatalf("error receiving account resources status: %v", err) + } + + // Insert appropriate handling for received account status +} + +``` + Streaming of resource changes - Research required https://github.com/onflow/flow-go-sdk/blob/master/examples/storage_usage/main.go @@ -277,7 +347,6 @@ Regarding the REST aspect, it's crucial to emphasize the importance of evaluatin The anticipated performance gains and drawbacks should correspond to those outlined in [FLIP73 -> Nodes](https://github.com/onflow/flips/blob/main/protocol/20230309-accessnode-event-streaming-api.md#nodes) and [FLIP73 -> Clients](https://github.com/onflow/flips/blob/main/protocol/20230309-accessnode-event-streaming-api.md#clients). However, these outcomes will correlate with a broader range of available endpoints and their respective usage. - ### Dependencies This FLIP should not necessitate any new dependencies. Its foundation will primarily rely on the existing functionality of the Access Streaming API and involve modifications and enhancements to the current codebase. From f7979c6cadf73d1f4e94d17922a6bae1d11bf3d1 Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Thu, 7 Dec 2023 15:56:40 +0200 Subject: [PATCH 05/23] Cleaned up. Added disscussion questions --- ...1114-accessnode-streaming-api-expansion.md | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/protocol/20231114-accessnode-streaming-api-expansion.md b/protocol/20231114-accessnode-streaming-api-expansion.md index 6a881115..08985da9 100644 --- a/protocol/20231114-accessnode-streaming-api-expansion.md +++ b/protocol/20231114-accessnode-streaming-api-expansion.md @@ -26,9 +26,9 @@ The Access Node Subscription API would introduce the following new streaming end - SubscribeBlocksHeaders - SubscribeBlocksLightweight - SendAndSubscribeTransactionStatuses -- SubscribeAccountStatuses - ? -- SubscribeResourcesMovement - ? -- SubscribeResourcesChanges - ? +- SubscribeAccountStatuses +- SubscribeResourcesMovement +- SubscribeResourcesChanges Additionally, it proposes an improved version of the REST WebSocket Subscription with a single connection. @@ -222,9 +222,11 @@ for { } ``` -### SubscribeAccountStatuses - ? +### SubscribeAccountStatuses -This endpoint allows users to subscribe to the streaming of account status changes. Each response for the account status should include the `?` fields. +> ! Disscussion and resarch required + +This endpoint allows users to subscribe to the streaming of account status changes. Each response for the account status should include the `[Insert appropriate account status field(s) after disscussion]` fields. - **Address** (address of the account to stream status changes) @@ -250,14 +252,22 @@ for { log.Fatalf("error receiving account status: %v", err) } - // Insert appropriate handling for received account status + // Insert appropriate handling for received account status after disscussion } ``` -### SubscribeResourcesMovement - ? +#### Questions for Discussion + +1. Should this endpoint return statuses similar to the standard [built-in account event types](https://developers.flow.com/build/basics/events#core-events)? +2. Is it advisable for this endpoint to provide extra data along with the statuses? +3. Would it be beneficial for this endpoint to include an additional parameter to specify the status type to track or a filter to remove unnecessary statuses? + +### SubscribeResourcesMovement -This endpoint allows users to subscribe to the streaming of account resource movement. Each response for the account resource movement should include the `?` fields. +> ! Disscussion and resarch required + +This endpoint allows users to subscribe to the streaming of account resource movement. Each response for the account resource movement should include the `[Insert appropriate resource movement field(s) after disscussion]` fields. - **Address** (address of the account to stream resources movement) - **Type** (type of the resources in the account storage to stream) @@ -285,24 +295,17 @@ for { log.Fatalf("error receiving account resource movement: %v", err) } - // Insert appropriate handling for received account status + // Insert appropriate handling for received resource movement status after disscussion } ``` -Streaming of resource movement by type - Research required - -- https://github.com/onflow/flow-go-sdk/blob/master/examples/storage_usage/main.go -- https://developers.flow.com/tools/flow-js-testing/api#storage-inspection -- https://github.com/onflow/flow-js-testing/blob/e2d4f1a3c8fe8549a01785566090d8c8adcb6903/src/storage.js#L257 -- https://github.com/onflow/flow-go/blob/master/fvm/environment/accounts_status.go -- https://developers.flow.com/build/basics/events#core-events -- https://forum.flow.com/t/suggestion-default-events/2170 +### SubscribeResourcesChanges -### SubscribeResourcesChanges - ? +> ! Disscussion and resarch required -This endpoint allows users to subscribe to the streaming of account resource changes. Each response for the account resource changes should include the `?` fields. +This endpoint allows users to subscribe to the streaming of account resource changes. Each response for the account resource changes should include the `[Insert appropriate resource changed field(s) after disscussion]` fields. - **Address** (address of the account to stream status changes) @@ -315,7 +318,7 @@ req := &executiondata.SubscribeResourcesChangesRequest{ stream, err := client.SubscribeResourcesChanges(ctx, req) if err != nil { - log.Fatalf("error subscribing to account resources statuses: %v", err) + log.Fatalf("error subscribing to account resources changes: %v", err) } for { @@ -325,17 +328,20 @@ for { } if err != nil { - log.Fatalf("error receiving account resources status: %v", err) + log.Fatalf("error receiving resources changes: %v", err) } - // Insert appropriate handling for received account status + // Insert appropriate handling for resources changes } ``` -Streaming of resource changes - Research required +#### Questions for Discussion -https://github.com/onflow/flow-go-sdk/blob/master/examples/storage_usage/main.go +1. Which resource changes in storage should be tracked by this endpoint: all data in storage or solely contract allocated data? +2. Should [the FVM environment Account](https://github.com/onflow/flow-go/blob/456c1318b00b9131ecfe8d95a0813e458ebe7fb1/fvm/environment/accounts.go#L23) be used to monitor resource statuses (both movements and changes) by polling data periodically, or should alternative notification mechanisms be implemented to signify storage data changes? +3. Would FVM only be accessible on execution nodes? +4. What should the `Type` argument be responsible for in the `SubscribeResourcesMovement` endpoint? ### Performance Implications From 604e96bfd03dd38cd72bbb5fe382db7092538dc4 Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Thu, 14 Dec 2023 18:20:07 +0200 Subject: [PATCH 06/23] Added question --- protocol/20231114-accessnode-streaming-api-expansion.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/protocol/20231114-accessnode-streaming-api-expansion.md b/protocol/20231114-accessnode-streaming-api-expansion.md index 08985da9..8936a3df 100644 --- a/protocol/20231114-accessnode-streaming-api-expansion.md +++ b/protocol/20231114-accessnode-streaming-api-expansion.md @@ -222,6 +222,10 @@ for { } ``` +#### Questions for Discussion + +1. Should we drop subscribtion when transaction and block that contains it become sealed? + ### SubscribeAccountStatuses > ! Disscussion and resarch required From d566a686e3a2f0333ca8575766a823b185259a54 Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Fri, 15 Dec 2023 15:25:11 +0200 Subject: [PATCH 07/23] Cleaned up questions. End up document --- ...1114-accessnode-streaming-api-expansion.md | 113 +++--------------- 1 file changed, 15 insertions(+), 98 deletions(-) diff --git a/protocol/20231114-accessnode-streaming-api-expansion.md b/protocol/20231114-accessnode-streaming-api-expansion.md index 8936a3df..5c9ba2d6 100644 --- a/protocol/20231114-accessnode-streaming-api-expansion.md +++ b/protocol/20231114-accessnode-streaming-api-expansion.md @@ -27,8 +27,6 @@ The Access Node Subscription API would introduce the following new streaming end - SubscribeBlocksLightweight - SendAndSubscribeTransactionStatuses - SubscribeAccountStatuses -- SubscribeResourcesMovement -- SubscribeResourcesChanges Additionally, it proposes an improved version of the REST WebSocket Subscription with a single connection. @@ -60,7 +58,7 @@ This endpoint enables users to subscribe to the streaming of blocks, commencing Usage example: ```go -req := &executiondata.SubscribeBlocksRequest{ +req := &access.SubscribeBlocksRequest{ // If no start block height or ID is provided, the latest block is used StartBlockHeight: 1234, // or StartBlockID: startBlockID[:], @@ -103,7 +101,7 @@ This endpoint enables users to subscribe to the streaming of block headers, comm Usage example: ```go -req := &executiondata.SubscribeBlocksRequest{ +req := &access.SubscribeBlocksRequest{ // If no start block height or Id is provided, the latest block is used StartBlockHeight: 1234, // or StartBlockId: startBlockID[:], @@ -147,7 +145,7 @@ This endpoint enables users to subscribe to the streaming of lightweight block i Usage example: ```go -req := &executiondata.SubscribeBlocksLightweightRequest{ +req := &access.SubscribeBlocksLightweightRequest{ // If no start block height or Id is provided, the latest block is used StartBlockHeight: 1234, // or StartBlockId: startBlockID[:], @@ -193,7 +191,7 @@ if err != nil { log.Fatalf("error converting transaction to message: %v", err) } -req := &executiondata.SendTransactionRequest{ +req := &access.SendTransactionRequest{ Transaction: txMsg, } @@ -222,23 +220,21 @@ for { } ``` -#### Questions for Discussion - -1. Should we drop subscribtion when transaction and block that contains it become sealed? - ### SubscribeAccountStatuses -> ! Disscussion and resarch required - -This endpoint allows users to subscribe to the streaming of account status changes. Each response for the account status should include the `[Insert appropriate account status field(s) after disscussion]` fields. +This endpoint enables users to subscribe to the streaming of account status changes. Each response for the account status should include the `Address` and the `Status` fields ([built-in account event types](https://developers.flow.com/build/basics/events#core-events)). In the future, this endpoint could potentially incorporate additional fields to provide supplementary data essential for tracking alongside the status. - **Address** (address of the account to stream status changes) +- **Filter** (array of statuses matching the client’s filter. Any statuses that match at least one of the conditions are returned.) Usage example: ```go req := &executiondata.SubscribeAccountStatusesRequest{ Address: "123456789abcdef0", + Filter: []string{ + "flow.AccountContractUpdated", + } } stream, err := client.SubscribeAccountStatuses(ctx, req) @@ -256,96 +252,17 @@ for { log.Fatalf("error receiving account status: %v", err) } - // Insert appropriate handling for received account status after disscussion -} - -``` - -#### Questions for Discussion - -1. Should this endpoint return statuses similar to the standard [built-in account event types](https://developers.flow.com/build/basics/events#core-events)? -2. Is it advisable for this endpoint to provide extra data along with the statuses? -3. Would it be beneficial for this endpoint to include an additional parameter to specify the status type to track or a filter to remove unnecessary statuses? - -### SubscribeResourcesMovement - -> ! Disscussion and resarch required - -This endpoint allows users to subscribe to the streaming of account resource movement. Each response for the account resource movement should include the `[Insert appropriate resource movement field(s) after disscussion]` fields. - -- **Address** (address of the account to stream resources movement) -- **Type** (type of the resources in the account storage to stream) - -Usage example: - -```go -req := &executiondata.SubscribeResourcesMovementRequest{ - Address: "123456789abcdef0", - Type: "", -} - -stream, err := client.SubscribeResourcesMovement(ctx, req) -if err != nil { - log.Fatalf("error subscribing to account resource movement: %v", err) -} - -for { - resp, err := stream.Recv() - if err == io.EOF { - break - } - - if err != nil { - log.Fatalf("error receiving account resource movement: %v", err) - } - - // Insert appropriate handling for received resource movement status after disscussion -} - -``` - - -### SubscribeResourcesChanges - -> ! Disscussion and resarch required - -This endpoint allows users to subscribe to the streaming of account resource changes. Each response for the account resource changes should include the `[Insert appropriate resource changed field(s) after disscussion]` fields. - -- **Address** (address of the account to stream status changes) - -Usage example: - -```go -req := &executiondata.SubscribeResourcesChangesRequest{ - Address: "123456789abcdef0", -} - -stream, err := client.SubscribeResourcesChanges(ctx, req) -if err != nil { - log.Fatalf("error subscribing to account resources changes: %v", err) -} - -for { - resp, err := stream.Recv() - if err == io.EOF { - break - } - - if err != nil { - log.Fatalf("error receiving resources changes: %v", err) - } - - // Insert appropriate handling for resources changes + log.Printf("received account status with address: %s and status: %s", + resp.Address.String(), + resp.Status.String(), + ) } ``` -#### Questions for Discussion +### Future Subscriptions -1. Which resource changes in storage should be tracked by this endpoint: all data in storage or solely contract allocated data? -2. Should [the FVM environment Account](https://github.com/onflow/flow-go/blob/456c1318b00b9131ecfe8d95a0813e458ebe7fb1/fvm/environment/accounts.go#L23) be used to monitor resource statuses (both movements and changes) by polling data periodically, or should alternative notification mechanisms be implemented to signify storage data changes? -3. Would FVM only be accessible on execution nodes? -4. What should the `Type` argument be responsible for in the `SubscribeResourcesMovement` endpoint? +The `SubscribeResourcesMovement` would enable users to subscribe to the streaming of account resource movement, while `SubscribeResourcesChanges` would allow users to subscribe to the streaming of account resource changes. As these functionalities necessitate additional research and discussions, they will be described and developed in the future. ### Performance Implications From 8b55bce8be87b4571de5ac7b65a5e113c9ae7ec7 Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Fri, 15 Dec 2023 15:26:56 +0200 Subject: [PATCH 08/23] Renamed file --- ...ansion.md => 20231215-accessnode-streaming-api-expansion.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename protocol/{20231114-accessnode-streaming-api-expansion.md => 20231215-accessnode-streaming-api-expansion.md} (99%) diff --git a/protocol/20231114-accessnode-streaming-api-expansion.md b/protocol/20231215-accessnode-streaming-api-expansion.md similarity index 99% rename from protocol/20231114-accessnode-streaming-api-expansion.md rename to protocol/20231215-accessnode-streaming-api-expansion.md index 5c9ba2d6..0ddef788 100644 --- a/protocol/20231114-accessnode-streaming-api-expansion.md +++ b/protocol/20231215-accessnode-streaming-api-expansion.md @@ -3,7 +3,7 @@ status: draft flip: NNN (set to the issue number) authors: Andrii Slisarchuk (andriyslisarchuk@gmail.com) sponsor: AN Expert (core-contributor@example.org) -updated: 2023-11-14 +updated: 2023-12-15 --- # [FLIP DRAFT] Access Streaming API Expansion From d28ea088c525b03a8abfc43c2a900c01ddf266d2 Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Fri, 15 Dec 2023 15:37:34 +0200 Subject: [PATCH 09/23] Added FLIP number --- protocol/20231215-accessnode-streaming-api-expansion.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocol/20231215-accessnode-streaming-api-expansion.md b/protocol/20231215-accessnode-streaming-api-expansion.md index 0ddef788..99c459af 100644 --- a/protocol/20231215-accessnode-streaming-api-expansion.md +++ b/protocol/20231215-accessnode-streaming-api-expansion.md @@ -1,12 +1,12 @@ --- status: draft -flip: NNN (set to the issue number) +flip: 229 authors: Andrii Slisarchuk (andriyslisarchuk@gmail.com) sponsor: AN Expert (core-contributor@example.org) updated: 2023-12-15 --- -# [FLIP DRAFT] Access Streaming API Expansion +# [FLIP 229] Access Streaming API Expansion ## Objective From 1eabe61adf8b2b565413d2a70f7443fa2828e945 Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Wed, 20 Dec 2023 18:35:40 +0200 Subject: [PATCH 10/23] Apply suggestions from code review Co-authored-by: Peter Argue <89119817+peterargue@users.noreply.github.com> --- protocol/20231215-accessnode-streaming-api-expansion.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocol/20231215-accessnode-streaming-api-expansion.md b/protocol/20231215-accessnode-streaming-api-expansion.md index 99c459af..a7c10b3b 100644 --- a/protocol/20231215-accessnode-streaming-api-expansion.md +++ b/protocol/20231215-accessnode-streaming-api-expansion.md @@ -23,7 +23,7 @@ The enhanced version seeks to provide a simpler and more efficient REST WebSocke The Access Node Subscription API would introduce the following new streaming endpoints: - SubscribeBlocks -- SubscribeBlocksHeaders +- SubscribeBlockHeaders - SubscribeBlocksLightweight - SendAndSubscribeTransactionStatuses - SubscribeAccountStatuses @@ -149,7 +149,7 @@ req := &access.SubscribeBlocksLightweightRequest{ // If no start block height or Id is provided, the latest block is used StartBlockHeight: 1234, // or StartBlockId: startBlockID[:], - BlockStatus: BlockStatus.BlockStatusFinalized // Use BlockStatusSealed to receive only finalized or only sealed blocks information + BlockStatus: BlockStatus.BlockStatusFinalized // Returns only finalized blocks. Use BlockStatusSealed to receive only sealed blocks } stream, err := client.SubscribeBlocksLightweight(ctx, req) From 8323be52b9ccf10b42b3a2145efb858bf8707386 Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Thu, 21 Dec 2023 23:34:34 +0200 Subject: [PATCH 11/23] Changed to SubscribeBlockHeaders --- protocol/20231215-accessnode-streaming-api-expansion.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/protocol/20231215-accessnode-streaming-api-expansion.md b/protocol/20231215-accessnode-streaming-api-expansion.md index a7c10b3b..f8f08419 100644 --- a/protocol/20231215-accessnode-streaming-api-expansion.md +++ b/protocol/20231215-accessnode-streaming-api-expansion.md @@ -45,7 +45,7 @@ The proposed enhancement involves the following aspects: - The `WSHandler` will initiate a connection only for the initial subscription and retain it for subsequent subscriptions. - An unsubscribe mechanism will be introduced. With multiple subscriptions on a single connection, simply closing this connection to halt updates becomes insufficient. Therefore, a dedicated endpoint for unsubscribing from undesired updates is necessary. -- Each subscription response will now include a message type. For instance, the `SubscribeEvents` endpoint will result in a message with `type: "Events"`, while `SubscribeBlocksHeaders` will yield `type: "BlockHeader"` in messages, and so forth. This is essential to distinguish between various subscription results that will be delivered through a single connection. +- Each subscription response will now include a message type. For instance, the `SubscribeEvents` endpoint will result in a message with `type: "Events"`, while `SubscribeBlockHeaders` will yield `type: "BlockHeader"` in messages, and so forth. This is essential to distinguish between various subscription results that will be delivered through a single connection. ### SubscribeBlocks @@ -90,7 +90,7 @@ for { } ``` -### SubscribeBlocksHeaders +### SubscribeBlockHeaders This endpoint enables users to subscribe to the streaming of block headers, commencing from a provided block ID or height. Additionally, users are required to specify the desired status of the streamed blocks to receive. The response for each block header should include the block's `Header`. This is a lighter version of `SubscribeBlocks` as it does not include the heavier `Payload` field. @@ -108,7 +108,7 @@ req := &access.SubscribeBlocksRequest{ BlockStatus: BlockStatus.BlockStatusFinalized // Use BlockStatusSealed to receive only finalized or only sealed blocks headers } -stream, err := client.SubscribeBlocksHeaders(ctx, req) +stream, err := client.SubscribeBlockHeaders(ctx, req) if err != nil { log.Fatalf("error subscribing to blocks headers: %v", err) From 7346da58802d57c0ad5974de1790cf009cb55959 Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Thu, 21 Dec 2023 23:36:35 +0200 Subject: [PATCH 12/23] Changed to SubscribeBlockDigests --- protocol/20231215-accessnode-streaming-api-expansion.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/protocol/20231215-accessnode-streaming-api-expansion.md b/protocol/20231215-accessnode-streaming-api-expansion.md index f8f08419..70b9649a 100644 --- a/protocol/20231215-accessnode-streaming-api-expansion.md +++ b/protocol/20231215-accessnode-streaming-api-expansion.md @@ -24,7 +24,7 @@ The Access Node Subscription API would introduce the following new streaming end - SubscribeBlocks - SubscribeBlockHeaders -- SubscribeBlocksLightweight +- SubscribeBlockDigests - SendAndSubscribeTransactionStatuses - SubscribeAccountStatuses @@ -134,7 +134,7 @@ for { } ``` -### SubscribeBlocksLightweight +### SubscribeBlockDigests This endpoint enables users to subscribe to the streaming of lightweight block information, commencing from a provided block ID or height. Additionally, users are required to specify the desired status of the streamed blocks to receive. The response for each block should include only the block's `ID`, `Height` and `Timestamp`. This is the lightest version among all block subscriptions. @@ -145,14 +145,14 @@ This endpoint enables users to subscribe to the streaming of lightweight block i Usage example: ```go -req := &access.SubscribeBlocksLightweightRequest{ +req := &access.SubscribeBlockDigestsRequest{ // If no start block height or Id is provided, the latest block is used StartBlockHeight: 1234, // or StartBlockId: startBlockID[:], BlockStatus: BlockStatus.BlockStatusFinalized // Returns only finalized blocks. Use BlockStatusSealed to receive only sealed blocks } -stream, err := client.SubscribeBlocksLightweight(ctx, req) +stream, err := client.SubscribeBlockDigests(ctx, req) if err != nil { log.Fatalf("error subscribing to lightweight blocks : %v", err) From ce81d8b26491bc062bc201bd22e8db53f17ea3f1 Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Fri, 22 Dec 2023 00:16:37 +0200 Subject: [PATCH 13/23] Added argument to SubscribeBlock --- protocol/20231215-accessnode-streaming-api-expansion.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/protocol/20231215-accessnode-streaming-api-expansion.md b/protocol/20231215-accessnode-streaming-api-expansion.md index 70b9649a..e20902ac 100644 --- a/protocol/20231215-accessnode-streaming-api-expansion.md +++ b/protocol/20231215-accessnode-streaming-api-expansion.md @@ -54,6 +54,7 @@ This endpoint enables users to subscribe to the streaming of blocks, commencing - **BlockHeight** (Block height of the streamed block) - **BlockId** (Block ID of the streamed block) - **BlockStatus** (`BlockStatusSealed` or `BlockStatusFinalized`) +- **FullBlockResponse** (Boolean value determining the response: 'full' if `true`, 'light' otherwise) Usage example: @@ -63,6 +64,7 @@ req := &access.SubscribeBlocksRequest{ StartBlockHeight: 1234, // or StartBlockID: startBlockID[:], BlockStatus: BlockStatus.BlockStatusFinalized, // Use BlockStatusSealed to receive only finalized or only sealed blocks + FullBlockResponse: false, // Return 'light' version of block in response } stream, err := client.SubscribeBlocks(ctx, req) From be1ca1aee2b884abe3291f8b2e8a4953d0a4e710 Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Fri, 22 Dec 2023 00:22:40 +0200 Subject: [PATCH 14/23] Added arguments to SubscribeAccountStatuses --- protocol/20231215-accessnode-streaming-api-expansion.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/protocol/20231215-accessnode-streaming-api-expansion.md b/protocol/20231215-accessnode-streaming-api-expansion.md index e20902ac..879ba75f 100644 --- a/protocol/20231215-accessnode-streaming-api-expansion.md +++ b/protocol/20231215-accessnode-streaming-api-expansion.md @@ -226,6 +226,8 @@ for { This endpoint enables users to subscribe to the streaming of account status changes. Each response for the account status should include the `Address` and the `Status` fields ([built-in account event types](https://developers.flow.com/build/basics/events#core-events)). In the future, this endpoint could potentially incorporate additional fields to provide supplementary data essential for tracking alongside the status. +- **BlockHeight** (Block height of the acount status being streamed) +- **BlockId** (Block ID of the acount status being streamed) - **Address** (address of the account to stream status changes) - **Filter** (array of statuses matching the client’s filter. Any statuses that match at least one of the conditions are returned.) @@ -233,6 +235,9 @@ Usage example: ```go req := &executiondata.SubscribeAccountStatusesRequest{ + // If no start block height or ID is provided, the latest block is used + StartBlockHeight: 1234, + // or StartBlockID: startBlockID[:], Address: "123456789abcdef0", Filter: []string{ "flow.AccountContractUpdated", From d0a98c84504d289df1860e60a498af983abe4c3b Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Thu, 4 Jan 2024 08:36:16 +0200 Subject: [PATCH 15/23] Added expected responces --- ...1215-accessnode-streaming-api-expansion.md | 68 +++++++++++++++++-- 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/protocol/20231215-accessnode-streaming-api-expansion.md b/protocol/20231215-accessnode-streaming-api-expansion.md index 879ba75f..12fcec7b 100644 --- a/protocol/20231215-accessnode-streaming-api-expansion.md +++ b/protocol/20231215-accessnode-streaming-api-expansion.md @@ -49,13 +49,23 @@ The proposed enhancement involves the following aspects: ### SubscribeBlocks -This endpoint enables users to subscribe to the streaming of blocks, commencing from a provided block ID or height. Additionally, users are required to specify the desired status of the streamed blocks to receive. Each block's response should include the `Block` containing `Header` and `Payload`. +This endpoint enables users to subscribe to the streaming of blocks, commencing from a provided block ID or height. Additionally, users are required to specify the desired status of the streamed blocks to receive. Each block's response should include the full or light `Block` depends on `FullBlockResponse` argument. + +Arguments: - **BlockHeight** (Block height of the streamed block) - **BlockId** (Block ID of the streamed block) - **BlockStatus** (`BlockStatusSealed` or `BlockStatusFinalized`) - **FullBlockResponse** (Boolean value determining the response: 'full' if `true`, 'light' otherwise) +Responce: + +```go +SubscribeBlocksResponce { + Block: entities.Block {} +} +``` + Usage example: ```go @@ -94,12 +104,22 @@ for { ### SubscribeBlockHeaders -This endpoint enables users to subscribe to the streaming of block headers, commencing from a provided block ID or height. Additionally, users are required to specify the desired status of the streamed blocks to receive. The response for each block header should include the block's `Header`. This is a lighter version of `SubscribeBlocks` as it does not include the heavier `Payload` field. +This endpoint enables users to subscribe to the streaming of block headers, commencing from a provided block ID or height. Additionally, users are required to specify the desired status of the streamed blocks to receive. The response for each block header should include the block's `BlockHeader`. This is a lighter version of `SubscribeBlocks` as it will never include the heavy `BlockPayload`. + +Arguments: - **BlockHeight** (Block height of the streamed block) - **BlockId** (Block ID of the streamed block) - **BlockStatus** (`BlockStatusSealed` or `BlockStatusFinalized`) +Responce: + +```go +SubscribeBlocksHeaderResponce { + BlockHeader: entities.BlockHeader {...} +} +``` + Usage example: ```go @@ -140,10 +160,22 @@ for { This endpoint enables users to subscribe to the streaming of lightweight block information, commencing from a provided block ID or height. Additionally, users are required to specify the desired status of the streamed blocks to receive. The response for each block should include only the block's `ID`, `Height` and `Timestamp`. This is the lightest version among all block subscriptions. +Arguments: + - **BlockHeight** (Block height of the streamed block) - **BlockId** (Block ID of the streamed block) - **BlockStatus** (`BlockStatusSealed` or `BlockStatusFinalized`) +Responce: + +```go +SubscribeBlockDigestsResponce { + BlockId: ... + BlockHeight: ... + BlockTimestamp: ... +} +``` + Usage example: ```go @@ -173,7 +205,6 @@ for { log.Printf("received lightweight block with ID: %x, Height: %d, parent block ID: %x and Timestamp: %s", resp.GetBlockId(), resp.GetBlockHeight(), - resp.GetParentBlockId(), resp.GetBlockTimestamp().String(), ) } @@ -183,8 +214,20 @@ for { This endpoint enables users to send a transaction and immediately subscribe to its status changes. The status is streamed back until the block containing the transaction becomes sealed. Each response for the transaction status should include the `ID`, `Status` and `Error` fields. +Arguments: + - **TransactionMsg** (transaction submitted to stream status) +Responce: + +```go +SendAndSubscribeTransactionResponce { + ID: ... + Status: ... + SequenceNumber: ... +} +``` + Usage example: ```go @@ -215,9 +258,9 @@ for { txResult := resp.GetTransactionResult() log.Printf("received transaction status with tx ID: %x, status: %s, and error: %v", - resp.GetTransactionID(), - txResult.Status.String(), - txResult.Error, + resp.ID, + resp.Status.String(), + resp.SequenceNumber ) } ``` @@ -226,11 +269,22 @@ for { This endpoint enables users to subscribe to the streaming of account status changes. Each response for the account status should include the `Address` and the `Status` fields ([built-in account event types](https://developers.flow.com/build/basics/events#core-events)). In the future, this endpoint could potentially incorporate additional fields to provide supplementary data essential for tracking alongside the status. +Arguments: + - **BlockHeight** (Block height of the acount status being streamed) - **BlockId** (Block ID of the acount status being streamed) - **Address** (address of the account to stream status changes) - **Filter** (array of statuses matching the client’s filter. Any statuses that match at least one of the conditions are returned.) +Responce: + +```go +SubscribeAccountStatusesResponce { + Address: ... + Status: ... +} +``` + Usage example: ```go @@ -269,7 +323,7 @@ for { ### Future Subscriptions -The `SubscribeResourcesMovement` would enable users to subscribe to the streaming of account resource movement, while `SubscribeResourcesChanges` would allow users to subscribe to the streaming of account resource changes. As these functionalities necessitate additional research and discussions, they will be described and developed in the future. +The `SubscribeResourcesChanges` would allow users to subscribe to the streaming of account resource movement and account resource changes. As these functionalities necessitate additional research and discussions, they will be described and developed in the future. ### Performance Implications From 3a93470426f9b29c77e71d01fed395fc0d0ce4aa Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Thu, 4 Jan 2024 15:48:19 +0200 Subject: [PATCH 16/23] Added explanation to arguments --- ...1215-accessnode-streaming-api-expansion.md | 112 +++++++++--------- 1 file changed, 54 insertions(+), 58 deletions(-) diff --git a/protocol/20231215-accessnode-streaming-api-expansion.md b/protocol/20231215-accessnode-streaming-api-expansion.md index 12fcec7b..e7eb7b16 100644 --- a/protocol/20231215-accessnode-streaming-api-expansion.md +++ b/protocol/20231215-accessnode-streaming-api-expansion.md @@ -51,22 +51,22 @@ The proposed enhancement involves the following aspects: This endpoint enables users to subscribe to the streaming of blocks, commencing from a provided block ID or height. Additionally, users are required to specify the desired status of the streamed blocks to receive. Each block's response should include the full or light `Block` depends on `FullBlockResponse` argument. -Arguments: +**Arguments:** -- **BlockHeight** (Block height of the streamed block) -- **BlockId** (Block ID of the streamed block) -- **BlockStatus** (`BlockStatusSealed` or `BlockStatusFinalized`) +- **StartBlockHeight** (Block height of the streamed block) +- **StartBlockId** (Block ID of the streamed block) +- **BlockStatus** (`BlockStatusSealed` or `BlockStatusFinalized`. ) - **FullBlockResponse** (Boolean value determining the response: 'full' if `true`, 'light' otherwise) -Responce: +Either one of the two arguments, `StartBlockHeight` or `StartBlockId`, should be set but not both. If none of these arguments is set, the last sealed block will be considered as the starting block. -```go -SubscribeBlocksResponce { - Block: entities.Block {} -} -``` +The `BlockStatus` cannot be set as `BlockStatusUnknown`. -Usage example: +**Expected response:** + +- **Block** (The new sealed or finalized `Block` that, has `entities.Block` type) + +**Usage example:** ```go req := &access.SubscribeBlocksRequest{ @@ -94,10 +94,10 @@ for { block := resp.GetBlock() - log.Printf("received block with ID: %x, Height: %d, and Payload Hash: %x", + log.Printf("received block with ID: %x, Height: %d, and number of CollectionGuarantees: %d", block.ID(), - block.Header.Height, - block.Payload.Hash(), + block.BlockHeader.Height, + len(block.CollectionGuarantees), ) } ``` @@ -106,21 +106,21 @@ for { This endpoint enables users to subscribe to the streaming of block headers, commencing from a provided block ID or height. Additionally, users are required to specify the desired status of the streamed blocks to receive. The response for each block header should include the block's `BlockHeader`. This is a lighter version of `SubscribeBlocks` as it will never include the heavy `BlockPayload`. -Arguments: +**Arguments:** -- **BlockHeight** (Block height of the streamed block) -- **BlockId** (Block ID of the streamed block) +- **StartBlockHeight** (Block height of the streamed block) +- **StartBlockId** (Block ID of the streamed block) - **BlockStatus** (`BlockStatusSealed` or `BlockStatusFinalized`) -Responce: +Either one of the two arguments, `StartBlockHeight` or `StartBlockId`, should be set but not both. If none of these arguments is set, the last sealed block will be considered as the starting block. -```go -SubscribeBlocksHeaderResponce { - BlockHeader: entities.BlockHeader {...} -} -``` +The `BlockStatus` cannot be set as `BlockStatusUnknown`. -Usage example: +**Expected response:** + +- **BlockHeader** (The header of new sealed or finalized `Block` that has `entities.BlockHeader` type) + +**Usage example:** ```go req := &access.SubscribeBlocksRequest{ @@ -160,23 +160,23 @@ for { This endpoint enables users to subscribe to the streaming of lightweight block information, commencing from a provided block ID or height. Additionally, users are required to specify the desired status of the streamed blocks to receive. The response for each block should include only the block's `ID`, `Height` and `Timestamp`. This is the lightest version among all block subscriptions. -Arguments: +**Arguments:** -- **BlockHeight** (Block height of the streamed block) -- **BlockId** (Block ID of the streamed block) +- **StartBlockHeight** (Block height of the streamed block) +- **StartBlockId** (Block ID of the streamed block) - **BlockStatus** (`BlockStatusSealed` or `BlockStatusFinalized`) -Responce: +**Expected response:** -```go -SubscribeBlockDigestsResponce { - BlockId: ... - BlockHeight: ... - BlockTimestamp: ... -} -``` +- **BlockId** (The ID of the new sealed or finalized block) +- **BlockHeight** (The height of the new sealed or finalized block) +- **BlockTimestamp** (The timestamp of the new sealed or finalized block) -Usage example: +Either one of the two arguments, `StartBlockHeight` or `StartBlockId`, should be set but not both. If none of these arguments is set, the last sealed block will be considered as the starting block. + +The `BlockStatus` cannot be set as `BlockStatusUnknown`. + +**Usage example:** ```go req := &access.SubscribeBlockDigestsRequest{ @@ -212,23 +212,19 @@ for { ### SendAndSubscribeTransactionStatuses -This endpoint enables users to send a transaction and immediately subscribe to its status changes. The status is streamed back until the block containing the transaction becomes sealed. Each response for the transaction status should include the `ID`, `Status` and `Error` fields. +This endpoint enables users to send a transaction and immediately subscribe to its status changes. The status is streamed back until the block containing the transaction becomes sealed. Each response for the transaction status should include the `ID`, `Status` and `SequenceNumber` fields. -Arguments: +**Arguments:** - **TransactionMsg** (transaction submitted to stream status) -Responce: +**Expected response:** -```go -SendAndSubscribeTransactionResponce { - ID: ... - Status: ... - SequenceNumber: ... -} -``` +- **ID** (The ID of the tracked transaction) +- **Status** (The status of the tracked transaction) +- **SequenceNumber** (The SequenceNumber of the tracked transaction) -Usage example: +**Usage example:** ```go txMsg, err := transactionToMessage(tx) @@ -269,21 +265,21 @@ for { This endpoint enables users to subscribe to the streaming of account status changes. Each response for the account status should include the `Address` and the `Status` fields ([built-in account event types](https://developers.flow.com/build/basics/events#core-events)). In the future, this endpoint could potentially incorporate additional fields to provide supplementary data essential for tracking alongside the status. -Arguments: +**Arguments:** -- **BlockHeight** (Block height of the acount status being streamed) -- **BlockId** (Block ID of the acount status being streamed) +- **StartBlockHeight** (Block height of the acount status being streamed) +- **StartBlockId** (Block ID of the acount status being streamed) - **Address** (address of the account to stream status changes) -- **Filter** (array of statuses matching the client’s filter. Any statuses that match at least one of the conditions are returned.) +- **Filter** (array of statuses matching the client’s filter) -Responce: +Either one of the two arguments, `StartBlockHeight` or `StartBlockId`, should be set but not both. If none of these arguments is set, the last sealed block will be considered as the starting block. -```go -SubscribeAccountStatusesResponce { - Address: ... - Status: ... -} -``` +If the `Filter` is empty, all account statuses will be returned. If filters are set, only statuses that precisely match one of these filter conditions will be returned. + +**Expected response:** + +- **Address** (The adress of the tracked account) +- **Status** (The status of the tracked account) Usage example: From 452924257139d36d1527644c8ac02276873c7414 Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Wed, 10 Jan 2024 17:02:25 +0200 Subject: [PATCH 17/23] Extend WS description. --- ...1215-accessnode-streaming-api-expansion.md | 77 +++++++++++++++--- .../websocket_communication.png | Bin 0 -> 35001 bytes 2 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 protocol/20231215-accessnode-streaming-api-expansion/websocket_communication.png diff --git a/protocol/20231215-accessnode-streaming-api-expansion.md b/protocol/20231215-accessnode-streaming-api-expansion.md index e7eb7b16..2b59890c 100644 --- a/protocol/20231215-accessnode-streaming-api-expansion.md +++ b/protocol/20231215-accessnode-streaming-api-expansion.md @@ -25,27 +25,82 @@ The Access Node Subscription API would introduce the following new streaming end - SubscribeBlocks - SubscribeBlockHeaders - SubscribeBlockDigests -- SendAndSubscribeTransactionStatuses +- SendAndSubscribeTransactionStatusess - SubscribeAccountStatuses Additionally, it proposes an improved version of the REST WebSocket Subscription with a single connection. ### WebSocket Subscriptions with a Single Connection -In the current version of REST subscription, [a new WebSocket connection is created](https://github.com/onflow/flow-go/blob/9577079fd24aad4c1fc1fc8710ef2686ead85ca4/engine/access/rest/routes/websocket_handler.go#L288) for each subscription request. However, with the potential growth in the number of endpoints available for subscription, this behavior becomes undesirable due to several reasons: +In the existing REST subscription implementation, a new WebSocket connection is established for each subscription request, as seen [here](https://github.com/onflow/flow-go/blob/9577079fd24aad4c1fc1fc8710ef2686ead85ca4/engine/access/rest/routes/websocket_handler.go#L288). However, as the number of available endpoints for subscription grows, this approach becomes less favorable due to several reasons: -- It results in a system for connection handling on the client side that is difficult to maintain. -- It causes unnecessary network load, impacting the overall performance of the application. -- Eventually, it will hit the connection limit, posing constraints. -- It complicates future expansion efforts. +- It leads to complex client-side connection handling that is hard to maintain. +- It causes unnecessary network load, adversely affecting overall application performance. +- Eventually, it could reach connection limits, imposing constraints. +- It complicates future scalability efforts. -To address this, an improvement in WebSocket behavior is proposed by implementing a single connection for each subscription. This improvement is commonly referred to as the "WebSocket Components Subscription Pattern", and an example of its React implementation can be found [here](https://blog.stackademic.com/websockets-and-react-wscontext-and-components-subscription-pattern-4e580fc67bb5). +To address this, a proposed improvement involves modifying WebSocket behavior by implementing a single connection for each subscription. This adjustment requires a shift in the core concept of REST WebSocket connections for the Access node. While the current concept of `WSHandler` should be retained for backward compatibility, a new concept needs to run in parallel. -The proposed enhancement involves the following aspects: +The proposed new concept draws inspiration from the ["Web Application Messaging Protocol"(WAMP)](https://wamp-proto.org/), considering a modification to the Access node's REST WebSocket connections. However, existing Go implementations of this protocol, like ["Nexus V3"](https://github.com/gammazero/nexus/tree/v2), contain numerous unnecessary features unsuitable for this purpose. -- The `WSHandler` will initiate a connection only for the initial subscription and retain it for subsequent subscriptions. -- An unsubscribe mechanism will be introduced. With multiple subscriptions on a single connection, simply closing this connection to halt updates becomes insufficient. Therefore, a dedicated endpoint for unsubscribing from undesired updates is necessary. -- Each subscription response will now include a message type. For instance, the `SubscribeEvents` endpoint will result in a message with `type: "Events"`, while `SubscribeBlockHeaders` will yield `type: "BlockHeader"` in messages, and so forth. This is essential to distinguish between various subscription results that will be delivered through a single connection. +#### Client + +Let's explore the visual representation of the proposed communication concept before delving into further details: + +![WebSocket Communication](20231215-accessnode-streaming-api-expansion/websocket_communication.png) + +- The client establishes a single WebSocket connection with the Access Node (AN) through `ws://localhost:8080/ws` and maintains this connection until closed by the AN WebSocketHandler or the client itself. + ```js + const ws = new WebSocket('ws://localhost:8080/ws'); + ``` + +- Upon connection establishment, both the AN and the client can exchange messages. + +- To subscribe to necessary topics, the client sends a special message through the WebSocket connection: + ```js + const subscribeMsg = { + action: 'subscribe', + topic: 'events', + }; + ws.send(subscribeMsg); + ``` + + The client can subscribe to multiple topics through a single connection but should manage messages from the connection itself. The return message from the AN with updates will resemble: + ```js + ws.onmessage = (event) => { + const message = JSON.parse(event.data); + /* + For the above subscription, the JSON message should appear as: + + { + topic: 'events', + data: [...] + } + */ + console.log('Received publication for topic:', message.topic, 'with message:', message.data); + }; + ``` + +- To unsubscribe from topics, the client sends a special message through the WebSocket connection: + ```js + const unsubscribeMsg = { + action: 'unsubscribe', + topic: 'events', + }; + ws.send(unsubscribeMsg); + ``` + +- The connection can be closed by the client if no longer needed or by the AN if the client has unsubscribed from all topics. + +#### Access Node WebSocketHandler + +The WebSocketHandler acts as a broker between the client and the Access Subscription API, akin to WAMP. It retains most functionalities from its predecessor, `WSHandler`, with added features and responsibilities: + +- Sets up WebSocket connections for different clients at the `/ws` route and manages these connections (addition, removal, ping). +- Manages subscriptions/unsubscriptions and maintains a mapping of topics for subscribed clients. +- Handles updates from the AN streaming API and broadcasts messages to all subscribers interested in the respective topics. + +Additionally, the Access Subscription API requires modifications, as all streamed updates should now include an additional `Topic` field. This inclusion enables WebSocketHandler to differentiate messages based on topics. ### SubscribeBlocks diff --git a/protocol/20231215-accessnode-streaming-api-expansion/websocket_communication.png b/protocol/20231215-accessnode-streaming-api-expansion/websocket_communication.png new file mode 100644 index 0000000000000000000000000000000000000000..3cd36e9fd23d42248217b3cb2ea4ccda15183750 GIT binary patch literal 35001 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

7fdNcKXN zE3HYNSP17-57SS%F6-Q8_m)d%B=Qt)-oPuo%EiTCw`tgJGJ>(SFFpzuetNeMw(?|6v6z7ma#Ydv%&0Xe1mx=d)1IniCrVGOU*FHb- zKq(S;P{tjv&a3GMyFt#feW`svYgewg!i-&+Xc>C1dd`-b^J*brcp58R7Oc3^oUgr! zUj)@ug`mpl`LborSm*q4Ao1jKW)agmM7#hSSqk|>Z7P$JK zNyoY8&jwFQ&+EXf$?|JJ^fdJ4S%#hU%j(kYukHM$V}7Iw!_2U1P(?Hda(oDa0i=eH zJ(oc0U_u2!l=R*DzCL>2;K}9idt6wLq8mhD6VT!E*LX;m%vlP}Elx5@yjuiWdvZ}V za5h0Ih8UHYOsNcl@axy6UuWp=h8jZ7^*W6|*liYpY{VoI3ARi>4V zdRHO8e0Xzd737DvUm91RKx_jF+g#5G8Lycp)mJ*|?P=iH#<2hj2a8H=hXz49CNebK zdl_TmwHCk1dNo<3?bdBejw) z)5>_l%?@l)S#h`QP)V#cWc^D|ON8V!y(cJ38ooRMiU94s0y_E)5SMUebSTXfF_(HG zItgo;-AfugMFDB<(@Ec3*&_8Pn!`b==Uu$By~Wjr{;stn$_zyMHVoi=Y3KFY0M#!g zTv$L_Sq-Xlge}q(Wy@QN>R1M%Y;Z_~syQfu%rOGBeGyR}-{-#!&w{eKVxSX!-rH+> zxY8b@OT!Bk7blNBmjeJ!MBMFC8%wZvXdDt0{rQ>(@6Cy$VDWvnpyaOncp9XOmO@9|i9L;e^LO945xA}OYL>Fdu76+|gA{S>tzgdNR91Qv}&^+y@X$%yWEb67XKfB*nwxR<64mu)UAYrQ+x zT=VtqMTQA?P=GfdcU_D7$60~8g&iQ3%FUNGn^rNcQo3k^PaKzDkoX=2u64PCEIpSw zppF$OXr4MVod{H*F5P?6vI;2NNCtBuuEI;gpqc6uqIR3HC}5@{O7y;gc@jMJX;s7V ziywiiPj~sJh}q5!`ZO`u33o{}rr-OjzAkOpN?|^Gl)P;d=zS4y+3<(v2qx^@2!NZq zte(AmeNH3ZPxR`!1dtOBv{ptjq-xgV@)p$Xu9qxZhT;-tnP~uY8?_aY-JE5s0`|k0 z6l^hz1Ltn)8TEYvz}i6T8FKkBTHfIS@sBn|Z)m%eioWEeR94c+5ZD;b8ti!1C2nqS zt~nu3PiTsd#Id8b;^xKNEN>oB%zVA)rKaL@hcQ7XP76_A6kLBzo9MResj9rYy>vhA zVk$u!bU?8@ip*jdu#bW51GZ`yv*w2miz^XK(v@O4nknY7T-q)R@~Jc^HJ)3BqZOe1 zFq~A9T`Gi>^i+P_dT=+L66W<1@D;8Y*+nmuHP6T8jx%~&h0w-Sh78RP9MbrZ#nq{P zi)91o>y`<=xqeUiZhf4CS^dn>M}e^Liv%! zfYrJiY-2%8UG*huYpQ;+Y(<{|96mrysVACE%sqaz#VR0Gawz-?2B2=`4xC-)cp7dX zM;7dAN)ff5Hhi7>rJ%S=Dxy3@o~!T$C`J{vv!7cL_g(}zQP-QU3dD058TYE}rURfP ztBTFKI^381$z^9ZedzF9Whd25B%d9lR!i4>gRAGYVPBS*tgJUEv4VHYtUesV8^g~e z6T1}Hnk*Igspp9*d9Yx1=jg!C^JiYCVxJFE5frgR<(6D3)y4D_0+ge#p>WJ|#aAhd zg0%j6)Yr$sXRQb|bPMxHm>b_t&WoK82)lyOpWsTh_ahz4vK?%^^iU%yU(4ns@iMsZ zyz+)lI*iVg{745tBZ<~!h|28g@6ZOKR=ZI}i<0ZjBU*U$u#l+-r^55kWaXT^wc8X* zZTmQo7G3XujKN~P{8PoX%W+cO6UV#B1scgty8E-)Ubom@1N9e}Rp2;lKNWxbpdk&) zQW0HW-S3aqc*TPH=olm9Wf0&@vP7w%?dYyHE|))3RM-7{xqN(q>pb_B#$!)t!9luw z1{|7|vU+Xx^;f@>${z9Wu5jqiYn8GKVEE+i2c8Vr{^f4^S6<9UDJIGox?eX*T9u)7}aPDU%br;JEtM3*j?HR{=8qfJ9#uOSBhG zoY89wthdO3&^oK|TJqme^v|Uwa9q7f>Rv5Qws+oI2SgxZB`*GOCXr=x$`^_AdPaKDf3ZzDFeo)Ugx&_fSTVY`qH@WvQZ|d7#!1j&{UBFAMke%iYU-Ke7p1I-` zBw7;$5Rq15NU$PR%y-^;{x0i~$yo@CjyWJV;!o>{&Yap?j8tBJHtXq**Vj&waoQ1P zy|o+O*4a{I4&4tN*pDY|vhRl~ikbDeD0AeFK<@#gL+s1WIz|lvp`J3y^^7cL=7Dg( zm^s*9n1c(?U^nA?vkr$ccaS%5tG9TR!&`c>PL?M)bN;%~V;aBIz zEeS_tsXgb+8(PGwdJRfgUkQ|##KmK!@^=;6pP#kLkHOJpp{1YV#r8%5l#=Cjg*9=& zNnM0c5R~Bj^g|TK{2{gTT3LPuW~`Ne$SBFO>@E6pEX>@RCuYQ^v+T0L9%X_X_`DYW z4QWXnUn5L6r~VbB8X>4yuJ}1!PRe(tac!3UnQVvDPU!iKyaWj__0SY551HW%*mxXyCUWC|p5Xd_O_4J$~ zGmccc(j<~gO`>v`7wBkSF9VS>+JIj7%C7$ZccrRG(e~9NcIwc2D^tl zttxxLX+;1ASMhn8m$bj7z%(V9zMI#@7$I`59SOzX53&6p=Bn=xOhM46ic3J|o;*K$ z$PkC%@9#4`p|a*JLNIHUI~a{?dlWB@yS$bcI#;Uh!a=y753yQ*38qjZB)I*MN|0<$ z83zdhZrU-P{YFpk3lGYuc?@av1sF~|7O*rZ1oLj+r0XnjvR-vh7ZKZ^3AmfH$4j54 zos1(lLZ^Hsxv_5c+j@)U!p=leg1I~#%lpy`43q-$%s508PY&qyWQ&Xy&B{LdJ9Qyc zRkzpbClJ^~i#`J9?g5i2Rh^Zaez>c-M{ur0M@TN?B&}8GmS+u71esofxq%rk0JFi;fD0?Skh!T2*w&P|Bptb! zJ+2q6l@;||`*5T)4{_8q*-<63Ls0`=*ju!EoWL6QabQyC&p+3?g%jbPiru;?xjm6E zpL74DsNau0Pcjk9SHeU10O+839<-%IBT*W90p5D)=Q zRzXCPQ%Z^RIlpo2d+SAQ{;4uR8S|XZl&p>%92Xmck!Mt74!zfBU7C_JazJ1|1ThRB z!uBj&>*e{pnZxDH$pf_()_ir~H}Ux?0Zlx0Vda{#u;BSNJI)-4KM&=h>x#=I@j%T1 zoynIanv?h2^Ig2tRefO^F}9l+>^9XhXTsN=cb*8t+c3FqvMWZ!q?vFBlPu@tSU zco$i$R=9K6=N|1r_5{H7Md=g!zGW2#|W%J zOy$ph=Ut|P%R7Z*DpqEbt;4vScS--T*t4riru^04Vio{7>M|LvT3HD)QuoPqk1LqlLzmdFtpqO~C}ut&hNYtb z*}j?tZd6p`E}fLVy^#O$$I1kgF6a$g+C-(%d$kj!`EaLBQN79M7lL&3^r1uyQqH+0 zR()^%c0K}T7;*02vM(>V&_^_?cXbGf`~d>_S0H}xq&2j!2lvjv6~{pw0syl5THK|R zX)ymRf#o<9Tf0y9DKF=wA2KFq3^plSWgxh_o5Fe^LAH<#ngU_3W77txJ(tX1jn^|S z@;!2FkOA=;ic6V`y*BYhxE$PmZfht!7?pg@0 zz5#d3t?_f$2%-EVz-ObXK_I)x73gYLJ|5)qL7fW%%E?fG#|~QC#Sx<*uYg*kih^)x z!S|i5CER7h1rYkaI(Pr)`v`C;CPl_H?0!VF3$izO@tyut84>_R^DEN7R_>_P$ zZA(q<-7~uOgtD3_Lyq+0&n+6?%0OIa-$AEOTckq4>Q`!WzDKK`bx4?+egA|kaT(w& zVj<^g(jhyiCgSL6T4MSS03BgU z+wuZ{Y|yNsbYSb5&u}FLqn#SeZR^w7kyH0ShjI;qOO)o)3$J3Ue|!#$cn1I^$11SQ zH0(XVf1ytIeEZU4Jv}IAq%y=r>;hwBvS5 z1e4e8o<{DT!Nt9^F}*1HS!0_FTGt7w}J$_aK^v0$%SD0e{l1$NL7T;0XpTXRmKQ@fW0PuRQ0XnV)c`-d7ZC>2cl z2j8H07@?3h2EV9l5_N`Df=53($`L>>X?<=uG$K}8*0M2(%<0yJKw-mHs`f3=;6Tec z(?Ma%2T_WX3ATPjC_-4o`|6#d=#pPK%P_c2RWt2sRO#$6GhLpk)V>MKs1&GGmo1}X z=&AOz>tc(@uBzwack1vgjFdjxoWSc&%E}aq6kN!xUdMg~?C1Nih#5m6sJA2URUdi!~~H zF{|G5s#YwJ`v8k7?Gzg-FE${yOhaNEXfGtJ0TAOe55%y}x}YLQ+S(34Y~iPco3~S( z;D$}TjRdve0;!dGyonWNpr5Nq4@4s?WT=uNC*rVP&2;rrC_qSXAFB-S}H9|)Tm zj~zJs71FISRRUtZW(Z`}0}tTWi=E)DXC&D;1WE_X+@I9;E|~ zev3WNBH66Eq9yaEcL~dj@atG-cv8awD{o@l2gC$f!;A}dUNUv#33jZOwkwB<874=c+HOW zGYeAk?{PNvIyT;2IyRyZWvA9G@Z^P+_(> z7uH&7ti~3a3jV`*vtFCfQGKiTPKcSmwz_o{Vi}bFadc{C#}{WO?5^wZK~@oqt^l;f zsGv=6n!%~p+HO)5pzlzcfZI`v2$(9X7i+C2Afbj&MS~-#{wdRIz=IpYE<|T37x9&T zG+jK67K}dcE4I15v_u>y{X#ig@%u0l$BaN5<3~Ju?f9!Y_n>&XMc{u_5Ev#kCAs*3 z7L9V3d!MU3;z<4tRFJqxM!E;RimdQz`aFFW_`B=2xEoo>klE@rjKKl`v+eb69D~L zIJz*&Tu1H8rbuV)yz1$O5CF+@Ie;dC!Cxz6~S&gzvGl`jkv4W{4h4 z#v_zM4UeGesMjprJx;=NJlJ*Wch8ia>$%XfT=L%@n(DrI1=rkdWqMe0)tr-<>KaGC zK=zf(?5HtAv@CEVOrc83>;}vu%z;k!LKhu^Mb`82mBiiDFzj0vWT9M%h!H1w9kke?o7>~W3@&(UXxSIrVxQ7+SP2Bm#wrev2oLENGT6b50g(x zQr*1MS*-UtjlyZCsu3a2>c!S3!0fb`IbB9>tdAMa=ip$&xN$sat)x1SWjd|EoS4mm zEpu!(`Y^c2U?&@mR97JGW41>9pZ3l?n##6qdkk+w_?^bk8kWr&pTym`Fqeb&3)f4{Z9wZ8teHv7J>>mJVY zJbuT~#TzN9@k!EUWIYd}Uv!&Ks}?jk&^COd)u^w*2uresf@mQw%^R&1W}o*0XJZ>d zo}%D@XGIB;#<7j zeVCpkZ(kVBuIPi;%A;sKYb~jig|KgNi@5{p5Obo+Y z=rjR!n1$cVexeJa5NZ-yk7!GF#EYaa zRL@U42f|#F>|~OfBt0%&x=3u1!&m#%mb^+Q4ssysm*&Na(Y9J-n-G;@{c?JdtwlIV z#O_nzX8IUQn{S8>##c4VY|)j;#mo(}?>4_A6gJMy`pZi9h8q|&tTYI#Y9&-^1)QkV z$8{OgFLc~sI5SNlc#h=)%eAjHU5z&_tqZ597j@W4eS9^A>2R^j8)rYnGF}_%mY3CZ zQ;*9J{$k&ia`m=V6Bs<8jbG$+)p}F&aB+CLO!UX zV3EBrR_i-*tNYIj>B_OOsVt_!*GNA!e%kw0nPJ(jN}Y4MlFaui8r(Fq2Dbe2Z_+N2 ztk-tkWSjo;D)`D0l(18a>b@T`iOw5k>N){KcxQtV<;d3hX|4z0wSNoD^MuXPR^2=F zUnO(`lWv76dIShpD8b`u#tx|TcEyS~Kwkh*VpD*Y&7!l^b?c1jemRaB?>=kHbP(H* zs9L44t)JIwb=FQk6s58OjBZq7d~wdIW>uE8IG|@ok;k49J2ik5*O4`^5s+YX*ozGAFP7qI`lbnnZltXPCb_rei2}C;n@t&2=BT=-JzP;NRB4~u2lfNH`EyI;H`{(VE zEBH~n=}w6#xK~Wys6)4ZVjonSi5xkaVITu~`8W?Ac_u?Hd(u_Zr(Nw#q2n!booY{< zb8>4`q`%g@J}R5JFp0-(O`?l)x|^X_Mij08)A}@F!;=f*S^9;eJ=)bMj>+r;IVDid zgSk?pQ-$TY2HhH9`6j3LTQQeqPC}pSXwYQNeNO; zeclfGK_)V1k=zh&a+wnwoi zMmp1CT{lM~JG#4%d zzv9(o|HaOTjqVq*`bKb>Odh#Gdtkba3n1r&CX>=WN^O>#^f)J`*xATu(>iv(X&;VU zxigdhF{`1R*lH&`m$>NA*cH zkjk+^=WI=|2&{kNljd93J1Qpbbb5azUlu1dV*UTO6 z6E%HG+hwP74C#>j(-f%wU?mgp+y7BLvR}Ymh635?_pVHbBxZ?XmXBeFlsA;^+s9_M zWMNlXj#gjlyx|<7bdZ+__w||2C3NK1lx#DsE{nN`4Ny=_bqM6$rd{+uon`&W!}x}3 z>y%S_wEwG?p!ZEE0E~&nQ72lfPm$S$Z06j>omP z$Mp%gR~lJAwFuEUR&OuY4xVAxp(abe?UUx6r;>`E&rm-kHTuO%mGJ}JX5N7LQ^j8rOGlh%l+E|uJ+S;(q6t3(+$NevOm{U za~b^m^okS|@I&RBMfpnc<((#MWb;)Qc@zUSHsY{8;j$%EGq|&HvT5gq3 zOjxGqaA|$g+5E_XApC=%?2ohE*zF^$tlK4D)}I%qd_OK8p55A+$Ll_rgchPAA-{VS z6h(6TG1;Fh)JoR2GqWX{aWTTw5#KS7zKF7Yl_UzLuU>*~h@>)IG9tWtObXW@nC8dm z>a3Q0=VW(CN{eL49eMoOshed6I$A1T-UHo@GImU^f>_$$#PL6EN!9taMtuTv-bK9b z+cVaDAhPm(e3R)PY8z7&s%QA^>83DoNv%MGEC7`!vgOIYeL@H)&S0T9rQq`9?%;$TDmdiA9VUqS8Uj!nt9 z>UQSb({6dGb5#-P2S}+ zJi90*3>5N_Di%o;NE;@=4CfL8mjKUGJ+lD_%s6zG9A%1~J}{25tT3yhA{;*9lEmfr zS~Yp>o-sv_rF&DiJT9#C9$M%SEGy}0HG+ZSYO2jO#S7$KYv4^7z1%D242(hzw5koN z@3z4_v&R7sL&q9=O{daaf=;m}`;*}wyo9VZ2MC74 zvCSm+jr8O0>4F-LB7%&V%V^|&gFz&fp{uUgbJeN zR(OXcAaP*Qu0tJ(FpK*Ib1ToPS^~~zx)ZzfzS$EXuDa#t)Z=iFu2oh~@~W=;CrfK2 zq$z^2$_nPLupVCcjhXBL2tYd%48z$On9dj;>p(k8rt57{J!*rvE zPVO_B<5WN;QWNmJ;7dIDJQ>qL)l&srM65I&=KDHQn50{Ixvswte#m#xdL-Mcu}lot z&6`*`{^Zgn=Nz9{#KsfK9sE^d9N>!u7+M8zgaguMHD2dAIUqNigMX0WpS-&DW8hMR zd27J?tIq2!0B?)C5V-p-iKA~JnA@vA%wKnRUUzzM*>o3EwG*TpZk$pIh1_M=iOl7;2`yU#+^T1>g z0K2fd)V!MGPb+be;TqU!ki5E6Tg&Z8!kk~#rkG)FKNv=qHqBs59z7tKW2jDu>5MBW&`ehjzb3x5Xrk?eY46lO=NzPLNZ>LeBbkzy#AfW*e@r zel9KmWZbXQape%%+z;sm-_e(;K`o{))84KeGsHujn45R7m(v4lPee&gE0d)h&I zKR+hi9vGNg`@~H84k)g{;+!=9NqIQrqYP_=u*Kl|!z?Z|nes7wFg)x;LN1?833>WSSxZ|ob?8?TUgN2!mkghH%c0Mfzyp>*yi z)ViCSuOnG-Gq19nROJ8UD)&RASx>{jv8Jz*Bgc+kfgwhy<&*ng?ju4Dvz!s!9cHlp z3_U^cqzYtNfXiU1@(YXu+0&0iVD*kQKR(sesg|b{+Q>I^?yTI4hWZ1?r{_oDA>gr} zg2r2eGex@1CdbG3?5!8n$@Y_poN`0hDM&BM^_8Aw5p|unoxEI3_vD1eZFg=WWWXRn zN(33_Icm{I;rMrQ&NDVE4KNuOSD_nGYIF1u_l|i6Pl4(sBzg+p@uRB;k5HWWDa_@_ z*O6ffmA&ODb1r=)IvExGT#^~9=hL$*$0@><(gFj3JwoLU#;_Hi;hW^BIKU; zK1|`u<5JeSmly~zqJ&>K6Wd7;^Wm*1AYc2LC463B2*+Ca+@-h&KKn- z9xyu=qr!>$>45EuxIM;9`j=LL36Aa;j3d`?mMUVBNO#|T#ysBhygK;Hw=<~u7(eCt zpB}S`Vp%*?t=4K!_P8z>q|uIouCe*S*Do~*82)j}ZPrf=O6>6S&NHFXT2L}+XxsNbB?37HAsH4Zig>Y?t@=eXg2)PpdV) z5l(7heA0Cux2x7VKmmyenjSsX|Ma}?h{O%18VjaWfC3aJ5rT(y{G_>B4W{{u8UTD; zzf+D$quo>FNBz`kOc+S}u zWL{m)Xq5#-bw)hus`GjOFpR-xzdx zGgqVOy)Q10K~R9V&kdIjlJ5QtEwYOO)WpaClXTeCOuf7hue`MG}0r+mj zm7^kl0#(GU+!>?}sKqXLQDn}BfA8DlMG#P#ez-&DIEeY|g#mmNKtPkLa;og9d&)Sn z)Qmm8mVjzJ@Sho8(nUy$9R@(R^wxU@N)Xvk8k%Oez>m?`U%b8S+Ak53>0f*X8}MI@ z6)mOTSm7^pqN=mbD(*rTtBhqNd!U0*G;+RKc6-#x`CV>os`!S}MZ=WIGiUG=*c~or z2ca@zTCsQwf6XkEYB+*yhxQP(P`|d#qk|pi$X)JKWS0-*e61)6~eG-H8QL?{r zqRkz8EA+(M{d)%^!Lvh-F-C=#_QbNo@|1JQ{Uc-JkcftPLgEKWqrG`{ar@$k( z04{wT2$N4+y4?OO9>A8E1@IZ93R6W4?Otic32JHiTi~O1^7?O{`zTtO8!?rLOl;*= zR$O`ES(w*~2hEPZ{`!CS*B@-4&P($D{H^>RT%w~It(1o@&O0Lxg}ZWluKLdJ@aEqM z+l84{Ife+6-@%~OdB-cSWWnu@nCU&HI^z0y^FdBWLY@QQ-8c$$ohu~OKE1^%@to!S z6}u)Nx2r&#m-aNsmU(6Q*u@SYfw))T>Rnt1!8@0hO|qlWZ|-8udnx~U6C@H3@v=Tf zCQ@T{p6-Wi{F0%f^I3x#;~4y152O@F(m)AMulhCt2e1I*<71?{4qEy0Nr4>U^xgAk z!4U0f-?w!Q(2Htu7Mb+b@``G=>ztQwxFdSx4taiy96|#j9su$N5|I*k?aTe!mO#Gq zx0j9VLkuesn?R2g>Qz;I1|LQ)o5e_f3@-6g|XLi)ybVNi7A#e3UC<=}|k~+Dnm#^?_!9Z0EIsMxr^1%k;xVA9`w?7M30IgzGSvE!RfXrcW` zqJqX@mPSJsV~%LFMX7k_SBQ13POHRxK=AftslNc<@MoEUlI|iUr+2tNq}pJM(MKKP z(qKw*&OGoxZ1NGB+Yi@OpT@jb|G@Jav1%*6X$cK(f(;UT=TiS+30lH65QdsQe0p9& zU%z}5h=|x+=)?lxdL4uK1c#337zzI^<@}u(mJ=qCoGfa?zk^x|LcxbONp5<*90eGY^PVAKw1YY&>*=QAUZAndgTUNeTya`jX}Rz6U9$Ll3WX81WR)r6-z11Jutnt z!U7}UyEB^K%!`$lZd5gLtK(iL*A_x$fSrAMq8Ft7T3;RY;rbNJHBq}iu?O?4yd`|; zI4M$*p0Pi-eF;KJX=E+Z|B1u^retcVvs*t z?bQseZ@}{dg7rRAL7nq6AwjUnfdpZ%B_y31tPoet>8Z2pAVxxd{9=NbQ#&P5-Y)|_ z^C6M*VZ#i&bHt$_da%BWxr>I7PC_%mc*u`J$CvvzbcAxGGn{$&d4vhUP9mM^sfS%| zooUa2ajKW{e6OQo#cDevfjlzbB2F*isBrq(&=)*!WlFb9BYsiXX@C+ebl${2u|!zOACv!C5mFD1Hj|++y_c@ z+nIJMw}{HW5qQ(1XSeGGn&$G=Z}A{E{fk3jBB|Nk=kcEv%_HPuhXkMoP zPWSzpVhToh((PvLus;3|+!lAXjI(@vuV$P_DjBI)wR?rz{{qtZca{?cA5tbqwHfZ; zI~KrOcQ{T)fit1;k>ILC5?W37&N|7|^q*_NOZ-`+Cq^B?i(u$v7UfxT$VZVFs?0Zb zmncL>SDl2VlUR3YR*CK#S#VSA7&DRN2CCx~N?G#}dlfn>5`+Mpud2$hgC@8c@0kArdM-^ImJ9CaphNeh7-ce{*ZHr90Bv z?Cx^N&RXZs>e&Ci%QSquMh839vm37knvUkH_$!<3oiF-7%HL%F`524+%ll!M!}{)P zG0^Yr@)c&ys?OW6U|}~`V^?-b z&XvHUhm^THsGERWBzJ{8ROsaf5%Nj4a6G(3vV@YxbO9h{9bN0B2^aU;0_F zb#*citn&RAcL%dA;s1`i Date: Thu, 11 Jan 2024 13:08:44 +0200 Subject: [PATCH 18/23] Added heartbeat --- protocol/20231215-accessnode-streaming-api-expansion.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/protocol/20231215-accessnode-streaming-api-expansion.md b/protocol/20231215-accessnode-streaming-api-expansion.md index 2b59890c..528420b5 100644 --- a/protocol/20231215-accessnode-streaming-api-expansion.md +++ b/protocol/20231215-accessnode-streaming-api-expansion.md @@ -326,13 +326,17 @@ This endpoint enables users to subscribe to the streaming of account status chan - **StartBlockId** (Block ID of the acount status being streamed) - **Address** (address of the account to stream status changes) - **Filter** (array of statuses matching the client’s filter) +- **HeartbeatInterval** (Interval in block heights at which the server should return a heartbeat message to the client) Either one of the two arguments, `StartBlockHeight` or `StartBlockId`, should be set but not both. If none of these arguments is set, the last sealed block will be considered as the starting block. If the `Filter` is empty, all account statuses will be returned. If filters are set, only statuses that precisely match one of these filter conditions will be returned. +The API will send a heartbeat message periodically. This will configure a regular response message containing the `Address` and `BlockId` fields, but no `Status`. If the `HeartbeatInterval` is not set by the caller, the default heartbeat interval will be used. + **Expected response:** +- **BlockId** (The block ID of the block containing the statuses) - **Address** (The adress of the tracked account) - **Status** (The status of the tracked account) From 061be1fc9d24951537a9e8a623f53dff67c9d73a Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Wed, 17 Jan 2024 16:00:24 +0200 Subject: [PATCH 19/23] Apply suggestions from code review Co-authored-by: Peter Argue <89119817+peterargue@users.noreply.github.com> --- protocol/20231215-accessnode-streaming-api-expansion.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/protocol/20231215-accessnode-streaming-api-expansion.md b/protocol/20231215-accessnode-streaming-api-expansion.md index 528420b5..ee894866 100644 --- a/protocol/20231215-accessnode-streaming-api-expansion.md +++ b/protocol/20231215-accessnode-streaming-api-expansion.md @@ -25,7 +25,7 @@ The Access Node Subscription API would introduce the following new streaming end - SubscribeBlocks - SubscribeBlockHeaders - SubscribeBlockDigests -- SendAndSubscribeTransactionStatusess +- SendAndSubscribeTransactionStatuses - SubscribeAccountStatuses Additionally, it proposes an improved version of the REST WebSocket Subscription with a single connection. @@ -104,7 +104,7 @@ Additionally, the Access Subscription API requires modifications, as all streame ### SubscribeBlocks -This endpoint enables users to subscribe to the streaming of blocks, commencing from a provided block ID or height. Additionally, users are required to specify the desired status of the streamed blocks to receive. Each block's response should include the full or light `Block` depends on `FullBlockResponse` argument. +This endpoint enables users to subscribe to the streaming of blocks, commencing from a provided block ID or height. Additionally, users are required to specify the desired status of the streamed blocks to receive. Each block's response should include the full or light `Block` depends on `FullBlockResponse` argument. The "full" response contains all data needed to reproduce the block's hash, while the "light" response only contains the most commonly used fields. **Arguments:** @@ -178,7 +178,7 @@ The `BlockStatus` cannot be set as `BlockStatusUnknown`. **Usage example:** ```go -req := &access.SubscribeBlocksRequest{ +req := &access.SubscribeBlockHeadersRequest{ // If no start block height or Id is provided, the latest block is used StartBlockHeight: 1234, // or StartBlockId: startBlockID[:], @@ -277,7 +277,7 @@ This endpoint enables users to send a transaction and immediately subscribe to i - **ID** (The ID of the tracked transaction) - **Status** (The status of the tracked transaction) -- **SequenceNumber** (The SequenceNumber of the tracked transaction) +- **SequenceNumber** (The SequenceNumber of the response message. Used by the client to ensure they received all messages.) **Usage example:** From 9fdcacca26e811b50698453eb1f1dbe284681582 Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Thu, 18 Jan 2024 12:42:43 +0200 Subject: [PATCH 20/23] fixed remarks --- ...31215-accessnode-streaming-api-expansion.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/protocol/20231215-accessnode-streaming-api-expansion.md b/protocol/20231215-accessnode-streaming-api-expansion.md index ee894866..e6ddc961 100644 --- a/protocol/20231215-accessnode-streaming-api-expansion.md +++ b/protocol/20231215-accessnode-streaming-api-expansion.md @@ -279,6 +279,14 @@ This endpoint enables users to send a transaction and immediately subscribe to i - **Status** (The status of the tracked transaction) - **SequenceNumber** (The SequenceNumber of the response message. Used by the client to ensure they received all messages.) +Possible transaction statuses are: + +- `TransactionStatusPending` +- `TransactionStatusFinalized` +- `TransactionStatusExecuted` +- `TransactionStatusSealed` +- `TransactionStatusExpired` + **Usage example:** ```go @@ -318,7 +326,7 @@ for { ### SubscribeAccountStatuses -This endpoint enables users to subscribe to the streaming of account status changes. Each response for the account status should include the `Address` and the `Status` fields ([built-in account event types](https://developers.flow.com/build/basics/events#core-events)). In the future, this endpoint could potentially incorporate additional fields to provide supplementary data essential for tracking alongside the status. +This endpoint enables users to subscribe to the streaming of account status changes. Each response for the account status should include the `Address` and the `Events` fields ([built-in account event types](https://developers.flow.com/build/basics/events#core-events)). In the future, this endpoint could potentially incorporate additional fields to provide supplementary data essential for tracking alongside the status. **Arguments:** @@ -332,13 +340,13 @@ Either one of the two arguments, `StartBlockHeight` or `StartBlockId`, should be If the `Filter` is empty, all account statuses will be returned. If filters are set, only statuses that precisely match one of these filter conditions will be returned. -The API will send a heartbeat message periodically. This will configure a regular response message containing the `Address` and `BlockId` fields, but no `Status`. If the `HeartbeatInterval` is not set by the caller, the default heartbeat interval will be used. +The API will send a heartbeat message periodically. This will configure a regular response message containing the `Address` and `BlockId` fields, but no `Events`. If the `HeartbeatInterval` is not set by the caller, the default heartbeat interval will be used. **Expected response:** - **BlockId** (The block ID of the block containing the statuses) - **Address** (The adress of the tracked account) -- **Status** (The status of the tracked account) +- **Events** (The array of core events of the tracked account) Usage example: @@ -368,9 +376,9 @@ for { log.Fatalf("error receiving account status: %v", err) } - log.Printf("received account status with address: %s and status: %s", + log.Printf("received account status with address: %s and event count: %d", resp.Address.String(), - resp.Status.String(), + len(resp.Events), ) } From b610718c2e898676cd6b2529f44cb90d20775e36 Mon Sep 17 00:00:00 2001 From: Andrii Slisarchuk Date: Tue, 23 Jan 2024 15:13:31 +0200 Subject: [PATCH 21/23] Renamed SeqNumber to MessageeIndex --- .../20231215-accessnode-streaming-api-expansion.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/protocol/20231215-accessnode-streaming-api-expansion.md b/protocol/20231215-accessnode-streaming-api-expansion.md index e6ddc961..1836719e 100644 --- a/protocol/20231215-accessnode-streaming-api-expansion.md +++ b/protocol/20231215-accessnode-streaming-api-expansion.md @@ -267,7 +267,7 @@ for { ### SendAndSubscribeTransactionStatuses -This endpoint enables users to send a transaction and immediately subscribe to its status changes. The status is streamed back until the block containing the transaction becomes sealed. Each response for the transaction status should include the `ID`, `Status` and `SequenceNumber` fields. +This endpoint enables users to send a transaction and immediately subscribe to its status changes. The status is streamed back until the block containing the transaction becomes sealed. Each response for the transaction status should include the `ID`, `Status` and `MessageIndex` fields. **Arguments:** @@ -276,8 +276,8 @@ This endpoint enables users to send a transaction and immediately subscribe to i **Expected response:** - **ID** (The ID of the tracked transaction) -- **Status** (The status of the tracked transaction) -- **SequenceNumber** (The SequenceNumber of the response message. Used by the client to ensure they received all messages.) +- **Status** (The status of the tracked transaction, see possible statuses below) +- **MessageIndex** (The MessageIndex of the response message. Used by the client to ensure they received all messages.) Possible transaction statuses are: @@ -316,10 +316,10 @@ for { txResult := resp.GetTransactionResult() - log.Printf("received transaction status with tx ID: %x, status: %s, and error: %v", + log.Printf("received transaction status with tx ID: %x, status: %s, and message index: %d", resp.ID, resp.Status.String(), - resp.SequenceNumber + resp.MessageIndex ) } ``` @@ -347,6 +347,7 @@ The API will send a heartbeat message periodically. This will configure a regula - **BlockId** (The block ID of the block containing the statuses) - **Address** (The adress of the tracked account) - **Events** (The array of core events of the tracked account) +- **MessageIndex** (The MessageIndex of the response message. Used by the client to ensure they received all messages.) Usage example: @@ -376,9 +377,10 @@ for { log.Fatalf("error receiving account status: %v", err) } - log.Printf("received account status with address: %s and event count: %d", + log.Printf("received account status with address: %s, event count: %d and message index: %d", resp.Address.String(), len(resp.Events), + resp.MessageIndex, ) } From 144b2c996ef5661a5163bdc7dac8586e9d516566 Mon Sep 17 00:00:00 2001 From: j pimmel Date: Wed, 24 Jan 2024 15:12:27 -0800 Subject: [PATCH 22/23] Fix incorrect FLIP numbering error I didn't realize the number was supposed to be the GH Issue number mapping to this FLIP in this repo --- protocol/20231215-accessnode-streaming-api-expansion.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/20231215-accessnode-streaming-api-expansion.md b/protocol/20231215-accessnode-streaming-api-expansion.md index 1836719e..6aa38d43 100644 --- a/protocol/20231215-accessnode-streaming-api-expansion.md +++ b/protocol/20231215-accessnode-streaming-api-expansion.md @@ -1,6 +1,6 @@ --- status: draft -flip: 229 +flip: 244 authors: Andrii Slisarchuk (andriyslisarchuk@gmail.com) sponsor: AN Expert (core-contributor@example.org) updated: 2023-12-15 From 261874a91531087d1e8a23c78d88c7e6a684e799 Mon Sep 17 00:00:00 2001 From: j pimmel Date: Wed, 24 Jan 2024 15:15:23 -0800 Subject: [PATCH 23/23] Fixed sponsor field --- protocol/20231215-accessnode-streaming-api-expansion.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/20231215-accessnode-streaming-api-expansion.md b/protocol/20231215-accessnode-streaming-api-expansion.md index 6aa38d43..473c9346 100644 --- a/protocol/20231215-accessnode-streaming-api-expansion.md +++ b/protocol/20231215-accessnode-streaming-api-expansion.md @@ -2,7 +2,7 @@ status: draft flip: 244 authors: Andrii Slisarchuk (andriyslisarchuk@gmail.com) -sponsor: AN Expert (core-contributor@example.org) +sponsor: Jerome Pimmel (jerome.pimmel@flowfoundation.org), Peter Argue (peter.argue@flowfoundation.org) updated: 2023-12-15 ---