From 536b4c1a2542912eb452fcf8043608f577a8e722 Mon Sep 17 00:00:00 2001 From: Jack May Date: Tue, 23 Jul 2019 21:34:17 -0700 Subject: [PATCH] Export genesis creation function (#5252) --- programs/move_loader_api/Cargo.toml | 4 +- programs/move_loader_api/src/account_state.rs | 140 +++++ programs/move_loader_api/src/data_store.rs | 3 - programs/move_loader_api/src/lib.rs | 521 +----------------- programs/move_loader_api/src/processor.rs | 463 ++++++++++++++++ programs/move_loader_program/Cargo.lock | 5 +- programs/move_loader_program/src/lib.rs | 2 +- 7 files changed, 611 insertions(+), 527 deletions(-) create mode 100644 programs/move_loader_api/src/account_state.rs create mode 100644 programs/move_loader_api/src/processor.rs diff --git a/programs/move_loader_api/Cargo.toml b/programs/move_loader_api/Cargo.toml index c642a3c69..596f90c5c 100644 --- a/programs/move_loader_api/Cargo.toml +++ b/programs/move_loader_api/Cargo.toml @@ -20,6 +20,7 @@ solana-logger = { path = "../../logger", version = "0.17.0" } solana-sdk = { path = "../../sdk", version = "0.17.0" } bytecode_verifier = { git = "https://github.com/solana-labs/libra", tag = "v0.0.0.1" } +compiler = { git = "https://github.com/solana-labs/libra", tag = "v0.0.0.1" } failure = { git = "https://github.com/solana-labs/libra", tag = "v0.0.0.1", package = "failure_ext" } language_e2e_tests = { git = "https://github.com/solana-labs/libra", tag = "v0.0.0.1" } state_view = { git = "https://github.com/solana-labs/libra", tag = "v0.0.0.1" } @@ -29,9 +30,6 @@ vm = { git = "https://github.com/solana-labs/libra", tag = "v0.0.0.1" } vm_cache_map = { git = "https://github.com/solana-labs/libra", tag = "v0.0.0.1" } vm_runtime = { git = "https://github.com/solana-labs/libra", tag = "v0.0.0.1" } -[dev-dependencies] -compiler = { git = "https://github.com/solana-labs/libra", tag = "v0.0.0.1" } - [lib] crate-type = ["lib"] name = "solana_move_loader_api" diff --git a/programs/move_loader_api/src/account_state.rs b/programs/move_loader_api/src/account_state.rs new file mode 100644 index 000000000..b43f893d2 --- /dev/null +++ b/programs/move_loader_api/src/account_state.rs @@ -0,0 +1,140 @@ +#![allow(dead_code)] + +use crate::data_store::DataStore; +use compiler::Compiler; +use serde_derive::{Deserialize, Serialize}; +use solana_sdk::pubkey::Pubkey; +use std::convert::TryInto; +use stdlib::stdlib_modules; +use types::{ + account_address::AccountAddress, byte_array::ByteArray, transaction::Program, + write_set::WriteSet, +}; +use vm::{access::ModuleAccess, transaction_metadata::TransactionMetadata}; +use vm_cache_map::Arena; +use vm_runtime::{ + code_cache::{ + module_adapter::FakeFetcher, + module_cache::{BlockModuleCache, VMModuleCache}, + }, + data_cache::BlockDataCache, + txn_executor::{TransactionExecutor, ACCOUNT_MODULE, COIN_MODULE}, + value::Local, +}; + +// Helper function that converts a Solana Pubkey to a Libra AccountAddress (WIP) +pub fn pubkey_to_address(key: &Pubkey) -> AccountAddress { + AccountAddress::new(*to_array_32(key.as_ref())) +} +fn to_array_32(array: &[u8]) -> &[u8; 32] { + array.try_into().expect("slice with incorrect length") +} + +/// Type of Libra account held by a Solana account +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum LibraAccountState { + /// No data for this account yet + Unallocated, + /// Program bits + Program(Program), + /// Write set containing a Libra account's data + User(WriteSet), + /// Write sets containing the mint and stdlib modules + Genesis(WriteSet), +} +impl LibraAccountState { + pub fn create_unallocated() -> Self { + LibraAccountState::Unallocated + } + + pub fn create_program(sender_address: &AccountAddress, code: &str) -> Self { + let compiler = Compiler { + address: *sender_address, + code, + ..Compiler::default() + }; + let compiled_program = compiler.into_compiled_program().expect("Failed to compile"); + + let mut script = vec![]; + compiled_program + .script + .serialize(&mut script) + .expect("Unable to serialize script"); + let mut modules = vec![]; + for m in compiled_program.modules.iter() { + let mut buf = vec![]; + m.serialize(&mut buf).expect("Unable to serialize module"); + modules.push(buf); + } + LibraAccountState::Program(Program::new(script, modules, vec![])) + } + + pub fn create_user(write_set: WriteSet) -> Self { + LibraAccountState::User(write_set) + } + + pub fn create_genesis(mint_balance: u64) -> Self { + let modules = stdlib_modules(); + let arena = Arena::new(); + let state_view = DataStore::default(); + let vm_cache = VMModuleCache::new(&arena); + let mint_address = AccountAddress::default(); + // TODO: Need this? + let genesis_auth_key = ByteArray::new(mint_address.to_vec()); + + let write_set = { + let fake_fetcher = + FakeFetcher::new(modules.iter().map(|m| m.as_inner().clone()).collect()); + let data_cache = BlockDataCache::new(&state_view); + let block_cache = BlockModuleCache::new(&vm_cache, fake_fetcher); + + let mut txn_data = TransactionMetadata::default(); + txn_data.sender = mint_address; + + let mut txn_executor = TransactionExecutor::new(&block_cache, &data_cache, txn_data); + txn_executor.create_account(mint_address).unwrap().unwrap(); + txn_executor + .execute_function(&COIN_MODULE, "initialize", vec![]) + .unwrap() + .unwrap(); + + txn_executor + .execute_function( + &ACCOUNT_MODULE, + "mint_to_address", + vec![Local::address(mint_address), Local::u64(mint_balance)], + ) + .unwrap() + .unwrap(); + + txn_executor + .execute_function( + &ACCOUNT_MODULE, + "rotate_authentication_key", + vec![Local::bytearray(genesis_auth_key)], + ) + .unwrap() + .unwrap(); + + let stdlib_modules = modules + .iter() + .map(|m| { + let mut module_vec = vec![]; + m.serialize(&mut module_vec).unwrap(); + (m.self_id(), module_vec) + }) + .collect(); + + txn_executor + .make_write_set(stdlib_modules, Ok(Ok(()))) + .unwrap() + .write_set() + .clone() + .into_mut() + } + .freeze() + .unwrap(); + + LibraAccountState::Genesis(write_set) + } +} diff --git a/programs/move_loader_api/src/data_store.rs b/programs/move_loader_api/src/data_store.rs index 9303d9a35..6ad8a0d6a 100644 --- a/programs/move_loader_api/src/data_store.rs +++ b/programs/move_loader_api/src/data_store.rs @@ -1,6 +1,3 @@ -// TODO -#![allow(dead_code)] - use failure::prelude::*; use log::*; use state_view::StateView; diff --git a/programs/move_loader_api/src/lib.rs b/programs/move_loader_api/src/lib.rs index 11b2d78ca..6432f5676 100644 --- a/programs/move_loader_api/src/lib.rs +++ b/programs/move_loader_api/src/lib.rs @@ -1,3 +1,7 @@ +pub mod account_state; +pub mod data_store; +pub mod processor; + const MOVE_LOADER_PROGRAM_ID: [u8; 32] = [ 5, 91, 237, 31, 90, 253, 197, 145, 157, 236, 147, 43, 6, 5, 157, 238, 63, 151, 181, 165, 118, 224, 198, 97, 103, 136, 113, 64, 0, 0, 0, 0, @@ -7,520 +11,3 @@ solana_sdk::solana_name_id!( MOVE_LOADER_PROGRAM_ID, "MvLdr11111111111111111111111111111111111111" ); - -mod data_store; - -use bytecode_verifier::{VerifiedModule, VerifiedScript}; -use data_store::DataStore; -use log::*; -use serde_derive::{Deserialize, Serialize}; -use solana_sdk::{ - account::KeyedAccount, instruction::InstructionError, loader_instruction::LoaderInstruction, - pubkey::Pubkey, -}; -use std::convert::TryInto; -use types::{ - account_address::AccountAddress, - transaction::{Program, TransactionArgument, TransactionOutput, TransactionStatus}, - write_set::WriteSet, -}; -use vm::{ - access::ModuleAccess, file_format::CompiledScript, transaction_metadata::TransactionMetadata, -}; -use vm_cache_map::Arena; -use vm_runtime::{ - code_cache::{ - module_adapter::ModuleFetcherImpl, - module_cache::{BlockModuleCache, ModuleCache, VMModuleCache}, - }, - static_verify_program, - txn_executor::TransactionExecutor, - value::Local, -}; - -const PROGRAM_INDEX: usize = 0; -const GENESIS_INDEX: usize = 1; - -/// Type of Libra account held by a Solana account -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -pub enum LibraAccountState { - /// No data for this account yet - Unallocated, - /// Program bits - Program(Program), - /// Write set containing a Libra account's data - User(WriteSet), - /// Write sets containing the mint and stdlib modules - Genesis(WriteSet), -} - -// TODO: Not quite right yet -/// Invoke information passed via the Invoke Instruction -#[derive(Default, Debug, Serialize, Deserialize)] -pub struct InvokeInfo { - /// Sender of the 'transaction", the "sender" who is calling this program - sender_address: AccountAddress, - /// Arguments to pass to the program being invoked - args: Vec, -} - -fn arguments_to_locals(args: Vec) -> Vec { - let mut locals = vec![]; - for arg in args.into_iter() { - locals.push(match arg { - TransactionArgument::U64(i) => Local::u64(i), - TransactionArgument::Address(a) => Local::address(a), - TransactionArgument::ByteArray(b) => Local::bytearray(b), - TransactionArgument::String(s) => Local::string(s), - }); - } - locals -} - -fn pubkey_to_address(key: &Pubkey) -> AccountAddress { - AccountAddress::new(*to_array_32(key.as_ref())) -} -fn to_array_32(array: &[u8]) -> &[u8; 32] { - array.try_into().expect("slice with incorrect length") -} - -pub fn execute( - invoke_info: InvokeInfo, - script: VerifiedScript, - modules: Vec, - data_store: &DataStore, -) -> TransactionOutput { - let allocator = Arena::new(); - let code_cache = VMModuleCache::new(&allocator); - let module_cache = BlockModuleCache::new(&code_cache, ModuleFetcherImpl::new(data_store)); - for m in modules { - module_cache.cache_module(m); - } - let main_module = script.into_module(); - let module_id = main_module.self_id(); - module_cache.cache_module(main_module); - let mut txn_metadata = TransactionMetadata::default(); - txn_metadata.sender = invoke_info.sender_address; - - let mut vm = TransactionExecutor::new(&module_cache, data_store, txn_metadata); - let result = vm.execute_function( - &module_id, - &"main".to_string(), - arguments_to_locals(invoke_info.args), - ); - vm.make_write_set(vec![], result).unwrap() -} - -fn keyed_accounts_to_data_store(keyed_accounts: &[KeyedAccount]) -> DataStore { - let mut data_store = DataStore::default(); - for keyed_account in keyed_accounts { - match bincode::deserialize(&keyed_account.account.data).unwrap() { - LibraAccountState::Genesis(write_set) | LibraAccountState::User(write_set) => { - data_store.apply_write_set(&write_set) - } - _ => (), // ignore unallocated accounts - } - } - data_store -} - -pub fn process_instruction( - _program_id: &Pubkey, - keyed_accounts: &mut [KeyedAccount], - ix_data: &[u8], -) -> Result<(), InstructionError> { - solana_logger::setup(); - - if let Ok(instruction) = bincode::deserialize(ix_data) { - match instruction { - LoaderInstruction::Write { offset, bytes } => { - if keyed_accounts[PROGRAM_INDEX].signer_key().is_none() { - warn!("key[0] did not sign the transaction"); - return Err(InstructionError::GenericError); - } - let offset = offset as usize; - let len = bytes.len(); - debug!("Write: offset={} length={}", offset, len); - if keyed_accounts[PROGRAM_INDEX].account.data.len() < offset + len { - warn!( - "Write overflow: {} < {}", - keyed_accounts[PROGRAM_INDEX].account.data.len(), - offset + len - ); - return Err(InstructionError::GenericError); - } - keyed_accounts[PROGRAM_INDEX].account.data[offset..offset + len] - .copy_from_slice(&bytes); - } - LoaderInstruction::Finalize => { - if keyed_accounts[PROGRAM_INDEX].signer_key().is_none() { - warn!("key[0] did not sign the transaction"); - return Err(InstructionError::GenericError); - } - keyed_accounts[PROGRAM_INDEX].account.executable = true; - info!( - "Finalize: account {:?}", - keyed_accounts[PROGRAM_INDEX].signer_key().unwrap() - ); - } - LoaderInstruction::InvokeMain { data } => { - if keyed_accounts.len() < 2 { - error!("Need at least program and genesis accounts"); - return Err(InstructionError::InvalidArgument); - } - if keyed_accounts[PROGRAM_INDEX].account.owner - != Pubkey::new(&MOVE_LOADER_PROGRAM_ID) - { - error!("Move program account not owned by Move loader"); - return Err(InstructionError::InvalidArgument); - } - if !keyed_accounts[PROGRAM_INDEX].account.executable { - error!("Move program account not executable"); - return Err(InstructionError::InvalidArgument); - } - - // TODO: Return errors instead of panicking - - let invoke_info: InvokeInfo = bincode::deserialize(&data).unwrap(); - - let program = match bincode::deserialize(&keyed_accounts[0].account.data).unwrap() { - LibraAccountState::Program(program) => program, - _ => { - error!("First account must contain the program bits"); - return Err(InstructionError::InvalidArgument); - } - }; - let compiled_script = CompiledScript::deserialize(program.code()).unwrap(); - // TODO: Add support for modules - let modules = vec![]; - - let mut data_store = keyed_accounts_to_data_store(&keyed_accounts[GENESIS_INDEX..]); - - let (verified_script, modules) = - // TODO: This function calls `.expect()` internally, need an error friendly version - static_verify_program(&invoke_info.sender_address, compiled_script, modules) - .expect("verification failure"); - - let output = execute(invoke_info, verified_script, modules, &data_store); - for event in output.events() { - debug!("Event: {:?}", event); - } - if let TransactionStatus::Discard(status) = output.status() { - error!("Execution failed: {:?}", status); - return Err(InstructionError::GenericError); - } - data_store.apply_write_set(&output.write_set()); - - // Break data store into a list of address keyed WriteSets - let mut write_sets = data_store.into_write_sets(); - - // Genesis account holds both mint and stdliib under address 0x0 - let write_set = write_sets.remove(&AccountAddress::default()).unwrap(); - keyed_accounts[GENESIS_INDEX].account.data.clear(); - let writer = - std::io::BufWriter::new(&mut keyed_accounts[GENESIS_INDEX].account.data); - bincode::serialize_into(writer, &LibraAccountState::Genesis(write_set)).unwrap(); - - // Now do the rest of the accounts - for keyed_account in keyed_accounts[GENESIS_INDEX + 1..].iter_mut() { - let write_set = write_sets - .remove(&pubkey_to_address(keyed_account.unsigned_key())) - .unwrap(); - keyed_account.account.data.clear(); - let writer = std::io::BufWriter::new(&mut keyed_account.account.data); - bincode::serialize_into(writer, &LibraAccountState::User(write_set)).unwrap(); - } - if !write_sets.is_empty() { - error!("Missing keyed accounts"); - return Err(InstructionError::GenericError); - } - } - } - } else { - warn!("Invalid program transaction: {:?}", ix_data); - return Err(InstructionError::GenericError); - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use compiler::Compiler; - use language_e2e_tests::account::AccountResource; - use solana_sdk::account::Account; - use stdlib::stdlib_modules; - use types::byte_array::ByteArray; - use vm_runtime::{ - code_cache::{ - module_adapter::FakeFetcher, - module_cache::{BlockModuleCache, VMModuleCache}, - }, - data_cache::BlockDataCache, - txn_executor::{TransactionExecutor, ACCOUNT_MODULE, COIN_MODULE}, - }; - - #[test] - fn test_invoke_main() { - solana_logger::setup(); - - let code = "main() { return; }"; - let mut program = LibraAccount::create_program(&AccountAddress::default(), code); - let mut genesis = LibraAccount::create_genesis(); - - let mut keyed_accounts = vec![ - KeyedAccount::new(&program.key, false, &mut program.account), - KeyedAccount::new(&genesis.key, false, &mut genesis.account), - ]; - call_process_instruction(&mut keyed_accounts, InvokeInfo::default()); - } - - #[test] - fn test_invoke_mint_to_address() { - solana_logger::setup(); - - let amount = 42; - let accounts = mint_coins(amount).unwrap(); - - let mut data_store = DataStore::default(); - match bincode::deserialize(&accounts[GENESIS_INDEX + 1].account.data).unwrap() { - LibraAccountState::User(write_set) => data_store.apply_write_set(&write_set), - _ => panic!("Invalid account state"), - } - let payee_resource = data_store - .read_account_resource(&accounts[GENESIS_INDEX + 1].address) - .unwrap(); - - assert_eq!(amount, AccountResource::read_balance(&payee_resource)); - assert_eq!(0, AccountResource::read_sequence_number(&payee_resource)); - } - - #[test] - fn test_invoke_pay_from_sender() { - let amount_to_mint = 42; - let mut accounts = mint_coins(amount_to_mint).unwrap(); - - let code = " - import 0x0.LibraAccount; - import 0x0.LibraCoin; - main(payee: address, amount: u64) { - LibraAccount.pay_from_sender(move(payee), move(amount)); - return; - } - "; - let mut program = LibraAccount::create_program(&accounts[GENESIS_INDEX + 1].address, code); - let mut payee = LibraAccount::create_unallocated(); - - let (genesis, sender) = accounts.split_at_mut(GENESIS_INDEX + 1); - let genesis = &mut genesis[1]; - let sender = &mut sender[0]; - let mut keyed_accounts = vec![ - KeyedAccount::new(&program.key, false, &mut program.account), - KeyedAccount::new(&genesis.key, false, &mut genesis.account), - KeyedAccount::new(&sender.key, false, &mut sender.account), - KeyedAccount::new(&payee.key, false, &mut payee.account), - ]; - - let amount = 2; - let invoke_info = InvokeInfo { - sender_address: sender.address.clone(), - args: vec![ - TransactionArgument::Address(payee.address.clone()), - TransactionArgument::U64(amount), - ], - }; - - call_process_instruction(&mut keyed_accounts, invoke_info); - - let data_store = keyed_accounts_to_data_store(&keyed_accounts[1..]); - let sender_resource = data_store.read_account_resource(&sender.address).unwrap(); - let payee_resource = data_store.read_account_resource(&payee.address).unwrap(); - - assert_eq!( - amount_to_mint - amount, - AccountResource::read_balance(&sender_resource) - ); - assert_eq!(0, AccountResource::read_sequence_number(&sender_resource)); - assert_eq!(amount, AccountResource::read_balance(&payee_resource)); - assert_eq!(0, AccountResource::read_sequence_number(&payee_resource)); - } - - // Helpers - - fn mint_coins(amount: u64) -> Result, InstructionError> { - let code = " - import 0x0.LibraAccount; - import 0x0.LibraCoin; - main(payee: address, amount: u64) { - LibraAccount.mint_to_address(move(payee), move(amount)); - return; - } - "; - let mut genesis = LibraAccount::create_genesis(); - let mut program = LibraAccount::create_program(&genesis.address, code); - let mut payee = LibraAccount::create_unallocated(); - - let mut keyed_accounts = vec![ - KeyedAccount::new(&program.key, false, &mut program.account), - KeyedAccount::new(&genesis.key, false, &mut genesis.account), - KeyedAccount::new(&payee.key, false, &mut payee.account), - ]; - let invoke_info = InvokeInfo { - sender_address: genesis.address.clone(), - args: vec![ - TransactionArgument::Address(pubkey_to_address(&payee.key)), - TransactionArgument::U64(amount), - ], - }; - - call_process_instruction(&mut keyed_accounts, invoke_info); - - Ok(vec![ - LibraAccount::new(program.key, program.account), - LibraAccount::new(genesis.key, genesis.account), - LibraAccount::new(payee.key, payee.account), - ]) - } - - fn call_process_instruction(keyed_accounts: &mut [KeyedAccount], invoke_info: InvokeInfo) { - let program_id = Pubkey::new(&MOVE_LOADER_PROGRAM_ID); - - let data = bincode::serialize(&invoke_info).unwrap(); - let ix = LoaderInstruction::InvokeMain { data }; - let ix_data = bincode::serialize(&ix).unwrap(); - - process_instruction(&program_id, keyed_accounts, &ix_data).unwrap(); - } - - struct LibraAccount { - pub key: Pubkey, - pub address: AccountAddress, - pub account: Account, - } - impl LibraAccount { - pub fn new(key: Pubkey, account: Account) -> Self { - let address = pubkey_to_address(&key); - Self { - key, - address, - account, - } - } - - pub fn create_unallocated() -> Self { - let key = Pubkey::new_rand(); - let account = Account { - lamports: 1, - data: bincode::serialize(&LibraAccountState::Unallocated).unwrap(), - owner: Pubkey::new(&MOVE_LOADER_PROGRAM_ID), - executable: false, - }; - Self::new(key, account) - } - - pub fn create_genesis() -> Self { - let account = Account { - lamports: 1, - data: vec![], - owner: Pubkey::new(&MOVE_LOADER_PROGRAM_ID), - executable: false, - }; - let mut genesis = Self::new(Pubkey::default(), account); - - const INIT_BALANCE: u64 = 1_000_000_000; - - let modules = stdlib_modules(); - let arena = Arena::new(); - let state_view = DataStore::default(); - let vm_cache = VMModuleCache::new(&arena); - let genesis_addr = genesis.address; - let genesis_auth_key = ByteArray::new(genesis.address.clone().to_vec()); - - let write_set = { - let fake_fetcher = - FakeFetcher::new(modules.iter().map(|m| m.as_inner().clone()).collect()); - let data_cache = BlockDataCache::new(&state_view); - let block_cache = BlockModuleCache::new(&vm_cache, fake_fetcher); - { - let mut txn_data = TransactionMetadata::default(); - txn_data.sender = genesis_addr; - - let mut txn_executor = - TransactionExecutor::new(&block_cache, &data_cache, txn_data); - txn_executor.create_account(genesis_addr).unwrap().unwrap(); - txn_executor - .execute_function(&COIN_MODULE, "initialize", vec![]) - .unwrap() - .unwrap(); - - txn_executor - .execute_function( - &ACCOUNT_MODULE, - "mint_to_address", - vec![Local::address(genesis_addr), Local::u64(INIT_BALANCE)], - ) - .unwrap() - .unwrap(); - - txn_executor - .execute_function( - &ACCOUNT_MODULE, - "rotate_authentication_key", - vec![Local::bytearray(genesis_auth_key)], - ) - .unwrap() - .unwrap(); - - let stdlib_modules = modules - .iter() - .map(|m| { - let mut module_vec = vec![]; - m.serialize(&mut module_vec).unwrap(); - (m.self_id(), module_vec) - }) - .collect(); - - txn_executor - .make_write_set(stdlib_modules, Ok(Ok(()))) - .unwrap() - .write_set() - .clone() - .into_mut() - } - } - .freeze() - .unwrap(); - - genesis.account.data = bincode::serialize(&LibraAccountState::Genesis(write_set)) - .expect("Failed to serialize genesis WriteSet"); - genesis - } - - pub fn create_program(sender_address: &AccountAddress, code: &str) -> Self { - let compiler = Compiler { - address: sender_address.clone(), - code, - ..Compiler::default() - }; - let compiled_program = compiler.into_compiled_program().expect("Failed to compile"); - - let mut script = vec![]; - compiled_program - .script - .serialize(&mut script) - .expect("Unable to serialize script"); - let mut modules = vec![]; - for m in compiled_program.modules.iter() { - let mut buf = vec![]; - m.serialize(&mut buf).expect("Unable to serialize module"); - modules.push(buf); - } - let data = Program::new(script, modules, vec![]); - - let mut program = Self::create_unallocated(); - program.account.data = bincode::serialize(&LibraAccountState::Program(data)).unwrap(); - program.account.executable = true; - program - } - } -} diff --git a/programs/move_loader_api/src/processor.rs b/programs/move_loader_api/src/processor.rs new file mode 100644 index 000000000..a2008cc53 --- /dev/null +++ b/programs/move_loader_api/src/processor.rs @@ -0,0 +1,463 @@ +use crate::account_state::{pubkey_to_address, LibraAccountState}; +use crate::data_store::DataStore; +use crate::id; +use bytecode_verifier::{VerifiedModule, VerifiedScript}; +use log::*; +use serde_derive::{Deserialize, Serialize}; +use solana_sdk::{ + account::KeyedAccount, instruction::InstructionError, loader_instruction::LoaderInstruction, + pubkey::Pubkey, +}; +use types::{ + account_address::AccountAddress, + transaction::{TransactionArgument, TransactionOutput, TransactionStatus}, +}; +use vm::{ + access::ModuleAccess, file_format::CompiledScript, transaction_metadata::TransactionMetadata, +}; +use vm_cache_map::Arena; +use vm_runtime::{ + code_cache::{ + module_adapter::ModuleFetcherImpl, + module_cache::{BlockModuleCache, ModuleCache, VMModuleCache}, + }, + static_verify_program, + txn_executor::TransactionExecutor, + value::Local, +}; + +pub fn process_instruction( + _program_id: &Pubkey, + keyed_accounts: &mut [KeyedAccount], + data: &[u8], +) -> Result<(), InstructionError> { + solana_logger::setup(); + + let command = bincode::deserialize::(data).map_err(|err| { + info!("Invalid instruction: {:?} {:?}", data, err); + InstructionError::InvalidInstructionData + })?; + + trace!("{:?}", command); + + match command { + LoaderInstruction::Write { offset, bytes } => { + MoveProcessor::do_write(keyed_accounts, offset, bytes) + } + LoaderInstruction::Finalize => MoveProcessor::do_finalize(keyed_accounts), + LoaderInstruction::InvokeMain { data } => { + MoveProcessor::do_invoke_main(keyed_accounts, data) + } + } +} + +pub const PROGRAM_INDEX: usize = 0; +pub const GENESIS_INDEX: usize = 1; + +// TODO: Not quite right yet +/// Invoke information passed via the Invoke Instruction +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct InvokeInfo { + /// Sender of the "transaction", the "sender" who is calling this program + sender_address: AccountAddress, + /// Name of the function to call + function_name: String, + /// Arguments to pass to the program being invoked + args: Vec, +} + +pub struct MoveProcessor {} + +impl MoveProcessor { + #[allow(clippy::needless_pass_by_value)] + fn map_vm_runtime_error(err: vm::errors::VMRuntimeError) -> InstructionError { + error!("Move VM Error: {:?}", err); + InstructionError::GenericError + } + fn map_vm_binary_error(err: vm::errors::BinaryError) -> InstructionError { + error!("Move VM Error: {:?}", err); + InstructionError::GenericError + } + #[allow(clippy::needless_pass_by_value)] + fn map_data_error(err: std::boxed::Box) -> InstructionError { + error!("Account data error: {:?}", err); + InstructionError::InvalidAccountData + } + #[allow(clippy::needless_pass_by_value)] + fn missing_account() -> InstructionError { + error!("Missing account"); + InstructionError::InvalidAccountData + } + + fn arguments_to_locals(args: Vec) -> Vec { + let mut locals = vec![]; + for arg in args.into_iter() { + locals.push(match arg { + TransactionArgument::U64(i) => Local::u64(i), + TransactionArgument::Address(a) => Local::address(a), + TransactionArgument::ByteArray(b) => Local::bytearray(b), + TransactionArgument::String(s) => Local::string(s), + }); + } + locals + } + + fn execute( + invoke_info: InvokeInfo, + script: VerifiedScript, + modules: Vec, + data_store: &DataStore, + ) -> Result { + let allocator = Arena::new(); + let code_cache = VMModuleCache::new(&allocator); + let module_cache = BlockModuleCache::new(&code_cache, ModuleFetcherImpl::new(data_store)); + for m in modules { + module_cache.cache_module(m); + } + let main_module = script.into_module(); + let module_id = main_module.self_id(); + module_cache.cache_module(main_module); + let mut txn_metadata = TransactionMetadata::default(); + txn_metadata.sender = invoke_info.sender_address; + + let mut vm = TransactionExecutor::new(&module_cache, data_store, txn_metadata); + let result = vm.execute_function( + &module_id, + &invoke_info.function_name, + Self::arguments_to_locals(invoke_info.args), + ); + + Ok(vm + .make_write_set(vec![], result) + .map_err(Self::map_vm_runtime_error)?) + } + + fn keyed_accounts_to_data_store( + keyed_accounts: &[KeyedAccount], + ) -> Result { + let mut data_store = DataStore::default(); + for keyed_account in keyed_accounts { + match bincode::deserialize(&keyed_account.account.data).map_err(Self::map_data_error)? { + LibraAccountState::Genesis(write_set) | LibraAccountState::User(write_set) => { + data_store.apply_write_set(&write_set) + } + _ => (), // ignore unallocated accounts + } + } + Ok(data_store) + } + + pub fn do_write( + keyed_accounts: &mut [KeyedAccount], + offset: u32, + bytes: Vec, + ) -> Result<(), InstructionError> { + if keyed_accounts[PROGRAM_INDEX].signer_key().is_none() { + warn!("key[0] did not sign the transaction"); + return Err(InstructionError::GenericError); + } + let offset = offset as usize; + let len = bytes.len(); + debug!("Write: offset={} length={}", offset, len); + if keyed_accounts[PROGRAM_INDEX].account.data.len() < offset + len { + warn!( + "Write overflow: {} < {}", + keyed_accounts[PROGRAM_INDEX].account.data.len(), + offset + len + ); + return Err(InstructionError::GenericError); + } + keyed_accounts[PROGRAM_INDEX].account.data[offset..offset + len].copy_from_slice(&bytes); + Ok(()) + } + + pub fn do_finalize(keyed_accounts: &mut [KeyedAccount]) -> Result<(), InstructionError> { + if keyed_accounts[PROGRAM_INDEX].signer_key().is_none() { + warn!("key[0] did not sign the transaction"); + return Err(InstructionError::GenericError); + } + keyed_accounts[PROGRAM_INDEX].account.executable = true; + info!( + "Finalize: {:?}", + keyed_accounts[PROGRAM_INDEX] + .signer_key() + .unwrap_or(&Pubkey::default()) + ); + Ok(()) + } + + pub fn do_invoke_main( + keyed_accounts: &mut [KeyedAccount], + data: Vec, + ) -> Result<(), InstructionError> { + if keyed_accounts.len() < 2 { + error!("Requires at least aprogram and genesis accounts"); + return Err(InstructionError::InvalidArgument); + } + if keyed_accounts[PROGRAM_INDEX].account.owner != id() { + error!("Move program account not owned by Move loader"); + return Err(InstructionError::InvalidArgument); + } + if !keyed_accounts[PROGRAM_INDEX].account.executable { + error!("Move program account not executable"); + return Err(InstructionError::InvalidArgument); + } + + let invoke_info: InvokeInfo = bincode::deserialize(&data).map_err(Self::map_data_error)?; + + let program = match bincode::deserialize(&keyed_accounts[0].account.data) + .map_err(Self::map_data_error)? + { + LibraAccountState::Program(program) => program, + _ => { + error!("First account must contain the program bits"); + return Err(InstructionError::InvalidArgument); + } + }; + let compiled_script = + CompiledScript::deserialize(program.code()).map_err(Self::map_vm_binary_error)?; + // TODO: Add support for modules + let modules = vec![]; + + let mut data_store = Self::keyed_accounts_to_data_store(&keyed_accounts[GENESIS_INDEX..])?; + + let (verified_script, modules) = + // TODO: This function calls `.expect()`, switch to verify_program + static_verify_program(&invoke_info.sender_address, compiled_script, modules) + .expect("verification failure"); + let output = Self::execute(invoke_info, verified_script, modules, &data_store)?; + for event in output.events() { + debug!("Event: {:?}", event); + } + if let TransactionStatus::Discard(status) = output.status() { + error!("Execution failed: {:?}", status); + return Err(InstructionError::GenericError); + } + data_store.apply_write_set(&output.write_set()); + + // Break data store into a list of address keyed WriteSets + let mut write_sets = data_store.into_write_sets(); + + // Genesis account holds both mint and stdlib under address 0x0 + let write_set = write_sets + .remove(&AccountAddress::default()) + .ok_or_else(Self::missing_account)?; + keyed_accounts[GENESIS_INDEX].account.data.clear(); + let writer = std::io::BufWriter::new(&mut keyed_accounts[GENESIS_INDEX].account.data); + bincode::serialize_into(writer, &LibraAccountState::Genesis(write_set)) + .map_err(Self::map_data_error)?; + + // Now do the rest of the accounts + for keyed_account in keyed_accounts[GENESIS_INDEX + 1..].iter_mut() { + let write_set = write_sets + .remove(&pubkey_to_address(keyed_account.unsigned_key())) + .ok_or_else(Self::missing_account)?; + keyed_account.account.data.clear(); + let writer = std::io::BufWriter::new(&mut keyed_account.account.data); + bincode::serialize_into(writer, &LibraAccountState::User(write_set)) + .map_err(Self::map_data_error)?; + } + if !write_sets.is_empty() { + error!("Missing keyed accounts"); + return Err(InstructionError::GenericError); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use language_e2e_tests::account::AccountResource; + use solana_sdk::account::Account; + + #[test] + fn test_invoke_main() { + solana_logger::setup(); + + let code = "main() { return; }"; + let mut program = LibraAccount::create_program(&AccountAddress::default(), code); + let mut genesis = LibraAccount::create_genesis(); + + let mut keyed_accounts = vec![ + KeyedAccount::new(&program.key, false, &mut program.account), + KeyedAccount::new(&genesis.key, false, &mut genesis.account), + ]; + let invoke_info = InvokeInfo { + sender_address: AccountAddress::default(), + function_name: "main".to_string(), + args: vec![], + }; + MoveProcessor::do_invoke_main( + &mut keyed_accounts, + bincode::serialize(&invoke_info).unwrap(), + ) + .unwrap(); + } + + #[test] + fn test_invoke_mint_to_address() { + solana_logger::setup(); + + let amount = 42; + let accounts = mint_coins(amount).unwrap(); + + let mut data_store = DataStore::default(); + match bincode::deserialize(&accounts[GENESIS_INDEX + 1].account.data).unwrap() { + LibraAccountState::User(write_set) => data_store.apply_write_set(&write_set), + _ => panic!("Invalid account state"), + } + let payee_resource = data_store + .read_account_resource(&accounts[GENESIS_INDEX + 1].address) + .unwrap(); + + assert_eq!(amount, AccountResource::read_balance(&payee_resource)); + assert_eq!(0, AccountResource::read_sequence_number(&payee_resource)); + } + + #[test] + fn test_invoke_pay_from_sender() { + let amount_to_mint = 42; + let mut accounts = mint_coins(amount_to_mint).unwrap(); + + let code = " + import 0x0.LibraAccount; + import 0x0.LibraCoin; + main(payee: address, amount: u64) { + LibraAccount.pay_from_sender(move(payee), move(amount)); + return; + } + "; + let mut program = LibraAccount::create_program(&accounts[GENESIS_INDEX + 1].address, code); + let mut payee = LibraAccount::create_unallocated(); + + let (genesis, sender) = accounts.split_at_mut(GENESIS_INDEX + 1); + let genesis = &mut genesis[1]; + let sender = &mut sender[0]; + let mut keyed_accounts = vec![ + KeyedAccount::new(&program.key, false, &mut program.account), + KeyedAccount::new(&genesis.key, false, &mut genesis.account), + KeyedAccount::new(&sender.key, false, &mut sender.account), + KeyedAccount::new(&payee.key, false, &mut payee.account), + ]; + + let amount = 2; + let invoke_info = InvokeInfo { + sender_address: sender.address.clone(), + function_name: "main".to_string(), + args: vec![ + TransactionArgument::Address(payee.address.clone()), + TransactionArgument::U64(amount), + ], + }; + + MoveProcessor::do_invoke_main( + &mut keyed_accounts, + bincode::serialize(&invoke_info).unwrap(), + ) + .unwrap(); + + let data_store = MoveProcessor::keyed_accounts_to_data_store(&keyed_accounts[1..]).unwrap(); + let sender_resource = data_store.read_account_resource(&sender.address).unwrap(); + let payee_resource = data_store.read_account_resource(&payee.address).unwrap(); + + assert_eq!( + amount_to_mint - amount, + AccountResource::read_balance(&sender_resource) + ); + assert_eq!(0, AccountResource::read_sequence_number(&sender_resource)); + assert_eq!(amount, AccountResource::read_balance(&payee_resource)); + assert_eq!(0, AccountResource::read_sequence_number(&payee_resource)); + } + + // Helpers + + fn mint_coins(amount: u64) -> Result, InstructionError> { + let code = " + import 0x0.LibraAccount; + import 0x0.LibraCoin; + main(payee: address, amount: u64) { + LibraAccount.mint_to_address(move(payee), move(amount)); + return; + } + "; + let mut genesis = LibraAccount::create_genesis(); + let mut program = LibraAccount::create_program(&genesis.address, code); + let mut payee = LibraAccount::create_unallocated(); + + let mut keyed_accounts = vec![ + KeyedAccount::new(&program.key, false, &mut program.account), + KeyedAccount::new(&genesis.key, false, &mut genesis.account), + KeyedAccount::new(&payee.key, false, &mut payee.account), + ]; + let invoke_info = InvokeInfo { + sender_address: genesis.address.clone(), + function_name: "main".to_string(), + args: vec![ + TransactionArgument::Address(pubkey_to_address(&payee.key)), + TransactionArgument::U64(amount), + ], + }; + + MoveProcessor::do_invoke_main( + &mut keyed_accounts, + bincode::serialize(&invoke_info).unwrap(), + ) + .unwrap(); + + Ok(vec![ + LibraAccount::new(program.key, program.account), + LibraAccount::new(genesis.key, genesis.account), + LibraAccount::new(payee.key, payee.account), + ]) + } + + struct LibraAccount { + pub key: Pubkey, + pub address: AccountAddress, + pub account: Account, + } + impl LibraAccount { + pub fn new(key: Pubkey, account: Account) -> Self { + let address = pubkey_to_address(&key); + Self { + key, + address, + account, + } + } + + pub fn create_unallocated() -> Self { + let key = Pubkey::new_rand(); + let account = Account { + lamports: 1, + data: bincode::serialize(&LibraAccountState::create_unallocated()).unwrap(), + owner: id(), + executable: false, + }; + Self::new(key, account) + } + + pub fn create_genesis() -> Self { + let account = Account { + lamports: 1, + data: vec![], + owner: id(), + executable: false, + }; + let mut genesis = Self::new(Pubkey::default(), account); + genesis.account.data = + bincode::serialize(&LibraAccountState::create_genesis(1_000_000_000)).unwrap(); + genesis + } + + pub fn create_program(sender_address: &AccountAddress, code: &str) -> Self { + let mut program = Self::create_unallocated(); + program.account.data = + bincode::serialize(&LibraAccountState::create_program(sender_address, code)) + .unwrap(); + program.account.executable = true; + program + } + } +} diff --git a/programs/move_loader_program/Cargo.lock b/programs/move_loader_program/Cargo.lock index 20be50dc4..d6f23e648 100644 --- a/programs/move_loader_program/Cargo.lock +++ b/programs/move_loader_program/Cargo.lock @@ -2341,19 +2341,18 @@ dependencies = [ "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "bytecode_verifier 0.1.0 (git+https://github.com/solana-labs/libra?tag=v0.0.0.1)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "compiler 0.1.0 (git+https://github.com/solana-labs/libra?tag=v0.0.0.1)", "failure_ext 0.1.0 (git+https://github.com/solana-labs/libra?tag=v0.0.0.1)", "language_e2e_tests 0.1.0 (git+https://github.com/solana-labs/libra?tag=v0.0.0.1)", - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", - "proto_conv 0.1.0 (git+https://github.com/solana-labs/libra?tag=v0.0.0.1)", - "protobuf 2.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "solana-logger 0.17.0", "solana-sdk 0.17.0", "state_view 0.1.0 (git+https://github.com/solana-labs/libra?tag=v0.0.0.1)", + "stdlib 0.1.0 (git+https://github.com/solana-labs/libra?tag=v0.0.0.1)", "types 0.1.0 (git+https://github.com/solana-labs/libra?tag=v0.0.0.1)", "vm 0.1.0 (git+https://github.com/solana-labs/libra?tag=v0.0.0.1)", "vm_cache_map 0.1.0 (git+https://github.com/solana-labs/libra?tag=v0.0.0.1)", diff --git a/programs/move_loader_program/src/lib.rs b/programs/move_loader_program/src/lib.rs index 241914b27..0ff08c91b 100644 --- a/programs/move_loader_program/src/lib.rs +++ b/programs/move_loader_program/src/lib.rs @@ -8,5 +8,5 @@ macro_rules! solana_move_loader_program { }; } -use solana_move_loader_api::process_instruction; +use solana_move_loader_api::processor::process_instruction; solana_sdk::solana_entrypoint!(process_instruction);