geyser: support data_slice for accounts (#150)
This commit is contained in:
parent
b73848a37a
commit
ef9c079f07
|
@ -5,6 +5,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- uses: arduino/setup-protoc@v2
|
||||||
# Setup .npmrc file to publish to npm
|
# Setup .npmrc file to publish to npm
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -34,6 +34,16 @@ jobs:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
|
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/bin/
|
||||||
|
~/.cargo/registry/index/
|
||||||
|
~/.cargo/registry/cache/
|
||||||
|
~/.cargo/git/db/
|
||||||
|
yellowstone-grpc/target/
|
||||||
|
key: cargo-${{ hashFiles('rust-toolchain.toml') }}-${{ hashFiles('**/Cargo.lock') }}-0001
|
||||||
|
|
||||||
- name: Check Solana version
|
- name: Check Solana version
|
||||||
run: |
|
run: |
|
||||||
echo "CI_TAG=$(ci/getTag.sh)" >> "$GITHUB_ENV"
|
echo "CI_TAG=$(ci/getTag.sh)" >> "$GITHUB_ENV"
|
||||||
|
@ -50,6 +60,7 @@ jobs:
|
||||||
run: ./ci/create-tarball.sh
|
run: ./ci/create-tarball.sh
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ env.BUILD_NAME }}
|
tag_name: ${{ env.BUILD_NAME }}
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
# Source:
|
|
||||||
# https://github.com/solana-labs/solana-accountsdb-plugin-postgres/blob/master/.github/workflows/test.yml
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
|
@ -33,12 +30,15 @@ jobs:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
- uses: actions/cache@v2
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/registry
|
~/.cargo/bin/
|
||||||
~/.cargo/git
|
~/.cargo/registry/index/
|
||||||
key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}}
|
~/.cargo/registry/cache/
|
||||||
|
~/.cargo/git/db/
|
||||||
|
yellowstone-grpc/target/
|
||||||
|
key: cargo-${{ hashFiles('rust-toolchain.toml') }}-${{ hashFiles('**/Cargo.lock') }}-0001
|
||||||
|
|
||||||
- name: cargo tree
|
- name: cargo tree
|
||||||
run: |
|
run: |
|
||||||
|
|
77
CHANGELOG.md
77
CHANGELOG.md
|
@ -14,71 +14,48 @@ The minor version will be incremented upon a breaking change and the patch versi
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
### Breaking
|
||||||
|
|
||||||
|
## 2023-06-29
|
||||||
|
|
||||||
|
- @triton-one/yellowstone-grpc:0.1.2
|
||||||
|
- yellowstone-grpc-client-1.4.0+solana.1.16.1
|
||||||
|
- yellowstone-grpc-geyser-1.0.0+solana.1.16.1
|
||||||
|
- yellowstone-grpc-proto-1.4.0+solana.1.16.1
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- geyser: support data_slice for accounts ([#150](https://github.com/rpcpool/yellowstone-grpc/pull/150)).
|
||||||
|
- client: add TypeScript client ([#142](https://github.com/rpcpool/yellowstone-grpc/pull/142)).
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
- client: set max message size for decode ([#151](https://github.com/rpcpool/yellowstone-grpc/pull/151)).
|
- client: set max message size for decode ([#151](https://github.com/rpcpool/yellowstone-grpc/pull/151)).
|
||||||
- geyser: remove duplicated account updates for confirmed/finalized ([#152](https://github.com/rpcpool/yellowstone-grpc/pull/152)).
|
- geyser: remove duplicated account updates for confirmed/finalized ([#152](https://github.com/rpcpool/yellowstone-grpc/pull/152)).
|
||||||
|
|
||||||
### Breaking
|
## 2023-06-16
|
||||||
|
|
||||||
## [yellowstone-grpc-proto-1.3.0+solana.1.16.1] - 2023-06-16
|
- yellowstone-grpc-client-1.3.0+solana.1.16.1
|
||||||
|
- yellowstone-grpc-geyser-0.8.2+solana.1.16.1
|
||||||
|
- yellowstone-grpc-proto-1.3.0+solana.1.16.1
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- geyser: update solana =1.16.1 ([#146](https://github.com/rpcpool/yellowstone-grpc/pull/146)).
|
- geyser: update solana =1.16.1 ([#146](https://github.com/rpcpool/yellowstone-grpc/pull/146)).
|
||||||
|
|
||||||
### Fixes
|
## 2023-06-15
|
||||||
|
|
||||||
### Breaking
|
- yellowstone-grpc-client-1.3.0+solana.1.14.18
|
||||||
|
- yellowstone-grpc-client-1.3.0+solana.1.15.2
|
||||||
## [yellowstone-grpc-client-1.3.0+solana.1.16.1] - 2023-06-16
|
- yellowstone-grpc-geyser-0.8.2+solana.1.14.18
|
||||||
|
- yellowstone-grpc-geyser-0.8.2+solana.1.15.2
|
||||||
### Features
|
- yellowstone-grpc-proto-1.3.0+solana.1.14.18
|
||||||
|
- yellowstone-grpc-proto-1.3.0+solana.1.15.2
|
||||||
- geyser: update solana =1.16.1 ([#146](https://github.com/rpcpool/yellowstone-grpc/pull/146)).
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
|
|
||||||
### Breaking
|
|
||||||
|
|
||||||
## [yellowstone-grpc-geyser-0.8.2+solana.1.16.1] - 2023-06-16
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- geyser: update solana =1.16.1 ([#146](https://github.com/rpcpool/yellowstone-grpc/pull/146)).
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
|
|
||||||
### Breaking
|
|
||||||
|
|
||||||
## [yellowstone-grpc-proto-1.3.0+solana.1.15.2] - 2023-06-15
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- geyser: Update `tonic`, `0.8.2` => `0.9.2` ([#145](https://github.com/rpcpool/yellowstone-grpc/pull/145)).
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
|
|
||||||
### Breaking
|
|
||||||
|
|
||||||
## [yellowstone-grpc-client-1.3.0+solana.1.15.2] - 2023-06-15
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- geyser: Update `tonic`, `0.8.2` => `0.9.2` ([#145](https://github.com/rpcpool/yellowstone-grpc/pull/145)).
|
- geyser: Update `tonic`, `0.8.2` => `0.9.2` ([#145](https://github.com/rpcpool/yellowstone-grpc/pull/145)).
|
||||||
- geyser: Add methods `health_check` and `health_watch` ([#145](https://github.com/rpcpool/yellowstone-grpc/pull/145)).
|
- geyser: Add methods `health_check` and `health_watch` ([#145](https://github.com/rpcpool/yellowstone-grpc/pull/145)).
|
||||||
|
|
||||||
### Fixes
|
|
||||||
|
|
||||||
### Breaking
|
|
||||||
|
|
||||||
## [yellowstone-grpc-geyser-0.8.2+solana.1.15.2] - 2023-06-15
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- geyser: Add prometheus metric `message_queue_size` ([#145](https://github.com/rpcpool/yellowstone-grpc/pull/145)).
|
- geyser: Add prometheus metric `message_queue_size` ([#145](https://github.com/rpcpool/yellowstone-grpc/pull/145)).
|
||||||
- geyser: Update `tonic`, `0.8.2` => `0.9.2` ([#145](https://github.com/rpcpool/yellowstone-grpc/pull/145)).
|
|
||||||
- geyser: Send task per connection ([#145](https://github.com/rpcpool/yellowstone-grpc/pull/145)).
|
- geyser: Send task per connection ([#145](https://github.com/rpcpool/yellowstone-grpc/pull/145)).
|
||||||
- geyser: Send processed immediately without `Slot` message ([#145](https://github.com/rpcpool/yellowstone-grpc/pull/145)).
|
- geyser: Send processed immediately without `Slot` message ([#145](https://github.com/rpcpool/yellowstone-grpc/pull/145)).
|
||||||
|
|
||||||
### Fixes
|
|
||||||
|
|
||||||
### Breaking
|
|
||||||
|
|
|
@ -4125,7 +4125,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yellowstone-grpc-client"
|
name = "yellowstone-grpc-client"
|
||||||
version = "1.3.0+solana.1.16.1"
|
version = "1.4.0+solana.1.16.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures",
|
"futures",
|
||||||
|
@ -4139,7 +4139,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yellowstone-grpc-client-simple"
|
name = "yellowstone-grpc-client-simple"
|
||||||
version = "1.3.0+solana.1.16.1"
|
version = "1.4.0+solana.1.16.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"backoff",
|
"backoff",
|
||||||
|
@ -4157,7 +4157,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yellowstone-grpc-geyser"
|
name = "yellowstone-grpc-geyser"
|
||||||
version = "0.8.2+solana.1.16.1"
|
version = "1.0.0+solana.1.16.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.21.2",
|
"base64 0.21.2",
|
||||||
|
@ -4187,7 +4187,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yellowstone-grpc-proto"
|
name = "yellowstone-grpc-proto"
|
||||||
version = "1.3.0+solana.1.16.1"
|
version = "1.4.0+solana.1.16.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"prost",
|
"prost",
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"examples/rust", # 1.3.0+solana.1.16.1
|
"examples/rust", # 1.4.0+solana.1.16.1
|
||||||
"yellowstone-grpc-client", # 1.3.0+solana.1.16.1
|
"yellowstone-grpc-client", # 1.4.0+solana.1.16.1
|
||||||
"yellowstone-grpc-geyser", # 0.8.2+solana.1.16.1
|
"yellowstone-grpc-geyser", # 1.0.0+solana.1.16.1
|
||||||
"yellowstone-grpc-proto", # 1.3.0+solana.1.16.1
|
"yellowstone-grpc-proto", # 1.4.0+solana.1.16.1
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "yellowstone-grpc-client-simple"
|
name = "yellowstone-grpc-client-simple"
|
||||||
version = "1.3.0+solana.1.16.1"
|
version = "1.4.0+solana.1.16.1"
|
||||||
authors = ["Triton One"]
|
authors = ["Triton One"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
|
@ -11,10 +11,11 @@ use {
|
||||||
subscribe_request_filter_accounts_filter::Filter as AccountsFilterDataOneof,
|
subscribe_request_filter_accounts_filter::Filter as AccountsFilterDataOneof,
|
||||||
subscribe_request_filter_accounts_filter_memcmp::Data as AccountsFilterMemcmpOneof,
|
subscribe_request_filter_accounts_filter_memcmp::Data as AccountsFilterMemcmpOneof,
|
||||||
subscribe_update::UpdateOneof, CommitmentLevel, SubscribeRequest,
|
subscribe_update::UpdateOneof, CommitmentLevel, SubscribeRequest,
|
||||||
SubscribeRequestFilterAccounts, SubscribeRequestFilterAccountsFilter,
|
SubscribeRequestAccountsDataSlice, SubscribeRequestFilterAccounts,
|
||||||
SubscribeRequestFilterAccountsFilterMemcmp, SubscribeRequestFilterBlocks,
|
SubscribeRequestFilterAccountsFilter, SubscribeRequestFilterAccountsFilterMemcmp,
|
||||||
SubscribeRequestFilterBlocksMeta, SubscribeRequestFilterSlots,
|
SubscribeRequestFilterBlocks, SubscribeRequestFilterBlocksMeta,
|
||||||
SubscribeRequestFilterTransactions, SubscribeUpdateAccount,
|
SubscribeRequestFilterSlots, SubscribeRequestFilterTransactions,
|
||||||
|
SubscribeUpdateAccount,
|
||||||
},
|
},
|
||||||
tonic::service::Interceptor,
|
tonic::service::Interceptor,
|
||||||
},
|
},
|
||||||
|
@ -109,6 +110,10 @@ struct ActionSubscribe {
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
accounts_datasize: Option<u64>,
|
accounts_datasize: Option<u64>,
|
||||||
|
|
||||||
|
/// Receive only part of updated data account, format: `offset,size`
|
||||||
|
#[clap(long)]
|
||||||
|
accounts_data_slice: Vec<String>,
|
||||||
|
|
||||||
/// Subscribe on slots updates
|
/// Subscribe on slots updates
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
slots: bool,
|
slots: bool,
|
||||||
|
@ -229,6 +234,20 @@ impl Action {
|
||||||
blocks_meta.insert("client".to_owned(), SubscribeRequestFilterBlocksMeta {});
|
blocks_meta.insert("client".to_owned(), SubscribeRequestFilterBlocksMeta {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut accounts_data_slice = Vec::new();
|
||||||
|
for data_slice in args.accounts_data_slice.iter() {
|
||||||
|
match data_slice.split_once(',') {
|
||||||
|
Some((offset, length)) => match (offset.parse(), length.parse()) {
|
||||||
|
(Ok(offset), Ok(length)) => {
|
||||||
|
accounts_data_slice
|
||||||
|
.push(SubscribeRequestAccountsDataSlice { offset, length });
|
||||||
|
}
|
||||||
|
_ => anyhow::bail!("invalid data_slice"),
|
||||||
|
},
|
||||||
|
_ => anyhow::bail!("invalid data_slice"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Some((
|
Some((
|
||||||
SubscribeRequest {
|
SubscribeRequest {
|
||||||
slots,
|
slots,
|
||||||
|
@ -237,6 +256,7 @@ impl Action {
|
||||||
blocks,
|
blocks,
|
||||||
blocks_meta,
|
blocks_meta,
|
||||||
commitment: commitment.map(|x| x as i32),
|
commitment: commitment.map(|x| x as i32),
|
||||||
|
accounts_data_slice,
|
||||||
},
|
},
|
||||||
args.resub.unwrap_or(0),
|
args.resub.unwrap_or(0),
|
||||||
))
|
))
|
||||||
|
@ -421,6 +441,7 @@ async fn geyser_subscribe(
|
||||||
blocks: HashMap::default(),
|
blocks: HashMap::default(),
|
||||||
blocks_meta: HashMap::default(),
|
blocks_meta: HashMap::default(),
|
||||||
commitment: None,
|
commitment: None,
|
||||||
|
accounts_data_slice: Vec::default(),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(GeyserGrpcClientError::SubscribeSendError)?;
|
.map_err(GeyserGrpcClientError::SubscribeSendError)?;
|
||||||
|
|
|
@ -90,6 +90,7 @@ async function subscribeCommand(client, args) {
|
||||||
transactions: {},
|
transactions: {},
|
||||||
blocks: {},
|
blocks: {},
|
||||||
blocksMeta: {},
|
blocksMeta: {},
|
||||||
|
accountsDataSlice: [],
|
||||||
};
|
};
|
||||||
if (args.accounts) {
|
if (args.accounts) {
|
||||||
const filters: SubscribeRequestFilterAccountsFilter[] = [];
|
const filters: SubscribeRequestFilterAccountsFilter[] = [];
|
||||||
|
@ -142,6 +143,21 @@ async function subscribeCommand(client, args) {
|
||||||
request.blocksMeta.client = {};
|
request.blocksMeta.client = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (args.accounts.dataslice) {
|
||||||
|
for (let filter in args.accounts.dataslice) {
|
||||||
|
const filterSpec = filter.split(",", 1);
|
||||||
|
if (filterSpec.length != 2) {
|
||||||
|
throw new Error("invalid data slice");
|
||||||
|
}
|
||||||
|
|
||||||
|
const [offset, length] = filterSpec;
|
||||||
|
request.accountsDataSlice.push({
|
||||||
|
offset: parseInt(offset, 10),
|
||||||
|
length: parseInt(length, 10),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Send subscribe request
|
// Send subscribe request
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
stream.write(request, (err) => {
|
stream.write(request, (err) => {
|
||||||
|
@ -222,6 +238,12 @@ function parseCommandLineArgs() {
|
||||||
describe: "filter by data size",
|
describe: "filter by data size",
|
||||||
type: "number",
|
type: "number",
|
||||||
},
|
},
|
||||||
|
"accounts-dataslice": {
|
||||||
|
default: [],
|
||||||
|
describe:
|
||||||
|
"receive only part of updated data account, format: `offset,size`",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
slots: {
|
slots: {
|
||||||
default: false,
|
default: false,
|
||||||
describe: "subscribe on slots updates",
|
describe: "subscribe on slots updates",
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
node_modules
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@triton-one/yellowstone-grpc",
|
"name": "@triton-one/yellowstone-grpc",
|
||||||
"version": "0.1.1",
|
"version": "0.1.2",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"author": "Triton One",
|
"author": "Triton One",
|
||||||
"description": "Yellowstone gRPC Geyser Node.js Client",
|
"description": "Yellowstone gRPC Geyser Node.js Client",
|
||||||
|
@ -31,5 +31,6 @@
|
||||||
"prettier": "2.8.3",
|
"prettier": "2.8.3",
|
||||||
"ts-proto": "^1.139.0",
|
"ts-proto": "^1.139.0",
|
||||||
"typescript": "^4.9.5"
|
"typescript": "^4.9.5"
|
||||||
}
|
},
|
||||||
|
"files": ["dist"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
GetLatestBlockhashResponse,
|
GetLatestBlockhashResponse,
|
||||||
GeyserClient,
|
GeyserClient,
|
||||||
IsBlockhashValidResponse,
|
IsBlockhashValidResponse,
|
||||||
|
SubscribeRequestAccountsDataSlice,
|
||||||
SubscribeRequestFilterAccounts,
|
SubscribeRequestFilterAccounts,
|
||||||
SubscribeRequestFilterBlocks,
|
SubscribeRequestFilterBlocks,
|
||||||
SubscribeRequestFilterBlocksMeta,
|
SubscribeRequestFilterBlocksMeta,
|
||||||
|
@ -81,7 +82,8 @@ export default class Client {
|
||||||
transactions: { [key: string]: SubscribeRequestFilterTransactions },
|
transactions: { [key: string]: SubscribeRequestFilterTransactions },
|
||||||
blocks: { [key: string]: SubscribeRequestFilterBlocks },
|
blocks: { [key: string]: SubscribeRequestFilterBlocks },
|
||||||
blocksMeta: { [key: string]: SubscribeRequestFilterBlocksMeta },
|
blocksMeta: { [key: string]: SubscribeRequestFilterBlocksMeta },
|
||||||
commitment?: CommitmentLevel | undefined
|
commitment: CommitmentLevel | undefined,
|
||||||
|
accountsDataSlice: SubscribeRequestAccountsDataSlice[]
|
||||||
) {
|
) {
|
||||||
const stream = await this._client.subscribe();
|
const stream = await this._client.subscribe();
|
||||||
|
|
||||||
|
@ -94,6 +96,7 @@ export default class Client {
|
||||||
blocks,
|
blocks,
|
||||||
blocksMeta,
|
blocksMeta,
|
||||||
commitment,
|
commitment,
|
||||||
|
accountsDataSlice,
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
if (err === null || err === undefined) {
|
if (err === null || err === undefined) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "yellowstone-grpc-client"
|
name = "yellowstone-grpc-client"
|
||||||
version = "1.3.0+solana.1.16.1"
|
version = "1.4.0+solana.1.16.1"
|
||||||
authors = ["Triton One"]
|
authors = ["Triton One"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Yellowstone gRPC Geyser Simple Client"
|
description = "Yellowstone gRPC Geyser Simple Client"
|
||||||
|
@ -16,7 +16,7 @@ http = "0.2.8"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tonic = { version = "0.9.2", features = ["gzip", "tls", "tls-roots"] }
|
tonic = { version = "0.9.2", features = ["gzip", "tls", "tls-roots"] }
|
||||||
tonic-health = "0.9.2"
|
tonic-health = "0.9.2"
|
||||||
yellowstone-grpc-proto = { path = "../yellowstone-grpc-proto", version = "1.3.0+solana.1.16.1" }
|
yellowstone-grpc-proto = { path = "../yellowstone-grpc-proto", version = "1.4.0+solana.1.16.1" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1.21.2", features = ["macros"] }
|
tokio = { version = "1.21.2", features = ["macros"] }
|
||||||
|
|
|
@ -20,9 +20,9 @@ use {
|
||||||
GetBlockHeightResponse, GetLatestBlockhashRequest, GetLatestBlockhashResponse,
|
GetBlockHeightResponse, GetLatestBlockhashRequest, GetLatestBlockhashResponse,
|
||||||
GetSlotRequest, GetSlotResponse, GetVersionRequest, GetVersionResponse,
|
GetSlotRequest, GetSlotResponse, GetVersionRequest, GetVersionResponse,
|
||||||
IsBlockhashValidRequest, IsBlockhashValidResponse, PingRequest, PongResponse,
|
IsBlockhashValidRequest, IsBlockhashValidResponse, PingRequest, PongResponse,
|
||||||
SubscribeRequest, SubscribeRequestFilterAccounts, SubscribeRequestFilterBlocks,
|
SubscribeRequest, SubscribeRequestAccountsDataSlice, SubscribeRequestFilterAccounts,
|
||||||
SubscribeRequestFilterBlocksMeta, SubscribeRequestFilterSlots,
|
SubscribeRequestFilterBlocks, SubscribeRequestFilterBlocksMeta,
|
||||||
SubscribeRequestFilterTransactions, SubscribeUpdate,
|
SubscribeRequestFilterSlots, SubscribeRequestFilterTransactions, SubscribeUpdate,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -133,6 +133,7 @@ impl<F: Interceptor> GeyserGrpcClient<F> {
|
||||||
Ok((subscribe_tx, response.into_inner()))
|
Ok((subscribe_tx, response.into_inner()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn subscribe_once(
|
pub async fn subscribe_once(
|
||||||
&mut self,
|
&mut self,
|
||||||
slots: HashMap<String, SubscribeRequestFilterSlots>,
|
slots: HashMap<String, SubscribeRequestFilterSlots>,
|
||||||
|
@ -141,6 +142,7 @@ impl<F: Interceptor> GeyserGrpcClient<F> {
|
||||||
blocks: HashMap<String, SubscribeRequestFilterBlocks>,
|
blocks: HashMap<String, SubscribeRequestFilterBlocks>,
|
||||||
blocks_meta: HashMap<String, SubscribeRequestFilterBlocksMeta>,
|
blocks_meta: HashMap<String, SubscribeRequestFilterBlocksMeta>,
|
||||||
commitment: Option<CommitmentLevel>,
|
commitment: Option<CommitmentLevel>,
|
||||||
|
accounts_data_slice: Vec<SubscribeRequestAccountsDataSlice>,
|
||||||
) -> GeyserGrpcClientResult<impl Stream<Item = Result<SubscribeUpdate, Status>>> {
|
) -> GeyserGrpcClientResult<impl Stream<Item = Result<SubscribeUpdate, Status>>> {
|
||||||
let (mut subscribe_tx, response) = self.subscribe().await?;
|
let (mut subscribe_tx, response) = self.subscribe().await?;
|
||||||
subscribe_tx
|
subscribe_tx
|
||||||
|
@ -151,6 +153,7 @@ impl<F: Interceptor> GeyserGrpcClient<F> {
|
||||||
blocks,
|
blocks,
|
||||||
blocks_meta,
|
blocks_meta,
|
||||||
commitment: commitment.map(|value| value as i32),
|
commitment: commitment.map(|value| value as i32),
|
||||||
|
accounts_data_slice,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
Ok(response)
|
Ok(response)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "yellowstone-grpc-geyser"
|
name = "yellowstone-grpc-geyser"
|
||||||
version = "0.8.2+solana.1.16.1"
|
version = "1.0.0+solana.1.16.1"
|
||||||
authors = ["Triton One"]
|
authors = ["Triton One"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Yellowstone gRPC Geyser Plugin"
|
description = "Yellowstone gRPC Geyser Plugin"
|
||||||
|
|
|
@ -11,10 +11,10 @@ use {
|
||||||
proto::{
|
proto::{
|
||||||
subscribe_request_filter_accounts_filter::Filter as AccountsFilterDataOneof,
|
subscribe_request_filter_accounts_filter::Filter as AccountsFilterDataOneof,
|
||||||
subscribe_request_filter_accounts_filter_memcmp::Data as AccountsFilterMemcmpOneof,
|
subscribe_request_filter_accounts_filter_memcmp::Data as AccountsFilterMemcmpOneof,
|
||||||
CommitmentLevel, SubscribeRequest, SubscribeRequestFilterAccounts,
|
CommitmentLevel, SubscribeRequest, SubscribeRequestAccountsDataSlice,
|
||||||
SubscribeRequestFilterAccountsFilter, SubscribeRequestFilterBlocks,
|
SubscribeRequestFilterAccounts, SubscribeRequestFilterAccountsFilter,
|
||||||
SubscribeRequestFilterBlocksMeta, SubscribeRequestFilterSlots,
|
SubscribeRequestFilterBlocks, SubscribeRequestFilterBlocksMeta,
|
||||||
SubscribeRequestFilterTransactions, SubscribeUpdate,
|
SubscribeRequestFilterSlots, SubscribeRequestFilterTransactions, SubscribeUpdate,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
base64::{engine::general_purpose::STANDARD as base64_engine, Engine},
|
base64::{engine::general_purpose::STANDARD as base64_engine, Engine},
|
||||||
|
@ -34,6 +34,7 @@ pub struct Filter {
|
||||||
blocks: FilterBlocks,
|
blocks: FilterBlocks,
|
||||||
blocks_meta: FilterBlocksMeta,
|
blocks_meta: FilterBlocksMeta,
|
||||||
commitment: CommitmentLevel,
|
commitment: CommitmentLevel,
|
||||||
|
accounts_data_slice: Vec<FilterAccountsDataSlice>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Filter {
|
impl Filter {
|
||||||
|
@ -45,6 +46,7 @@ impl Filter {
|
||||||
blocks: FilterBlocks::new(&config.blocks, &limit.blocks)?,
|
blocks: FilterBlocks::new(&config.blocks, &limit.blocks)?,
|
||||||
blocks_meta: FilterBlocksMeta::new(&config.blocks_meta, &limit.blocks_meta)?,
|
blocks_meta: FilterBlocksMeta::new(&config.blocks_meta, &limit.blocks_meta)?,
|
||||||
commitment: Self::decode_commitment(config.commitment)?,
|
commitment: Self::decode_commitment(config.commitment)?,
|
||||||
|
accounts_data_slice: FilterAccountsDataSlice::create(&config.accounts_data_slice)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +93,7 @@ impl Filter {
|
||||||
} else {
|
} else {
|
||||||
Some(SubscribeUpdate {
|
Some(SubscribeUpdate {
|
||||||
filters,
|
filters,
|
||||||
update_oneof: Some(message.into()),
|
update_oneof: Some(message.to_proto(&self.accounts_data_slice)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -538,6 +540,43 @@ impl FilterBlocksMeta {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct FilterAccountsDataSlice {
|
||||||
|
pub start: usize,
|
||||||
|
pub end: usize,
|
||||||
|
pub length: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&SubscribeRequestAccountsDataSlice> for FilterAccountsDataSlice {
|
||||||
|
fn from(data_slice: &SubscribeRequestAccountsDataSlice) -> Self {
|
||||||
|
Self {
|
||||||
|
start: data_slice.offset as usize,
|
||||||
|
end: (data_slice.offset + data_slice.length) as usize,
|
||||||
|
length: data_slice.length as usize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FilterAccountsDataSlice {
|
||||||
|
pub fn create(slices: &[SubscribeRequestAccountsDataSlice]) -> anyhow::Result<Vec<Self>> {
|
||||||
|
let slices = slices.iter().map(Into::into).collect::<Vec<Self>>();
|
||||||
|
|
||||||
|
for (i, slice_a) in slices.iter().enumerate() {
|
||||||
|
// check order
|
||||||
|
for slice_b in slices[i + 1..].iter() {
|
||||||
|
anyhow::ensure!(slice_a.start <= slice_b.start, "data slices out of order");
|
||||||
|
}
|
||||||
|
|
||||||
|
// check overlap
|
||||||
|
for slice_b in slices[0..i].iter() {
|
||||||
|
anyhow::ensure!(slice_a.start >= slice_b.end, "data slices overlap");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(slices)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use {
|
use {
|
||||||
|
@ -614,6 +653,7 @@ mod tests {
|
||||||
blocks: HashMap::new(),
|
blocks: HashMap::new(),
|
||||||
blocks_meta: HashMap::new(),
|
blocks_meta: HashMap::new(),
|
||||||
commitment: None,
|
commitment: None,
|
||||||
|
accounts_data_slice: Vec::new(),
|
||||||
};
|
};
|
||||||
let limit = ConfigGrpcFilters::default();
|
let limit = ConfigGrpcFilters::default();
|
||||||
let filter = Filter::new(&config, &limit);
|
let filter = Filter::new(&config, &limit);
|
||||||
|
@ -640,6 +680,7 @@ mod tests {
|
||||||
blocks: HashMap::new(),
|
blocks: HashMap::new(),
|
||||||
blocks_meta: HashMap::new(),
|
blocks_meta: HashMap::new(),
|
||||||
commitment: None,
|
commitment: None,
|
||||||
|
accounts_data_slice: Vec::new(),
|
||||||
};
|
};
|
||||||
let mut limit = ConfigGrpcFilters::default();
|
let mut limit = ConfigGrpcFilters::default();
|
||||||
limit.accounts.any = false;
|
limit.accounts.any = false;
|
||||||
|
@ -671,6 +712,7 @@ mod tests {
|
||||||
blocks: HashMap::new(),
|
blocks: HashMap::new(),
|
||||||
blocks_meta: HashMap::new(),
|
blocks_meta: HashMap::new(),
|
||||||
commitment: None,
|
commitment: None,
|
||||||
|
accounts_data_slice: Vec::new(),
|
||||||
};
|
};
|
||||||
let mut limit = ConfigGrpcFilters::default();
|
let mut limit = ConfigGrpcFilters::default();
|
||||||
limit.transactions.any = false;
|
limit.transactions.any = false;
|
||||||
|
@ -701,6 +743,7 @@ mod tests {
|
||||||
blocks: HashMap::new(),
|
blocks: HashMap::new(),
|
||||||
blocks_meta: HashMap::new(),
|
blocks_meta: HashMap::new(),
|
||||||
commitment: None,
|
commitment: None,
|
||||||
|
accounts_data_slice: Vec::new(),
|
||||||
};
|
};
|
||||||
let mut limit = ConfigGrpcFilters::default();
|
let mut limit = ConfigGrpcFilters::default();
|
||||||
limit.transactions.any = false;
|
limit.transactions.any = false;
|
||||||
|
@ -737,6 +780,7 @@ mod tests {
|
||||||
blocks: HashMap::new(),
|
blocks: HashMap::new(),
|
||||||
blocks_meta: HashMap::new(),
|
blocks_meta: HashMap::new(),
|
||||||
commitment: None,
|
commitment: None,
|
||||||
|
accounts_data_slice: Vec::new(),
|
||||||
};
|
};
|
||||||
let limit = ConfigGrpcFilters::default();
|
let limit = ConfigGrpcFilters::default();
|
||||||
let filter = Filter::new(&config, &limit).unwrap();
|
let filter = Filter::new(&config, &limit).unwrap();
|
||||||
|
@ -776,6 +820,7 @@ mod tests {
|
||||||
blocks: HashMap::new(),
|
blocks: HashMap::new(),
|
||||||
blocks_meta: HashMap::new(),
|
blocks_meta: HashMap::new(),
|
||||||
commitment: None,
|
commitment: None,
|
||||||
|
accounts_data_slice: Vec::new(),
|
||||||
};
|
};
|
||||||
let limit = ConfigGrpcFilters::default();
|
let limit = ConfigGrpcFilters::default();
|
||||||
let filter = Filter::new(&config, &limit).unwrap();
|
let filter = Filter::new(&config, &limit).unwrap();
|
||||||
|
@ -815,6 +860,7 @@ mod tests {
|
||||||
blocks: HashMap::new(),
|
blocks: HashMap::new(),
|
||||||
blocks_meta: HashMap::new(),
|
blocks_meta: HashMap::new(),
|
||||||
commitment: None,
|
commitment: None,
|
||||||
|
accounts_data_slice: Vec::new(),
|
||||||
};
|
};
|
||||||
let limit = ConfigGrpcFilters::default();
|
let limit = ConfigGrpcFilters::default();
|
||||||
let filter = Filter::new(&config, &limit).unwrap();
|
let filter = Filter::new(&config, &limit).unwrap();
|
||||||
|
@ -860,6 +906,7 @@ mod tests {
|
||||||
blocks: HashMap::new(),
|
blocks: HashMap::new(),
|
||||||
blocks_meta: HashMap::new(),
|
blocks_meta: HashMap::new(),
|
||||||
commitment: None,
|
commitment: None,
|
||||||
|
accounts_data_slice: Vec::new(),
|
||||||
};
|
};
|
||||||
let limit = ConfigGrpcFilters::default();
|
let limit = ConfigGrpcFilters::default();
|
||||||
let filter = Filter::new(&config, &limit).unwrap();
|
let filter = Filter::new(&config, &limit).unwrap();
|
||||||
|
@ -907,6 +954,7 @@ mod tests {
|
||||||
blocks: HashMap::new(),
|
blocks: HashMap::new(),
|
||||||
blocks_meta: HashMap::new(),
|
blocks_meta: HashMap::new(),
|
||||||
commitment: None,
|
commitment: None,
|
||||||
|
accounts_data_slice: Vec::new(),
|
||||||
};
|
};
|
||||||
let limit = ConfigGrpcFilters::default();
|
let limit = ConfigGrpcFilters::default();
|
||||||
let filter = Filter::new(&config, &limit).unwrap();
|
let filter = Filter::new(&config, &limit).unwrap();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
config::ConfigGrpc,
|
config::ConfigGrpc,
|
||||||
filters::Filter,
|
filters::{Filter, FilterAccountsDataSlice},
|
||||||
prom::{CONNECTIONS_TOTAL, INVALID_FULL_BLOCKS, MESSAGE_QUEUE_SIZE},
|
prom::{CONNECTIONS_TOTAL, INVALID_FULL_BLOCKS, MESSAGE_QUEUE_SIZE},
|
||||||
proto::{
|
proto::{
|
||||||
self,
|
self,
|
||||||
|
@ -213,33 +213,59 @@ pub enum Message {
|
||||||
BlockMeta(MessageBlockMeta),
|
BlockMeta(MessageBlockMeta),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Message> for UpdateOneof {
|
impl Message {
|
||||||
fn from(message: &Message) -> Self {
|
pub const fn get_slot(&self) -> u64 {
|
||||||
match message {
|
match self {
|
||||||
Message::Slot(message) => UpdateOneof::Slot(SubscribeUpdateSlot {
|
Self::Slot(msg) => msg.slot,
|
||||||
|
Self::Account(msg) => msg.slot,
|
||||||
|
Self::Transaction(msg) => msg.slot,
|
||||||
|
Self::Block(msg) => msg.slot,
|
||||||
|
Self::BlockMeta(msg) => msg.slot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_proto(&self, accounts_data_slice: &[FilterAccountsDataSlice]) -> UpdateOneof {
|
||||||
|
match self {
|
||||||
|
Self::Slot(message) => UpdateOneof::Slot(SubscribeUpdateSlot {
|
||||||
slot: message.slot,
|
slot: message.slot,
|
||||||
parent: message.parent,
|
parent: message.parent,
|
||||||
status: message.status as i32,
|
status: message.status as i32,
|
||||||
}),
|
}),
|
||||||
Message::Account(message) => UpdateOneof::Account(SubscribeUpdateAccount {
|
Self::Account(message) => {
|
||||||
|
let data = if accounts_data_slice.is_empty() {
|
||||||
|
message.account.data.clone()
|
||||||
|
} else {
|
||||||
|
let mut data =
|
||||||
|
Vec::with_capacity(accounts_data_slice.iter().map(|ds| ds.length).sum());
|
||||||
|
for data_slice in accounts_data_slice {
|
||||||
|
if message.account.data.len() >= data_slice.end {
|
||||||
|
data.extend_from_slice(
|
||||||
|
&message.account.data[data_slice.start..data_slice.end],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data
|
||||||
|
};
|
||||||
|
UpdateOneof::Account(SubscribeUpdateAccount {
|
||||||
account: Some(SubscribeUpdateAccountInfo {
|
account: Some(SubscribeUpdateAccountInfo {
|
||||||
pubkey: message.account.pubkey.as_ref().into(),
|
pubkey: message.account.pubkey.as_ref().into(),
|
||||||
lamports: message.account.lamports,
|
lamports: message.account.lamports,
|
||||||
owner: message.account.owner.as_ref().into(),
|
owner: message.account.owner.as_ref().into(),
|
||||||
executable: message.account.executable,
|
executable: message.account.executable,
|
||||||
rent_epoch: message.account.rent_epoch,
|
rent_epoch: message.account.rent_epoch,
|
||||||
data: message.account.data.clone(),
|
data,
|
||||||
write_version: message.account.write_version,
|
write_version: message.account.write_version,
|
||||||
txn_signature: message.account.txn_signature.map(|s| s.as_ref().into()),
|
txn_signature: message.account.txn_signature.map(|s| s.as_ref().into()),
|
||||||
}),
|
}),
|
||||||
slot: message.slot,
|
slot: message.slot,
|
||||||
is_startup: message.is_startup,
|
is_startup: message.is_startup,
|
||||||
}),
|
})
|
||||||
Message::Transaction(message) => UpdateOneof::Transaction(SubscribeUpdateTransaction {
|
}
|
||||||
|
Self::Transaction(message) => UpdateOneof::Transaction(SubscribeUpdateTransaction {
|
||||||
transaction: Some((&message.transaction).into()),
|
transaction: Some((&message.transaction).into()),
|
||||||
slot: message.slot,
|
slot: message.slot,
|
||||||
}),
|
}),
|
||||||
Message::Block(message) => UpdateOneof::Block(SubscribeUpdateBlock {
|
Self::Block(message) => UpdateOneof::Block(SubscribeUpdateBlock {
|
||||||
slot: message.slot,
|
slot: message.slot,
|
||||||
blockhash: message.blockhash.clone(),
|
blockhash: message.blockhash.clone(),
|
||||||
rewards: Some(proto::convert::create_rewards(message.rewards.as_slice())),
|
rewards: Some(proto::convert::create_rewards(message.rewards.as_slice())),
|
||||||
|
@ -251,7 +277,7 @@ impl From<&Message> for UpdateOneof {
|
||||||
parent_slot: message.parent_slot,
|
parent_slot: message.parent_slot,
|
||||||
parent_blockhash: message.parent_blockhash.clone(),
|
parent_blockhash: message.parent_blockhash.clone(),
|
||||||
}),
|
}),
|
||||||
Message::BlockMeta(message) => UpdateOneof::BlockMeta(SubscribeUpdateBlockMeta {
|
Self::BlockMeta(message) => UpdateOneof::BlockMeta(SubscribeUpdateBlockMeta {
|
||||||
slot: message.slot,
|
slot: message.slot,
|
||||||
blockhash: message.blockhash.clone(),
|
blockhash: message.blockhash.clone(),
|
||||||
rewards: Some(proto::convert::create_rewards(message.rewards.as_slice())),
|
rewards: Some(proto::convert::create_rewards(message.rewards.as_slice())),
|
||||||
|
@ -267,18 +293,6 @@ impl From<&Message> for UpdateOneof {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message {
|
|
||||||
pub const fn get_slot(&self) -> u64 {
|
|
||||||
match self {
|
|
||||||
Self::Slot(msg) => msg.slot,
|
|
||||||
Self::Account(msg) => msg.slot,
|
|
||||||
Self::Transaction(msg) => msg.slot,
|
|
||||||
Self::Block(msg) => msg.slot,
|
|
||||||
Self::BlockMeta(msg) => msg.slot,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct BlockhashStatus {
|
struct BlockhashStatus {
|
||||||
slot: u64,
|
slot: u64,
|
||||||
|
@ -752,6 +766,7 @@ impl Geyser for GrpcService {
|
||||||
blocks: HashMap::new(),
|
blocks: HashMap::new(),
|
||||||
blocks_meta: HashMap::new(),
|
blocks_meta: HashMap::new(),
|
||||||
commitment: None,
|
commitment: None,
|
||||||
|
accounts_data_slice: Vec::new(),
|
||||||
},
|
},
|
||||||
&self.config.filters,
|
&self.config.filters,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "yellowstone-grpc-proto"
|
name = "yellowstone-grpc-proto"
|
||||||
version = "1.3.0+solana.1.16.1"
|
version = "1.4.0+solana.1.16.1"
|
||||||
authors = ["Triton One"]
|
authors = ["Triton One"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Yellowstone gRPC Geyser Protobuf Definitions"
|
description = "Yellowstone gRPC Geyser Protobuf Definitions"
|
||||||
|
|
|
@ -29,6 +29,7 @@ message SubscribeRequest {
|
||||||
map<string, SubscribeRequestFilterBlocks> blocks = 4;
|
map<string, SubscribeRequestFilterBlocks> blocks = 4;
|
||||||
map<string, SubscribeRequestFilterBlocksMeta> blocks_meta = 5;
|
map<string, SubscribeRequestFilterBlocksMeta> blocks_meta = 5;
|
||||||
optional CommitmentLevel commitment = 6;
|
optional CommitmentLevel commitment = 6;
|
||||||
|
repeated SubscribeRequestAccountsDataSlice accounts_data_slice = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SubscribeRequestFilterAccounts {
|
message SubscribeRequestFilterAccounts {
|
||||||
|
@ -68,6 +69,11 @@ message SubscribeRequestFilterBlocks {}
|
||||||
|
|
||||||
message SubscribeRequestFilterBlocksMeta {}
|
message SubscribeRequestFilterBlocksMeta {}
|
||||||
|
|
||||||
|
message SubscribeRequestAccountsDataSlice {
|
||||||
|
uint64 offset = 1;
|
||||||
|
uint64 length = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message SubscribeUpdate {
|
message SubscribeUpdate {
|
||||||
repeated string filters = 1;
|
repeated string filters = 1;
|
||||||
oneof update_oneof {
|
oneof update_oneof {
|
||||||
|
|
Loading…
Reference in New Issue