Skip to content

Commit 2762ee4

Browse files
committed
runtime: add yaml parsing support to wasm runtime
1 parent a3f3da7 commit 2762ee4

File tree

9 files changed

+244
-2
lines changed

9 files changed

+244
-2
lines changed

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

graph/src/runtime/gas/costs.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,10 @@ pub const JSON_FROM_BYTES: GasOp = GasOp {
8383
base_cost: DEFAULT_BASE_COST,
8484
size_mult: DEFAULT_GAS_PER_BYTE * 100,
8585
};
86+
87+
// Deeply nested YAML can take up more than 100 times the memory of the serialized format.
88+
// Multiplying the size cost by 100 accounts for this.
89+
pub const YAML_FROM_BYTES: GasOp = GasOp {
90+
base_cost: DEFAULT_BASE_COST,
91+
size_mult: DEFAULT_GAS_PER_BYTE * 100,
92+
};

graph/src/runtime/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,17 @@ pub enum IndexForAscTypeId {
371371
// Subgraph Data Source types
372372
AscEntityTrigger = 4500,
373373

374-
// Reserved discriminant space for a future blockchain type IDs: [4,500, 5,499]
374+
// Reserved discriminant space for YAML type IDs: [5,500, 6,499]
375+
YamlValue = 5500,
376+
YamlTaggedValue = 5501,
377+
YamlTypedMapEntryValueValue = 5502,
378+
YamlTypedMapValueValue = 5503,
379+
YamlArrayValue = 5504,
380+
YamlArrayTypedMapEntryValueValue = 5505,
381+
YamlWrappedValue = 5506,
382+
YamlResultValueBool = 5507,
383+
384+
// Reserved discriminant space for a future blockchain type IDs: [6,500, 7,499]
375385
//
376386
// Generated with the following shell script:
377387
//

runtime/wasm/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ wasm-instrument = { version = "0.2.0", features = ["std", "sign_ext"] }
2020

2121
# AssemblyScript uses sign extensions
2222
parity-wasm = { version = "0.45", features = ["std", "sign_ext"] }
23+
24+
serde_yaml = { workspace = true }

runtime/wasm/src/asc_abi/class.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,17 @@ impl AscIndexId for Array<AscPtr<AscBigDecimal>> {
398398
const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayBigDecimal;
399399
}
400400

401+
impl AscIndexId for Array<AscPtr<AscEnum<YamlValueKind>>> {
402+
const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::YamlArrayValue;
403+
}
404+
405+
impl AscIndexId
406+
for Array<AscPtr<AscTypedMapEntry<AscEnum<YamlValueKind>, AscEnum<YamlValueKind>>>>
407+
{
408+
const INDEX_ASC_TYPE_ID: IndexForAscTypeId =
409+
IndexForAscTypeId::YamlArrayTypedMapEntryValueValue;
410+
}
411+
401412
/// Represents any `AscValue` since they all fit in 64 bits.
402413
#[repr(C)]
403414
#[derive(Copy, Clone, Default)]
@@ -505,6 +516,10 @@ impl AscIndexId for AscEnum<JsonValueKind> {
505516
const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::JsonValue;
506517
}
507518

519+
impl AscIndexId for AscEnum<YamlValueKind> {
520+
const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::YamlValue;
521+
}
522+
508523
pub type AscEnumArray<D> = AscPtr<Array<AscPtr<AscEnum<D>>>>;
509524

510525
#[repr(u32)]
@@ -613,6 +628,10 @@ impl AscIndexId for AscTypedMapEntry<AscString, AscEnum<JsonValueKind>> {
613628
const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::TypedMapEntryStringJsonValue;
614629
}
615630

631+
impl AscIndexId for AscTypedMapEntry<AscEnum<YamlValueKind>, AscEnum<YamlValueKind>> {
632+
const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::YamlTypedMapEntryValueValue;
633+
}
634+
616635
pub(crate) type AscTypedMapEntryArray<K, V> = Array<AscPtr<AscTypedMapEntry<K, V>>>;
617636

618637
#[repr(C)]
@@ -638,6 +657,10 @@ impl AscIndexId for AscTypedMap<AscString, AscTypedMap<AscString, AscEnum<JsonVa
638657
IndexForAscTypeId::TypedMapStringTypedMapStringJsonValue;
639658
}
640659

660+
impl AscIndexId for AscTypedMap<AscEnum<YamlValueKind>, AscEnum<YamlValueKind>> {
661+
const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::YamlTypedMapValueValue;
662+
}
663+
641664
pub type AscEntity = AscTypedMap<AscString, AscEnum<StoreValueKind>>;
642665
pub(crate) type AscJson = AscTypedMap<AscString, AscEnum<JsonValueKind>>;
643666

@@ -725,6 +748,10 @@ impl AscIndexId for AscResult<AscPtr<AscEnum<JsonValueKind>>, bool> {
725748
const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ResultJsonValueBool;
726749
}
727750

751+
impl AscIndexId for AscResult<AscPtr<AscEnum<YamlValueKind>>, bool> {
752+
const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::YamlResultValueBool;
753+
}
754+
728755
#[repr(C)]
729756
#[derive(AscType, Copy, Clone)]
730757
pub struct AscWrapped<V: AscValue> {
@@ -742,3 +769,54 @@ impl AscIndexId for AscWrapped<bool> {
742769
impl AscIndexId for AscWrapped<AscPtr<AscEnum<JsonValueKind>>> {
743770
const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::WrappedJsonValue;
744771
}
772+
773+
impl AscIndexId for AscWrapped<AscPtr<AscEnum<YamlValueKind>>> {
774+
const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::YamlWrappedValue;
775+
}
776+
777+
#[repr(u32)]
778+
#[derive(AscType, Clone, Copy)]
779+
pub enum YamlValueKind {
780+
Null,
781+
Bool,
782+
Number,
783+
String,
784+
Array,
785+
Object,
786+
Tagged,
787+
}
788+
789+
impl Default for YamlValueKind {
790+
fn default() -> Self {
791+
YamlValueKind::Null
792+
}
793+
}
794+
795+
impl AscValue for YamlValueKind {}
796+
797+
impl YamlValueKind {
798+
pub(crate) fn get_kind(value: &serde_yaml::Value) -> Self {
799+
use serde_yaml::Value;
800+
801+
match value {
802+
Value::Null => Self::Null,
803+
Value::Bool(_) => Self::Bool,
804+
Value::Number(_) => Self::Number,
805+
Value::String(_) => Self::String,
806+
Value::Sequence(_) => Self::Array,
807+
Value::Mapping(_) => Self::Object,
808+
Value::Tagged(_) => Self::Tagged,
809+
}
810+
}
811+
}
812+
813+
#[repr(C)]
814+
#[derive(AscType)]
815+
pub struct AscYamlTaggedValue {
816+
pub tag: AscPtr<AscString>,
817+
pub value: AscPtr<AscEnum<YamlValueKind>>,
818+
}
819+
820+
impl AscIndexId for AscYamlTaggedValue {
821+
const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::YamlTaggedValue;
822+
}

runtime/wasm/src/host_exports.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,36 @@ impl HostExports {
12361236
.map(|mut tokens| tokens.pop().unwrap())
12371237
.context("Failed to decode")
12381238
}
1239+
1240+
pub(crate) fn yaml_from_bytes(
1241+
&self,
1242+
bytes: &[u8],
1243+
gas: &GasCounter,
1244+
state: &mut BlockState,
1245+
) -> Result<serde_yaml::Value, DeterministicHostError> {
1246+
const YAML_MAX_SIZE_BYTES: usize = 10_000_000;
1247+
1248+
Self::track_gas_and_ops(
1249+
gas,
1250+
state,
1251+
gas::YAML_FROM_BYTES.with_args(complexity::Size, bytes),
1252+
"yaml_from_bytes",
1253+
)?;
1254+
1255+
if bytes.len() > YAML_MAX_SIZE_BYTES {
1256+
return Err(DeterministicHostError::Other(
1257+
anyhow!(
1258+
"YAML size exceeds max size of {} bytes",
1259+
YAML_MAX_SIZE_BYTES
1260+
)
1261+
.into(),
1262+
));
1263+
}
1264+
1265+
serde_yaml::from_slice(bytes)
1266+
.context("failed to parse YAML from bytes")
1267+
.map_err(DeterministicHostError::from)
1268+
}
12391269
}
12401270

12411271
fn string_to_h160(string: &str) -> Result<H160, DeterministicHostError> {

runtime/wasm/src/module/context.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,4 +1188,64 @@ impl WasmInstanceContext<'_> {
11881188
"`box.profile` has been removed."
11891189
)))
11901190
}
1191+
1192+
/// function yaml.fromBytes(bytes: Bytes): YAMLValue
1193+
pub fn yaml_from_bytes(
1194+
&mut self,
1195+
gas: &GasCounter,
1196+
bytes_ptr: AscPtr<Uint8Array>,
1197+
) -> Result<AscPtr<AscEnum<YamlValueKind>>, HostExportError> {
1198+
let bytes: Vec<u8> = asc_get(self, bytes_ptr, gas)?;
1199+
let host_exports = self.as_ref().ctx.host_exports.cheap_clone();
1200+
let ctx = &mut self.as_mut().ctx;
1201+
1202+
let yaml_value = host_exports
1203+
.yaml_from_bytes(&bytes, gas, &mut ctx.state)
1204+
.inspect_err(|_| {
1205+
debug!(
1206+
&self.as_ref().ctx.logger,
1207+
"Failed to parse YAML from byte array";
1208+
"bytes" => truncate_yaml_bytes_for_logging(&bytes),
1209+
);
1210+
})?;
1211+
1212+
asc_new(self, &yaml_value, gas)
1213+
}
1214+
1215+
/// function yaml.try_fromBytes(bytes: Bytes): Result<YAMLValue, bool>
1216+
pub fn yaml_try_from_bytes(
1217+
&mut self,
1218+
gas: &GasCounter,
1219+
bytes_ptr: AscPtr<Uint8Array>,
1220+
) -> Result<AscPtr<AscResult<AscPtr<AscEnum<YamlValueKind>>, bool>>, HostExportError> {
1221+
let bytes: Vec<u8> = asc_get(self, bytes_ptr, gas)?;
1222+
let host_exports = self.as_ref().ctx.host_exports.cheap_clone();
1223+
let ctx = &mut self.as_mut().ctx;
1224+
1225+
let result = host_exports
1226+
.yaml_from_bytes(&bytes, gas, &mut ctx.state)
1227+
.map_err(|err| {
1228+
warn!(
1229+
&self.as_ref().ctx.logger,
1230+
"Failed to parse YAML from byte array";
1231+
"bytes" => truncate_yaml_bytes_for_logging(&bytes),
1232+
"error" => format!("{:#}", err),
1233+
);
1234+
1235+
true
1236+
});
1237+
1238+
asc_new(self, &result, gas)
1239+
}
1240+
}
1241+
1242+
/// For debugging, it might be useful to know exactly which bytes could not be parsed as YAML, but
1243+
/// since we can parse large YAML documents, even one bad mapping could produce terabytes of logs.
1244+
/// To avoid this, we only log the first 1024 bytes of the failed YAML source.
1245+
fn truncate_yaml_bytes_for_logging(bytes: &[u8]) -> String {
1246+
if bytes.len() > 1024 {
1247+
return format!("(truncated) 0x{}", hex::encode(&bytes[..1024]));
1248+
}
1249+
1250+
format!("0x{}", hex::encode(bytes))
11911251
}

runtime/wasm/src/module/instance.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,9 @@ impl WasmInstance {
468468
link!("json.toF64", json_to_f64, ptr);
469469
link!("json.toBigInt", json_to_big_int, ptr);
470470

471+
link!("yaml.fromBytes", yaml_from_bytes, ptr);
472+
link!("yaml.try_fromBytes", yaml_try_from_bytes, ptr);
473+
471474
link!("crypto.keccak256", crypto_keccak_256, ptr);
472475

473476
link!("bigInt.plus", big_int_plus, x_ptr, y_ptr);

runtime/wasm/src/to_from/external.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,3 +506,54 @@ impl ToAscObj<AscEntityTrigger> for EntitySourceOperation {
506506
impl AscIndexId for AscEntityTrigger {
507507
const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::AscEntityTrigger;
508508
}
509+
510+
impl ToAscObj<AscEnum<YamlValueKind>> for serde_yaml::Value {
511+
fn to_asc_obj<H: AscHeap + ?Sized>(
512+
&self,
513+
heap: &mut H,
514+
gas: &GasCounter,
515+
) -> Result<AscEnum<YamlValueKind>, HostExportError> {
516+
use serde_yaml::Value;
517+
518+
let payload = match self {
519+
Value::Null => EnumPayload(0),
520+
Value::Bool(val) => EnumPayload::from(*val),
521+
Value::Number(val) => asc_new(heap, &val.to_string(), gas)?.into(),
522+
Value::String(val) => asc_new(heap, val, gas)?.into(),
523+
Value::Sequence(val) => asc_new(heap, val.as_slice(), gas)?.into(),
524+
Value::Mapping(val) => asc_new(heap, val, gas)?.into(),
525+
Value::Tagged(val) => asc_new(heap, val.as_ref(), gas)?.into(),
526+
};
527+
528+
Ok(AscEnum {
529+
kind: YamlValueKind::get_kind(self),
530+
_padding: 0,
531+
payload,
532+
})
533+
}
534+
}
535+
536+
impl ToAscObj<AscTypedMap<AscEnum<YamlValueKind>, AscEnum<YamlValueKind>>> for serde_yaml::Mapping {
537+
fn to_asc_obj<H: AscHeap + ?Sized>(
538+
&self,
539+
heap: &mut H,
540+
gas: &GasCounter,
541+
) -> Result<AscTypedMap<AscEnum<YamlValueKind>, AscEnum<YamlValueKind>>, HostExportError> {
542+
Ok(AscTypedMap {
543+
entries: asc_new(heap, &*self.iter().collect::<Vec<_>>(), gas)?,
544+
})
545+
}
546+
}
547+
548+
impl ToAscObj<AscYamlTaggedValue> for serde_yaml::value::TaggedValue {
549+
fn to_asc_obj<H: AscHeap + ?Sized>(
550+
&self,
551+
heap: &mut H,
552+
gas: &GasCounter,
553+
) -> Result<AscYamlTaggedValue, HostExportError> {
554+
Ok(AscYamlTaggedValue {
555+
tag: asc_new(heap, &self.tag.to_string(), gas)?,
556+
value: asc_new(heap, &self.value, gas)?,
557+
})
558+
}
559+
}

0 commit comments

Comments
 (0)