From 309c24111736eb7103c9a0a89d4b1572c6975b50 Mon Sep 17 00:00:00 2001 From: 0xPause Date: Sat, 6 Apr 2024 23:58:25 +0800 Subject: [PATCH] Implement RLP native functions (#1527) --- Cargo.lock | 3 +- Cargo.toml | 2 +- crates/rooch-rpc-api/Cargo.toml | 1 - moveos/moveos-stdlib/Cargo.toml | 2 + moveos/moveos-stdlib/moveos-stdlib/doc/rlp.md | 27 ++- .../moveos-stdlib/sources/rlp.move | 7 +- .../moveos-stdlib/tests/rlp_tests.move | 104 ++++++++ .../src/natives/moveos_stdlib/rlp.rs | 229 ++++++++++++++++-- 8 files changed, 355 insertions(+), 20 deletions(-) create mode 100644 moveos/moveos-stdlib/moveos-stdlib/tests/rlp_tests.move diff --git a/Cargo.lock b/Cargo.lock index fccfb0b1a1..177000ebce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6103,6 +6103,8 @@ dependencies = [ "once_cell", "parking_lot 0.12.1", "petgraph 0.5.1", + "primitive-types 0.12.1", + "rlp", "serde 1.0.195", "serde_bytes", "serde_json", @@ -9043,7 +9045,6 @@ dependencies = [ "move-core-types", "move-resource-viewer", "moveos-types", - "open-fastrlp", "rand 0.8.5", "rooch-config", "rooch-open-rpc", diff --git a/Cargo.toml b/Cargo.toml index 3c4f43b859..871fbfa8e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -230,7 +230,7 @@ argon2 = "0.5.2" rpassword = "7.2.0" fixed-hash = "0.8.0" uint = "0.9.5" -open-fastrlp = "0.1.4" +rlp = "0.5.2" const-hex = "1.6.2" cached = "0.43.0" diesel = { version = "2.1.0", features = [ diff --git a/crates/rooch-rpc-api/Cargo.toml b/crates/rooch-rpc-api/Cargo.toml index ba04043f60..194ff5776c 100644 --- a/crates/rooch-rpc-api/Cargo.toml +++ b/crates/rooch-rpc-api/Cargo.toml @@ -38,7 +38,6 @@ bigdecimal = { workspace = true } fixed-hash = { workspace = true } uint = { workspace = true } bytes = { workspace = true } -open-fastrlp = { workspace = true } const-hex = { workspace = true } bitcoin = { workspace = true } bitcoincore-rpc = { workspace = true } diff --git a/moveos/moveos-stdlib/Cargo.toml b/moveos/moveos-stdlib/Cargo.toml index 44e31b53d3..8c9bfb90fc 100644 --- a/moveos/moveos-stdlib/Cargo.toml +++ b/moveos/moveos-stdlib/Cargo.toml @@ -30,6 +30,8 @@ petgraph = { workspace = true } parking_lot = { workspace = true } itertools = { workspace = true } ciborium = { workspace = true } +rlp = { workspace = true } +primitive-types = { workspace = true } move-binary-format = { workspace = true } move-bytecode-utils = { workspace = true } diff --git a/moveos/moveos-stdlib/moveos-stdlib/doc/rlp.md b/moveos/moveos-stdlib/moveos-stdlib/doc/rlp.md index 605fe70883..c224ab5edf 100644 --- a/moveos/moveos-stdlib/moveos-stdlib/doc/rlp.md +++ b/moveos/moveos-stdlib/moveos-stdlib/doc/rlp.md @@ -7,6 +7,7 @@ Utility for converting a Move value to its binary representation in RLP(Recursiv https://ethereum.org/nl/developers/docs/data-structures-and-encoding/rlp/ +- [Constants](#@Constants_0) - [Function `to_bytes`](#0x2_rlp_to_bytes) - [Function `from_bytes`](#0x2_rlp_from_bytes) @@ -15,6 +16,29 @@ https://ethereum.org/nl/developers/docs/data-structures-and-encoding/rlp/ + + +## Constants + + + + + + +
const ErrorRLPDeserializationFailure: u64 = 2;
+
+ + + + + + + +
const ErrorRLPSerializationFailure: u64 = 1;
+
+ + + ## Function `to_bytes` @@ -32,5 +56,6 @@ https://ethereum.org/nl/developers/docs/data-structures-and-encoding/rlp/ -
public(friend) fun from_bytes<MoveValue>(bytes: &vector<u8>): MoveValue
+
#[data_struct(#[MoveValue])]
+public fun from_bytes<MoveValue>(bytes: vector<u8>): MoveValue
 
diff --git a/moveos/moveos-stdlib/moveos-stdlib/sources/rlp.move b/moveos/moveos-stdlib/moveos-stdlib/sources/rlp.move index bb6a0e671b..393c82df5c 100644 --- a/moveos/moveos-stdlib/moveos-stdlib/sources/rlp.move +++ b/moveos/moveos-stdlib/moveos-stdlib/sources/rlp.move @@ -5,7 +5,12 @@ /// https://ethereum.org/nl/developers/docs/data-structures-and-encoding/rlp/ module moveos_std::rlp{ + const ErrorRLPSerializationFailure: u64 = 1; + const ErrorRLPDeserializationFailure: u64 = 2; + native public fun to_bytes(value: &MoveValue): vector; - public(friend) native fun from_bytes(bytes: &vector): MoveValue; + + #[data_struct(MoveValue)] + public native fun from_bytes(bytes: vector): MoveValue; } \ No newline at end of file diff --git a/moveos/moveos-stdlib/moveos-stdlib/tests/rlp_tests.move b/moveos/moveos-stdlib/moveos-stdlib/tests/rlp_tests.move new file mode 100644 index 0000000000..4f674391a8 --- /dev/null +++ b/moveos/moveos-stdlib/moveos-stdlib/tests/rlp_tests.move @@ -0,0 +1,104 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module moveos_std::rlp_tests { + use std::debug; + use std::vector; + use std::string::String; + use moveos_std::rlp; + + #[data_struct] + struct PrimaryStruct has copy, drop { + a: u8, + b: u16, + c: u32, + d: u64, + e: u128, + f: u256, + me: address, + } + + #[data_struct] + struct ChildStruct has copy, drop { + c: PrimaryStruct, + } + + #[data_struct] + struct NestedStruct has copy, drop { + child: ChildStruct, + v: vector, + } + + #[test] + fun test_basic() { + let bytes = rlp::to_bytes(&0u8); + assert!(bytes == x"80", 1); + + let bytes = rlp::to_bytes(&x"0400"); // 1024 + assert!(bytes == x"820400", 2); + + let bytes = rlp::to_bytes(&1024u64); + assert!(bytes == x"820400", 3); + + let bytes = rlp::to_bytes(&b"dog"); + assert!(bytes == x"83646f67", 4); + + let bytes = rlp::to_bytes(&std::string::utf8(b"dog")); + assert!(bytes == x"c483646f67", 5); + + let v = vector::empty(); + vector::push_back(&mut v, 1); + vector::push_back(&mut v, 2); + let bytes = rlp::to_bytes(&v); + assert!(bytes == x"c20102", 6); + + let bytes = rlp::to_bytes(&@0x42); + assert!(bytes == x"a00000000000000000000000000000000000000000000000000000000000000042", 7); + } + + fun primary_struct_instantiation(offset: u8): PrimaryStruct { + let v = vector::empty(); + vector::push_back(&mut v, 1); + vector::push_back(&mut v, 2); + PrimaryStruct { + a: 1 + offset, + b: 256, + c: 3 + (offset as u32), + d: 4 + (offset as u64), + e: 5 + (offset as u128), + f: 6 + (offset as u256), + me: @0x42 + } + } + + fun nested_struct_instantiation(): NestedStruct { + let v = vector::empty(); + vector::push_back(&mut v, primary_struct_instantiation(1)); + let child = ChildStruct { c: primary_struct_instantiation(2)}; + let data = NestedStruct { child: child, v: v }; + data + } + + #[test] + fun test_primary_struct() { + let data = primary_struct_instantiation(1); + let bytes = rlp::to_bytes(&data); + debug::print(&bytes); + let decoded_data = rlp::from_bytes(bytes); + assert!(decoded_data == data, 1); + } + + #[test] + fun test_nested_struct() { + let data = nested_struct_instantiation(); + let bytes = rlp::to_bytes(&data); + debug::print(&bytes); + let decoded_data = rlp::from_bytes(bytes); + assert!(decoded_data.child == data.child, 1); + assert!(vector::borrow(&decoded_data.v, 0) == vector::borrow(&data.v, 0), 2); + } +} \ No newline at end of file diff --git a/moveos/moveos-stdlib/src/natives/moveos_stdlib/rlp.rs b/moveos/moveos-stdlib/src/natives/moveos_stdlib/rlp.rs index 71ce9df752..39ded4c9db 100644 --- a/moveos/moveos-stdlib/src/natives/moveos_stdlib/rlp.rs +++ b/moveos/moveos-stdlib/src/natives/moveos_stdlib/rlp.rs @@ -1,36 +1,205 @@ // Copyright (c) RoochNetwork // SPDX-License-Identifier: Apache-2.0 -use move_binary_format::errors::PartialVMResult; -use move_core_types::gas_algebra::InternalGas; +use log::info; +use move_binary_format::errors::{PartialVMError, PartialVMResult}; +use move_core_types::account_address::AccountAddress; +use move_core_types::gas_algebra::{InternalGas, InternalGasPerByte, NumBytes}; +use move_core_types::u256::{self, U256_NUM_BYTES}; +use move_core_types::value::MoveTypeLayout; +use move_core_types::value::MoveValue; +use move_core_types::vm_status::StatusCode; use move_vm_runtime::native_functions::{NativeContext, NativeFunction}; use move_vm_types::{ - loaded_data::runtime_types::Type, natives::function::NativeResult, values::Value, + loaded_data::runtime_types::Type, + natives::function::NativeResult, + pop_arg, + values::{values_impl::Reference, Struct, Value}, }; +use primitive_types::U256 as PrimitiveU256; +use rlp::{self, Rlp, RlpStream}; +use smallvec::smallvec; use std::{collections::VecDeque, sync::Arc}; use crate::natives::helpers::make_module_natives; +const E_RLP_SERIALIZATION_FAILURE: u64 = 1; +const E_RLP_DESERIALIZATION_FAILURE: u64 = 2; + +struct MoveValueWrapper { + layout: MoveTypeLayout, + val: MoveValue, +} + +impl rlp::Encodable for MoveValueWrapper { + fn rlp_append(&self, s: &mut RlpStream) { + use MoveTypeLayout as L; + match (&self.layout, &self.val) { + (L::Struct(layout), MoveValue::Struct(struct_)) => { + let layout_fields = layout.fields(); + let value_fields = struct_.fields(); + s.begin_list(layout_fields.len()); + for (layout, value) in layout_fields.iter().zip(value_fields) { + s.append(&MoveValueWrapper { + layout: layout.clone(), + val: value.clone(), + }); + } + } + (L::Bool, MoveValue::Bool(b)) => b.rlp_append(s), + (L::U8, MoveValue::U8(i)) => i.rlp_append(s), + (L::U16, MoveValue::U16(i)) => i.rlp_append(s), + (L::U32, MoveValue::U32(i)) => i.rlp_append(s), + (L::U64, MoveValue::U64(i)) => i.rlp_append(s), + (L::U128, MoveValue::U128(i)) => i.rlp_append(s), + (L::U256, MoveValue::U256(i)) => { + let slice = i.to_le_bytes(); + let value = PrimitiveU256::from_little_endian(&slice); + let leading_empty_bytes = value.leading_zeros() as usize / 8; + let mut buffer = [0u8; U256_NUM_BYTES]; + value.to_big_endian(&mut buffer); + s.encoder().encode_value(&buffer[leading_empty_bytes..]); + } + (L::Address, MoveValue::Address(a)) => a.to_vec().rlp_append(s), + (L::Signer, MoveValue::Signer(a)) => a.to_vec().rlp_append(s), + (L::Vector(layout), MoveValue::Vector(v)) => { + let layout = &**layout; + match layout { + L::U8 => { + let mut bytes = Vec::with_capacity(v.len()); + + for byte in v { + match byte { + MoveValue::U8(u8) => { + bytes.push(*u8); + } + _ => unreachable!("This should not happen."), + } + } + bytes.rlp_append(s); + } + _ => { + s.begin_list(v.len()); + for val in v { + s.append(&MoveValueWrapper { + layout: layout.clone(), + val: val.clone(), + }); + } + } + } + } + _ => todo!(), + } + } +} + +fn decode_rlp(rlp: Rlp, layout: MoveTypeLayout) -> anyhow::Result { + let value = match layout { + MoveTypeLayout::Bool => Value::bool(rlp.as_val::()?), + MoveTypeLayout::U8 => Value::u8(rlp.as_val::()?), + MoveTypeLayout::U16 => Value::u16(rlp.as_val::()?), + MoveTypeLayout::U32 => Value::u32(rlp.as_val::()?), + MoveTypeLayout::U64 => Value::u64(rlp.as_val::()?), + MoveTypeLayout::U128 => Value::u128(rlp.as_val::()?), + MoveTypeLayout::U256 => { + let bytes = rlp.as_val::>()?; + let value = PrimitiveU256::from_big_endian(&bytes); + let mut buffer = [0u8; U256_NUM_BYTES]; + value.to_little_endian(&mut buffer); + Value::u256(u256::U256::from_le_bytes(&buffer)) + } + MoveTypeLayout::Address => { + let bytes = rlp.as_val::>()?; + AccountAddress::try_from(bytes).map(Value::address)? + } + MoveTypeLayout::Signer => { + let bytes = rlp.as_val::>()?; + AccountAddress::try_from(bytes).map(Value::signer)? + } + MoveTypeLayout::Struct(ty) => { + let mut fields = vec![]; + for (index, field_ty) in ty.into_fields().into_iter().enumerate() { + let val = decode_rlp(rlp.at(index)?, field_ty)?; + fields.push(val); + } + Value::struct_(Struct::pack(fields)) + } + MoveTypeLayout::Vector(layout) => { + match *layout.clone() { + MoveTypeLayout::U8 => { + let bytes = rlp.as_val::>()?; + Value::vector_u8(bytes) + } + _ => { + let count = rlp.item_count()?; + + let mut elements = vec![]; + for i in 0..count { + let val = decode_rlp(rlp.at(i)?, *layout.clone())?; + elements.push(val); + } + + // TODO: This API may break + Value::vector_for_testing_only(elements) + } + } + } + }; + Ok(value) +} + #[derive(Debug, Clone)] pub struct ToBytesGasParameters { pub base: InternalGas, + pub per_byte: InternalGasPerByte, } impl ToBytesGasParameters { pub fn zeros() -> Self { - Self { base: 0.into() } + Self { + base: 0.into(), + per_byte: 0.into(), + } } } /// Rust implementation of Move's `native public fun to_bytes(&T): vector in rlp module` #[inline] fn native_to_bytes( - _gas_params: &ToBytesGasParameters, - _context: &mut NativeContext, - mut _ty_args: Vec, - mut _args: VecDeque, + gas_params: &ToBytesGasParameters, + context: &mut NativeContext, + mut ty_args: Vec, + mut args: VecDeque, ) -> PartialVMResult { - todo!() + debug_assert!(ty_args.len() == 1); + debug_assert!(args.len() == 1); + + let mut cost = gas_params.base; + + // pop type and value + let ref_to_val = pop_arg!(args, Reference); + let arg_type = ty_args.pop().unwrap(); + + // get type layout + let layout = match context.type_to_type_layout(&arg_type)? { + Some(layout) => layout, + None => { + return Ok(NativeResult::err(cost, E_RLP_SERIALIZATION_FAILURE)); + } + }; + // serialize value + let val = ref_to_val.read_ref()?.as_move_value(&layout); + let serialized_value = rlp::encode(&MoveValueWrapper { layout, val }) + .into_iter() + .collect::>(); + + cost += gas_params.per_byte * NumBytes::new(serialized_value.len() as u64); + + Ok(NativeResult::ok( + cost, + smallvec![Value::vector_u8(serialized_value)], + )) } pub fn make_native_to_bytes(gas_params: ToBytesGasParameters) -> NativeFunction { Arc::new( @@ -43,23 +212,53 @@ pub fn make_native_to_bytes(gas_params: ToBytesGasParameters) -> NativeFunction #[derive(Debug, Clone)] pub struct FromBytesGasParameters { pub base: InternalGas, + pub per_byte: InternalGasPerByte, } impl FromBytesGasParameters { pub fn zeros() -> Self { - Self { base: 0.into() } + Self { + base: 0.into(), + per_byte: 0.into(), + } } } /// Rust implementation of Move's `native public(friend) fun from_bytes(vector): T in rlp module` #[inline] fn native_from_bytes( - _gas_params: &FromBytesGasParameters, - _context: &mut NativeContext, - mut _ty_args: Vec, - mut _args: VecDeque, + gas_params: &FromBytesGasParameters, + context: &mut NativeContext, + ty_args: Vec, + mut args: VecDeque, ) -> PartialVMResult { - todo!() + debug_assert_eq!(ty_args.len(), 1); + debug_assert_eq!(args.len(), 1); + + let mut cost = gas_params.base; + + // TODO(Gas): charge for getting the layout + let layout = context.type_to_type_layout(&ty_args[0])?.ok_or_else(|| { + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(format!( + "Failed to get layout of type {:?} -- this should not happen", + ty_args[0] + )) + })?; + + let bytes = pop_arg!(args, Vec); + cost += gas_params.per_byte * NumBytes::new(bytes.len() as u64); + + // let value = Value::vector_u8(bytes.clone()); + let rlp = Rlp::new(&bytes); + + let value = match decode_rlp(rlp, layout) { + Ok(val) => val, + Err(err) => { + info!("RLP deserialization error: {:?}", err); + return Ok(NativeResult::err(cost, E_RLP_DESERIALIZATION_FAILURE)); + } + }; + Ok(NativeResult::ok(cost, smallvec![value])) } pub fn make_native_from_bytes(gas_params: FromBytesGasParameters) -> NativeFunction {