diff --git a/README.md b/README.md index c6183f6ab6183e..e2b9007b3dbd50 100644 --- a/README.md +++ b/README.md @@ -1,147 +1,2 @@ -

- - Solana - -

-[![Solana crate](https://img.shields.io/crates/v/solana-core.svg)](https://crates.io/crates/solana-core) -[![Solana documentation](https://docs.rs/solana-core/badge.svg)](https://docs.rs/solana-core) -[![Build status](https://badge.buildkite.com/8cc350de251d61483db98bdfc895b9ea0ac8ffa4a32ee850ed.svg?branch=master)](https://buildkite.com/solana-labs/solana/builds?branch=master) -[![codecov](https://codecov.io/gh/solana-labs/solana/branch/master/graph/badge.svg)](https://codecov.io/gh/solana-labs/solana) - -# Building - -## **1. Install rustc, cargo and rustfmt.** - -```bash -$ curl https://sh.rustup.rs -sSf | sh -$ source $HOME/.cargo/env -$ rustup component add rustfmt -``` - -When building the master branch, please make sure you are using the latest stable rust version by running: - -```bash -$ rustup update -``` - -When building a specific release branch, you should check the rust version in `ci/rust-version.sh` and if necessary, install that version by running: -```bash -$ rustup install VERSION -``` -Note that if this is not the latest rust version on your machine, cargo commands may require an [override](https://rust-lang.github.io/rustup/overrides.html) in order to use the correct version. - -On Linux systems you may need to install libssl-dev, pkg-config, zlib1g-dev, protobuf etc. - -On Ubuntu: -```bash -$ sudo apt-get update -$ sudo apt-get install libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang cmake make libprotobuf-dev protobuf-compiler -``` - -On Fedora: -```bash -$ sudo dnf install openssl-devel systemd-devel pkg-config zlib-devel llvm clang cmake make protobuf-devel protobuf-compiler perl-core -``` - -## **2. Download the source code.** - -```bash -$ git clone https://github.com/solana-labs/solana.git -$ cd solana -``` - -## **3. Build.** - -```bash -$ ./cargo build -``` - -# Testing - -**Run the test suite:** - -```bash -$ ./cargo test -``` - -### Starting a local testnet - -Start your own testnet locally, instructions are in the [online docs](https://docs.solanalabs.com/clusters/benchmark). - -### Accessing the remote development cluster - -* `devnet` - stable public cluster for development accessible via -devnet.solana.com. Runs 24/7. Learn more about the [public clusters](https://docs.solanalabs.com/clusters) - -# Benchmarking - -First, install the nightly build of rustc. `cargo bench` requires the use of the -unstable features only available in the nightly build. - -```bash -$ rustup install nightly -``` - -Run the benchmarks: - -```bash -$ cargo +nightly bench -``` - -# Release Process - -The release process for this project is described [here](RELEASE.md). - -# Code coverage - -To generate code coverage statistics: - -```bash -$ scripts/coverage.sh -$ open target/cov/lcov-local/index.html -``` - -Why coverage? While most see coverage as a code quality metric, we see it primarily as a developer -productivity metric. When a developer makes a change to the codebase, presumably it's a *solution* to -some problem. Our unit-test suite is how we encode the set of *problems* the codebase solves. Running -the test suite should indicate that your change didn't *infringe* on anyone else's solutions. Adding a -test *protects* your solution from future changes. Say you don't understand why a line of code exists, -try deleting it and running the unit-tests. The nearest test failure should tell you what problem -was solved by that code. If no test fails, go ahead and submit a Pull Request that asks, "what -problem is solved by this code?" On the other hand, if a test does fail and you can think of a -better way to solve the same problem, a Pull Request with your solution would most certainly be -welcome! Likewise, if rewriting a test can better communicate what code it's protecting, please -send us that patch! - -# Disclaimer - -All claims, content, designs, algorithms, estimates, roadmaps, -specifications, and performance measurements described in this project -are done with the Solana Labs, Inc. (“SL”) good faith efforts. It is up to -the reader to check and validate their accuracy and truthfulness. -Furthermore, nothing in this project constitutes a solicitation for -investment. - -Any content produced by SL or developer resources that SL provides are -for educational and inspirational purposes only. SL does not encourage, -induce or sanction the deployment, integration or use of any such -applications (including the code comprising the Solana blockchain -protocol) in violation of applicable laws or regulations and hereby -prohibits any such deployment, integration or use. This includes the use of -any such applications by the reader (a) in violation of export control -or sanctions laws of the United States or any other applicable -jurisdiction, (b) if the reader is located in or ordinarily resident in -a country or territory subject to comprehensive sanctions administered -by the U.S. Office of Foreign Assets Control (OFAC), or (c) if the -reader is or is working on behalf of a Specially Designated National -(SDN) or a person subject to similar blocking or denied party -prohibitions. - -The reader should be aware that U.S. export control and sanctions laws prohibit -U.S. persons (and other persons that are subject to such laws) from transacting -with persons in certain countries and territories or that are on the SDN list. -Accordingly, there is a risk to individuals that other persons using any of the -code contained in this repo, or a derivation thereof, may be sanctioned persons -and that transactions with such persons would be a violation of U.S. export -controls and sanctions law. +***THIS PROJECT IS ARCHIVED. PLEASE USE https://github.com/x1-labs/tachyon*** diff --git a/genesis/src/genesis_accounts.rs b/genesis/src/genesis_accounts.rs index 0704985913838f..007effb2102149 100644 --- a/genesis/src/genesis_accounts.rs +++ b/genesis/src/genesis_accounts.rs @@ -1,3 +1,4 @@ +#![allow(warnings)] use { crate::{ stakes::{create_and_add_stakes, StakerInfo}, @@ -7,22 +8,22 @@ use { }; // 9 month schedule is 100% after 9 months -const UNLOCKS_ALL_AT_9_MONTHS: UnlockInfo = UnlockInfo { - cliff_fraction: 1.0, - cliff_years: 0.75, - unlocks: 0, - unlock_years: 0.0, - custodian: "Mc5XB47H3DKJHym5RLa9mPzWv5snERsF3KNv5AauXK8", -}; +// const UNLOCKS_ALL_AT_9_MONTHS: UnlockInfo = UnlockInfo { +// cliff_fraction: 1.0, +// cliff_years: 0.75, +// unlocks: 0, +// unlock_years: 0.0, +// custodian: "Mc5XB47H3DKJHym5RLa9mPzWv5snERsF3KNv5AauXK8", +// }; // 9 month schedule is 50% after 9 months, then monthly for 2 years -const UNLOCKS_HALF_AT_9_MONTHS: UnlockInfo = UnlockInfo { - cliff_fraction: 0.5, - cliff_years: 0.75, - unlocks: 24, - unlock_years: 2.0, - custodian: "Mc5XB47H3DKJHym5RLa9mPzWv5snERsF3KNv5AauXK8", -}; +// const UNLOCKS_HALF_AT_9_MONTHS: UnlockInfo = UnlockInfo { +// cliff_fraction: 0.5, +// cliff_years: 0.75, +// unlocks: 24, +// unlock_years: 2.0, +// custodian: "Mc5XB47H3DKJHym5RLa9mPzWv5snERsF3KNv5AauXK8", +// }; // no lockups const UNLOCKS_ALL_DAY_ZERO: UnlockInfo = UnlockInfo { @@ -37,91 +38,91 @@ pub const CREATOR_STAKER_INFOS: &[StakerInfo] = &[ StakerInfo { name: "impossible pizza", staker: "uE3TVEffRp69mrgknYr71M18GDqL7GxCNGYYRjb3oUt", - lamports: 5_000_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("59SLqk4ete5QttM1WmjfMA7uNJnJVFLQqXJSy9rvuj7c"), }, StakerInfo { name: "nutritious examination", staker: "9noVEZreMmgQvE8iyKmxy7CGTJ2enELyuJ1qxFtXrfJB", - lamports: 5_000_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("ERnx3Csgu3LjrGGrCeCUZzuHguRu6XabT1kufSB1NDWi"), }, StakerInfo { name: "tidy impression", staker: "BU7LA4kYvicfPCp22EM2Tth3eaeWAXYo6yCgWXQFJ42z", - lamports: 5_000_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("5eKcGy7ZCPJdQSQGVnfmT7kGz6MKPMKaNaMEYJbmwhuT"), }, StakerInfo { name: "dramatic treatment", staker: "BrNFrFeuev8TosKhRe2kvVZTYrcUuYaqCfptWutxs17B", - lamports: 1_205_602 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("2pKqwFKfKj2nGrknPNDSP8vXGYrgAjd28fT6yLew8sT3"), }, StakerInfo { name: "angry noise", staker: "34HCVh8Yx4jNkaeLUQEKibFKUZDPQMjWzkXy8qUfdhS4", - lamports: 5_000_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("Hw3sP6PreBtFCnwXbNvUypMhty62GXibjfiZ1zHBXFk6"), }, StakerInfo { name: "hard cousin", staker: "AyZb3xrZE8wnS6gYBdsJg5v8CjyrX2ZGXU2zMakCFyYd", - lamports: 5_000_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("9j3WzBSZRHrD2DbzFTUVVi81QX6boVvUTpGWcSiMwD5W"), }, StakerInfo { name: "lopsided skill", staker: "7SbpY8LmZUb5XRqDbyoreUrSVVV9c39wkpEz81kEAXu5", - lamports: 5_000_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("EJyZGbQ1PmpcWxfqGME6SUNHfurh1zggDqCT7rV9xLzL"), }, StakerInfo { name: "red snake", staker: "C9CfFpmLDsQsz6wt7MrrZquNB5oS4QkpJkmDAiboVEZZ", - lamports: 3_655_292 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("JBGnGdLyo7V2z9hz51mnnbyDp9sBACtw5WYH9YRG8n7e"), }, StakerInfo { name: "jolly year", staker: "5WbxKiW7bghkr8JN6ZAv2TQt4PfJFvtuqNaN8gyQ5UzU", - lamports: 5_000_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("43XAfG3AFiF1ockdh7xp91fpFyZkbWSZq9ZFBCGUVV41"), }, StakerInfo { name: "typical initiative", staker: "Gc8XnHU6Nnriwt9RbEwi7PTosx4YanLyXak9GTbB8VaH", - lamports: 5_000_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("7s2GVwFo8VSrCwX9Tztt42ueiEaUtJ6zCEHU8XGvuf5E"), }, StakerInfo { name: "deserted window", staker: "AMmYEynkd78uNTZDFFrMw6NKjWTgqW7M8EFjvajk23VR", - lamports: 3_655_292 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("23PJYLS1WFLqhXnXq2Hobc17DbvZaoinoTZYLyGRT8E2"), }, StakerInfo { name: "eight nation", staker: "4qWoqt71p7h6siSDS6osu4oVWpw8R7E6uYYiY7Z6oJbH", - lamports: 103_519 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("6bFjx3egMjVsGKFb445564a4bwgibwbUB2tVFsJcdPv7"), }, StakerInfo { name: "earsplitting meaning", staker: "GYitoBY53E9awc56NWHJ2kxMwj4do5GSmvTRowjGaRDw", - lamports: 5_000_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("jXMEkVQQpoqebVMGN7DfpvdRLwJDEkoVNrwPVphNm7i"), }, StakerInfo { name: "alike cheese", staker: "Drg9uSvSEfjtn15jqmmrEQnA4pvU1ToYSGSa1Dv9C6Fk", - lamports: 3_880_295 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("BxmwgfnyAqZnqRCJGdsEea35pcc92GFTcyGeSj4RNfJJ"), }, StakerInfo { name: "noisy honey", staker: "95HsPFFvwbWpk42MKzenauSoULNzk8Tg6fc6EiJhLsUZ", - lamports: 5_000_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("Aj3K933zdRQhYEJi2Yjz8hJWXN3Z3hrKJQtPtE8VmUnq"), }, ]; @@ -130,37 +131,37 @@ pub const SERVICE_STAKER_INFOS: &[StakerInfo] = &[ StakerInfo { name: "wretched texture", staker: "B1hegzthtfNQxyEPzkESySxRjMidNqaxrzbQ28GaEwn8", - lamports: 225_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("HWzeqw1Yk5uiLgT2uGUim5ocFJNCwYUFbeCtDVpx9yUb"), }, StakerInfo { name: "unbecoming silver", staker: "4AcoZa1P8fF5XK21RJsiuMRZPEScbbWNc75oakRFHiBz", - lamports: 28_800 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: None, }, StakerInfo { name: "inexpensive uncle", staker: "AkJ7yssRqS3X4UWLUsPTxbP6LfVgdPYBWH4Jgk5EETgZ", - lamports: 300_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("6mudxxoe5VyXXNXsJ3NSGSTGESfG2t86PBCQGbouHpXX"), }, StakerInfo { name: "hellish money", staker: "4DVkqvRP8y26JvzNwsnQEQuC7HASwpGs58GsAT9XJMVg", - lamports: 200_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("ASJpWZAxY96kbciLqzb7sg45gsH32yPzGcxjn7HPcARn"), }, StakerInfo { name: "full grape", staker: "B2EWnwgmNd3KMpD71yZMijhML1jd4TYp96zJdhMiWZ7b", - lamports: 450_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("9oaCkokBBhgBsgyg4sL7fMJyQseaJb1TbADZeoPdpWdc"), }, StakerInfo { name: "nice ghost", staker: "HtQS1CH3nsUHmnLpenj5W6KHzFWTf3mzCn1mTqK7LkB7", - lamports: 650_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("4YnNnycEZXCkuVs2hDthdNxMD4E8wc7ZPgyAK7Lm1uZc"), }, ]; @@ -169,13 +170,13 @@ pub const FOUNDATION_STAKER_INFOS: &[StakerInfo] = &[ StakerInfo { name: "lyrical supermarket", staker: "4xh7vtQCTim3vgpQ1dQQWjtKrBSkbtL3s15FimXVJAAP", - lamports: 5_000_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("C7WS9ic7KN9XNcLsNoMvzTvbzURM3rFGDEQN7qJMWNLn"), }, StakerInfo { name: "frequent description", staker: "95Nf8XfoecteSXU9nbcvzkrFQdu6FqPaH3EvhwLaC83t", - lamports: 57_500_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("FdGYQdiRky8NZzN9wZtczTBcWLYYRXrJ3LMDhqDPn5rM"), }, ]; @@ -184,13 +185,13 @@ pub const GRANTS_STAKER_INFOS: &[StakerInfo] = &[ StakerInfo { name: "rightful agreement", staker: "8w5cgUQfXAZZWyVgenPHpQ1uABXUVLnymqXbuZPx7yqt", - lamports: 5_000_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("EDwSQShtUWQtmFfN9SpUUd6hgonL7tRdxngAsNKv9Pe6"), }, StakerInfo { name: "tasty location", staker: "9eyXtP43dCp59oyvWG2R7WQCeJ2bA6TWoLzXg1KTDfQQ", - lamports: 15_000_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("9BgvWHerNACjnx6ZpK51k2LEsnwBP3gFwWDzhKkHKH1m"), }, ]; @@ -199,19 +200,19 @@ pub const COMMUNITY_STAKER_INFOS: &[StakerInfo] = &[ StakerInfo { name: "shrill charity", staker: "Eo1iDtrZZiAkQFA8u431hedChaSUnPbU8MWg849MFvEZ", - lamports: 5_000_000 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("8CUUMKYNGxdgYio5CLHRHyzMEhhVRMcqefgE6dLqnVRK"), }, StakerInfo { name: "legal gate", staker: "7KCzZCbZz6V1U1YXUpBNaqQzQCg2DKo8JsNhKASKtYxe", - lamports: 30_301_032 * LAMPORTS_PER_SOL, + lamports: 1, withdrawer: Some("92viKFftk1dJjqJwreFqT2qHXxjSUuEE9VyHvTdY1mpY"), }, StakerInfo { name: "cluttered complaint", staker: "2J8mJU6tWg78DdQVEqMfpN3rMeNbcRT9qGL3yLbmSXYL", - lamports: 153_333_633 * LAMPORTS_PER_SOL + 41 * LAMPORTS_PER_SOL / 100, + lamports: 1, withdrawer: Some("7kgfDmgbEfypBujqn4tyApjf8H7ZWuaL3F6Ah9vQHzgR"), }, ]; @@ -231,24 +232,24 @@ pub fn add_genesis_accounts(genesis_config: &mut GenesisConfig, mut issued_lampo // add_stakes() and add_validators() award tokens for rent exemption and // to cover an initial transfer-free period of the network - issued_lamports += add_stakes( - genesis_config, - CREATOR_STAKER_INFOS, - &UNLOCKS_HALF_AT_9_MONTHS, - ) + add_stakes( - genesis_config, - SERVICE_STAKER_INFOS, - &UNLOCKS_ALL_AT_9_MONTHS, - ) + add_stakes( - genesis_config, - FOUNDATION_STAKER_INFOS, - &UNLOCKS_ALL_DAY_ZERO, - ) + add_stakes(genesis_config, GRANTS_STAKER_INFOS, &UNLOCKS_ALL_DAY_ZERO) - + add_stakes( - genesis_config, - COMMUNITY_STAKER_INFOS, - &UNLOCKS_ALL_DAY_ZERO, - ); + // issued_lamports += add_stakes( + // genesis_config, + // CREATOR_STAKER_INFOS, + // &UNLOCKS_HALF_AT_9_MONTHS, + // ) + add_stakes( + // genesis_config, + // SERVICE_STAKER_INFOS, + // &UNLOCKS_ALL_AT_9_MONTHS, + // ) + add_stakes( + // genesis_config, + // FOUNDATION_STAKER_INFOS, + // &UNLOCKS_ALL_DAY_ZERO, + // ) + add_stakes(genesis_config, GRANTS_STAKER_INFOS, &UNLOCKS_ALL_DAY_ZERO) + // + add_stakes( + // genesis_config, + // COMMUNITY_STAKER_INFOS, + // &UNLOCKS_ALL_DAY_ZERO, + // ); // "one thanks" (community pool) gets 500_000_000SOL (total) - above distributions create_and_add_stakes( @@ -256,7 +257,8 @@ pub fn add_genesis_accounts(genesis_config: &mut GenesisConfig, mut issued_lampo &StakerInfo { name: "one thanks", staker: "7vEAL3nS9CWmy1q6njUUyHE7Cf5RmyQpND6CsoHjzPiR", - lamports: (500_000_000 * LAMPORTS_PER_SOL).saturating_sub(issued_lamports), + //lamports: (500_000_000 * LAMPORTS_PER_SOL).saturating_sub(issued_lamports), + lamports: (100 * LAMPORTS_PER_SOL).saturating_sub(issued_lamports), withdrawer: Some("3FFaheyqtyAXZSYxDzsr5CVKvJuvZD1WE1VEsBtDbRqB"), }, &UNLOCKS_ALL_DAY_ZERO, diff --git a/genesis/src/stakes.rs b/genesis/src/stakes.rs index b1f545b218fa6f..3c628e5f2916a6 100644 --- a/genesis/src/stakes.rs +++ b/genesis/src/stakes.rs @@ -1,4 +1,7 @@ //! stakes generator +#![allow(warnings)] +#![allow(unused)] + use { crate::{ address_generator::AddressGenerator, @@ -34,10 +37,10 @@ pub struct StakerInfo { fn calculate_staker_fees(genesis_config: &GenesisConfig, years: f64) -> u64 { genesis_config.fee_rate_governor.max_lamports_per_signature * genesis_config.epoch_schedule.get_epoch(years_as_slots( - years, - &genesis_config.poh_config.target_tick_duration, - genesis_config.ticks_per_slot, - ) as Slot) + years, + &genesis_config.poh_config.target_tick_duration, + genesis_config.ticks_per_slot, + ) as Slot) } /// create stake accounts for lamports with at most stake_granularity in each @@ -51,6 +54,7 @@ pub fn create_and_add_stakes( // the largest each stake account should be, in lamports granularity: Option, ) -> u64 { + /* let granularity = granularity.unwrap_or(std::u64::MAX); let staker = &staker_info .staker @@ -161,6 +165,8 @@ pub fn create_and_add_stakes( } } total_lamports + */ + 10000 } #[cfg(test)] diff --git a/sdk/src/fee.rs b/sdk/src/fee.rs index f3377b5254f0a6..9fbf5fd925d75e 100644 --- a/sdk/src/fee.rs +++ b/sdk/src/fee.rs @@ -1,8 +1,431 @@ //! Fee structures. +//use std::time::{SystemTime, UNIX_EPOCH}; +use std::collections::{HashMap, HashSet}; +use std::str::FromStr; + +use lazy_static::lazy_static; use crate::native_token::sol_to_lamports; +use log::trace; +use solana_program::borsh1::try_from_slice_unchecked; +use solana_program::clock::{Epoch, Slot}; +use solana_program::epoch_schedule::EpochSchedule; +use solana_program::instruction::{CompiledInstruction, InstructionError}; + #[cfg(not(target_os = "solana"))] use solana_program::message::SanitizedMessage; +use solana_program::pubkey::Pubkey; +use crate::{compute_budget, feature_set}; +use crate::compute_budget::ComputeBudgetInstruction; +use crate::feature_set::{FEATURE_NAMES, full_inflation, FULL_INFLATION_FEATURE_PAIRS, include_loaded_accounts_data_size_in_fee_calculation, reduce_stake_warmup_cooldown}; +use crate::transaction::{TransactionError}; +// use crate::transaction::{SanitizedTransaction, TransactionError}; + +pub const COMPUTE_UNIT_TO_US_RATIO: u64 = 30; +pub const SIGNATURE_COST: u64 = COMPUTE_UNIT_TO_US_RATIO * 24; +/// Number of compute units for one secp256k1 signature verification. +pub const SECP256K1_VERIFY_COST: u64 = COMPUTE_UNIT_TO_US_RATIO * 223; +/// Number of compute units for one ed25519 signature verification. +pub const ED25519_VERIFY_COST: u64 = COMPUTE_UNIT_TO_US_RATIO * 76; +pub const WRITE_LOCK_UNITS: u64 = COMPUTE_UNIT_TO_US_RATIO * 10; +pub const DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT: u32 = 200_000; +pub const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; +pub const HEAP_LENGTH: usize = 32 * 1024; +const MAX_HEAP_FRAME_BYTES: u32 = 256 * 1024; +pub const MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES: u32 = 64 * 1024 * 1024; +pub const DEFAULT_HEAP_COST: u64 = 8; +pub const INSTRUCTION_DATA_BYTES_COST: u64 = 140 /*bytes per us*/ / COMPUTE_UNIT_TO_US_RATIO; + + +/// Value used to indicate that a serialized account is not a duplicate +pub const NON_DUP_MARKER: u8 = u8::MAX; + +lazy_static! { + pub static ref BUILT_IN_INSTRUCTION_COSTS: HashMap = [ + (Pubkey::from_str("Stake11111111111111111111111111111111111111").unwrap(), 750u64), + (Pubkey::from_str("Config1111111111111111111111111111111111111").unwrap(), 450u64), + (Pubkey::from_str("Vote111111111111111111111111111111111111111").unwrap(), 2_100u64), + (Pubkey::from_str("11111111111111111111111111111111").unwrap(), 150u64), + (Pubkey::from_str("ComputeBudget111111111111111111111111111111").unwrap(), 150u64), + (Pubkey::from_str("AddressLookupTab1e1111111111111111111111111").unwrap(), 750u64), + (Pubkey::from_str("BPFLoaderUpgradeab1e11111111111111111111111").unwrap(), 2_370u64), + (Pubkey::from_str("BPFLoader1111111111111111111111111111111111").unwrap(), 1_140u64), + (Pubkey::from_str("BPFLoader2111111111111111111111111111111111").unwrap(), 570u64), + (Pubkey::from_str("LoaderV411111111111111111111111111111111111").unwrap(), 2_000u64), + // Note: These are precompile, run directly in bank during sanitizing; + (Pubkey::from_str("KeccakSecp256k11111111111111111111111111111").unwrap(), 0u64), + (Pubkey::from_str("Ed25519SigVerify111111111111111111111111111").unwrap(), 0u64) + ] + .iter() + .cloned() + .collect(); +} + +#[derive(AbiExample, Debug, Clone, Eq, PartialEq)] +pub struct FeatureSet { + pub active: HashMap, + pub inactive: HashSet, +} +impl Default for FeatureSet { + fn default() -> Self { + // All features disabled + Self { + active: HashMap::new(), + inactive: FEATURE_NAMES.keys().cloned().collect(), + } + } +} +impl FeatureSet { + pub fn is_active(&self, feature_id: &Pubkey) -> bool { + self.active.contains_key(feature_id) + } + + pub fn activated_slot(&self, feature_id: &Pubkey) -> Option { + self.active.get(feature_id).copied() + } + + /// List of enabled features that trigger full inflation + pub fn full_inflation_features_enabled(&self) -> HashSet { + let mut hash_set = FULL_INFLATION_FEATURE_PAIRS + .iter() + .filter_map(|pair| { + if self.is_active(&pair.vote_id) && self.is_active(&pair.enable_id) { + Some(pair.enable_id) + } else { + None + } + }) + .collect::>(); + + if self.is_active(&full_inflation::devnet_and_testnet::id()) { + hash_set.insert(full_inflation::devnet_and_testnet::id()); + } + hash_set + } + + /// All features enabled, useful for testing + pub fn all_enabled() -> Self { + Self { + active: FEATURE_NAMES.keys().cloned().map(|key| (key, 0)).collect(), + inactive: HashSet::new(), + } + } + + /// Activate a feature + pub fn activate(&mut self, feature_id: &Pubkey, slot: u64) { + self.inactive.remove(feature_id); + self.active.insert(*feature_id, slot); + } + + /// Deactivate a feature + pub fn deactivate(&mut self, feature_id: &Pubkey) { + self.active.remove(feature_id); + self.inactive.insert(*feature_id); + } + + pub fn new_warmup_cooldown_rate_epoch(&self, epoch_schedule: &EpochSchedule) -> Option { + self.activated_slot(&reduce_stake_warmup_cooldown::id()) + .map(|slot| epoch_schedule.get_epoch(slot)) + } +} + +pub fn get_signature_cost_from_message(tx_cost: &mut UsageCostDetails, message: &SanitizedMessage) { + // Get the signature details from the message + let signatures_count_detail = message.get_signature_details(); + + // Set the details to the tx_cost structure + tx_cost.num_transaction_signatures = signatures_count_detail.num_transaction_signatures(); + tx_cost.num_secp256k1_instruction_signatures = signatures_count_detail.num_secp256k1_instruction_signatures(); + tx_cost.num_ed25519_instruction_signatures = signatures_count_detail.num_ed25519_instruction_signatures(); + + // Calculate the signature cost based on the number of signatures + tx_cost.signature_cost = signatures_count_detail + .num_transaction_signatures() + .saturating_mul(SIGNATURE_COST) + .saturating_add( + signatures_count_detail + .num_secp256k1_instruction_signatures() + .saturating_mul(SECP256K1_VERIFY_COST), + ) + .saturating_add( + signatures_count_detail + .num_ed25519_instruction_signatures() + .saturating_mul(ED25519_VERIFY_COST), + ); +} + +/* +fn get_writable_accounts(message: &SanitizedMessage) -> Vec { + message + .account_keys() + .iter() + .enumerate() + .filter_map(|(i, k)| { + if message.is_writable(i) { + Some(*k) + } else { + None + } + }) + .collect() +} +*/ + +fn get_write_lock_cost( + tx_cost: &mut UsageCostDetails, + message: &SanitizedMessage, + feature_set: &FeatureSet, +) { + tx_cost.writable_accounts = vec![]; //get_writable_accounts(transaction); + let num_write_locks = + if feature_set.is_active(&feature_set::cost_model_requested_write_lock_cost::id()) { + message.num_write_locks() + } else { + tx_cost.writable_accounts.len() as u64 + }; + tx_cost.write_lock_cost = WRITE_LOCK_UNITS.saturating_mul(num_write_locks); +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ComputeBudgetLimits { + pub updated_heap_bytes: u32, + pub compute_unit_limit: u32, + pub compute_unit_price: u64, + pub loaded_accounts_bytes: u32, +} + +fn sanitize_requested_heap_size(bytes: u32) -> bool { + (u32::try_from(HEAP_LENGTH).unwrap()..=MAX_HEAP_FRAME_BYTES).contains(&bytes) + && bytes % 1024 == 0 +} +pub fn process_compute_budget_instructions<'a>( + instructions: impl Iterator, +) -> Result { + let mut num_non_compute_budget_instructions: u32 = 0; + let mut updated_compute_unit_limit = None; + let mut updated_compute_unit_price = None; + let mut requested_heap_size = None; + let mut updated_loaded_accounts_data_size_limit = None; + + for (i, (program_id, instruction)) in instructions.enumerate() { + if compute_budget::check_id(program_id) { + let invalid_instruction_data_error = TransactionError::InstructionError( + i as u8, + InstructionError::InvalidInstructionData, + ); + let duplicate_instruction_error = TransactionError::DuplicateInstruction(i as u8); + + match try_from_slice_unchecked(&instruction.data) { + Ok(ComputeBudgetInstruction::RequestHeapFrame(bytes)) => { + if requested_heap_size.is_some() { + return Err(duplicate_instruction_error); + } + if sanitize_requested_heap_size(bytes) { + requested_heap_size = Some(bytes); + } else { + return Err(invalid_instruction_data_error); + } + } + Ok(ComputeBudgetInstruction::SetComputeUnitLimit(compute_unit_limit)) => { + if updated_compute_unit_limit.is_some() { + return Err(duplicate_instruction_error); + } + updated_compute_unit_limit = Some(compute_unit_limit); + } + Ok(ComputeBudgetInstruction::SetComputeUnitPrice(micro_lamports)) => { + if updated_compute_unit_price.is_some() { + return Err(duplicate_instruction_error); + } + updated_compute_unit_price = Some(micro_lamports); + } + Ok(ComputeBudgetInstruction::SetLoadedAccountsDataSizeLimit(bytes)) => { + if updated_loaded_accounts_data_size_limit.is_some() { + return Err(duplicate_instruction_error); + } + updated_loaded_accounts_data_size_limit = Some(bytes); + } + _ => return Err(invalid_instruction_data_error), + } + } else { + // only include non-request instructions in default max calc + num_non_compute_budget_instructions = + num_non_compute_budget_instructions.saturating_add(1); + } + } + + // sanitize limits + let updated_heap_bytes = requested_heap_size + .unwrap_or(u32::try_from(HEAP_LENGTH).unwrap()) // loader's default heap_size + .min(MAX_HEAP_FRAME_BYTES); + + let compute_unit_limit = updated_compute_unit_limit + .unwrap_or_else(|| { + num_non_compute_budget_instructions + .saturating_mul(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT) + }) + .min(MAX_COMPUTE_UNIT_LIMIT); + + let compute_unit_price = updated_compute_unit_price.unwrap_or(0); + + let loaded_accounts_bytes = updated_loaded_accounts_data_size_limit + .unwrap_or(MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES) + .min(MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES); + + Ok(ComputeBudgetLimits { + updated_heap_bytes, + compute_unit_limit, + compute_unit_price, + loaded_accounts_bytes, + }) +} + +fn get_compute_unit_price_from_message( + tx_cost: &mut UsageCostDetails, + message: &SanitizedMessage, +) { + // Iterate through instructions and search for ComputeBudgetInstruction::SetComputeUnitPrice + for (program_id, instruction) in message.program_instructions_iter() { + if compute_budget::check_id(program_id) { + if let Ok(ComputeBudgetInstruction::SetComputeUnitPrice(price)) = + try_from_slice_unchecked(&instruction.data) + { + // Set the compute unit price in tx_cost + tx_cost.compute_unit_price = price; + } + } + } +} + + +fn get_transaction_cost( + tx_cost: &mut UsageCostDetails, + message: &SanitizedMessage, + feature_set: &FeatureSet, +) { + let mut builtin_costs = 0u64; + let mut bpf_costs = 0u64; + let mut loaded_accounts_data_size_cost = 0u64; + let mut data_bytes_len_total = 0u64; + let mut compute_unit_limit_is_set = false; + + for (program_id, instruction) in message.program_instructions_iter() { + // to keep the same behavior, look for builtin first + if let Some(builtin_cost) = BUILT_IN_INSTRUCTION_COSTS.get(program_id) { + builtin_costs = builtin_costs.saturating_add(*builtin_cost); + } else { + bpf_costs = bpf_costs + .saturating_add(u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT)) + .min(u64::from(MAX_COMPUTE_UNIT_LIMIT)); + } + data_bytes_len_total = + data_bytes_len_total.saturating_add(instruction.data.len() as u64); + + if compute_budget::check_id(program_id) { + if let Ok(ComputeBudgetInstruction::SetComputeUnitLimit(_)) = + try_from_slice_unchecked(&instruction.data) + { + compute_unit_limit_is_set = true; + } + } + } + + // calculate bpf cost based on compute budget instructions + + // if failed to process compute_budget instructions, the transaction will not be executed + // by `bank`, therefore it should be considered as no execution cost by cost model. + match process_compute_budget_instructions(message.program_instructions_iter()) + { + Ok(compute_budget_limits) => { + // if tx contained user-space instructions and a more accurate estimate available correct it, + // where "user-space instructions" must be specifically checked by + // 'compute_unit_limit_is_set' flag, because compute_budget does not distinguish + // builtin and bpf instructions when calculating default compute-unit-limit. (see + // compute_budget.rs test `test_process_mixed_instructions_without_compute_budget`) + if bpf_costs > 0 && compute_unit_limit_is_set { + bpf_costs = u64::from(compute_budget_limits.compute_unit_limit); + } + + if feature_set + .is_active(&include_loaded_accounts_data_size_in_fee_calculation::id()) + { + loaded_accounts_data_size_cost = FeeStructure::calculate_memory_usage_cost( + usize::try_from(compute_budget_limits.loaded_accounts_bytes).unwrap(), + DEFAULT_HEAP_COST, + ) + } + } + Err(_) => { + builtin_costs = 0; + bpf_costs = 0; + } + } + + tx_cost.builtins_execution_cost = builtin_costs; + tx_cost.bpf_execution_cost = bpf_costs; + tx_cost.loaded_accounts_data_size_cost = loaded_accounts_data_size_cost; + tx_cost.data_bytes_cost = data_bytes_len_total / INSTRUCTION_DATA_BYTES_COST; +} + +const MAX_WRITABLE_ACCOUNTS: usize = 256; + +// costs are stored in number of 'compute unit's +#[derive(Debug)] +pub struct UsageCostDetails { + pub writable_accounts: Vec, + pub signature_cost: u64, + pub write_lock_cost: u64, + pub data_bytes_cost: u64, + pub builtins_execution_cost: u64, + pub bpf_execution_cost: u64, + pub loaded_accounts_data_size_cost: u64, + pub account_data_size: u64, + pub num_transaction_signatures: u64, + pub num_secp256k1_instruction_signatures: u64, + pub num_ed25519_instruction_signatures: u64, + pub compute_unit_price: u64, +} + +impl Default for UsageCostDetails { + fn default() -> Self { + Self { + writable_accounts: Vec::with_capacity(MAX_WRITABLE_ACCOUNTS), + signature_cost: 0u64, + write_lock_cost: 0u64, + data_bytes_cost: 0u64, + builtins_execution_cost: 0u64, + bpf_execution_cost: 0u64, + loaded_accounts_data_size_cost: 0u64, + account_data_size: 0u64, + num_transaction_signatures: 0u64, + num_secp256k1_instruction_signatures: 0u64, + num_ed25519_instruction_signatures: 0u64, + compute_unit_price: 0u64, + } + } +} + +#[cfg(test)] +impl PartialEq for UsageCostDetails { + fn eq(&self, other: &Self) -> bool { + fn to_hash_set(v: &[Pubkey]) -> std::collections::HashSet<&Pubkey> { + v.iter().collect() + } + + self.signature_cost == other.signature_cost + && self.write_lock_cost == other.write_lock_cost + && self.data_bytes_cost == other.data_bytes_cost + && self.builtins_execution_cost == other.builtins_execution_cost + && self.bpf_execution_cost == other.bpf_execution_cost + && self.loaded_accounts_data_size_cost == other.loaded_accounts_data_size_cost + && self.account_data_size == other.account_data_size + && self.num_transaction_signatures == other.num_transaction_signatures + && self.num_secp256k1_instruction_signatures + == other.num_secp256k1_instruction_signatures + && self.num_ed25519_instruction_signatures == other.num_ed25519_instruction_signatures + && to_hash_set(&self.writable_accounts) == to_hash_set(&other.writable_accounts) + } +} + /// A fee and its associated compute unit limit #[derive(Debug, Default, Clone, Eq, PartialEq)] @@ -39,6 +462,7 @@ impl FeeStructure { sol_per_write_lock: f64, compute_fee_bins: Vec<(u64, f64)>, ) -> Self { + trace!("Creating FeeStructure with sol_per_signature: {}", sol_per_signature); let compute_fee_bins = compute_fee_bins .iter() .map(|(limit, sol)| FeeBin { @@ -84,19 +508,38 @@ impl FeeStructure { budget_limits: &FeeBudgetLimits, include_loaded_account_data_size_in_fee: bool, ) -> u64 { - // Fee based on compute units and signatures - let congestion_multiplier = if lamports_per_signature == 0 { - 0.0 // test only - } else { - 1.0 // multiplier that has no effect - }; + trace!("Calculating fee with lamports_per_signature: {} and self.lamports_per_signature {} ", lamports_per_signature, self.lamports_per_signature); + trace!("Dump message: {:?}", message); + trace!("Dump keys: {:?}", message.account_keys()); + + let vote_program_id = &solana_sdk::vote::program::id(); + let contains_vote_program = message.account_keys().iter().any(|key| key == vote_program_id); + + + // Query the current Unix time and reduce it to a value between 1 and 100 + // let current_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards"); + // let unix_time = current_time.as_secs(); // You can use `as_millis()` if higher precision is needed + + // Use mod 100 and add 1 to ensure the result is between 1 and 100 + // let congestion_multiplier = (unix_time % 100 + 1) as f64 / 1.0; + + let mut tx_cost = UsageCostDetails::default(); + get_signature_cost_from_message(&mut tx_cost, &message); + get_write_lock_cost(&mut tx_cost, message, &FeatureSet::default()); + get_transaction_cost(&mut tx_cost, message, &FeatureSet::default()); + get_compute_unit_price_from_message(&mut tx_cost, &message); + + trace!("UsageCostDetails {:?}", tx_cost); let signature_fee = message .num_signatures() - .saturating_mul(self.lamports_per_signature); + .saturating_mul(lamports_per_signature); + trace!("Calculated signature_fee: {}", signature_fee); + let write_lock_fee = message .num_write_locks() .saturating_mul(self.lamports_per_write_lock); + trace!("Calculated write_lock_fee: {}", write_lock_fee); // `compute_fee` covers costs for both requested_compute_units and // requested_loaded_account_data_size @@ -108,8 +551,13 @@ impl FeeStructure { } else { 0_u64 }; + trace!("Calculated loaded_accounts_data_size_cost: {}", loaded_accounts_data_size_cost); + let total_compute_units = loaded_accounts_data_size_cost.saturating_add(budget_limits.compute_unit_limit); + + trace!("total_compute_units {}", total_compute_units); + let compute_fee = self .compute_fee_bins .iter() @@ -121,20 +569,56 @@ impl FeeStructure { .map(|bin| bin.fee) .unwrap_or_default() }); + trace!("Calculated compute_fee: {}", compute_fee); - ((budget_limits + /* + let mut total_fee = ((budget_limits .prioritization_fee .saturating_add(signature_fee) .saturating_add(write_lock_fee) .saturating_add(compute_fee) as f64) * congestion_multiplier) - .round() as u64 + .round() as u64; + */ + + let derived_cu = tx_cost.builtins_execution_cost + .saturating_add(tx_cost.bpf_execution_cost); + + //let mut total_fee = (derived_cu * 10) // base fee calculation CU * M (10) + // .saturating_add (derived_cu * budget_limits.prioritization_fee) + // as u64; + + + let adjusted_compute_unit_price = if derived_cu < 1000 && tx_cost.compute_unit_price < 1_000_000 { + 1_000_000 + } else { + tx_cost.compute_unit_price + }; + + let mut total_fee = derived_cu + .saturating_mul(10) // ensures multiplication doesn't overflow + .saturating_add(derived_cu.saturating_mul(adjusted_compute_unit_price as u64) + .saturating_div(1_000_000)); // change to 1_000_000 to convert to micro lamports + + + // derived_cu * (10 + budget_limits.prioritization_fee) + + // If the message contains the vote program, set the total fee to 0 + if contains_vote_program { + trace!("Vote program detected, setting total_fee to 0"); + total_fee = 0; + } else { + trace!("Calculated total_fee: {} with compute units: {} compute unit price {}", total_fee, derived_cu, tx_cost.compute_unit_price); + } + + + total_fee } } impl Default for FeeStructure { fn default() -> Self { - Self::new(0.000005, 0.0, vec![(1_400_000, 0.0)]) + Self::new(0.000000005, 0.0, vec![(1_400_000, 0.0)]) } }