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

[genesis] add logic for extracting genesis modules for FastX framework #72

Merged
merged 1 commit into from
Dec 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fastpay_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ serde = { version = "1.0.115", features = ["derive"] }
tokio = { version = "0.2.22", features = ["full"] }

fastx-adapter = { path = "../fastx_programmability/adapter" }
fastx-framework = { path = "../fastx_programmability/framework" }
fastx-types = { path = "../fastx_types" }

move-binary-format = { git = "https://github.com/diem/diem", rev="5572eae5e35407f10316b473f95cb155a5e8d40b" }
Expand Down
16 changes: 8 additions & 8 deletions fastpay_core/src/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,24 +249,24 @@ impl Authority for AuthorityState {
.map_err(|_| FastPayError::MoveExecutionFailure)?;
}
OrderKind::Publish(m) => {
// Fake the gas payment
Copy link
Collaborator Author

@sblackshear sblackshear Dec 17, 2021

Choose a reason for hiding this comment

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

We want to charge for gas here--otherwise, a failed publish attempt is free.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we need to reflect on what failed means: there is a transaction that just fails because it is invalid, versus one that fails but costs are charged. Right I feel we do not make enough of a difference between these cases.

Copy link
Collaborator Author

@sblackshear sblackshear Dec 21, 2021

Choose a reason for hiding this comment

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

Yes, agreed that we need consistent definitions/terminology here. Some attempts, eager to hear your thoughts:

  • An invalid order will not be signed by any honest authority
  • An invalid certificate will not be processed by any honest authority (i.e., will have no effect on the persistent state of that authority)
  • A valid order will be signed by any honest authority, and thus will eventually be signed by enough honest authorities to form a certificate (under the usual assumptions)
  • A valid, but failed certificate will be processed by any honest authority and change its persistent state only to (1) charge for gas, and update the certificate store. Non-gas inputs will still be spendable by future orders
  • A valid, but successful certificate will be processed by any honest authority and change its persistent state to charge for gas, update the certificate store, and reflect the effects of executing the order "payload" (i.e., a module publish, move call, or transfer). All inputs are consumed and cannot be spent by future orders.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Some reasons (full list in overleaf spec) for invalid orders include:

  • Inputs not in LockMap
  • Gas fee input is insufficient to pay for the cost of processing the transaction
  • Invalid signature from sender

Some reasons for invalid certificates include:

  • Invalid signatures from authorities
  • The order embedded in the certificate is invalid (should only happen due to dishonest authorities or bugs)

Some reason for failed certificate processing include (this is the biggest category):

  • Function pointer in a call transaction does not correspond to a function published on-chain
  • Input object types do not match the type signature of the function invoked
  • Executing the function exceeded its gas budget
  • Executing the function encountered a Move abort

let mut gas_object = temporary_store
.read_object(&object_id)
.expect("Checked existence at start of function.");
gas_object.next_sequence_number = gas_object.next_sequence_number.increment()?;
temporary_store.write_object(gas_object);
// TODO(https://github.com/MystenLabs/fastnft/issues/45): charge for gas
let sender = m.sender.to_address_hack();
match adapter::publish(&mut temporary_store, m.modules, &sender, &mut tx_ctx) {
Ok(outputs) => {
// Fake the gas payment
let mut gas_object = temporary_store
.read_object(&object_id)
.expect("Checked existance at start of function.");
gas_object.next_sequence_number =
gas_object.next_sequence_number.increment()?;
temporary_store.write_object(gas_object);

// TODO: AccountInfoResponse should return all object ID outputs.
// but for now it only returns one, so use this hack
object_id = outputs[0].0;
}
Err(_e) => {
println!("failure during publishing: {:?}", _e);
// TODO: return this error to the client
object_id = m.gas_payment.0;
}
}
}
Expand Down
29 changes: 29 additions & 0 deletions fastpay_core/src/genesis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Mysten Labs
// SPDX-License-Identifier: Apache-2.0

use anyhow::Result;
use fastx_adapter::adapter;
use fastx_framework::{self};
use fastx_types::{
base_types::{PublicKeyBytes, SequenceNumber, TransactionDigest, TxContext},
object::Object,
FASTX_FRAMEWORK_ADDRESS,
};

/// Create and return objects wrapping the genesis modules for fastX
pub fn create_genesis_module_objects() -> Result<Vec<Object>> {
let mut tx_context = TxContext::new(TransactionDigest::genesis());
let mut modules = fastx_framework::get_framework_modules()?;
adapter::generate_module_ids(&mut modules, &mut tx_context)?;
let module_objects = modules
.into_iter()
.map(|m| {
Object::new_module(
m,
PublicKeyBytes::from_move_address_hack(&FASTX_FRAMEWORK_ADDRESS),
SequenceNumber::new(),
)
})
.collect();
Ok(module_objects)
}
1 change: 1 addition & 0 deletions fastpay_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ pub mod authority;
pub mod client;
pub mod downloader;
pub mod fastpay_smart_contract;
pub mod genesis;
69 changes: 62 additions & 7 deletions fastpay_core/src/unit_tests/authority_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
// SPDX-License-Identifier: Apache-2.0

use super::*;
use crate::genesis;
#[cfg(test)]
use fastx_types::base_types::dbg_addr;
use move_binary_format::file_format;
use move_binary_format::{
file_format::{self, AddressIdentifierIndex, IdentifierIndex, ModuleHandle},
CompiledModule,
};
use move_core_types::ident_str;
#[cfg(test)]
use move_core_types::language_storage::ModuleId;
Expand Down Expand Up @@ -159,17 +163,69 @@ fn send_and_confirm_order(
authority.handle_confirmation_order(ConfirmationOrder::new(certificate))
}

/// Create a `CompiledModule` that depends on `m`
fn make_dependent_module(m: &CompiledModule) -> CompiledModule {
let mut dependent_module = file_format::empty_module();
dependent_module
.identifiers
.push(m.self_id().name().to_owned());
dependent_module
.address_identifiers
.push(*m.self_id().address());
dependent_module.module_handles.push(ModuleHandle {
address: AddressIdentifierIndex((dependent_module.address_identifiers.len() - 1) as u16),
name: IdentifierIndex((dependent_module.identifiers.len() - 1) as u16),
});
dependent_module
}

// Test that publishing a module that depends on an existing one works
#[test]
fn test_publish_dependent_module_ok() {
let (sender, sender_key) = get_key_pair();
// create a dummy gas payment object. ok for now because we don't check gas
let gas_payment_object_id = ObjectID::random();
let gas_payment_object = Object::with_id_owner_for_testing(gas_payment_object_id, sender);
let gas_payment_object_ref = gas_payment_object.to_object_reference();
// create a genesis state that contains the gas object and genesis modules
let mut genesis_module_objects = genesis::create_genesis_module_objects().unwrap();
let genesis_module = match &genesis_module_objects[0].data {
Data::Module(m) => m,
_ => unreachable!(),
};
// create a module that depends on a genesis module
let dependent_module = make_dependent_module(genesis_module);
let dependent_module_bytes = {
let mut bytes = Vec::new();
dependent_module.serialize(&mut bytes).unwrap();
bytes
};
genesis_module_objects.push(gas_payment_object);
let mut authority = init_state_with_objects(genesis_module_objects);

let order = Order::new_module(
sender,
gas_payment_object_ref,
vec![dependent_module_bytes],
&sender_key,
);
let dependent_module_id = TxContext::new(order.digest()).fresh_id();
let response = send_and_confirm_order(&mut authority, order).unwrap();
// check that the dependent module got published
assert!(response.object_id == dependent_module_id);
}

// Test that publishing a module with no dependencies works
#[test]
fn test_publish_module_ok() {
fn test_publish_module_no_dependencies_ok() {
let (sender, sender_key) = get_key_pair();
// create a dummy gas payment object. ok for now because we don't check gas
let gas_payment_object_id = ObjectID::random();
let gas_payment_object = Object::with_id_owner_for_testing(gas_payment_object_id, sender);
let gas_payment_object_ref = gas_payment_object.to_object_reference();
let mut authority = init_state_with_objects(vec![gas_payment_object]);

let mut module = file_format::empty_module();
module.address_identifiers[0] = sender.to_address_hack();
let module = file_format::empty_module();
let mut module_bytes = Vec::new();
module.serialize(&mut module_bytes).unwrap();
let order = Order::new_module(
Expand All @@ -178,11 +234,10 @@ fn test_publish_module_ok() {
vec![module_bytes],
&sender_key,
);
let module_object_id = TxContext::new(order.digest()).fresh_id();
let module_id = TxContext::new(order.digest()).fresh_id();
let response = send_and_confirm_order(&mut authority, order).unwrap();
// check that the module actually got published
assert!(response.object_id == module_object_id);
// TODO: eventually, `response` should return a vector of ID's created + we should check non-emptiness of this vector
assert!(response.object_id == module_id);
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions fastx_programmability/adapter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ structopt = "0.3.21"

move-binary-format = { git = "https://github.com/diem/diem", rev="5572eae5e35407f10316b473f95cb155a5e8d40b" }
move-bytecode-utils = { git = "https://github.com/diem/diem", rev="5572eae5e35407f10316b473f95cb155a5e8d40b" }
move-bytecode-verifier = { git = "https://github.com/diem/diem", rev="5572eae5e35407f10316b473f95cb155a5e8d40b" }
move-core-types = { git = "https://github.com/diem/diem", rev="5572eae5e35407f10316b473f95cb155a5e8d40b" }
move-cli = { git = "https://github.com/diem/diem", rev="5572eae5e35407f10316b473f95cb155a5e8d40b" }
move-vm-runtime = { git = "https://github.com/diem/diem", rev="5572eae5e35407f10316b473f95cb155a5e8d40b" }
Expand Down
95 changes: 55 additions & 40 deletions fastx_programmability/adapter/src/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
// SPDX-License-Identifier: Apache-2.0

use anyhow::{bail, Result};
use fastx_framework::{natives, FASTX_FRAMEWORK_ADDRESS, MOVE_STDLIB_ADDRESS};
use fastx_framework::natives;
use fastx_types::{
base_types::{FastPayAddress, ObjectRef, SequenceNumber, TxContext},
error::{FastPayError, FastPayResult},
object::Object,
storage::Storage,
FASTX_FRAMEWORK_ADDRESS, MOVE_STDLIB_ADDRESS,
};
use fastx_verifier::verifier;
use move_binary_format::{errors::VMError, file_format::CompiledModule};
Expand All @@ -23,7 +24,9 @@ use move_core_types::{
transaction_argument::{convert_txn_args, TransactionArgument},
};
use move_vm_runtime::move_vm::MoveVM;
use std::fmt::Debug;
use std::{collections::BTreeMap, fmt::Debug};

use crate::bytecode_rewriter::ModuleHandleRewriter;

/// Execute `module::function<type_args>(object_args ++ pure_args)` as a call from `sender` with the given `gas_budget`.
/// Execution will read from/write to the store in `state_view`.
Expand Down Expand Up @@ -144,55 +147,67 @@ pub fn publish<E: Debug, S: ResourceResolver<Error = E> + ModuleResolver<Error =
})
})
.collect::<FastPayResult<Vec<CompiledModule>>>()?;

// Use the Move VM's publish API to run the Move bytecode verifier and linker.
// It is important to do this before running the FastX verifier, since the fastX
// verifier may assume well-formedness conditions enforced by the Move verifier hold
// TODO(https://github.com/MystenLabs/fastnft/issues/57):
// it would be more efficient to call the linker/verifier directly instead of
// creating a VM. It will also save us from serializing/deserializing the modules twice
let vm = create_vm();
let mut session = vm.new_session(state_view);
let mut gas_status = get_gas_status(None).expect("Cannot fail when called with None");
session
.publish_module_bundle(module_bytes, *sender, &mut gas_status)
.map_err(|e| FastPayError::ModulePublishFailure {
error: e.to_string(),
generate_module_ids(&mut modules, ctx)?;
// verify and link modules, wrap them in objects, write them to the store
let mut written_refs = Vec::with_capacity(modules.len());
for module in modules {
// It is important to do this before running the FastX verifier, since the fastX
// verifier may assume well-formedness conditions enforced by the Move verifier hold
move_bytecode_verifier::verify_module(&module).map_err(|e| {
FastPayError::ModuleVerificationFailure {
error: e.to_string(),
}
})?;
// Run FastX bytecode verifier
verifier::verify_module(&module)?;

// Run FastX bytecode verifier
for module in &modules {
verifier::verify_module(module)?
}

// derive fresh ID's for each module and mutate its self address to the ID.
// this ensures that each module can be uniquely identified/retrieved by its self address
// TODO: do this *before* passing the modules to the verifier. Right now, we can't because
// `publish_module_bundle` insists that the tx sender is equal to the module's self_address()
for module in modules.iter_mut() {
let fresh_id = ctx.fresh_id();
// TODO(https://github.com/MystenLabs/fastnft/issues/56):
// add a FastX bytecode verifier pass to ensure that no bytecodes reference `module.address_identifiers[0]`
// otherwise, code like `if (x == old_self_address)` could sneakily change to `if (x == fresh_id)` after the mutation below
module.address_identifiers[0] = fresh_id;
assert!(module.self_id().address() == &fresh_id);
}
// TODO(https://github.com/MystenLabs/fastnft/issues/69):
// run Move linker using state_view. it currently can only be called through the VM's publish or publish_module_bundle API's,
// but we can't use those because they require module.self_address() == sender, which is not true for FastX modules
let _ = state_view;

// Create and return module objects
let mut written_refs = Vec::with_capacity(modules.len());
for m in modules {
let new_module = Object::new_module(
m,
// Create module objects and write them to the store
let module_object = Object::new_module(
module,
FastPayAddress::from_move_address_hack(sender),
SequenceNumber::new(),
);
written_refs.push(new_module.to_object_reference());
state_view.write_object(new_module);
written_refs.push(module_object.to_object_reference());
state_view.write_object(module_object);
}

Ok(written_refs)
}

/// Use `ctx` to generate fresh ID's for each module in `modules`.
/// Mutate each module's self ID to the appropriate fresh ID and update its module handle tables
/// to reflect the new ID's of its dependencies
pub fn generate_module_ids(
modules: &mut Vec<CompiledModule>,
ctx: &mut TxContext,
) -> Result<(), FastPayError> {
let mut sub_map = BTreeMap::new();
for module in modules.iter() {
// derive a fresh ID's for each module and mutate its self address to the ID.
// this ensures that the module can be uniquely identified/retrieved by its self address
let old_module_id = module.self_id();
let fresh_object_id = ctx.fresh_id();
let new_module_id = ModuleId::new(fresh_object_id, old_module_id.name().to_owned());
if sub_map.insert(old_module_id, new_module_id).is_some() {
return Err(FastPayError::ModulePublishFailure {
error: "Publishing two modules with the same ID".to_string(),
});
}
}
// Safe to unwrap because we checked for duplicate domain entries above, and range entries are fresh ID's
let rewriter = ModuleHandleRewriter::new(sub_map).unwrap();
for module in modules.iter_mut() {
// rewrite module handles to reflect freshly generated ID's
rewriter.sub_module_ids(module);
}
Ok(())
}

/// Check if this is a special event type emitted when there is a transfer between fastX addresses
pub fn is_transfer_event(e: &Event) -> bool {
// TODO: hack that leverages implementation of Transfer::transfer_internal native function
Expand Down
Loading