Skip to content

Commit 018eb05

Browse files
committed
feat(lazer): add DynamicValue conversions, add source to GovernanceInstruction
1 parent 3867108 commit 018eb05

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

0 commit comments

Comments
 (0)