Skip to content

Commit 5788fdc

Browse files
committed
feat(lazer): add DynamicValue conversions, add source to GovernanceInstruction
1 parent f9bc4ac commit 5788fdc

File tree

4 files changed

+204
-26
lines changed

4 files changed

+204
-26
lines changed

lazer/Cargo.lock

Lines changed: 45 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lazer/publisher_sdk/proto/governance_instruction.proto

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,21 @@ package pyth_lazer_transaction;
1414
// Representation of a complete governance instruction. This value will be signed
1515
// by a governance source.
1616
message GovernanceInstruction {
17+
// [required] Governance source that signed this instruction.
18+
optional GovernanceSource source = 1;
1719
// Action requested by this instruction. For the instruction to be accepted, all directives
1820
// must be successfully applied. In case of any failure, the whole instruction is reverted.
1921
// However, note that if the instruction targets multiple (or all) shards, each shard will
2022
// accept or reject the instruction independently of other shards.
21-
repeated GovernanceDirective directives = 1;
23+
repeated GovernanceDirective directives = 2;
2224
// [optional] If specified, the instruction will be rejected if the current timestamp
2325
// is less than the specified value. In case of rejection, the same instruction can be resubmitted
2426
// and executed later once the time requirement is met.
25-
optional google.protobuf.Timestamp min_execution_timestamp = 2;
27+
optional google.protobuf.Timestamp min_execution_timestamp = 3;
2628
// [optional] If specified, the instruction will be rejected if the current timestamp
2729
// is greater than the specified value. After `max_execution_timestamp` is in the past,
2830
// it will no longer be possible to execute this instruction.
29-
optional google.protobuf.Timestamp max_execution_timestamp = 3;
31+
optional google.protobuf.Timestamp max_execution_timestamp = 4;
3032
// [required] Sequence number of this instruction. It must be greater than 0.
3133
// It must always be increasing, but not required to be
3234
// strictly sequential (i.e. gaps are allowed). Each shard separately keeps track of the last executed
@@ -35,7 +37,7 @@ message GovernanceInstruction {
3537
// rejected (e.g. if instruction #3 has been successfully processed before instruction #2 was observed,
3638
// #2 will always be rejected).
3739
// Sequence numbers are assigned and tracked separately for each governance source.
38-
optional uint32 governance_sequence_no = 4;
40+
optional uint32 governance_sequence_no = 5;
3941
}
4042

4143
// Specifies which shards the governance instruction applies to.
@@ -160,7 +162,12 @@ message GovernanceSource {
160162
}
161163
}
162164

163-
// Create a new shard. Shard name will be determined by the value of `GovernanceDirective.filter`.
165+
// Create a new shard. A shard is a partially isolated part of Lazer that has its own state and
166+
// cannot be directly influenced by other shards. The main purpose of shards in Lazer is
167+
// to allow horizontal scaling when the number of feeds grows. Feeds can be divided into subsets
168+
// and each subset will be assigned to a shard.
169+
//
170+
// Shard name will be determined by the value of `GovernanceDirective.filter`.
164171
// This action will be rejected unless `GovernanceDirective.filter` specified a single shard.
165172
// Shard name must be unique across all shards in all groups.
166173
// (Warning: it's not possible to enforce this rule within a shard!)

lazer/publisher_sdk/rust/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ license = "Apache-2.0"
77
repository = "https://github.com/pyth-network/pyth-crosschain"
88

99
[dependencies]
10+
pyth-lazer-protocol = { version = "0.7.2", path = "../../sdk/rust/protocol" }
11+
anyhow = "1.0.98"
1012
protobuf = "3.7.2"
13+
serde-value = "0.7.0"
14+
humantime = "2.2.0"
15+
tracing = "0.1.41"
1116

1217
[build-dependencies]
1318
fs-err = "3.1.0"

lazer/publisher_sdk/rust/src/lib.rs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
use std::{collections::BTreeMap, time::Duration};
2+
3+
use ::protobuf::MessageField;
4+
use anyhow::{bail, ensure, Context};
5+
use humantime::format_duration;
6+
use protobuf::dynamic_value::{dynamic_value, DynamicValue};
7+
use pyth_lazer_protocol::router::TimestampUs;
8+
19
pub mod transaction {
210
pub use crate::protobuf::pyth_lazer_transaction::*;
311
}
@@ -13,3 +21,137 @@ pub mod governance_instruction {
1321
mod protobuf {
1422
include!(concat!(env!("OUT_DIR"), "/protobuf/mod.rs"));
1523
}
24+
25+
impl DynamicValue {
26+
pub fn try_option_from_serde(value: serde_value::Value) -> anyhow::Result<Option<Self>> {
27+
match value {
28+
serde_value::Value::Option(value) => {
29+
if let Some(value) = value {
30+
Ok(Some((*value).try_into()?))
31+
} else {
32+
Ok(None)
33+
}
34+
}
35+
value => Ok(Some(value.try_into()?)),
36+
}
37+
}
38+
39+
pub fn to_timestamp(&self) -> anyhow::Result<TimestampUs> {
40+
let value = self.value.as_ref().context("missing DynamicValue.value")?;
41+
match value {
42+
dynamic_value::Value::TimestampValue(ts) => Ok(ts.try_into()?),
43+
_ => bail!("expected timestamp, got {:?}", self),
44+
}
45+
}
46+
47+
pub fn to_duration(&self) -> anyhow::Result<Duration> {
48+
let value = self.value.as_ref().context("missing DynamicValue.value")?;
49+
match value {
50+
dynamic_value::Value::DurationValue(duration) => Ok(duration.clone().into()),
51+
_ => bail!("expected duration, got {:?}", self),
52+
}
53+
}
54+
}
55+
56+
impl TryFrom<serde_value::Value> for DynamicValue {
57+
type Error = anyhow::Error;
58+
59+
fn try_from(value: serde_value::Value) -> Result<Self, Self::Error> {
60+
let converted = match value {
61+
serde_value::Value::Bool(value) => dynamic_value::Value::BoolValue(value),
62+
serde_value::Value::U8(value) => dynamic_value::Value::UintValue(value.into()),
63+
serde_value::Value::U16(value) => dynamic_value::Value::UintValue(value.into()),
64+
serde_value::Value::U32(value) => dynamic_value::Value::UintValue(value.into()),
65+
serde_value::Value::U64(value) => dynamic_value::Value::UintValue(value),
66+
serde_value::Value::I8(value) => dynamic_value::Value::IntValue(value.into()),
67+
serde_value::Value::I16(value) => dynamic_value::Value::IntValue(value.into()),
68+
serde_value::Value::I32(value) => dynamic_value::Value::IntValue(value.into()),
69+
serde_value::Value::I64(value) => dynamic_value::Value::IntValue(value),
70+
serde_value::Value::F32(value) => dynamic_value::Value::DoubleValue(value.into()),
71+
serde_value::Value::F64(value) => dynamic_value::Value::DoubleValue(value),
72+
serde_value::Value::Char(value) => dynamic_value::Value::StringValue(value.to_string()),
73+
serde_value::Value::String(value) => dynamic_value::Value::StringValue(value),
74+
serde_value::Value::Bytes(value) => dynamic_value::Value::BytesValue(value),
75+
serde_value::Value::Seq(values) => {
76+
let mut items = Vec::new();
77+
for value in values {
78+
items.push(value.try_into()?);
79+
}
80+
dynamic_value::Value::List(dynamic_value::List {
81+
items,
82+
special_fields: Default::default(),
83+
})
84+
}
85+
serde_value::Value::Map(values) => {
86+
let mut items = Vec::new();
87+
for (key, value) in values {
88+
let key = match key {
89+
serde_value::Value::String(key) => key,
90+
_ => bail!("unsupported key type: expected string, got {:?}", key),
91+
};
92+
items.push(dynamic_value::MapItem {
93+
key: Some(key),
94+
value: MessageField::some(value.try_into()?),
95+
special_fields: Default::default(),
96+
})
97+
}
98+
dynamic_value::Value::Map(dynamic_value::Map {
99+
items,
100+
special_fields: Default::default(),
101+
})
102+
}
103+
serde_value::Value::Unit
104+
| serde_value::Value::Option(_)
105+
| serde_value::Value::Newtype(_) => bail!("unsupported type: {:?}", value),
106+
};
107+
Ok(DynamicValue {
108+
value: Some(converted),
109+
special_fields: Default::default(),
110+
})
111+
}
112+
}
113+
114+
impl TryFrom<DynamicValue> for serde_value::Value {
115+
type Error = anyhow::Error;
116+
117+
fn try_from(value: DynamicValue) -> Result<Self, Self::Error> {
118+
let value = value.value.context("missing DynamicValue.value")?;
119+
match value {
120+
dynamic_value::Value::StringValue(value) => Ok(serde_value::Value::String(value)),
121+
dynamic_value::Value::DoubleValue(value) => Ok(serde_value::Value::F64(value)),
122+
dynamic_value::Value::UintValue(value) => Ok(serde_value::Value::U64(value)),
123+
dynamic_value::Value::IntValue(value) => Ok(serde_value::Value::I64(value)),
124+
dynamic_value::Value::BoolValue(value) => Ok(serde_value::Value::Bool(value)),
125+
dynamic_value::Value::BytesValue(value) => Ok(serde_value::Value::Bytes(value)),
126+
dynamic_value::Value::DurationValue(duration) => {
127+
let s: Duration = duration.into();
128+
Ok(serde_value::Value::String(format_duration(s).to_string()))
129+
}
130+
dynamic_value::Value::TimestampValue(ts) => {
131+
let ts = TimestampUs::try_from(&ts)?;
132+
Ok(serde_value::Value::U64(ts.0))
133+
}
134+
dynamic_value::Value::List(list) => {
135+
let mut output = Vec::new();
136+
for item in list.items {
137+
output.push(item.try_into()?);
138+
}
139+
Ok(serde_value::Value::Seq(output).into())
140+
}
141+
dynamic_value::Value::Map(map) => {
142+
let mut output = BTreeMap::new();
143+
for item in map.items {
144+
let key = item.key.context("missing DynamicValue.MapItem.key")?;
145+
let value = item
146+
.value
147+
.into_option()
148+
.context("missing DynamicValue.MapItem.value")?
149+
.try_into()?;
150+
let old = output.insert(serde_value::Value::String(key), value);
151+
ensure!(old.is_none(), "duplicate DynamicValue.MapItem.key");
152+
}
153+
Ok(serde_value::Value::Map(output).into())
154+
}
155+
}
156+
}
157+
}

0 commit comments

Comments
 (0)