Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pyth-lazer-protocol): Implement Protobuf files for Lazer Transactions #2557

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
89 changes: 88 additions & 1 deletion lazer/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions lazer/sdk/js/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ node_modules/
dist/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These SDKs are for end-users, so I think it will be confusing to them if we have publisher-related code in the same package. Maybe it's better to create a separate set of packages for publishers under lazer/publisher_sdk/{js, rust}?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh good point. I will make a new set of packages for them then. The index.ts example for it can include generating a payload, signing it, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll do it in a separate PR when we are closer to wanting publishers to work with the agent. We have types for Publishers already defined in the rust protocol.


tsconfig.tsbuildinfo

# Types generated for .proto files
src/generated/*
12 changes: 12 additions & 0 deletions lazer/sdk/js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,15 @@
## Contributing & Development

See [contributing.md](docs/contributing/contributing.md) for information on how to develop or contribute to this project!

## Installation and build

### pnpm

```
cd to crosschain root
$ pnpm install
$ pnpm turbo --filter @pythnetwork/pyth-lazer-sdk build
```

As part of the build, files will be generated from the proto files found in the lazer/proto folder. These generated files are placed in the src/generated/ folder.
2 changes: 1 addition & 1 deletion lazer/sdk/js/examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { PythLazerClient } from "../src/index.js";

// Ignore debug messages
console.debug = () => {};
console.debug = () => { };

const client = await PythLazerClient.create(
["wss://pyth-lazer.dourolabs.app/v1/stream"],
Expand Down
4 changes: 4 additions & 0 deletions lazer/sdk/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
}
},
"scripts": {
"build:proto-codegen": "mkdir -p src/generated && pnpm exec pbjs -t static-module -w es6 -o src/generated/proto.js ../proto/*.proto && pnpm exec pbts -o src/generated/proto.d.ts src/generated/proto.js",
"build:cjs": "tsc --project tsconfig.build.json --verbatimModuleSyntax false --module commonjs --outDir ./dist/cjs && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
"build:esm": "tsc --project tsconfig.build.json --outDir ./dist/esm && echo '{\"type\":\"module\"}' > dist/esm/package.json",
"fix:lint": "eslint --fix . --max-warnings 0",
Expand All @@ -30,6 +31,7 @@
"fix:format": "prettier --write .",
"example": "node --loader ts-node/esm examples/index.js",
"doc": "typedoc --out docs/typedoc src",
"prepublishOnly": "pnpm run build",
"publish": "pnpm run script -- publish"
},
"devDependencies": {
Expand All @@ -39,6 +41,8 @@
"@types/ws": "^8.5.12",
"eslint": "catalog:",
"prettier": "catalog:",
"protobufjs": "^7.4.0",
"protobufjs-cli": "^1.1.3",
"ts-node": "catalog:",
"typedoc": "^0.26.8",
"typescript": "catalog:"
Expand Down
24 changes: 24 additions & 0 deletions lazer/sdk/js/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"$schema": "https://turbo.build/schema.json",
"extends": ["//"],
"tasks": {
"build:proto-codegen": {
"dependsOn": ["//#install:modules"],
"inputs": ["../proto/**"],
"outputs": ["src/generated/**"],
"cache": false
},
"build": {
"dependsOn": ["build:proto-codegen", "^build"],
"outputs": ["dist/**"]
},
"build:cjs": {
"dependsOn": ["build:proto-codegen"],
"outputs": ["dist/cjs/**"]
},
"build:esm": {
"dependsOn": ["build:proto-codegen"],
"outputs": ["dist/esm/**"]
}
}
}
55 changes: 55 additions & 0 deletions lazer/sdk/proto/publisher_update.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
syntax = "proto3";

import "google/protobuf/timestamp.proto";

package pyth_lazer_transaction;

// PublisherUpdate contains an array of individual updates and a timestamp
message PublisherUpdate {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need publisher_id field here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that publisher ID would be something only Relayer knows about. The ID would map to an access token and a public key. The access token used to connect to Relayer determines what public key is accepted, and that key is used to verify the signature. In that scenario, publishers would not know/care what ID they are, and would not include it in any messages. Only the signature.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to use this format in the kafka/nats messages and read them in the aggregator (and add governance instructions as another type of transaction along with PublisherUpdate). Aggregator won't know access tokens so it needs another way to know the publisher id.

// Array of updates, each of which target a single feed
repeated FeedUpdate updates = 1;

// Timestamp when this message was created
optional google.protobuf.Timestamp publisher_timestamp = 2;
}

// Update to a feed. May contain different types of data depending on what kind of update it is
message FeedUpdate {
// Feed which the update should be applied to
// Should match a feed id recognized by PythLazer
optional uint32 feed_id = 1;

// Timestamp when this data was first acquired or generated
optional google.protobuf.Timestamp source_timestamp = 2;

// one of the valid updates allowed by publishers for a lazer feed
oneof update {
PriceUpdate price_update = 3;
FundingRateUpdate funding_rate_update = 4;
};
}

message PriceUpdate {
// Price for the symbol as an integer
// Should be produced with a matching exponent to the configured exponent value in PythLazer
// May be missing if no price data is available
optional int64 price = 1;

// Best Bid Price for the symbol as an integer
// Should be produced with a matching exponent to the configured exponent value in PythLazer
// May be missing if no data is available
optional int64 best_bid_price = 2;

// Best Ask Price for the symbol as an integer
// Should be produced with a matching exponent to the configured exponent value in PythLazer
// May be missing if no data is available
optional int64 best_ask_price = 3;
}

message FundingRateUpdate {
// Price for which the funding rate applies to
optional int64 price = 1;

// Perpetual Future funding rate
optional int64 rate = 2;
}
34 changes: 34 additions & 0 deletions lazer/sdk/proto/pyth_lazer_transaction.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
syntax = "proto3";

package pyth_lazer_transaction;

import "publisher_update.proto";

// Types of Signatures allowed for signing Lazer Transactions
enum TransactionSignatureType {
// signature is 64 bytes long
ed25519 = 0;
}

// Signed lazer transaction payload
// This is what Pyth Lazer expects as input to the system
message SignedLazerTransaction {
// Type and signature should match
optional TransactionSignatureType signature_type = 1;

// Signature derived by signing payload with private key
optional bytes signature = 2;

// a LazerTransaction message which is already encoded with protobuf as bytes
// The encoded bytes are what should be signed
optional bytes payload = 3;
}

// Transaction contianing one of the valid Lazer Transactions
message LazerTransaction {
oneof payload {
// Expected transaction sent by Publishers
// May contain many individual updates to various feeds
PublisherUpdate publisher_update = 1;
}
}
8 changes: 7 additions & 1 deletion lazer/sdk/rust/protocol/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
[package]
name = "pyth-lazer-protocol"
version = "0.7.0"
version = "0.7.1"
edition = "2021"
description = "Pyth Lazer SDK - protocol types."
license = "Apache-2.0"
repository = "https://github.com/pyth-network/pyth-crosschain"
build = "build.rs"

[dependencies]
byteorder = "1.5.0"
Expand All @@ -15,6 +16,8 @@ derive_more = { version = "1.0.0", features = ["from"] }
itertools = "0.13.0"
rust_decimal = "1.36.0"
base64 = "0.22.1"
prost = "0.13.5"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered using protobuf and protobuf-codegen? I think I'm going to try it for snapshots and see how it works. I think it can be a bit better because it has better typing in the generted code (especially for enums) and doesn't require protoc to be installed. But at this point I'm not sure which one is better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that's an interesting suggestion. I'll give that a go and compare the generated code. Thanks!

prost-types = "0.13.5"

[dev-dependencies]
bincode = "1.3.3"
Expand All @@ -23,3 +26,6 @@ hex = "0.4.3"
libsecp256k1 = "0.7.1"
bs58 = "0.5.1"
alloy-primitives = "0.8.19"

[build-dependencies]
prost-build = "0.13.5"
22 changes: 22 additions & 0 deletions lazer/sdk/rust/protocol/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use std::io::Result;

/// Automatically runs during cargo build.
/// Proto files for Lazer are defined in the lazer sdk folder in the proto/ subdirectory.
/// Both JS and Rust SDKs read the proto files for generating types.
fn main() -> Result<()> {
// Tell cargo to recompile if any .proto files change
println!("cargo:rerun-if-changed=proto/");

// Selects proto files to be read for compiling
let proto_files = vec![
"../../proto/pyth_lazer_transaction.proto",
"../../proto/publisher_update.proto",
];

// Compiles protos and generates Rust types
// Generated types are present in the output folder
prost_build::compile_protos(&proto_files, &["../../proto"])
.expect("Failed to compile protos and generate types");

Ok(())
}
3 changes: 3 additions & 0 deletions lazer/sdk/rust/protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ mod serde_price_as_i64;
mod serde_str;
pub mod subscription;
pub mod symbol_state;
pub mod transaction {
include!(concat!(env!("OUT_DIR"), "/pyth_lazer_transaction.rs"));
}

#[test]
fn magics_in_big_endian() {
Expand Down
Loading
Loading