From be90eea0ffe3e0632951fc0100cd7f647a9c3075 Mon Sep 17 00:00:00 2001 From: Tim Zakian Date: Fri, 27 Sep 2019 15:04:10 -0700 Subject: [PATCH] [language] On-chain definition of gas schedule The gas schedule within the VM is defined on-chain, and is read in once per-block. Normal transactions initiatied from the association account can add, or update the costs within this gas schedule. Closes: #1406 Approved by: dariorussi --- executor/src/mock_vm/mod.rs | 3 +- language/benchmarks/src/transactions.rs | 4 +- language/e2e-tests/src/executor.rs | 10 +- language/e2e-tests/src/gas_costs.rs | 12 +- language/e2e-tests/src/lib.rs | 3 +- .../e2e-tests/src/tests/account_universe.rs | 4 +- .../e2e-tests/src/tests/create_account.rs | 26 +- language/e2e-tests/src/tests/genesis.rs | 3 +- language/e2e-tests/src/tests/mint.rs | 29 +- language/e2e-tests/src/tests/peer_to_peer.rs | 44 +-- language/e2e-tests/src/tests/rotate_key.rs | 6 +- language/functional_tests/src/evaluator.rs | 2 +- ...ule_has_proper_number_of_instructions.mvir | 7 + .../gas_schedule_resource_exists.mvir | 6 + language/stdlib/modules/gas_schedule.mvir | 277 ++++++++++++++ language/stdlib/src/stdlib.rs | 2 + language/tools/cost-synthesis/src/main.rs | 2 +- .../tools/cost-synthesis/src/vm_runner.rs | 4 +- language/tools/test-generation/src/lib.rs | 1 - language/vm/src/gas_schedule.rs | 337 +++++++++++------- language/vm/src/serializer.rs | 8 - language/vm/vm-genesis/genesis/genesis.blob | Bin 18220 -> 21245 bytes language/vm/vm-genesis/src/lib.rs | 18 +- language/vm/vm-runtime/src/block_processor.rs | 38 +- .../vm-runtime/src/code_cache/module_cache.rs | 5 +- language/vm/vm-runtime/src/gas_meter.rs | 103 ++++-- language/vm/vm-runtime/src/interpreter.rs | 10 +- language/vm/vm-runtime/src/lib.rs | 3 +- .../src/loaded_data/loaded_module.rs | 9 +- language/vm/vm-runtime/src/move_vm.rs | 4 +- language/vm/vm-runtime/src/process_txn/mod.rs | 5 +- .../vm/vm-runtime/src/process_txn/validate.rs | 20 +- language/vm/vm-runtime/src/runtime.rs | 32 +- language/vm/vm-runtime/src/txn_executor.rs | 35 +- .../src/unit_tests/module_cache_tests.rs | 8 +- terraform/validator-sets/100/genesis.blob | Bin 67490 -> 70925 bytes terraform/validator-sets/dev/genesis.blob | Bin 14497 -> 17933 bytes types/src/vm_error.rs | 8 + 38 files changed, 788 insertions(+), 300 deletions(-) create mode 100644 language/functional_tests/tests/testsuite/gas_schedule/gas_schedule_has_proper_number_of_instructions.mvir create mode 100644 language/functional_tests/tests/testsuite/gas_schedule/gas_schedule_resource_exists.mvir create mode 100644 language/stdlib/modules/gas_schedule.mvir diff --git a/executor/src/mock_vm/mod.rs b/executor/src/mock_vm/mod.rs index 84df360b628c..7fffe1a80938 100644 --- a/executor/src/mock_vm/mod.rs +++ b/executor/src/mock_vm/mod.rs @@ -4,7 +4,6 @@ #[cfg(test)] mod mock_vm_test; -use failure::Result; use lazy_static::lazy_static; use libra_config::config::VMConfig; use libra_crypto::ed25519::compat; @@ -54,7 +53,7 @@ impl VMExecutor for MockVM { transactions: Vec, _config: &VMConfig, state_view: &dyn StateView, - ) -> Result> { + ) -> Result, VMStatus> { if state_view.is_genesis() { assert_eq!( transactions.len(), diff --git a/language/benchmarks/src/transactions.rs b/language/benchmarks/src/transactions.rs index f5f872dc4963..2997808e021c 100644 --- a/language/benchmarks/src/transactions.rs +++ b/language/benchmarks/src/transactions.rs @@ -132,7 +132,9 @@ impl TransactionBenchState { fn execute(self) { // The output is ignored here since we're just testing transaction performance, not trying // to assert correctness. - self.executor.execute_block(self.transactions); + self.executor + .execute_block(self.transactions) + .expect("VM should not fail to start"); } } diff --git a/language/e2e-tests/src/executor.rs b/language/e2e-tests/src/executor.rs index 67d718efbb16..3f421e10a4cd 100644 --- a/language/e2e-tests/src/executor.rs +++ b/language/e2e-tests/src/executor.rs @@ -150,7 +150,10 @@ impl FakeExecutor { /// /// Typical tests will call this method and check that the output matches what was expected. /// However, this doesn't apply the results of successful transactions to the data store. - pub fn execute_block(&self, txn_block: Vec) -> Vec { + pub fn execute_block( + &self, + txn_block: Vec, + ) -> Result, VMStatus> { MoveVM::execute_block( txn_block .into_iter() @@ -159,12 +162,13 @@ impl FakeExecutor { &self.config.vm_config, &self.data_store, ) - .expect("The VM should not fail to start") } pub fn execute_transaction(&self, txn: SignedTransaction) -> TransactionOutput { let txn_block = vec![txn]; - let mut outputs = self.execute_block(txn_block); + let mut outputs = self + .execute_block(txn_block) + .expect("The VM should not fail to startup"); outputs .pop() .expect("A block with one transaction should have one output") diff --git a/language/e2e-tests/src/gas_costs.rs b/language/e2e-tests/src/gas_costs.rs index 38623562568f..25b4ee64b3f6 100644 --- a/language/e2e-tests/src/gas_costs.rs +++ b/language/e2e-tests/src/gas_costs.rs @@ -45,7 +45,7 @@ lazy_static! { create_account_txn(sender.account(), &Account::new(), 10, 20_000), create_account_txn(sender.account(), &Account::new(), 11, 20_000), ]; - let output = &executor.execute_block(txns); + let output = &executor.execute_block(txns).expect("The VM should not fail to startup"); output[1].gas_used() }; @@ -83,7 +83,7 @@ lazy_static! { create_account_txn(sender.account(), &Account::new(), 10, 10), create_account_txn(sender.account(), &Account::new(), 11, balance), ]; - let output = &executor.execute_block(txns); + let output = &executor.execute_block(txns).expect("The VM should not fail to startup"); output[1].gas_used() }; @@ -118,7 +118,7 @@ lazy_static! { create_account_txn(sender.account(), &Account::new(), 10, 20_000), create_account_txn(sender.account(), receiver.account(), 11, 20_000), ]; - let output = &executor.execute_block(txns); + let output = &executor.execute_block(txns).expect("The VM should not fail to startup"); output[1].gas_used() }; @@ -184,7 +184,7 @@ lazy_static! { peer_to_peer_txn(sender.account(), &Account::new(), 10, 20_000), peer_to_peer_txn(sender.account(), &Account::new(), 11, 20_000), ]; - let output = &executor.execute_block(txns); + let output = &executor.execute_block(txns).expect("The VM should not fail to startup"); output[1].gas_used() }; @@ -224,7 +224,7 @@ lazy_static! { peer_to_peer_txn(sender.account(), &Account::new(), 10, 10_000), peer_to_peer_txn(sender.account(), &Account::new(), 11, balance), ]; - let output = &executor.execute_block(txns); + let output = &executor.execute_block(txns).expect("The VM should not fail to startup"); output[1].gas_used() }; @@ -244,6 +244,6 @@ lazy_static! { } fn compute_gas_used(txn: SignedTransaction, executor: &mut FakeExecutor) -> u64 { - let output = &executor.execute_block(vec![txn])[0]; + let output = &executor.execute_transaction(txn); output.gas_used() } diff --git a/language/e2e-tests/src/lib.rs b/language/e2e-tests/src/lib.rs index b77b9635f496..219d22f704c5 100644 --- a/language/e2e-tests/src/lib.rs +++ b/language/e2e-tests/src/lib.rs @@ -7,7 +7,7 @@ use bytecode_verifier::{VerifiedModule, VerifiedScript}; use compiler::Compiler; -use data_store::FakeDataStore; +use data_store::{FakeDataStore, GENESIS_WRITE_SET}; use libra_types::{ access_path::AccessPath, account_address::AccountAddress, @@ -55,6 +55,7 @@ pub fn execute( ) -> VMResult<()> { // set up the DB let mut data_view = FakeDataStore::default(); + data_view.add_write_set(&GENESIS_WRITE_SET); data_view.set( AccessPath::new(AccountAddress::random(), vec![]), vec![0, 0], diff --git a/language/e2e-tests/src/tests/account_universe.rs b/language/e2e-tests/src/tests/account_universe.rs index b3b69f2970e9..6aeff8a07d48 100644 --- a/language/e2e-tests/src/tests/account_universe.rs +++ b/language/e2e-tests/src/tests/account_universe.rs @@ -109,7 +109,7 @@ pub(crate) fn run_and_assert_gas_cost_stability( .iter() .map(|transaction_gen| transaction_gen.clone().apply(&mut universe)) .unzip(); - let outputs = executor.execute_block(transactions); + let outputs = executor.execute_block(transactions).unwrap(); for (idx, (output, expected_value)) in outputs.iter().zip(&expected_values).enumerate() { @@ -142,7 +142,7 @@ pub(crate) fn run_and_assert_universe( .iter() .map(|transaction_gen| transaction_gen.clone().apply(&mut universe)) .unzip(); - let outputs = executor.execute_block(transactions); + let outputs = executor.execute_block(transactions).unwrap(); prop_assert_eq!(outputs.len(), expected_values.len()); diff --git a/language/e2e-tests/src/tests/create_account.rs b/language/e2e-tests/src/tests/create_account.rs index 1876fbc98558..a86a89cbde87 100644 --- a/language/e2e-tests/src/tests/create_account.rs +++ b/language/e2e-tests/src/tests/create_account.rs @@ -7,7 +7,7 @@ use crate::{ executor::test_all_genesis, }; use libra_types::{ - transaction::{SignedTransaction, TransactionStatus}, + transaction::TransactionStatus, vm_error::{StatusCode, VMStatus}, }; @@ -23,18 +23,16 @@ fn create_account() { let txn = create_account_txn(sender.account(), &new_account, 10, initial_amount); // execute transaction - let txns: Vec = vec![txn]; - let output = executor.execute_block(txns); - let txn_output = output.get(0).expect("must have a transaction output"); + let output = executor.execute_transaction(txn); assert_eq!( - output[0].status(), + output.status(), &TransactionStatus::Keep(VMStatus::new(StatusCode::EXECUTED)) ); - println!("write set {:?}", txn_output.write_set()); - executor.apply_write_set(txn_output.write_set()); + println!("write set {:?}", output.write_set()); + executor.apply_write_set(output.write_set()); // check that numbers in stored DB are correct - let gas = txn_output.gas_used(); + let gas = output.gas_used(); let sender_balance = 1_000_000 - initial_amount - gas; let updated_sender = executor .read_account_resource(sender.account()) @@ -62,18 +60,16 @@ fn create_account_zero_balance() { let txn = create_account_txn(sender.account(), &new_account, 10, initial_amount); // execute transaction - let txns: Vec = vec![txn]; - let output = executor.execute_block(txns); - let txn_output = output.get(0).expect("must have a transaction output"); + let output = executor.execute_transaction(txn); assert_eq!( - output[0].status(), + output.status(), &TransactionStatus::Keep(VMStatus::new(StatusCode::EXECUTED)) ); - println!("write set {:?}", txn_output.write_set()); - executor.apply_write_set(txn_output.write_set()); + println!("write set {:?}", output.write_set()); + executor.apply_write_set(output.write_set()); // check that numbers in stored DB are correct - let gas = txn_output.gas_used(); + let gas = output.gas_used(); let sender_balance = 1_000_000 - initial_amount - gas; let updated_sender = executor .read_account_resource(sender.account()) diff --git a/language/e2e-tests/src/tests/genesis.rs b/language/e2e-tests/src/tests/genesis.rs index 47a01dbb8d4e..52af7270460d 100644 --- a/language/e2e-tests/src/tests/genesis.rs +++ b/language/e2e-tests/src/tests/genesis.rs @@ -10,7 +10,8 @@ use libra_types::{ account_config, test_helpers::transaction_test_helpers, transaction::TransactionStatus, - vm_error::{StatusCode, VMStatus}, + vm_error::StatusCode, + vm_error::VMStatus, write_set::{WriteOp, WriteSetMut}, }; diff --git a/language/e2e-tests/src/tests/mint.rs b/language/e2e-tests/src/tests/mint.rs index f2890f85dc10..3b1abfb8d99c 100644 --- a/language/e2e-tests/src/tests/mint.rs +++ b/language/e2e-tests/src/tests/mint.rs @@ -9,7 +9,7 @@ use crate::{ transaction_status_eq, }; use libra_types::{ - transaction::{SignedTransaction, TransactionStatus}, + transaction::TransactionStatus, vm_error::{StatusCode, VMStatus}, }; @@ -29,18 +29,16 @@ fn mint_to_existing() { let txn = mint_txn(&genesis_account, receiver.account(), 1, mint_amount); // execute transaction - let txns: Vec = vec![txn]; - let output = executor.execute_block(txns); - let txn_output = output.get(0).expect("must have a transaction output"); + let output = executor.execute_transaction(txn); assert_eq!( - output[0].status(), + output.status(), &TransactionStatus::Keep(VMStatus::new(StatusCode::EXECUTED)) ); - println!("write set {:?}", txn_output.write_set()); - executor.apply_write_set(txn_output.write_set()); + println!("write set {:?}", output.write_set()); + executor.apply_write_set(output.write_set()); // check that numbers in stored DB are correct - let gas = txn_output.gas_used(); + let gas = output.gas_used(); let sender_balance = 1_000_000_000 - gas; let receiver_balance = 1_000_000 + mint_amount; @@ -72,17 +70,15 @@ fn mint_to_new_account() { let txn = mint_txn(&genesis_account, &new_account, 1, mint_amount); // execute transaction - let txns: Vec = vec![txn]; - let output = executor.execute_block(txns); - let txn_output = output.get(0).expect("must have a transaction output"); + let output = executor.execute_transaction(txn); assert!(transaction_status_eq( - &output[0].status(), + &output.status(), &TransactionStatus::Keep(VMStatus::new(StatusCode::EXECUTED)) )); - executor.apply_write_set(txn_output.write_set()); + executor.apply_write_set(output.write_set()); // check that numbers in stored DB are correct - let gas = txn_output.gas_used(); + let gas = output.gas_used(); let sender_balance = 1_000_000_000 - gas; let receiver_balance = mint_amount; @@ -99,11 +95,10 @@ fn mint_to_new_account() { // Mint can only be called from genesis address; let txn = mint_txn(&new_account, &new_account, 0, mint_amount); - let txns: Vec = vec![txn]; - let output = executor.execute_block(txns); + let output = executor.execute_transaction(txn); assert!(transaction_status_eq( - &output[0].status(), + &output.status(), &TransactionStatus::Keep(VMStatus::new(StatusCode::MISSING_DATA)) )); } diff --git a/language/e2e-tests/src/tests/peer_to_peer.rs b/language/e2e-tests/src/tests/peer_to_peer.rs index 062972697f62..878213e63c2a 100644 --- a/language/e2e-tests/src/tests/peer_to_peer.rs +++ b/language/e2e-tests/src/tests/peer_to_peer.rs @@ -30,18 +30,16 @@ fn single_peer_to_peer_with_event() { let txn = peer_to_peer_txn(sender.account(), receiver.account(), 10, transfer_amount); // execute transaction - let txns: Vec = vec![txn]; - let output = executor.execute_block(txns); - let txn_output = output.get(0).expect("must have a transaction output"); + let output = executor.execute_transaction(txn); assert_eq!( - output[0].status(), + output.status(), &TransactionStatus::Keep(VMStatus::new(StatusCode::EXECUTED)) ); - executor.apply_write_set(txn_output.write_set()); + executor.apply_write_set(output.write_set()); // check that numbers in stored DB are correct - let gas = txn_output.gas_used(); + let gas = output.gas_used(); let sender_balance = 1_000_000 - transfer_amount - gas; let receiver_balance = 100_000 + transfer_amount; let updated_sender = executor @@ -60,7 +58,7 @@ fn single_peer_to_peer_with_event() { let rec_ev_path = receiver.received_events_key().to_vec(); let sent_ev_path = sender.sent_events_key().to_vec(); - for event in txn_output.events() { + for event in output.events() { assert!( rec_ev_path.as_slice() == event.key().as_bytes() || sent_ev_path.as_slice() == event.key().as_bytes() @@ -96,18 +94,16 @@ fn single_peer_to_peer_with_padding() { let unpadded_txn = peer_to_peer_txn(sender.account(), receiver.account(), 10, transfer_amount); assert!(txn.raw_txn_bytes_len() > unpadded_txn.raw_txn_bytes_len()); // execute transaction - let txns: Vec = vec![txn]; - let output = executor.execute_block(txns); - let txn_output = output.get(0).expect("must have a transaction output"); + let output = executor.execute_transaction(txn); assert_eq!( - output[0].status(), + output.status(), &TransactionStatus::Keep(VMStatus::new(StatusCode::EXECUTED)) ); - executor.apply_write_set(txn_output.write_set()); + executor.apply_write_set(output.write_set()); // check that numbers in stored DB are correct - let gas = txn_output.gas_used(); + let gas = output.gas_used(); let sender_balance = 1_000_000 - transfer_amount - gas; let receiver_balance = 100_000 + transfer_amount; let updated_sender = executor @@ -140,7 +136,7 @@ fn few_peer_to_peer_with_event() { peer_to_peer_txn(sender.account(), receiver.account(), 12, transfer_amount), peer_to_peer_txn(sender.account(), receiver.account(), 13, transfer_amount), ]; - let output = executor.execute_block(txns); + let output = executor.execute_block(txns).unwrap(); for (idx, txn_output) in output.iter().enumerate() { assert_eq!( txn_output.status(), @@ -205,7 +201,7 @@ fn zero_amount_peer_to_peer() { transfer_amount, ); - let output = &executor.execute_block(vec![txn])[0]; + let output = &executor.execute_transaction(txn); // Error code 7 means that the transaction was a zero-amount one. assert!(transaction_status_eq( &output.status(), @@ -228,17 +224,15 @@ fn peer_to_peer_create_account() { let txn = peer_to_peer_txn(sender.account(), &new_account, 10, transfer_amount); // execute transaction - let txns: Vec = vec![txn]; - let output = executor.execute_block(txns); - let txn_output = output.get(0).expect("must have a transaction output"); + let output = executor.execute_transaction(txn); assert_eq!( - output[0].status(), + output.status(), &TransactionStatus::Keep(VMStatus::new(StatusCode::EXECUTED)) ); - executor.apply_write_set(txn_output.write_set()); + executor.apply_write_set(output.write_set()); // check that numbers in stored DB are correct - let gas = txn_output.gas_used(); + let gas = output.gas_used(); let sender_balance = 1_000_000 - transfer_amount - gas; let receiver_balance = transfer_amount; let updated_sender = executor @@ -423,7 +417,7 @@ fn cycle_peer_to_peer() { // execute transaction let mut execution_time = 0u128; let now = Instant::now(); - let output = executor.execute_block(txns); + let output = executor.execute_block(txns).unwrap(); execution_time += now.elapsed().as_nanos(); println!("EXECUTION TIME: {}", execution_time); for txn_output in &output { @@ -469,7 +463,7 @@ fn cycle_peer_to_peer_multi_block() { // execute transaction let now = Instant::now(); - let output = executor.execute_block(txns); + let output = executor.execute_block(txns).unwrap(); execution_time += now.elapsed().as_nanos(); for txn_output in &output { assert_eq!( @@ -516,7 +510,7 @@ fn one_to_many_peer_to_peer() { // execute transaction let now = Instant::now(); - let output = executor.execute_block(txns); + let output = executor.execute_block(txns).unwrap(); execution_time += now.elapsed().as_nanos(); for txn_output in &output { assert_eq!( @@ -563,7 +557,7 @@ fn many_to_one_peer_to_peer() { // execute transaction let now = Instant::now(); - let output = executor.execute_block(txns); + let output = executor.execute_block(txns).unwrap(); execution_time += now.elapsed().as_nanos(); for txn_output in &output { assert_eq!( diff --git a/language/e2e-tests/src/tests/rotate_key.rs b/language/e2e-tests/src/tests/rotate_key.rs index 0e9716aae9fb..d8d72a0d7075 100644 --- a/language/e2e-tests/src/tests/rotate_key.rs +++ b/language/e2e-tests/src/tests/rotate_key.rs @@ -25,7 +25,7 @@ fn rotate_key() { let txn = rotate_key_txn(sender.account(), new_key_hash, 10); // execute transaction - let output = &executor.execute_block(vec![txn])[0]; + let output = &executor.execute_transaction(txn); assert_eq!( output.status(), &TransactionStatus::Keep(VMStatus::new(StatusCode::EXECUTED)), @@ -48,7 +48,7 @@ fn rotate_key() { // Check that transactions cannot be sent with the old key any more. let new_account = Account::new(); let old_key_txn = create_account_txn(sender.account(), &new_account, 11, 100_000); - let old_key_output = &executor.execute_block(vec![old_key_txn])[0]; + let old_key_output = &executor.execute_transaction(old_key_txn); assert_eq!( old_key_output.status(), &TransactionStatus::Discard(VMStatus::new(StatusCode::INVALID_AUTH_KEY)), @@ -57,7 +57,7 @@ fn rotate_key() { // Check that transactions can be sent with the new key. sender.rotate_key(privkey, pubkey); let new_key_txn = create_account_txn(sender.account(), &new_account, 11, 100_000); - let new_key_output = &executor.execute_block(vec![new_key_txn])[0]; + let new_key_output = &executor.execute_transaction(new_key_txn); assert_eq!( new_key_output.status(), &TransactionStatus::Keep(VMStatus::new(StatusCode::EXECUTED)), diff --git a/language/functional_tests/src/evaluator.rs b/language/functional_tests/src/evaluator.rs index 72ed407ca33f..fcc00a0dda24 100644 --- a/language/functional_tests/src/evaluator.rs +++ b/language/functional_tests/src/evaluator.rs @@ -273,7 +273,7 @@ fn run_transaction( exec: &mut FakeExecutor, transaction: SignedTransaction, ) -> Result { - let mut outputs = exec.execute_block(vec![transaction]); + let mut outputs = exec.execute_block(vec![transaction]).unwrap(); if outputs.len() == 1 { let output = outputs.pop().unwrap(); match output.status() { diff --git a/language/functional_tests/tests/testsuite/gas_schedule/gas_schedule_has_proper_number_of_instructions.mvir b/language/functional_tests/tests/testsuite/gas_schedule/gas_schedule_has_proper_number_of_instructions.mvir new file mode 100644 index 000000000000..d9decb3540f1 --- /dev/null +++ b/language/functional_tests/tests/testsuite/gas_schedule/gas_schedule_has_proper_number_of_instructions.mvir @@ -0,0 +1,7 @@ +import 0x0.GasSchedule; + +main() { + assert(GasSchedule.instruction_table_size() == 53, 0); + assert(GasSchedule.native_table_size() == 0, 0); + return; +} diff --git a/language/functional_tests/tests/testsuite/gas_schedule/gas_schedule_resource_exists.mvir b/language/functional_tests/tests/testsuite/gas_schedule/gas_schedule_resource_exists.mvir new file mode 100644 index 000000000000..2bc4f4992a9b --- /dev/null +++ b/language/functional_tests/tests/testsuite/gas_schedule/gas_schedule_resource_exists.mvir @@ -0,0 +1,6 @@ +import 0x0.GasSchedule; + +main() { + assert(GasSchedule.has_gas_schedule_resource(), 0); + return; +} diff --git a/language/stdlib/modules/gas_schedule.mvir b/language/stdlib/modules/gas_schedule.mvir new file mode 100644 index 000000000000..9e085b652a8c --- /dev/null +++ b/language/stdlib/modules/gas_schedule.mvir @@ -0,0 +1,277 @@ +// The gas schedule keeps two separate schedules for the gas: +// * The instruction_schedule: This holds the gas for each bytecode instruction. +// * The native_schedule: This holds the gas for used (per-byte operated over) for each native +// function. +// A couple notes: +// 1. In the case that an instruction is deleted from the bytecode, that part of the cost schedule +// still needs to remain the same; once a slot in the table is taken by an instruction, that is its +// slot for the rest of time (since that instruction could already exist in a module on-chain). +// 2. The initialization of the module will publish the instruction table to the association +// address, and will preload the vector with the gas schedule for instructions. The VM will then +// load this into memory at the startup of each block. +module GasSchedule { + import 0x0.Vector; + + // The gas cost for each instruction is represented using two amounts; + // one for the cpu, and the other for storage. + struct Cost { + cpu: u64, + storage: u64, + } + + resource T { + instruction_schedule: Vector.T, + native_schedule: Vector.T, + } + + // Initialize the table under the association account + initialize() { + let instruction_table: Vector.T; + assert(get_txn_sender() == 0xA550C18, 0); + instruction_table = Vector.empty(); + + // NB: DO NOT change the order of these pushes to the vector. + Vector.push_back( + &mut instruction_table, + Cost { cpu: 27, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 28, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 31, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 29, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 10, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 29, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 36, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 52, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 29, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 30, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 41, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 41, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 28, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 45, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 45, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 58, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 58, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 56, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 197, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 73, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 94, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 51, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 65, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 45, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 44, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 41, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 42, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 41, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 45, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 44, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 46, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 43, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 49, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 35, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 48, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 51, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 49, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 46, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 47, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 46, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 39, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 29, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 34, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 32, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 30, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 856, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 929, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 929, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 917, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 774, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 29, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 41, storage: 1 } + ); + Vector.push_back( + &mut instruction_table, + Cost { cpu: 10, storage: 1 } + ); + + move_to_sender(T { + instruction_schedule: move(instruction_table), + native_schedule: Vector.empty(), + }); + return; + } + + public new_cost(cpu: u64, storage: u64): Self.Cost { + return Cost {cpu: move(cpu), storage: move(storage) }; + } + + public has_gas_schedule_resource(): bool { + return exists(0xA550C18); + } + + public instruction_table_size(): u64 acquires T { + let table: &Self.T; + let instruction_table_len: u64; + table = borrow_global(0xA550C18); + instruction_table_len = Vector.length(&move(table).instruction_schedule); + return move(instruction_table_len); + } + + public native_table_size(): u64 acquires T { + let table: &Self.T; + let native_table_len: u64; + table = borrow_global(0xA550C18); + native_table_len = Vector.length(&move(table).native_schedule); + return move(native_table_len); + } +} diff --git a/language/stdlib/src/stdlib.rs b/language/stdlib/src/stdlib.rs index e9571c49e2a4..d245b8818458 100644 --- a/language/stdlib/src/stdlib.rs +++ b/language/stdlib/src/stdlib.rs @@ -35,6 +35,7 @@ lazy_static! { static ref TRANSACTION_FEE_DISTRIBUTION_MODULE: ModuleDefinition = make_module_definition!("../modules/transaction_fee_distribution.mvir"); static ref EVENT_MODULE: ModuleDefinition = make_module_definition!("../modules/event.mvir"); + static ref GAS_SCHEDULE: ModuleDefinition = make_module_definition!("../modules/gas_schedule.mvir"); static ref MODULE_DEFS: Vec<&'static ModuleDefinition> = { // Note: a module can depend on earlier modules in the list, but not vice versa. Don't try // to rearrange without considering this! @@ -47,6 +48,7 @@ lazy_static! { &*U64_UTIL_MODULE, &*VECTOR_MODULE, &*VALIDATOR_CONFIG_MODULE, + &*GAS_SCHEDULE, // depends on Vector &*EVENT_MODULE, // depends on AddressUtil, BytearrayUtil, Hash, U64Util &*ACCOUNT_MODULE, // depends on LibraCoin, Event, AddressUtil, BytearrayUtil, U64Util &*LIBRA_SYSTEM_MODULE, // depends on LibraAccount, ValidatorConfig diff --git a/language/tools/cost-synthesis/src/main.rs b/language/tools/cost-synthesis/src/main.rs index cde37ccfc8da..296fffadd408 100644 --- a/language/tools/cost-synthesis/src/main.rs +++ b/language/tools/cost-synthesis/src/main.rs @@ -30,7 +30,7 @@ use vm::{ FunctionDefinitionIndex, FunctionHandleIndex, StructDefinitionIndex, UserStringIndex, NO_TYPE_ACTUALS, }, - gas_schedule::{AbstractMemorySize, GasAlgebra, GasCarrier}, + gas_schedule::{AbstractMemorySize, CostTable, GasAlgebra, GasCarrier}, transaction_metadata::TransactionMetadata, }; use vm_cache_map::Arena; diff --git a/language/tools/cost-synthesis/src/vm_runner.rs b/language/tools/cost-synthesis/src/vm_runner.rs index 3314ac770e03..805b769655ae 100644 --- a/language/tools/cost-synthesis/src/vm_runner.rs +++ b/language/tools/cost-synthesis/src/vm_runner.rs @@ -39,9 +39,11 @@ macro_rules! with_loaded_vm { for (access_path, blob) in $root_account.generate_resources(&mut inhabitor).into_iter() { data_cache.set(access_path, blob); } + let gas_schedule = CostTable::zero(); let txn_data = TransactionMetadata::default(); let data_cache = TransactionDataCache::new(&data_cache); - let mut $vm = InterpreterForCostSynthesis::new(&$module_cache, txn_data, data_cache); + let mut $vm = + InterpreterForCostSynthesis::new(&$module_cache, txn_data, data_cache, &gas_schedule); $vm.turn_off_gas_metering(); $vm.push_frame(entry_func, vec![]); }; diff --git a/language/tools/test-generation/src/lib.rs b/language/tools/test-generation/src/lib.rs index fce5ce28f044..91e811bdf372 100644 --- a/language/tools/test-generation/src/lib.rs +++ b/language/tools/test-generation/src/lib.rs @@ -43,7 +43,6 @@ fn run_verifier(module: CompiledModule) -> Result { } /// This function runs a verified module in the VM runtime -/// This code is based on `cost-synthesis/src/vm_runner.rs` fn run_vm(module: VerifiedModule) -> Result<(), String> { let modules = ::stdlib::stdlib_modules().to_vec(); // The standard library modules are bounded diff --git a/language/vm/src/gas_schedule.rs b/language/vm/src/gas_schedule.rs index 777c209e4228..ef3ba4f8227e 100644 --- a/language/vm/src/gas_schedule.rs +++ b/language/vm/src/gas_schedule.rs @@ -11,12 +11,12 @@ use crate::{ AddressPoolIndex, ByteArrayPoolIndex, Bytecode, FieldDefinitionIndex, FunctionHandleIndex, StructDefinitionIndex, UserStringIndex, NO_TYPE_ACTUALS, NUMBER_OF_BYTECODE_INSTRUCTIONS, }, - serializer::serialize_instruction, + file_format_common::Opcodes, }; use lazy_static::lazy_static; -use libra_types::transaction::MAX_TRANSACTION_SIZE_IN_BYTES; +use libra_types::{identifier::Identifier, transaction::MAX_TRANSACTION_SIZE_IN_BYTES}; +use serde::{Deserialize, Serialize}; use std::{ - collections::HashMap, ops::{Add, Div, Mul, Sub}, u64, }; @@ -105,7 +105,7 @@ macro_rules! define_gas_unit { carrier: $carrier: ty, doc: $comment: literal } => { - #[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)] + #[derive(Debug, Hash, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)] #[doc=$comment] pub struct $name(GasCarrier); impl GasAlgebra<$carrier> for $name<$carrier> { @@ -137,11 +137,6 @@ define_gas_unit! { doc: "A newtype wrapper around the gas price for each unit of gas consumed." } -/// A newtype wrapper around the on-chain representation of an instruction key. This is the -/// serialization of the instruction but disregarding any instruction arguments. -#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)] -pub struct InstructionKey(pub u8); - lazy_static! { /// The cost per-byte written to global storage. /// TODO: Fill this in with a proper number once it's determined. @@ -189,164 +184,240 @@ lazy_static! { /// Any transaction over this size will be charged `INTRINSIC_GAS_PER_BYTE` per byte pub static ref LARGE_TRANSACTION_CUTOFF: AbstractMemorySize = AbstractMemorySize::new(600); + + pub static ref GAS_SCHEDULE_NAME: Identifier = Identifier::new("T").unwrap(); +} + +/// The encoding of the instruction is the serialized form of it, but disregarding the +/// serialization of the instruction's argument(s). +pub fn instruction_key(instruction: &Bytecode) -> u8 { + use Bytecode::*; + let opcode = match instruction { + Pop => Opcodes::POP, + Ret => Opcodes::RET, + BrTrue(_) => Opcodes::BR_TRUE, + BrFalse(_) => Opcodes::BR_FALSE, + Branch(_) => Opcodes::BRANCH, + LdConst(_) => Opcodes::LD_CONST, + LdStr(_) => Opcodes::LD_STR, + LdByteArray(_) => Opcodes::LD_BYTEARRAY, + LdAddr(_) => Opcodes::LD_ADDR, + LdTrue => Opcodes::LD_TRUE, + LdFalse => Opcodes::LD_FALSE, + CopyLoc(_) => Opcodes::COPY_LOC, + MoveLoc(_) => Opcodes::MOVE_LOC, + StLoc(_) => Opcodes::ST_LOC, + Call(_, _) => Opcodes::CALL, + Pack(_, _) => Opcodes::PACK, + Unpack(_, _) => Opcodes::UNPACK, + ReadRef => Opcodes::READ_REF, + WriteRef => Opcodes::WRITE_REF, + FreezeRef => Opcodes::FREEZE_REF, + MutBorrowLoc(_) => Opcodes::MUT_BORROW_LOC, + ImmBorrowLoc(_) => Opcodes::IMM_BORROW_LOC, + MutBorrowField(_) => Opcodes::MUT_BORROW_FIELD, + ImmBorrowField(_) => Opcodes::IMM_BORROW_FIELD, + MutBorrowGlobal(_, _) => Opcodes::MUT_BORROW_GLOBAL, + ImmBorrowGlobal(_, _) => Opcodes::IMM_BORROW_GLOBAL, + Add => Opcodes::ADD, + Sub => Opcodes::SUB, + Mul => Opcodes::MUL, + Mod => Opcodes::MOD, + Div => Opcodes::DIV, + BitOr => Opcodes::BIT_OR, + BitAnd => Opcodes::BIT_AND, + Xor => Opcodes::XOR, + Or => Opcodes::OR, + And => Opcodes::AND, + Not => Opcodes::NOT, + Eq => Opcodes::EQ, + Neq => Opcodes::NEQ, + Lt => Opcodes::LT, + Gt => Opcodes::GT, + Le => Opcodes::LE, + Ge => Opcodes::GE, + Abort => Opcodes::ABORT, + GetTxnGasUnitPrice => Opcodes::GET_TXN_GAS_UNIT_PRICE, + GetTxnMaxGasUnits => Opcodes::GET_TXN_MAX_GAS_UNITS, + GetGasRemaining => Opcodes::GET_GAS_REMAINING, + GetTxnSenderAddress => Opcodes::GET_TXN_SENDER, + Exists(_, _) => Opcodes::EXISTS, + MoveFrom(_, _) => Opcodes::MOVE_FROM, + MoveToSender(_, _) => Opcodes::MOVE_TO, + GetTxnSequenceNumber => Opcodes::GET_TXN_SEQUENCE_NUMBER, + GetTxnPublicKey => Opcodes::GET_TXN_PUBLIC_KEY, + }; + opcode as u8 } /// The cost tables, keyed by the serialized form of the bytecode instruction. We use the /// serialized form as opposed to the instruction enum itself as the key since this will be the /// on-chain representation of bytecode instructions in the future. -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct CostTable { - pub compute_table: HashMap>, - pub memory_table: HashMap>, -} - -impl InstructionKey { - /// The encoding of the instruction is the serialized form of it, but disregarding the - /// serializtion of the instructions arguments. - pub fn new(instruction: &Bytecode) -> Self { - let mut vec = Vec::new(); - serialize_instruction(&mut vec, instruction).unwrap(); - Self(vec[0]) - } + pub instruction_table: Vec, + // TODO: The native table needs to be populated + pub native_table: Vec, } impl CostTable { - pub fn new(instrs: Vec<(Bytecode, u64, u64)>) -> Self { - let mut compute_table = HashMap::new(); - let mut memory_table = HashMap::new(); - let mut instructions_covered = 0; - for (instr, comp_cost, mem_cost) in instrs.into_iter() { - let code = InstructionKey::new(&instr); - if cfg!(debug_assertions) && compute_table.get(&code).is_none() { - instructions_covered += 1; + pub fn new(mut instrs: Vec<(Bytecode, GasCost)>) -> Self { + instrs.sort_by_key(|cost| instruction_key(&cost.0)); + + if cfg!(debug_assertions) { + let mut instructions_covered = 0; + for (index, (instr, _)) in instrs.iter().enumerate() { + let key = instruction_key(instr); + if index == (key - 1) as usize { + instructions_covered += 1; + } } - compute_table.insert(code, GasUnits::new(comp_cost)); - memory_table.insert(code, GasUnits::new(mem_cost)); + debug_assert!( + instructions_covered == NUMBER_OF_BYTECODE_INSTRUCTIONS, + "all instructions must be in the cost table" + ); } - debug_assert!( - instructions_covered == NUMBER_OF_BYTECODE_INSTRUCTIONS, - "all instructions must be in the cost table" - ); + + let instruction_table = instrs + .into_iter() + .map(|(_, cost)| cost) + .collect::>(); + // TODO: populate the native table Self { - compute_table, - memory_table, + instruction_table, + native_table: Vec::new(), } } - pub fn memory_gas( + pub fn get_gas( &self, instr: &Bytecode, size_provider: AbstractMemorySize, - ) -> GasUnits { - let code = InstructionKey::new(instr); - let memory_cost = self.memory_table.get(&code); - // CostTable initialization checks that every instruction is included in the memory_table - assume!(memory_cost.is_some()); - memory_cost.unwrap().map2(size_provider, Mul::mul) - } - - pub fn comp_gas( - &self, - instr: &Bytecode, - size_provider: AbstractMemorySize, - ) -> GasUnits { - let code = InstructionKey::new(instr); - let compute_cost = self.compute_table.get(&code); - // CostTable initialization checks that every instruction is included in the compute_table - assume!(compute_cost.is_some()); - compute_cost.unwrap().map2(size_provider, Mul::mul) + ) -> GasCost { + // NB: instruction keys are 1-indexed. This means that their location in the cost array + // will be the key - 1. + let key = instruction_key(instr); + let cost = self.instruction_table.get((key - 1) as usize); + assume!(cost.is_some()); + let good_cost = cost.unwrap(); + GasCost { + instruction_gas: good_cost.instruction_gas.map2(size_provider, Mul::mul), + memory_gas: good_cost.memory_gas.map2(size_provider, Mul::mul), + } } -} -lazy_static! { - static ref GAS_SCHEDULE: CostTable = { + // Only used for genesis, cost synthesis (for now) and for tests where we need a cost table and + // don't have a genesis storage state. + pub fn zero() -> Self { use Bytecode::*; - // Arguments to the instructions don't matter -- these will be removed in the - // `encode_instruction` function. - // - // The second element of the tuple is the computational cost. The third element of the - // tuple is the memory cost per-byte for the instruction. - // TODO: At the moment the computational cost is correct, and the memory cost is not - // correct at all (hence why they're all 1's at the moment). + // The actual costs for the instructions in this table _DO NOT MATTER_. This is only used + // for genesis, cost synthesis, and testing, and for these cases we don't need to worry + // about the actual gas for instructions. The only thing we care about is having an entry + // in the gas schedule for each instruction. let instrs = vec![ - (MoveToSender(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), 774, 1), - (GetTxnSenderAddress, 30, 1), - (MoveFrom(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), 917, 1), - (BrTrue(0), 31, 1), - (WriteRef, 65, 1), - (Mul, 41, 1), - (MoveLoc(0), 41, 1), - (And, 49, 1), - (GetTxnPublicKey, 41, 1), - (Pop, 27, 1), - (BitAnd, 44, 1), - (ReadRef, 51, 1), - (Sub, 44, 1), - (MutBorrowField(FieldDefinitionIndex::new(0)), 58, 1), - (ImmBorrowField(FieldDefinitionIndex::new(0)), 58, 1), - (Add, 45, 1), - (CopyLoc(0), 41, 1), - (StLoc(0), 28, 1), - (Ret, 28, 1), - (Lt, 49, 1), - (LdConst(0), 29, 1), - (Abort, 39, 1), - (MutBorrowLoc(0), 45, 1), - (ImmBorrowLoc(0), 45, 1), - (LdStr(UserStringIndex::new(0)), 52, 1), - (LdAddr(AddressPoolIndex::new(0)), 36, 1), - (Ge, 46, 1), - (Xor, 46, 1), - (Neq, 51, 1), - (Not, 35,1), - (Call(FunctionHandleIndex::new(0), NO_TYPE_ACTUALS), 197, 1), - (Le, 47, 1), - (Branch(0), 10, 1), - (Unpack(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), 94, 1), - (Or, 43, 1), - (LdFalse, 30, 1), - (LdTrue, 29, 1), - (GetTxnGasUnitPrice, 29, 1), - (Mod, 42, 1), - (BrFalse(0), 29, 1), - (Exists(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), 856, 1), - (GetGasRemaining, 32, 1), - (BitOr, 45, 1), - (GetTxnMaxGasUnits, 34, 1), - (GetTxnSequenceNumber, 29, 1), - (FreezeRef, 10, 1), - (MutBorrowGlobal(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), 929, 1), - (ImmBorrowGlobal(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), 929, 1), - (Div, 41, 1), - (Eq, 48, 1), - (LdByteArray(ByteArrayPoolIndex::new(0)), 56, 1), - (Gt, 46, 1), - (Pack(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), 73, 1), + ( + MoveToSender(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), + GasCost::new(0, 0), + ), + (GetTxnSenderAddress, GasCost::new(0, 0)), + ( + MoveFrom(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), + GasCost::new(0, 0), + ), + (BrTrue(0), GasCost::new(0, 0)), + (WriteRef, GasCost::new(0, 0)), + (Mul, GasCost::new(0, 0)), + (MoveLoc(0), GasCost::new(0, 0)), + (And, GasCost::new(0, 0)), + (GetTxnPublicKey, GasCost::new(0, 0)), + (Pop, GasCost::new(0, 0)), + (BitAnd, GasCost::new(0, 0)), + (ReadRef, GasCost::new(0, 0)), + (Sub, GasCost::new(0, 0)), + ( + MutBorrowField(FieldDefinitionIndex::new(0)), + GasCost::new(0, 0), + ), + ( + ImmBorrowField(FieldDefinitionIndex::new(0)), + GasCost::new(0, 0), + ), + (Add, GasCost::new(0, 0)), + (CopyLoc(0), GasCost::new(0, 0)), + (StLoc(0), GasCost::new(0, 0)), + (Ret, GasCost::new(0, 0)), + (Lt, GasCost::new(0, 0)), + (LdConst(0), GasCost::new(0, 0)), + (Abort, GasCost::new(0, 0)), + (MutBorrowLoc(0), GasCost::new(0, 0)), + (ImmBorrowLoc(0), GasCost::new(0, 0)), + (LdStr(UserStringIndex::new(0)), GasCost::new(0, 0)), + (LdAddr(AddressPoolIndex::new(0)), GasCost::new(0, 0)), + (Ge, GasCost::new(0, 0)), + (Xor, GasCost::new(0, 0)), + (Neq, GasCost::new(0, 0)), + (Not, GasCost::new(0, 0)), + ( + Call(FunctionHandleIndex::new(0), NO_TYPE_ACTUALS), + GasCost::new(0, 0), + ), + (Le, GasCost::new(0, 0)), + (Branch(0), GasCost::new(0, 0)), + ( + Unpack(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), + GasCost::new(0, 0), + ), + (Or, GasCost::new(0, 0)), + (LdFalse, GasCost::new(0, 0)), + (LdTrue, GasCost::new(0, 0)), + (GetTxnGasUnitPrice, GasCost::new(0, 0)), + (Mod, GasCost::new(0, 0)), + (BrFalse(0), GasCost::new(0, 0)), + ( + Exists(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), + GasCost::new(0, 0), + ), + (GetGasRemaining, GasCost::new(0, 0)), + (BitOr, GasCost::new(0, 0)), + (GetTxnMaxGasUnits, GasCost::new(0, 0)), + (GetTxnSequenceNumber, GasCost::new(0, 0)), + (FreezeRef, GasCost::new(0, 0)), + ( + MutBorrowGlobal(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), + GasCost::new(0, 0), + ), + ( + ImmBorrowGlobal(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), + GasCost::new(0, 0), + ), + (Div, GasCost::new(0, 0)), + (Eq, GasCost::new(0, 0)), + (LdByteArray(ByteArrayPoolIndex::new(0)), GasCost::new(0, 0)), + (Gt, GasCost::new(0, 0)), + ( + Pack(StructDefinitionIndex::new(0), NO_TYPE_ACTUALS), + GasCost::new(0, 0), + ), ]; CostTable::new(instrs) - }; + } } /// The `GasCost` tracks: /// - instruction cost: how much time/computational power is needed to perform the instruction /// - memory cost: how much memory is required for the instruction, and storage overhead -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct GasCost { pub instruction_gas: GasUnits, pub memory_gas: GasUnits, } -/// Statically cost a bytecode instruction. -/// -/// Don't take into account current stack or memory size. Don't track whether references are to -/// global or local storage. -pub fn static_cost_instr( - instr: &Bytecode, - size_provider: AbstractMemorySize, -) -> GasCost { - GasCost { - instruction_gas: GAS_SCHEDULE.comp_gas(instr, size_provider), - memory_gas: GAS_SCHEDULE.memory_gas(instr, size_provider), +impl GasCost { + pub fn new(instr_gas: GasCarrier, mem_gas: GasCarrier) -> Self { + Self { + instruction_gas: GasUnits::new(instr_gas), + memory_gas: GasUnits::new(mem_gas), + } } } diff --git a/language/vm/src/serializer.rs b/language/vm/src/serializer.rs index f5a254c82e94..83340bf46ca0 100644 --- a/language/vm/src/serializer.rs +++ b/language/vm/src/serializer.rs @@ -554,14 +554,6 @@ fn serialize_code_unit(binary: &mut BinaryData, code: &CodeUnit) -> Result<()> { serialize_code(binary, &code.code) } -/// Serializes a single `Bytecode` instruction into a vector -pub(crate) fn serialize_instruction(binary: &mut Vec, opcode: &Bytecode) -> Result<()> { - let mut binary_data = BinaryData::from(binary.clone()); - serialize_instruction_inner(&mut binary_data, opcode)?; - *binary = binary_data.into_inner(); - Ok(()) -} - /// Serializes a single `Bytecode` instruction. fn serialize_instruction_inner(binary: &mut BinaryData, opcode: &Bytecode) -> Result<()> { let res = match opcode { diff --git a/language/vm/vm-genesis/genesis/genesis.blob b/language/vm/vm-genesis/genesis/genesis.blob index 72f24c3c2d573fec5ae7aa1ea60b585c1c0e8c4b..597a6ccdcd276276e44d7df790a95ca8819f4824 100644 GIT binary patch delta 3024 zcmbVOTWC~A7@mL5EqjZVgtlsGmW`msBy}4#!50%1WQ#>miPi_#b$gm!yFDBBoUKHK z^WuX~;u3G5P_GD$O1+|pmlg$|ER=wtwc2-!eQ31>EB=$stjRw)gm#jdbH4fJ`_DhO z|KGEF;osAsxBhb)D#@lw{5@#DkbD`bbT#`{fs}=bRZnapivfjkYJrXGi8ey*rZHWL$lqCeG4_Sfhuq31N*L!$ut8`bXHz#A^JB&2_B$Ikee>FQMN7 zFF_r?h5y2?D8591Gxn>2Z(>n60u2~w96)@~0A_M#X{=6zm=5&@Of!Llh5zUbN~$j? z69R0oZLzA&(3l}M~I*YwCav3+>kJTNMq)WD2ESIwO`+3LpO69DVFFI*2 zv$bHST~tnYHfFs1H}<$LEjv)QT>MU^-);A8$PZF;FQF^gc(EtqGD6K*ay&VK}$lnm?_p&#z&Dnn1n(`9!Zr6*2q|`YGl+N&-hipgU!inIbKxN$ounO z7-fC@+S17gsG19AWExO4!Hi4|RgJitH5X)-P|c+#-#y$SnK@LAjIb)x`_7v9K9-sb z!E==fK{Xe=5i;upYkZaZKQ72*pjwl8N@Yefj+nnnwTv#xWQswQ~3WolD39rAaE zO38GmGQo3|DNxk}-Y?%w1kN=c$OKq_lf)3;~z@mo`Ed(CJG1Ga21u+cf zTvsSfQnwDbQ$j+Q9{ynf@{hRntZHZql lzs|l~{@u-MofnS|?^@&E9O)QLJy_x7KYxDY+wLVJ@D{JG-<|*f delta 102 zcmV-s0Ga>&r2(vt0R$?jjscM|9FZUp0V}aF_6w7c0wA;O4+9>v8#baGKvcMT>;Quf zW++4KU84vA*~Z5sy`bmSVFm)Sa!jqOW( @@ -30,25 +31,14 @@ pub fn execute_user_transaction_block<'alloc>( script_cache: &ScriptCache<'alloc>, data_view: &dyn StateView, publishing_option: &VMPublishingOption, -) -> Vec { +) -> Result, VMStatus> { trace!("[VM] Execute block, transaction count: {}", txn_block.len()); report_block_count(txn_block.len()); let mode = if data_view.is_genesis() { // The genesis transaction must be in a block of its own. if txn_block.len() != 1 { - // XXX Need a way to return that an entire block failed. - return txn_block - .iter() - .map(|_| { - TransactionOutput::new( - WriteSet::default(), - vec![], - 0, - TransactionStatus::from(VMStatus::new(StatusCode::REJECTED_WRITE_SET)), - ) - }) - .collect(); + return Err(VMStatus::new(StatusCode::REJECTED_WRITE_SET)); } else { ValidationMode::Genesis } @@ -60,6 +50,18 @@ pub fn execute_user_transaction_block<'alloc>( let mut data_cache = BlockDataCache::new(data_view); let mut result = vec![]; + // If we fail to load the gas schedule, then we fail to process the block. + let gas_schedule = match load_gas_schedule(&module_cache, &data_cache) { + // TODO/XXX: This is a hack to get around not having proper writesets yet. Once that gets + // in remove this line. + Err(_) if data_view.is_genesis() => CostTable::zero(), + Err(_) => { + return Err(VMStatus::new(StatusCode::VM_STARTUP_FAILURE) + .with_sub_status(sub_status::VSF_GAS_SCHEDULE_NOT_FOUND)) + } + Ok(cost_table) => cost_table, + }; + let signature_verified_block: Vec> = txn_block .into_par_iter() .map(|txn| { @@ -78,6 +80,7 @@ pub fn execute_user_transaction_block<'alloc>( &data_cache, mode, publishing_option, + &gas_schedule, ), Err(vm_status) => ExecutedTransaction::discard_error_output(vm_status), }; @@ -93,7 +96,7 @@ pub fn execute_user_transaction_block<'alloc>( } } trace!("[VM] Execute block finished"); - result + Ok(result) } /// Process a transaction and emit a TransactionOutput. @@ -116,12 +119,13 @@ fn transaction_flow<'alloc, P>( data_cache: &BlockDataCache<'_>, mode: ValidationMode, publishing_option: &VMPublishingOption, + gas_schedule: &CostTable, ) -> TransactionOutput where P: ModuleCache<'alloc>, { let arena = Arena::new(); - let process_txn = ProcessTransaction::new(txn, &module_cache, data_cache, &arena); + let process_txn = ProcessTransaction::new(txn, gas_schedule, &module_cache, data_cache, &arena); let validated_txn = record_stats! {time_hist | TXN_VALIDATION_TIME_TAKEN | { match process_txn.validate(mode, publishing_option) { diff --git a/language/vm/vm-runtime/src/code_cache/module_cache.rs b/language/vm/vm-runtime/src/code_cache/module_cache.rs index 66063a732b48..2e329e8e7038 100644 --- a/language/vm/vm-runtime/src/code_cache/module_cache.rs +++ b/language/vm/vm-runtime/src/code_cache/module_cache.rs @@ -229,10 +229,7 @@ impl<'alloc> VMModuleCache<'alloc> { let struct_def_module_id = StructHandleView::new(module, struct_handle).module_id(); match self.get_loaded_module_with_fetcher(&struct_def_module_id, fetcher) { Ok(Some(module)) => { - let struct_def_idx = module - .struct_defs_table - .get(struct_name) - .ok_or_else(|| VMStatus::new(StatusCode::LINKER_ERROR))?; + let struct_def_idx = module.get_struct_def_index(struct_name)?; self.resolve_struct_def_with_fetcher(module, *struct_def_idx, gas_meter, fetcher) } Ok(None) => Ok(None), diff --git a/language/vm/vm-runtime/src/gas_meter.rs b/language/vm/vm-runtime/src/gas_meter.rs index 137c5e6f0310..4adea9746433 100644 --- a/language/vm/vm-runtime/src/gas_meter.rs +++ b/language/vm/vm-runtime/src/gas_meter.rs @@ -2,19 +2,81 @@ // SPDX-License-Identifier: Apache-2.0 //! Gas metering logic for the Move VM. -use crate::{interpreter::InterpreterForGasCost, loaded_data::function::FunctionReference}; +use crate::{ + code_cache::module_cache::ModuleCache, + data_cache::RemoteCache, + identifier::{create_access_path, resource_storage_key}, + interpreter::InterpreterForGasCost, + loaded_data::function::FunctionReference, +}; use libra_types::{ account_address::ADDRESS_LENGTH, + account_config, + identifier::Identifier, + language_storage::ModuleId, transaction::MAX_TRANSACTION_SIZE_IN_BYTES, - vm_error::{StatusCode, VMStatus}, + vm_error::{sub_status, StatusCode, VMStatus}, }; use vm::{access::ModuleAccess, errors::VMResult, file_format::Bytecode, gas_schedule::*}; +//*************************************************************************** +// Gas Schedule Loading +//*************************************************************************** + +lazy_static! { + /// The ModuleId for the gas schedule module + pub static ref GAS_SCHEDULE_MODULE: ModuleId = + { ModuleId::new(account_config::core_code_address(), Identifier::new("GasSchedule").unwrap()) }; +} + +pub(crate) fn load_gas_schedule<'alloc, P>( + module_cache: P, + data_view: &dyn RemoteCache, +) -> VMResult +where + P: ModuleCache<'alloc>, +{ + let address = account_config::association_address(); + let gas_module = module_cache + .get_loaded_module(&GAS_SCHEDULE_MODULE)? + .ok_or_else(|| { + VMStatus::new(StatusCode::GAS_SCHEDULE_ERROR) + .with_sub_status(sub_status::GSE_UNABLE_TO_LOAD_MODULE) + })?; + + let gas_struct_def_idx = gas_module.get_struct_def_index(&GAS_SCHEDULE_NAME)?; + let struct_tag = resource_storage_key(gas_module, *gas_struct_def_idx); + let access_path = create_access_path(&address, struct_tag); + + let data_blob = data_view + .get(&access_path) + .map_err(|_| { + VMStatus::new(StatusCode::GAS_SCHEDULE_ERROR) + .with_sub_status(sub_status::GSE_UNABLE_TO_LOAD_RESOURCE) + })? + .ok_or_else(|| { + VMStatus::new(StatusCode::GAS_SCHEDULE_ERROR) + .with_sub_status(sub_status::GSE_UNABLE_TO_LOAD_RESOURCE) + })?; + let table: CostTable = lcs::from_bytes(&data_blob).map_err(|_| { + VMStatus::new(StatusCode::GAS_SCHEDULE_ERROR) + .with_sub_status(sub_status::GSE_UNABLE_TO_DESERIALIZE) + })?; + Ok(table) +} + +//*************************************************************************** +// Gas Metering Logic +//*************************************************************************** + /// Holds the state of the gas meter. -pub struct GasMeter { +pub struct GasMeter<'txn> { // The current amount of gas that is left ("unburnt gas") in the gas meter. current_gas_left: GasUnits, + // The gas schedule used for computing the gas cost per bytecode instruction. + gas_schedule: &'txn CostTable, + // We need to disable and enable gas metering for both the prologue and epilogue of the Account // contract. The VM will then internally unset/set this flag before executing either of them. meter_on: bool, @@ -23,11 +85,12 @@ pub struct GasMeter { // NB: A number of the functions/methods in this struct will return a VMResult // since we will need to access stack and memory states, and we need to be able // to report errors properly from these accesses. -impl GasMeter { +impl<'txn> GasMeter<'txn> { /// Create a new gas meter with starting gas amount `gas_amount` - pub fn new(gas_amount: GasUnits) -> Self { + pub fn new(gas_amount: GasUnits, gas_schedule: &'txn CostTable) -> Self { GasMeter { current_gas_left: gas_amount, + gas_schedule, meter_on: true, } } @@ -72,7 +135,7 @@ impl GasMeter { } /// A wrapper that calculates and then consumes the gas unless metering is disabled. - pub fn calculate_and_consume<'a, 'alloc, 'txn>( + pub fn calculate_and_consume<'a, 'alloc>( &mut self, instr: &Bytecode, interpreter: InterpreterForGasCost<'a, 'alloc, 'txn>, @@ -91,7 +154,7 @@ impl GasMeter { /// Calculate the gas usage for an instruction taking into account the current stack state, and /// the size of memory that is being accessed. - pub fn gas_for_instruction<'a, 'alloc, 'txn>( + pub fn gas_for_instruction<'a, 'alloc>( &mut self, instr: &Bytecode, interpreter: InterpreterForGasCost<'a, 'alloc, 'txn>, @@ -143,7 +206,7 @@ impl GasMeter { // value stack. Because of this, the cost of the instruction is not dependent upon the // size of the value being returned. | Bytecode::Ret => { - let default_gas = static_cost_instr(instr, AbstractMemorySize::new(1)); + let default_gas = self.gas_schedule.get_gas(instr, AbstractMemorySize::new(1)); Self::gas_of(default_gas) } Bytecode::Eq @@ -151,18 +214,18 @@ impl GasMeter { let lhs_size = interpreter.peek()?.size(); let rhs_size = interpreter.peek_at(1)?.size(); let max_size = lhs_size.map2(rhs_size, std::cmp::max); - Self::gas_of(static_cost_instr(instr, max_size)) + Self::gas_of(self.gas_schedule.get_gas(instr, max_size)) } Bytecode::LdAddr(_) => { let size = AbstractMemorySize::new(ADDRESS_LENGTH as GasCarrier); - let default_gas = static_cost_instr(instr, size); + let default_gas = self.gas_schedule.get_gas(instr, size); Self::gas_of(default_gas) } Bytecode::LdByteArray(idx) => { let byte_array_ref = interpreter.module().byte_array_at(*idx); let byte_array_len = AbstractMemorySize::new(byte_array_ref.len() as GasCarrier); let byte_array_len = words_in(byte_array_len); - let default_gas = static_cost_instr(instr, byte_array_len); + let default_gas = self.gas_schedule.get_gas(instr, byte_array_len); Self::gas_of(default_gas) } // We charge by the length of the string being stored on the stack. @@ -170,7 +233,7 @@ impl GasMeter { let string_ref = interpreter.module().user_string_at(*idx); let str_len = AbstractMemorySize::new(string_ref.len() as GasCarrier); let str_len = words_in(str_len); - let default_gas = static_cost_instr(instr, str_len); + let default_gas = self.gas_schedule.get_gas(instr, str_len); Self::gas_of(default_gas) } Bytecode::StLoc(_) => { @@ -178,14 +241,14 @@ impl GasMeter { let local = interpreter.peek()?; // Get the size of the local let size = local.size(); - let default_gas = static_cost_instr(instr, size); + let default_gas = self.gas_schedule.get_gas(instr, size); Self::gas_of(default_gas) } // Note that a moveLoc incurs a copy overhead Bytecode::CopyLoc(local_idx) | Bytecode::MoveLoc(local_idx) => { let local = interpreter.copy_loc(*local_idx)?; let size = local.size(); - let default_gas = static_cost_instr(instr, size); + let default_gas = self.gas_schedule.get_gas(instr, size); Self::gas_of(default_gas) } Bytecode::Call(call_idx, _) => { @@ -197,13 +260,13 @@ impl GasMeter { GasUnits::new(0) // This will be costed at the call site/by the native function } else { let call_size = AbstractMemorySize::new(function_ref.arg_count() as GasCarrier); - let call_gas = static_cost_instr(instr, call_size); + let call_gas = self.gas_schedule.get_gas(instr, call_size); Self::gas_of(call_gas) } } Bytecode::Unpack(_, _) => { let size = interpreter.peek()?.size(); - Self::gas_of(static_cost_instr(instr, size)) + Self::gas_of(self.gas_schedule.get_gas(instr, size)) } Bytecode::Pack(struct_idx, _) => { let struct_def = interpreter.module().struct_def_at(*struct_idx); @@ -214,7 +277,7 @@ impl GasMeter { let arg_count = AbstractMemorySize::new(u64::from(member_count)); let total_size = arg_count.add(*STRUCT_SIZE); - let new_gas = static_cost_instr(instr, total_size); + let new_gas = self.gas_schedule.get_gas(instr, total_size); Self::gas_of(new_gas) } Bytecode::WriteRef => { @@ -224,7 +287,7 @@ impl GasMeter { let ref_val = interpreter.peek()?; // Get the size of this value and charge accordingly. let size = write_val.size(); - let mut default_gas = static_cost_instr(instr, size); + let mut default_gas = self.gas_schedule.get_gas(instr, size); // Determine if the reference is global. If so charge for any expansion of global // memory along with the write operation that will be incurred. if ref_val.is_global_ref() { @@ -249,7 +312,7 @@ impl GasMeter { // from global memory that is performed by a BorrowGlobal operation. After this, // all ReadRefs will be reading from local cache and we don't need to distinguish. let size = interpreter.peek()?.size(); - let default_gas = static_cost_instr(instr, size); + let default_gas = self.gas_schedule.get_gas(instr, size); Self::gas_of(default_gas) } // Note that we charge twice for these operations; once at the start of @@ -278,7 +341,7 @@ impl GasMeter { } else { AbstractMemorySize::new(0) // We already charged for size 1 }; - Self::gas_of(static_cost_instr(instr, mem_size)) + Self::gas_of(self.gas_schedule.get_gas(instr, mem_size)) } }; Ok(instruction_reqs) diff --git a/language/vm/vm-runtime/src/interpreter.rs b/language/vm/vm-runtime/src/interpreter.rs index 93acccb9e855..085b18be4a7c 100644 --- a/language/vm/vm-runtime/src/interpreter.rs +++ b/language/vm/vm-runtime/src/interpreter.rs @@ -36,7 +36,7 @@ use vm::{ Bytecode, FunctionHandleIndex, LocalIndex, LocalsSignatureIndex, SignatureToken, StructDefinitionIndex, }, - gas_schedule::{AbstractMemorySize, GasAlgebra, GasCarrier, GasUnits}, + gas_schedule::{AbstractMemorySize, CostTable, GasAlgebra, GasCarrier, GasUnits}, transaction_metadata::TransactionMetadata, IndexKind, }; @@ -86,7 +86,7 @@ where /// The stack of active functions. call_stack: CallStack<'txn>, /// Gas metering to track cost of execution. - gas_meter: GasMeter, + gas_meter: GasMeter<'txn>, /// Transaction data to resolve special bytecodes (e.g. GetTxnSequenceNumber, GetTxnPublicKey, /// GetTxnSenderAddress, ...) txn_data: TransactionMetadata, @@ -161,11 +161,12 @@ where module_cache: P, txn_data: TransactionMetadata, data_view: TransactionDataCache<'txn>, + gas_schedule: &'txn CostTable, ) -> Self { Interpreter { operand_stack: Stack::new(), call_stack: CallStack::new(), - gas_meter: GasMeter::new(txn_data.max_gas_amount()), + gas_meter: GasMeter::new(txn_data.max_gas_amount(), gas_schedule), txn_data, event_data: vec![], data_view, @@ -1198,8 +1199,9 @@ where module_cache: P, txn_data: TransactionMetadata, data_view: TransactionDataCache<'txn>, + gas_schedule: &'txn CostTable, ) -> Self { - let interpreter = Interpreter::new(module_cache, txn_data, data_view); + let interpreter = Interpreter::new(module_cache, txn_data, data_view, gas_schedule); InterpreterForCostSynthesis(interpreter) } diff --git a/language/vm/vm-runtime/src/lib.rs b/language/vm/vm-runtime/src/lib.rs index b891d419ad3e..0076cfea8ba3 100644 --- a/language/vm/vm-runtime/src/lib.rs +++ b/language/vm/vm-runtime/src/lib.rs @@ -131,7 +131,6 @@ pub use move_vm::MoveVM; pub use process_txn::verify::static_verify_program; pub use txn_executor::execute_function; -use failure::prelude::*; use libra_config::config::VMConfig; use libra_state_view::StateView; use libra_types::{ @@ -163,5 +162,5 @@ pub trait VMExecutor { transactions: Vec, config: &VMConfig, state_view: &dyn StateView, - ) -> Result>; + ) -> Result, VMStatus>; } diff --git a/language/vm/vm-runtime/src/loaded_data/loaded_module.rs b/language/vm/vm-runtime/src/loaded_data/loaded_module.rs index 9d370a8ff35e..a37b869481b2 100644 --- a/language/vm/vm-runtime/src/loaded_data/loaded_module.rs +++ b/language/vm/vm-runtime/src/loaded_data/loaded_module.rs @@ -5,7 +5,7 @@ use crate::loaded_data::function::FunctionDef; use bytecode_verifier::VerifiedModule; use libra_types::{ - identifier::Identifier, + identifier::{IdentStr, Identifier}, vm_error::{StatusCode, VMStatus}, }; use std::{collections::HashMap, sync::RwLock}; @@ -25,7 +25,6 @@ use vm_runtime_types::loaded_data::struct_def::StructDef; #[derive(Debug, Eq, PartialEq)] pub struct LoadedModule { module: VerifiedModule, - #[allow(dead_code)] pub struct_defs_table: HashMap, #[allow(dead_code)] pub field_defs_table: HashMap, @@ -151,6 +150,12 @@ impl LoadedModule { .cloned() .ok_or_else(|| VMStatus::new(StatusCode::LINKER_ERROR)) } + + pub fn get_struct_def_index(&self, struct_name: &IdentStr) -> VMResult<&StructDefinitionIndex> { + self.struct_defs_table + .get(struct_name) + .ok_or_else(|| VMStatus::new(StatusCode::LINKER_ERROR)) + } } // Compile-time test to ensure that this struct stays thread-safe. diff --git a/language/vm/vm-runtime/src/move_vm.rs b/language/vm/vm-runtime/src/move_vm.rs index e729b3913bc4..95c0f8c8cde1 100644 --- a/language/vm/vm-runtime/src/move_vm.rs +++ b/language/vm/vm-runtime/src/move_vm.rs @@ -5,13 +5,13 @@ use crate::{ counters::*, loaded_data::loaded_module::LoadedModule, runtime::VMRuntime, VMExecutor, VMVerifier, }; -use failure::prelude::*; use libra_state_view::StateView; use libra_types::{ transaction::{SignedTransaction, Transaction, TransactionOutput}, vm_error::VMStatus, }; use std::sync::Arc; +use vm::errors::VMResult; use vm_cache_map::Arena; rental! { @@ -66,7 +66,7 @@ impl VMExecutor for MoveVM { transactions: Vec, config: &VMConfig, state_view: &dyn StateView, - ) -> Result> { + ) -> VMResult> { let vm = MoveVMImpl::new(Box::new(Arena::new()), |arena| { // XXX This means that scripts and modules are NOT tested against the whitelist! This // needs to be fixed. diff --git a/language/vm/vm-runtime/src/process_txn/mod.rs b/language/vm/vm-runtime/src/process_txn/mod.rs index 7c97d80a3743..74b7938eb119 100644 --- a/language/vm/vm-runtime/src/process_txn/mod.rs +++ b/language/vm/vm-runtime/src/process_txn/mod.rs @@ -8,7 +8,7 @@ use crate::{ use libra_config::config::VMPublishingOption; use libra_types::transaction::SignatureCheckedTransaction; use std::marker::PhantomData; -use vm::errors::VMResult; +use vm::{errors::VMResult, gas_schedule::CostTable}; use vm_cache_map::Arena; pub mod execute; @@ -25,6 +25,7 @@ where P: ModuleCache<'alloc>, { txn: SignatureCheckedTransaction, + gas_schedule: &'txn CostTable, module_cache: P, data_cache: &'txn dyn RemoteCache, allocator: &'txn Arena, @@ -39,12 +40,14 @@ where /// Creates a new instance of `ProcessTransaction`. pub fn new( txn: SignatureCheckedTransaction, + gas_schedule: &'txn CostTable, module_cache: P, data_cache: &'txn dyn RemoteCache, allocator: &'txn Arena, ) -> Self { Self { txn, + gas_schedule, module_cache, data_cache, allocator, diff --git a/language/vm/vm-runtime/src/process_txn/validate.rs b/language/vm/vm-runtime/src/process_txn/validate.rs index 69db7be5c64c..005816697b1e 100644 --- a/language/vm/vm-runtime/src/process_txn/validate.rs +++ b/language/vm/vm-runtime/src/process_txn/validate.rs @@ -20,7 +20,7 @@ use libra_types::{ }; use vm::{ errors::convert_prologue_runtime_error, - gas_schedule::{self, AbstractMemorySize, GasAlgebra, GasCarrier}, + gas_schedule::{self, AbstractMemorySize, CostTable, GasAlgebra, GasCarrier}, transaction_metadata::TransactionMetadata, }; use vm_cache_map::Arena; @@ -75,6 +75,7 @@ where ) -> Result { let ProcessTransaction { txn, + gas_schedule, module_cache, data_cache, allocator, @@ -85,6 +86,7 @@ where TransactionPayload::Program(program) => { Some(ValidatedTransaction::validate( &txn, + gas_schedule, module_cache, data_cache, allocator, @@ -110,6 +112,7 @@ where TransactionPayload::Script(script) => { Some(ValidatedTransaction::validate( &txn, + gas_schedule, module_cache, data_cache, allocator, @@ -128,6 +131,7 @@ where debug!("validate module {:?}", module); Some(ValidatedTransaction::validate( &txn, + gas_schedule, module_cache, data_cache, allocator, @@ -193,6 +197,7 @@ where fn validate( txn: &SignatureCheckedTransaction, + gas_schedule: &'txn CostTable, module_cache: P, data_cache: &'txn dyn RemoteCache, allocator: &'txn Arena, @@ -303,8 +308,13 @@ where payload_check()?; let metadata = TransactionMetadata::new(&txn); - let mut txn_state = - ValidatedTransactionState::new(metadata, module_cache, data_cache, allocator); + let mut txn_state = ValidatedTransactionState::new( + metadata, + gas_schedule, + module_cache, + data_cache, + allocator, + ); // Run the prologue to ensure that clients have enough gas and aren't tricking us by // sending us garbage. @@ -351,13 +361,15 @@ where { fn new( metadata: TransactionMetadata, + gas_schedule: &'txn CostTable, module_cache: P, data_cache: &'txn dyn RemoteCache, allocator: &'txn Arena, ) -> Self { // This temporary cache is used for modules published by a single transaction. let txn_module_cache = TransactionModuleCache::new(module_cache, allocator); - let txn_executor = TransactionExecutor::new(txn_module_cache, data_cache, metadata); + let txn_executor = + TransactionExecutor::new(txn_module_cache, gas_schedule, data_cache, metadata); Self { txn_executor } } } diff --git a/language/vm/vm-runtime/src/runtime.rs b/language/vm/vm-runtime/src/runtime.rs index 98cefec700a1..5bde6cf9a181 100644 --- a/language/vm/vm-runtime/src/runtime.rs +++ b/language/vm/vm-runtime/src/runtime.rs @@ -10,19 +10,20 @@ use crate::{ }, counters::report_verification_status, data_cache::BlockDataCache, + gas_meter::load_gas_schedule, loaded_data::loaded_module::LoadedModule, process_txn::{validate::ValidationMode, ProcessTransaction}, }; -use failure::prelude::*; use libra_config::config::{VMConfig, VMPublishingOption}; use libra_logger::prelude::*; use libra_state_view::StateView; use libra_types::{ block_metadata::BlockMetadata, transaction::{SignedTransaction, Transaction, TransactionOutput}, - vm_error::{StatusCode, VMStatus}, + vm_error::{sub_status, StatusCode, VMStatus}, write_set::WriteSet, }; +use vm::{errors::VMResult, gas_schedule::CostTable}; use vm_cache_map::Arena; /// An instantiation of the MoveVM. @@ -75,14 +76,33 @@ impl<'alloc> VMRuntime<'alloc> { BlockModuleCache::new(&self.code_cache, ModuleFetcherImpl::new(data_view)); let data_cache = BlockDataCache::new(data_view); + // If we fail to load the gas schedule, then we fail to process the block. + let gas_schedule = match load_gas_schedule(&module_cache, &data_cache) { + // TODO/XXX: This is a hack to get around not having proper writesets yet. Once that gets + // in remove this line. + Err(_) if data_view.is_genesis() => CostTable::zero(), + Ok(cost_table) => cost_table, + Err(_error) => { + return Some( + VMStatus::new(StatusCode::VM_STARTUP_FAILURE) + .with_sub_status(sub_status::VSF_GAS_SCHEDULE_NOT_FOUND), + ) + } + }; + let arena = Arena::new(); let signature_verified_txn = match txn.check_signature() { Ok(t) => t, Err(_) => return Some(VMStatus::new(StatusCode::INVALID_SIGNATURE)), }; - let process_txn = - ProcessTransaction::new(signature_verified_txn, module_cache, &data_cache, &arena); + let process_txn = ProcessTransaction::new( + signature_verified_txn, + &gas_schedule, + module_cache, + &data_cache, + &arena, + ); let mode = if data_view.is_genesis() { ValidationMode::Genesis } else { @@ -114,7 +134,7 @@ impl<'alloc> VMRuntime<'alloc> { &self, txn_block: Vec, data_view: &dyn StateView, - ) -> Result> { + ) -> VMResult> { let mut result = vec![]; let blocks = chunk_block_transactions(txn_block); for block in blocks { @@ -126,7 +146,7 @@ impl<'alloc> VMRuntime<'alloc> { &self.script_cache, data_view, &self.publishing_option, - )) + )?) } // TODO: Implement the logic for processing system transactions. TransactionBlock::BlockPrologue(_) => unimplemented!(""), diff --git a/language/vm/vm-runtime/src/txn_executor.rs b/language/vm/vm-runtime/src/txn_executor.rs index 652f670b984b..a4f64a1c0594 100644 --- a/language/vm/vm-runtime/src/txn_executor.rs +++ b/language/vm/vm-runtime/src/txn_executor.rs @@ -3,9 +3,13 @@ //! Processor for a single transaction. use crate::{ - code_cache::module_cache::{ModuleCache, VMModuleCache}, + code_cache::{ + module_adapter::ModuleFetcherImpl, + module_cache::{BlockModuleCache, ModuleCache, VMModuleCache}, + }, counters::*, - data_cache::{RemoteCache, TransactionDataCache}, + data_cache::{BlockDataCache, RemoteCache, TransactionDataCache}, + gas_meter::load_gas_schedule, interpreter::Interpreter, loaded_data::{ function::{FunctionRef, FunctionReference}, @@ -13,6 +17,7 @@ use crate::{ }, }; use bytecode_verifier::{VerifiedModule, VerifiedScript}; +use libra_state_view::StateView; use libra_types::{ account_address::AccountAddress, account_config, @@ -23,12 +28,14 @@ use libra_types::{ write_set::WriteSet, }; use vm::{ - errors::*, file_format::CompiledScript, transaction_metadata::TransactionMetadata, - vm_string::VMString, + errors::*, file_format::CompiledScript, gas_schedule::CostTable, + transaction_metadata::TransactionMetadata, vm_string::VMString, }; use vm_cache_map::Arena; use vm_runtime_types::value::Value; +pub use crate::gas_meter::GAS_SCHEDULE_MODULE; + // Metadata needed for resolving the account module. lazy_static! { /// The ModuleId for the Account module @@ -84,6 +91,7 @@ where /// transactions within the same block. pub fn new( module_cache: P, + gas_schedule: &'txn CostTable, data_cache: &'txn dyn RemoteCache, txn_data: TransactionMetadata, ) -> Self { @@ -92,6 +100,7 @@ where module_cache, txn_data, TransactionDataCache::new(data_cache), + gas_schedule, ), } } @@ -275,21 +284,25 @@ pub fn execute_function( caller_script: VerifiedScript, modules: Vec, args: Vec, - data_cache: &dyn RemoteCache, + data_view: &dyn StateView, ) -> VMResult<()> { + let txn_metadata = TransactionMetadata::default(); let allocator = Arena::new(); - let module_cache = VMModuleCache::new(&allocator); + let code_cache = VMModuleCache::new(&allocator); + for m in modules { + code_cache.cache_module(m); + } let main_module = caller_script.into_module(); let loaded_main = LoadedModule::new(main_module); let entry_func = FunctionRef::new(&loaded_main, CompiledScript::MAIN_INDEX); - let txn_metadata = TransactionMetadata::default(); - for m in modules { - module_cache.cache_module(m); - } + let module_cache = BlockModuleCache::new(&code_cache, ModuleFetcherImpl::new(data_view)); + let data_cache = BlockDataCache::new(data_view); + let gas_schedule = load_gas_schedule(&module_cache, &data_cache)?; let mut interpreter = Interpreter::new( module_cache, txn_metadata, - TransactionDataCache::new(data_cache), + TransactionDataCache::new(&data_cache), + &gas_schedule, ); interpreter.interpeter_entrypoint(entry_func, convert_txn_args(args)) } diff --git a/language/vm/vm-runtime/src/unit_tests/module_cache_tests.rs b/language/vm/vm-runtime/src/unit_tests/module_cache_tests.rs index 11ac969122f7..0637a1cc1cf3 100644 --- a/language/vm/vm-runtime/src/unit_tests/module_cache_tests.rs +++ b/language/vm/vm-runtime/src/unit_tests/module_cache_tests.rs @@ -24,7 +24,7 @@ use libra_types::{ use vm::{ access::ModuleAccess, file_format::*, - gas_schedule::{GasAlgebra, GasUnits}, + gas_schedule::{CostTable, GasAlgebra, GasUnits}, }; use vm_cache_map::Arena; use vm_runtime_types::loaded_data::{struct_def::StructDef, types::Type}; @@ -527,7 +527,8 @@ fn test_same_module_struct_resolution() { { let module_id = ModuleId::new(AccountAddress::default(), ident("M1")); let module_ref = block_cache.get_loaded_module(&module_id).unwrap().unwrap(); - let gas = GasMeter::new(GasUnits::new(100_000_000)); + let gas_schedule = CostTable::zero(); + let gas = GasMeter::new(GasUnits::new(100_000_000), &gas_schedule); let struct_x = block_cache .resolve_struct_def(module_ref, StructDefinitionIndex::new(0), &gas) .unwrap() @@ -570,6 +571,7 @@ fn test_multi_module_struct_resolution() { let module = parse_and_compile_modules(&code); let fetcher = FakeFetcher::new(module); let block_cache = BlockModuleCache::new(&vm_cache, fetcher); + let gas_schedule = CostTable::zero(); { let module_id_2 = ModuleId::new(AccountAddress::default(), ident("M2")); let module2_ref = block_cache @@ -577,7 +579,7 @@ fn test_multi_module_struct_resolution() { .unwrap() .unwrap(); - let gas = GasMeter::new(GasUnits::new(100_000_000)); + let gas = GasMeter::new(GasUnits::new(100_000_000), &gas_schedule); let struct_t = block_cache .resolve_struct_def(module2_ref, StructDefinitionIndex::new(0), &gas) .unwrap() diff --git a/terraform/validator-sets/100/genesis.blob b/terraform/validator-sets/100/genesis.blob index 3f5a8441fccfc7a022e0e6bec6314ce1e30d310f..90a66b1b776966c7ea779f1d2f0d55834f98c810 100644 GIT binary patch delta 4245 zcmbVPYit}>6+Y+QJ2N{wGwa=*_1bRi*s-0saT3RNS~Wo`Nq7_)fdoy}Ru!<>Y?gRi zXKi_Rsh~pp2M~>@Dr;#LsjU=RL7}C*+!oPFyp&XkA4q|sA|z{yQ_T-N@H9@^2nk8F5z* zWmZPi>WAO?U0yi|T-A3F5&z=Rv(Mb~!pDby^phK2|MP|8ch1BY=B~eV^O5(={P>}7 zf9CTqGAi9JiLS?Y8;X9kO+SO}Pe&gP=|dqszD2JSg)5A2sX%mHNDtTF9>RCOjlN~T z=%4n59K!lLLU>p|7{V`v^tq7!u}!+}pAKv?Xz^%B4-aT>2oKMA@@?=FxBI7Xhxjb;{%h>Jz zO@APSn;|_spzsm}r@&}+xVnD$(_a<+vT@Jl@q>D9-Wj~UhLV^?1--$HJ0X-Is9!@E zfnfg=f`Z_zLm1@7iu?qWq`^6YJBU; zd`zAyR%NAFZRB~hap;fhI~*pV406e3UvO5qOwusP6)lV6zUD>2yH@Xbxhm?lzpjrz z_QWGk|1|O3-rvq#dhptp&c85shw;qohy5qMbla0-7%9DE5q)6pT{j=S^@DLHhg-9P zT`c3}p-8hnGHgiQnLL4T|v^Uf)MiPom8u5bRf(>J)<%|2s@B1cwFD z?+i$KWf23COe_Vn4jf`{3bG`{BzuYz2KSgO*uozM1DCb{H|?N<*fQz35woo*0Ef%Va%j(^Wl`7v7FO^HxQhuTI zsbZ{LygOHbbznYU$(=%}Y`El>i;|G6sE_wqJT|HxWuE) zbw>i`iu!a%0(NWr=#$wQLO^uU=H{-xdRU@>{GvVqNkoyOZ?(2@Uw4MkeXaqgMVsrs z5CL&^_o+vr|7QpRpG8{>s5XkUNK?;03I+TdMFJ*{BHhCsuyfRB3Kwkai9!KmN0IJx z)l8?Ks884PRX0c5K38!X>56nelsc=`Pjm=D2Cx;W>s#yUkVb;EV5^fxf&?Lobe*et zm5o~8xH!AqbS~k}&9Ioo9SzPBQnhFs#|1hrZb=qT0h5eJ+0dq0c&L*SzQrCcbD<)T z3?Rg*`76Prt3UtK`Gk1C-T30a#*cjzR(Bn*42qY;75^-R#Lpny4Z%1H*7Ol&bqjd- zI4ssv5Hu6mPeVvTh+lx9?_uX<2zl9X)`se%m;mntwaRIF7F6wvP4GG>;toWu&zM&ItfH7h)qI%l7xj#CxkJ9 zYWpR5${|Ok?Ch>l<&t@?ztOuReUC;}n#?nS$z?|+<9#mbC+$90;^XweH`q`f#84tF9jraX!Zt)A7{Pqqo4sopG7NW%w^z%espJ~`y~H9y3>e^)0Jaom{Q-ofNBP%aBjdA;Jb}+(^DE5nHIq8KiZ}npygh?{RopWi#u$d!6Rz-k znm86ak890HlWV9cj67*!6d5LeJ5G5D6A(_SGXcf|Fs87xwE!Ep9qo1)R|PhhNbCCU zz(yQ|BXk^^j|m>)?DsARepns3?eJ3*H$Hsk2Yl=w^uu$n*sJH~YTjS+-r#4?UNR=W px%NsqE><4e^NovN|M%ynKXTLYNp=6!nZh4_Ih$+H+H;q^{{TuDY~TO@ delta 880 zcmYk3OK2295QeL&dmcMIGdr`hJK5bljLEL9iR9q}L}C;~5L8eURD|FQ5jEgJMZF|~ z9wb>b%%MRHkL!Gi=3iU+}i2O$t|iDCjCL_ApQ3mdBY>%Y3HtGcUG^6m{O zJMP?&W4%pdtpUzH>ixPYc{>x0jpvfHn^Va;tkkCZuVL-o)(aE#gp!@}r47pgz`M%R zD4QQ3K#9Rv$8dr{-ePEFNIhkc4C*<uUN0Z@&Qu1WsL~;b%lI@>JgA7Is0Zg#T8NTB%D^|$>U@xS~ z3;~0)o}rOJ?PmBy`lN-;s)Z17IUiyVu&np_*p3-}(Z{Y_20dv+;E)4IrWJvLDALb->|T*YQe3*dmbDP# zD968-mQf5I^3nhv@sig;8n>A)M^j24N@Ghl}Lod1tDXd;Y+I4U@0nFLOi(2;>l;@CQW!MZo26q@eI4;Xt|rs(0NR zyT_{&wB0jR^>y`BS66q{K6USx&!RZ^@L60h4y+HQab*3$u@{crV&YZn56-U-x$D>F zHeNN);rc@Z(W|B%{^6OgoqOQ5&kgVPfAi}vs!~jOG7Y>@>IgoN6 z?K?Zb&<18$AYaHBaI;O@fGj%OH=gAR|}Lw^Qwp=9{x6!fMK0%wd#Pm=-8 zjph2W%2IH$xg40c1(n9in)v8Sy@9tTtF>yQT3)W+A0%qQeWeOX`OP&?IFieAoMw?1fv8|Xm_OqDK7IaIbBFyBN%)IA(WhT0=RLqbwg=SqVJQ70;nbk|X-Ca$BB z&Q>IB+ueOSb*pSuYU`-u|GywH|AZaasS7+lU3WxfvA9lmL}j+Njy#wh6I7CmPq(=9 z?$H*h#24352_lXhd4D$feQF&Oy02B`v-ot~H$o-N?mA%{`hQGNxhy`LO0#jKl`!S{ z zh^LF2gN4(Caik_};%1R~G$zA*3q88bnTSDLLFC`4nH$~s{lF_J_9;7x4vinXi-hha zdgQ2kX}aNmLPY9!MBGP&afEWFPlb`Vm9p@0l2|_`LM8(HMIzEfB+nBeuVLpcBFa2U zP7a2nWIyhu#L8!B23uwu%H~vZfQOJm=;4s&kP(nIz!#7+p;tiOfo|sIDsUALUiJ3GnX82&14`umqR?PW0pF@EOne~Ot)@T_h6Ls{&WWO(1A|X`T zr@Dfnh+dj}vZp2IhD1%O9yactYNDNAU0bOH^?E5fKfQk;McXI8fjrAby->cQH^+tYc>+nqtIdNQG~h|T``s7T0PU4saF7z`l{ zBaU!3#@V>w`xxKP_yOb;KeQCu-A-zjewS^+_tDR>9D})baU&oVWr_n{CwXVVV$oFb z`cP0xmt`ImdlU%Zr-4}%Sd1C_X~+x*PdMO+jGf;%DqJuh_BTJ_aNmPbkp=S+C34Xb z>14sB@oBr@a(X!h(oN($$D+>_=|q7gWEBMlP{A=v+nmTVTS;1iWS%9r3G6(hVx+Cp z8sW$?mG+ftUW#%G8yV%M-%IgMVqqO>i!-Lj$3v$x;)#_U%nzR7NW^p=Z1`t4h@BcHtM&yQP| jZ~FTO7k=>ifltgl_Pq;>w)x%nzWA#T4}U0s=W*-5`}krQ delta 858 zcmYk3O=uHQ5XWccz5Phu?shl3-8M~9+cfD%Ds7_`qpda`6e$XdJqTVDL6jm=s3;Yw zf_kw9%R49{QZJ%{2Z;w!@B^y{&mM#->P=8=MeraVocM(c^WN{x?9BWBcV}nt+Jq=w zotP_U=h}Q+ai=_KqOHqjmtM9KdJwy7CF?BK#++{+-6Op%Q>e59fP03Q84k}!fC7Ut z$Z&u`US?=xNZec-iLX;HTOfG4vV$(t&>#i?OE_> z*w$ax7Iy9AMrWCqh{KgU;WmSJhv6`T(MkZiH36TYj0I>~C3E7dvf|5l?cDka=klmgrHHfq? zGQPSd%Nx^>hYm;ZDA-zy*&Iphc+|=q!C&OG6-43 zoS+qg+N8*ne!Zb3)qtT-5y7hZFa!dn?TuMo-Z(rhWDFMd3-E0h6~-<(pvbKItUt{i zG6Ltnvx7~c17z<1U56eEQ6LyRWI#Y*$7Hj2G%T=ifWQR@0y7Md5}cN1@9%VAx#>mzOzGnzeT^w}r4d4sl7M7dAt(q#{a~cOGeT0V+Fs460OGKb|5t+yV{nnTCUA+j zX6;uun-2FK33;j4#p8e*g`}ui=<@m zPb4Z($TEw0>3}KzdRFK_n8KL6`ui&V$15rN<&{4qIQ1UyzaEEczq)GWO;=>~;}fy? z;o1D+t=*NUqf;+l9$R{I@%`uNPphIw?e99y-R(;(=EuK!i9@^lx8FP&9f95xmxhPD Osj0_XUXGsYvVH